# Steering, follow-up, and cancellation are separate controls

Imagine a model requests three tools and the user types “Stop editing; just explain.” There are at least three possible contracts: cancel active work, let the current call finish but skip the rest, or finish the batch and apply the instruction to the next request. A control named “interrupt” does not tell you which one is implemented.

For input arriving during a response/tool batch, pi's core consumes steering after that batch completes; it also polls initially and after next-turn preparation when needed. Follow-up waits until work would otherwise stop. Codex admits input to an active turn with expected-turn guards and has feature-controlled paths that can preempt sampling/retry waits. Goose drains a session's steering queue between model/tool phases; its ACP interface can check an expected active run ID. [Pi scheduling boundaries][p-steer], [Codex start/steer][c-steer], [Goose steering][g-steer].

Cancellation propagates to cooperating work. It does not undo a successful file write or remote action. Nor does a queued steering message prove the model has seen it. A client should expose the distinction between receiving input and admitting it into the next model context.

**Design implication:** specify the admission boundary and test it under a slow model, slow tool, parallel batch, pending approval, and run completion. A stale run ID should not accidentally steer a later task.

## Sidekick: input placement is durable

Sidekick records queued input with its author and requested mode. At the next native main-loop iteration it checks cancellation, places steers, commits their message rows, and rebuilds context. Child, panel-peer, and deliberation loops do not drain that main-thread queue. After an answered turn, settlement can admit waiting follow-ups and unconsumed steers as a new turn; an unanswered settlement leaves them queued. Outpost adapters without steering support downgrade busy steering to follow-up. The arrival of an event and the admission of its input are separate steps. [Iteration boundary][s-steer], [next-turn admission][s-followup], [adapter capabilities][s-adapter] (private source).

[p-steer]: https://github.com/earendil-works/pi/blob/482c1381a00d8d00bc81697e4a980c593db6803e/packages/agent/src/agent-loop.ts#L159-L321
[c-steer]: https://github.com/openai/codex/blob/8c076942cb70fba8e5c4732c23f1faabc4aed484/codex-rs/app-server/src/request_processors/turn_processor.rs#L1045-L1150
[g-steer]: https://github.com/aaif-goose/goose/blob/34a222e3f2b95d8b4a43482b53c2cc957ecce26d/crates/goose/src/agents/state_machine/ops_steer.rs#L46-L98

[s-steer]: https://github.com/datex/sidekick/blob/463d653124e1c2cda46a3f14f01f3d6f384326b1/backend/src/sidekick_worker/services/agent_loop.py#L397-L488
[s-followup]: https://github.com/datex/sidekick/blob/463d653124e1c2cda46a3f14f01f3d6f384326b1/backend/src/sidekick/services/message_queue.py#L603-L679
[s-adapter]: https://github.com/datex/sidekick/blob/463d653124e1c2cda46a3f14f01f3d6f384326b1/backend/src/sidekick/services/message_queue.py#L721-L813
