# Backend Code Structure This document describes package ownership for the Go backend. It is about where code belongs. See [architecture.md](architecture.md) for lifecycle behavior, status derivation, persistence, CDC, and invariants. ## Goal The backend is a local daemon that supervises coding-agent sessions. The code needs clear homes for product workflows, protocol surfaces, persistence, and replaceable external systems without turning any single package into a catch-all. The current structure is a layered hybrid: ``` domain → shared product vocabulary and durable fact records service/* → controller-facing product use cases and read models session_manager → internal session mutations and resource orchestration lifecycle → durable session fact reducer ports → narrow capability interfaces consumed by core code adapters/* → implementations of those capabilities storage/sqlite → persistence and change delivery httpd → HTTP protocol concerns cli → CLI protocol concerns daemon → production composition root ``` ## Package Architecture Overview ```mermaid graph TB subgraph EntryPoints["Entry Points"] CLI["cmd/ao
CLI entrypoint"] Main["main.go
Daemon entrypoint"] end subgraph CoreLayer["Core Layer"] Domain["domain
Shared vocabulary"] Ports["ports
Capability interfaces"] Services["service/*
Product use cases"] SessionMgr["session_manager
Internal commands"] Lifecycle["lifecycle
Fact reducer"] end subgraph AdapterLayer["Adapter Layer"] AgentAdapters["adapters/agent/*
23+ agents"] RuntimeAdapters["adapters/runtime/*
tmux + conpty"] WorkspaceAdapters["adapters/workspace/*
git worktree"] SCMAdapters["adapters/scm/*
GitHub"] TrackerAdapters["adapters/tracker/*
GitHub"] end subgraph Infrastructure["Infrastructure"] Storage["storage/sqlite
Persistence"] CDC["cdc
Change delivery"] Terminal["terminal
PTY protocol"] HTTPD["httpd
HTTP API"] CLI2["cli
CLI commands"] Daemon["daemon
Composition root"] Config["config
Configuration"] end CLI --> CLI2 Main --> Daemon Daemon --> Services Daemon --> AdapterLayer Daemon --> Infrastructure Services --> Domain Services --> Ports Services --> SessionMgr SessionMgr --> Ports SessionMgr --> Lifecycle AdapterLayer --> Ports AdapterLayer --> Domain HTTPD --> Services CLI2 --> HTTPD Services --> Storage Lifecycle --> Storage Storage --> CDC Terminal --> RuntimeAdapters HTTPD --> Terminal ``` ## Package Relationships ```mermaid graph LR Controllers["httpd/controllers"] -->|calls| Services["service/*"] Controllers2["cli commands"] -->|calls| HTTP["httpd"] Services -->|orchestrates| SessionMgr["session_manager"] Services -->|queries| Storage["storage/sqlite"] SessionMgr -->|uses| Runtime["ports.Runtime"] SessionMgr -->|uses| Workspace["ports.Workspace"] SessionMgr -->|uses| Agent["ports.Agent"] SessionMgr -->|reports to| Lifecycle["lifecycle"] Runtime -->|implemented by| Tmux["adapters/runtime/tmux"] Runtime -->|implemented by| Conpty["adapters/runtime/conpty"] Lifecycle -->|persists to| Storage Storage -->|triggers| CDC["cdc"] CDC -->|broadcasts to| Frontend["Frontend subscribers"] ``` ## Package Roles ### `internal/domain` `domain` is AO's shared product language. Keep it stable and free of infrastructure imports. **Belongs here:** - Shared IDs: `ProjectID`, `SessionID`, `IssueID` - Shared enums and status vocabulary - Durable fact records that multiple packages must agree on - PR, tracker, project, and session vocabulary (not transport-specific) **Does not belong here:** - HTTP request/response DTOs - CLI output shapes - OpenAPI wrapper/envelope types - sqlc generated rows - GitHub, tmux, Claude, Codex, or OpenCode payloads - One-resource controller helper types **Rule of thumb:** If AO would still use the concept after replacing HTTP, the CLI, SQLite, GitHub, the tmux/conpty runtime, and every agent adapter, and more than one package needs the exact vocabulary, it may belong in `domain`. ### `internal/service/*` `service` packages are the controller-facing application boundary. **Belongs here:** - Resource use cases called by HTTP controllers and CLI-backed API flows - Resource read models and command/result types - Display-model assembly (session status derived from session and PR facts) - Resource-specific validation and user-facing errors - Small store interfaces consumed by the service **Does not belong here:** - Low-level runtime/workspace/agent process control - Raw sqlc generated rows as public service results - HTTP routing, path parsing, status-code decisions, or OpenAPI generation - Concrete external adapter details ### `internal/session_manager` `session_manager` owns internal session commands: spawn, restore, kill, cleanup, and send-related orchestration over runtime, workspace, agent, storage, messenger, and lifecycle dependencies. **Belongs here:** - Multi-step session mutations - Rollback/cleanup sequencing when spawn partially succeeds - Resource teardown safety - Internal errors (not found, terminated, not restorable) **Does not belong here:** - HTTP request decoding - CLI formatting - Controller-facing list/get read-model assembly - Terminal WebSocket framing The split is intentional: `service/session` is the product/API boundary; `session_manager` is the internal command engine. ### `internal/lifecycle` `lifecycle` is the canonical write path for durable session lifecycle facts. It reduces runtime observations, activity signals, spawn completion, termination, and PR observations into small persisted facts. **Belongs here:** - Updates to lifecycle-owned session facts - Guardrails around runtime/activity observations - Lifecycle-triggered agent nudges for actionable PR facts **Does not belong here:** - Display status persistence - HTTP/CLI DTOs - Direct adapter implementation details - PR row persistence ### `internal/ports` `ports` contains narrow capability interfaces and shared adapter-facing structs. It connects core code to replaceable systems. **Capability interfaces:** - `Runtime` — Create/Destroy/IsAlive for tmux/conpty sessions - `Workspace` — Git worktree creation/destruction - `Agent` — Agent launch, restore, hooks, session info - `Attacher` — Terminal streaming attachment - `PRWriter` — PR fact persistence - `AgentResolver` — Agent binary resolution - `AgentMessenger` — Message delivery to agents **Belongs here:** - Interfaces consumed by core packages and implemented by adapters - Capability structs: `RuntimeConfig`, `WorkspaceConfig`, `SpawnConfig` - Vocabulary needed at the boundary between core orchestration and adapters **Does not belong here:** - Resource read models (project/session API responses) - HTTP request/response DTOs - sqlc rows - Concrete adapter options - One-off interfaces that only a single package needs internally ### `internal/adapters/*` Adapters are concrete implementations of external systems. They should be leaves in the import graph. **Runtime Adapters:** ```mermaid graph LR RunSelect["runtimeselect"] -->|Darwin/Linux| Tmux["tmux
Unix PTY integration"] RunSelect -->|Windows| Conpty["conpty
ConPTY + B1 protocol"] Tmux -->|implements| RuntimePort["ports.Runtime"] Conpty -->|implements| RuntimePort Tmux -->|uses| PtyExec["ptyexec
Unix PTY spawning"] Conpty -->|uses| PtyHost["Pty-host server
Ring buffer + Registry"] ``` **Current adapters:** ```txt internal/adapters/agent/claudecode internal/adapters/agent/codex internal/adapters/agent/opencode internal/adapters/runtime/tmux # Darwin/Linux internal/adapters/runtime/conpty # Windows internal/adapters/runtime/runtimeselect # Platform selector internal/adapters/runtime/ptyexec # PTY spawning internal/adapters/workspace/gitworktree internal/adapters/scm/github internal/adapters/tracker/github internal/adapters/reviewer/claudecode ``` **Good dependencies:** ``` session_manager → ports.Runtime adapters/runtime/tmux → ports + domain adapters/workspace/gitworktree → ports + domain daemon → adapters + services + storage ``` **Avoid:** ``` domain → adapters service/session → adapters/runtime/tmux httpd/controllers → storage/sqlite/store adapters/* → httpd ``` ### `internal/storage/sqlite` `storage/sqlite` owns SQLite setup, migrations, sqlc generated code, and store implementations. **Belongs here:** - Connection setup and PRAGMAs - Goose migrations - sqlc queries and generated code - Table-specific store methods - Transactions and CDC-triggered persistence behavior **Does not belong here:** - HTTP response types - CLI output formatting - Product display status rules - External adapter logic Generated sqlc types should stay behind store methods. Services and lifecycle code should work with domain records or service read models, not generated rows. ### `internal/cdc` `cdc` owns `change_log` polling and event broadcasting. SQLite triggers append durable events to `change_log`; the poller tails that table and fans events out to subscribers. **Belongs here:** - Event type definitions for the CDC stream - Poller and broadcaster logic - Subscriber fan-out behavior **Does not belong here:** - Terminal byte streams - Product workflow decisions - Database schema ownership ### `internal/terminal` `terminal` owns the terminal session protocol and PTY attach management used by the HTTP mux. The runtime is selected by `runtimeselect`: tmux on Darwin/Linux, conpty on Windows. ```mermaid sequenceDiagram participant WS as WebSocket Client participant Mux as Terminal Mux participant Term as terminal package participant Runtime as Runtime Adapter WS->>Mux: WebSocket upgrade Mux->>Term: Create attach session Term->>Runtime: Runtime.Attach(handle, rows, cols) alt tmux (Darwin/Linux) Runtime->>Runtime: tmux attach-session -t Runtime->>Runtime: PTY stream via ptyexec else conpty (Windows) Runtime->>Runtime: Dial loopback TCP Runtime->>Runtime: B1 protocol handshake Runtime->>Runtime: MsgTerminalData (scrollback) end Runtime-->>Term: Stream Term-->>Mux: Framed protocol Mux-->>WS: WebSocket messages ``` **Belongs here:** - Per-client attachment lifecycle (liveness gating, re-attach backoff) - Input/output framing independent of HTTP - PTY-backed attach handling and terminal protocol tests ### `internal/httpd` `httpd` is the HTTP protocol adapter. **Belongs here:** - Routing and middleware - HTTP request decoding and response encoding - Path/query parameter handling - Status-code mapping - API error envelopes - OpenAPI generation and serving - WebSocket upgrade handling for terminal mux Controllers call service managers and translate service results/errors into HTTP responses. Controllers should not reach directly into concrete adapters or the SQLite store. ### `internal/cli` `cli` owns the user-facing `ao` command. It should stay thin: - Discover the local daemon - Call the daemon's loopback HTTP API - Format command output - Start/stop/status/doctor process control The CLI should not duplicate daemon business logic. If a command needs product behavior, put the behavior in the daemon service/API path and have the CLI call that path. ### `internal/daemon` `daemon` is the production composition root. It wires config, logging, SQLite, CDC, lifecycle, reaper, runtime, terminal manager, services, HTTP, and shutdown. ```mermaid graph TD Daemon["daemon package"] --> Config["config"] Daemon --> Logging["slog logger"] Daemon --> Storage["storage/sqlite"] Daemon --> CDC["cdc"] Daemon --> Lifecycle["lifecycle"] Daemon --> Reaper["observe/reaper"] Daemon --> Runtime["runtimeselect"] Daemon --> Terminal["terminal"] Daemon --> Services["service/*"] Daemon --> HTTPD["httpd"] Config -->|"env vars"| Daemon Storage -->|"triggers"| CDC CDC -->|"events"| HTTPD ``` **Belongs here:** - Production dependency construction - Adapter registration - Startup/shutdown sequencing - Cross-component wiring **Does not belong here:** - Business logic that should be testable in service, lifecycle, or manager packages - Adapter implementation details ## Current Tree ```mermaid graph TB Root["backend/"] Root --> Cmd["cmd/ao/
# CLI entrypoint"] Root --> Main["main.go
# Daemon entrypoint"] Root --> Sqlc["sqlc.yaml"] Root --> Domain["internal/domain/
# Shared vocabulary"] Root --> Ports["internal/ports/
# Capability interfaces"] Root --> Service["internal/service/"] Service --> Proj["project/
# Project API"] Service --> Sess["session/
# Session API"] Service --> PR["pr/
# PR service"] Service --> Review["review/
# Code review"] Root --> SessMgr["internal/session_manager/
# Internal commands"] Root --> Life["internal/lifecycle/
# Fact reducer"] Root --> Observe["internal/observe/"] Observe --> SCM["scm/
# GitHub observer"] Observe --> Reap["reaper/
# Liveness observer"] Root --> Store["internal/storage/sqlite/
# DB + stores"] Root --> CDC2["internal/cdc/
# Change delivery"] Root --> Term["internal/terminal/
# PTY protocol"] Root --> HTTPD2["internal/httpd/
# HTTP API"] Root --> CLI2["internal/cli/
# CLI commands"] Root --> Daemon2["internal/daemon/
# Composition"] Root --> Cfg["internal/config/
# Config"] Root --> Adapters["internal/adapters/"] Adapters --> Agent["agent/
# 23+ agents"] Adapters --> Runtime2["runtime/"] Runtime2 --> Tmux2["tmux
# Darwin/Linux"] Runtime2 --> Conpty2["conpty
# Windows"] Runtime2 --> RS["runtimeselect
# Selector"] Runtime2 --> PE["ptyexec
# PTY spawning"] Adapters --> WS["workspace/gitworktree"] Adapters --> SCM2["scm/github"] Adapters --> Track["tracker/github"] Adapters --> Rev["reviewer/claudecode"] ``` ## Interface Placement Prefer interfaces near their consumers, except for shared capabilities. - **Single consumer:** Define the smallest interface in that package - **Multiple core consumers:** Define it in `ports` - **Resource service:** Use the owning `service/*` manager interface - **Return types:** Return concrete types from constructors unless callers genuinely need an interface ## Adding New Code Use these defaults: ```mermaid graph LR NewCode["New Code"] --> Choice{"What type?"} Choice -->|HTTP route| HTTPRoute["Add to httpd/
Call service/*
Update OpenAPI"] Choice -->|Product resource| Product["domain + service/
storage + ports"] Choice -->|Adapter| AdapterPath["adapters//
Implement ports
Wire in daemon"] Choice -->|Persisted fact| Fact["migration + sqlc
store + domain
CDC trigger"] Choice -->|CLI command| CLIPath["cli parsing/formatting
Call daemon API"] ``` ## Project Routes Example Project-owned concepts live in `internal/service/project`: - Project read models - Project add/remove command types - Project validation and user-facing errors - The `Manager` contract consumed by HTTP controllers `internal/httpd/controllers` remains responsible for: - Route registration - JSON decoding/encoding - HTTP status codes and error envelopes - Mapping service errors to responses ## Dependency Rules ```mermaid graph LR subgraph Allowed["Allowed Dependencies"] Controllers["httpd/controllers"] -->|✓| Services["service/*"] Services -->|✓| SessionMgr["session_manager"] Services -->|✓| Storage["storage/sqlite"] SessionMgr -->|✓| Ports["ports"] Adapters["adapters/*"] -->|✓| Ports Adapters -->|✓| Domain["domain"] end subgraph Avoid["Avoid Dependencies"] Services2["service/*"] -.->|✗| Adapters2["adapters/*"] Domain2["domain"] -.->|✗| Adapters2 Httpd["httpd"] -.->|✗| Storage2["storage/sqlite"] Adapters2 -.->|✗| Httpd end ``` **Key rules:** - Controllers call services, not storage directly - Services call session manager, not adapters directly - Adapters implement ports, don't depend on HTTP/storage - Domain stays pure, no infrastructure dependencies - Terminal and httpd are separate (terminal should not import httpd) ## Runtime Architecture Details ### Platform Selection The `runtimeselect` package automatically chooses the appropriate runtime based on the platform: ```go func New(_ *slog.Logger) Runtime { if runtime.GOOS != "windows" { return tmux.New(tmux.Options{}) // Darwin/Linux } return conpty.New(conpty.Options{}) // Windows } ``` No configuration needed — the system handles this automatically. ### tmux Implementation (Darwin/Linux) **File:** `internal/adapters/runtime/tmux/tmux.go` - Creates detached tmux sessions - Uses `tmux send-keys` for input delivery - Uses `tmux capture-pane` for scrollback - Spawns `tmux attach-session` for terminal streaming - Sessions survive daemon restart (tmux persistence) ### conpty Implementation (Windows) **Files:** `internal/adapters/runtime/conpty/*.go` - Spawns detached pty-host process - Uses custom B1 binary protocol over loopback TCP - Implements ring buffer for scrollback - File-based registry for crash recovery - Direct TCP connection for terminal streaming (no CLI attach) ## Related Documentation - [Architecture](architecture.md) — System architecture and data flows - [AGENTS.md](../AGENTS.md) — Contributor and worker-agent contract - [CLI Reference](cli/README.md) — Complete CLI command documentation