| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
…anomalyco#32425) Ports the subagent interrupt mechanism from the upstream anomalyco/opencode PR anomalyco#32425. Allows a parent session to steer, cancel, or abort a running subagent session mid-turn. Three interrupt modes: - task_steer: injects a <steer> frame into the subagent's context and continues execution. The subagent reads the guidance and can adjust course. - task_cancel: injects a <cancel> frame and starts a grace countdown (CANCEL_GRACE_TURNS turns). After grace expires the loop breaks cleanly. - task_abort: hard-stops the session via SessionRunState.cancel. Implementation: - interrupt.ts: Interrupt service with consume(), recordTerminal(), renderMarker(), renderCancel(), renderSteer(), CANCEL_GRACE_TURNS. Stores pending interrupts in a per-session in-memory queue. - task-interrupt.ts: TaskAbortTool, TaskCancelTool, TaskSteerTool definitions that parent sessions can call to interrupt child sessions. - prompt.ts: added interrupt consume loop at runLoop turn boundary. Only fires when session.parentID is set AND OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT flag is enabled. Tracks cancelGrace countdown. - registry.ts: registers interrupt tools in tool catalog, conditionally adds steer/cancel/abort to builtin tool list behind the experimental flag. Added SessionRunState, SessionStatus, Permission, and Interrupt to the defaultLayer dependency chain. - flag.ts: added OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT feature flag. Enable with: OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT=1 or OPENCODE_EXPERIMENTAL=1. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…anomalyco#32425) Ports the subagent interrupt mechanism from the upstream anomalyco/opencode PR anomalyco#32425. Allows a parent session to steer, cancel, or abort a running subagent session mid-turn. Three interrupt modes: - task_steer: injects a <steer> frame into the subagent's context and continues execution. The subagent reads the guidance and can adjust course. - task_cancel: injects a <cancel> frame and starts a grace countdown (CANCEL_GRACE_TURNS turns). After grace expires the loop breaks cleanly. - task_abort: hard-stops the session via SessionRunState.cancel. Implementation: - interrupt.ts: Interrupt service with consume(), recordTerminal(), renderMarker(), renderCancel(), renderSteer(), CANCEL_GRACE_TURNS. Stores pending interrupts in a per-session in-memory queue. - task-interrupt.ts: TaskAbortTool, TaskCancelTool, TaskSteerTool definitions that parent sessions can call to interrupt child sessions. - prompt.ts: added interrupt consume loop at runLoop turn boundary. Only fires when session.parentID is set AND OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT flag is enabled. Tracks cancelGrace countdown. - registry.ts: registers interrupt tools in tool catalog, conditionally adds steer/cancel/abort to builtin tool list behind the experimental flag. Added SessionRunState, SessionStatus, Permission, and Interrupt to the defaultLayer dependency chain. - flag.ts: added OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT feature flag. Enable with: OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT=1 or OPENCODE_EXPERIMENTAL=1. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add an experimental subagent-interrupt capability (behind OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT): an Interrupt service with pending/terminal state, task_steer/task_cancel/task_abort tools (gated by a new `interrupt` permission), a POST /session/:id/interrupt endpoint, and a turn-boundary consume in the prompt loop that injects an escaped steer/cancel frame plus a visible transcript marker. Cancel records a terminal reason and force-breaks after a grace window; abort writes a marker, records the terminal, and cancels the BackgroundJob immediately. The TUI gains an esc-with-reason flow, footer button, and distinct interrupt markers. Ported from anomalyco#32425 (adapted for the Effect layer; merged with the prior task.ts/registry.ts/prompt.ts changes from anomalyco#29447/anomalyco#32122/anomalyco#12520/anomalyco#32192/anomalyco#19961). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an experimental subagent-interrupt capability (behind OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT): an Interrupt service with pending/terminal state, task_steer/task_cancel/task_abort tools (gated by a new `interrupt` permission), a POST /session/:id/interrupt endpoint, and a turn-boundary consume in the prompt loop that injects an escaped steer/cancel frame plus a visible transcript marker. Cancel records a terminal reason and force-breaks after a grace window; abort writes a marker, records the terminal, and cancels the BackgroundJob immediately. The TUI gains an esc-with-reason flow, footer button, and distinct interrupt markers. Ported from anomalyco#32425 (adapted for the Effect layer; merged with the prior task.ts/registry.ts/prompt.ts changes from anomalyco#29447/anomalyco#32122/anomalyco#12520/anomalyco#32192/anomalyco#19961). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@iceteaSA I don't think your very nice PR or my slimmer one will ever get any attention :( |
Sorry, something went wrong.
Yeah, I have a couple of other nice ones as well, won't hold my breath. |
Sorry, something went wrong.
Add an experimental subagent-interrupt capability (behind OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT): an Interrupt service with pending/terminal state, task_steer/task_cancel/task_abort tools (gated by a new `interrupt` permission), a POST /session/:id/interrupt endpoint, and a turn-boundary consume in the prompt loop that injects an escaped steer/cancel frame plus a visible transcript marker. Cancel records a terminal reason and force-breaks after a grace window; abort writes a marker, records the terminal, and cancels the BackgroundJob immediately. The TUI gains an esc-with-reason flow, footer button, and distinct interrupt markers. Ported from anomalyco#32425 (adapted for the Effect layer; merged with the prior task.ts/registry.ts/prompt.ts changes from anomalyco#29447/anomalyco#32122/anomalyco#12520/anomalyco#32192/anomalyco#19961). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an experimental subagent-interrupt capability (behind OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT): an Interrupt service with pending/terminal state, task_steer/task_cancel/task_abort tools (gated by a new `interrupt` permission), a POST /session/:id/interrupt endpoint, and a turn-boundary consume in the prompt loop that injects an escaped steer/cancel frame plus a visible transcript marker. Cancel records a terminal reason and force-breaks after a grace window; abort writes a marker, records the terminal, and cancels the BackgroundJob immediately. The TUI gains an esc-with-reason flow, footer button, and distinct interrupt markers. Ported from anomalyco#32425 (adapted for the Effect layer; merged with the prior task.ts/registry.ts/prompt.ts changes from anomalyco#29447/anomalyco#32122/anomalyco#12520/anomalyco#32192/anomalyco#19961). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add an experimental subagent-interrupt capability (behind OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT): an Interrupt service with pending/terminal state, task_steer/task_cancel/task_abort tools (gated by a new `interrupt` permission), a POST /session/:id/interrupt endpoint, and a turn-boundary consume in the prompt loop that injects an escaped steer/cancel frame plus a visible transcript marker. Cancel records a terminal reason and force-breaks after a grace window; abort writes a marker, records the terminal, and cancels the BackgroundJob immediately. The TUI gains an esc-with-reason flow, footer button, and distinct interrupt markers. Ported from anomalyco#32425 (adapted for the Effect layer; merged with the prior task.ts/registry.ts/prompt.ts changes from anomalyco#29447/anomalyco#32122/anomalyco#12520/anomalyco#32192/anomalyco#19961). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
A hard stop is what we need for the subagent session, manually by user or by the lead agent. |
Sorry, something went wrong.
|
@bukit-kronik that's what this does — three levels, since "stop it" turns out to mean different things depending on how stuck the subagent is:
All three are parentage-checked, so an agent can only interrupt its own descendants. The distinction that took a while to get right: task_cancel only lands at a turn boundary, so a subagent wedged inside a long-running tool call (a 10-minute build, say) won't stop until that tool returns. task_abort is the answer there — it's the difference between "stop when you can" and "stop now." On the lead-agent-vs-user split you raise: the tools are available to whichever agent spawned the subagent, so a lead agent can stop its own children. A user-facing hard stop is a separate surface — the TUI would need to expose it — and isn't in this PR. Running in a fork for a while now; the abort path is the one that gets used, usually on a subagent looping without progress. @tobwen on attention — no movement since June, but the PR's been rebased onto current dev a few times and the tests still pass, so it stays mergeable if a maintainer picks it up. |
Sorry, something went wrong.
Add an experimental subagent-interrupt capability (behind OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT): an Interrupt service with pending/terminal state, task_steer/task_cancel/task_abort tools (gated by a new `interrupt` permission), a POST /session/:id/interrupt endpoint, and a turn-boundary consume in the prompt loop that injects an escaped steer/cancel frame plus a visible transcript marker. Cancel records a terminal reason and force-breaks after a grace window; abort writes a marker, records the terminal, and cancels the BackgroundJob immediately. The TUI gains an esc-with-reason flow, footer button, and distinct interrupt markers. Ported from anomalyco#32425 (adapted for the Effect layer; merged with the prior task.ts/registry.ts/prompt.ts changes from anomalyco#29447/anomalyco#32122/anomalyco#12520/anomalyco#32192/anomalyco#19961). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
This needs to be added. Whether it's this PR or another. We can't just let subagents go rogue or be called accidentally without a mechanism for canceling them. |
Sorry, something went wrong.
Lets a parent agent (via tools) or the human (via TUI esc) steer, gracefully cancel, or hard-abort a single running Task subagent mid-run, without affecting the parent or sibling subagents. A per-instance Interrupt service holds one pending interrupt per child plus a terminal record; the child consumes it at its own runLoop turn boundary. Steer injects a frame and the child adapts and continues; cancel injects a frame, records a terminal, and force-breaks within a grace window; abort cancels the BackgroundJob immediately. Interrupt tools reach any descendant through a bounded ancestry walk that fails closed off the caller's subtree. Off by default behind OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT.
| Back | FazBrowse Home | New Git URL |
Issue for this PR
Closes #38966
Related: #32431 (stuck subagents can't be nudged — the steer case, but this PR does not claim to close it without demonstrating stuck-session recovery), #27511 (suspend/resume — overlapping on graceful stop, but resumable suspension is a different guarantee than cancel).
Type of change
What does this PR do?
Lets a parent agent (via tools) or the human (via TUI esc) steer, gracefully cancel, or hard-abort a single running Task subagent mid-run, without affecting the parent or sibling subagents. Off by default behind OPENCODE_EXPERIMENTAL_SUBAGENT_INTERRUPT.
A small per-instance Interrupt service holds one pending interrupt per child plus a terminal record. The child consumes it at its own runLoop turn boundary (session/prompt.ts): steer injects a <steer> frame and the child adapts and continues; cancel injects a <cancel> frame, records a terminal, and force-breaks within a small grace window; abort records a terminal and cancels the BackgroundJob immediately. Cancel is turn-boundary-soft — a child inside a long-running tool call only breaks once that tool returns, so task_abort is the hard fallback.
Surfaces:
Interrupt tools reach any descendant, not just direct children, through a bounded ancestry walk that fails closed off the caller's subtree — otherwise a runaway grandchild is unreachable from the only session that can see it. They also accept a slug task_id; a resolved slug still passes the same ancestry check, so a process-global handle cannot be used to reach outside the subtree.
Every interrupt drops a distinct visible marker into the subagent transcript — ⊘ Steered / Cancelled / Aborted by user or by parent, with the reason — attributed to its origin (a human TUI action shows "by user"; an agent tool shows "by parent"). Untrusted reasons are XML-escaped at every sink and length-capped; both the tool task_id path and the HTTP endpoint verify the target is the caller's own subagent before doing anything.
Why it can't hang or deadlock: the child only reads a pending interrupt at a turn boundary and never blocks on one, so there's no new wait. Cancel always terminates (grace window → force-break, or task_abort as the hard fallback). With the flag off the tools are unregistered, the endpoint rejects, and TUI esc falls back to its existing behavior.
How did you verify your code works?
New tests (no mocks): the Interrupt registry (request/consume/terminal/clear, cancel-overrides-steer, reason escaping, length cap), the runLoop consume + frame injection, the three tools (not_found / already_finished / running, permission routing, origin attribution), the ancestry walk including its exact hop bound, slug resolution still passing the descendant check, the abort marker for a model-less subagent (model derived from the latest user message), and a frame-breakout reason that must reach the model only escaped.
Screenshots / recordings
Subagent controls + interrupt menu
Steer — course-correct, then the subagent continues
Cancel — graceful wrap-up within the grace window
Abort — immediate hard stop
Checklist