feat(web): a coding agent can be archived and brought back, stopping a running turn first
Merged
DA
7d ago+415+415+012345678901234567890123456789-84-84-01234567890123456789
What
The server side of archiving a coding agent and bringing it back (Dean: "a way to clean up agents within a plan, very helpful for failed agents"). This is PR 1 of 2; the desktop UI follows in a stacked PR.
- Schema:
coding_agent.archived_at, a nullabletimestamptz(additive migration0091_coding_agent_archived, folded intocoding-agent-schema.ts). The release still serving never reads the column, so this ships in one step under the expand/contract rule. - Archive and restore:
PATCH /api/agents/{id}takes{ "archived": true | false }, next to the rename and model-choice bodies it already reads. The answer is the agent's summary, which now carriesarchivedAtwhile it is archived. - A running agent is stopped first. Archiving one with a turn under way goes through the same path a Stop takes: eve's cancel of that turn, then the row's stamp.
stopEndpointand the archive now share onestoppedTurnhelper. If eve can't be reached the archive is refused as unavailable and nothing is stamped, so an agent is never archived while it is still working. - Nothing archived keeps working out of sight. An agent still starting, or with a message waiting on its turn, has no turn a Stop can name yet. Its archive is refused as
agent_not_ready(409, the desktop says try again) until that turn exists or has ended. A failed agent (past the starting grace) archives normally. An archived agent also takes no message: the shared steer path refuses it as not found, which covers the route, the voice tool, and the inbox hand-off. - Lists hide archived agents by default.
GET /api/plans/{id}/agentsleaves them out unless?archived=includeasks; this is the same vocabulary the plan list uses for archived plans (CODING_AGENT_LIST_QUERY/CODING_AGENT_LIST_ARCHIVED). Every existing reader keeps today's view:- the sidebar's per-plan agent briefs (
listPublishedCodingAgents), which drive the hover card and the working/PR marks; - the voice brain's
list_coding_agentstool.
- the sidebar's per-plan agent briefs (
- Reads by id still find an archived agent (transcript, pull request), so its transcript and PR are kept. No bulk archive and no permanent delete, as scoped.
- Wire goldens:
codingAgentArchiveRequestSchemais recorded, and the summary/answer/list goldens gain the optionalarchivedAt(regenerated withLUKE_UPDATE_FIXTURES=1). The only change is one added optional property. tests/inbox-migration.test.tsused the current store's Start against a database migrated only to 0087, so any newcoding_agentcolumn breaks it (#2235 will hit the same thing). Its setup now writes the agent row naming only the columns that existed before 0088, the same way it already writes the old inbox rows.
Tests (PGlite, tests/coding-agents-app.test.ts)
- An archived agent:
- is left out of the plan's list and of the plans' briefs;
- is listed with its stamp under
archived=include; - still reads its transcript by id;
- is listed again after
archived: false; - is not found for another account;
- refuses a message (404, eve asked nothing);
- is refused as not ready while still starting.
- Archiving an agent whose turn runs cancels that turn at eve first, so it reads cancelled and archived. When eve refuses the cancel, the archive is unavailable and the agent stays running and unarchived.
tests/drizzle-schema.test.tschecks the new column againstinformation_schema.
./scripts/check.sh exits 0 locally. This PR touches no macOS UI, and verify.sh was not run (Linux VM).
š¤ Generated with Claude Code