Skip to main content
When a user hits /stop (or supersedes the turn with a new message in busy_mode="interrupt"), any approval the turn was waiting on is cancelled β€” the bot replies immediately instead of hanging, and a late Allow tap for that stopped turn is dropped.

Quick Start

1

Run a bot with interrupt mode and presentation approval

Set busy_mode="interrupt" and approval="presentation" β€” the turn-liveness wiring is automatic. No extra config.
2

Scenario A β€” /stop unblocks a parked turn

Trigger a tool that needs approval, then send /stop before you tap Allow. The turn unwinds immediately instead of hanging on the approval wait.
The awaiting tool call resolves with reason="cancelled" β€” the bot replies rather than parking indefinitely.
3

Scenario B β€” a late Allow tap is dropped

Tap Allow on that stopped turn’s card after /stop. Nothing happens: no stale tool runs, no stale reply lands.
The resolution is dropped fail-closed and recorded with reason="superseded" in the audit trail.
No new user-facing config is introduced. The binding is wired automatically when a bot uses busy_mode with SessionRunControl (for example Telegram with busy_mode="interrupt"). Requests that carry no session_id/run_generation behave exactly as before.

How It Works

A gateway approval request can optionally bind to the turn liveness of the originating run via session_id + run_generation. When the run is stopped or superseded, the gateway fail-closes any pending future and drops any resolution that arrives afterwards.

User Interaction Flow


What Triggers the Cancel

busy_mode="steer" folds the new message into the same turn, and busy_mode="queue" preserves the running turn β€” neither fires on_supersede, so a pending approval on that turn stays live. busy_mode="interrupt" (and /stop) is the path that cancels.

Backward Compatibility

No new user-facing config, and a no-op for unbound requests. Existing single-turn flows are unchanged.
  • The binding is automatic when a bot uses busy_mode with SessionRunControl (e.g. Telegram with busy_mode="interrupt").
  • Approvals not stamped with session_id/run_generation β€” anything outside the bot β†’ gateway path today β€” are always considered live and keep behaving exactly as before.
  • Today’s single-turn / no-supersede flows behave identically; the cancel path only runs when a turn is actually stopped or superseded.

Failure Semantics

If /stop marks the generation superseded before the tool’s register() call lands, register() denies immediately with reason="superseded" β€” it never parks the caller on a future that no cancel batch will ever complete.
An un-coercible run_generation logs a warning and registers the approval unbound rather than becoming silently un-cancellable. The request still resolves normally (fail-open to unbound, not fail-closed to stuck).
Cancelling generation N does not affect generation N+1 on the same session. A fresh turn started after the stop is unaffected and resolves normally.
The on_supersede callback is best-effort. Any exception is logged and swallowed (see _notify_supersede) so /stop and interrupt handling are never broken by a missing gateway package or manager.

Best Practices

Turn-liveness cancellation matters most when the latest message should win. In busy_mode="interrupt", a superseding message (or /stop) cancels the abandoned turn’s pending approval so the bot never hangs on a decision for a turn the user already replaced.
Queue mode preserves the current turn on purpose β€” that is the point. A follow-up is parked for the next turn, so the running turn’s pending approval stays live and can still be resolved normally.
Pass on_supersede=make_approval_supersede_callback() (or an explicit ExecApprovalManager) to opt in:
Without the callback, the old silent-hang behaviour returns.
Run the bot with busy_mode="interrupt", trigger an approval, and issue /stop:
  • The awaiting tool call must unwind with reason="cancelled".
  • A late reviewer Allow for the stopped turn must be a no-op with reason="superseded" in the audit trail.

Bot Run Control

The busy_mode + /stop side that fires the supersede callback

Gateway Approval Durability

The persistence side β€” pending approvals that survive a restart

Telegram Durable Approval

The transport side β€” Allow/Deny buttons on chat

Gateway Scoped Approvals

Per-request reviewer custody on the same manager