DocsDevelop

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/stream

Every 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}/live

The 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}/cancel

Reusing 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:

FieldValuesMeaning
strategyauto, manual, disabledDecides whether compaction starts automatically, only on request, or never.
thresholdxs, sm, md, lg, xlSets a model-relative trigger at 10%, 20%, 40%, 60%, or 80% of the session model's context window.
threshold_tokensInteger from 1 to 10,000,000Sets 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}/compact

This 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}/cancel

Cancellation 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}/cancel

The response is the updated session. Cancellation does not delete transcript rows or reverse external effects that already happened.

Next