Skip to content

System Architecture

IMPORTANT

TL;DR — LoopTroop is a local control plane: a React browser client talks to a Hono backend, durable state lives in SQLite plus .ticket/** artifacts, each ticket executes in its own git worktree, and all model work goes through OpenCode behind a session-ownership layer. On restart, the backend rebuilds runtime projections, hydrates ticket actors, and reconnects only the sessions it still owns.

This document is the canonical architecture reference for the current LoopTroop application.

LoopTroop is not a thin chat wrapper around a coding model. It is a long-running workflow system with explicit planning phases, durable storage, isolated execution worktrees, resumable ticket actors, and restart-aware OpenCode session ownership. The core architectural rule is simple: important state must survive the model and survive the browser.

1. Mental Model

LoopTroop operates as a layered local system:

  1. The browser runs a React SPA plus local providers for UI state, log caching, and AI question handling.
  2. A Hono API owns the REST and SSE boundary and applies auth, rate limiting, and JSON validation before requests reach workflow code.
  3. XState ticket actors hold the live workflow state machine, while the workflow runner dispatches the phase-specific handlers.
  4. Council orchestration and structured-output normalization turn raw model text into bounded, typed artifacts before anything becomes canonical.
  5. Durable truth lives in SQLite, .ticket/** artifacts, runtime projections, and JSONL logs rather than in model transcripts.
  6. OpenCode sessions do the model work inside isolated ticket worktrees, and Git/GitHub delivery turns the resulting change set into a PR outcome.

2. Runtime Actors

ActorResponsibilityPrimary modules
Browser shellApp bootstrap, modal/URL coordination, dashboard vs ticket workspace switching, startup noticessrc/main.tsx, src/App.tsx, src/components/layout/*
Browser state providersPersistent UI state, AI question queue, per-ticket log cache and recoverysrc/context/UIContext.tsx, src/context/AIQuestionContext.tsx, src/context/LogContext.tsx
React Query + SSE hooksFetching, cache invalidation, replay recovery, startup-status fetchessrc/hooks/*, especially useTickets.ts, useTicketArtifacts.ts, useSSE.ts, useStartupStatus.ts
Hono APIREST routes and SSE endpoint under /apiserver/index.ts, server/routes/*
API guard railsCORS, token auth, per-bucket rate limiting, JSON validationserver/middleware/*
Ticket state machineCanonical ticket statuses, legal transitions, blocked-error resume rulesserver/machines/ticketMachine.ts, server/machines/types.ts
Actor persistence and hydrationSnapshot reconciliation, safe restore, startup hydration of non-terminal ticketsserver/machines/persistence.ts, server/startup.ts
Phase orchestratorsPlanning, approval, execution, retry, cleanup, and delivery logicserver/workflow/*, server/phases/*
Council + structured output layerDraft/vote/refine orchestration, tagged-output parsing, schema normalization, retry diagnosticsserver/council/*, server/structuredOutput/*, server/phases/parserTaggedStructuredOutput.ts, server/lib/structuredOutput*.ts
OpenCode integrationSession creation, prompting, event/question translation, ownership validation, blocked-error diagnosticsserver/opencode/*
SSE broadcaster + execution loggingTicket-scoped fan-out, replay buffer, durable log ingestion, session-status log translationserver/sse/broadcaster.ts, server/log/*, server/workflow/sessionStatusLogging.ts
App databaseSingleton profile, attached projects, startup UI metaserver/db/index.ts, server/db/init.ts
Project database + ticket storageTickets, artifacts, phase attempts, OpenCode sessions, error history, runtime projectionsserver/db/project.ts, server/storage/*
Git and GitHub layerWorktrees, diffs, commits, PR creation, merge/close flowsserver/phases/execution/gitOps.ts, server/git/*
Startup bootstrapDatabase init, crash recovery, WSL/runtime diagnostics, actor hydration, session reconnectserver/startup.ts, server/startupState.ts, server/runtime.ts

3. Authoritative Data Ownership

LoopTroop deliberately splits state across several storage layers. Each layer owns a different class of truth.

Storage locationCanonical contentsNotes
~/.config/looptroop/app.sqlite by defaultSingleton profile, attached projects, app meta such as startup restore-notice dismissalConfigurable via LOOPTROOP_CONFIG_DIR or LOOPTROOP_APP_DB_PATH
<project>/.looptroop/db.sqliteTickets, runtime metadata, OpenCode session ownership, phase_artifacts, ticket_phase_attempts, status history, error occurrencesThis is the project-local operational database
<project>/.looptroop/worktrees/<ticket>/The isolated ticket worktree used for planning artifacts, runtime files, and code changesStartup blocks if .looptroop is tracked by Git so stale runtime data cannot be checked out into new worktrees; .ticket/** stays local LoopTroop state and is excluded from bead commits and PR diffs
.ticket/relevant-files.yamlRelevant-file scan output used by later planning phasesReplaces older codebase-map.yaml terminology
.ticket/interview.yaml and .ticket/prd.yamlEditable review artifacts for the approved planning stagesThese are user-facing canonical documents
.ticket/beads/<flow>/.beads/issues.jsonlThe current bead plan for a given flow or base branchStored as JSONL, rewritten atomically on updates
.ticket/runtime/execution-log.jsonl, .debug.jsonl, .ai.jsonlDurable workflow, debug, and AI-detail log channelsThe UI consumes these both live through SSE and on reload through /api/files/:ticketId/logs
.ticket/runtime/state.yamlDerived runtime projection for the active non-terminal ticketRebuilt from ticket state on startup; convenient to inspect, but not the only source of truth
.ticket/runtime/execution-setup-profile.jsonConcrete execution environment profile produced after approved setup runsSeparate from the reviewable execution setup plan artifact
.ticket/runtime/execution-setup/**, especially tool-cache/Ticket-owned temp roots, wrapper outputs, execution-only toolchains, and reusable cachesPreserved across setup-plan rewinds when safe so retries do not throw away valid tool caches
phase_artifacts tableStructured snapshots used by the API and UIHolds artifact content, phase, attempt number, timestamps, approval receipts, edit receipts, cleanup/integration reports, and content hashes

Note SQLite and the filesystem are complementary, not redundant. The database is optimized for querying, ownership, and workflow bookkeeping; .ticket/** keeps artifacts inspectable, editable, and recoverable without polluting the target repository branch.

4. End-to-End Ticket Lifecycle

  1. A ticket starts in DRAFT with editable title, description, and priority.
  2. SCANNING_RELEVANT_FILES creates relevant-files.yaml from the ticket description and repo context.
  3. The interview council drafts, votes, refines the interview artifact, and iterates until interview coverage is good enough.
  4. The user approves the interview artifact.
  5. The PRD council drafts, votes, refines, and coverage-checks the spec.
  6. The user approves the PRD artifact.
  7. The beads council drafts, votes, refines, expands, and coverage-checks the execution plan.
  8. The user approves the beads artifact and then reviews the pre-implementation execution setup plan.
  9. Implementation runs bead by bead in an isolated ticket worktree, with bounded retry per bead.
  10. Post-implementation final testing, file-effects auditing, integration, PR creation, review follow-up, and cleanup drive the ticket to COMPLETED, CANCELED, or BLOCKED_ERROR.

The full phase map lives in Ticket Flow & State Machine.

5. Planning Flow

Planning is intentionally artifact-driven.

StagePrimary inputPrimary outputWhy it exists
Discovery scanTicket detailsrelevant-files.yamlGrounds planning in the actual codebase
Interview councilTicket details, relevant filesInterview document and answer sessionForces ambiguity out before specs
PRD councilTicket details, interview, relevant files, member-specific Full AnswersPRD documentProduces the feature contract
Beads councilTicket details, PRD, relevant filesExecution bead planConverts the spec into execution units
Execution setup planningTicket details, PRD, beads, execution profileReviewable setup planMakes the coding environment explicit before code changes begin

The planning phases are not one long conversation. Each stage assembles a new context window from durable artifacts and runs in its own session scope.

Councils are a reusable subsystem, not bespoke logic embedded in each phase. server/council/pipeline.ts handles the common draft -> quorum -> vote -> refine shape, while each phase provides its own context and normalization rules.

Structured output is a hard boundary. server/structuredOutput/* and server/phases/parserTaggedStructuredOutput.ts normalize, validate, and optionally repair model output before anything becomes canonical artifact content. Rejected or uncorrectable responses are preserved as diagnostics and raw attempts so downstream phases never consume malformed text as if it were approved state.

Human approval gates are content-addressed. The API exposes the current artifact hash for interview, PRD, beads, and execution setup plan views; approval requests must send expectedContentSha256; stale approvals return 409 instead of approving bytes the user did not review. Approval snapshots and receipts keep the reviewed raw content plus content_sha256, and interview/PRD receipts also record the post-stamp stored hash when approval metadata changes the YAML.

6. Execution Flow

Execution is built around beads, not around one monolithic coding prompt.

  1. PRE_FLIGHT_CHECK verifies the ticket can enter pre-implementation setup, including worktree cleanliness before setup starts.
  2. WAITING_EXECUTION_SETUP_APPROVAL pauses for setup-plan review before setup commands run.
  3. PREPARING_EXECUTION_ENV creates the temporary execution environment described by the approved setup plan, provisioning missing required tooling under ticket-owned runtime roots, exposing setup-scoped OpenCode websearch/webfetch for unresolved official launcher artifact lookup, validating declared wrappers and tooling_probe_commands, requiring tool_requirements.provisioning_attempts evidence for failed launcher provisioning, and rejecting ready results that leave committable project changes behind.
  4. CODING selects the next runnable bead from the scheduler.
  5. executeBead() starts or reattaches to the owned OpenCode session for that bead attempt.
  6. The model must emit the expected structured bead status markers. Missing or malformed markers trigger a structured retry path instead of silently progressing.
  7. If the attempt stalls or fails, LoopTroop generates a context wipe note, resets the worktree to the bead start commit, and retries in fresh context.
  8. When the bead succeeds, LoopTroop finalizes it locally before marking it done: changed work must be committed, true no-op work may complete without a commit, push failures are warnings, and fatal finalization failures route to BLOCKED_ERROR.
  9. RUNNING_FINAL_TEST, INTEGRATING_CHANGES, and CREATING_PULL_REQUEST package the result for post-implementation delivery, with final-test commands automatically reusing a validated setup wrapper and PR creation auditing the final candidate files before anything is pushed.
  10. During all non-terminal execution states, runtime projections and execution logs are updated so a restarted backend or reloaded browser can restore the ticket from durable state rather than from memory.

See Beads & Execution.

7. Recovery Flow

Recovery is a first-class architectural concern.

Failure typeRecovery strategy
Browser reload, close, or reconnect gapREST state remains canonical; the browser keeps the last SSE event id, restores best-effort log cache detail, replays buffered live events, and then refetches tickets, artifacts, bead state, interview state, and matching server logs
Frontend crash or tab closeInterview drafts, approval drafts, and browser-cached logs are persisted locally and flushed on unload with best-effort keepalive behavior
Crash during atomic write or appendStartup promotes orphan .tmp files, repairs trailing corrupt JSONL lines when safe, and rebuilds runtime projections
Invalid model outputRetry with repair or explicit re-prompt, depending on phase
Bead execution stallGenerate context wipe note, reset worktree, retry in fresh session
OpenCode reconnect gapValidate the exact owned session against remote sessions and recreate only when ownership can no longer be proven
Backend process restartReconcile persisted XState snapshots, hydrate ticket actors from durable ticket state, and immediately process restored active snapshots
User edits approved interview or PRDArchive the active approved generation and downstream attempts, cancel downstream sessions intentionally, clear stale downstream artifacts/UI state, persist a user_edit_receipt:*, and restart from the next drafting phase
User edits or regenerates setup plan during runtime setupStop active runtime setup, archive both setup-plan and runtime attempts, preserve the tool cache when safe, clear stale setup outputs, and return to WAITING_EXECUTION_SETUP_APPROVAL for fresh approval
Stale approvalReturn 409 with the expected and current SHA-256 hashes, keeping the ticket at the approval gate
Bead finalization failureKeep the bead retryable, avoid bead_complete, send BEAD_ERROR with BEAD_FINALIZATION_FAILED, and route to BLOCKED_ERROR
Cleanup warningPersist a cleanup_report with status: warning, expose the cleanup summary on the ticket, and still complete the ticket
Terminal blockageEnter BLOCKED_ERROR with persisted error occurrence history

LoopTroop tries hard to preserve the work product while discarding the bad conversational state that produced the failure.

If a resume point cannot be proven, recovery stops at BLOCKED_ERROR instead of continuing execution against unknown state. BLOCKED_ERROR retry requires a preserved previousStatus; CODING retry also requires a successful reset to the failed bead's beadStartCommit.

8. Restart And Session Ownership

LoopTroop pairs persisted ticket snapshots with OpenCode session ownership records so it can decide whether a remote session still belongs to the exact workflow slot that wants to use it.

Ownership keys can include:

  • ticketId
  • phase
  • phaseAttempt
  • memberId
  • beadId
  • iteration
  • step

This lets LoopTroop safely reconnect in cases like:

  • server restart while a ticket is still in the same phase
  • phase retry that should resume the currently owned session
  • multi-model council phases where each member has its own session identity
  • bead execution where iteration and bead identity both matter

Reconnect deliberately does not mean "resume any random old transcript." Validation succeeds only if the ticket is still in the same workflow state, the project database still records that ownership slot as active, and the exact remote session still exists.

Snapshot restore is equally defensive. server/machines/persistence.ts reconciles persisted XState snapshots with the ticket row; if the snapshot is missing required structure, cannot be reconciled, or would resume from an unprovable state, the ticket is rebuilt conservatively or moved to BLOCKED_ERROR instead of guessing.

Prompt acquisition is bounded by timeout and abort signals. OpenCode create, list, getSession, and message-read calls are guarded so an OpenCode restart cannot indefinitely block the workflow runner.

9. Module Map

Frontend

AreaModules
App bootstrap and query clientsrc/main.tsx, src/lib/queryClient.ts
App shell, modal routing, startup overlayssrc/App.tsx, src/components/layout/*, src/components/shared/StartupRestorePopup.tsx, src/components/shared/WelcomeDisclaimer.tsx
Ticket workspace and shared UIsrc/components/ticket/*, src/components/workspace/*, src/components/shared/*
Browser state providerssrc/context/UIContext.tsx, src/context/AIQuestionContext.tsx, src/context/LogContext.tsx
Data hooks and live updatessrc/hooks/useTickets.ts, useTicketArtifacts.ts, useTicketPhaseAttempts.ts, useWorkflowMeta.ts, useSSE.ts, useStartupStatus.ts, useRecoveryAutoReload.ts

API Surface

AreaModules
App entry and route mountingserver/index.ts
Middleware and request guardsserver/middleware/apiAuth.ts, rateLimit.ts, validation.ts
Ticket routes and modular ticket handlersserver/routes/tickets.ts, server/routes/ticketHandlers/*
Files, beads, streamingserver/routes/files.ts, beads.ts, stream.ts
Profile, projects, health, models, workflow metaserver/routes/profiles.ts, projects.ts, health.ts, models.ts, workflow.ts

Ticket Orchestration

AreaModules
Ticket status machineserver/machines/ticketMachine.ts, server/machines/types.ts
Actor persistence and restoreserver/machines/persistence.ts
Workflow runner and phase dispatchserver/workflow/runner.ts, server/workflow/phases/*
Planning phasesserver/phases/interview/*, server/phases/prd/*, server/phases/beads/*, server/phases/executionSetupPlan/*
Execution and delivery phasesserver/phases/preflight/*, server/phases/executionSetup/*, server/phases/execution/*, server/phases/finalTest/*, server/phases/integration/*, server/phases/cleanup/*, server/phases/verification/*
Ticket initialization and relevant-file preparationserver/ticket/create.ts, server/ticket/initialize.ts, server/ticket/relevantFiles.ts, server/ticket/metadata.ts

Council And Structured Output

AreaModules
Reusable council pipelineserver/council/pipeline.ts, drafter.ts, voter.ts, refiner.ts, quorum.ts
Structured-output schemas and normalizersserver/structuredOutput/*
Tagged marker extraction and repair-aware parsingserver/phases/parserTaggedStructuredOutput.ts
Retry policy, raw-attempt capture, prompt echo detectionserver/lib/structuredOutputRetry.ts, structuredRawAttempts.ts, structuredRetryDiagnostics.ts, promptEcho.ts

Persistence

AreaModules
App DB connection and schema bootstrapserver/db/index.ts, server/db/init.ts
Project DB bootstrap and schemaserver/db/project.ts, server/db/schema.ts
Project attach, repo-path normalization, local exclude setupserver/storage/projects.ts, server/storage/paths.ts, server/git/repository.ts
Ticket artifact and attempt storageserver/storage/ticketArtifacts.ts, ticketPhaseAttempts.ts, ticketMutations.ts, ticketQueries.ts
Ticket runtime projectionserver/storage/ticketRuntimeProjection.ts

Observability And Recovery

AreaModules
SSE replay and fan-outserver/sse/broadcaster.ts, server/routes/stream.ts
Execution log ingestion and dedupeserver/log/executionLog.ts, readDedupe.ts, commandLogger.ts
Startup bootstrap and restore stateserver/startup.ts, server/startupState.ts, server/runtime.ts
Crash-safe IO and recoveryserver/io/atomicWrite.ts, atomicAppend.ts, jsonl.ts, recovery.ts

OpenCode Integration

AreaModules
Adapter and factoryserver/opencode/adapter.ts, server/opencode/factory.ts
Context assemblyserver/opencode/contextBuilder.ts
Session ownership and reconnectserver/opencode/sessionManager.ts
Prompt runner and phase bridgeserver/workflow/runOpenCodePrompt.ts
Question handling and blocked-error mappingserver/routes/ticketHandlers/openCodeQuestionHandlers.ts, server/opencode/blockedErrorDiagnostics.ts

10. ASCII Overview

text
User
  |
  v
React SPA + browser providers
  |  REST (/api/*)                         SSE (/api/stream)
  |-------------------------------------> Hono API <--------------------+
  |                                         |                           |
  |                                         v                           |
  |                                 auth / rate limit / validation      |
  |                                         |                           |
  |                                         v                           |
  |                                  routes + ticket handlers           |
  |                                         |                           |
  |                                         v                           |
  |                             ticket machine + workflow runner        |
  |                               |            |            |           |
  |                               |            |            |           |
  |                               v            v            v           |
  |                         council pipeline  phase logic  OpenCode     |
  |                               |                         adapter      |
  |                               v                            |         |
  |                     structured output layer                v         |
  |                                                            OpenCode |
  |                                                              server |
  |                                                                 |   |
  |                                                                 v   |
  |                                                          provider models
  |
  +<--------------------------- SSE broadcaster <-------------------+
                                ^            ^
                                |            |
                        project DB      runtime logs/state
                                ^            ^
                                |            |
                        app DB / project DB / ticket worktree /.ticket/**

Startup bootstrap runs before normal traffic:
  initialize DB -> recover temp/log files -> rebuild runtime projections ->
  hydrate ticket actors -> reconnect owned OpenCode sessions

11. Detailed Mermaid Diagram

12. Startup State System

On startup, LoopTroop restores durable state through server/startup.ts and server/startupState.ts. The workflow runner hydrates ticket actors from the project database, and the OpenCode layer attempts to reconnect only sessions that still match a valid ownership record.

Bootstrap Sequence

startupSequence() performs the runtime bootstrap in this order:

  1. Initialize the app/project databases and create runtime indexes.
  2. Classify the startup storage state and capture runtime diagnostics such as WSL mounted-drive warnings.
  3. Recover ticket runtime artifacts by promoting orphan .tmp files, repairing trailing JSONL corruption where safe, and rebuilding .ticket/runtime/state.yaml projections.
  4. Start the WAL checkpoint timer and probe OpenCode health.
  5. Hydrate XState actors for non-terminal tickets from attached project databases.
  6. Validate and reconnect active OpenCode sessions; records that no longer map to a live owned session are marked abandoned.

Startup Classification

classifyStartupStorageKind() determines the current storage condition:

KindMeaning
freshFirst-ever startup — no prior app database exists.
empty_existingApp database exists but has no attached projects.
restoredDatabase found with existing projects — full state restoration.

Restore Flow

  1. initializeStartupState() reads the app database path, profile count, and attached project count, then persists the startup classification.
  2. getStartupStatus() exposes the cached startup snapshot through GET /api/health/startup.
  3. The frontend uses useStartupStatus() and StartupRestorePopup to show restore context after a real restore.
  4. dismissStartupRestoreNotice() persists the user's dismissal in app metadata so the restore notice does not keep reappearing.

Session recovery is best-effort. If OpenCode is unavailable during startup, ticket actors are still hydrated from durable workflow state and later phase work either reconnects, creates fresh owned sessions, or blocks with a persisted error.

The startup health endpoint exposes the storage path, kind, source, runtime warning state, restored project list, dismissed state, and human-readable summary for diagnostics and UI messaging.

13. IO Utilities

The IO layer provides crash-safe file operations and recovery used by the workflow engine. All modules live in server/io/.

ModulePurposeKey Export
atomicWrite.tsCrash-safe file writessafeAtomicWrite(filePath, content) — writes to a .tmp file, calls fsync, renames to the target path, then best-effort fsyncs the parent directory. Prevents partial overwrites on system failure.
atomicAppend.tsCrash-safe line appendssafeAtomicAppend(filePath, line) — opens with the a+ flag, checks trailing newline, adds a \n prefix when needed, then calls fsync. Used for durable JSONL log appends.
jsonl.tsJSON Lines I/OreadJsonl<T>(), writeJsonl<T>(), appendJsonl<T>() — type-safe JSONL read/write/append with graceful malformed-line skipping and newline integrity.
recovery.tsCrash recoveryrecoverOrphanTmpFiles(folder) — recursively promotes .tmp files to their target paths after a crash; fixTrailingLineCorruption(filePath) — validates and truncates trailing corrupt JSONL lines (scans backward in 8KB chunks, stays under 4MB scan limit).

These utilities form the durability backbone: atomic writes protect mutable state files (YAML and JSON artifacts), atomic appends protect append-only logs, and recovery handles the edge case where a process stops mid-write.

14. Session Status Logging

OpenCode session status events are translated into normalized log entries by server/workflow/sessionStatusLogging.ts. Each entry captures a retry event or phase change as a structured execution-log record so live SSE views and reload-time log reads converge on the same timeline.

buildSessionStatusLogEntries() converts OpenCode SessionStatusStreamEvent objects into SessionStatusLogEntry[] — ordered, typed log entries with:

FieldMeaning
idStable entry identifier
typeinfo or error
kindsession or error
opappend, upsert, or finalize — determines how the log viewer merges this entry
contentHuman-readable description of the status event

The log builder handles retry status events (rate limits, usage limits, timeouts, transport errors) and session phase transitions. These entries feed the normal execution log alongside phase log entries, while the separate AI-detail log keeps prompt/tool-call depth when that channel is needed.

LoopTroop documentation for the current runtime.