Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Start with [architecture.md](architecture.md) for the current backend model and
| Doc | What it covers |
|-----|----------------|
| [architecture.md](architecture.md) | Current backend model, package layout, status derivation, persistence/CDC, and load-bearing rules. |
| [backend-code-structure.md](backend-code-structure.md) | Package ownership rules for the Go backend: domain, services, ports, adapters, storage, HTTP, CLI, and daemon wiring. |
| [cli/README.md](cli/README.md) | CLI commands and daemon control surface. |
| [status.md](status.md) | Current implementation shape, build/test command, and next integration work. |
| [stack.md](stack.md) | Accepted library/runtime choices, pending stack decisions, and dependencies explicitly avoided for V1. |
Expand Down
382 changes: 382 additions & 0 deletions docs/backend-code-structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,382 @@
# 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` holds shared product vocabulary and durable fact records.
- `service/*` owns controller-facing product use cases and read models.
- `session_manager` owns internal session mutations and resource orchestration.
- `lifecycle` owns the durable session fact reducer.
- `ports` defines narrow capability interfaces consumed by core code.
- `adapters/*` implements those capabilities with real external systems.
- `storage/sqlite` and `cdc` own persistence and change delivery.
- `httpd` and `cli` own protocol concerns.
- `daemon` wires the production graph together.

## Package Roles

### `internal/domain`

`domain` is AO's shared product language. Keep it stable and free of
infrastructure imports.

Belongs here:

- shared IDs such as `ProjectID`, `SessionID`, and `IssueID`;
- shared enums and status vocabulary;
- durable fact records that multiple packages must agree on;
- PR, tracker, project, and session vocabulary that is not transport-specific.

Does not belong here:

- HTTP request/response DTOs;
- CLI output shapes;
- OpenAPI wrapper/envelope types;
- sqlc generated rows;
- GitHub, Zellij, 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, Zellij, 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.

Current examples:

```txt
internal/service/project
internal/service/session
internal/service/pr
```

Belongs here:

- resource use cases called by HTTP controllers and CLI-backed API flows;
- resource read models and command/result types;
- display-model assembly, such as 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.

For example, project API concepts live in `internal/service/project`, not in
`domain` and not in a top-level `internal/project` package.

### `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 such as not found, terminated, or 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.

The UI status is derived at read time by service code. Do not store display
status in lifecycle or SQLite.

### `internal/ports`

`ports` contains narrow capability interfaces and shared adapter-facing structs.
It connects core code to replaceable systems.

Current capability examples:

- `Runtime`
- `Workspace`
- `Agent`
- `AgentResolver`
- `AgentMessenger`
- `PRWriter`

Belongs here:

- interfaces consumed by core packages and implemented by adapters;
- capability structs such as `RuntimeConfig`, `WorkspaceConfig`, and
`SpawnConfig`;
- vocabulary needed at the boundary between core orchestration and adapters.

Does not belong here:

- resource read models like project/session API responses;
- HTTP request/response DTOs;
- sqlc rows;
- concrete adapter options;
- one-off interfaces that only a single package needs internally.

Keep `ports` capability-oriented. It should not become the dumping ground for
every manager, DTO, and resource contract.

### `internal/adapters/*`

Adapters are concrete implementations of external systems.

Current examples:

```txt
internal/adapters/agent/claudecode
internal/adapters/agent/codex
internal/adapters/agent/opencode
internal/adapters/runtime/zellij
internal/adapters/workspace/gitworktree
internal/adapters/scm/github
internal/adapters/tracker/github
```

Adapters should be leaves in the import graph. They translate external behavior
into AO ports and domain concepts; they should not own product workflows.

Good:

```txt
session_manager -> ports.Runtime
adapters/runtime/zellij -> ports + domain
adapters/workspace/gitworktree -> ports + domain
daemon -> adapters + services + storage
```

Avoid:

```txt
domain -> adapters
service/session -> adapters/runtime/zellij
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/session management used by
the HTTP mux.

Belongs here:

- terminal session lifecycle;
- input/output framing independent of HTTP;
- PTY-backed session handling;
- ring buffers and terminal protocol tests.

`httpd` adapts WebSocket connections to terminal interfaces; `terminal` should
not import `httpd`.

### `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.

HTTP-only request/response wrappers belong in `httpd` or
`httpd/controllers`. Application read models shared by controller and CLI flows
belong in the owning `service/*` package.

### `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.

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.

## Interface Placement

Prefer interfaces near their consumers, except for shared capabilities.

- If only one package consumes an abstraction, define the smallest interface in
that package.
- If multiple core packages consume a replaceable capability, define it in
`ports`.
- If HTTP controllers need a resource service, use the owning `service/*`
manager interface.
- Return concrete types from constructors unless callers genuinely need an
interface.

## Current Tree

The current main-line shape is:

```txt
backend/
cmd/ao/ # CLI entrypoint
main.go # daemon entrypoint compatibility
sqlc.yaml

internal/domain/ # shared product vocabulary and durable facts
internal/ports/ # capability interfaces
internal/service/
project/ # project API/use-case boundary
session/ # session API/use-case boundary
pr/ # PR observation/action service
internal/session_manager/ # internal session command engine
internal/lifecycle/ # durable lifecycle fact reducer
internal/observe/reaper/ # runtime observation loop
internal/storage/sqlite/ # DB, migrations, queries, generated sqlc, stores
internal/cdc/ # change_log poller and broadcaster
internal/terminal/ # terminal session protocol and PTY handling
internal/httpd/ # HTTP API, controllers, OpenAPI, terminal mux
internal/cli/ # user-facing ao command
internal/daemon/ # production wiring and shutdown
internal/config/ # daemon env/default config
internal/adapters/ # concrete agent/runtime/workspace/SCM/tracker adapters
```
Comment on lines +319 to +346
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Current Tree omits several real packages

The "Current Tree" section is missing at least four packages that exist on disk: internal/daemonmeta/ (build/version metadata), internal/processalive/ (liveness checking for agent processes), internal/runfile/ (daemon run-file / lock management), and internal/integration/ (integration tests). A developer looking for the right home for code related to any of these areas won't find guidance here and may place new code in the wrong package or, worse, create a duplicate.


## Adding New Code

Use these defaults:

- New HTTP route: add controller/API code in `httpd`, call a `service/*`
package, and update OpenAPI generation/spec tests.
- New product resource: put shared IDs/vocabulary in `domain`, use cases and
read models in `service/<resource>`, storage in `storage/sqlite`, and external
system seams in `ports`.
- New adapter: implement a `ports` interface under `adapters/<capability>/<impl>`
and wire it in `daemon`.
- New persisted fact: add a migration, sqlc query, store method, domain record or
event vocabulary, and CDC behavior when the UI/API must observe it.
- New CLI command: keep command parsing/formatting in `cli`; call the daemon API
rather than reimplementing daemon behavior.

## 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.

When a type is ambiguous, ask whether it is a product use-case/read model or an
HTTP wire wrapper. Product use-case/read models belong in `service/project`;
HTTP wire wrappers belong in `httpd`.
Loading