Skip to content

Let an agent archive its own session - #972

Merged
selfcontained merged 3 commits into
mainfrom
agt_9e362f1d3dd3/build-agent-self-archive
Aug 18, 2026
Merged

Let an agent archive its own session#972
selfcontained merged 3 commits into
mainfrom
agt_9e362f1d3dd3/build-agent-self-archive

Conversation

@selfcontained

@selfcontainedselfcontained commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Idea: agent-self-archiveGive an agent the ability to archive itself.

dispatch_archive_agent was scoped to direct children, so an agent that had finished its work and reported it had no way to retire itself — archiving was only ever something done to it from outside. It now accepts the caller's own ID.

# before
$ tools/call dispatch_archive_agent {"agentId": "<its own id>"}
"You can only archive agents you launched via dispatch_launch_agent or dispatch_launch_persona."
# after
$ tools/call dispatch_archive_agent {"agentId": "<its own id>"}
Archiving agent "self archiver" (agt_selfa). This is your own session — it will stop momentarily.

Same tool, same rules, one path

No new tool and no new guardrails. Archiving yourself is not more dangerous than archiving a child — the same worktree can be lost either way — so the rules don't fork: still rejects an agent you neither launched nor are, still defaults cleanupWorktree to auto, so a worktree with unmerged or uncommitted work is preserved on disk.

The one thing self-archiving genuinely needs is that the teardown can't be awaited: the session being stopped is the one waiting for the response. So the handler claims the archive, answers, and runs teardown in the background — gated on the response actually having been written, which the MCP route hands down from the hijacked reply's finish/close. Holding teardown until the response flushes costs a child archive nothing, so both targets take the identical path and the handler has no branch in it.

agentLifecycleRuntime.beginBackgroundArchive owns claim → publish → background teardown → shutdown tracking, so the archive is still awaited at shutdown and still publishes its UI events.

Two guards came out of review. A job agent can't archive itself — a job agent leaves by reporting its outcome, and the run's terminal state is what auto-archives it; archiving around that made the job monitor report the run as crashed and page whoever the job notifies. dispatch_archive_agent now rejects any target with an active run and names job_complete / job_failed / job_needs_input. That applies to both targets, and only to agent-initiated archives — the HTTP archive route and the job runner's own auto-archive are untouched, since a human still needs to be able to kill a job agent that has genuinely gone wrong.

Second, handleLaunchAgent now refuses to create a child under a parent whose status is archiving. Only review children cascade, so a child launched after the claim would be orphaned the instant it started. That window existed before this change — a child being archived stays alive through its own teardown — but self-archive widens it, since the parent is deliberately kept alive until its response is written and is the one holding the MCP connection. The guard applies to every archive target, not only self.

One behavioural change to archiving a child: the tool returns archiving: true when teardown is scheduled rather than archived: true once it finished — matching what the HTTP archive route already reports.

Verified on a dev instance

Over the real agent-scoped MCP route:

checkresult
tools/listone archive tool, unchanged name
agent you neither launched nor arerejected
child you launchedarchives, as before
your own IDarchives; response says the session is stopping
worktree with 1 uncommitted filepreserved on disk after teardown (auto default)
response-before-teardownserver log: request completed, teardown 837ms later
launch inside the grace windowrefused; zero orphan rows created
job agent self-archivingrefused, pointing at job_complete; run and agent intact
DELETE /api/v1/agents/:id on a job agentstill 202, still archives (human path unaffected)
job_complete on a job agentrun goes terminal, agent auto-archived as before

Review

Three personas (release-readiness, backend-security, infra) reviewed this specifically for release side effects.

  • release-readiness: approved, no findings — no schema/config change, no rollback incompatibility.
  • backend-security: two findings. The orphan-child window is fixed above. The second reported a job run left stuck running after its agent self-archived; measured on the dev stack, a run the JobService is monitoring goes to crashed in under 2s (agentSessionCrashed returns true once getAgent finds no row), and reconcileActiveRuns restarts monitors at boot. The observed row had been inserted outside that lifecycle; the reviewer confirmed and dismissed it. The underlying concern was still worth acting on, though — crashed is a false alarm for a deliberate exit — hence the active-run guard above.
  • infra: flagged that finish is kernel handoff, not client receipt, so the 750ms settle can't be a hard bound. Accepted as a best-effort UX race rather than a correctness one — the archive is claimed and durable before the response, and MCP offers no client acknowledgement to wait on. Reviewer withdrew it and advised keeping 750ms.

Checks

pnpm run check, pnpm run test (3780 passing), pnpm run finalize:web green.

An earlier revision of this PR added a second tool plus reason/force/live-children guardrails and the locking to make those guardrails correct — ~1.3k lines. Those rules didn't apply to archiving a child, so they had no business applying only to archiving yourself; dropping them took the change to ~156 lines of src.

dispatch_archive_agent was scoped to direct children, so an agent that had
finished and reported had no way to retire itself — archiving was only ever
something done to it from outside. It now accepts the caller's own ID.
Same tool, same rules, one path. The only thing self-archiving needs that
archiving a child does not is that the teardown cannot be awaited: the session
being stopped is the one waiting for the response. So the handler claims the
archive, answers, and runs teardown in the background — gated on the response
actually having been written, which the MCP route now hands down from the
hijacked reply's finish/close. Holding teardown until the response flushes
costs a child archive nothing, so both targets take the identical path and the
handler has no branch in it.
That backgrounding is the one behavioural change to archiving a child: the tool
returns `archiving: true` when the teardown has been scheduled rather than
`archived: true` once it finished, matching what the HTTP archive route already
does. agentLifecycleRuntime.beginBackgroundArchive owns the claim → publish →
background teardown → shutdown tracking, so the archive is still tracked at
shutdown and still publishes its UI events.
Everything else is unchanged: still rejects agents you neither launched nor
are, still defaults cleanupWorktree to 'auto', so a worktree holding unmerged
or uncommitted work is preserved on disk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@selfcontained
selfcontainedforce-pushed the agt_9e362f1d3dd3/build-agent-self-archive branch from 686b6bc to bdc5367CompareAugust 17, 2026 16:03
selfcontainedand others added 2 commits August 17, 2026 12:53
An archive stops the parent moments from now and only cascades to review
children, so a child launched after the claim is orphaned the instant it
starts. The window existed before — a child being archived stays alive through
its own teardown — but self-archive widens it: the parent is deliberately kept
alive until its response is written, and it is the one holding the MCP
connection, so it can issue the launch itself.
Guarded in handleLaunchAgent for every archive target, not only self.
Verified on the dev instance: self-archive, then dispatch_launch_agent inside
the grace window, is refused and no orphan row is created.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A job agent leaves by reporting its outcome; the run's terminal state is what
auto-archives it. Archiving around that path stops the session mid-run, so the
job monitor reports the run as crashed and pages whoever the job notifies —
a false alarm for what was a deliberate exit.
dispatch_archive_agent now rejects a target with an active run and names the
tools that actually end one. Uniform across targets: a job agent cannot retire
itself this way, and neither can a parent archive a child mid-run.
Scoped to agent-initiated archives only. The HTTP archive route and the job
runner's own auto-archive are untouched, since a human still needs to be able
to archive a job agent that has genuinely gone wrong.
Verified on the dev instance: the self-archive attempt is refused with the run
and agent intact; DELETE /api/v1/agents/:id still archives the same agent
(202); and job_complete still drives the run terminal and auto-archives its
agent.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@selfcontained
selfcontained merged commit b6bb08f into mainAug 18, 2026
1 check passed
@selfcontained
selfcontained deleted the agt_9e362f1d3dd3/build-agent-self-archive branch August 18, 2026 01:34
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@selfcontained