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.
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, andpr_commenttables.
The UI status is not stored. service.Session computes it from the session
record plus PR facts while assembling controller-facing read models.
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
service.Session selects the display PR from all PR snapshots for a session, then
applies this rough precedence:
is_terminated→terminated, except merged PRs displaymerged.activity_state=waiting_input→needs_input.- Open PR facts drive PR pipeline statuses:
ci_failed,draft,changes_requested,mergeable,approved,review_pending,pr_open. activity_state=active→working.- 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 confidentidle). Hook-less harnesses stayidle. - Everything else →
idle.
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;exitedalso 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 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.Manager performs internal session mutations:
Spawncreates a row, creates workspace/runtime resources, and reports the handles to the lifecycle manager.Killmarks the row terminated, then tears down runtime/workspace resources.Restorerelaunches a terminated session and clearsis_terminatedvia 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.
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.
- 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.