A schedule is a time trigger, and only the first step of a pipeline is really
waiting on time — every later step waits on the previous step's result. Modeling
"watch a source -> write a spec -> implement it" as three schedules made each fire
blind: on a quiet day the coding agent still spawned, paid a full model run to find
there was nothing to do, and left an empty run in Activity.
So an agent can start the next step itself. `dispatch_agent` (a tool on the bundled
MCP server, and on the pi bridge through the same --call seam) enqueues an ordinary
spawn command in the agent's own project, tagged requested_by=agent:<id> — so a
handoff is visible in Activity with no new surface to build.
- Project-scoped by construction: project_id is read from the spawn environment and
never from the tool arguments.
- Bounded rather than gated: MAX_DISPATCH_PER_RUN counts the command rows the agent
already wrote; MAX_DISPATCH_DEPTH rides in the spawn payload and is recovered by
spawn._dispatch_depth, so a chain keeps its place across a resume and a cycle
terminates instead of fanning out.
- New scout and planner roles, with built-in skills (handler-scout, handler-planner,
handler-dispatch) carrying the judgment code can't: dedupe against a memory-note
watermark, treat "nothing new" as a complete run, and write a task the receiving
cold-start agent can act on.
- A scout ending on a clean tree skips the test gate and records the new
tests_status='skipped' (migration 0017, additive CHECK widening). The gate promises
`done` means tests passed for the work that shipped; nothing shipped.
Rejected a `condition` field on schedules: "is this paper new and does it matter
here?" is a semantic judgment, so it belongs to a model, not a scheduler column. The
scout is the condition; dispatch is how it reports true — one mechanism that covers
future pipelines too.
426 tests (14 new for dispatch, 3 for the gate exemption).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HcbDevyMcJWE6qPA56C7mZ
The Next.js export is a generated artifact and deliberately untracked
(82183d9 stopped tracking it; the Docker image builds it in its own
node stage), but three tests still asserted its presence — failing on
every fresh clone. They now skip with a clear reason when
src/handler/api/static/ was never built, and keep guarding any
environment that has it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
Worktree spawns now fetch only. The agent's branch is cut from
origin/* and the root checkout - which may hold the operator's own
work - is never fast-forwarded, merged, or re-parked by a spawn. A
failed fetch on a worktree spawn is fatal instead of a silent note:
the contract is 'starts at the remote's latest push', and cutting a
branch from stale refs would break it quietly.
Hardening around the same contract: the branch start falls back
origin/HEAD -> origin/main -> origin/master when the head pin is
missing, and a stale agent/<name> branch left behind by a deleted
agent is reset to the remote tip (-B) instead of silently shadowing
it - while -B's refusal to move a branch checked out elsewhere keeps
in-flight agents protected. Root/subdir placements (schedule firings,
mise-init) keep the fast-forward behavior for their shared tree, as
does the explicit sync command.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
A spawn with no explicit placement ran the agent in the shared
project-root checkout. The root only fast-forwards while it is parked
on the default branch, so the moment one agent left it on a feature
branch, every later no-placement spawn — the mobile app always, the web
form whenever the branch field was blank — started from a stale tree:
the operator's freshly pushed commit was fetched into origin/* but
invisible to the agent.
Operator spawns on a git root now default to a worktree on
agent/<name>, cut from origin/HEAD like any explicit worktree spawn, so
a new agent always starts at the remote's latest push and gets the
per-agent isolation the README promises. Schedule firings opt out
(auto_worktree=False): their continuity convention is a state file
living in the root tree across runs. The mise-init bootstrap and
non-git roots keep root placement unchanged.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
The Running / Waiting / Done cards now navigate to a new agent-list
screen pre-filtered to that bucket, using exactly the same grouping as
the counts so the number tapped is the number listed. The list shows
every agent row the API knows — an agent that hasn't dropped a
checkmark yet is visible the moment it spawns, with a status badge, its
age, and a live last-output line while it works. Rows open the agent
detail screen, whose back button now returns to wherever the detail was
opened from (fleet or the list).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
Settings → Manage → Activity lists every control command with status
filter chips, when/where it ran (project, agent, claiming worker or
'unclaimed'), and tap-to-expand result/error text; a Sweep CI button
enqueues the global poll. The list auto-refreshes every 5s while open,
so a login_start or spawn can be watched to completion — this was the
one web dashboard page missing from the phone, and exactly the view
needed to diagnose a stuck command.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
Phase 4 (1517e4d) deleted the tmux launch path and with it the only
caller of claude_config.ensure_onboarded, so agent working dirs -
every fresh worktree is a brand-new path - were never pre-trusted in
~/.claude.json. Headless 'claude -p' runs then wedge or refuse on the
workspace-trust dialog with nobody at a TTY to accept it.
Spawn and resume now mark onboarding complete and trust the working
dir right before launch, next to the settings/claude_gen
materialization. Resume matters independently: a cross-worker resume
can land in a container whose ~/.claude.json has never seen the dir.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
Agents narrate far more than anyone reads: the transcript is not the
deliverable, and prose there is spent tokens burying information where
no one looks. The new skill routes each kind of output to its store —
work happens through tool calls; a minimized NOTES.md ledger (one
bullet per action, committed with the work) records what happened and
how; problems and causes go to memory; status goes to the final
checkpoint-sized message the Stop hook captures onto the checkmark; and
questions go through the question tool, which reaches the operator as a
push notification and an answer prompt in the web and mobile apps
instead of stalling silently in the transcript.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
Seven skills now travel with Handler itself and are seeded into the
managed skill store when the API boots: gate recovery, the test
authorship standard, checkpoint quality, memory discipline, mise-task
rules, scheduled-run continuity, and secrets hygiene. They cover the
judgment layer the hard gates cannot enforce — the gates check that
tests pass, not that an agent responded to a blocked gate sensibly or
kept credentials out of logs.
Seeding is idempotent by name: existing rows are never touched, so
operator edits and enable/disable choices survive every upgrade;
deleting a built-in restores it as shipped on the next start (disable
is the supported off-switch). Rows are created shared and enabled, so
they sync to every worker like any managed skill and remain
admin-editable from the dashboard or the mobile app. Seeding failures
log and never block the API from serving.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01731mKtVzsfeT4Vi3TvkR48
Keep-a-Changelog format, seeded with this PR's release: the pi harness
feature set, the control-image changes (Node 22, pi baked in), the additive
0015_model_harness migration, and a numbered rollout checklist (image order,
env, rollback, volume invariants) so the deployment strategy can be verified
against it after merge. README links to it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KdGv3u3DfTsP1S188KDhVH