Skip to main content
This guide covers everything you need to run a saved agent: open a session, send turns, consume the event stream, and handle pauses, threads, and disconnects.
Prerequisite: an agent saved in the Registry. See Create an agent or the Agent Playground. For the conceptual overview and a full walkthrough, see SDK overview.

Install and connect

Running agents uses the gateway SDK, not the truefoundry client.
For TypeScript, install truefoundry-gateway-sdk from npm. Python examples use the sync TrueFoundryGateway client; see Create a client for AsyncTrueFoundryGateway. TypeScript uses TrueFoundryGateway with await / for await. Both call client.agents.sessions.*. Set TFY_GATEWAY_URL and TFY_API_KEY as above. Every example below comes in both languages. SSE streams return a Stream. Iterating yields typed event bodies. When you need the SSE id (for resume with after_sequence_number), use Python .with_metadata() or TypeScript .withMetadata() — each item exposes .id and .data — see Resume streaming.

Create a client

Construct a TrueFoundryGateway (Python sync or TypeScript) or AsyncTrueFoundryGateway (Python asyncio). Examples below use the sync client unless noted.

Create a session

A session is the conversation context for a saved agent — one customer working through one issue. It persists across many turns; persist session.id to resume later.

Open a session

A Session is the conversation context for a saved agent. Create one by name with create / create. The response wraps the session in .data.

List sessions for an agent

List the existing sessions for a saved agent with list / list. It iterates newest-first and auto-paginates. Use it to resume an earlier conversation — each Session exposes its id (persist or reuse it to continue).

Create a turn

A turn is one request/response cycle. Send input, the agent runs until it finishes or pauses, then the stream closes. Create another turn on the same session to continue — turns chain automatically.

Create and stream a turn

Start a turn with create_turn_stream / createTurnStream and iterate the returned SSE stream. The first event is always turn.created (carrying the turn_id); the stream closes with turn.done. When the stream runs to completion, read the terminal state from the final turn.done event (or refetch with get_turn). 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.
Creating a new turn in a session automatically cancels any other turn that is still running in that session.

List turns

List the turns in a session with list_turns / listTurns. Each turn exposes its input, and state holds the current lifecycle state.
list_turns() always returns turns in descending order (newest-first).

Non-streaming turn

If you don’t need live events, call create_turn / createTurn. It returns the turn object immediately (no SSE) — the turn may still be running. Use get_turn / getTurn or create_turn_stream when you need terminal state or events.

Attach images or files to a turn

A UserMessage’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>.
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.

Cancel a turn

To stop the currently running turn, call cancel / cancel on the 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. You can continue by creating a new turn, which chains on the cancelled turn’s history.

Handle MCP outbound auth

When the agent needs to call a tool on an MCP server that requires a separate outbound authentication, it emits an mcp.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.

Handle tool approvals

When a tool call is configured to require human approval, the agent emits a tool.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 UserToolApprovalEvent per pending tool call — allow it, or deny it with an optional reason. Each pending ToolCallRef carries the source_event_id of the model.message that emitted the tool call. Keep the same id-keyed event index you build while streaming, then look up events[source_event_id] to read the tool’s name and arguments — no separate bookkeeping required.

Answer agent questions

When the agent needs input it cannot safely assume, it can ask the user a structured question via the built-in client-side ask_user_question tool. Since the tool runs on your side, the agent emits a 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 UserToolResponseEvent per pending tool call. Each pending ToolCallRef carries the source_event_id of the model.message that emitted the tool call. Keep the same id-keyed event index you build while streaming, then look up events[source_event_id] to read the tool’s name and arguments — no separate bookkeeping required.

Subscribe to events

Every turn emits a stream of events over SSE. The stream opens with turn.created and closes with turn.done. See the Turn events reference for every event type.

Handling Event Delta while streaming

Most events in a turn are complete on their own - a single payload you can use directly. Some updates are streamed instead: the base event arrives first, followed by a series of Event Deltas - incremental fragments that you merge into the base. All deltas for one update share the base event’s id. The SSE id (sequence number used for resume) increases across the base and all its deltas — read it with Python .with_metadata() / TypeScript .withMetadata().
Because every delta carries the base event’s id, keep an id-keyed index of assembled events: store each non-delta event under its id, and merge each delta into the base with the same id. The id is unique per message, so deltas from concurrently streaming threads (the main agent and any sub-agents) always merge into the right base.
Both languages ship is_event_delta and merge_event_delta from truefoundry_gateway_sdk.agents (Python) and truefoundry-gateway-sdk/agents (TypeScript).
Event Deltas appear only while streaming. When you list turn events via the events API, the deltas are already merged into a single assembled event.
The most common example is the assistant’s model output: a base ModelMessageEvent followed by ModelMessageDeltaEvent deltas that carry incremental text and tool-call chunks. Merge the deltas into the base as they arrive — read the base’s growing content for a live typing effect.

Resume streaming

When you lose the original create_turn_stream / createTurnStream stream (for example, after a page reload), call get and get_turn, then check turn.state. If it is still running, reconnect with subscribe_to_turn / subscribeToTurn and keep merging; if it has already finished, rebuild the index from list_turn_events / listTurnEvents instead. When resuming a running turn, pass after_sequence_number to continue after a known point; the stream closes when the turn reaches a terminal state. Either way, merge into the same id-keyed index of assembled events, so base events and their deltas keep merging seamlessly across the reconnect. The sequence number is the SSE id, not a field on the event body. Track it while streaming with Python .with_metadata() or TypeScript .withMetadata():

Handle threads

A single turn stream interleaves events from the root agent and any sub-agents that run in parallel. Every event carries a thread_id: See Turn Events for the full reference. The sequence below shows a turn where the root agent spawns a research sub-agent. Events interleave across two threads (main and the sub-agent), and the run pauses for an approval before a second turn resumes it. Solid arrows are turn inputs you send; dashed arrows are SSE events streamed back to your client.

Listing Events

Fetch the full event log of a finished turn with list_turn_events / listTurnEvents. It yields the turn’s events in order and auto-paginates. Pass order="asc" (default, oldest-first) or order="desc" to control the direction. Unlike live create_turn_stream / subscribe_to_turn streams, list_turn_events returns already-merged events: each model message arrives as a single assembled ModelMessageEvent, never as a base plus deltas. There is nothing to merge — you can use each event directly. For example, a model message that the live stream delivers as a base event followed by deltas:
is returned by list_turn_events as one fully-merged event:
list_turn_events is only available for turns that have completed. A running turn has no stored event log yet — use create_turn_stream or subscribe_to_turn for live delivery instead.

Complete example

The Complete example is a runnable terminal chat client that implements every pattern in this guide — streaming, delta merging, approvals, questions, MCP auth, sub-agent threads, and multi-turn chaining.