| title | Sessions, events and history |
|---|
This contract covers what happens inside a Session: sending input, the live event stream, and reading the durable history of Turns, Items and usage. The Session resource itself (creation configuration, retry identity, update, list and deletion) is in API behavior. Message and function-result content is in message content. The Agents API guide shows the calls with the SDK and HTTP.
The event stream is live only; Turns and Items are durable. A client that needs every result:
- Opens
GET /v1/agents/sessions/{session_id}/eventsbefore sending input. - After a disconnect, subscribes again and buffers new events.
- Reads the Session, its Turns and its Items, deduplicates Items by ID and keeps finalized Items when it applies the buffered updates.
A stream is an observer. Closing it never cancels admitted work, and a stream never replays events it did not send. After a lost input response, retry with the same Idempotency-Key, then read the Session, Turns and Items.
A Session's status and last_active_at derive from its latest root Turn and from input still waiting for its Environment:
status |
When |
|---|---|
idle |
No Turn yet, or the latest Turn completed or was cancelled. Input reserved for a hosted Environment that is still provisioning also reads idle |
in_progress |
The latest Turn is queued, running or waiting |
requires_action |
The latest Turn waits for a function result, including after a cancellation request until the Turn ends, or input waits for a self_hosted machine to connect. required_actions lists function_call or environment_connection entries |
failed |
The latest Turn failed (error is "The execution could not complete."), input reserved for the Environment failed, initial input expired before admission, or the Environment failed to initialize (see Environment initialization failure) |
stateDiagram-v2
[*] --> idle
idle --> in_progress: Turn queued
in_progress --> idle: completed or cancelled
in_progress --> requires_action: awaits function result
idle --> requires_action: awaits self_hosted machine
requires_action --> in_progress: result or machine
requires_action --> idle: Turn ended
in_progress --> failed: Turn failed
idle --> failed: input expired or Environment failed
failed --> in_progress: new input
A Session stays usable after a Turn fails: new input starts a new Turn. Later reserved input that expires leaves the Session idle. An Environment initialization failure or expiry prevents new work.
POST /v1/agents/sessions/{session_id}/events takes an ordered array of 1 to 64 events: agent.session.input.message, agent.session.input.cancel and agent.session.input.tool_result. The whole batch is admitted atomically, and the response is 202 with no body once the batch is stored, before any Harness reads it. Admission never confirms native application.
- Limits. The request body is at most 1 MiB, and the stored input of one request at most 512 KiB.
- Null batch. An explicit
nullforeventsis invalid. - Empty batch.
{"events": []}checks that the Session exists and returns 202. It creates no Turn, Item or retry identity. - Retries. An
Idempotency-Keyof 1–256 bytes identifies the whole ordered batch; an empty or longer key returns 400. The same key and batch return 202 again without admitting anything twice; the same key with another batch returns 409idempotency_conflict. A request without a key is always new. - Messages. On an idle Session a message batch starts a queued Turn. While a Turn runs, messages join it (steering); they never start a parallel Turn. Each message stays its own user Item, even when the Harness receives several as one prompt.
- Cancellation. A queued Turn is cancelled without a live Runtime. A running Turn is cancelled when the Runtime confirms it; completion can win that race. The Turn has stopped when it reads
cancelled, not when the request returns. A Turn that waits for a function result keeps the Session atrequires_actionwith itsrequired_actionsuntil then, and the request emits no Session event. A cancellation on an idle Session with no pending input is accepted and has no effect; while an input reservation is pending, it returns 409. - Function results.
turn_id,call_idandsuccessare required;outputanderrorare optional and nullable (content rules). An identical repeated result returns 202 without another application or event. The result Item appears when the Harness applies the result; a result that cancellation prevents from being applied stays stored but produces no Item. - Queueing. A queued Turn starts when a Runtime that supports the Session's Harness and configuration is connected and one of Core's
OAC_EXECUTION_CONCURRENCYwork slots is free. A Session stays bound to the Runtime that first ran it. - Execution availability. While Core shuts down, or after its Worker loses execution ownership, requests return 503
execution_unavailable.
On openai_hosted and self_hosted Sessions, messages sent while a Turn runs join it at once. Messages sent to an idle Session reserve the batch for the Environment: the request waits until a Turn starts, for at most five minutes from the reservation, and ends with 202 when the Turn starts or with one of the input errors. While the reservation waits for a self_hosted machine, the Session reads requires_action with an environment_connection action. A batch that carries messages on these placements may contain only messages. Cancellation-only and result-only batches are admitted at once and create no Turn. Disconnecting the waiting request does not cancel the reservation or restart its deadline.
Checks run in this order: request validation, whose paths start with events[i], Session lookup, the Harness input check, retry lookup, the Credential check for a message that starts a Turn, the Environment file-write gate for batches with a message, the pending-input gate, then each event in batch order. A rejected batch writes nothing and leaves any pending action unchanged. Every 409 has type conflict_error (error envelope).
| Case | Status and code | Message |
|---|---|---|
Earlier input to the Session still waits for admission, such as the reserved initial input of a provisioning hosted Session or of an offline self_hosted Session |
409 conflict_error |
"Earlier input to this Session is still pending." |
| Input the Turn cannot accept in its state, such as a result after cancellation or after its Turn ended without a stored result | 409 conflict_error |
"The Turn cannot accept this input in its current state." |
| A result that differs from the call's stored result, before or after its Turn ends | 409 conflict_error |
"The tool call already has a different result." |
The same Idempotency-Key with a different batch |
409 idempotency_conflict |
"This idempotency key was used with different input." |
A result whose call_id names no function call of this Session |
400 invalid_request_error, param null |
"Unknown pending tool call: {call_id}" |
A result for a call of this Session whose turn_id names another Turn, an unknown Turn ID or a value that is not a Turn ID |
400 invalid_request_error, param null |
"The tool call belongs to a different Turn." |
| A missing, malformed or foreign Session | 404 not_found_error |
"Resource not found." |
| A reserved batch whose five-minute deadline passed before a Turn started | 409 environment_input_expired |
"The environment input deadline elapsed before admission." |
| A reserved batch cancelled because an administrator archived the Session or reset its deployment | 409 environment_input_cancelled |
"The environment input was cancelled before admission." |
New input after an openai_hosted Environment failed to provision |
409 conflict_error |
"the hosted environment failed to provision" |
New input after a self_hosted Environment failed, input already waiting when the Environment failed, or an expired Environment |
409 environment_unavailable |
"The environment is no longer available for new input." |
| A message that would start a Turn on a Session whose bound MCP Credential, or its Vault, was deleted | 400 invalid_request_error, param null |
"MCP credential_id {id} was not found in an attached vault" |
| A message the Session's Harness cannot take, such as whitespace-only text on MiniMax Code | 400 unsupported_or_invalid_configuration |
See whitespace-only text |
An empty turn_id or blank call_id is the generic 400 invalid_request. Apart from the unknown tool call message, which names its call_id as the official SDK expects, and the Credential message, which names the Credential as Session creation does, error messages never repeat caller input or internal identifiers.
POST /v1/agents/sessions accepts input as a string (one text message) or an array of user messages, with the same validation and admission as the events endpoint.
- Initial input is required on
none(400invalid_request_error, "conversation-only sessions currently require initial input") and forstream: trueon every placement exceptself_hosted(400, "streaming session creation requires initial input"). These checks run before the creation retry lookup. - The Session and its initial work commit in one transaction. On
nonethat includes the first Turn and the input Items. Onopenai_hostedthe input is reserved while the Environment provisions. Onself_hostedit is reserved with anenvironment_connectionaction, and creation returns while the machine is offline. - Reserved initial input has the same five-minute deadline as later input. When it passes before a Turn starts, the Session reads
failedwithout a Turn. - A creation retry returns the original Session and never admits its input again, including after later Turns (creation retries).
stream: true on Session creation returns 201 with an event stream instead of JSON.
- The first event is
agent.session.createdwith the same committed Session as the JSON 201 body, read after the commit. With initial input onnoneit already readsin_progress; onself_hostedit already shows theenvironment_connectionaction. - The stream then sends every committed event of the creation exactly once, starting at the creation's own position, so fast execution cannot skip its first events.
- It ends right after the first
agent.session.idlerecorded when a Turn ends or an input reservation stops waiting (expired, cancelled or failed), or after anyagent.session.failed, and never sends later events.requires_action, function results, resumed work and aself_hostedconnection keep it open. A reservation keeps it open until a Turn settles or the reservation ends. - A creation that admitted nothing ends right after
agent.session.created. When a settlement records no event, the stream ends after the events committed up to the settled state; another client's work committed before that point can still be sent.
Input reserved while the ending Turn captures Artifacts can start a later Turn that the creation stream does not follow.
A retry with the same Idempotency-Key and stream: true returns 201 with only the connection comment and ends at once: it admits nothing and follows no work. To recover a lost Session ID, repeat the request with the same key and stream: false, then read the Session, Turns and Items. Disconnecting stops only the observer. Observe later Turns with the GET stream.
GET /v1/agents/sessions/{session_id}/events starts at the latest committed event and sends only events committed after that. Last-Event-ID is ignored. Events publish after their transaction commits.
- Lifetime. The stream stays open across Turns and after a Turn fails. It ends when the Session is deleted or after the terminal
agent.session.failedof an Environment initialization failure; a stream opened after that failure stays open. A keepalive comment is sent every 15 seconds. - Buffer. Core keeps at most 256 events and 64 MiB of events per Session, plus one larger event when needed. A reader that falls behind the buffer receives an
errorevent with typeserver_errorand codestream_interrupted, and the stream closes. A socket write that blocks for five seconds also closes it. Execution never waits for a reader. - Key recheck. An open stream checks the original Project key at most once per second while idle and before sending output. Revoking the key or archiving the Project closes the stream, and so does an authentication failure. A recheck uses the normal five-second authentication timeout and sends no Session data while it waits. Bytes already sent cannot be recalled.
- Root work only. Child Turns and child Items publish no Session events, except a Subagent's
function_callItem, published once asitem.added;agent.session.subagent.*events and root coordination Items do. Read child work through the Subagent resources.
- Session events carry
event_id,typeand thesessionsnapshot at that transition. Turn events carrysession_idandturn_id; Environment events carrysession_idand a nullturn_id. There is no Turnwaitingevent. - A new Turn publishes
agent.session.turn.created, the useritem.added,agent.session.in_progress, thenagent.session.turn.in_progress, all from one transaction. When a batch holds several messages, the later messages follow the Session activity. - Terminal Turn events (
completed,failed,cancelled) carry top-levelusagecopied from the Turn snapshot at that moment, null when unknown. Other events omit it. item.addedanditem.donealways carryoutput_index, null for input Items. Function results emititem.addedonly;item.doneis for agent output.- An assistant message follows one sequence:
item.addedin progress with emptycontent,content_part.addedwith empty text,output_text.deltaevents,output_text.done,content_part.doneanditem.done. A message first observed complete, such as structured output, sends its whole text in oneoutput_text.delta, byte for byte. The completed text replaces the accumulated deltas. - Codex command output streams as
agent.output.command_execution_output.deltawith the command's Item ID and output index. Native output quotas and text conversion apply, so the deltas are not a byte-exact capture; the completed Item is authoritative. - A reasoning Item follows one sequence:
item.addedin progress with an emptysummary; for eachsummary_textpart in order,reasoning_summary_part.addedwith empty text,reasoning_summary_text.deltaevents, thenreasoning_summary_text.doneandreasoning_summary_part.doneonce the next part starts or the Item ends; thenitem.done.reasoning_summary_part.donehasstatusincompletefor a part a cancelled or failed Turn cut off, otherwise null. - A cancelled or failed Turn marks its unfinished Items
incompleteand keeps their partial content. - A function call stays in
required_actionsuntil the Harness applies its result or the Turn ends. A cancellation request does not remove it from a waiting Turn. A repeated notification emits no new state.
Turn and Item lists take after, limit and order (list rules). Cursors are IDs within the same Session.
Turns. Session Turn routes hold root Turns only, ordered by creation time then ID; a child Turn ID returns 404 there. A failed Turn's error has an official code and a fixed message for that code, never raw engine diagnostics; Differences from OpenAI lists the codes Core reports. Stream snapshots report the same error as reads. Administrators read the failure category through Session diagnostics.
Items. Items are ordered by the time they were first observed, then by their position in the Session, then by ID. Updates and retries never move an Item or change its output_index, a zero-based position among the Turn's output Items; input Items have none. Reads use the stored history index and never rebuild it from native journals. The Items list includes Items that are still in progress or incomplete. GET /agents/sessions/{session_id}/turns/{turn_id}/items lists only the Items of that root Turn with the same paging, ordering and cursor rules; a Turn that does not exist in the Session returns 404.
Item type |
Content |
|---|---|
message |
User or assistant content parts. phase is the Harness's phase when it reports one (commentary, final_answer), otherwise null |
command_execution |
Command, reported output, exit code, duration and working directory |
mcp_call |
Server and tool identity, arguments, structured result or error |
function_call, function_call_output |
A linked call and its result. The result always carries output and error, null when the submission omitted them; stored results keep the submitted field presence. Codex file changes appear as an apply_patch function call with the changes as arguments and no invented result |
web_search_call |
The supported action fields (search, open_page, find_in_page, other); Claude Code reports search for WebSearch and open_page for WebFetch |
reasoning |
One native reasoning item, each native part one summary_text part, present only when the Agent sets reasoning.summary; with it null (the default) no reasoning Items or reasoning events appear for any Turn, root or child. OpenAI compatibility describes the text. Status can be null on a child Turn's Item |
agent_message and the Subagent coordination calls |
See Subagents. agent_message has no status |
A failed tool does not fail its Turn. Tool output is readable by the Session's Project and can contain the tool's own diagnostic text.
PostgreSQL cannot store U+0000, so Harness output that contains it, such as a command's output, is stored, streamed and listed with U+FFFD in its place, and the Turn continues.
Turn and Session usage uses the pinned TokenUsage fields: input, cached input, output, reasoning output and total tokens. Null means unknown, never zero.
- Turn usage is the latest complete snapshot the Harness reported for that Turn. A new snapshot replaces the previous one; repeats never add. Stored snapshots survive cancellation and Worker restarts.
- Session usage is the sum of root Turn usage when every root Turn has ended (completed, failed or cancelled) with known usage. It is null while any root Turn is queued, running or waiting, and stays null once a root Turn ends without usage. Subagent Turns do not count.
- By Harness. Codex reports measured snapshots while a Turn runs and at its end; a Turn interrupted before any usage report stays null. Claude Code and MiniMax Code report no complete public breakdown, so their usage is null.
Usage is best-effort accounting of reported measurements. It is not an invoice, and Core never estimates missing usage.
When an Environment fails to initialize, whether an openai_hosted sandbox or a self_hosted machine, one transaction records the failure and three events, in this order:
| Event | Payload |
|---|---|
agent.session.environment.failed |
error: {type: "environment_error", code: "environment_connection_failed", message: "The environment failed to connect."} |
error |
error: {type: "environment_error", code: "sandbox_error", message: <reason>, param: null} |
agent.session.failed |
The Session: status: "failed", the reason as error, required_actions: [] and the failure time as last_active_at |
Session reads and lists return the same Session, and input that was waiting for the Environment settles as failed in the same snapshot. The GET stream and the creation stream end after agent.session.failed. New input returns 409 (input errors); the Session can be deleted.
The Environment initialization contract defines the fixed failure reasons.