104 lines
4.5 KiB
Markdown
104 lines
4.5 KiB
Markdown
# Agent Orchestrator backend architecture
|
|
|
|
The backend is a long-running Go daemon that supervises coding-agent sessions.
|
|
The current model is intentionally small: session rows persist only durable facts,
|
|
and display status is derived at read time.
|
|
|
|
## Mental model
|
|
|
|
```
|
|
OBSERVE external facts → UPDATE durable facts → DERIVE display status / ACT
|
|
```
|
|
|
|
The durable session facts are:
|
|
|
|
- `activity_state` — what the agent last reported or what the runtime observer
|
|
can safely conclude (`active`, `idle`, `waiting_input`, `exited`).
|
|
- `is_terminated` — whether the session should be treated as over.
|
|
- PR facts in the `pr`, `pr_checks`, and `pr_comment` tables.
|
|
|
|
The UI status is not stored. `service.Session` computes it from the session
|
|
record plus PR facts while assembling controller-facing read models.
|
|
|
|
## Package layout
|
|
|
|
```
|
|
backend/internal/domain shared vocabulary and API status value types
|
|
backend/internal/ports inbound/outbound interfaces
|
|
backend/internal/service/{project,session,pr,review}
|
|
controller-facing services and read-model assembly
|
|
backend/internal/session_manager internal session command manager
|
|
backend/internal/lifecycle runtime/activity/spawn/termination session fact reducer
|
|
backend/internal/observe/scm SCM (GitHub) observer loop feeding PR facts
|
|
backend/internal/observe/reaper runtime liveness observation loop
|
|
backend/internal/storage SQLite persistence and DB-triggered CDC
|
|
backend/internal/cdc change-log poller and broadcaster
|
|
backend/internal/httpd daemon HTTP surface (REST + SSE + terminal mux)
|
|
backend/internal/terminal WebSocket terminal multiplexer
|
|
backend/internal/adapters agent/Zellij/git-worktree/GitHub SCM + tracker adapters
|
|
backend/internal/daemon production wiring and shutdown
|
|
backend/internal/config daemon env/default config
|
|
```
|
|
|
|
## Status derivation
|
|
|
|
`service.Session` selects the display PR from all PR snapshots for a session, then
|
|
applies this rough precedence:
|
|
|
|
1. `is_terminated` → `terminated`, except merged PRs display `merged`.
|
|
2. `activity_state=waiting_input` → `needs_input`.
|
|
3. Open PR facts drive PR pipeline statuses: `ci_failed`, `draft`,
|
|
`changes_requested`, `mergeable`, `approved`, `review_pending`, `pr_open`.
|
|
4. `activity_state=active` → `working`.
|
|
5. A signal-capable harness that has never sent a hook callback past the
|
|
~90s spawn grace → `no_signal` (a broken hook pipeline is visible rather
|
|
than reported as a confident `idle`). Hook-less harnesses stay `idle`.
|
|
6. Everything else → `idle`.
|
|
|
|
## Lifecycle manager
|
|
|
|
`lifecycle.Manager` is the write path for session lifecycle facts and lifecycle-owned agent nudges:
|
|
|
|
- runtime observations can mark a session terminated only when runtime and
|
|
process are both clearly dead and recent activity does not contradict that;
|
|
failed/unknown probes do not persist a special state.
|
|
- activity signals update `activity_state`; `exited` also marks the session
|
|
terminated.
|
|
- PR observations do not write PR rows here, but after the PR service persists
|
|
them lifecycle sends actionable agent nudges for CI failures, review feedback,
|
|
and merge conflicts.
|
|
|
|
## PR manager
|
|
|
|
`pr.Manager` records SCM observations into the PR/check/comment tables, then
|
|
forwards the observation to lifecycle for agent nudges. A merged PR marks the
|
|
owning session terminated through the lifecycle manager; other PR facts are
|
|
consumed at read time for display status.
|
|
|
|
## Session manager
|
|
|
|
`session_manager.Manager` performs internal session mutations:
|
|
|
|
- `Spawn` creates a row, creates workspace/runtime resources, and reports the
|
|
handles to the lifecycle manager.
|
|
- `Kill` marks the row terminated, then tears down runtime/workspace resources.
|
|
- `Restore` relaunches a terminated session and clears `is_terminated` via the
|
|
spawn-completed path.
|
|
|
|
`service.Session` is the controller-facing boundary. It delegates commands to
|
|
`session_manager.Manager` and attaches derived display status on read paths.
|
|
|
|
## Persistence and CDC
|
|
|
|
SQLite is the durable store. User-visible table changes are captured by database
|
|
triggers into `change_log`; the Go store does not manually emit CDC events. A
|
|
poller tails `change_log` and publishes live events to in-process subscribers.
|
|
|
|
## Load-bearing rules
|
|
|
|
- Do not store display status.
|
|
- Keep session status facts small: `activity_state`, `is_terminated`, and PR
|
|
facts are the durable inputs.
|
|
- Do not treat failed probes as death.
|
|
- Do not force-delete registered dirty worktrees.
|