Skip to main content
Stop a hung or runaway gateway turn cooperatively using /stop, an abort WebSocket frame, or a configurable per-turn timeout — or stop the whole session (the running turn and everything queued behind it) with /stop all, /cancel, or abort carrying scope: "session".
Available since PraisonAI#3472. The gateway advertises abort in the hello_ok capability handshake, accepts abort / message_abort WebSocket frames, honours /stop (or stop) in chat, and enforces a configurable per-turn timeout via gateway.per_turn_timeout.Session-scoped stop (/stop all, /cancel, abort with scope: "session") added in PraisonAI#5130. Default remains turn-scoped for back-compat.
The gateway relays a message_abort event to clients when a turn is cancelled, so a custom client can render the turn as “cancelled” instead of hanging or leaking a raw traceback. Pick the scope that matches what you want stopped — the running answer only, or that answer plus everything you queued behind it.

Quick Start

1

Cancel from any chat client with /stop

Any chat client can cancel the in-flight turn by sending /stop (or bare stop, case-insensitive). Reply is either {"type":"aborted","session_id":"..."} or {"type":"no_active_turn","session_id":"..."}.
2

Cancel the whole session with /stop all or /cancel

When the user typed several follow-ups and wants everything they queued cancelled too, a session-scoped stop cancels the in-flight turn and drains the session inbox.
Reply carries the drained count:
/cancel is an alias for /stop all.
3

Session-scope the abort WebSocket frame

The same abort frame drains the backlog when you add scope: "session":
scope is optional; absent or unknown falls back to TURN.
4

Cancel with an abort WebSocket frame

Send an abort frame — requires the write operator scope (same as sending a message as the agent):
reason is optional (defaults to "user") and is echoed back in the terminal turn message.
5

Set a per-turn wall-clock ceiling

Set a wall-clock ceiling per turn in the gateway config. 0 (default) disables the timeout — behaviour is byte-identical to earlier releases.
A timed-out turn’s terminal response is "Turn cancelled: exceeded per-turn timeout.".
6

Render the message_abort event on the client

When the gateway cancels a turn it emits a message_abort event; treat it as a terminal outcome for that turn.

Config: gateway.per_turn_timeout

Wall-clock ceiling for a single agent turn. When exceeded, the turn is cancelled cooperatively (via its InterruptController) and, if it does not unwind within a bounded grace window, the driving task is cancelled hard. Three ways to set it:
Default 0.0 = disabled. Every turn runs to completion, exactly as in releases before #3472.

Stop Scopes

Two scopes. TURN stops the current answer. SESSION stops the current answer and drains everything the user queued behind it. Both StopScope and StopResult are exported from the public gateway surface:
StopResult.to_dict() serialises to exactly {"scope": ..., "turn_aborted": ..., "pending_cancelled": ...}. pending_cancelled is the count of queued messages a SESSION stop drained — a visible, intentional non-outcome, never a silent drop — and is always 0 for a TURN stop.
An unknown or malformed scope never escalates to SESSION — it falls back to the caller’s default (TURN). A session drain always requires an explicit opt-in.

Frame shapes

Client → Gateway

Abort frame — requires the write operator scope:
message_abort is accepted as an alias for type.
abort is classified as write in the gateway method registry — the same scope as sending a message, on the principle that aborting a turn mutates it exactly as sending one does.
Portable stop command — any client already joined to a session can cancel via the message channel:
Matching is case-insensitive and whitespace-trimmed.

Gateway → Client (reply to abort)

The reply carries the StopResult fields alongside type and session_id. type is "aborted" when either turn_aborted is true or pending_cancelled > 0; otherwise "no_active_turn". A SESSION stop with no active turn but 3 queued messages still replies "aborted" with pending_cancelled: 3 — the queued work counts as work stopped. Turn-scoped reply:
Session-scoped reply:

Terminal turn message

The turn’s own response (delivered on the normal message channel) is a typed string:

The message_abort event

EventType.MESSAGE_ABORT is defined in praisonaiagents/gateway/protocols.py and serialises to the wire string "message_abort".
Because EventType is a str Enum, EventType.MESSAGE_ABORT == "message_abort" compares True, so clients can match on the raw string without importing the enum.

How It Works

Every turn runs through a per-turn InterruptController, so a /stop, an abort frame, or a timeout on one session never touches another session’s turn.
Cancellation always requests interruption via the turn’s InterruptController first, so the agent stops at its next safe checkpoint and partial output is preserved. A hard task.cancel() fires only if the turn hasn’t unwound within 5 seconds (_ABORT_GRACE_SECONDS) — this bounds the case where a sync agent.chat running in a worker thread cannot be force-killed but must not keep mutating shared state after the queue advances.
Each turn creates its own InterruptController and passes it as cancel_token= into arun / achat / chat. Overlapping turns from different sessions never share a controller, so one session’s /stop never interrupts another’s turn — even when they share the same Agent instance.
Agent entry points that predate cancel_token= fall back to stamping agent.interrupt_controller on the shared attribute for the turn’s duration. This is best-effort and non-isolated — prefer keeping agents on the current SDK so per-turn isolation applies.
per_turn_timeout defaults to 0.0, which means no wall-clock cancellation. Every turn runs to completion, exactly as in releases before #3472 — enabling the timeout is opt-in.
Prior to PraisonAI PR #4301, the gateway’s cooperative cancel reached the agent but the agent dropped the token before OpenAIClient on the default sync path. So the gateway’s _ABORT_GRACE_SECONDS = 5.0 hard-cancel was the only real halt on plain Agent(llm="gpt-4o-mini"). Upgrade to get cooperative interruption between tool iterations, not just after the 5-second grace.
The queue worker dequeues a message before registering it as an active turn. A SESSION stop bumps a per-session _stop_epoch counter; the worker snapshots the epoch at dequeue and re-checks it just before dispatch. A mismatch means a stop landed in the gap — the message is dropped instead of executed, and it is counted toward pending_cancelled so the reported outcome is never a silent loss. See praisonai_bot/gateway/server.py (search dequeued_stop_epoch).

Common Patterns

Real scenarios this page lets you pattern-match to:

Chat /stop Command

The chat-side twin of the gateway’s abort surface.

Handshake Protocol

Capability negotiation and the hello_ok feature set.

Gateway CLI

Process-level praisonai gateway stop versus turn-level cancellation.

Interactive TUI Ctrl-C

The terminal-side twin of the same cooperative primitive.