Skip to main content
Agent SDK is experimental and will have breaking changes!
The Agent Harness SDK lets you define agents in code, save them to the Agent Registry, and invoke them from any application — with multi-turn conversations, live event streaming, human-in-the-loop approvals, and parallel sub-agents. Read the concepts below — each one uses the same support-bot example so you can see how the pieces connect. When you’re ready to write code, follow one of the two paths in Getting started.

Core concepts

Interaction with an agent follows a strict hierarchy. The sections below walk through each layer using a single running example: a customer support agent named support-bot.

Agent

An agent is a saved definition in the Agent Registry — not a running process. You define it once (model, instructions, tools, config) and any number of customers can invoke it by name. Imagine support-bot: a customer support assistant that looks up orders and processes refunds via an MCP server. Its spec might look like this:
support-bot — AgentManifest
type: truefoundry-agent
name: support-bot
description: Customer support assistant for order lookups and refunds
model:
  name: anthropic/claude-sonnet-4-6
  params:
    max_tokens: 4096
instructions: |
  You help customers with orders. Look up order details before taking action.
  Always confirm before processing refunds.
mcp_servers:
  - name: orders-api
    enable_tools: ["get_order", "process_refund"]
    require_approval_for_tools: ["process_refund"]
config:
  iteration_limit: 25
  sandbox:
    enabled: true
collaborators: []
Every customer session loads this same definition. Changing the agent (new model, new tools) creates a new version in the Registry — existing sessions keep the config they started with. See Agent manifest reference for every field.

Session

A session is one customer working through one issue with the agent. It is the conversation context: all turns on that issue chain together, and the agent remembers what happened earlier in the same session. Example — Jane Doe has two separate issues:
SessionIssueHow it starts
sess-7f2a9c1bRefund for order ORD-2031Jane opens chat: “What’s the status of order ORD-2031?”
sess-9d4e2a88Can’t log in to her accountJane starts a new chat for a different problem
Jane keeps chatting about the refund in sess-7f2a9c1b — follow-up messages stay in that session. When she has a login problem, she (or your app) creates a new session so the agent doesn’t mix refund context with account access.
Session for Jane's refund issue
{
  "id": "sess-7f2a9c1b",
  "agent_name": "support-bot",
  "title": "Jane Doe — refund for ORD-2031",
  "created_at": "2026-06-24T10:00:00Z"
}
Persist session.id so Jane can return tomorrow and pick up where she left off. Only one turn runs inside a session at a time.

Turn

A turn is one request in the conversation — a single back-and-forth boundary between your app and the agent. Each time Jane sends a message (or your app sends an approval), you create a new turn. Turns inside a session chain automatically: the agent sees every earlier turn in that session. Example — three turns in Jane’s refund session:
TurnWho sends itInputWhat happens
Turn 1Jane (via your app)"What's the status of order ORD-2031?"Agent looks up the order and replies with shipping status.
Turn 2Jane"Please issue a full refund for that order."Agent prepares a refund, hits an approval gate, and pauses.
Turn 3Jane (or support rep)Approval: allow process_refundAgent runs the refund and sends a confirmation.
Your code calls session.create_turn(input) then turn.stream() once per row. Turn 2 knows Jane asked about ORD-2031 in Turn 1 without you resending that history — the harness chains turns for you.
A turn can also end paused when the agent asks a clarifying question (tool.response_required) or needs MCP OAuth (mcp.auth_required). You resume with a new turn, just like Turn 3 above.

Event

While a turn runs, the agent emits events over Server-Sent Events (SSE) — one JSON object at a time. Events tell your app what the agent is doing: calling a tool, getting a result, writing a reply, or finishing. Example — events during Turn 1 (Jane asks for order status):
OrderEvent typeWhat your app learns
1turn.createdTurn started — turn_id: turn-001
2mcp.initializeAgent connected to orders-api
3model.messageAgent decided to call get_order
4tool.responseOrder data: shipped, $1,240.00
5model.messageAgent starts composing the reply
6–8model.message.deltaReply text arriving in chunks — see Delta below
9turn.doneTurn finished — final reply in state.output
Sample payloads:
turn.created
{ "type": "turn.created", "turn_id": "turn-001", "thread_id": null, "sequence_number": 1 }
tool.response
{
  "type": "tool.response",
  "thread_id": "main",
  "tool_call_id": "call-get-order-01",
  "content": "{\"order_id\":\"ORD-2031\",\"status\":\"shipped\",\"total\":1240.00}"
}
turn.done
{
  "type": "turn.done",
  "thread_id": null,
  "state": {
    "status": "done",
    "output": {
      "type": "model.message",
      "content": "Your order ORD-2031 shipped on June 12. Total: $1,240.00."
    }
  }
}
Every event carries an id, a thread_id ("main" for the root agent, or null for turn-level events like turn.created), and a sequence_number. The stream always opens with turn.created and closes with turn.done.

Delta

Most events arrive as a complete payload. Model output is different — the LLM streams token by token, so the harness sends a base model.message event first, then a series of model.message.delta fragments that you merge into it. All deltas share the base event’s id. Example — streaming Jane’s reply in Turn 1: The agent’s full reply is: “Your order ORD-2031 shipped on June 12. Total: $1,240.00.” Your client receives this sequence:
Base event (empty shell)
{ "type": "model.message", "id": "msg-a1", "thread_id": "main", "content": "", "sequence_number": 5 }
Deltas (merge into msg-a1 as they arrive)
{ "type": "model.message.delta", "id": "msg-a1", "content": "Your order ORD-2031", "sequence_number": 6 }
{ "type": "model.message.delta", "id": "msg-a1", "content": " shipped on June 12.", "sequence_number": 7 }
{ "type": "model.message.delta", "id": "msg-a1", "content": " Total: $1,240.00.", "finish_reason": "stop", "sequence_number": 8 }
Why stream deltas to the client? So you can show progress while the model generates — a typing indicator, word-by-word rendering, or a partial reply before the turn finishes. Your app merges each delta into the base event and re-renders events["msg-a1"].content on every chunk:
Your order ORD-2031
Your order ORD-2031 shipped on June 12.
Your order ORD-2031 shipped on June 12. Total: $1,240.00.
Use merge_event_delta(base, delta) from the SDK to fold fragments in place. When the turn completes, list_events() returns one pre-merged model.message — no deltas to handle on replay. See Use an agent — Subscribe to events for the full merge pattern.
Thread is a related concept: an execution context inside a session. The root agent runs on thread_id: "main"; sub-agents get their own thread IDs. Events carry thread_id so you can partition a single turn stream when sub-agents run in parallel.
ConceptReal-world analogy (support-bot)Created by
AgentThe support-bot playbook — model, instructions, toolstruefoundry client or Playground
SessionJane’s refund ticket — one issue, many messagescreate_session("support-bot")
TurnOne message Jane sends (or one approval she gives)create_turn(input) + stream()
EventA step in the agent’s work — tool result, reply, doneEmitted during stream()
DeltaOne word/chunk of the streaming replyStreamed after a base model.message
Sequence diagram showing a client creating a session, then chaining multiple turns: each turn streams SSE events such as turn.created and model.message, ends with turn.done, and can resume with user follow-up input such as tool approvals or responses

End-to-end walkthrough

The three turns from Turn above, shown together. Jane’s refund session (sess-7f2a9c1b) against support-bot:
Input: "What's the status of order ORD-2031?"
seqtypesummary
1turn.createdTurn starts
2mcp.initializeConnected to orders-api
3–4model.message + deltasAgent calls get_order
5tool.response{ status: shipped, total: 1240.00 }
6–8model.message + deltasStreams reply to Jane’s UI
9turn.doneDone — output has full reply
TurnJane’s actionEnds withWhat your app does next
1Asks for order statusReply in outputShow reply, wait for next message
2Requests refundtool.approval_requiredShow approval UI → Turn 3
3Approves refundConfirmation in outputShow confirmation, issue closed
If Jane opens a new session later for a login problem, Turn 1 of that session starts fresh — no refund history carried over.

Getting started

Two SDK packages, two paths. You can create agents in the Playground and skip the create path entirely.
TaskPackageClient
Define and save an agenttruefoundryfrom truefoundry import client
Run a saved agenttruefoundry-gateway-sdkTrueFoundryGateway

Create an agent

Define an AgentManifest and save it to the Agent Registry with the truefoundry client. Covers model, MCP tools, skills, and runtime config.

Use an agent

Invoke a saved agent with TrueFoundryGateway. Covers sessions, turns, event streaming, delta merging, approvals, threads, and reconnects.
1

Create an agent

Install truefoundry, authenticate with tfy login, and call client.agents.create_or_update(). See Create an agent.
2

Use an agent

Install truefoundry-gateway-sdk, set GATEWAY_BASE_URL and TFY_API_KEY, then create_session()create_turn()stream(). See Use an agent.

Complete example

Runnable terminal chat client handling every event type.

Agent Playground

Build and test agents in the UI, then invoke them from code.

Reference

Agent manifest

Every field on AgentManifest.

Turn events

All event types with field schemas.

Runtime API

SDK methods and turn input types.