Agent SDK is experimental and will have breaking changes!
For a guided walkthrough, see Use an agent — Subscribe to events.
Turn Events
create_turn() streams Server-Sent Events (SSE). Each event is a JSON object with a type field.
- The stream always opens with
turn.created and closes with turn.done.
- All events carry a
thread_id. Thread-scoped events use "main" (root agent) or a unique sub-agent ID; turn-level events use None.
thread.created and thread.done track sub-agent threads only - they are not emitted for the main thread, which is created automatically on the first Turn and lives for the session’s lifetime.
- All events carry an
id. It identifies the logical event and is present in both stream() and list_events().
- Events delivered via
stream() also carry a sequence_number, monotonically increasing within a Turn.
- All events carry a
created_at ISO-8601 timestamp marking when the event was emitted.
turn.created and turn.done are stream-only; they appear on the stream (stream()) but are not returned by list_events().
turn.*
model.*
tool.*
thread.*
mcp.*
sandbox.*
type | Description |
|---|
turn.created | First event on the stream. Carries turn_id. |
turn.done | Last event on every stream. Carries the final state. |
Model output.type | Description |
|---|
model.message.delta | LLM output - assistant text and/or tool call chunks. Streamed as ModelMessageEventDelta. Most frequent event. |
model.message | Assembled model message delta. Returned by list_events() as ModelMessageEvent. |
Sub-agent thread lifecycle.type | Description |
|---|
thread.created | A sub-agent thread started. Not emitted for the main thread. |
thread.done | A sub-agent thread completed (done) or failed (error). Not emitted for the main thread; does not close the stream. |
MCP server lifecycle.type | Description |
|---|
mcp.initialize | One or more MCP server sessions were initialized. |
mcp.auth_required | MCP server(s) need OAuth before proceeding. Resume after the user authorizes. |
Sandbox lifecycle.type | Description |
|---|
sandbox.created | A sandbox was provisioned for the Turn. |
TurnCreatedEvent
First event on every Turn stream, carrying the Turn’s turn_id and metadata. Stream-only.
| Field | Type | Required | Description |
|---|
type | "turn.created" | Yes | |
id | string | Yes | Unique event identifier. |
thread_id | null | Yes | Always null — turn-level event, not tied to a thread. |
turn_id | string | Yes | Unique ID for this Turn. |
previous_turn_id | string | None | No | Turn ID this Turn chains from, or None for the first Turn in a session. |
created_by | string | Yes | Subject (user / service account) that created this Turn. |
created_at | string | Yes | ISO-8601 timestamp when the Turn was created. |
ModelMessageEventDelta
The most frequent SSE event. Carries an incremental LLM delta for one message - assistant text and/or tool call chunks. Every delta shares the id of the base ModelMessageEvent it belongs to; merge each delta into that base event in your id-keyed event index. A delta with a non-null finish_reason signals the message is complete.
Use merge_event_delta from truefoundry_gateway_sdk to fold a delta into its base event in place. Alternatively, ModelMessageBuilder accumulates deltas on their own (without a base event) into a complete ModelMessage: add() returns the accumulated message so far, and build_and_reset() returns the complete message once finish_reason is set.
| Field | Type | Required | Description |
|---|
type | "model.message.delta" | Yes | |
id | string | Yes | Shared by all deltas of one message; equal to the assembled ModelMessageEvent id. Use sequence_number to distinguish individual deltas. |
thread_id | string | Yes | Thread this message belongs to. "main" for the root agent; a unique ID for sub-agents. |
content | string | No | Incremental text content for this delta. |
reasoning_content | string | No | Incremental reasoning/thinking text for this delta; concatenate across deltas. |
tool_calls | ToolCallDelta[] | No | Tool call chunks to accumulate by index; fully assembled when finish_reason is set. |
finish_reason | string | No | Set on the final delta. Signals the accumulated message is complete. |
Each ToolCallDelta is shaped like an OpenAI streaming tool-call chunk. id, type, and tool_info appear only on the first chunk for a given index; function.arguments is a partial JSON string concatenated across chunks.
| Field | Type | Required | Description |
|---|
index | int | Yes | Position in the tool_calls array; used to merge chunks. |
id | string | No | Tool call ID. First chunk only. |
type | "function" | No | First chunk only. |
function | ToolCallFunctionDelta | Yes | The function name and partial arguments for this chunk. |
tool_info | ToolCallInfo | No | Tool metadata. First chunk only. |
ToolCallFunctionDelta:
| Field | Type | Required | Description |
|---|
name | string | No | Tool name. Present only in the first chunk for this index. |
arguments | string | No | Partial JSON string; concatenate across chunks. |
ToolCallInfo carries metadata about the tool, discriminated on type. Read the tool name from name.
For MCP-backed tools (type: "mcp"):
| Field | Type | Required | Description |
|---|
type | "mcp" | Yes | |
mcp_server_id | string | Yes | ID of the MCP server backing this tool. |
mcp_server_name | string | Yes | Name of the MCP server backing this tool. |
name | string | Yes | The tool’s original name on the MCP server. |
is_deferred | bool | No | Whether the tool call is deferred. |
For built-in TrueFoundry system tools (type: "truefoundry-system"), for example ask_user_question:
| Field | Type | Required | Description |
|---|
type | "truefoundry-system" | Yes | |
name | string | Yes | The system tool’s name. |
ModelMessage
The complete, assembled form of a model message: fully concatenated content and fully reconstructed tool_calls, with finish_reason set. This is the shape you get after merging all of a message’s deltas into its base event, or from ModelMessageBuilder.build_and_reset().
| Field | Type | Required | Description |
|---|
id | string | Yes | Shared across all deltas of one message; equal to the assembled ModelMessageEvent id. |
thread_id | string | Yes | Thread this message belongs to. "main" for the root agent; a unique ID for sub-agents. |
content | string | No | The complete assistant text. |
reasoning_content | string | No | The complete reasoning/thinking text. |
tool_calls | ToolCall[] | No | Fully assembled tool calls. |
finish_reason | string | Yes | Why the message ended (for example, "stop" or "tool_calls"). |
ModelMessageEvent
The event form of ModelMessage (adds type and id). Returned by list_events(); on the stream, the assistant output arrives as ModelMessageEventDelta deltas instead.
| Field | Type | Required | Description |
|---|
type | "model.message" | Yes | |
id | string | Yes | Unique event identifier. Equal to the id carried by this message’s model.message.delta events. |
thread_id | string | Yes | Thread this message belongs to. "main" for the root agent; a unique ID for sub-agents. |
content | string | No | The complete assistant text. |
reasoning_content | string | No | The complete reasoning/thinking text. |
tool_calls | ToolCall[] | No | Fully assembled tool calls. |
finish_reason | string | Yes | Why the message ended (for example, "stop" or "tool_calls"). |
Each ToolCall is shaped like OpenAI’s ChatCompletionMessageToolCall. Read the tool name from tool_info.name.
| Field | Type | Required | Description |
|---|
id | string | Yes | Tool call ID. |
type | "function" | Yes | Always "function". |
function | ToolCallFunction | Yes | The tool name and complete arguments. |
tool_info | ToolCallInfo | No | Tool metadata (same shape as above). |
ToolCallFunction:
| Field | Type | Required | Description |
|---|
name | string | Yes | Tool name. |
arguments | string | Yes | Complete JSON string of the tool’s input arguments. |
A complete server-side tool result returned to the LLM. Not a streaming delta - one event carries the full result.
| Field | Type | Required | Description |
|---|
type | "tool.response" | Yes | |
thread_id | string | Yes | Thread this result belongs to. |
tool_call_id | string | Yes | Links this result back to the originating tool call. |
content | string | Yes | The tool result. |
ThreadCreatedEvent
Emitted when a sub-agent thread starts.
Not emitted for the main thread. The main thread (thread_id: "main") is the root agent; it is created automatically on the session’s first Turn and is never the subject of a thread.created or thread.done event. thread.created/thread.done track only sub-agent threads.
| Field | Type | Required | Description |
|---|
type | "thread.created" | Yes | |
thread_id | string | Yes | Unique ID for the sub-agent thread. |
title | string | No | Human-readable label for the sub-agent thread. |
parent | AgentParent | Yes | Parent thread and tool call that spawned this sub-agent. |
agent_info | AgentInfo | Yes | Type, name, and input of the thread’s agent. |
AgentInfo:
| Field | Type | Required | Description |
|---|
type | "dynamic" | Yes | How the sub-agent was defined. |
name | string | Yes | The sub-agent’s name. |
input | string | No | The input the parent thread handed to the sub-agent. |
ThreadDoneEvent
Emitted when a sub-agent thread reaches a terminal state. Does not terminate the overall Turn stream.
Not emitted for the main thread. The main thread is never “done” - it lives across Turns for the lifetime of the session. The overall Turn’s completion is signalled by turn.done, and the session ends only when it is cancelled.
| Field | Type | Required | Description |
|---|
type | "thread.done" | Yes | |
thread_id | string | Yes | Unique ID for the sub-agent thread. |
title | string | No | Human-readable label for the sub-agent thread. |
status | "done" | "error" | Yes | done - completed normally, output is present. error - failed, message is present. |
output | ModelMessage | No | Final accumulated message. Present when status is "done". |
parent | AgentParent | Yes | Parent thread and tool call that spawned this sub-agent. |
message | string | No | Present when status is "error". |
McpInitializedEvent
Emitted when one or more MCP server sessions are initialized for a thread.
| Field | Type | Required | Description |
|---|
type | "mcp.initialize" | Yes | |
thread_id | string | Yes | |
content | McpInitializationInfo[] | Yes | List of initialized MCP servers, each with mcp_server_name and session_id. |
SandboxCreatedEvent
Emitted once when a sandbox is provisioned for the Turn. The sandbox is reused across Turns within the session.
| Field | Type | Required | Description |
|---|
type | "sandbox.created" | Yes | |
thread_id | null | Yes | Always null — turn-level event, not tied to a thread. |
sandbox_id | string | Yes | Unique identifier for the provisioned sandbox. |
McpAuthRequiredEvent
Emitted when one or more MCP servers require OAuth authorization before the agent can proceed. The stream ends after this event. Resume by creating a new Turn on the same session and starting it (for example with stream()) after the user completes the OAuth flow - no input is required (omit input or pass []).
| Field | Type | Required | Description |
|---|
type | "mcp.auth_required" | Yes | |
thread_id | null | Yes | Always null — turn-level event. Blocked threads are listed per server in servers[].thread_ids. |
servers | McpServerAuthInfo[] | Yes | List of servers needing authorization, each with mcp_server_name, auth_url, and thread_ids. |
Emitted when one or more tool calls require explicit human approval before they can run. The stream ends after this event. Resume by creating a new Turn with UserToolApproval items - one per pending tool_call_id - and starting it (for example with stream()).
| Field | Type | Required | Description |
|---|
type | "tool.approval_required" | Yes | |
thread_id | string | Yes | |
tool_calls | ToolCallRef[] | Yes | The tool calls awaiting approval, each with a tool_call_id (id) and the event_id of the model.message that emitted it - look it up in your event index for the tool name and arguments. |
Emitted when the agent has called a client-side tool and is waiting for the result. The stream ends after this event. Resume by creating a new Turn with UserToolResponse items - one per pending tool_call_id - and starting it (for example with stream()).
| Field | Type | Required | Description |
|---|
type | "tool.response_required" | Yes | |
thread_id | string | Yes | |
tool_calls | ToolCallRef[] | Yes | The tool calls awaiting a client-supplied result, each with a tool_call_id (id) and the event_id of the model.message that emitted it - look it up in your event index for the tool name and arguments. |
TurnDoneEvent
Final event on every Turn stream. Stream-only. Its state is the terminal state object - the same value returned by wait_for_completion() - so callers don’t need a separate Turn fetch to know what happened.
| Field | Type | Required | Description |
|---|
type | "turn.done" | Yes | |
thread_id | null | Yes | Always null — turn-level event, not tied to a thread. |
state | TurnTerminalState | Yes | The Turn’s terminal state; never running. See TurnTerminalState. |
TurnTerminalState
The terminal lifecycle state of a Turn, returned by wait_for_completion() and carried by TurnDoneEvent’s state. It is one of three objects, discriminated on status: TurnDoneState, TurnCancelledState, or TurnErrorState. (The non-terminal TurnRunningState, returned by state() while a Turn is still running, has only status: "running".)
TurnDoneState
The Turn completed normally.
| Field | Type | Required | Description |
|---|
status | "done" | Yes | |
output | ModelMessageEvent | null | No | The final assistant message, or null if the Turn paused (then required_actions is populated). |
required_actions | (ToolApprovalRequiredEvent | ToolResponseRequiredEvent | McpAuthRequiredEvent)[] | No | Pause events to act on. Empty if none. |
completed_at | string | Yes | ISO-8601 timestamp when the Turn completed. |
TurnCancelledState
The Turn was cancelled.
| Field | Type | Required | Description |
|---|
status | "cancelled" | Yes | |
reason | string | null | No | Why the Turn was cancelled. |
completed_at | string | Yes | ISO-8601 timestamp when the Turn was cancelled. |
TurnErrorState
The Turn failed.
| Field | Type | Required | Description |
|---|
status | "error" | Yes | |
message | string | Yes | Human-readable error description. |
completed_at | string | Yes | ISO-8601 timestamp when the Turn failed. |