Coming soon.
Overview
This page covers running an agent that is already saved in the Agent Registry. To create or configure an agent, see Agent. The Agent Harness ships with a Python and TypeScript SDK for interacting with agents and consuming the event stream. We also provide a React component for building a chat interface for an agent.How interaction works
Interaction is organized as a hierarchy: Session → Turn → Events, with threads running underneath.- Session — a long-running agent process. The session holds the agent configuration and persists across many Turns.
- Thread — an execution context inside the session. The main thread is the root agent, which interacts with the user and acts as a coordinator for other agents. It can spin up additional threads, each running a sub-agent. Threads are long-lived - they can live across Turns.
- Turn — a single user interaction with the session, and the request boundary. Turns within a session are chained, so each Turn builds on the history of the ones before it. Only one Turn can be running in a session at any point in time. A Turn runs until the agent finishes, or until it hits the iteration limit or timeout configured for the agent. A Turn can be cancelled while it is running.
- Events — a Turn streams events as the agent works. Each event identifies the thread it came from. A Turn begins with a creation event and ends with a terminal event.
Creating a Session
A Session is the conversation context for a saved agent. Create one by name withcreate_session().
Python
Listing sessions for an agent
List the existing Sessions for a saved agent withlist_sessions(). It iterates newest-first, transparently paginating. Use it to resume an earlier conversation - each Session exposes its id (persist or reuse it to continue).
Python
Interacting with a Session by creating a Turn
Create a Turn in the session withcreate_turn(). It streams the Turn’s events as they arrive. The first event is always turn.created (carrying the turn_id); the stream closes on a terminal event.
To continue the conversation, create another Turn on the same session. Turns within a session are chained automatically, so each Turn sees the history of the ones before it.
Python
Non-streaming Turn
If you don’t need live events, passstream=False. Instead of an event iterator, create_turn() returns a Turn directly once the Turn is created server-side. Block until it finishes with wait(), which returns the terminal state.
Python
Attaching images or files to a Turn
AUserMessage’s content can be a plain string or a list of content parts. Use content parts to send text alongside one or more file uploads (images, PDFs, and other documents). Each file is passed as a data URI of the form data:<mime>;base64,<payload>.
Python
Whether an attached file is understood depends on the agent’s model. Send images only to a vision-capable model; document handling (for example, PDFs) likewise depends on model and agent configuration.
Non-image files such as PDFs require the sandbox to be enabled on the agent - the harness uses it to process the document.
Listing Turns
List the Turns in a session withlist_turns(). It defaults to order="desc" (newest-first); pass order="asc" for oldest-first. Each Turn exposes its input, and state() returns the current status (and output once the Turn is terminal).
Python
Cancelling a Turn
To stop the currently running Turn, callcancel() on its session. Cancelling aborts any in-flight model request, waits for running MCP tool calls to finish, and force-stops any sandbox the Turn provisioned. Cancellation is idempotent. The cancelled Turn is terminal, so its event log is stored and can be replayed - and you can continue the conversation by creating a new Turn, which chains on the cancelled Turn’s history.
Python
Streaming a running Turn’s events
Reconnect to a running Turn’s live event stream withstream(). This is useful when you have lost the original create_turn() stream (for example, after a page reload). It resumes transparently from the last delivered event on any connection drop and closes when the Turn reaches a terminal state.
Python
Getting events for a completed Turn
Fetch the full event log of a finished Turn withevents(). It yields the Turn’s events in order, auto-paginating. Pass order="asc" (default, oldest-first) or order="desc" to control the direction.
events() is only available for Turns that are not running. A running Turn has no stored event log yet - use stream() for live delivery instead. turn.created and turn.done are SSE-only and do not appear in events().Python
Handling MCP Outbound Auth
When the agent needs to call a tool on an MCP server that requires a separate outbound authentication, it emits a terminalmcp.auth_required event and the Turn ends. The event lists each server that needs authentication along with an auth_url. Depending on how the server is configured, this may be an OAuth flow or an API-key entry - see MCP authentication scenarios for details. Send the user to that URL to complete authentication, then resume by creating a new Turn.
When the previous Turn ended with
mcp.auth_required, passing a UserMessage in the resuming Turn is not allowed.Python
Merging the model message stream
The assistant’s response arrives as a sequence ofModelMessageDelta events - streaming deltas that carry incremental text and tool-call chunks. Render them as they arrive for a live typing effect, or, if you only need the finished message, merge the deltas into a single complete message.
The SDK ships a ModelMessageBuilder utility for this. Feed it each ModelMessageDelta; when a delta arrives with a non-null finish_reason, the message is complete and build_and_reset() returns the assembled ModelMessage (concatenated content and fully reconstructed tool_calls). Group by thread_id, since the main agent and any sub-agents stream concurrently.
Python
Handling tool approvals
When a tool call is configured to require human approval, the agent emits a terminaltool.approval_required event and the Turn ends. Each event carries a thread_id and the tool_calls awaiting a decision - a single Turn can emit more than one (for example, when parallel threads each call a gated tool), so collect all of them. Resume by creating a new Turn with one UserToolApproval per pending tool call - allow it, or deny it with an optional reason.
Python
Answering agent’s questions
When the agent needs input it cannot safely assume, it can ask the user a structured question via the built-in client-sideask_user_question tool. Since the tool runs on your side, the agent emits a terminal tool.response_required event and the Turn ends. The pending tool call’s arguments carry the question and its options. Collect the user’s answer and resume by creating a new Turn with one UserToolResponse per pending tool call.
Python
Turn input
Each Turn’sinput is a list of one of these types. Resuming a Turn paused by mcp.auth_required needs no input - omit input or pass [].
User messages (
UserMessage) cannot be mixed with tool approvals or client-side tool responses in the same input list. UserToolApproval and UserToolResponse may be mixed together.UserMessage
Start a new conversation or send the next user message.content is either a plain string or a list of content parts, letting you attach files alongside text.
| Field | Type | Required | Description |
|---|---|---|---|
type | "user.message" | Yes | |
content | string | UserContentPart[] | Yes | The message text, or a list of content parts (text and file uploads). |
UserContentPart
A content part is one of: Text| Field | Type | Required | Description |
|---|---|---|---|
type | "text" | Yes | |
text | string | Yes | The message text. |
| Field | Type | Required | Description |
|---|---|---|---|
type | "file_upload" | Yes | |
file.filename | string | Yes | Name of the uploaded file. |
file.file_data | string | Yes | Data URI: data:<mime>;base64,<payload>. |
UserToolApproval
Sent to resume a Turn paused bytool.approval_required. One item per pending tool call.
| Field | Type | Required | Description |
|---|---|---|---|
type | "user.tool_approval" | Yes | |
thread_id | string | Yes | thread_id from the tool.approval_required event. |
tool_call_id | string | Yes | ID of the tool call being approved or denied. |
approval | ApprovalAllow | ApprovalDeny | Yes | Use {"status": "allow"} to permit the call, or {"status": "deny", "reason": "..."} to block it. reason is optional. |
UserToolResponse
Sent to resume a Turn paused bytool.response_required. One item per pending tool call.
| Field | Type | Required | Description |
|---|---|---|---|
type | "user.tool_response" | Yes | |
thread_id | string | Yes | thread_id from the tool.response_required event. |
tool_call_id | string | Yes | ID of the tool call whose result is being supplied. |
content | string | Yes | The result to return to the agent. |
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.createdand closes after a terminal event. - Events tied to a thread carry a
thread_id. The root agent’s thread is always"main"; dynamically spawned sub-agents have unique thread IDs. thread.createdandthread.donetrack 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. The overall Turn’s completion is signalled byturn.done.- All events carry a
sequence_id, monotonically increasing within a Turn. turn.createdandturn.doneappear only in the SSE stream; they are not stored in the Turn body’seventslist.
turn.*
type | Description |
|---|---|
turn.created | First event on the stream. Carries turn_id. |
turn.done | Last event on every stream. Carries the final status. |
model.* - Model output
type | Description |
|---|---|
model.message | LLM output - assistant text and/or tool call chunks. Streamed as ModelMessageDelta; stored in the Turn body as the assembled ModelMessage. Most frequent event. |
tool.* - Tool calls and results
type | Description |
|---|---|
tool.response | Complete server-side tool result. |
tool.approval_required | A tool call is awaiting human approval. Resume with an approval. |
tool.response_required | A client-side tool was called and needs a result. Resume with the tool response. |
thread.* - 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.* - 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.* - Sandbox lifecycle
type | Description |
|---|---|
sandbox.created | A sandbox was provisioned for the Turn. |
TurnCreated
First event on every Turn stream. Carries theturn_id. SSE only - not stored in the Turn body’s events.
| Field | Type | Required | Description |
|---|---|---|---|
type | "turn.created" | Yes | |
turn_id | string | Yes | Unique ID for this Turn. |
created_at | string | Yes | ISO-8601 timestamp when the Turn was created. |
ModelMessageDelta
The most frequent SSE event. Carries an incremental LLM delta for one thread - assistant text and/or tool call chunks. Group deltas bythread_id and accumulate; a delta with a non-null finish_reason signals the message is complete. The Turn body stores the assembled ModelMessage instead of these deltas.
Use ModelMessageBuilder from truefoundry_gateway_sdk to handle delta accumulation.
| Field | Type | Required | Description |
|---|---|---|---|
type | "model.message" | Yes | |
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. |
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. |
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 | MCP / routing 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 underlying MCP server. Read the tool name from original_tool_name.
| Field | Type | Required | Description |
|---|---|---|---|
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. |
original_tool_name | string | Yes | The tool’s original name on the MCP server. |
is_approval_required | bool | No | Whether the tool call requires human approval. |
is_deferred | bool | No | Whether the tool call is deferred. |
is_client_side | bool | No | Whether the tool runs client-side (for example, ask_user_question). |
ModelMessage
The assembled (non-delta) form of a model message: fully concatenatedcontent and fully reconstructed tool_calls, with finish_reason always set. This is the form stored in the Turn body’s events (and returned by the list-events endpoint), and the value ModelMessageBuilder.build_and_reset() returns after merging ModelMessageDelta events.
| Field | Type | Required | Description |
|---|---|---|---|
type | "model.message" | Yes | |
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. |
tool_calls | ToolCall[] | No | Fully assembled tool calls. |
finish_reason | string | Yes | Why the message ended (for example, "stop" or "tool_calls"). |
ToolCall is shaped like OpenAI’s ChatCompletionMessageToolCall. Read the tool name from tool_info.original_tool_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 | MCP / routing 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. |
ToolResponse
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. |
ThreadCreated
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. |
parent | AgentParent | Yes | Parent thread and tool call that spawned this sub-agent. |
agent_info | AgentInfo | Yes | Name and input of the thread’s agent. |
created_at | string | No |
ThreadDone
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. |
status | "done" | "error" | Yes | done - completed normally, output is present. error - failed, message is present. |
output | object | No | Final accumulated output. 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". |
McpInitialized
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. |
SandboxCreated
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 | |
sandbox_id | string | Yes | Unique identifier for the provisioned sandbox. |
McpAuthRequired
Emitted when one or more MCP servers require OAuth authorization before the agent can proceed. The stream ends after this event. Resume by callingcreate_turn() again on the same session after the user completes the OAuth flow - no input is required (omit input or pass []).
| Field | Type | Required | Description |
|---|---|---|---|
type | "mcp.auth_required" | Yes | |
servers | McpServerAuthInfo[] | Yes | List of servers needing authorization, each with mcp_server_name, auth_url, and thread_ids. |
ToolApprovalRequired
Emitted when one or more tool calls require explicit human approval before they can run. The stream ends after this event. Resume by sendingUserToolApproval items - one per pending tool_call_id - on the next create_turn().
| Field | Type | Required | Description |
|---|---|---|---|
type | "tool.approval_required" | Yes | |
thread_id | string | Yes | |
tool_calls | ToolCallRef[] | Yes | The tool calls awaiting approval, each with an id. |
ToolResponseRequired
Emitted when the agent has called a client-side tool and is waiting for the result. The stream ends after this event. Resume by sendingUserToolResponse items - one per pending tool_call_id - on the next create_turn().
| 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 an id. |
TurnDone
Final event on every Turn stream. Thestatus field distinguishes normal completion from cancellation or error. SSE only - not stored in the Turn body’s events. Its output mirrors the Turn body’s output field, listing any terminal events that occurred.
| Field | Type | Required | Description |
|---|---|---|---|
type | "turn.done" | Yes | |
status | "done" | "cancelled" | "error" | Yes | |
output | TurnOutputEvent[] | No | Terminal events that occurred this Turn (tool.approval_required, tool.response_required, mcp.auth_required) plus accumulated model messages. |
cancellation_reason | string | No | Present when status is "cancelled". Values: "client-cancelled", "server-execution-timeout". |
message | string | No | Present when status is "error". Human-readable error description. |
Complete usage example
This is a complete, runnable terminal chat client built on the SDK. It handles every event the harness can emit:- streams assistant tokens in real time,
- shows tool calls and tool results inline,
- prompts the user to answer
ask_user_questioncalls, - prompts the user to allow or deny tool approvals,
- shows in-chat MCP OAuth prompts,
- handles parallel sub-agents,
- continues the conversation across Turns.
Python
Sample run
Adapting the flow
A few common variations on top of the same skeleton:- Persisted history. Save
session.idafter the first Turn. On the next process start, callclient.agents.get_session(session_id)and uselist_turns()withturn.events()to rebuild UI state, orturn.stream()if the latest Turn is still running. - JSON output for piping. Replace the
printcalls inside_handle_eventwithjson.dumps(event.model_dump())to emit one event per line for downstream tools. - Generative UI. When you detect a fenced
```openuiblock in assembledmodel.messagecontent, hand the block to the OpenUI React renderer instead of printing it. Everything else stays the same. - Browser / web UI. The same event shape is delivered over Server-Sent Events on
POST /sessions/{session_id}/turns. Replacesession.create_turnwith an SSE consumer; the handlers above are unchanged.
Reference
create_session
| Param | Type | Required | Description |
|---|---|---|---|
agent_name | string | Yes | Name of the saved agent to invoke. |
title | string | No | Optional human-readable title for the session. |
list_sessions
| Param | Type | Required | Description |
|---|---|---|---|
agent_name | string | Yes | Filter to sessions for a specific named agent. |
created_by_subject_slug | string | No | Filter to sessions created by a specific user / service account. |
start_timestamp | string | No | ISO-8601 lower bound on session creation time. |
end_timestamp | string | No | ISO-8601 upper bound on session creation time. |
limit | int | No | Number of sessions fetched per page (not a total cap). Defaults to 100. |
Session
The conversation context for a saved agent. Turns created within a Session are chained automatically, so each Turn sees the history of earlier ones. Key members:| Member | Type | Description |
|---|---|---|
id | string | Unique session identifier. Persist it to resume the conversation later via client.agents.get_session(session_id). |
create_turn(...) | method | Start a Turn and stream its events, or pass stream=False to get a Turn back without streaming. See create_turn. |
list_turns(...) | method | Iterate over the Turns in this session, newest first by default. |
get_turn(turn_id) | method | Fetch a single Turn by ID. |
cancel() | method | Cancel the session and any currently running Turn. Idempotent; after this, create_turn() raises. |
create_turn
stream=True (default) it streams the Turn’s events - the first event is always turn.created and the stream closes on a terminal event. With stream=False it returns a Turn without streaming.
| Param | Type | Required | Description |
|---|---|---|---|
input | TurnInput | No | Input items for this Turn. See Turn input. Omit or pass [] to resume after mcp.auth_required. |
previous_turn_id | string | "auto" | No | Turn chaining point. Defaults to "auto", letting the server chain to the latest Turn. |
stream | bool | No | Stream events as they arrive. Defaults to True. Set False to create the Turn and return a Turn without streaming - drive it with wait() / state() / events(). |
Turn
A single request/response cycle within a Session. Transitions:running → done | cancelled | error.
| Member | Type | Description |
|---|---|---|
id | string | Unique turn identifier (UUIDv7). |
session | Session | The Session this Turn belongs to. |
previous_turn_id | string | None | Turn ID this Turn chains from, or None for the first Turn. |
created_by | string | Subject (user / service account) that created this Turn. |
created_at | string | ISO-8601 timestamp of Turn creation. |
input | TurnInput | None | The Turn input that triggered this Turn, if any. |
state() | method | Fetch the current lifecycle state from the server. Returns one of TurnRunningState, TurnDoneState, TurnCancelledState, or TurnErrorState. |
stream(...) | method | Subscribe to the live SSE stream for this Turn. Handles reconnection and resumption transparently. Closes when the Turn reaches a terminal state. Raises if the Turn is not running — use events() for completed Turns. |
events(...) | method | Return a point-in-time snapshot of events accumulated so far, paginating automatically. Works for both running and terminal Turns. order="asc" is natural for replaying history forward. TurnCreated and TurnDone are SSE-only and do not appear here. |
wait() | method | Block until the Turn reaches a terminal state and return it. Returns immediately if already terminal. |
cancel() | method | Request cancellation. Idempotent; safe to call on already-terminal Turns. Cancellation is asynchronous — a few more events may arrive before the stream closes. |
To define or configure the agent itself, see Agent.