REST API

The engine serves four HTTP surfaces on one host. Requests and responses are JSON; streaming endpoints return text/event-stream. Every shape used in a body is defined under Objects, so this page stands on its own.

SurfacePrefixAuthFor
Client/api/clientClient JWTEnd-user clients (browser, app backend).
Machine/api/machineAPI keyYour backend and worker callbacks.
Admin/api/adminAPI keySelf-hosted session inspection.
Cloud/api/v1API keyThe hosted control plane the subs cloud commands wrap.

Base URL

Prepend your engine's host to every path.

  • Self-hosted: your subs serve address, such as http://localhost:8080. All four surfaces share it.
  • Cloud: the control plane (/api/v1) is https://api.substructure.ai. The client and machine surfaces are served at your app's own engine URL, shown by subs open and handed to clients alongside their token.

Headers

HeaderWhen
Authorization: Bearer <token>Every request. Which token depends on the surface (see each section).
Content-Type: application/jsonAny request with a body.
Accept: text/event-streamThe streaming and AG-UI endpoints.

Errors

Failures return a status and a JSON body. Most surfaces use the first shape; the machine submit and settle endpoints use the second.

{ "error": "session not found" }
{ "ok": false, "error": "effect is not pending" }
StatusMeaning
202Input accepted.
400Malformed request or a validation failure.
401Missing or invalid credential.
403Wrong tenant, not the session owner, or wrong caller for the effect.
404Unknown session or effect.
409Conflict: session interrupted, effect not pending, or a completed turn.
422Unrecognized JSON body.

Streaming

The .../events/stream endpoints return text/event-stream. Each frame carries id (the event's stream version), event (the event type), and data (an Event envelope). Resume after a drop with ?after_stream_version=<n>; the id of the last frame you saw is that number.

id: 7
event: message.new
data: {"global_position":42,"aggregate_id":"0193a1","stream_version":7,"occurred_at":"2026-07-14T10:00:03Z","payload":{"type":"message.new","message":{"id":"m2","role":"assistant","content":"Hi"}}}

event: llm.token.delta
data: {"type":"llm.token.delta","session_id":"0193a1","call_id":"llm-1","seq":3,"text":"Hi"}

llm.token.delta frames stream partial assistant output between events; see StreamDelta. The event value is one of:

EventPayload highlights
message.newA message was added to the tree: { message: Message, parent_id? }.
turn.started / turn.completedTurn boundaries, { turn_id, … }; turn.completed closes a turn-scoped stream.
tool.call.requested / .completed / .errored{ tool_call_id, name, result? / error? }.
llm.call.requested / .completed / .erroredAn LLM call's lifecycle.
sub_agent.requested / .started / .turn_completed / .erroredA child session's lifecycle.
session.created / .done / .cancelledSession lifecycle.
session.interrupted / .interrupt_resumed{ interrupt_id, origin, reason, payload, anchor? }; anchor is the head the interrupt parks (absent = every branch).
worker.state.updated / agent.updatedState or config was rewritten.
worker.decision.requested / .completed / .errored, decision_request.queued / .dropped, call.voided, session.message_requestedEngine bookkeeping, useful for observability.

The AG-UI endpoints emit AG-UI protocol events instead; see AG-UI events.

Client API

For end-user clients: a browser, or your app's frontend.

Authentication. Authorization: Bearer <client-jwt>. A client token is short-lived and minted by your backend through the machine API's client-tokens endpoint; never ship an API key to a browser. The token fixes the tenant and owner, so a client acts only as itself. This surface answers CORS preflight, so it is safe to call from a browser.

curl $BASE/api/client/sessions/input \
    -H "Authorization: Bearer $CLIENT_JWT" \
    -H "Content-Type: application/json" \
    -d '{"input":{"type":"client.message","agent_id":"my-agent","message":{"role":"user","content":"hi"}}}'

POST /api/client/sessions/input

Submit a ClientInput: a message, action, interrupt resume, or client-tool settle. session_id is minted when omitted; a completed turn_id returns the existing turn instead of re-running it.

Headers Authorization: Bearer <client-jwt>, Content-Type: application/json

Request body

{
  "session_id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "input": {
    "type": "client.message",
    "agent_id": "my-agent",
    "message": { "role": "user", "content": "what time is it?" }
  }
}

Response 202

{
  "session_id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "turn_id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a60"
}

GET /api/client/sessions/{session_id}

The session resource: head-resolved status, every open interrupt, messages — the active conversation (root→head lineage), ready to render — and the full message tree for clients that show branches. Enough for a client to rehydrate a conversation on page load.

Headers Authorization: Bearer <client-jwt>

Path parameters

ParameterDescription
session_idThe session to fetch.

Response 200

{
  "session_id": "sess-1",
  "agent_id": "my-agent",
  "status": "idle",
  "interrupts": [],
  "messages": [
    { "id": "m1", "role": "user", "content": "hi" },
    { "id": "m2", "role": "assistant", "content": "Hello!" }
  ],
  "message_tree": {
    "head_id": "m2",
    "nodes": [
      { "message": { "id": "m1", "role": "user", "content": "hi" } },
      { "parent_id": "m1", "message": { "id": "m2", "role": "assistant", "content": "Hello!" } }
    ]
  }
}

status is idle, interrupted, or done, resolved at the active branch: it reads interrupted only when an open interrupt parks the head path. interrupts lists every open interrupt on any branch as { interrupt_id, origin, reason, anchor? }anchor is the message the interrupt is parked at (absent = parks every branch). A session that has never received input is 404.

GET /api/client/sessions/{session_id}/events/stream

Stream a session's events.

Headers Authorization: Bearer <client-jwt>, Accept: text/event-stream

Path parameters

ParameterDescription
session_idThe session to stream.

Query parameters

ParameterTypeDescription
turn_idstringOptional. Scope to one turn; the stream closes when it completes.
after_stream_versionintegerOptional. Resume after this stream version.

Response text/event-stream. See Streaming.

POST /api/client/sessions/{session_id}/interrupt

Pause the session's active branch. The interrupt anchors at the current head; other branches stay live (see Interrupts). interrupt_id is minted when omitted.

Headers Authorization: Bearer <client-jwt>, Content-Type: application/json

Path parameters

ParameterDescription
session_idThe session to interrupt.

Request body

{
  "reason": "confirm",
  "payload": { "message": "Send the email?" }
}

Response 200

{ "ok": true, "interrupt_id": "int-1" }

POST /api/client/ag-ui/agents/{agent_id}/run

Run a turn from an AG-UI client.

Headers Authorization: Bearer <client-jwt>, Content-Type: application/json, Accept: text/event-stream

Path parameters

ParameterDescription
agent_idThe agent to run.

Request body: a RunAgentInput.

{
  "threadId": "sess-1",
  "runId": "turn-1",
  "messages": [{ "id": "m1", "role": "user", "content": "hi" }],
  "tools": [],
  "context": [],
  "state": {},
  "resume": []
}

Response text/event-stream of AG-UI events.

event: RUN_STARTED
data: {"type":"RUN_STARTED","threadId":"sess-1","runId":"turn-1"}

event: TEXT_MESSAGE_CONTENT
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"m2","delta":"Hi"}

event: RUN_FINISHED
data: {"type":"RUN_FINISHED","threadId":"sess-1","runId":"turn-1"}

POST /api/client/ag-ui/agents/{agent_id}/connect

Replay the session as a one-shot AG-UI snapshot, for reconnecting.

Headers Authorization: Bearer <client-jwt>, Content-Type: application/json, Accept: text/event-stream

Path parameters

ParameterDescription
agent_idPresent for symmetry; only threadId in the body is used.

Request body: threadId addresses the session; runId is optional and labels the snapshot frames.

{ "threadId": "sess-1" }

Response text/event-stream: RUN_STARTED, MESSAGES_SNAPSHOT, RUN_FINISHED, then close.

Machine API

For your own backend and for worker callbacks. Server-side only.

Authentication. Authorization: Bearer <api-key>, the app's API key. It carries full tenant privileges, so keep it on your server and never expose it to a browser. This surface has no CORS. It is where you mint client tokens, submit on a user's behalf, and settle deferred calls.

curl $BASE/api/machine/client-tokens \
    -H "Authorization: Bearer $API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"identity":{"id":"user_42"}}'

POST /api/machine/client-tokens

Mint a client JWT for a browser session.

Headers Authorization: Bearer <api-key>, Content-Type: application/json

Request body. identity.id is required; ttl_seconds defaults to 600.

{
  "identity": { "id": "user_42", "metadata": { "plan": "pro" } },
  "ttl_seconds": 600
}

Response 200. expires_at is Unix seconds.

{ "token": "eyJhbGciOiJIUzI1NiJ9...", "expires_at": 1784000000 }

POST /api/machine/sessions/submit

Submit a ClientPayload from your backend; the owner is attested from your key and the request identity.

Headers Authorization: Bearer <api-key>, Content-Type: application/json

Request body. agent_id and identity.id are required.

{
  "agent_id": "my-agent",
  "identity": { "id": "user_42" },
  "session_id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "payload": {
    "type": "client.message",
    "message": { "role": "user", "content": "hi" }
  }
}

Response 202

{
  "session_id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "turn_id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a60"
}

POST /api/machine/sessions/{session_id}/calls/settle

Settle a worker-handled tool or LLM call out of band, by effect id. This is how a deferred call reports its result later.

Headers Authorization: Bearer <api-key>, Content-Type: application/json

Path parameters

ParameterDescription
session_idThe session the call belongs to.

Request body. One of four variants (response is an LlmResponse, code an ErrorCode):

{ "type": "tool.result", "id": "call_abc", "result": "sunny" }
{ "type": "tool.error",  "id": "call_abc", "error": "upstream 503", "retryable": true, "code": "provider_error" }
{ "type": "llm.result",  "id": "llm_abc",  "response": { "model": "gpt-4", "content": "…" } }
{ "type": "llm.error",   "id": "llm_abc",  "error": "rate limited", "retryable": true }

Response 200

{ "ok": true }

POST /api/machine/workers/submit

Push a worker-authored DecisionResponse into the engine out of band, keyed by session_id and decision_id. Every settle action must name its id and every llm.call its model, since no trigger is in scope to fill them.

Headers Authorization: Bearer <api-key>, Content-Type: application/json

Request body

{
  "session_id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
  "decision_id": "dec_abc",
  "actions": [{ "type": "tool.result", "id": "call_abc", "result": "sunny" }]
}

Response 200

{ "ok": true }

GET /api/machine/sessions/{session_id}/events/stream

The session event stream under API-key auth.

Headers Authorization: Bearer <api-key>, Accept: text/event-stream

Path parameters

ParameterDescription
session_idThe session to stream.

Query parameters

ParameterTypeDescription
turn_idstringOptional. Scope to one turn.
after_stream_versionintegerOptional. Resume after this stream version.

Response text/event-stream. See Streaming.

Admin API

Read-only session inspection on a self-hosted server.

Authentication. Authorization: Bearer <api-key>, the same key as the machine surface. Results are scoped to the key's tenant. These routes exist only on a subs serve host, not in the cloud.

curl $BASE/api/admin/sessions -H "Authorization: Bearer $API_KEY"

GET /api/admin/sessions

List sessions, newest first.

Headers Authorization: Bearer <api-key>

Query parameters

ParameterTypeDescription
top_levelbooleanDefault true. Exclude sub-agent sessions.
sortstringlast_event_desc (default), first_event_asc, first_event_desc, or wake_at_asc.
limitintegerPage size.
cursorstringPage cursor; echo next_cursor from the previous page.
session_idstringFilter to one session.
agent_idstringFilter by agent.

Response 200

{
  "items": [
    {
      "session_id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
      "tenant_id": "default",
      "stream_version": 12,
      "first_event_at": "2026-07-14T10:00:00Z",
      "last_event_at": "2026-07-14T10:03:00Z",
      "wake_at": null,
      "top_level": true,
      "agent_id": "my-agent",
      "cost": "0.0012",
      "sub_agent_cost": "0",
      "status": "idle",
      "turn_id": null
    }
  ],
  "next_cursor": null
}

GET /api/admin/sessions/{session_id}

Fetch one session's current state. state is the internal session aggregate (owner, status, running cost, message tree, open calls). Treat it as read-only diagnostics; it may grow fields.

Headers Authorization: Bearer <api-key>

Path parameters

ParameterDescription
session_idThe session.

Response 200

{
  "stream_version": 12,
  "first_event_at": "2026-07-14T10:00:00Z",
  "last_event_at": "2026-07-14T10:03:00Z",
  "state": {
    "status": "idle",
    "owner": { "tenant_id": "default", "id": "user_42" },
    "cost": "0.0012",
    "message_tree": { "nodes": [], "head_id": "m2" }
  }
}

GET /api/admin/sessions/{session_id}/events

Fetch stored Events.

Headers Authorization: Bearer <api-key>

Path parameters

ParameterDescription
session_idThe session.

Query parameters

ParameterTypeDescription
after_stream_versionintegerReturn events after this version.
limitintegerPage size.

Response 200

[
  {
    "global_position": 128,
    "id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a70",
    "tenant_id": "default",
    "aggregate_type": "session",
    "aggregate_id": "0193a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
    "stream_version": 3,
    "occurred_at": "2026-07-14T10:00:03Z",
    "payload": {
      "type": "message.new",
      "message": { "id": "m2", "role": "assistant", "content": "Hi" }
    }
  }
]

GET /api/admin/sessions/{session_id}/events/stream

The same events as SSE. after_stream_version defaults to 0, replaying full history.

Headers Authorization: Bearer <api-key>, Accept: text/event-stream

Path parameters

ParameterDescription
session_idThe session to stream.

Query parameters

ParameterTypeDescription
after_stream_versionintegerDefault 0. Resume after this version.
limitintegerOptional cap on replayed history.

Response text/event-stream. See Streaming.

POST /api/admin/sessions/{session_id}/ag-ui/connect

Replay the session as an AG-UI snapshot.

Headers Authorization: Bearer <api-key>, Content-Type: application/json, Accept: text/event-stream

Path parameters

ParameterDescription
session_idThe session to replay.

Request body: a RunAgentInput.

Response text/event-stream of AG-UI events.

Cloud API

The hosted control plane at https://api.substructure.ai, wrapped by the subs cloud commands (see CLI).

Authentication. Authorization: Bearer <token>: your subs login token against the cloud, or an app API key against a local server. A local subs serve re-serves the same shape as a single tenant, so the {org} and {app} segments are ignored, and the mutating endpoints return 400:

{ "error": { "code": "unsupported_on_local", "message": "this operation is not supported on a local server; it only exists in the hosted cloud" } }
curl https://api.substructure.ai/api/v1/orgs -H "Authorization: Bearer $TOKEN"

GET /api/v1/orgs

List the orgs you belong to.

Headers Authorization: Bearer <token>

Response 200

[{ "id": "org_1", "name": "Acme", "role": "owner" }]

GET /api/v1/orgs/{org}/apps

List an org's apps. Returns an array of App.

Headers Authorization: Bearer <token>

Path parameters

ParameterDescription
orgThe org id.

GET /api/v1/apps/{app}

Fetch one app.

Headers Authorization: Bearer <token>

Path parameters

ParameterDescription
appThe app id.

Response 200. An App. balanceUsd and sessionCount are omitted on a local server.

{
  "id": "app_abc",
  "organizationId": "org_1",
  "name": "my-bot",
  "createdAt": "2026-07-01T00:00:00Z",
  "balanceUsd": "42.50",
  "sessionCount": 1280
}

POST /api/v1/orgs/{org}/apps

Create an app. Hosted only.

Headers Authorization: Bearer <token>, Content-Type: application/json

Path parameters

ParameterDescription
orgThe org to create the app in.

Request body

{ "name": "my-bot" }

Response 200

{
  "app": { "id": "app_abc", "organizationId": "org_1", "name": "my-bot" },
  "signing_secret": "9f3c1a…"
}

PATCH /api/v1/apps/{app}

Rename an app. Hosted only.

Headers Authorization: Bearer <token>, Content-Type: application/json

Path parameters

ParameterDescription
appThe app id.

Request body: { "name": "new-name" }. Response 200: the updated App.

DELETE /api/v1/apps/{app}

Delete an app. Hosted only.

Headers Authorization: Bearer <token>

Path parameters

ParameterDescription
appThe app id.

GET /api/v1/apps/{app}/worker

Read the webhook worker.

Headers Authorization: Bearer <token>

Path parameters

ParameterDescription
appThe app id.

Response 200

{
  "endpoint_url": "https://my-worker.example.com/agent",
  "state": "enabled",
  "signing_secret": "9f3c1a…"
}

PUT /api/v1/apps/{app}/worker

Set the webhook worker. Enabling reuses or mints the signing secret.

Headers Authorization: Bearer <token>, Content-Type: application/json

Path parameters

ParameterDescription
appThe app id.

Request body. state is "enabled" or "disabled".

{ "endpointUrl": "https://my-worker.example.com/agent", "state": "enabled" }

Response 200. The worker config, as GET returns.

POST /api/v1/apps/{app}/worker/rotate-secret

Rotate the signing secret. No body; returns the worker config with the new secret.

Headers Authorization: Bearer <token>

Path parameters

ParameterDescription
appThe app id.

GET /api/v1/apps/{app}/api-keys

List client API keys. Hosted only.

Headers Authorization: Bearer <token>

Response 200

[{ "key_id": "key_1", "label": "web-frontend", "created_at": "…", "last_used_at": null }]

POST /api/v1/apps/{app}/api-keys

Create a client API key. Hosted only.

Headers Authorization: Bearer <token>, Content-Type: application/json

Request body: { "label": "web-frontend" }.

Response 200. The key is shown once.

{ "api_key": "sk_live_…" }

DELETE /api/v1/apps/{app}/api-keys/{key_id}

Revoke a key. Hosted only.

Headers Authorization: Bearer <token>

Path parameters

ParameterDescription
appThe app id.
key_idThe key to revoke.

GET /api/v1/apps/{app}/sessions…

Session reads, identical to the Admin API with the {app} segment ignored on a local server: /sessions, /sessions/{id}, /sessions/{id}/events, and /sessions/{id}/events/stream.

Objects

The types used in the bodies above. ? marks an optional field; unknown is any JSON value. These mirror the machine-readable schemas/protocol.schema.json; see Typed bindings to generate them.

Messages

type Role = "system" | "user" | "assistant" | "tool"
type Content = string | ContentPart[]
type ContentPart =
    | { type: "text"; text: string }
    | { type: "image_url"; image_url: { url: string } }
    | { type: "file"; file: { filename: string; file_data: string } }
    | { type: "input_audio"; input_audio: { data: string; format: string } }
    | { type: "video_url"; video_url: { url: string } }
type ToolCall = { id: string; type: string; function: { name: string; arguments: string } }
// A message; the id is optional when you submit one (the engine assigns it).
type Message = {
    id?: string
    role: Role
    content?: Content
    tool_calls?: ToolCall[]
    tool_call_id?: string
    name?: string
}

Client input

The body of POST /api/client/sessions/input is { session_id?, input: ClientInput }.

type ClientInput =
    | { type: "client.message"; agent_id: string; turn_id?: string; message: Message; stream?: boolean }
    | { type: "client.messages"; agent_id: string; turn_id?: string; messages: Message[]; stream?: boolean; client?: ClientContext }
    | { type: "client.action"; agent_id: string; turn_id?: string; name: string; args?: unknown }
    | { type: "interrupt.resume"; interrupt_id: string; payload?: unknown }
    | { type: "tool.result"; id: string; attempt?: number; result?: unknown }
    | { type: "tool.error"; id: string; error: string; retryable: boolean; attempt?: number }

// Browser-contributed tools and context, layered onto the agent for this turn.
type ClientContext = {
    tools?: AgentTool[]
    context?: unknown[]
    state?: unknown
    forwarded_props?: unknown
}
type AgentTool = {
    name: string
    description?: string
    input?: unknown             // JSON Schema for arguments
    output?: unknown            // JSON Schema the result must satisfy
    handler?: "worker" | "client"
}

turn_id is an idempotency key; resubmitting a completed one returns the existing turn.

Client payload

The payload of POST /api/machine/sessions/submit: a client input without the addressing fields, which the machine supplies separately.

type ClientPayload =
    | { type: "client.message"; message: Message; stream?: boolean }
    | { type: "client.messages"; messages: Message[]; stream?: boolean; client?: ClientContext }
    | { type: "client.action"; name: string; args?: unknown }

Decision response

The body of POST /api/machine/workers/submit.

type DecisionResponse = {
    messages?: Message[]        // messages to record
    actions?: Action[]          // what the engine should do next
    state?: unknown             // omitted or null keeps current state
    agent?: AgentConfig         // omitted keeps current config
}

type Action =
    | { type: "llm.call"; id?: string; model?: string; messages?: Message[]; tools?: LlmTool[]; temperature?: number; max_completion_tokens?: number; reasoning?: ReasoningConfig; stream?: boolean; retry?: RetryPolicy; handler?: "server" | "worker" }
    | { type: "tool.call"; id?: string; name: string; arguments: unknown; handler?: "worker" | "client"; retry?: RetryPolicy }
    | { type: "tool.result"; id?: string; attempt?: number; result: unknown }
    | { type: "tool.error"; id?: string; attempt?: number; error: string; retryable?: boolean; code?: ErrorCode; detail?: unknown }
    | { type: "llm.result"; id?: string; attempt?: number; response: LlmResponse }
    | { type: "llm.error"; id?: string; attempt?: number; error: string; retryable?: boolean; code?: ErrorCode; detail?: unknown }
    | { type: "sub_agent.spawn"; session_id: string; agent_id: string; tool_call_id: string; retry?: RetryPolicy }
    | { type: "message.send"; session_id: string; message: Message }
    | { type: "interrupt"; interrupt_id?: string; reason: string; payload?: unknown }
    | { type: "done"; data?: unknown }

type AgentConfig = {
    model: string
    system?: string
    stream?: boolean
    handler?: "server" | "worker"
    format?: "openai" | "anthropic"
    retry?: RetryPolicy
    tools?: AgentTool[]
    sub_agents?: { id: string; description?: string }[]
}
type LlmTool = { name: string; description: string; input?: unknown; output?: unknown }
type ReasoningConfig = { effort?: "xhigh" | "high" | "medium" | "low" | "minimal" | "none"; max_tokens?: number; exclude?: boolean; enabled?: boolean }

LLM response

Used in the llm.result settle and action.

type LlmResponse = {
    model: string
    content?: string
    tool_calls?: ToolCall[]
    finish_reason?: string
    usage?: unknown
    cost?: string               // dollars, decimal string
    images?: { url: string }[]
}

Retries and errors

type RetryPolicy = {
    timeout_secs: number | null   // deadline per attempt; null waits forever
    max_retries: number           // cap on total attempts
    backoff_base_secs: number
    backoff_max_secs: number
}
type ErrorCode = "provider_error" | "rate_limited" | "refused" | "budget_exceeded" | "deadline_exceeded"

Events

A stored event, from the event-stream and .../events endpoints. payload is tagged by type (see the Streaming table).

type Event = {
    global_position: number
    id: string
    tenant_id: string
    aggregate_type: string       // "session"
    aggregate_id: string         // the session id
    stream_version: number       // the SSE frame id
    occurred_at: string          // RFC 3339
    payload: { type: string; [field: string]: unknown }
}

type StreamDelta = {             // the llm.token.delta data
    type: "llm.token.delta"
    session_id: string
    call_id: string
    seq: number
    text?: string
    reasoning?: string
    tool_calls?: { id: string; name?: string; arguments?: string }[]
    finish_reason?: string
}

App

The Cloud App resource. balanceUsd and sessionCount are omitted on a local server.

type App = {
    id: string
    organizationId: string
    name: string
    createdAt?: string
    balanceUsd?: string          // decimal string
    sessionCount?: number
}

RunAgentInput

The AG-UI request body (camelCase).

type RunAgentInput = {
    threadId: string             // the session id
    runId: string                // the turn id
    messages: { id?: string; role: string; content?: string; toolCallId?: string; toolCalls?: unknown[] }[]
    state?: unknown
    tools?: { name: string; description?: string; parameters?: unknown }[]
    context?: unknown[]
    forwardedProps?: unknown
    resume?: { interruptId: string; status: "resolved" | "cancelled"; payload?: unknown }[]
}

AG-UI events

The AG-UI run and connect streams emit these, each a frame whose event is the type and data is a camelCase JSON object.

RUN_STARTED, RUN_FINISHED, RUN_ERROR, MESSAGES_SNAPSHOT, TEXT_MESSAGE_START, TEXT_MESSAGE_CONTENT, TEXT_MESSAGE_END, TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END, TOOL_CALL_RESULT, REASONING_START, REASONING_MESSAGE_START, REASONING_MESSAGE_CONTENT, REASONING_MESSAGE_END, REASONING_END. RUN_FINISHED carries an interrupt outcome when the agent paused. See AG-UI.

Next