Agent runs are now always worker-owned 'claude -p' subprocesses; tmux
survives only for the interactive /login flow.
- deleted: worker.capture_agent_output/_pane_tail + the capture loop
arm (the empty-/log bug's home), spawn's tmux launch/_claude_command,
the tmux resume/kill branches (the silent-send-keys bug's home),
tmux.session_name/list_sessions, the CLI attach subcommand, the
'runner' setting
- spawn: task is now a hard requirement (headless has no idle REPL) -
enforced in spawn (SpawnError) and the API (400); onboarding seeding
dropped (-p skips the trust dialog)
- resume: single headless path; pre-headless agent rows (no session_id)
degrade to the context-re-injection fresh run
- settings_gen: permissions allowlist is always emitted
- credsync: change-triggered uploads key on .claude/.credentials.json
only (claude touches ~/.claude.json every run - keying on it would
ping-pong uploads between workers); logins still publish explicitly
- cli list: liveness from agent_runs in the DB, not tmux
- tests: spawn/kill/resume re-pointed at the fake_launch seam
(conftest); integration test now drives API -> worker -> real fake
claude subprocess -> events endpoint; README documents the headless
model + multi-worker deployment invariants
Suite 295 green; frontend unchanged since phase 3.
Phase 2 configures forge for the agents (operator only sets a credential_ref +
optional version pin) and lets them drive a junior→senior→deploy workflow:
- Credential resolution/injection (control/credentials.py): credential_ref pointers
(env:/file:/cmd:) resolved only at spawn, injected as FORGE_TOKEN + host var, with a
forge-host-scoped git credential helper reading the token from env (never on disk / in
the DB). Resolution is a fail-fast spawn gate.
- Role-based forge skills committed into the managed repo (control/skills_gen.py,
`handler forge-init`): forge-junior/senior/deploy + a workflow overview.
- Hard approval gate (hooks/gate.py, approvals table, migration 0002): merge/deploy —
and direct pushes to protected branches — are denied unless a DIFFERENT agent has an
`approved` record for the branch, pinned to the reviewed commit (approved_sha). Senior
records verdicts via `handler approve`/`reject`.
- forge/git seams (control/forge.py, control/gitops.py) matching the Phase 1 seam pattern.
- CI status poller (control/poller.py, `handler poll-ci [--watch]`) backfilling
ci_status/ci_checked_at via `forge ci list`.
- Fix: migrations/env.py commits explicitly after run_migrations — pysqlite on Py 3.12+
was rolling back the final migration's DDL + alembic_version stamp (latent in Phase 1).
Reviewed via a separate code-reviewer pass; gate-bypass and credential-scoping findings
addressed. 106 tests, ruff clean, verified end-to-end against real git + migrations.