Skip to content

Agent steering, interruption, and completion

This page owns the runtime boundaries for inserting work into an active main or agent loop, interrupting or cancelling that work, and deciding when agent/task state is complete enough to notify or evict.

It intentionally does not repeat adjacent owners:

Short answer

Claude Code never mutates an in-flight provider request. New work enters at a boundary owned by the target:

flowchart TD
Input[typed prompt / SDK frame / SendMessage / completion / cron] --> Route{target and urgency}
Route -->|main now| AbortMain[abort active main turn]
Route -->|main next| MainQueue[next-priority queue]
Route -->|main later| LaterQueue[later-priority queue]
Route -->|live Agent| AgentQueue[pendingMessages]
Route -->|teammate| Mailbox[team mailbox / pending user queue]
AbortMain --> MainQueue
MainQueue --> Fold[fold between tool batches or next top-level drain]
LaterQueue --> Drain[later top-level drain]
AgentQueue --> AgentBoundary[next Agent attachment pass]
Mailbox --> TeamBoundary[turn-end or idle poll]
Fold --> Execute[normal model/tool permission loop]
Drain --> Execute
AgentBoundary --> Execute
TeamBoundary --> Execute
Execute --> Terminal{terminal state?}
Terminal -->|no| Route
Terminal -->|yes| Notify[deliver result / task notification]
Notify --> Evict{retention and keepalive allow eviction?}

An interrupt aborts current work but does not imply that queued work disappeared. Cancelling one queued UUID is different from cancelling a correlated control request, and both are different from stopping an agent/task lifecycle.

Source anchors

Semantic aliasApproximate location in cli.renamed.jsExact symbol or stringMeaning
MainPriorityQueue~256,890–257,215OQn = {now:0,next:1,later:2}, CSg()Orders main-thread prompts and notifications by priority, FIFO within a tier.
MidTurnQueueFold~462,520–462,610getCommandsByMaxPriority("next"), registerFoldInFlight(), queued_commandFolds eligible now/next input between tool batches before another provider call.
InteractivePromptInsert~860,880–860,930h7o(), hasInterruptibleToolInProgressQueues typed input and, for an interruptible active tool, aborts the current turn first.
InteractiveTurnCancel~924,380–924,430onPreRestore(), user-cancel, remote-cancelCancels the active interactive turn without declaring every detached worker stopped.
SdkInterruptReceipt~943,331–943,350interrupt, still_queuedAborts the active headless/SDK turn and reports UUID-stamped work that survives.
QueuedMessageCancel~954,118cancel_async_messageRemoves one message only while it remains queued.
CorrelatedControlCancel~943,000–944,999control_cancel_request, request_idCancels one pending host/runtime control operation.
AgentSteeringQueue~434,301y6e(), iho()Appends and atomically drains ordinary Agent steering messages.
AgentPendingAttachment~571,670getAgentPendingMessageAttachments()Converts drained Agent messages into queued_command attachments.
TeammateWorkInterrupt~340,439, ~391,860–392,330PDu(), currentWorkAbortControllerAborts one in-process teammate turn while preserving the teammate lifecycle.
IdleTaskClaim~391,728–391,770A7g(), mju()Lets an idle teammate lock-claim the first eligible pending task.
AgentCompletionNotification~434,320ERt(), task-notificationDelivers terminal Agent state/results to the owner/main queue.
SdkTaskTerminalPredicatefunction R5H(H)completed, failed, cancelledDefines terminal states for the SDK/MCP task protocol.
InternalTaskEviction~350,180–350,363notified, retain, evictAfter, keepalive reasonsSeparates terminal state from safe registry eviction.
UserStopResumeGuard~413,414–414,470AgentStoppedByUserErrorPrevents a later message from silently resurrecting explicitly stopped work.

Main-session insertion priorities

The main queue has three priorities:

PriorityTypical useVisibility boundaryPreemption
nowUrgent SDK/headless steeringHighest-priority next drain; may fold before the next recursive provider call.Queue watchers abort an active main turn so the queued entry can run next.
nextTyped prompts, main-directed Agent messages, completion notificationsMay fold as queued_command input between tool batches; otherwise enters the next top-level drain.Does not normally abort the current request.
laterCron wakeups and low-urgency notificationsExcluded from mid-turn folding and waits for a later top-level drain.No.

Dequeue selects the earliest entry in the highest-priority non-empty tier. Compatible top-level prompts can be coalesced, so queue identity and eventual provider-turn identity are not always one-to-one.

Mid-turn folding is cooperative. The runtime first turns eligible entries into attachments, then removes them from the queue. An abort during attachment production leaves the entries available for a later drain rather than silently losing them.

Interactive input while work is active

When the TUI receives a prompt during an active query, it normally queues the prompt at next. If an interruptible tool is active, submission first aborts the current turn and retains the new prompt for the following drain. Pressing Escape uses the interactive cancellation path (user-cancel, or remote-cancel from the remote surface); it cancels the active turn, not every detached Agent or scheduled task associated with the session.

Target-specific steering

TargetInsertion pathWhen the receiver sees it
Main conversationnow/next/later queueMid-turn fold or a later top-level drain.
Live ordinary AgentpendingMessages[] via y6e()getAgentPendingMessageAttachments() on a later Agent model/tool iteration.
In-process teammatependingUserMessages or team mailboxTurn-end check or 500 ms idle poll.
External pane teammate / leadTeam mailboxOne-second inbox poll; busy sessions buffer until the turn boundary.
Stopped/evicted AgentTranscript-backed resume pathFirst resumed turn, unless a user-stop marker forbids resumption.

These paths carry task direction, not user authority. When inserted work later produces a tool call, normal permissions, hooks, and sandbox checks run again.

For transport acknowledgement, addressing, pins, mailbox persistence, peer-session branches, and reply semantics, use Agent messaging and communication.

Interrupt, cancel, and stop are different

flowchart LR
Action{Action} -->|interrupt / Esc| Abort[Abort active turn controller]
Action -->|cancel_async_message| CancelQueued[Remove one still-queued message UUID]
Action -->|control_cancel_request| CancelControl[Cancel one pending request_id]
Action -->|TaskStop / kill| Stop[Stop agent or task lifecycle]
Abort --> Survive[Queued work can survive]
CancelQueued --> Race[Already dequeued/folded can return cancelled:false]
Stop --> Marker[User stop marker can block later auto-resume]

Turn interruption

An SDK/headless interrupt aborts the active conversation controller and performs the applicable turn-owned cleanup. Its response includes still_queued, which can contain:

  • UUID-stamped main-thread entries still in the queue;
  • the already-dequeued imminent batch; and
  • internally generated continuation or scheduled IDs.

An empty list is not proof that no work remains: an unstamped entry can still execute, and detached background work has its own lifecycle.

Queued-message cancellation

cancel_async_message targets a prompt UUID, not the active turn. It succeeds only while the item is still queued, with a narrow pending-cancel set covering a cancel-before-dispatch race. Once work has been dequeued, folded, or coalesced, the command can return cancelled:false even though some of that content may already be executing.

Correlated control cancellation

control_cancel_request targets one pending host/runtime operation by request_id, such as a side question. It does not identify a prompt UUID and does not cancel the whole session.

Lifecycle stop

TaskStop/agent kill invokes the task-kind-specific stop handler, aborts lifecycle state, and marks a terminal status. An explicit user stop is persisted separately so later SendMessage delivery refuses automatic resumption. By contrast, aborting an in-process teammate’s currentWorkAbortController ends only that turn; the teammate returns to idle and remains available.

Completion is layered

No single status means every owner has finished cleanup.

LayerCompletion or terminal ruleWhat it does not prove
SDK/MCP task protocolcompleted, failed, or cancelledDoes not include internal killed, and does not prove queue cleanup finished before the response.
Internal task registrycompleted, failed, or killed, plus notification/retention checksTerminal status alone does not authorize eviction.
Subagent contextSubagentStopDoes not imply a paired task record already emitted TaskCompleted.
Task hook layerTaskCompletedHook delivery is not the same as owner notification or registry eviction.
Ordinary background Agenttask-notification sent to owner/mainA later transcript-backed resume can produce another terminal notification for the same task identity.
WorkflowWorkflow result plus workflow-specific retention/deadline stateUses its own journal, abort, and evictAfter rules.
One-shot cronPrompt fired and task removedScheduling is owned by the cron subsystem, not by agent completion status.

Notification before eviction

The internal registry keeps a terminal task until its result has had a chance to reach the owner. Eviction can depend on:

  • whether terminal notification was emitted;
  • retain or evictAfter state;
  • active keepalive reasons;
  • workflow-specific deadlines; and
  • task-kind cleanup state.

This separation prevents a worker from disappearing before its result is delivered. Agent-owned monitors and shell tasks can also be keepaliveGated, allowing selected resources to outlive the completed model turn while ownership remains active.

SDK/MCP task-result cleanup

The method-oriented SDK/MCP TaskGet can wait until a protocol task reaches a terminal state. Terminal retrieval and cancellation start _clearTaskQueue(), which drains residual task messages and rejects queued request resolvers with Task cancelled or completed; those callers do not await the cleanup promise before returning. The complete protocol belongs in Agents, tasks, and subagents.

The local Agent Teams TaskGet is intentionally different: it reads the current shared JSON task record immediately. Idle teammate claiming and task-file locking belong in Agent Teams.

Scheduling handoffs

“Scheduling” names several mechanisms that should not be merged into this page:

MechanismCanonical owner
Consecutive concurrency-safe Agent tool calls and model precedenceAgents, tasks, and subagents
Teammate idle claim of a dependency-ready taskAgent Teams
Workflow FIFO agent-slot limiting and deterministic pipeline/parallel controlDynamic workflows
Local /loop, CronCreate, durable scheduled tasks, jitter, missed tasks, and cloud RemoteTrigger routinesCron and scheduled tasks

All of them eventually insert work into an existing runtime boundary, but they have different state stores, clocks, concurrency rules, and cleanup owners.

Failure and observability

ConditionSource-confirmed behavior
Interrupt received with queued workActive turn aborts; still_queued reports only the UUID-stamped subset the receipt can identify.
Queued cancellation loses the dequeue raceReturns cancelled:false; it does not retroactively remove folded content.
Agent was explicitly stopped by the userMessage-triggered auto-resume fails with AgentStoppedByUserError.
Terminal task still has keepalive ownershipNotification can be delivered while eviction remains deferred.
SDK/MCP queue cleanup is still runningTerminal retrieval/cancellation can already have returned; residual resolvers are rejected asynchronously.
Teammate current work is interruptedCurrent turn stops and teammate returns idle; lifecycle remains registered.

Useful signals include task_started, task_updated, task_progress, task_notification, TaskCreated, TaskCompleted, SubagentStart, and SubagentStop. Use Telemetry and tracing for sink behavior and Hooks and events reference for canonical event names.

Caveats

  • Queue and task state is coordinated in-process unless an owning subsystem explicitly persists it. Process-local ordering is not a cross-process transaction.
  • A delivery/interrupt/cancellation acknowledgement describes the local boundary reached; it is not proof of provider interpretation or remote exactly-once handling.
  • Status vocabularies differ by layer. Do not add killed to the SDK/MCP terminal predicate or treat cancelled as the internal UI’s universal terminal name.
  • Operating-system scheduling fairness and provider cancellation latency are outside the retained client artifact.

Created and maintained by Yingting Huang.