Lifecycle
Read a session's conversation and turn, and know when the message you sent is answered.
A session is a conversation with an agent. The session object reports it in two parts: conversation says whether the session can take more messages, and turn says what is happening to the latest message. A turn is one message and the agent's answer to it. The message you sent is answered when its turn reaches a final status.
Conversation
| Field | Meaning |
|---|---|
conversation.state | open while the session can take messages; closed is permanent |
conversation.interactive | Whether the session stays open after its first turn; set it when starting a session, on an agent, or when starting an agent session |
conversation.warm | Whether an agent is ready to answer the next message right away; when false, the next message first prepares an environment, which takes longer |
conversation.prompting | Whether a message you send now would be accepted: enabled, and when it is false, blocked_reason and detail say why |
A session started with interactive: false closes when its turn ends; after that, start a new session. warm is a hint for what to show while a message is pending, never something to branch on. prompting.detail is a sentence you can show as is; blocked_reason is one of:
| Reason | Meaning |
|---|---|
closed | The conversation is closed |
mention_surface | The session was started by a mention; reply on the surface surface_name names (GitHub, Slack, or Linear) |
non_interactive | The session was started with interactive: false |
harness_single_turn | The harness takes one message per session |
budget_exhausted | The session spent its budget |
POST /v1/sessions/{session_id}/messages fails with 409 while prompting.enabled is false.
Turn
| Status | Meaning |
|---|---|
pending | The message was received; the agent hasn't started on it, because another turn is running or the environment is being prepared |
running | The agent is working on the message |
completed | The agent finished |
failed | The turn ended with an error; reason and detail explain |
stopped | Someone stopped the turn; stopped.at and stopped.by say when and who |
cancelled | The turn was refused before the agent started; reason and detail explain |
completed, failed, stopped, and cancelled are final. A final turn ends that turn, not the conversation: an interactive session still takes follow-ups while prompting.enabled is true. The other turn fields are id, index (its position in the session, from 0), created_at (when the message was received), started_at, ended_at, cost, and tokens.
reason is set only on failed and cancelled turns:
| Reason | Meaning |
|---|---|
error | The agent ended with an error |
tool_call_failed | The agent process failed or stalled before finishing |
budget_hit | The session budget or a spend limit was reached |
payment_required | The account has no credit or a subscription problem |
blocked | The account or user is blocked |
contact_email_required | The account needs a contact email |
lifecycle_hook_failed | An environment hook exited with a nonzero status |
missing_repo_access | The GitHub installation can't access a repository the session needs |
missing_token_permissions | The GitHub installation lacks a permission the session needs |
missing_environment_variables | An environment variable has no value |
missing_github_integration | The account has no GitHub installation |
agent_unavailable | The saved agent was deleted or disabled |
upstream_unavailable | The Claude or Codex subscription the session uses is unavailable |
unsupported_configuration | The harness, model, and settings can't be combined |
execution_disabled | Sessions from this source are disabled |
interrupted | The turn lost its agent before answering; the message is queued again on a new turn |
detail is a sentence you can show as is.
Wait for the answer
The message you sent is answered when the turn with its turn_id reaches a final status. POST /v1/sessions returns the session with the turn for its prompt in turn; POST /v1/sessions/{session_id}/messages returns the message and its turn. Poll GET /v1/sessions/{session_id}/turns/{turn_id} or watch the stream's session frames until status is final. Waiting for the conversation to close, or for it to go quiet, is wrong: an open conversation stays open between turns.
The SDK handle's wait() does this for you. It returns when the turn it waits on ends: the session's opening turn, then each turn a send() creates.
session.turn is the turn in progress, or the latest one when none is running, and null until the session has a message. GET /v1/sessions/{session_id}/turns lists every turn and message, oldest first.
Follow the stream
Pass this callback to the stream in the Python or TypeScript SDK. snapshot sends the current session, session replaces it whenever the turn, the conversation, or the cost changes, records_append adds saved records in feed_seq order, and done means the conversation closed. delta frames carry live partial output that isn't saved. A turn_ended record carries the same status, reason, and detail as the turn. Frames can repeat, so ignore duplicates and types you don't recognize. See Session events for every event.
A session keeps the Claude or Codex subscription it started with; connecting a different one affects only new sessions.