server prop accepts either a built-in config object or an object implementing AgentUIServer.
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
AgentChatServer
The runtime calls these. All operations are keyed by a flatsessionId.
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 intocreateTurn:
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
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.
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.
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
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: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.