Lifecycle
Read a session's status, and know when the message you sent is answered.
A session is a conversation with an agent. The session object reports where it is in status, why its last message ended badly in error, and whether it can take more messages in conversation. Every message you send is answered in order. The message you sent is answered once the session is idle or closed.
Status
| Status | Meaning |
|---|---|
queued | A message was received and waits to start, or waits until a session ends when your account has its limit of sessions alive |
preparing | The environment for the next message is being set up |
running | The agent is working on a message |
idle | Every message has been answered and the session can take another |
closed | The session can take no more messages. closed is permanent |
A session started with interactive: false closes when its first message is answered. After that, start a new session.
Error
error is null while the session is busy and after a message was answered. When the last message failed, was stopped, or was cancelled, error.reason says why and error.detail is a sentence you can show as is. The next message clears it.
| 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 billing 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 model provider was rate limited or down after retries |
unsupported_configuration | The harness, model, and settings can't be combined |
execution_disabled | Sessions from this source are disabled |
timeout | The message's work ran for the environment's compute.timeout and was stopped |
interrupted | The agent was lost before answering. The message starts again on a new environment |
stopped | Someone stopped the session |
Conversation
| Field | Meaning |
|---|---|
conversation.interactive | Whether the session stays open after its first message is answered. 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. When enabled is false, blocked_reason and detail say why |
warm is a hint for what to show while a session is queued or preparing, never something to branch on. prompting.detail is a sentence you can show as is. blocked_reason takes these values.
| Reason | Meaning |
|---|---|
closed | The session 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/{id}/messages fails with 409 while prompting.enabled is false.
Wait for the answer
Poll GET /v1/sessions/{id} or watch the stream's session frames until status is idle or closed. Waiting for the session to go quiet is wrong because a busy session can be quiet while its environment is prepared.
How a message ended is in the session's log. Its message_ended record carries status (completed, failed, stopped, or cancelled), error (the same reason and detail object the session carries, null when answered), and what answering it cost. Messages answered together report the cost once, on the last of them. GET /v1/sessions/{id}/records?message_id=... returns one message's records.
The SDK handle's wait() does both. It returns the message_ended payload of the message it waits on. That is the last one sent through send(), else the session's latest message.
Follow the stream
Pass this callback to the stream in the Python or TypeScript SDK. The first frame is session, resent whenever the status, the error, or the cost changes. records adds saved records in feed_seq order. A session frame with status closed is the last one. The stream then closes normally. delta frames carry live partial output that isn't saved. Frames can repeat, so ignore duplicates and types you don't recognize. See Session events for every event.