Add interrupt_agent / followup_task (CL-6997) - #619
Merged
TheGreatAxios merged 2 commits intoAug 24, 2026
Merged
Conversation
Second half of reusable worker sessions: interrupt_agent stops a retained worker's current turn while keeping the session and its context reusable, and followup_task sends new work into a retained session's existing agent, reusing its prior context and tool outputs. interrupt_agent fires a signal scoped only to the in-flight agent.send() call, never close() — it cannot hit the close()-ordering workdir-lock issue tracked separately (CL-6984). There is no lower- level stop primitive in the vendored agent for the reactor cycle itself, so this is an approximation: it stops the caller from waiting, not the worker's compute, which keeps running in the background until it finishes naturally. Both verbs are gated to orchestrator tiers via the existing FLEET_VERBS / assertTierMayMountFleetVerb mechanism, denied to leaves.
lifecycle-tools.test.ts only exercised interrupt_agent/followup_task against fake registered closures at the tool/store layer, not run.ts's real onAgentReady wiring where followup calls agent!.send() on the same live agent object. Add a test that drives runSubAgent end to end with createAgentWithLiveToolDispatch replaced by a stub Agent, proving the followup send after an interrupt lands on the same agent instance (one construction, one shared message log) rather than a rebuilt one.
TheGreatAxios
force-pushed
the
cl-6997-interrupt_agent-and-followup_task-the-second-half-of
branch
from
August 24, 2026 15:05
6dc9513 to
efefe8e
Compare
TheGreatAxios
enabled auto-merge (squash)
August 24, 2026 15:05
TheGreatAxios
added a commit
that referenced
this pull request
Aug 27, 2026
* Add interrupt_agent / followup_task (CL-6997) Second half of reusable worker sessions: interrupt_agent stops a retained worker's current turn while keeping the session and its context reusable, and followup_task sends new work into a retained session's existing agent, reusing its prior context and tool outputs. interrupt_agent fires a signal scoped only to the in-flight agent.send() call, never close() — it cannot hit the close()-ordering workdir-lock issue tracked separately (CL-6984). There is no lower- level stop primitive in the vendored agent for the reactor cycle itself, so this is an approximation: it stops the caller from waiting, not the worker's compute, which keeps running in the background until it finishes naturally. Both verbs are gated to orchestrator tiers via the existing FLEET_VERBS / assertTierMayMountFleetVerb mechanism, denied to leaves. * Add live-agent regression guard for followup_task (CL-6997) lifecycle-tools.test.ts only exercised interrupt_agent/followup_task against fake registered closures at the tool/store layer, not run.ts's real onAgentReady wiring where followup calls agent!.send() on the same live agent object. Add a test that drives runSubAgent end to end with createAgentWithLiveToolDispatch replaced by a stub Agent, proving the followup send after an interrupt lands on the same agent instance (one construction, one shared message log) rather than a rebuilt one.
TheGreatAxios
added a commit
that referenced
this pull request
Aug 27, 2026
* Add interrupt_agent / followup_task (CL-6997) Second half of reusable worker sessions: interrupt_agent stops a retained worker's current turn while keeping the session and its context reusable, and followup_task sends new work into a retained session's existing agent, reusing its prior context and tool outputs. interrupt_agent fires a signal scoped only to the in-flight agent.send() call, never close() — it cannot hit the close()-ordering workdir-lock issue tracked separately (CL-6984). There is no lower- level stop primitive in the vendored agent for the reactor cycle itself, so this is an approximation: it stops the caller from waiting, not the worker's compute, which keeps running in the background until it finishes naturally. Both verbs are gated to orchestrator tiers via the existing FLEET_VERBS / assertTierMayMountFleetVerb mechanism, denied to leaves. * Add live-agent regression guard for followup_task (CL-6997) lifecycle-tools.test.ts only exercised interrupt_agent/followup_task against fake registered closures at the tool/store layer, not run.ts's real onAgentReady wiring where followup calls agent!.send() on the same live agent object. Add a test that drives runSubAgent end to end with createAgentWithLiveToolDispatch replaced by a stub Agent, proving the followup send after an interrupt lands on the same agent instance (one construction, one shared message log) rather than a rebuilt one.
TheGreatAxios
deleted the
cl-6997-interrupt_agent-and-followup_task-the-second-half-of
branch
August 28, 2026 00:09
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
interrupt_agent({ target })stops a retained worker's current turn while keeping the session and its context reusable (distinct from the permanentclose_agent).followup_task({ target, message })sends new work into a retained session's existing agent, reusing its prior context and tool outputs rather than starting fresh.FLEET_VERBS/assertTierMayMountFleetVerbmechanism, denied to leaves.Safety note on interrupt_agent / CL-6984
The vendored
Agentobject exposes nointerrupt()/abort()primitive separate fromclose()— the reactor's own internalabort()(vendor/intx-inference/src/reactor.ts) is not exposed on the publicAgentinterface; onlyclose()reaches it, andclose()is the codepath with the known close()-ordering workdir-lock risk (CL-6984, still open).interrupt_agenttherefore does not callclose()at all. It fires a dedicatedAbortControllersignal scoped only to the in-flightagent.send()call (the samesignaloptionAgent.sendalready documents), combined viaAbortSignal.anyalongside the run's own controller only for that one send. This rejects the caller's wait on that turn without touchingclose(),disposeSubAgentSession, or the workdir lock — so it cannot wedge the lock the way a stuckclose()can.The tradeoff, stated plainly: this is an approximation, not a true hard-stop. Per the vendored send-queue's own documentation, aborting a signal mid-cycle rejects the caller's promise immediately but the reactor cycle keeps running in the background until it finishes naturally; there is no lower-level primitive in the vendored code to stop that compute without closing the session.
followup_task's newagent.send()call queues behind that cycle via the vendored send-queue's own FIFO ordering — no second continuation mechanism was built.Regression coverage for real agent reuse
lifecycle-tools.test.tsonly proves interrupt/followup behave correctly against fake registered closures at the tool/store layer — it doesn't touch run.ts's realonAgentReadywiring wherefollowupcallsagent!.send()on the same live agent object. Addedsrc/subagent/followup-live-agent.test.ts, using option 1 (stub live agent through the real run.ts flow) rather than the call-count fallback: it replacescreateAgentWithLiveToolDispatchwith a stubAgentviawithMockedModuleDuring, drives the realrunSubAgentend to end (real tool assembly, real dispatch brief, real onAgentReady/interrupt/followup closures), interrupts the in-flight send, then calls the capturedfollowuphandle directly, and asserts on the stub's own shared message log and a construction counter — proving one agent instance was built and reused, not rebuilt. Chose option 1 because it exercises the actual code path the regression would break, not just an indirect proxy for it; the construction-count check from option 2 is folded in as an extra assertion rather than the whole test.Test plan
bun test src/subagent/lifecycle-tools.test.ts src/subagent/authority.test.ts src/subagent/followup-live-agent.test.tsbun run check(lint, typecheck, build, full test suite) — 5340 pass, 0 fail