Skip to content

Turns

A turn is one message and the agent's work on it; a session runs one at a time and queues up to 20

A turn’s id is tn_ followed by 12 characters.

Send

turns.send (POST /sessions/{id}/turns) takes input (1 to 100,000 characters) and, optionally, ifBusy and idempotencyKey.

{"jsonrpc":"2.0","id":3,"method":"turns.send","params":{"sessionId":"cs_0123456789abcdef","input":"Add a test for the empty case","ifBusy":"steer","idempotencyKey":"7f3c1c0e-1"}}
{"jsonrpc":"2.0","id":3,"result":{"turn":{"id":"tn_0123456789ab","status":"running","...":"..."},"delivery":"steered","duplicate":false,"seq":412}}
Session stateifBusyWhat happensdeliveryturn in the result
Idle, nothing queuedAnyThe turn startsstartedThe new turn
Busyqueue (default)The message waits as a queued turn. Queued turns start in order. Open asks stay openqueuedThe new turn
BusysteerThe message joins the running turn’s steering list. Open asks close as declined; the message is the answer. With no running turn the message is queuedsteeredThe running turn
BusyinterruptQueued turns are cancelled and the running turn goes to cancelling. The message becomes a new turn that starts once the harness has stoppedstartedThe new turn

A send to a suspended session is recorded as a queued turn and starts a runner.

Each send records a message.received event; a steer also records turn.steered on the running turn.

ErrorCause
409 queue_full (retryable: true, retryAfterMs: 5000)The session already holds 20 queued turns

Status

statusMeaningEvent
queuedWaiting for the running turn to endturn.queued with position
runningThe harness is workingturn.started with model, effort, mode
cancellingInterrupted; the harness has not stopped yetturn.status_changed
completedEnded normally. text is the final messageturn.completed
failedEnded with an error (code, message)turn.failed
cancelledInterrupted or withdrawn. reason says whichturn.cancelled

A turn with trigger: "subagent" was started by the harness, not by a message; see Subagents.

Wait for the result

TransportHow
WebSocketAttach to the session and read events until turn.completed, turn.failed or turn.cancelled for the turn id
HTTPLong-poll turns.get (GET /sessions/{id}/turns/{turnId})
GET /sessions/cs_0123456789abcdef/turns/tn_0123456789ab?waitMs=60000
ParamEffect
waitMs (up to 60,000)Returns when the turn has ended or the wait is over. Check status and call again if it has not ended
statusReturns when the turn has left this status instead
minSeqWaits for the journal: Session journal

The ended turn carries text, error, usage (tokens and costUsd), startedAt and endedAt. A turn that stopped for a person has an open ask on the session.

Cancel a queued turn

turns.cancel (POST /sessions/{id}/turns/{turnId}/cancel) withdraws a queued turn: it ends as cancelled with reason: "withdrawn".

ErrorCause
409 turn_not_cancellableThe turn is not queued. The error carries the turn

Interrupt

sessions.interrupt (POST /sessions/{id}/interrupt) cancels every queued turn and stops the running one. It returns at once with the session.

CaseResult
Running turnGoes to cancelling; when the harness stops, ends as cancelled with reason: "interrupted"
Harness not stopped 10 seconds after the first interruptThe harness process is ended and the turn is cancelled with forced: true
Open asksClose as interrupted
turnId givenDoes nothing unless that turn is the running one or is queued
Suspended session whose journal shows a started turn (its runner died)That turn ends as cancelled with forced: true