{
  "schemaVersion": 1,
  "updatedAt": "2026-10-15",
  "contracts": [
    {
      "harness": "claude-code",
      "variant": "cli-current",
      "default": true,
      "intentCoverage": {
        "user-steering": "documented",
        "interruption": "documented",
        "harness-context": "documented",
        "side-question": "documented"
      },
      "deliveries": [
        {
          "name": "queued-message",
          "intent": "user-steering",
          "surface": "CLI",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "conditional",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "claude-interactive-docs",
            "steering-claude-code-docs-en-checkpointing",
            "claude-changelog"
          ],
          "reference": "https://code.claude.com/docs/en/interactive-mode#when-claude-code-sends-what-you-queued",
          "notes": [
            "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."
          ]
        },
        {
          "name": "queued-command",
          "intent": "user-steering",
          "surface": "CLI",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "one-at-a-time",
          "evidence": ["claude-interactive-docs"],
          "reference": "https://code.claude.com/docs/en/interactive-mode#when-claude-code-sends-what-you-queued",
          "notes": [
            "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.",
            "representation is inferred, not stated."
          ]
        },
        {
          "name": "send-now",
          "intent": "user-steering",
          "surface": "CLI (Ctrl+Enter / Ctrl+X Ctrl+S, chat:sendNow)",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "conditional",
          "effect": "redirect",
          "interruptsRunningTool": "conditional",
          "skipsRemainingTools": "unknown",
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": ["claude-interactive-docs", "claude-changelog"],
          "reference": "https://code.claude.com/docs/en/interactive-mode#when-claude-code-sends-what-you-queued",
          "notes": [
            "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."
          ]
        },
        {
          "name": "escape",
          "intent": "interruption",
          "surface": "CLI",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "interrupt",
          "interruptsRunningTool": true,
          "skipsRemainingTools": true,
          "representation": "unknown",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["claude-interactive-docs", "claude-agent-view-docs", "claude-changelog"],
          "reference": "https://code.claude.com/docs/en/interactive-mode#general-controls",
          "notes": [
            "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."
          ]
        },
        {
          "name": "ctrl-c",
          "intent": "interruption",
          "surface": "CLI",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "interrupt",
          "interruptsRunningTool": true,
          "skipsRemainingTools": "unknown",
          "representation": "unknown",
          "visibility": "unknown",
          "persistence": "unknown",
          "ordering": "not-applicable",
          "evidence": ["claude-interactive-docs", "claude-agent-view-docs"],
          "reference": "https://code.claude.com/docs/en/interactive-mode#general-controls",
          "notes": [
            "'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."
          ]
        },
        {
          "name": "permission-deny-comment",
          "intent": "user-steering",
          "surface": "CLI permission prompt",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "next-model-boundary",
          "effect": "redirect",
          "interruptsRunningTool": false,
          "skipsRemainingTools": "unknown",
          "representation": "unknown",
          "visibility": "unknown",
          "persistence": "unknown",
          "ordering": "not-applicable",
          "evidence": ["claude-permissions-docs"],
          "reference": "https://code.claude.com/docs/en/permissions#add-a-comment-when-you-answer-a-permission-prompt",
          "notes": [
            "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."
          ]
        },
        {
          "name": "btw-side-question",
          "intent": "side-question",
          "surface": "CLI (also VS Code, Remote Control, non-interactive)",
          "admission": "idle-or-active",
          "authority": "none",
          "deliveryBoundary": "independent",
          "effect": "answer-separately",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "side-channel",
          "visibility": "ui-only",
          "persistence": "ephemeral",
          "ordering": "not-applicable",
          "evidence": ["claude-interactive-docs", "claude-changelog"],
          "reference": "https://code.claude.com/docs/en/interactive-mode#side-questions-with-%2Fbtw",
          "notes": [
            "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."
          ]
        },
        {
          "name": "hook-additional-context",
          "intent": "harness-context",
          "surface": "hooks",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "next-model-boundary",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "hidden",
          "persistence": "conversation",
          "ordering": "all",
          "evidence": ["claude-hooks-docs", "steering-claude-code-docs-en-glossary"],
          "reference": "https://code.claude.com/docs/en/hooks#add-context-for-claude",
          "wrapper": "<system-reminder>",
          "notes": [
            "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."
          ]
        },
        {
          "name": "async-hook-rewake",
          "intent": "harness-context",
          "surface": "hooks (async / asyncRewake)",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "conditional",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "hidden",
          "persistence": "conversation",
          "ordering": "unknown",
          "evidence": ["claude-hooks-docs"],
          "reference": "https://code.claude.com/docs/en/hooks#run-hooks-in-the-background",
          "notes": [
            "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'.",
            "persistence is inferred from the general rule for additionalContext."
          ]
        },
        {
          "name": "file-change-reminder",
          "intent": "harness-context",
          "surface": "CLI",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "unknown",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["claude-prompt-caching-docs", "steering-claude-code-docs-en-glossary"],
          "reference": "https://code.claude.com/docs/en/prompt-caching#editing-files-in-your-repository",
          "wrapper": "<system-reminder>",
          "notes": [
            "Claude Code 'appends a <system-reminder> noting the file changed'.",
            "The docs do not say whether the user can see it."
          ]
        },
        {
          "name": "channel-event",
          "intent": "harness-context",
          "surface": "MCP channels (research preview)",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "unknown",
          "ordering": "fifo",
          "evidence": [
            "steering-claude-code-docs-en-channels-reference",
            "steering-claude-code-docs-en-channels",
            "steering-claude-code-docs-en-agent-sdk-typescript"
          ],
          "reference": "https://code.claude.com/docs/en/channels-reference#notification-format",
          "notes": [
            "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.",
            "representation is inferred from SDKMessageOrigin kind 'channel' on user-role messages.",
            "Comes from: channel sender (allowlist-gated)."
          ]
        },
        {
          "name": "sdk-user-message-next",
          "intent": "user-steering",
          "surface": "Agent SDK streamInput / -p --input-format stream-json",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "conditional",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-claude-code-docs-en-agent-sdk-typescript",
            "claude-cli-reference-docs",
            "image-input-docs-1"
          ],
          "reference": "https://code.claude.com/docs/en/agent-sdk/typescript#sdkusermessage",
          "notes": [
            "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."
          ]
        },
        {
          "name": "sdk-user-message-later",
          "intent": "user-steering",
          "surface": "Agent SDK (priority:'later')",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": ["steering-claude-code-docs-en-agent-sdk-typescript"],
          "reference": "https://code.claude.com/docs/en/agent-sdk/typescript#sdkusermessage",
          "notes": ["'holds the message until the turn ends and sends it as a new turn'."]
        },
        {
          "name": "sdk-user-message-now",
          "intent": "user-steering",
          "surface": "Agent SDK (priority:'now')",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "conditional",
          "effect": "redirect",
          "interruptsRunningTool": "conditional",
          "skipsRemainingTools": "unknown",
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "unknown",
          "evidence": ["steering-claude-code-docs-en-agent-sdk-typescript"],
          "reference": "https://code.claude.com/docs/en/agent-sdk/typescript#sdkusermessage",
          "notes": [
            "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."
          ]
        },
        {
          "name": "sdk-context-no-query",
          "intent": "harness-context",
          "surface": "Agent SDK (shouldQuery:false)",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "conditional",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "mutated-user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "unknown",
          "evidence": ["steering-claude-code-docs-en-agent-sdk-typescript"],
          "reference": "https://code.claude.com/docs/en/agent-sdk/typescript#sdkusermessage",
          "notes": [
            "'held and merged into the next user message that does trigger a turn'.",
            "Comes from: host application."
          ]
        },
        {
          "name": "sdk-interrupt",
          "intent": "interruption",
          "surface": "Agent SDK interrupt() / stream-json control request",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "interrupt",
          "interruptsRunningTool": true,
          "skipsRemainingTools": "unknown",
          "representation": "unknown",
          "visibility": "unknown",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "steering-claude-code-docs-en-agent-sdk-typescript",
            "steering-claude-code-docs-en-agent-sdk-python",
            "claude-changelog"
          ],
          "reference": "https://code.claude.com/docs/en/agent-sdk/typescript#sdkcontrolinterruptresponse",
          "notes": [
            "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).",
            "Comes from: host application."
          ]
        },
        {
          "name": "sdk-permission-deny-interrupt",
          "intent": "interruption",
          "surface": "Agent SDK canUseTool",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "interrupt",
          "interruptsRunningTool": "unknown",
          "skipsRemainingTools": "unknown",
          "representation": "unknown",
          "visibility": "unknown",
          "persistence": "unknown",
          "ordering": "not-applicable",
          "evidence": [
            "steering-claude-code-docs-en-agent-sdk-typescript",
            "steering-claude-code-docs-en-agent-sdk-python"
          ],
          "reference": "https://code.claude.com/docs/en/agent-sdk/typescript#permissionresult",
          "notes": [
            "PermissionResult deny with interrupt:true aborts the turn; terminal_reason is aborted_*.",
            "Comes from: host application."
          ]
        },
        {
          "name": "headless-sigint",
          "intent": "interruption",
          "surface": "claude -p",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "interrupt",
          "interruptsRunningTool": "unknown",
          "skipsRemainingTools": "unknown",
          "representation": "unknown",
          "visibility": "unknown",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["steering-claude-code-docs-en-headless"],
          "reference": "https://code.claude.com/docs/en/headless#stop-a-run-with-sigterm",
          "notes": [
            "'To end the turn instead, send SIGINT'.",
            "SIGTERM instead exits 143 and leaves the turn unfinished, kills running Bash, and records no result.",
            "Comes from: user/host."
          ]
        },
        {
          "name": "agent-view-peek-reply",
          "intent": "user-steering",
          "surface": "agent view (claude agents)",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "conditional",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": ["claude-agent-view-docs", "claude-changelog"],
          "reference": "https://code.claude.com/docs/en/agent-view#peek-and-reply",
          "notes": [
            "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."
          ]
        },
        {
          "name": "agent-view-stop",
          "intent": "interruption",
          "surface": "agent view peek reply '/stop', claude stop <id>",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "cancel",
          "interruptsRunningTool": true,
          "skipsRemainingTools": true,
          "representation": "unknown",
          "visibility": "unknown",
          "persistence": "unknown",
          "ordering": "not-applicable",
          "evidence": ["claude-agent-view-docs"],
          "reference": "https://code.claude.com/docs/en/agent-view#peek-and-reply",
          "notes": [
            "'stops the session at once instead of being delivered to it'.",
            "The interrupts/skips values are inferred from 'stops the session'."
          ]
        },
        {
          "name": "cloud-follow-up",
          "intent": "user-steering",
          "surface": "CLI -> cloud session (claude -p --cloud <id>)",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "unknown",
          "effect": "queue",
          "interruptsRunningTool": "unknown",
          "skipsRemainingTools": "unknown",
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "unknown",
          "evidence": ["claude-web-docs", "steering-claude-code-docs-en-headless"],
          "reference": "https://code.claude.com/docs/en/claude-code-on-the-web#send-follow-ups-from-the-cli",
          "notes": [
            "'queues the message into the session and exits'; documented for steering a long-running session."
          ]
        }
      ]
    },
    {
      "harness": "codex",
      "variant": "cli-current",
      "default": true,
      "intentCoverage": {
        "user-steering": "documented",
        "interruption": "documented",
        "harness-context": "documented",
        "side-question": "documented"
      },
      "deliveries": [
        {
          "name": "enter-while-running",
          "intent": "user-steering",
          "surface": "TUI composer: Enter while a turn runs",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": "conditional",
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "all",
          "evidence": [
            "codex-developer-commands-docs",
            "steering-codex-codex-rs-tui-src-bottom-pane-chat-composer-rs",
            "steering-codex-codex-rs-tui-src-chatwidget-input-flow-rs",
            "steering-codex-codex-rs-tui-src-app-thread-routing-rs",
            "steering-codex-codex-rs-tui-src-bottom-pane-pending-input-preview-rs",
            "steering-codex-codex-rs-core-src-session-turn-input-rs"
          ],
          "reference": "https://learn.chatgpt.com/docs/developer-commands#interactive-shortcuts",
          "notes": [
            "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."
          ]
        },
        {
          "name": "tab-queue",
          "intent": "user-steering",
          "surface": "TUI composer: Tab while a turn runs",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "codex-developer-commands-docs",
            "steering-codex-codex-rs-tui-src-chatwidget-input-flow-rs",
            "steering-codex-codex-rs-tui-src-chatwidget-input-queue-rs",
            "steering-codex-codex-rs-tui-src-chatwidget-interaction-rs",
            "steering-codex-codex-rs-tui-src-keymap-rs"
          ],
          "reference": "https://learn.chatgpt.com/docs/developer-commands#interactive-shortcuts",
          "notes": [
            "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."
          ]
        },
        {
          "name": "esc-interrupt",
          "intent": "interruption",
          "surface": "TUI: Esc (chat.interrupt_turn) while running",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "cancel",
          "interruptsRunningTool": "conditional",
          "skipsRemainingTools": true,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "steering-codex-codex-rs-tui-src-bottom-pane-mod-rs",
            "steering-codex-codex-rs-tui-src-keymap-rs",
            "steering-codex-codex-rs-tui-src-app-thread-routing-rs",
            "steering-codex-codex-rs-tui-src-chatwidget-input-restore-rs",
            "steering-codex-codex-rs-core-src-tasks-mod-rs"
          ],
          "reference": "https://github.com/openai/codex/blob/092d3acd6bec3e3a14bdc7e7a2810ab628ab759d/codex-rs/tui/src/bottom_pane/mod.rs#L1712-L1727",
          "notes": [
            "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."
          ]
        },
        {
          "name": "esc-send-pending-steers-now",
          "intent": "user-steering",
          "surface": "TUI: Esc while steers are pending",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "redirect",
          "interruptsRunningTool": "conditional",
          "skipsRemainingTools": true,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "all",
          "evidence": [
            "steering-codex-codex-rs-tui-src-chatwidget-interaction-rs",
            "steering-codex-codex-rs-tui-src-bottom-pane-pending-input-preview-rs",
            "steering-codex-codex-rs-tui-src-chatwidget-input-restore-rs"
          ],
          "reference": "https://github.com/openai/codex/blob/092d3acd6bec3e3a14bdc7e7a2810ab628ab759d/codex-rs/tui/src/chatwidget/interaction.rs#L215-L229",
          "notes": [
            "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."
          ]
        },
        {
          "name": "side-btw",
          "intent": "side-question",
          "surface": "TUI slash command /side (alias /btw), optional inline text",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "independent",
          "effect": "answer-separately",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "side-channel",
          "visibility": "ui-only",
          "persistence": "ephemeral",
          "ordering": "one-at-a-time",
          "evidence": [
            "codex-developer-commands-docs",
            "codex-slash-commands-source-0-162-1",
            "steering-codex-codex-rs-tui-src-app-side-rs"
          ],
          "reference": "https://learn.chatgpt.com/docs/developer-commands#start-a-side-chat-with-side",
          "notes": [
            "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.",
            "Comes from: user (fork carries hidden developer instructions)."
          ]
        },
        {
          "name": "codex-queue",
          "intent": "user-steering",
          "surface": "CLI: codex queue --thread <UUID|name> --message <TEXT>",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "user-reorderable",
          "evidence": [
            "codex-developer-commands-docs",
            "steering-codex-codex-rs-tui-src-session-queue-commands-rs",
            "steering-codex-codex-rs-cli-src-main-rs",
            "steering-codex-codex-rs-ext-queue-src-service-rs"
          ],
          "reference": "https://learn.chatgpt.com/docs/developer-commands#codex-queue",
          "notes": [
            "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."
          ]
        },
        {
          "name": "async-command-hook-output",
          "intent": "harness-context",
          "surface": "Background (async: true) command hooks",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "safe-boundary",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "hidden",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "codex-hooks-docs",
            "steering-codex-codex-rs-core-src-hook-runtime-rs",
            "steering-codex-codex-rs-tui-src-history-cell-hook-cell-rs"
          ],
          "reference": "https://learn.chatgpt.com/docs/hooks#how-background-hooks-run",
          "notes": [
            "Same core path as the App Server entry.",
            "The TUI hides hook runs that completed with only context entries.",
            "Comes from: hook/context (developer message)."
          ]
        },
        {
          "name": "sync-hook-additionalContext",
          "intent": "harness-context",
          "surface": "Synchronous hooks",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": "conditional",
          "representation": "injected-context",
          "visibility": "hidden",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "steering-codex-codex-rs-core-src-hook-runtime-rs",
            "steering-codex-codex-rs-tui-src-history-cell-hook-cell-rs"
          ],
          "reference": "https://github.com/openai/codex/blob/092d3acd6bec3e3a14bdc7e7a2810ab628ab759d/codex-rs/core/src/hook_runtime.rs#L849-L862",
          "notes": [
            "Same as the App Server entry; context-only runs are quiet in the TUI.",
            "Comes from: hook/context (developer message)."
          ]
        }
      ]
    },
    {
      "harness": "codex",
      "variant": "app-server-current",
      "default": false,
      "intentCoverage": {
        "user-steering": "documented",
        "interruption": "documented",
        "harness-context": "documented",
        "side-question": "documented"
      },
      "deliveries": [
        {
          "name": "turn/steer",
          "intent": "user-steering",
          "surface": "App Server JSON-RPC turn/steer",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": "conditional",
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "all",
          "evidence": [
            "steering-codex-codex-rs-core-src-session-turn-rs",
            "codex-appserver-docs",
            "steering-codex-codex-rs-app-server-protocol-src-protocol-v2-turn-rs",
            "steering-codex-codex-rs-app-server-src-request-processors-turn-processor-rs",
            "steering-codex-codex-rs-core-src-session-turn-input-rs",
            "steering-codex-codex-rs-core-src-session-input-queue-rs",
            "steering-codex-codex-rs-core-src-session-mod-rs",
            "steering-codex-codex-rs-features-src-lib-rs",
            "steering-codex-codex-rs-core-src-hook-runtime-rs"
          ],
          "reference": "https://github.com/openai/codex/blob/092d3acd6bec3e3a14bdc7e7a2810ab628ab759d/codex-rs/core/src/session/turn.rs#L416-L444",
          "notes": [
            "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."
          ]
        },
        {
          "name": "turn/interrupt",
          "intent": "interruption",
          "surface": "App Server JSON-RPC turn/interrupt",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "cancel",
          "interruptsRunningTool": "conditional",
          "skipsRemainingTools": true,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "codex-appserver-docs",
            "steering-codex-codex-rs-app-server-src-request-processors-turn-processor-rs",
            "steering-codex-codex-rs-core-src-tasks-mod-rs",
            "steering-codex-codex-rs-core-src-context-turn-aborted-rs",
            "steering-codex-codex-rs-core-src-spawn-rs",
            "codex-hooks-docs"
          ],
          "reference": "https://learn.chatgpt.com/docs/app-server#interrupt-a-turn",
          "notes": [
            "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."
          ]
        },
        {
          "name": "async-command-hook-output",
          "intent": "harness-context",
          "surface": "Background (async: true) command hooks",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "safe-boundary",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "ui-only",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "codex-hooks-docs",
            "steering-codex-codex-rs-core-src-hook-runtime-rs",
            "steering-codex-codex-rs-core-src-session-turn-rs",
            "steering-codex-codex-rs-core-src-session-inject-rs",
            "steering-codex-codex-rs-app-server-protocol-src-protocol-v2-hook-rs"
          ],
          "reference": "https://learn.chatgpt.com/docs/hooks#how-background-hooks-run",
          "notes": [
            "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.",
            "Comes from: hook/context (developer message)."
          ]
        },
        {
          "name": "sync-hook-additionalContext",
          "intent": "harness-context",
          "surface": "Synchronous hooks (SessionStart, UserPromptSubmit, PostToolUse, Pre/PostCompact, SubagentStart/Stop, Stop)",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": "conditional",
          "representation": "injected-context",
          "visibility": "ui-only",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["codex-hooks-docs", "steering-codex-codex-rs-core-src-hook-runtime-rs"],
          "reference": "https://learn.chatgpt.com/docs/hooks#run-hooks-in-the-background",
          "notes": [
            "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).",
            "Comes from: hook/context (developer message)."
          ]
        },
        {
          "name": "thread/inject_items",
          "intent": "harness-context",
          "surface": "App Server JSON-RPC thread/inject_items",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "conditional",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "hidden",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "codex-appserver-docs",
            "steering-codex-codex-rs-app-server-protocol-src-protocol-v2-thread-rs",
            "steering-codex-codex-rs-app-server-src-request-processors-turn-processor-rs",
            "steering-codex-codex-rs-core-src-session-inject-rs"
          ],
          "reference": "https://learn.chatgpt.com/docs/app-server#inject-items-into-a-thread",
          "notes": [
            "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.",
            "Comes from: client (raw Responses API items, any role)."
          ]
        },
        {
          "name": "thread/queue/add",
          "intent": "user-steering",
          "surface": "App Server JSON-RPC thread/queue/* (experimental)",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "user-reorderable",
          "evidence": [
            "steering-codex-codex-rs-app-server-protocol-src-protocol-common-rs",
            "steering-codex-codex-rs-app-server-protocol-src-protocol-v2-thread-rs",
            "steering-codex-codex-rs-ext-queue-src-service-rs",
            "steering-codex--app-server-src-request-processors-thread-queue-processor-rs"
          ],
          "reference": "https://github.com/openai/codex/blob/092d3acd6bec3e3a14bdc7e7a2810ab628ab759d/codex-rs/app-server-protocol/src/protocol/common.rs#L630-L665",
          "notes": [
            "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."
          ]
        },
        {
          "name": "thread/fork-ephemeral",
          "intent": "side-question",
          "surface": "App Server JSON-RPC thread/fork {ephemeral:true}",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "independent",
          "effect": "answer-separately",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "side-channel",
          "visibility": "ui-only",
          "persistence": "ephemeral",
          "ordering": "not-applicable",
          "evidence": [
            "codex-appserver-docs",
            "steering-codex-codex-rs-app-server-protocol-src-protocol-v2-thread-rs"
          ],
          "reference": "https://learn.chatgpt.com/docs/app-server#start-or-resume-a-thread",
          "notes": [
            "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."
          ]
        }
      ]
    },
    {
      "harness": "cursor-agent",
      "variant": "cli-current",
      "default": true,
      "intentCoverage": {
        "user-steering": "documented",
        "interruption": "documented",
        "harness-context": "documented",
        "side-question": "documented"
      },
      "deliveries": [
        {
          "name": "enter-steer",
          "intent": "user-steering",
          "surface": "CLI interactive prompt: Enter while the agent works",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "safe-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "unknown",
          "evidence": ["cursor-cli-changelog", "cursor-agent-docs"],
          "reference": "https://cursor.com/docs/cli/changelog#aug-11-2026-steering-and-subagents",
          "notes": [
            "'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)."
          ]
        },
        {
          "name": "enter-again-interrupt",
          "intent": "interruption",
          "surface": "CLI: second Enter while a turn runs",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "interrupt",
          "interruptsRunningTool": false,
          "skipsRemainingTools": true,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["cursor-cli-changelog", "cursor-agent-docs"],
          "reference": "https://cursor.com/docs/cli/changelog#aug-11-2026-steering-and-subagents",
          "notes": [
            "'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).",
            "Running tools: 'follow-ups move running tools to the background instead of killing them' (may-7). The tool is detached rather than killed; this is inferred, not stated for the current UI.",
            "visibility=transcript is inferred from the queued message being sent as the next user message."
          ]
        },
        {
          "name": "ctrl-c-stop",
          "intent": "interruption",
          "surface": "CLI: Ctrl+C / Stop",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "cancel",
          "interruptsRunningTool": "unknown",
          "skipsRemainingTools": true,
          "representation": "unknown",
          "visibility": "unknown",
          "persistence": "unknown",
          "ordering": "not-applicable",
          "evidence": ["cursor-cli-changelog", "cursor-agent-docs", "cursor-hooks-docs"],
          "reference": "https://cursor.com/docs/cli/changelog#may-7-2026",
          "notes": [
            "'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'."
          ]
        },
        {
          "name": "shell-approval-skip-and-tell",
          "intent": "user-steering",
          "surface": "CLI shell approval prompt: 'Skip & tell the agent what to do instead'",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "next-tool-boundary",
          "effect": "redirect",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "unknown",
          "visibility": "unknown",
          "persistence": "unknown",
          "ordering": "not-applicable",
          "evidence": ["cursor-cli-changelog"],
          "reference": "https://cursor.com/docs/cli/changelog#jul-13-2026-approvals-and-trust",
          "notes": [
            "'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."
          ]
        },
        {
          "name": "btw-side-question",
          "intent": "side-question",
          "surface": "CLI /btw",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "independent",
          "effect": "answer-separately",
          "interruptsRunningTool": "unknown",
          "skipsRemainingTools": "unknown",
          "representation": "side-channel",
          "visibility": "ui-only",
          "persistence": "ephemeral",
          "ordering": "not-applicable",
          "evidence": ["cursor-cli-changelog", "cursor-agent-docs"],
          "reference": "https://cursor.com/docs/cli/changelog#april-2026",
          "notes": [
            "'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."
          ]
        },
        {
          "name": "hook-sessionStart-additional_context",
          "intent": "harness-context",
          "surface": "sessionStart hook (hooks.json)",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "unknown",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["cursor-hooks-docs", "cursor-cli-changelog"],
          "reference": "https://cursor.com/docs/hooks#sessionstart",
          "notes": [
            "'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.",
            "Comes from: hook script (project/user/team/enterprise hooks.json; Claude Code settings.json merged)."
          ]
        },
        {
          "name": "hook-postToolUse-additional_context",
          "intent": "harness-context",
          "surface": "postToolUse / postToolUseFailure hooks",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "unknown",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["cursor-hooks-docs"],
          "reference": "https://cursor.com/docs/hooks#posttooluse",
          "notes": [
            "'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.",
            "Comes from: hook script."
          ]
        },
        {
          "name": "hook-stop-followup_message",
          "intent": "harness-context",
          "surface": "stop / subagentStop hook followup_message",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "unknown",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["cursor-hooks-docs", "cursor-cli-changelog"],
          "reference": "https://cursor.com/docs/hooks#stop",
          "notes": [
            "'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).",
            "Comes from: hook script."
          ]
        },
        {
          "name": "hook-deny-agent_message",
          "intent": "harness-context",
          "surface": "preToolUse / beforeShellExecution / beforeMCPExecution deny",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "conditional",
          "effect": "redirect",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "unknown",
          "persistence": "unknown",
          "ordering": "not-applicable",
          "evidence": ["cursor-hooks-docs"],
          "reference": "https://cursor.com/docs/hooks#pretooluse",
          "notes": [
            "'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.",
            "Comes from: hook script."
          ]
        }
      ]
    },
    {
      "harness": "cursor-agent",
      "variant": "agent-ui-current",
      "default": false,
      "intentCoverage": {
        "user-steering": "documented",
        "interruption": "unknown",
        "harness-context": "documented",
        "side-question": "documented"
      },
      "deliveries": [
        {
          "name": "steer-running-agent",
          "intent": "user-steering",
          "surface": "cursor.com/agents; rolling out in the Agents Window ('Send now' or Enter twice)",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "next-tool-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "unknown",
          "evidence": ["cursor-agent-docs"],
          "reference": "https://cursor.com/docs/agent/overview#steer-a-running-agent",
          "notes": [
            "'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.'"
          ]
        },
        {
          "name": "queued-message",
          "intent": "user-steering",
          "surface": "Agent UI queue (Enter in the editor agent panel; Tab in the steering UI)",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "user-reorderable",
          "evidence": ["cursor-agent-docs"],
          "reference": "https://cursor.com/docs/agent/overview#using-the-queue",
          "notes": [
            "'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."
          ]
        },
        {
          "name": "immediate-message",
          "intent": "user-steering",
          "surface": "Agent UI: Cmd/Ctrl+Enter",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "next-tool-boundary",
          "effect": "redirect",
          "interruptsRunningTool": "unknown",
          "skipsRemainingTools": "unknown",
          "representation": "mutated-user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["cursor-agent-docs"],
          "reference": "https://cursor.com/docs/agent/overview#immediate-messaging",
          "notes": [
            "'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."
          ]
        },
        {
          "name": "side-chat",
          "intent": "side-question",
          "surface": "/side or /btw, 'Ask in Side Chat', Shift+Cmd/Ctrl+S",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "independent",
          "effect": "answer-separately",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "side-channel",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["cursor-agent-docs", "steering-cursor-agent-help-ai-features-side-chats"],
          "reference": "https://cursor.com/docs/agent/overview#side-chats",
          "notes": [
            "'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."
          ]
        },
        {
          "name": "hook-sessionStart-additional_context",
          "intent": "harness-context",
          "surface": "sessionStart hook",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "unknown",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["cursor-hooks-docs"],
          "reference": "https://cursor.com/docs/hooks#sessionstart",
          "notes": [
            "'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.",
            "Comes from: hook script."
          ]
        },
        {
          "name": "hook-postToolUse-additional_context",
          "intent": "harness-context",
          "surface": "postToolUse / postToolUseFailure hooks",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "unknown",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["cursor-hooks-docs"],
          "reference": "https://cursor.com/docs/hooks#posttooluse",
          "notes": [
            "'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.",
            "Comes from: hook script."
          ]
        },
        {
          "name": "hook-stop-followup_message",
          "intent": "harness-context",
          "surface": "stop / subagentStop hook",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "unknown",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": ["cursor-hooks-docs"],
          "reference": "https://cursor.com/docs/hooks#stop",
          "notes": [
            "'automatically submit it as the next user message'; capped by loop_limit (default 5).",
            "Cloud: 'stop | Yes'.",
            "Comes from: hook script."
          ]
        },
        {
          "name": "hook-deny-agent_message",
          "intent": "harness-context",
          "surface": "preToolUse / beforeShellExecution / beforeMCPExecution deny",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "conditional",
          "effect": "redirect",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "unknown",
          "persistence": "unknown",
          "ordering": "not-applicable",
          "evidence": ["cursor-hooks-docs"],
          "reference": "https://cursor.com/docs/hooks#pretooluse",
          "notes": [
            "'Message fed back to the agent when the action is denied'.",
            "beforeMCPExecution is not available in cloud agents.",
            "Comes from: hook script."
          ]
        }
      ]
    },
    {
      "harness": "opencode",
      "variant": "1.x",
      "default": false,
      "intentCoverage": {
        "user-steering": "documented",
        "interruption": "documented",
        "harness-context": "documented",
        "side-question": "unsupported"
      },
      "deliveries": [
        {
          "name": "tui-mid-run-prompt",
          "intent": "user-steering",
          "surface": "TUI: type a prompt and press Enter while the session is busy",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "all",
          "evidence": [
            "steering-opencode-packages-opencode-src-session-prompt-ts",
            "steering-opencode-packages-tui-src-component-prompt-index-tsx",
            "steering-opencode-packages-opencode-src-effect-runner-ts",
            "steering-opencode-packages-opencode-src-session-message-v2-ts",
            "steering-opencode-packages-tui-src-routes-session-index-tsx"
          ],
          "reference": "https://github.com/anomalyco/opencode/blob/733b2c627e5aec39cb33cc22373bdce33053ef2c/packages/opencode/src/session/prompt.ts#L1088-L1130",
          "notes": [
            "The TUI calls session.prompt at once even when the session is busy; there is no busy check and no client-side queue.",
            "The message is saved immediately. The running loop reloads history at the top of each step, so the newest user message becomes lastUser and the loop continues instead of exiting.",
            "The TUI shows a 'QUEUED' badge on messages after an unfinished assistant message, even though they steer the current run.",
            "Tool calls already running in the current step finish first. All waiting messages reach the model at the next step as plain user messages, with no wrapper."
          ]
        },
        {
          "name": "api-prompt",
          "intent": "user-steering",
          "surface": "Server API: POST /session/:id/message or POST /session/:id/prompt_async (SDK session.prompt / promptAsync)",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "all",
          "evidence": [
            "steering-opencode-docs-server",
            "steering-opencode-pencode-src-server-routes-instance-httpapi-groups-session-ts",
            "steering-opencode-ncode-src-server-routes-instance-httpapi-handlers-session-ts",
            "steering-opencode-packages-opencode-src-effect-runner-ts"
          ],
          "reference": "https://opencode.ai/docs/server/#messages",
          "notes": [
            "Same core path as the TUI. If a run is already active, ensureRunning joins it: /message waits for that run to finish, and prompt_async returns 204 straight away.",
            "There is no steer, queue or delivery parameter, and no dedicated steer endpoint.",
            "Comes from: user or API client."
          ]
        },
        {
          "name": "run-interactive-queue",
          "intent": "user-steering",
          "surface": "`opencode run --interactive` (split-footer mode): submit while a turn is active",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-opencode-packages-opencode-src-cli-cmd-run-runtime-queue-ts",
            "steering-opencode-packages-opencode-src-cli-cmd-run-ts"
          ],
          "reference": "https://github.com/anomalyco/opencode/blob/733b2c627e5aec39cb33cc22373bdce33053ef2c/packages/opencode/src/cli/cmd/run/runtime.queue.ts#L1-L9",
          "notes": [
            "Client-side queue drained one turn at a time. While a prompt waits it exists only in the footer UI, where it can be edited or removed; it becomes a transcript message only when its turn begins.",
            "Applies to `run -i` only, not the main TUI."
          ]
        },
        {
          "name": "double-esc-abort",
          "intent": "interruption",
          "surface": "TUI: Esc twice within 5 s (session_interrupt, default 'escape') -> POST /session/:id/abort",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "cancel",
          "interruptsRunningTool": true,
          "skipsRemainingTools": true,
          "representation": "side-channel",
          "visibility": "transcript",
          "persistence": "turn",
          "ordering": "not-applicable",
          "evidence": [
            "steering-opencode-packages-tui-src-component-prompt-index-tsx",
            "steering-opencode-packages-tui-src-config-keybind-ts",
            "steering-opencode-pencode-src-server-routes-instance-httpapi-groups-session-ts",
            "steering-opencode-ncode-src-server-routes-instance-httpapi-handlers-session-ts",
            "steering-opencode-packages-opencode-src-session-run-state-ts",
            "steering-opencode-packages-opencode-src-effect-runner-ts",
            "steering-opencode-packages-opencode-src-session-prompt-ts",
            "steering-opencode-packages-opencode-src-cli-cmd-run-footer-ts",
            "steering-opencode-docs-server",
            "steering-opencode-docs-keybinds"
          ],
          "reference": "https://github.com/anomalyco/opencode/blob/733b2c627e5aec39cb33cc22373bdce33053ef2c/packages/tui/src/component/prompt/index.tsx#L392-L422",
          "notes": [
            "The first Esc only arms the interrupt; the second Esc calls session.abort.",
            "Abort runs SessionPrompt.cancel, which interrupts the run fiber and cancels background jobs. The assistant message gets an AbortedError and tool parts are marked interrupted.",
            "Messages sent mid-run stay in history but are not answered until the next prompt starts a new loop.",
            "`opencode run -i` has its own two-press Esc."
          ]
        },
        {
          "name": "plugin-chat-message",
          "intent": "harness-context",
          "surface": "Plugin hook chat.message",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "mutated-user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "steering-opencode-packages-opencode-src-session-prompt-ts",
            "steering-opencode-packages-plugin-src-index-ts",
            "steering-opencode-packages-tui-src-routes-session-index-tsx"
          ],
          "reference": "https://github.com/anomalyco/opencode/blob/733b2c627e5aec39cb33cc22373bdce33053ef2c/packages/opencode/src/session/prompt.ts#L999-L1009",
          "notes": [
            "Fires for every user message, including ones sent mid-run, before the message is saved.",
            "The plugin can edit the message and its parts; edits are saved.",
            "Parts the plugin marks synthetic are hidden in the TUI.",
            "Comes from: plugin."
          ]
        },
        {
          "name": "plugin-messages-system-transform",
          "intent": "harness-context",
          "surface": "Plugin hooks experimental.chat.messages.transform / experimental.chat.system.transform",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "hidden",
          "persistence": "ephemeral",
          "ordering": "not-applicable",
          "evidence": [
            "steering-opencode-packages-opencode-src-session-prompt-ts",
            "steering-opencode-packages-plugin-src-index-ts",
            "steering-opencode-packages-opencode-src-session-compaction-ts"
          ],
          "reference": "https://github.com/anomalyco/opencode/blob/733b2c627e5aec39cb33cc22373bdce33053ef2c/packages/opencode/src/session/prompt.ts#L1255",
          "notes": [
            "Runs before every model step on the in-memory message array, which is reloaded each step. Changes are not saved unless the plugin saves them itself.",
            "Also runs during compaction.",
            "Experimental, and not covered on the docs plugin page.",
            "Comes from: plugin."
          ]
        },
        {
          "name": "api-noreply-context",
          "intent": "harness-context",
          "surface": "Server/SDK session.prompt with noReply:true (synthetic parts) or per-message `system` string",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "conditional",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "hidden",
          "persistence": "conversation",
          "ordering": "all",
          "evidence": [
            "steering-opencode-packages-opencode-src-session-prompt-ts",
            "steering-opencode-packages-opencode-src-session-llm-request-ts",
            "steering-opencode-docs-server"
          ],
          "reference": "https://github.com/anomalyco/opencode/blob/733b2c627e5aec39cb33cc22373bdce33053ef2c/packages/opencode/src/session/prompt.ts#L1069",
          "notes": [
            "With noReply the message is saved but no loop starts.",
            "If a run is active, the loop sees the message at its next step; if the session is idle, it waits for the next prompt.",
            "Visibility is 'hidden' only when the parts are synthetic; ordinary text parts show in the transcript.",
            "`system` on a user message is appended to the system prompt for that turn.",
            "Comes from: plugin or API client."
          ]
        }
      ]
    },
    {
      "harness": "opencode",
      "variant": "2.x",
      "default": true,
      "intentCoverage": {
        "user-steering": "documented",
        "interruption": "documented",
        "harness-context": "documented",
        "side-question": "documented"
      },
      "deliveries": [
        {
          "name": "steer",
          "intent": "user-steering",
          "surface": "TUI Enter (prompt.submit, delivery 'steer' default) / POST /api/session/:id/prompt with delivery=steer",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "all",
          "evidence": [
            "steering-opencode-v2-docs-api",
            "steering-opencode-specs-v2-session-md",
            "steering-opencode-packages-protocol-src-groups-session-ts",
            "steering-opencode-packages-core-src-session-runner-llm-ts",
            "steering-opencode-packages-core-src-session-inbox-ts",
            "steering-opencode-packages-tui-src-component-prompt-index-tsx"
          ],
          "reference": "https://opencode.ai/v2/docs/api/#session.prompt",
          "notes": [
            "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.",
            "Comes from: user or API client."
          ]
        },
        {
          "name": "queue",
          "intent": "user-steering",
          "surface": "TUI <leader>Enter (prompt.queue) / POST /api/session/:id/prompt with delivery=queue",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "one-at-a-time",
          "evidence": [
            "steering-opencode-specs-v2-session-md",
            "steering-opencode-packages-tui-src-config-keybind-ts",
            "steering-opencode-packages-tui-src-component-prompt-index-tsx",
            "steering-opencode-packages-tui-src-routes-session-index-tsx",
            "steering-opencode-packages-protocol-src-groups-session-ts",
            "steering-opencode-v2-docs-api"
          ],
          "reference": "https://github.com/anomalyco/opencode/blob/042d0832f5f3a73eaa039a58a7d565776f6d4290/specs/v2/session.md#L18-L19",
          "notes": [
            "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.",
            "Comes from: user or API client."
          ]
        },
        {
          "name": "double-esc-interrupt",
          "intent": "interruption",
          "surface": "TUI Esc twice within 5 s (session.interrupt) -> POST /api/session/:id/interrupt?resume=true",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "interrupt",
          "interruptsRunningTool": true,
          "skipsRemainingTools": true,
          "representation": "side-channel",
          "visibility": "transcript",
          "persistence": "turn",
          "ordering": "not-applicable",
          "evidence": [
            "steering-opencode-v2-docs-api",
            "steering-opencode-packages-tui-src-config-keybind-ts",
            "steering-opencode-packages-tui-src-component-prompt-index-tsx",
            "steering-opencode-packages-protocol-src-groups-session-ts",
            "steering-opencode-packages-core-src-session-execution-ts",
            "steering-opencode-packages-core-src-session-runner-step-ts",
            "steering-opencode-specs-v2-session-md"
          ],
          "reference": "https://opencode.ai/v2/docs/api/#session.interrupt",
          "notes": [
            "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."
          ]
        },
        {
          "name": "btw",
          "intent": "side-question",
          "surface": "TUI /btw <question> (command session.aside, built-in plugin opencode.btw) -> POST /api/session/:id/generate",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "independent",
          "effect": "answer-separately",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "side-channel",
          "visibility": "ui-only",
          "persistence": "ephemeral",
          "ordering": "not-applicable",
          "evidence": [
            "steering-opencode-packages-tui-src-feature-plugins-prompt-btw-tsx",
            "steering-opencode-packages-tui-src-plugin-builtins-ts",
            "steering-opencode-packages-protocol-src-groups-session-ts",
            "steering-opencode-packages-core-src-session-generate-ts",
            "steering-opencode-v2-docs-api"
          ],
          "reference": "https://github.com/anomalyco/opencode/blob/042d0832f5f3a73eaa039a58a7d565776f6d4290/packages/tui/src/feature-plugins/prompt/btw.tsx#L15-L80",
          "notes": [
            "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."
          ]
        },
        {
          "name": "api-synthetic",
          "intent": "harness-context",
          "surface": "POST /api/session/:id/synthetic (plugin ctx.session.synthetic)",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "conditional",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-opencode-v2-docs-api",
            "steering-opencode-packages-protocol-src-groups-session-ts",
            "steering-opencode-specs-v2-session-md"
          ],
          "reference": "https://opencode.ai/v2/docs/api/#session.synthetic",
          "notes": [
            "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).",
            "Comes from: plugin or API client."
          ]
        },
        {
          "name": "plugin-prompt-hook",
          "intent": "harness-context",
          "surface": "Plugin ctx.session.hook(\"prompt\") (v1 chat.message successor)",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "mutated-user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "steering-opencode-v2-docs-build-plugins-migrate-v1",
            "steering-opencode-packages-core-src-session-prompt-ts",
            "steering-opencode-packages-plugin-src-effect-session-ts"
          ],
          "reference": "https://opencode.ai/v2/docs/build/plugins/migrate-v1/#migrate-request-hooks",
          "notes": [
            "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.",
            "Comes from: plugin."
          ]
        },
        {
          "name": "plugin-context-hook",
          "intent": "harness-context",
          "surface": "Plugin ctx.session.hook(\"context\") (v1 experimental.chat.messages/system.transform successor)",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "hidden",
          "persistence": "ephemeral",
          "ordering": "not-applicable",
          "evidence": [
            "steering-opencode-v2-docs-build-plugins-effect",
            "steering-opencode-packages-core-src-session-model-request-ts",
            "steering-opencode-packages-plugin-src-effect-session-ts",
            "steering-opencode-packages-core-src-plugin-plan-ts"
          ],
          "reference": "https://opencode.ai/v2/docs/build/plugins/effect/#hooks",
          "notes": [
            "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).",
            "Comes from: plugin."
          ]
        },
        {
          "name": "api-instruction-entries",
          "intent": "harness-context",
          "surface": "Experimental API PUT/DELETE /api/experimental/session/:id/instructions/entries/:key",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "next-model-boundary",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "hidden",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "steering-opencode-packages-protocol-src-groups-session-ts",
            "steering-opencode-packages-core-src-session-generate-ts"
          ],
          "reference": "https://github.com/anomalyco/opencode/blob/042d0832f5f3a73eaa039a58a7d565776f6d4290/packages/protocol/src/groups/session.ts#L666-L706",
          "notes": [
            "Stores durable instruction entries; changes are announced to the model at the next step boundary.",
            "Experimental.",
            "Comes from: API client."
          ]
        }
      ]
    },
    {
      "harness": "pi",
      "variant": "interactive-current",
      "default": true,
      "intentCoverage": {
        "user-steering": "documented",
        "interruption": "documented",
        "harness-context": "documented",
        "side-question": "unsupported"
      },
      "deliveries": [
        {
          "name": "enter-steer",
          "intent": "user-steering",
          "surface": "TUI: type and press Enter while the agent is working",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "next-model-boundary",
          "effect": "redirect",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "one-at-a-time",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-usage-md",
            "steering-pi-kages-coding-agent-src-modes-interactive-interactive-mode-ts",
            "steering-pi-packages-coding-agent-docs-keybindings-md",
            "steering-pi-packages-coding-agent-docs-settings-md",
            "steering-pi-packages-agent-src-agent-loop-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/usage.md#L29-L40",
          "notes": [
            "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."
          ]
        },
        {
          "name": "alt-enter-follow-up",
          "intent": "user-steering",
          "surface": "TUI: Alt+Enter (Ctrl+Q on Windows/WSL), app.message.followUp",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "one-at-a-time",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-keybindings-md",
            "steering-pi-kages-coding-agent-src-modes-interactive-interactive-mode-ts",
            "steering-pi-packages-coding-agent-docs-usage-md",
            "steering-pi-packages-coding-agent-docs-settings-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/keybindings.md#L166",
          "notes": [
            "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."
          ]
        },
        {
          "name": "alt-up-dequeue",
          "intent": "user-steering",
          "surface": "TUI: Alt+Up (Alt+Q on Windows/WSL), app.message.dequeue",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "cancel",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "side-channel",
          "visibility": "ui-only",
          "persistence": "ephemeral",
          "ordering": "user-reorderable",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-keybindings-md",
            "steering-pi-kages-coding-agent-src-modes-interactive-interactive-mode-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/keybindings.md#L167",
          "notes": [
            "Clears both queues and puts all queued text back in the editor, where it can be edited or reordered and resent.",
            "Does not abort."
          ]
        },
        {
          "name": "escape-abort",
          "intent": "interruption",
          "surface": "TUI: Escape (app.interrupt)",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "cancel",
          "interruptsRunningTool": "conditional",
          "skipsRemainingTools": true,
          "representation": "side-channel",
          "visibility": "transcript",
          "persistence": "ephemeral",
          "ordering": "not-applicable",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-keybindings-md",
            "steering-pi-kages-coding-agent-src-modes-interactive-interactive-mode-ts",
            "steering-pi-packages-coding-agent-docs-usage-md",
            "steering-pi-packages-coding-agent-docs-how-pi-works-md",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/keybindings.md#L124",
          "notes": [
            "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."
          ]
        },
        {
          "name": "bang-bash",
          "intent": "harness-context",
          "surface": "TUI: !cmd (in context) / !!cmd (excluded from context)",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "conditional",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-pi-kages-coding-agent-src-modes-interactive-interactive-mode-ts",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-messages-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/modes/interactive/interactive-mode.ts#L3346-L3361",
          "notes": [
            "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.",
            "Comes from: user (shell output)."
          ]
        },
        {
          "name": "ext-send-message-queued",
          "intent": "harness-context",
          "surface": "Extension pi.sendMessage(customMsg, {deliverAs:\"steer\"|\"followUp\"}) while streaming",
          "admission": "active-turn",
          "authority": "context",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "one-at-a-time",
          "evidence": [
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-coding-agent-src-core-messages-ts",
            "steering-pi-packages-coding-agent-docs-extensions-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/agent-session.ts#L2305-L2338",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-send-message-no-trigger",
          "intent": "harness-context",
          "surface": "Extension pi.sendMessage(..., {triggerTurn:false}) while streaming",
          "admission": "active-turn",
          "authority": "context",
          "deliveryBoundary": "safe-boundary",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": ["steering-pi-packages-coding-agent-src-core-agent-session-ts"],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/agent-session.ts#L2345-L2350",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-send-message-trigger-turn",
          "intent": "harness-context",
          "surface": "Extension pi.sendMessage(..., {triggerTurn:true}) while idle",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "immediate",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-coding-agent-docs-extensions-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/agent-session.ts#L2339-L2344",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-send-message-next-turn",
          "intent": "harness-context",
          "surface": "Extension pi.sendMessage(..., {deliverAs:\"nextTurn\"})",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "conditional",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": ["steering-pi-packages-coding-agent-src-core-agent-session-ts"],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/agent-session.ts#L2331-L2332",
          "notes": [
            "Held until the next run starts, then injected next to that run's starting message.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-send-user-message",
          "intent": "user-steering",
          "surface": "Extension pi.sendUserMessage(content, {deliverAs})",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "conditional",
          "effect": "redirect",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "one-at-a-time",
          "evidence": [
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/agent-session.ts#L2382-L2420",
          "notes": [
            "Goes through prompt(): starts a run when idle; while streaming, deliverAs steer or followUp is required.",
            "Template expansion is off by default.",
            "Comes from: extension (on the user's behalf)."
          ]
        },
        {
          "name": "ext-before-agent-start",
          "intent": "harness-context",
          "surface": "Extension before_agent_start handler returning message or system prompt changes",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-extensions-md",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/extensions.md#L104",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-context",
          "intent": "harness-context",
          "surface": "Extension context / context_with_system handler",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "hidden",
          "persistence": "turn",
          "ordering": "not-applicable",
          "evidence": [
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-runner-ts",
            "steering-pi-packages-coding-agent-docs-extensions-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/extensions/types.ts#L878-L888",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-turn-end",
          "intent": "harness-context",
          "surface": "Extension turn_end handler returning entries and/or continue:true",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-extensions-md",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-agent-src-agent-loop-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/extensions.md#L118",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-agent-before-settle",
          "intent": "harness-context",
          "surface": "Extension agent_before_settle handler returning entries and/or continue:true",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-extensions-md",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/extensions.md#L65-L68",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-input-transform",
          "intent": "harness-context",
          "surface": "Extension input handler (transform/handled)",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "immediate",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "mutated-user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-docs-rpc-commands-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/extensions/types.ts#L1170-L1187",
          "notes": [
            "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\".",
            "Comes from: extension."
          ]
        }
      ]
    },
    {
      "harness": "pi",
      "variant": "rpc-current",
      "default": false,
      "intentCoverage": {
        "user-steering": "documented",
        "interruption": "documented",
        "harness-context": "documented",
        "side-question": "unsupported"
      },
      "deliveries": [
        {
          "name": "steer",
          "intent": "user-steering",
          "surface": "RPC steer command, or prompt with streamingBehavior:\"steer\"; SDK session.steer()",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "next-model-boundary",
          "effect": "redirect",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "one-at-a-time",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-rpc-commands-md",
            "steering-pi-packages-agent-src-agent-loop-ts",
            "steering-pi-packages-agent-readme-md",
            "steering-pi-packages-agent-src-agent-ts",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-docs-sdk-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/rpc-commands.md#L46-L68",
          "notes": [
            "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."
          ]
        },
        {
          "name": "follow-up",
          "intent": "user-steering",
          "surface": "RPC follow_up command, or prompt with streamingBehavior:\"followUp\"; SDK session.followUp()",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "after-active-turn",
          "effect": "queue",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "one-at-a-time",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-rpc-commands-md",
            "steering-pi-packages-agent-src-agent-loop-ts",
            "steering-pi-packages-agent-readme-md",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/rpc-commands.md#L70-L92",
          "notes": [
            "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."
          ]
        },
        {
          "name": "abort",
          "intent": "interruption",
          "surface": "RPC abort; SDK session.abort()",
          "admission": "active-turn",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "cancel",
          "interruptsRunningTool": "conditional",
          "skipsRemainingTools": true,
          "representation": "side-channel",
          "visibility": "transcript",
          "persistence": "ephemeral",
          "ordering": "not-applicable",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-rpc-commands-md",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-agent-src-agent-ts",
            "steering-pi-packages-agent-src-agent-loop-ts",
            "steering-pi-packages-coding-agent-src-core-tools-bash-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/rpc-commands.md#L94-L105",
          "notes": [
            "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."
          ]
        },
        {
          "name": "clear-queue",
          "intent": "interruption",
          "surface": "RPC clear_queue; SDK session.clearQueue()",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "immediate",
          "effect": "cancel",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "side-channel",
          "visibility": "ui-only",
          "persistence": "ephemeral",
          "ordering": "not-applicable",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-rpc-commands-md",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-docs-json-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/rpc-commands.md#L107-L128",
          "notes": [
            "Removes all queued steering and follow-up messages and returns their text; emits queue_update."
          ]
        },
        {
          "name": "user-bash",
          "intent": "harness-context",
          "surface": "RPC bash command (with excludeFromContext); SDK executeBash/recordBashResult",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "conditional",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-rpc-commands-md",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-messages-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/rpc-commands.md#L449-L509",
          "notes": [
            "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.",
            "Comes from: user (shell output)."
          ]
        },
        {
          "name": "ext-send-message-queued",
          "intent": "harness-context",
          "surface": "Extension pi.sendMessage(customMsg, {deliverAs:\"steer\"|\"followUp\"}) while streaming",
          "admission": "active-turn",
          "authority": "context",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "one-at-a-time",
          "evidence": [
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-coding-agent-src-core-messages-ts",
            "steering-pi-packages-coding-agent-docs-extensions-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/agent-session.ts#L2305-L2338",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-send-message-no-trigger",
          "intent": "harness-context",
          "surface": "Extension pi.sendMessage(..., {triggerTurn:false}) while streaming",
          "admission": "active-turn",
          "authority": "context",
          "deliveryBoundary": "safe-boundary",
          "effect": "inform",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": ["steering-pi-packages-coding-agent-src-core-agent-session-ts"],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/agent-session.ts#L2345-L2350",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-send-message-trigger-turn",
          "intent": "harness-context",
          "surface": "Extension pi.sendMessage(..., {triggerTurn:true}) while idle",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "immediate",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-coding-agent-docs-extensions-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/agent-session.ts#L2339-L2344",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-send-message-next-turn",
          "intent": "harness-context",
          "surface": "Extension pi.sendMessage(..., {deliverAs:\"nextTurn\"})",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "conditional",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": ["steering-pi-packages-coding-agent-src-core-agent-session-ts"],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/agent-session.ts#L2331-L2332",
          "notes": [
            "Held until the next run starts, then injected next to that run's starting message.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-send-user-message",
          "intent": "user-steering",
          "surface": "Extension pi.sendUserMessage(content, {deliverAs})",
          "admission": "idle-or-active",
          "authority": "user",
          "deliveryBoundary": "conditional",
          "effect": "redirect",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "one-at-a-time",
          "evidence": [
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/agent-session.ts#L2382-L2420",
          "notes": [
            "Goes through prompt(): starts a run when idle; while streaming, deliverAs steer or followUp is required.",
            "Template expansion is off by default.",
            "Comes from: extension (on the user's behalf)."
          ]
        },
        {
          "name": "ext-before-agent-start",
          "intent": "harness-context",
          "surface": "Extension before_agent_start handler returning message or system prompt changes",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-extensions-md",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/extensions.md#L104",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-context",
          "intent": "harness-context",
          "surface": "Extension context / context_with_system handler",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "next-model-boundary",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "hidden",
          "persistence": "turn",
          "ordering": "not-applicable",
          "evidence": [
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-runner-ts",
            "steering-pi-packages-coding-agent-docs-extensions-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/extensions/types.ts#L878-L888",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-turn-end",
          "intent": "harness-context",
          "surface": "Extension turn_end handler returning entries and/or continue:true",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-extensions-md",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-agent-src-agent-loop-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/extensions.md#L118",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-agent-before-settle",
          "intent": "harness-context",
          "surface": "Extension agent_before_settle handler returning entries and/or continue:true",
          "admission": "event",
          "authority": "context",
          "deliveryBoundary": "event-relative",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "injected-context",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "fifo",
          "evidence": [
            "steering-pi-packages-coding-agent-docs-extensions-md",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/docs/extensions.md#L65-L68",
          "notes": [
            "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.",
            "Comes from: extension."
          ]
        },
        {
          "name": "ext-input-transform",
          "intent": "harness-context",
          "surface": "Extension input handler (transform/handled)",
          "admission": "idle-or-active",
          "authority": "context",
          "deliveryBoundary": "immediate",
          "effect": "augment",
          "interruptsRunningTool": false,
          "skipsRemainingTools": false,
          "representation": "mutated-user-message",
          "visibility": "transcript",
          "persistence": "conversation",
          "ordering": "not-applicable",
          "evidence": [
            "steering-pi-packages-coding-agent-src-core-extensions-types-ts",
            "steering-pi-packages-coding-agent-src-core-agent-session-ts",
            "steering-pi-packages-coding-agent-docs-rpc-commands-md"
          ],
          "reference": "https://github.com/earendil-works/pi/blob/06a466935536f8c9bcacb2d5efc0fc021be1bdff/packages/coding-agent/src/core/extensions/types.ts#L1170-L1187",
          "notes": [
            "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\".",
            "Comes from: extension."
          ]
        }
      ]
    }
  ]
}
