978 lines
25 KiB
Markdown
978 lines
25 KiB
Markdown
# Backend Code Structure
|
|
|
|
This document describes package ownership for the Go backend. It defines where code belongs, how packages interact, and the architectural boundaries that keep the system maintainable.
|
|
|
|
## Table of Contents
|
|
|
|
- [Overview](#overview)
|
|
- [Architecture Layers](#architecture-layers)
|
|
- [Package-by-Package Ownership](#package-by-package-ownership)
|
|
- [Interface Placement Rules](#interface-placement-rules)
|
|
- [Import Graph](#import-graph)
|
|
- [Adding New Code](#adding-new-code)
|
|
- [Examples](#examples)
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The backend is a **layered hybrid** architecture with clear separation between core business logic and external concerns:
|
|
|
|
```mermaid
|
|
graph TB
|
|
subgraph CLI["CLI Layer"]
|
|
CLI[internal/cli]
|
|
end
|
|
|
|
subgraph HTTP["HTTP Layer"]
|
|
HTTPD[internal/httpd]
|
|
end
|
|
|
|
subgraph Services["Service Layer"]
|
|
Project[internal/service/project]
|
|
Session[internal/service/session]
|
|
PR[internal/service/pr]
|
|
Review[internal/service/review]
|
|
end
|
|
|
|
subgraph Core["Core Layer"]
|
|
SessionMgr[internal/session_manager]
|
|
Lifecycle[internal/lifecycle]
|
|
Observe[internal/observe/*]
|
|
end
|
|
|
|
subgraph Data["Data Layer"]
|
|
Domain[internal/domain]
|
|
Ports[internal/ports]
|
|
Storage[internal/storage/sqlite]
|
|
CDC[internal/cdc]
|
|
end
|
|
|
|
subgraph Infra["Infrastructure Layer"]
|
|
Terminal[internal/terminal]
|
|
Adapters[internal/adapters/*]
|
|
Daemon[internal/daemon]
|
|
Config[internal/config]
|
|
end
|
|
|
|
CLI -->|calls| HTTPD
|
|
HTTPD -->|calls| Services
|
|
Services -->|calls| Core
|
|
Services -->|uses| Data
|
|
Core -->|uses| Data
|
|
Core -->|uses| Infra
|
|
HTTPD -->|uses| Data
|
|
|
|
```
|
|
|
|
### Key Architectural Principles
|
|
|
|
1. **Domain stays pure** — No infrastructure dependencies
|
|
2. **Ports define contracts** — Interfaces consumed by core, implemented by adapters
|
|
3. **Services orchestrate** — Controller-facing use cases over core and data
|
|
4. **Adapters are leaves** — Implement ports, don't import core
|
|
5. **CLI/HTTP stay thin** — Just protocol handling, all logic in daemon
|
|
|
|
---
|
|
|
|
## Architecture Layers
|
|
|
|
### Layer Interactions
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
Protocol[Protocol Layer<br/>CLI + HTTP] -->|uses| Services[Service Layer<br/>Use Cases]
|
|
Services -->|commands| Core[Core Layer<br/>Session Manager + Lifecycle]
|
|
Services -->|queries| Data[Data Layer<br/>Domain + Ports + Storage]
|
|
Core -->|reads/writes| Data
|
|
Core -->|invokes| Infra[Infrastructure<br/>Adapters + Terminal]
|
|
Infra -->|implements| Ports
|
|
|
|
```
|
|
|
|
### Dependency Rules
|
|
|
|
```mermaid
|
|
graph TD
|
|
Direction[Dependency Direction]
|
|
Direction --> Down["Top-down only"]
|
|
Direction --> No["No upward dependencies"]
|
|
|
|
CLI[CLI] -->|OK| HTTP[HTTP]
|
|
HTTP -->|OK| Services[Services]
|
|
Services -->|OK| Core[Core]
|
|
Services -->|OK| Storage[Storage]
|
|
Core -->|OK| Adapters[Adapters]
|
|
|
|
Bad1[Adapters] -.->|FORBIDDEN| Services
|
|
Bad2[Storage] -.->|FORBIDDEN| HTTP
|
|
Bad3[Core] -.->|FORBIDDEN| CLI
|
|
|
|
```
|
|
|
|
---
|
|
|
|
## Package-by-Package Ownership
|
|
|
|
### `internal/domain`
|
|
|
|
**Purpose:** Shared product vocabulary and durable fact records. The single source of truth for domain concepts.
|
|
|
|
```mermaid
|
|
graph TD
|
|
Domain[internal/domain] --> Contains[Contains]
|
|
Contains --> IDs[Shared IDs<br/>ProjectID, SessionID, IssueID]
|
|
Contains --> Enums[Status Enums<br/>SessionStatus, ActivityState]
|
|
Contains --> Records[Durable Records<br/>SessionRecord, PRRecord]
|
|
Contains --> Vocab[Product Vocabulary<br/>PR, Project, Review concepts]
|
|
|
|
DoesNot[Does NOT Contain] --> HTTP[HTTP DTOs]
|
|
DoesNot --> CLI[CLI Output]
|
|
DoesNot --> Generated[sqlc Generated Rows]
|
|
DoesNot --> External[External Payloads<br/>GitHub, Claude, etc.]
|
|
|
|
```
|
|
|
|
**Belongs here:**
|
|
|
|
- Shared IDs: `ProjectID`, `SessionID`, `IssueID`
|
|
- Enums and status vocabulary
|
|
- Durable fact records used across packages
|
|
- PR, tracker, project, session vocabulary
|
|
|
|
**Does NOT belong here:**
|
|
|
|
- HTTP request/response DTOs
|
|
- CLI output shapes
|
|
- OpenAPI wrapper types
|
|
- sqlc generated rows
|
|
- External system payloads (GitHub, tmux, agent-specific)
|
|
|
|
**Rule of thumb:** If AO would still use the concept after replacing HTTP, CLI, SQLite, GitHub, tmux, and every agent adapter, it belongs in domain.
|
|
|
|
---
|
|
|
|
### `internal/ports`
|
|
|
|
**Purpose:** Narrow capability interfaces that connect core code to replaceable external systems.
|
|
|
|
```mermaid
|
|
graph LR
|
|
Core[Core Code] -->|consumes| Ports[Ports Interfaces]
|
|
Adapters[Adapters] -->|implement| Ports
|
|
|
|
Ports --> Examples[Examples]
|
|
Examples --> Runtime[Runtime<br/>Create, Destroy, IsAlive, Attach]
|
|
Examples --> Workspace[Workspace<br/>Create, Destroy, ValidatePath]
|
|
Examples --> Agent[Agent<br/>GetLaunchCommand, GetAgentHooks]
|
|
Examples --> SCM[SCM<br/>ListPRs, FetchPR, FetchChecks]
|
|
Examples --> PR[PR<br/>WriteSCMObservation]
|
|
|
|
```
|
|
|
|
**Belongs here:**
|
|
|
|
- Interfaces consumed by core packages, implemented by adapters
|
|
- Capability structs: `RuntimeConfig`, `WorkspaceConfig`, `SpawnConfig`
|
|
- Vocabulary at the boundary between core and adapters
|
|
|
|
**Does NOT belong here:**
|
|
|
|
- Resource read models (belongs in `service/*`)
|
|
- HTTP request/response DTOs (belongs in `httpd`)
|
|
- sqlc rows (belongs in `storage/sqlite`)
|
|
- One-off internal interfaces
|
|
|
|
**Key Port Interfaces:**
|
|
|
|
| Port | Purpose | Implementations |
|
|
| ---------------- | ----------------------- | ----------------------- |
|
|
| `Runtime` | Process isolation | `tmux`, `conpty` |
|
|
| `Workspace` | Git worktree management | `gitworktree` |
|
|
| `Agent` | Agent launching | 23+ agent adapters |
|
|
| `SCM` | PR/CI observation | `github` |
|
|
| `Tracker` | Issue tracking | `github` (adapter only) |
|
|
| `AgentMessenger` | Agent communication | Agent hooks |
|
|
| `PRWriter` | PR persistence | `pr.Manager` |
|
|
|
|
---
|
|
|
|
### `internal/service/*`
|
|
|
|
**Purpose:** Controller-facing application boundary. Owns product use cases and read-model assembly.
|
|
|
|
```mermaid
|
|
graph TD
|
|
subgraph Services
|
|
Project[project]
|
|
Session[session]
|
|
PR[pr]
|
|
Review[review]
|
|
end
|
|
|
|
subgraph Responsibilities
|
|
UseCases[Use Cases]
|
|
ReadModels[Read Models]
|
|
Validation[Validation]
|
|
Errors[User-Facing Errors]
|
|
end
|
|
|
|
Project --> Responsibilities
|
|
Session --> Responsibilities
|
|
PR --> Responsibilities
|
|
Review --> Responsibilities
|
|
|
|
subgraph NotHere
|
|
LowLevel[Low-level runtime control]
|
|
RawSQL[Raw sqlc rows]
|
|
HTTP[HTTP routing]
|
|
end
|
|
|
|
```
|
|
|
|
**Current service packages:**
|
|
|
|
```mermaid
|
|
graph LR
|
|
Controllers[HTTP Controllers] -->|call| Services
|
|
|
|
Services --> Project[internal/service/project<br/>Project CRUD]
|
|
Services --> Session[internal/service/session<br/>Session read-model assembly]
|
|
Services --> PR[internal/service/pr<br/>PR observation/actions]
|
|
Services --> Review[internal/service/review<br/>Code review]
|
|
|
|
Services -->|delegate to| SessionMgr[session_manager]
|
|
Services -->|query| Store[storage stores]
|
|
|
|
```
|
|
|
|
**Belongs here:**
|
|
|
|
- Resource use cases called by HTTP controllers and CLI
|
|
- Resource read models and command/result types
|
|
- Display-model assembly (e.g., session status derivation)
|
|
- 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 results
|
|
- HTTP routing, path parsing, status-code decisions
|
|
- Concrete external adapter details
|
|
|
|
**Example:** Project concepts live in `internal/service/project`, not in `domain` and not in `internal/project`.
|
|
|
|
---
|
|
|
|
### `internal/session_manager`
|
|
|
|
**Purpose:** Internal session command engine. Owns multi-step session mutations and resource orchestration.
|
|
|
|
```mermaid
|
|
graph TD
|
|
Service[service/session] -->|commands| Mgr[session_manager]
|
|
Mgr -->|orchestrates| Resources[Resources]
|
|
|
|
Resources --> Workspace[Workspace Adapter]
|
|
Resources --> Runtime[Runtime Adapter]
|
|
Resources --> Agent[Agent Adapter]
|
|
Resources --> Storage[Storage Store]
|
|
Resources --> Lifecycle[Lifecycle Manager]
|
|
Resources --> Messenger[Agent Messenger]
|
|
|
|
Mgr -->|owns| Operations[Operations]
|
|
Operations --> Spawn[Spawn<br/>Create all resources]
|
|
Operations --> Kill[Kill<br/>Teardown all resources]
|
|
Operations --> Restore[Restore<br/>Relaunch terminated session]
|
|
Operations --> Send[Send<br/>Message to agent]
|
|
|
|
```
|
|
|
|
**Belongs here:**
|
|
|
|
- Multi-step session mutations with rollback
|
|
- Resource sequencing (workspace → runtime → agent)
|
|
- Resource teardown safety and cleanup
|
|
- 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
|
|
|
|
**Intentional split:** `service/session` is the product/API boundary; `session_manager` is the internal command engine.
|
|
|
|
---
|
|
|
|
### `internal/lifecycle`
|
|
|
|
**Purpose:** Canonical write path for durable session lifecycle facts. Reduces observations into minimal persisted state.
|
|
|
|
```mermaid
|
|
graph LR
|
|
subgraph Inputs
|
|
RuntimeObs[Runtime Observations<br/>from Reaper]
|
|
ActivitySignals[Activity Signals<br/>from Agent Hooks]
|
|
SCMObs[SCM Observations<br/>from SCM Observer]
|
|
end
|
|
|
|
subgraph Lifecycle
|
|
LCM[Lifecycle Manager]
|
|
Reducer[Fact Reducer]
|
|
StateMachine[State Machine]
|
|
Nudge[Agent Nudge Engine]
|
|
end
|
|
|
|
subgraph Outputs
|
|
ActivityState[activity_state]
|
|
IsTerminated[is_terminated]
|
|
PRFacts[PR Facts]
|
|
Nudges[Agent Nudges]
|
|
end
|
|
|
|
RuntimeObs --> LCM
|
|
ActivitySignals --> LCM
|
|
SCMObs --> LCM
|
|
|
|
LCM --> Reducer
|
|
Reducer --> StateMachine
|
|
StateMachine --> ActivityState
|
|
StateMachine --> IsTerminated
|
|
|
|
SCMObs --> Nudge
|
|
Nudge --> Nudges
|
|
|
|
```
|
|
|
|
**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 (use service layer instead)
|
|
- HTTP/CLI DTOs
|
|
- Direct adapter implementation details
|
|
- PR row persistence (use `pr.Manager`)
|
|
|
|
**Key invariant:** The UI status is derived at read time by service code. Do not store display status in lifecycle or SQLite.
|
|
|
|
---
|
|
|
|
### `internal/observe/*`
|
|
|
|
**Purpose:** Observation loops that poll external state and report facts to lifecycle.
|
|
|
|
```mermaid
|
|
graph TD
|
|
subgraph Observe
|
|
SCM[observe/scm<br/>SCM Observer]
|
|
Reaper[observe/reaper<br/>Runtime Reaper]
|
|
end
|
|
|
|
subgraph External
|
|
GitHub[GitHub API]
|
|
Runtimes[tmux/conpty]
|
|
end
|
|
|
|
subgraph Internal
|
|
LCM[Lifecycle Manager]
|
|
Store[SQLite Store]
|
|
end
|
|
|
|
SCM -->|polls| GitHub
|
|
SCM -->|writes| Store
|
|
SCM -->|notifies| LCM
|
|
|
|
Reaper -->|probes| Runtimes
|
|
Reaper -->|reports to| LCM
|
|
|
|
```
|
|
|
|
**Current observation packages:**
|
|
|
|
- `internal/observe/scm` — SCM (GitHub) observer loop
|
|
- `internal/observe/reaper` — Runtime liveness observation loop
|
|
|
|
**Belongs here:**
|
|
|
|
- Polling loops and observation logic
|
|
- External state transformation into domain facts
|
|
- Observation error handling and retry logic
|
|
|
|
**Does NOT belong here:**
|
|
|
|
- Product workflow decisions (belongs in service layer)
|
|
- Direct storage writes (use lifecycle instead)
|
|
|
|
---
|
|
|
|
### `internal/storage/sqlite`
|
|
|
|
**Purpose:** SQLite setup, migrations, queries, and store implementations.
|
|
|
|
```mermaid
|
|
graph TD
|
|
Storage[storage/sqlite] --> Components[Components]
|
|
|
|
Components --> Setup[Connection Setup<br/>PRAGMAs]
|
|
Components --> Migrations[Goose Migrations]
|
|
Components --> SQLC[sqlc Queries + Generated Code]
|
|
Components --> Stores[Table-specific Stores]
|
|
Components --> Trans[Transactions + CDC]
|
|
|
|
Components -->|NOT| HTTP[HTTP Response Types]
|
|
Components -->|NOT| CLI[CLI Formatting]
|
|
Components -->|NOT| Product[Product Display Status Rules]
|
|
|
|
```
|
|
|
|
**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
|
|
|
|
**Rule:** Generated sqlc types should stay behind store methods. Services should work with domain records or service read models, not generated rows.
|
|
|
|
---
|
|
|
|
### `internal/cdc`
|
|
|
|
**Purpose:** Change-log polling and event broadcasting.
|
|
|
|
```mermaid
|
|
graph LR
|
|
SQLite[(SQLite)] -->|triggers| ChangeLog[change_log]
|
|
ChangeLog -->|tail| Poller[CDC Poller]
|
|
Poller -->|Event| Broadcaster[Broadcaster]
|
|
Broadcaster -->|fan-out| Subs[Subscribers]
|
|
|
|
Subs --> SSE[SSE Writer]
|
|
Subs --> Term[Terminal Fanout]
|
|
Subs --> Cache[Cache Invalidation]
|
|
|
|
```
|
|
|
|
**Belongs here:**
|
|
|
|
- Event type definitions for the CDC stream
|
|
- Poller and broadcaster logic
|
|
- Subscriber fan-out behavior
|
|
|
|
**Does NOT belong here:**
|
|
|
|
- Terminal byte streams (belongs in `internal/terminal`)
|
|
- Product workflow decisions (belongs in service layer)
|
|
- Database schema ownership (belongs in `storage/sqlite`)
|
|
|
|
---
|
|
|
|
### `internal/terminal`
|
|
|
|
**Purpose:** Terminal session protocol and PTY attach management used by the HTTP terminal mux.
|
|
|
|
```mermaid
|
|
graph TD
|
|
HTTP[httpd] -->|WebSocket| Terminal[terminal]
|
|
Terminal -->|creates| Attach[Attach Streams]
|
|
Attach -->|wraps| PTY[PTY Sessions]
|
|
|
|
PTY --> Unix[Unix: tmux attach<br/>via ptyexec]
|
|
PTY --> Windows[Windows: conpty<br/>loopback dial]
|
|
|
|
Terminal -->|manages| State[Session States]
|
|
State --> Liveness[Liveness gating]
|
|
State --> Backoff[Re-attach backoff]
|
|
|
|
```
|
|
|
|
**Belongs here:**
|
|
|
|
- Per-client attachment lifecycle
|
|
- Input/output framing independent of HTTP
|
|
- PTY-backed attach handling and terminal protocol tests
|
|
|
|
**Does NOT belong here:**
|
|
|
|
- HTTP-specific concerns (belongs in `httpd`)
|
|
- HTTP routing or WebSocket upgrade logic
|
|
|
|
**Note:** `httpd` adapts WebSocket connections to terminal interfaces. `terminal` should not import `httpd`.
|
|
|
|
---
|
|
|
|
### `internal/httpd`
|
|
|
|
**Purpose:** HTTP protocol adapter. Handles routing, middleware, and request/response encoding.
|
|
|
|
```mermaid
|
|
graph TD
|
|
HTTPD[httpd] --> Components[Components]
|
|
|
|
Components --> Routing[Routing + Middleware]
|
|
Components --> Decode[Request Decoding]
|
|
Components --> Encode[Response Encoding]
|
|
Components --> Errors[API Error Envelopes]
|
|
Components --> OpenAPI[OpenAPI Generation]
|
|
Components --> WS[WebSocket for Terminal]
|
|
|
|
Components -->|calls| Services[service/*]
|
|
|
|
Services -->|NOT| Adapters[Direct Adapter Access]
|
|
Services -->|NOT| Storage[Direct SQLite Access]
|
|
|
|
```
|
|
|
|
**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
|
|
|
|
**Does NOT belong here:**
|
|
|
|
- Direct adapter or SQLite store access
|
|
- Application read models shared with CLI (belongs in `service/*`)
|
|
|
|
**Rule:** Controllers call service managers and translate service results/errors into HTTP responses.
|
|
|
|
---
|
|
|
|
### `internal/cli`
|
|
|
|
**Purpose:** User-facing `ao` command. Thin client over the daemon HTTP API.
|
|
|
|
```mermaid
|
|
graph LR
|
|
CLI[cli] --> Operations[Operations]
|
|
|
|
Operations --> Discover[Discover Daemon]
|
|
Operations --> Call[Call HTTP API]
|
|
Operations --> Format[Format Output]
|
|
Operations --> Control[Process Control<br/>start/stop/status/doctor]
|
|
|
|
Operations -->|NOT| Direct[Direct Storage/DB Access]
|
|
Operations -->|NOT| Runtime[Direct Runtime Control]
|
|
Operations -->|NOT| Adapters[Direct Adapter Calls]
|
|
|
|
```
|
|
|
|
**Belongs here:**
|
|
|
|
- Daemon discovery
|
|
- HTTP API calls
|
|
- Command output formatting
|
|
- Process control: start/stop/status/doctor
|
|
|
|
**Does NOT belong here:**
|
|
|
|
- Duplicate daemon business logic (put in daemon service/API)
|
|
- Direct storage, runtime, or adapter access
|
|
|
|
**Rule:** If a command needs product behavior, put it in the daemon and have the CLI call that API path.
|
|
|
|
---
|
|
|
|
### `internal/adapters/*`
|
|
|
|
**Purpose:** Concrete implementations of ports interfaces. Wraps external systems.
|
|
|
|
```mermaid
|
|
graph TD
|
|
Ports[Ports Interfaces] -->|implemented by| Adapters[Adapters]
|
|
|
|
Adapters --> Agent[agent/*<br/>23+ harnesses]
|
|
Adapters --> Runtime[runtime/*<br/>tmux, conpty]
|
|
Adapters --> Workspace[workspace/*<br/>gitworktree]
|
|
Adapters --> SCM[scm/*<br/>github]
|
|
Adapters --> Tracker[tracker/*<br/>github]
|
|
|
|
Agent --> Codex[codex]
|
|
Agent --> Claude[claude-code]
|
|
Agent --> Cursor[cursor]
|
|
Agent --> Aider[aider]
|
|
Agent -->|... 20+| More[more agents]
|
|
|
|
```
|
|
|
|
**Adapter principles:**
|
|
|
|
- Adapters are leaves in the import graph
|
|
- Adapters translate external behavior into AO ports/domain concepts
|
|
- Adapters should not own product workflows
|
|
- All adapter-written files must be gitignored
|
|
|
|
**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/daemon`
|
|
|
|
**Purpose:** Production composition root. Wires all dependencies together.
|
|
|
|
```mermaid
|
|
graph TD
|
|
Daemon[daemon] --> Responsibilities[Responsibilities]
|
|
|
|
Responsibilities --> Wire[Dependency Construction]
|
|
Responsibilities --> Register[Adapter Registration]
|
|
Responsibilities --> Startup[Startup Sequencing]
|
|
Responsibilities --> Shutdown[Shutdown Sequencing]
|
|
Responsibilities --> Cross[Cross-component Wiring]
|
|
|
|
Responsibilities -->|NOT| Business[Business Logic<br/>(put in service/lifecycle)]
|
|
Responsibilities -->|NOT| Adapter[Adapter Implementation<br/>(put in adapters/*)]
|
|
|
|
```
|
|
|
|
**Belongs here:**
|
|
|
|
- Production dependency construction
|
|
- Adapter registration
|
|
- Startup/shutdown sequencing
|
|
- Cross-component wiring
|
|
|
|
**Does NOT belong here:**
|
|
|
|
- Business logic (belongs in service, lifecycle, or manager packages)
|
|
- Adapter implementation details (belongs in adapters/\*)
|
|
|
|
---
|
|
|
|
### `internal/config`
|
|
|
|
**Purpose:** Environment-based daemon configuration.
|
|
|
|
```mermaid
|
|
graph LR
|
|
Config[config] --> Sources[Sources]
|
|
|
|
Sources --> Env[Environment Variables]
|
|
Sources --> Defaults[Built-in Defaults]
|
|
Sources --> Validate[Validation]
|
|
|
|
Config -->|provides| Settings[Settings]
|
|
|
|
Settings --> Port[AO_PORT]
|
|
Settings --> Timeout[AO_REQUEST_TIMEOUT]
|
|
Settings --> DataDir[AO_DATA_DIR]
|
|
Settings --> RunFile[AO_RUN_FILE]
|
|
Settings --> Agent[AO_AGENT]
|
|
|
|
```
|
|
|
|
**Key environment variables:**
|
|
|
|
- `AO_PORT` — HTTP bind port (default: 3001)
|
|
- `AO_REQUEST_TIMEOUT` — Per-request timeout (default: 60s)
|
|
- `AO_SHUTDOWN_TIMEOUT` — Graceful shutdown cap (default: 10s)
|
|
- `AO_RUN_FILE` — PID/port handshake (default: ~/.ao/running.json)
|
|
- `AO_DATA_DIR` — SQLite data directory (default: ~/.ao/data)
|
|
- `AO_AGENT` — Compatibility agent adapter (default: claude-code)
|
|
- `GITHUB_TOKEN` — GitHub authentication
|
|
|
|
---
|
|
|
|
## Interface Placement Rules
|
|
|
|
```mermaid
|
|
graph TD
|
|
Question{Where to define<br/>interface?}
|
|
|
|
Question --> Single{Only one package<br/>consumes it?}
|
|
Single -->|Yes| InPackage[Define in consuming<br/>package]
|
|
Single -->|No| Multiple{Multiple core packages<br/>need it?}
|
|
|
|
Multiple -->|Yes| Ports[Define in ports]
|
|
Multiple -->|No| HTTP{HTTP controllers<br/>need it?}
|
|
|
|
HTTP -->|Yes| Service[Define in service/*]
|
|
HTTP -->|No| Concrete[Return concrete type<br/>from constructor]
|
|
|
|
```
|
|
|
|
**Rules:**
|
|
|
|
1. **Single consumer** → Define in the consuming package (smallest interface)
|
|
2. **Multiple core consumers** → Define in `ports` (shared capability)
|
|
3. **HTTP controllers need resource** → Use `service/*` manager interface
|
|
4. **Return from constructor** → Return concrete type unless genuinely needed
|
|
|
|
**Examples:**
|
|
|
|
```go
|
|
// Good: Interface near single consumer
|
|
type sessionGetter interface {
|
|
GetSession(ctx context.Context, id SessionID) (SessionRecord, bool, error)
|
|
}
|
|
|
|
// Good: Shared capability in ports
|
|
type Runtime interface {
|
|
Create(ctx context.Context, cfg RuntimeConfig) (RuntimeHandle, error)
|
|
Destroy(ctx context.Context, handle RuntimeHandle) error
|
|
IsAlive(ctx context.Context, handle RuntimeHandle) (bool, error)
|
|
}
|
|
|
|
// Good: Service interface for controllers
|
|
type Manager interface {
|
|
List(ctx context.Context) ([]Project, error)
|
|
Add(ctx context.Context, cfg Config) (Project, error)
|
|
Remove(ctx context.Context, id string) error
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Import Graph
|
|
|
|
```mermaid
|
|
graph TD
|
|
CLI[cli] --> HTTPD[httpd]
|
|
HTTPD --> Services[service/*]
|
|
HTTPD --> Terminal[terminal]
|
|
|
|
Services --> SessionMgr[session_manager]
|
|
Services --> Lifecycle[lifecycle]
|
|
Services --> Storage[storage/sqlite]
|
|
Services --> Domain[domain]
|
|
Services --> Ports[ports]
|
|
|
|
SessionMgr --> Ports
|
|
SessionMgr --> Adapters[adapters/*]
|
|
SessionMgr --> Lifecycle
|
|
|
|
Lifecycle --> Ports
|
|
Lifecycle --> Storage
|
|
Lifecycle --> Domain
|
|
|
|
Observe[observe/*] --> Ports
|
|
Observe --> Storage
|
|
Observe --> Lifecycle
|
|
|
|
Storage --> Domain
|
|
Storage --> Ports
|
|
|
|
Adapters --> Ports
|
|
Adapters --> Domain
|
|
|
|
CDC[cdc] --> Storage
|
|
Terminal --> Ports
|
|
|
|
Daemon[daemon] --> All[All packages]
|
|
|
|
```
|
|
|
|
**Key patterns:**
|
|
|
|
- All arrows point downward (no cycles)
|
|
- Adapters and domain are leaves
|
|
- CLI and HTTPD don't touch storage directly
|
|
- Everything depends on ports and domain
|
|
|
|
---
|
|
|
|
## Adding New Code
|
|
|
|
### New HTTP Route
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
AddRoute[Add HTTP Route] --> Route[Register in httpd]
|
|
Route --> Call[Call service/*]
|
|
Call --> Update[Update OpenAPI]
|
|
Update --> Test[Add tests]
|
|
|
|
```
|
|
|
|
**Steps:**
|
|
|
|
1. Add controller in `httpd/controllers/`
|
|
2. Call a `service/*` package
|
|
3. Update OpenAPI generation
|
|
4. Add spec tests
|
|
|
|
### New Product Resource
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
NewResource[New Resource] --> Domain[Add IDs/vocab to domain]
|
|
Domain --> Service[Create service/resource]
|
|
Service --> Storage[Add storage queries]
|
|
Storage --> Ports[Add ports if needed]
|
|
Ports --> Adapter[Implement adapter if needed]
|
|
|
|
```
|
|
|
|
**Steps:**
|
|
|
|
1. Add shared IDs/vocabulary to `domain`
|
|
2. Create use cases in `service/<resource>`
|
|
3. Add storage in `storage/sqlite`
|
|
4. Add ports if external system needed
|
|
5. Implement adapter in `adapters/<capability>/<impl>`
|
|
6. Wire in `daemon`
|
|
|
|
### New Adapter
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
NewAdapter[New Adapter] --> Port[Implement port interface]
|
|
Port --> Hooks[Implement hooks if agent]
|
|
Hooks --> Gitignore[Add .gitignore entries]
|
|
Gitignore --> Wire[Wire in daemon]
|
|
Wire --> Test[Add conformance tests]
|
|
|
|
```
|
|
|
|
**Steps:**
|
|
|
|
1. Implement a `ports` interface under `adapters/<capability>/<impl>`
|
|
2. For agents: implement hooks with gitignored files
|
|
3. Wire in `daemon`
|
|
4. Add conformance tests
|
|
|
|
---
|
|
|
|
## Examples
|
|
|
|
### Example: Adding a Session Command
|
|
|
|
```go
|
|
// In internal/service/session/service.go
|
|
func (s *Service) MyNewCommand(ctx context.Context, id SessionID) (Result, error) {
|
|
// 1. Validate input
|
|
// 2. Call session_manager
|
|
// 3. Enrich result
|
|
// 4. Return read model
|
|
}
|
|
|
|
// In internal/httpd/controllers/sessions.go
|
|
func (c *SessionsController) myNewCommand(w http.ResponseWriter, r *http.Request) {
|
|
// 1. Decode request
|
|
// 2. Call service
|
|
// 3. Encode response
|
|
}
|
|
```
|
|
|
|
### Example: Adding a Port Interface
|
|
|
|
```go
|
|
// In internal/ports/myfeature.go
|
|
package ports
|
|
|
|
type MyFeature interface {
|
|
DoSomething(ctx context.Context, cfg Config) (Result, error)
|
|
}
|
|
|
|
// In internal/adapters/myfeature/impl.go
|
|
package impl
|
|
|
|
import "github.com/aoagents/agent-orchestrator/backend/internal/ports"
|
|
|
|
type Impl struct { ... }
|
|
|
|
func (i *Impl) DoSomething(ctx context.Context, cfg ports.Config) (ports.Result, error) {
|
|
// Implementation
|
|
}
|
|
```
|
|
|
|
### Example: Service Layer Pattern
|
|
|
|
```go
|
|
// In internal/service/myresource/service.go
|
|
package service
|
|
|
|
// Service is the controller-facing boundary
|
|
type Service struct {
|
|
manager *manager.Manager // Internal command engine
|
|
store Store // Storage interface
|
|
}
|
|
|
|
// New constructs the service
|
|
func New(mgr *manager.Manager, store Store) *Service {
|
|
return &Service{manager: mgr, store: store}
|
|
}
|
|
|
|
// List returns enriched read models
|
|
func (s *Service) List(ctx context.Context) ([]MyResource, error) {
|
|
records, err := s.store.List(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.enrich(records), nil
|
|
}
|
|
|
|
// Create performs a use case
|
|
func (s *Service) Create(ctx context.Context, cfg Config) (MyResource, error) {
|
|
// Business logic
|
|
result, err := s.manager.Create(ctx, cfg)
|
|
if err != nil {
|
|
return MyResource{}, err
|
|
}
|
|
return s.enrichOne(result), nil
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Key takeaways:**
|
|
|
|
1. **Domain** stays pure — shared vocabulary only
|
|
2. **Ports** define contracts — interfaces for external systems
|
|
3. **Services** orchestrate — controller-facing use cases
|
|
4. **Adapters** are leaves — implement ports, no core imports
|
|
5. **CLI/HTTP** stay thin — protocol handling only
|
|
6. **Daemon** wires it all — composition root
|
|
|
|
**Always ask:**
|
|
|
|
- Does this belong in domain (shared concept)?
|
|
- Does this belong in ports (shared capability)?
|
|
- Does this belong in service (use case)?
|
|
- Does this belong in adapters (external system)?
|
|
|
|
**Never:**
|
|
|
|
- Put HTTP types in domain
|
|
- Put display status in storage
|
|
- Put business logic in CLI
|
|
- Import core from adapters
|
|
- Import adapters from services
|