Skip to main content
The server prop accepts either a built-in config object or an object implementing AgentUIServer.
An AgentUIServer object is used immediately. The built-in trueforge config resolves asynchronously, so the SDK renders a loading indicator (role="status") for a moment first. It loads plugins/trueforge-agent-server-adapter and composes a full Harness AgentUIServer — see Quickstart. catalog and permissions optionally override the adapter’s built-in ports. A custom AgentUIServer can expose the same optional ports directly.

The composed contract

Chat and session operations, agent-builder operations, and an optional settings catalog. Only the first two are required. Every interface is generic. The SDK’s types constrain only the fields the UI itself reads, so when your backend returns richer objects you widen the type parameters rather than casting or stripping fields. See widening below.

AgentChatServer

The runtime calls these. All operations are keyed by a flat sessionId.
deleteSession and renameSession are optional. Presence gates Delete and Rename in the session list; renameSession persists { sessionId, title }. TrueForge implements both. TrueFoundry Gateway does not persist titles and should omit renameSession. listTurnEvents hydrates the events of a single in-flight turn when your backend can serve them per turn. subscribeToTurn lets a reconnecting client resume a stream from afterSequenceNumber rather than replaying it — pass the same abortSignal you use on createTurn to tear it down. downloadSandboxFile enables artifact downloads.
downloadSandboxFile receives both turnId and sandboxId because backends address sandboxes differently. If your download route is scoped to a turn, resolve the sandbox from turnId and ignore sandboxId; if you address sandboxes directly, use sandboxId.
createTurn and subscribeToTurn yield TurnStreamData, an envelope of { sequenceNumber, event }. The event protocol is documented in Streaming events.

Sessions

id, isMutable, createdAt, and updatedAt are required; timestamps are ISO strings. Set isMutable: true while the agent spec may still be edited. renameSession must work for named and inline sessions. CreateSessionRequest is { agentName?, agentSpec?, title? }; UpdateSessionRequest is { sessionId, agentSpec?, title? }.

Pagination

ListResult is a plain DTO — data holds the rows and nextPageToken carries the cursor for the next page, absent on the last page. Requests accept a matching pageToken, which the SDK feeds back on the following call.
Because ListResult is a plain object, you can return a parsed JSON body directly as long as it exposes data and (optionally) nextPageToken.

Turn input

What the UI sends into createTurn:
A plain chat message arrives as user.message. Approving or rejecting a tool call, and answering a prompt the agent raised, arrive as the other two — all three through the same createTurn entry point, so branch on type.

Turn state

A turn ends in one of three terminal states, each carrying completedAt. A done turn may still carry requiredActions — that is how a turn signals it is waiting on the user, such as a tool approval, rather than being finished outright. The user’s reply arrives on the next createTurn call as a user.tool_approval or user.tool_response input item.

AgentBuilderServer

Powers the agent library and the draft composer.
deleteAgent is the only optional method here — the rest must be implemented.

Capabilities

getCapabilities tells the UI which optional features your backend supports, so it can hide what you do not offer rather than surfacing controls that fail.
It is fetched once when the server resolves. skill.reason is shown to the user when skills are disabled, so use it to explain why — for example that no skill registry is configured. Setting settings.enabled to false hides the settings control even when a catalog is attached; omitting settings leaves it visible.
Read the resolved value anywhere in your own components with useServerCapabilities().

Picker rows

The remaining methods return the minimum each selector renders: provider is a { name; logo? } object, and properties is a { reasoningEfforts?: string[] } object — both are required on the row even when empty (properties: {}).
Return a non-empty properties.reasoningEfforts on a ModelSelection and the UI shows a reasoning-effort picker beside the model selector. On AgentLibraryEntry, agentId is the stable identifier used for selection, history filtering, and session binding; omit it and the row falls back to name. Supply agentSpec to enable Edit; omit it for try-only agents.
SearchAgentsParams is { query?, limit?, offset? } — offset paging, since it backs a search box rather than an infinite list. SaveAgentRequest is { agentName, agentSpec, intent, sessionId? }, where intent is "create" for a new agent and "update" when the chat is already bound to one. It returns a SaveAgentResult of { agentId?, sessionUpdatedAt? } — agentId is the id the host registry allocated, and sessionUpdatedAt is set when the active mutable session was updated in the same call.

AgentSpec

Only model is required. instructions carries the system prompt. config toggles runtime features per agent (generative UI, dynamic sub-agents, ask-user questions), and variables holds string substitutions the backend interpolates. SkillMount and McpServerMount are typed as object. The runtime stores and forwards mounts without reading any field, and your backend owns their shape — so the base constrains only that a mount is an object, and you intersect your concrete type over it.

Widening the types

Every interface takes type parameters defaulting to the SDK-minimal shapes. Supply your own to carry extra fields through the UI without casting:
The UI keeps reading only the fields it knows about; your own components — supplied through slot overrides — can read the rest.

Next

Worked implementation

A minimal AgentUIServer you can copy.

Streaming events

The event protocol createTurn yields.

Settings catalog

The optional catalog port behind the settings UI.

Type reference

Every exported server type.