Harness Notes

JSON

Steering

8 records · Updated 2026-10-14

How to redirect, queue, stop or question an agent while it works, and how tools and extensions can feed it context. Each entry says when the input lands, what happens to running tools, and whether you can see it afterwards. Columns show each harness's main surface first, then its other surfaces.

You want toCodex CLIterminalCodex CLIApp Server APIClaude CodeterminalCursor AgentterminalCursor Agentagents window and webOpenCode2.xpiterminalpiRPC and SDK
Change direction mid-turn
TUI composer: Enter while a turn runs

Lands before the next model call; lets running tools finish; may skip tool calls not yet started; kept in the transcript.

Sent as turn/steer; same core semantics as the App Server entry, including instant_interrupt preemption.

Shown as 'Messages to be submitted after next tool call' until committed.

If the turn is not steerable (review/compact), the message is kept and resubmitted at the end of the turn.

If only user '!' shell commands are running, the message is queued instead.

TUI: Esc while steers are pending

Takes effect immediately; may stop running tools; skips remaining tool calls; kept in the transcript.

The preview hint reads '(press Esc to interrupt and send immediately)'. This interrupts the turn, then merges all pending steers into one new turn.

Shows 'Model interrupted to submit steer instructions.'

Blocked during /review.

App Server JSON-RPC turn/steer

Lands before the next model call; lets running tools finish; may skip tool calls not yet started; kept in the transcript.

Requires expectedTurnId; rejected with no active turn, on a mismatch, during review/compact turns (ActiveTurnNotSteerable), and for empty input or a different output schema.

Queued into the active turn's pending input. All pending steers are drained together before the next model request, after in-flight tools finish.

instant_interrupt (default on in 0.162.1, still 'under development') cuts the current model response short when user input arrives. Started tools still finish, but tool calls the model had not yet emitted are lost.

UserPromptSubmit hooks run on steered input and can block it.

Optional experimental additionalContext and responsesapiClientMetadata.

CLI (Ctrl+Enter / Ctrl+X Ctrl+S, chat:sendNow)

Timing depends on the situation; may stop running tools; kept in the transcript.

Backgroundable work keeps running in the background and Claude reads the message in the same turn.

Otherwise the turn is interrupted and the message sent next.

Before 2.1.281 it always interrupted. Requires 2.1.275 or later.

CLI permission prompt

Lands before the next model call; lets running tools finish.

No + comment: 'sends your comment to Claude as the reason for the denial'.

No without a comment stops the turn. Yes + comment is sent after the result.

Agent SDK (priority:'now')

Timing depends on the situation; may stop running tools; kept in the transcript.

With origin {kind:'human'} on 2.1.286 or later, shell, subagent and MCP work (plus WebFetch/WebSearch on 2.1.287+) moves to the background and the message is read in the same turn. Otherwise the turn is interrupted.

Without that origin it always interrupts.

CLI interactive prompt: Enter while the agent works

Lands at a safe point; lets running tools finish; kept in the transcript.

'Pressing Enter while the agent works now sends your queued message into the active run at a safe boundary to steer it' (v2026.08.11).

The overview gives the same behaviour.

Since May 2026, 'follow-ups move running tools to the background instead of killing them'.

A queued message still passes through beforeSubmitPrompt (see jun-29).

CLI shell approval prompt: 'Skip & tell the agent what to do instead'

Lands at the next tool call; lets running tools finish.

'choosing it opens a composer whose message is sent to the agent as the reason the command was skipped. Submitting an empty message just skips.'

Only available while a shell approval is pending. Whether it is sent as a tool result or a user message is not documented.

Cursor.com/agents; rolling out in the Agents Window ('Send now' or Enter twice)

Lands at the next tool call; lets running tools finish; kept in the transcript.

'delivered at the agent's next tool call instead of cutting off work mid-action, which preserves in-flight work'.

'available on cursor.com/agents now and rolling out in the Agents Window'.

'Press Tab to queue the message for after the turn instead.'

Agent UI: Cmd/Ctrl+Enter

Lands at the next tool call; kept in the transcript.

'appended to the most recent user message in the chat and processed right away'.

'Your message attaches to tool results and sends immediately'.

'Use this when you need to interrupt or redirect'. No cancellation is described.

This may be an older description of the same feature as steer-running-agent.

TUI Enter (prompt.submit, delivery 'steer' default) / POST /api/session/:id/prompt with delivery=steer

Lands before the next model call; lets running tools finish; kept in the transcript.

The input is first written to the durable inbox and stays out of model-visible history until delivered.

Before each step, all pending steers are delivered as one batch, in enqueue order. A pending compaction or move item stops the batch.

Delivering a steer resets the agent's step allowance.

Tool outcomes from the current step settle first.

Shell mode, exit words and slash commands cannot be queued from the TUI.

TUI: type and press Enter while the agent is working

Lands before the next model call; lets running tools finish; kept in the transcript.

Calls prompt(text,{streamingBehavior:"steer"}); extension commands run immediately instead.

Waits until the current response and its tool calls finish; pending items show as "Steering: ...".

steeringMode setting is one-at-a-time by default.

During compaction, input is held in a local queue and sent afterwards.

Extension pi.sendUserMessage(content, {deliverAs})

Timing depends on the situation; lets running tools finish; kept in the transcript.

Goes through prompt(): starts a run when idle; while streaming, deliverAs steer or followUp is required.

Template expansion is off by default.

RPC steer command, or prompt with streamingBehavior:"steer"; SDK session.steer()

Lands before the next model call; lets running tools finish; kept in the transcript.

Injected after the current assistant turn has finished ALL its tool calls, before the next LLM call; remaining tools are not skipped.

One per completed turn by default; set_steering_mode "all" delivers all at once.

prompt sent while streaming with no streamingBehavior is rejected.

No idle guard: sent while idle it is injected at the start of the next run.

Input handlers and skill/template expansion apply; extension commands are rejected.

Extension pi.sendUserMessage(content, {deliverAs})

Timing depends on the situation; lets running tools finish; kept in the transcript.

Goes through prompt(): starts a run when idle; while streaming, deliverAs steer or followUp is required.

Template expansion is off by default.

Queue a follow-up
TUI composer: Tab while a turn runs

Waits until the turn ends; lets running tools finish; kept in the transcript.

Queues prompts, slash commands (parsed when they run) and ! shell commands in a TUI-local in-memory queue. Each one starts a new turn, one per idle.

Alt+Up or Shift+Left pops the last queued item back into the composer for editing. There is no reordering.

On interrupt, queued items are restored to the composer, not sent.

CLI: codex queue --thread <UUID|name> --message <TEXT>

Waits until the turn ends; lets running tools finish; kept in the transcript.

Sends thread/queue/add to the shared daemon or --remote. It refuses --no-daemon and an embedded server while a daemon is running.

Text only; image attachments are not supported by the docs, although -i is listed in --help.

Starts a new turn when the target goes idle, but not after an interrupt. Reordering is only possible through the App Server API.

App Server JSON-RPC thread/queue/* (experimental)

Waits until the turn ends; lets running tools finish; kept in the transcript.

Durable, SQLite-backed queue. The head item starts a new turn (turn_trigger 'queue') when the thread goes idle, one item per idle.

No automatic dispatch after an interrupt-caused idle; thread/queue/start sends an item manually.

Supports list/update/delete/reorder and the thread/queue/changed notification. Requires the experimentalApi opt-in.

CLI

Timing depends on the situation; lets running tools finish; kept in the transcript.

While tools run, the message reaches Claude when they finish, in the same turn. Anything still queued at turn end goes out in typed order.

This is the 0.2.108 'real-time steering' feature under its current name.

Up takes queued entries back for editing; Esc interrupts and sends the queue right away.

A message read mid-turn appears in the rewind menu but has no checkpoint of its own.

CLI

Waits until the turn ends; lets running tools finish; kept in the transcript.

Covers queued slash commands and ! shell commands.

/model and /effort run at once and apply to the next request in the same turn; /fast applies from the next turn.

Agent SDK streamInput / -p --input-format stream-json

Timing depends on the situation; lets running tools finish; kept in the transcript.

Default priority: 'Claude reads the message in the same turn, as soon as the tool calls it is running finish'; otherwise it starts the next turn.

Several pending messages 'can merge... into one turn'.

Queued messages survive the --max-turns limit.

Agent SDK (priority:'later')

Waits until the turn ends; lets running tools finish; kept in the transcript.

'holds the message until the turn ends and sends it as a new turn'.

Agent view (claude agents)

Timing depends on the situation; lets running tools finish; kept in the transcript.

To a working session, replies 'join the session's message queue instead of interrupting'.

/model, /effort, /rename and /usage run right away; other commands wait for turn end.

A reply does not answer a permission dialog. Undeliverable plain replies are saved for the next start.

Since 2.1.287 replies arrive as queued messages.

CLI -> cloud session (claude -p --cloud <id>)

Timing not documented; kept in the transcript.

'queues the message into the session and exits'; documented for steering a long-running session.

—
Agent UI queue (Enter in the editor agent panel; Tab in the steering UI)

Waits until the turn ends; lets running tools finish; kept in the transcript.

'Messages appear in order below the active task' … 'Drag to reorder queued messages' … 'processes them sequentially after finishing'.

Key conflict on the same page: #keyboard-shortcuts says Enter queues, while #steer-a-running-agent says Tab queues.

TUI <leader>Enter (prompt.queue) / POST /api/session/:id/prompt with delivery=queue

Waits until the turn ends; lets running tools finish; kept in the transcript.

A queued item is delivered only at an idle boundary, one per idle boundary; steers still go first.

<leader>q opens a 'Queued prompts' dialog: selecting an item promotes it to a steer; ctrl+d deletes it.

API equivalents: PATCH /api/session/:id/inbox/:inboxID {delivery} (switching to steer wakes execution) and DELETE /api/session/:id/inbox/:inboxID.

The queue cannot be reordered.

A queued item is not in the transcript until it is delivered.

TUI: Alt+Enter (Ctrl+Q on Windows/WSL), app.message.followUp

Waits until the turn ends; lets running tools finish; kept in the transcript.

While streaming it queues a follow-up; while idle it behaves like a normal Enter submit.

followUpMode setting is one-at-a-time by default.

TUI: Alt+Up (Alt+Q on Windows/WSL), app.message.dequeue

Takes effect immediately; lets running tools finish; shown on screen only.

Clears both queues and puts all queued text back in the editor, where it can be edited or reordered and resent.

Does not abort.

RPC follow_up command, or prompt with streamingBehavior:"followUp"; SDK session.followUp()

Waits until the turn ends; lets running tools finish; kept in the transcript.

Delivered only when the agent would otherwise stop (no more tool calls or steering); another turn then runs.

Sent while idle it does not start a run; it waits for the end of the next run.

One per completion by default; set_follow_up_mode "all" delivers all at once.

Stop it
TUI: Esc (chat.interrupt_turn) while running

Takes effect immediately; may stop running tools; skips remaining tool calls; kept in the transcript.

Sends turn/interrupt, with the same core effects as the App Server entry.

The TUI prints 'Conversation interrupted' and restores queued and unsent steers into the composer.

The key can be remapped. Esc does not interrupt while popups are open, in vim insert mode, or while typing /agents.

App Server JSON-RPC turn/interrupt

Takes effect immediately; may stop running tools; skips remaining tool calls; kept in the transcript.

Cancels the turn token, allows a 100 ms grace period, then aborts the task. Spawned children are kill_on_drop, but unified-exec/background processes may keep running.

Pending steers for the turn are discarded.

Records a model-visible turn-aborted marker (contextual-user, or developer for multi-agent V2) unless agent_interrupt_message_enabled is false; then the Interrupt hook runs.

Responds after TurnAborted; the turn ends with status interrupted.

CLI

Takes effect immediately; stops running tools; skips remaining tool calls; kept in the transcript.

Keeps completed work and sends queued messages next.

On a permission prompt, Esc declines; on a dialog or selected footer row, it closes or deselects instead.

The changelog mentions an Interrupted row and '[Request interrupted by user]' messages; what the model sees is not documented.

CLI

Takes effect immediately; stops running tools.

'Interrupts a running operation.' When idle, it clears input, and a second press exits.

While attached to a background session it cancels the response; a double press on an empty prompt detaches.

Agent SDK interrupt() / stream-json control request

Takes effect immediately; stops running tools.

Streaming input mode only. The receipt (interrupt_receipt_v1, 2.1.205+) lists still_queued messages, which run afterwards.

A raw control request with cancel_queued:true (2.1.219+) cancels them instead.

Python: the interrupted tool's 'tool result carries the interruption message'. The partial assistant reply is kept (2.1.94).

Agent SDK canUseTool

Takes effect immediately.

PermissionResult deny with interrupt:true aborts the turn; terminal_reason is aborted_*.

Claude -p

Takes effect immediately.

'To end the turn instead, send SIGINT'.

SIGTERM instead exits 143 and leaves the turn unfinished, kills running Bash, and records no result.

Agent view peek reply '/stop', claude stop <id>

Takes effect immediately; stops running tools; skips remaining tool calls.

'stops the session at once instead of being delivered to it'.

CLI: second Enter while a turn runs

Takes effect immediately; lets running tools finish; skips remaining tool calls; kept in the transcript.

'only interrupts the turn if you press Enter again' (aug-11).

'Press Enter again on an empty prompt to stop the current turn and send your queued message immediately, including while a beforeSubmitPrompt hook is running' (jun-29).

CLI: Ctrl+C / Stop

Takes effect immediately; skips remaining tool calls.

'Stopping a turn preserves the output of in-flight shell commands' (may-7).

'Network-level cancels and aborts no longer end the turn as if Ctrl+C had been pressed' (jun-9). This implies Ctrl+C ends the turn.

'In the CLI, Ctrl+C pauses the goal' (overview).

No dedicated docs page for the key. The postToolUseFailure field is_interrupt means 'failure was caused by a user interrupt/cancellation'.

Not documented
TUI Esc twice within 5 s (session.interrupt) -> POST /api/session/:id/interrupt?resume=true

Takes effect immediately; stops running tools; skips remaining tool calls; kept in the transcript.

Interrupting cancels the stream and the running tool fibers. Tools fail with 'Tool execution interrupted' and the assistant message shows 'Step interrupted'.

Interrupting does not delete pending inbox input.

The TUI sends resume=true: pending steers (and compaction or move controls) then run straight away while queued prompts stay parked, so typing then double Esc works as interrupt-and-redirect.

With resume omitted, the API is a plain cancel.

Only an execution owned by this process can be interrupted; an idle session is a no-op.

The composer also has ctrl+d to interrupt a subagent.

TUI: Escape (app.interrupt)

Takes effect immediately; may stop running tools; skips remaining tool calls; kept in the transcript.

While streaming it clears the queues, puts their text back in the editor, then calls session.abort().

Tools receive the AbortSignal (built-in bash kills its process tree); custom tools are interrupted only if they honour it. Sequential mode skips tools after the current one; in parallel mode (default) calls not yet started are skipped and running calls get the signal.

If no run is active but a user bash is running, Esc aborts the bash instead.

RPC abort; SDK session.abort()

Takes effect immediately; may stop running tools; skips remaining tool calls; kept in the transcript.

Fires the run's AbortSignal and waits for idle; also cancels retry, compaction and branch summary.

Tools receive the AbortSignal (built-in bash kills its process tree); custom tools are interrupted only if they honour it. Sequential mode skips tools after the current one; in parallel mode (default) calls not yet started are skipped and running calls get the signal.

Partial assistant message is kept with stopReason "aborted"; agent_settled reports aborted:true.

Does NOT clear the queues; send clear_queue first.

RPC clear_queue; SDK session.clearQueue()

Takes effect immediately; lets running tools finish; shown on screen only.

Removes all queued steering and follow-up messages and returns their text; emits queue_update.

Ask a side question
TUI slash command /side (alias /btw), optional inline text

Answered separately; the main run is untouched; lets running tools finish; shown on screen only.

Opens an ephemeral thread/fork of the main chat. The main turn keeps running and its status stays visible.

Injects a hidden boundary user message plus developer instructions: inherited history is reference only, no mutations unless explicitly asked, no sub-agents.

Only one side chat at a time; Ctrl+C returns to the parent. Unavailable before the first message, inside another side chat, and during review. The answer never enters the main transcript.

App Server JSON-RPC thread/fork {ephemeral:true}

Answered separately; the main run is untouched; lets running tools finish; shown on screen only.

There is no dedicated side-question API; this is a building block. The TUI /side uses it plus thread/inject_items and developer instructions.

Forking a mid-turn parent records an interruption marker in the fork only; the parent keeps running.

CLI (also VS Code, Remote Control, non-interactive)

Answered separately; the main run is untouched; lets running tools finish; shown on screen only.

No tools; it sees the conversation except the reply in progress.

Replays the newest 20 earlier /btw exchanges. The terminal keeps them in memory until exit; the VS Code panel survives reloads.

'f' forks it into a subagent with tools.

CLI /btw

Answered separately; the main run is untouched; shown on screen only.

'Ask a quick read-only question mid-turn; the answer streams into a dismissible overlay with full conversation context and never touches your conversation history.'

Fixes are listed at jul-13-2026-reliability.

Not listed in /cli/reference/slash-commands.

Whether /btw works while idle is undocumented.

/side or /btw, 'Ask in Side Chat', Shift+Cmd/Ctrl+S

Answered separately; the main run is untouched; lets running tools finish; kept in the transcript.

'ask a question or investigate a tangent without interrupting the main conversation'.

'uses the parent thread as hidden reference context and keeps its own transcript'.

'Follow-up messages stay in the side chat and do not appear in the parent transcript'.

'Side chats are local-only … not currently available for Cloud Agents', so they are absent on cursor.com/agents.

Context returns to the parent via @-mention.

TUI /btw <question> (command session.aside, built-in plugin opencode.btw) -> POST /api/session/:id/generate

Answered separately; the main run is untouched; lets running tools finish; shown on screen only.

One-shot model call over the current session context plus a no-tools instruction; the answer appears in a dialog with a copy option.

Session history is not changed.

Does not wait for or touch the active run.

There is no docs page: https://opencode.ai/v2/docs/btw/ returns 404. Only the generate endpoint is documented.

The desktop and web app also ship a btw sidebar extension.

Not supportedNot supported
Feed it context (hooks, extensions, APIs)
Background (async: true) command hooks

Lands at a safe point; lets running tools finish; hidden from you.

Same core path as the App Server entry.

The TUI hides hook runs that completed with only context entries.

Synchronous hooks

Arrives when its event fires; lets running tools finish; may skip tool calls not yet started; hidden from you.

Same as the App Server entry; context-only runs are quiet in the TUI.

Background (async: true) command hooks

Lands at a safe point; lets running tools finish; shown on screen only.

When a turn is active, results are drained after the current sampling request and its tools finish, then injected for the next model request.

When idle, results wait for the next user turn (recorded before the prompt). Finishing a hook never starts a turn.

additionalContext becomes a developer message; systemMessage becomes a warning.

Entries are exposed to clients in hook/completed.

Cannot block, approve or rewrite. At most 8 background hooks run concurrently per session.

Synchronous hooks (SessionStart, UserPromptSubmit, PostToolUse, Pre/PostCompact, SubagentStart/Stop, Stop)

Arrives when its event fires; lets running tools finish; may skip tool calls not yet started; shown on screen only.

additionalContext is recorded as developer messages at the hook's own lifecycle point and is seen by the next model request.

Hooks that can stop (UserPromptSubmit block, PostToolUse/Stop stop) change the turn. Default limit is about 2500 tokens (additionalContextLimit).

App Server JSON-RPC thread/inject_items

Timing depends on the situation; lets running tools finish; hidden from you.

Active turn: queued as pending input and applied at the next model boundary. It does not trigger instant-interrupt preemption, which watches only user input.

Idle: written straight into history; no turn starts.

Hooks

Lands before the next model call; lets running tools finish; hidden from you.

Exception: Stop/SubagentStop additionalContext shows as 'Stop hook feedback' and continues the turn.

Values over 10,000 characters are written to a file and passed as a path plus preview.

Saved text is replayed, not re-run, on resume.

On some models system reminders are a separate system-role message.

Hooks (async / asyncRewake)

Timing depends on the situation; lets running tools finish; hidden from you.

Output is 'delivered on the next conversation turn'; neither field is shown to the user.

asyncRewake exit 2 'wakes Claude immediately even when the session is idle'.

CLI

Arrives when its event fires; lets running tools finish.

Claude Code 'appends a <system-reminder> noting the file changed'.

The docs do not say whether the user can see it.

MCP channels (research preview)

Waits until the turn ends; lets running tools finish; kept in the transcript.

Arrives wrapped in a <channel source=...> tag.

'processed in order'; events arriving while busy are 'delivered together on the next turn'.

No acknowledgement. Shown in the terminal as an inbound line.

Agent SDK (shouldQuery:false)

Timing depends on the situation; lets running tools finish; kept in the transcript.

'held and merged into the next user message that does trigger a turn'.

SessionStart hook (hooks.json)

Arrives when its event fires; lets running tools finish.

'additional_context … Additional context to add to the conversation's initial system context'.

'This hook runs as fire-and-forget; the agent loop does not wait'.

CLI support: 'Hooks. Session start/end…' (january-2026).

When several hooks respond, the last response wins for non-message fields.

PostToolUse / postToolUseFailure hooks

Arrives when its event fires; lets running tools finish.

'Extra context injected into the conversation after the tool result'. postToolUseFailure does the same 'after the failed tool result'.

The hooks page does not state CLI support for these two events.

Stop / subagentStop hook followup_message

Waits until the turn ends; lets running tools finish.

'Cursor will automatically submit it as the next user message'.

Capped at 'default limit is 5 auto follow-ups per script' (loop_limit).

subagentStop's followup_message is 'Only consumed when status is completed'.

CLI support: 'stop hooks with follow-up loops' (january-2026).

PreToolUse / beforeShellExecution / beforeMCPExecution deny

Timing depends on the situation; lets running tools finish.

'agent_message … Message fed back to the agent when the action is denied'. Only fires when a hook denies the tool call.

When several hooks respond, the agent_message values are concatenated.

SessionStart hook

Arrives when its event fires; lets running tools finish.

'additional_context … Additional context to add to the conversation's initial system context'; 'runs as fire-and-forget'.

Not available in cloud agents: 'sessionStart … Deferred while cloud agents can still start in a read-only environment'. It only applies to local agents in the Agents Window or editor.

PostToolUse / postToolUseFailure hooks

Arrives when its event fires; lets running tools finish.

'Extra context injected into the conversation after the tool result'.

Supported in cloud agents (the #supported-hooks table says 'postToolUse | Yes'). Cloud runs only project, team and enterprise hooks.

'Hooks do not run' during read-only early cloud turns.

Stop / subagentStop hook

Waits until the turn ends; lets running tools finish.

'automatically submit it as the next user message'; capped by loop_limit (default 5).

Cloud: 'stop | Yes'.

PreToolUse / beforeShellExecution / beforeMCPExecution deny

Timing depends on the situation; lets running tools finish.

'Message fed back to the agent when the action is denied'.

beforeMCPExecution is not available in cloud agents.

POST /api/session/:id/synthetic (plugin ctx.session.synthetic)

Timing depends on the situation; lets running tools finish; kept in the transcript.

Goes through the same inbox as prompts, with steer or queue delivery; becomes a synthetic message when delivered.

resume=false records the message without waking execution; shell completion notices and the Plan plugin use this.

Field fit: 'ordering' is really the inbox discipline (steers as a batch, queued items one at a time).

Plugin ctx.session.hook("prompt") (v1 chat.message successor)

Arrives when its event fires; lets running tools finish; kept in the transcript.

Runs before durable admission; the plugin can change the prompt's text, files, agents and skills.

The hook is told the delivery mode, steer or queue.

Plugin ctx.session.hook("context") (v1 experimental.chat.messages/system.transform successor)

Lands before the next model call; lets running tools finish; hidden from you.

Runs for each primary model request; the plugin can edit system, messages, options and tools.

Separate hooks exist for compaction and generate.

The built-in Plan plugin splices a reminder in here and also saves it through synthetic(resume:false).

Experimental API PUT/DELETE /api/experimental/session/:id/instructions/entries/:key

Lands before the next model call; lets running tools finish; hidden from you.

Stores durable instruction entries; changes are announced to the model at the next step boundary.

Experimental.

TUI: !cmd (in context) / !!cmd (excluded from context)

Timing depends on the situation; lets running tools finish; kept in the transcript.

Handled before the streaming check, so it works mid-run.

Output is held until the run ends, then reaches the model on the next prompt.

!! is stored but excluded from model context.

Extension pi.sendMessage(customMsg, {deliverAs:"steer"|"followUp"}) while streaming

Lands before the next model call; lets running tools finish; kept in the transcript.

Custom message goes into the agent's steer queue (default) or follow-up queue; followUp waits until the agent would stop.

Reaches the model as a user-role message; `display` controls rendering.

Extension pi.sendMessage(..., {triggerTurn:false}) while streaming

Lands at a safe point; lets running tools finish; kept in the transcript.

Appended at turn_end after the turn's tool results, so it never lands between a tool call and its result.

Does not trigger a turn by itself.

Extension pi.sendMessage(..., {triggerTurn:true}) while idle

Takes effect immediately; lets running tools finish; kept in the transcript.

Starts a new run with the custom message as its starting message; before_agent_start sees messageType "custom".

Without triggerTurn, an idle message is only appended.

Extension pi.sendMessage(..., {deliverAs:"nextTurn"})

Timing depends on the situation; lets running tools finish; kept in the transcript.

Held until the next run starts, then injected next to that run's starting message.

Extension before_agent_start handler returning message or system prompt changes

Arrives when its event fires; lets running tools finish; kept in the transcript.

Fires once per run before the first request; the returned custom message is added after the starting message.

Returning systemPrompt replaces the prompt for that run only.

Extension context / context_with_system handler

Lands before the next model call; lets running tools finish; hidden from you.

Runs before every LLM call on a structuredClone of the messages; the change applies to that request only and is not written to the session.

Allowed values have no "request" persistence; "turn" is the closest.

Extension turn_end handler returning entries and/or continue:true

Arrives when its event fires; lets running tools finish; kept in the transcript.

Draft entries (custom, custom_message, context_edit, compaction) are committed after the turn's tool results.

continue:true guarantees exactly one more provider request; rejected if there is no runnable context.

Extension agent_before_settle handler returning entries and/or continue:true

Arrives when its event fires; lets running tools finish; kept in the transcript.

Last point an extension can act before the run settles: add entries and ask for one continuation.

Ignored if an abort happened during the boundary; queued messages also cause a continuation.

Extension input handler (transform/handled)

Takes effect immediately; lets running tools finish; kept in the transcript.

Can rewrite or consume any prompt, steer or follow-up before it is queued (streamingBehavior is exposed).

"handled" consumes the input; RPC reports disposition "handled".

RPC bash command (with excludeFromContext); SDK executeBash/recordBashResult

Timing depends on the situation; lets running tools finish; kept in the transcript.

Runs immediately; output is stored as a bashExecution message and becomes a user-role message on the next prompt.

While streaming, the message is held until the run ends.

excludeFromContext keeps it out of model context; stop it with abort_bash.

Extension pi.sendMessage(customMsg, {deliverAs:"steer"|"followUp"}) while streaming

Lands before the next model call; lets running tools finish; kept in the transcript.

Custom message goes into the agent's steer queue (default) or follow-up queue; followUp waits until the agent would stop.

Reaches the model as a user-role message; `display` controls rendering.

Extension pi.sendMessage(..., {triggerTurn:false}) while streaming

Lands at a safe point; lets running tools finish; kept in the transcript.

Appended at turn_end after the turn's tool results, so it never lands between a tool call and its result.

Does not trigger a turn by itself.

Extension pi.sendMessage(..., {triggerTurn:true}) while idle

Takes effect immediately; lets running tools finish; kept in the transcript.

Starts a new run with the custom message as its starting message; before_agent_start sees messageType "custom".

Without triggerTurn, an idle message is only appended.

Extension pi.sendMessage(..., {deliverAs:"nextTurn"})

Timing depends on the situation; lets running tools finish; kept in the transcript.

Held until the next run starts, then injected next to that run's starting message.

Extension before_agent_start handler returning message or system prompt changes

Arrives when its event fires; lets running tools finish; kept in the transcript.

Fires once per run before the first request; the returned custom message is added after the starting message.

Returning systemPrompt replaces the prompt for that run only.

Extension context / context_with_system handler

Lands before the next model call; lets running tools finish; hidden from you.

Runs before every LLM call on a structuredClone of the messages; the change applies to that request only and is not written to the session.

Allowed values have no "request" persistence; "turn" is the closest.

Extension turn_end handler returning entries and/or continue:true

Arrives when its event fires; lets running tools finish; kept in the transcript.

Draft entries (custom, custom_message, context_edit, compaction) are committed after the turn's tool results.

continue:true guarantees exactly one more provider request; rejected if there is no runnable context.

Extension agent_before_settle handler returning entries and/or continue:true

Arrives when its event fires; lets running tools finish; kept in the transcript.

Last point an extension can act before the run settles: add entries and ask for one continuation.

Ignored if an abort happened during the boundary; queued messages also cause a continuation.

Extension input handler (transform/handled)

Takes effect immediately; lets running tools finish; kept in the transcript.

Can rewrite or consume any prompt, steer or follow-up before it is queued (streamingBehavior is exposed).

"handled" consumes the input; RPC reports disposition "handled".

Sources

Codex CLI, terminal
Codex CLI, App Server API
Claude Code, terminal
Cursor Agent, terminal
Cursor Agent, agents window and web
OpenCode, 2.x
pi, terminal
pi, RPC and SDK