Sessions and turns
Create durable sessions, start turns, stream activity, and recover in-progress conversations.
The session API is the lower-level interface for durable agent conversations. Use the compound agent invocation endpoint for most product chat surfaces. Use session and turn endpoints when your client must create sessions itself, page the transcript, steer work in progress, or manage compaction directly.
Resource model
A session owns an ordered transcript. A turn is one execution attempt against that transcript. Messages and compaction summaries are durable; typing previews, model deltas, and live tool activity are not.
The core endpoints are:
POST /v1/sessions
GET /v1/sessions/{session_id}
GET /v1/sessions/{session_id}/messages
POST /v1/sessions/{session_id}/turns
GET /v1/sessions/{session_id}/transcript/streamEvery session is scoped to one organization and one agent. A stable name lets
your application resolve the same conversation again without storing a Mobius
session ID as its only lookup key.
Start a turn
Send content to POST .../turns:
{
"input": {
"content": [
{
"type": "text",
"text": "Summarize the open support tickets."
}
],
"idempotency_key": "<message-id>"
}
}By default, the endpoint returns 202 Accepted while the turn continues:
{
"session_id": "<session-id>",
"turn_id": "<turn-id>",
"status": "queued",
"after_sequence": 18
}To follow the turn, open the transcript stream from the returned
resume_cursor (see the event catalog). If the
start request sends Accept: text/event-stream, Mobius returns 200 OK and
streams the same turn inline.
Use a provider message ID or another durable application event ID as
idempotency_key. Retrying the same key and content resolves the original
turn instead of starting duplicate work.
Stream and replay
On the inline stream, committed transcript rows arrive as user.message,
agent.message, and compaction.created frames. Their server-sent event id
equals the durable message sequence.
Live frames such as turn.started, message.delta,
session.message.preview, tool.call, and tool.result help render progress,
but they are not transcript history. Streamed text arrives as message.delta,
merged per block for up to 50 ms. Do not persist live_sequence as a
reconnect cursor.
If a client attaches in the middle of a turn, request the turn's live snapshot:
GET /v1/sessions/{session_id}/turns/{turn_id}/liveThe snapshot rebuilds the current preview. The final agent.message still
arrives as a durable transcript row even when a client misses live frames.
Tool history also lives in the transcript. A tool_use block appears on the
agent message and the matching tool_result block appears on the following
user message. Pair them by tool_use_id; treat live tool frames as optional
progress only.
Read what a turn cost
A completed turn carries its token usage on the turn resource, so you can ask what a turn cost long after it ran:
GET /v1/sessions/{session_id}/turns/{turn_id}{
"id": "<turn-id>",
"status": "completed",
"usage": {
"input_tokens": 1840,
"output_tokens": 412,
"cache_read_input_tokens": 20480,
"calls": 3
}
}The same usage object appears on GET .../turns, on the transcript's
turn.upsert frames, and on the live turn.completed event, so a client that
missed the live frame never has to replay the stream to recover it.
Counters are summed over every model call the turn made. Cache reads and writes
are reported separately and are never folded into input_tokens, because
providers price them differently. reasoning_tokens covers hidden reasoning
that is billed as output but never appears in the reply, which is often where
an unexpectedly expensive turn went. A counter that stayed at zero, or that the
provider did not report, is omitted rather than sent as 0.
A turn that failed carries no usage, even if it spent tokens before failing. Read your billing usage events when you need charged amounts rather than a usage report.
Nudge work in progress
A nudge adds direction at the next safe iteration boundary without cancelling the active turn:
POST /v1/sessions/{session_id}/nudges{
"content": "Do not deploy. Prepare the diff and wait.",
"idempotency_key": "<nudge-id>",
"wake": false
}A delivery of current_turn means an existing turn will receive the nudge
as runtime input at its next safe boundary, including one final boundary as
the turn finishes, so direction that arrives during the closing response is
still answered in the same turn. A delivery of new_turn means no turn
could absorb it (the session was idle, or the target turn settled first), so
the content became a regular user message on a fresh turn, exactly as if it
had been sent to the session, and the nudge is delivered immediately.
Set wake: true only when a turn is waiting on an agent tool and should resume
immediately. One oldest nudge is delivered per safe boundary. A session accepts
up to 32 pending nudges.
Asking a question with mobius.interaction.create does not hold the turn. The
outcome (answered, dismissed, or expired) arrives in the conversation later as
a user-role message whose metadata.event_type is interaction.resolved. Its
event_projection summarizes the outcome (title is Request answered,
Request dismissed, or Request expired); render that rather than the raw
message text, which is an event record meant for the model. A turn that waits
on a question with mobius_event_wait resumes on every nudge, with or without
wake, because nobody may answer for days. The question stays open, and its
outcome still arrives later. woke_turn in the response says whether the turn
resumed.
Use these endpoints to reconcile or cancel pending direction:
GET /v1/sessions/{session_id}/nudges?status=pending
POST /v1/sessions/{session_id}/nudges/{nudge_id}/cancelReusing an idempotency key with different content returns
409 idempotency_conflict. A full queue returns 409 nudge_queue_full.
Compaction
A session compaction policy has three controls. Choose either threshold or
threshold_tokens, not both:
| Field | Values | Meaning |
|---|---|---|
strategy | auto, manual, disabled | Decides whether compaction starts automatically, only on request, or never. |
threshold | xs, sm, md, lg, xl | Sets a model-relative trigger at 10%, 20%, 40%, 60%, or 80% of the session model's context window. |
threshold_tokens | Integer from 1 to 10,000,000 | Sets an exact estimated-token trigger instead of a preset. |
The same platform model writes every summary. A summary_model field is
accepted and ignored.
Update the session to change its policy. Existing sessions do not re-resolve defaults from the agent or messaging binding.
{
"compaction_policy": {
"strategy": "auto",
"threshold": "md"
}
}For an exact threshold, replace threshold with threshold_tokens:
{
"compaction_policy": {
"strategy": "auto",
"threshold_tokens": 375000
}
}Compaction always runs inside a turn. Under auto, a turn compacts before it
reads the conversation when the history has crossed the threshold; the turn's
own input is never summarized. To compact on request, under auto or
manual:
POST /v1/sessions/{session_id}/compactThis queues a compaction turn (kind: "compaction"). It waits for any turn in
progress, then summarizes, and writes no messages besides the summary. Asking
again while one is queued returns the same turn. disabled rejects the
request; change the strategy first. A pass that fails leaves the transcript
unchanged, and the next turn compacts the same messages again.
Show compaction progress
A turn that is summarizing reports compacting: true on the turn resource.
Read it from the session's turns or from the turn.upsert frames on the
transcript stream, so a client that connects mid-pass sees it too. A queued or
running turn with kind: "compaction" is a requested compaction. The summary
lands in the transcript as a compaction entry when the pass is done.
compaction.started, compaction.created, and compaction.failed also arrive
on a turn's inline stream, each carrying the turn's
agent_turn_id. compaction.started and compaction.failed are live-only
and best effort; compaction.created replays from the transcript with the
summary's own message sequence.
The session read's compaction.threshold_tokens is the estimated-token size at
which this session compacts automatically, resolved from its policy and the
session model. Pair it with the transcript you already render and you can show
a context-capacity gauge, which tells a user that summarization is coming
before it interrupts them. It is absent for manual and disabled sessions,
which never compact on their own. compaction.in_progress is deprecated and
always false.
Cancel a turn or session
Cancel a specific turn when you know its ID:
POST /v1/sessions/{session_id}/turns/{turn_id}/cancelCancellation is idempotent, cooperative, and terminal. Repeating it returns the
current terminal turn without another lifecycle transition. Mobius retires
pending jobs, waits, and nudges owned by the cancelled turn, but does not roll
back tool or external effects that already occurred. Questions the turn asked
stay open, and their outcomes still reach the conversation; dismiss one with
POST /v1/interactions/{interaction_id}/cancel. Committed transcript rows
remain; live-only preview text is discarded.
A cancelled turn cannot resume. Reusing its original invocation idempotency key returns the same cancelled turn. Use a new key to attempt the task again, and make external actions idempotent because the new attempt may repeat an effect. Stream cursors resume observation, not execution.
Use the session cancel endpoint when you need to cancel every active turn in the session rather than naming one turn:
POST /v1/sessions/{session_id}/cancelThe response is the updated session. Cancellation does not delete transcript rows or reverse external effects that already happened.
Next
- Use the higher-level agent invocation flow for most embedded assistants.
- Render live events with the event catalog.
- Understand transcript history and compaction in agent sessions.
- See every schema in the interactive API reference.