Docs/API Reference/Streaming: SSE & AG-UI

Streaming: SSE & AG-UI

Transport
Server-Sent Events (text/event-stream) over HTTPS POST/GET. No WebSocket endpoint is documented on this page.
Auth
Bearer JWT for the tenant-facing endpoints ( Authentication ); share/demo streams use their own credentials.
Two wire formats
The legacy chat stream (in-band marker chunks) and the structured AG-UI stream. Both run the same turn through AgentChatService.

SSE endpoints in core

Verb and pathControllerAuthPurpose
POST /api/agents/{agentId}/chatAgentControllerJWT, agent RUNNERStreamed agent turn (token chunks).
POST /api/agents/{agentId}/agui/runAgUiRunControllerJWT, RUNNERAG-UI run (consumes application/json).
GET /api/agents/{agentId}/agui/runs/{runId}/eventsAgUiRunControllerJWT, RUNNERRejoin a run after a dropped connection.
GET /api/agents/{agentId}/jobs/{jobId}/trace/streamAgentJobObservabilityControllerJWT, VIEWERLive trace of an autonomous job; closes when the job reaches a terminal state.
POST /api/share/agents/{shareId}/chat/streamAgentShareControllerNone (share id)Anonymous shared-agent chat; body message (max 2000 chars), optional sessionKey. 429 rate_limited, 410 expired, 422 invalid.
POST /api/share/agents/{shareId}/voice/turn; POST /api/voice/agents/{agentId}/turnAgentShareVoiceController; VoiceCallControllerShare id; JWTVoice turn, multipart request, SSE response.
POST /api/quick-task/streamQuickTaskControllerJWTQuick-task stream.
POST /api/briefs/planBriefControllerJWT with PERM_MANAGE_AGENTS (catch-all /api/briefs/** matcher)Brief planning stream.
POST /api/demo/studio/agents/{agentId}/mission; POST /api/demo/studio/workflows/{workflowId}/runDemoStudioControllerX-Demo-TokenDemo Agent Studio streams.

Workflow runs are not streamed: POST /api/workflows/{id}/run returns the run (WorkflowRunResponse). With "background": true it returns the freshly persisted RUNNING row and the client polls GET /api/workflows/{id}/runs/{runId} (see Workflow Engine). Request fields (WorkflowRunRequest): input, requestedBy, clientRunId, attachments, background. The A2A message/stream method also answers with text/event-stream; see Developer API.

Chat stream (POST /api/agents/{agentId}/chat)

Request (ChatRequest)

FieldTypeNotes
messagestring, requiredMax 8000 characters (bean validation).
sessionIdstringGenerated if omitted. May not start with eval: or sim: (400).
attachmentKey, attachmentMimeType, attachments, visionSessionId, turnRefvariousAttachment and vision context. Upload via POST /api/agents/{agentId}/chat/attachments (multipart).
forceFastDepthbooleanUsed by the voice bridge to force the FAST reasoning depth.
stepEventsbooleanOpt in to tool-activity events on the stream.
toolNamesstring[]Narrows this one turn to the listed tools of the agent (the playground's "this run only" tool switches). Narrowing only: a name the agent does not offer you for this turn is refused (400, naming it); a tool that is kept is still behind its approval, autonomy and guardrail gates; the agent's saved tool access is never changed. Omitted leaves the agent's tools as they are; an empty list runs the turn without tools. At most 200 names of 128 characters. The turn the platform starts after a human decides a gated call keeps the same selection, and the turn journal's intake event lists it (runTools=).

Response framing

In-band markers

ConstantValueMeaning
STREAM_ERROR_MARKER@@AGENT_CHAT_STREAM_ERROR_9f2b1c@@Chunk prefix; the rest is a failure message. Emitted only if the stream already wrote to the wire (status line committed). A failure before the first byte is a normal HTTP error instead (402, 503 and so on, via GlobalExceptionHandler).
STEP_EVENT_MARKER@@AGENT_STEP_EVENT_4d81ba@@Chunk prefix followed by single-line JSON step event (tool call, tool result, error, plan, guardrail, handoff, run start/finish). Emitted only when stepEvents:true.
INTERRUPTED_REPLY_MARKER\n\n[reply interrupted]Appended to a partially persisted reply when a turn is cancelled or the client disconnects (package-private constant in AgentChatService).
Why step events are opt-in
Many consumers read this wire format without being able to opt out: the LiveKit voice bridge (would speak the JSON aloud), omnichannel adapters (would paste it into customer messages), and IDE clients. So the trace is a property of the request, never a default. The exact step-event JSON fields were not enumerated for this page: not documented here; see AgentStepEventEmitter.

Turn-lifecycle step events

Besides the tool and run events, a stream that set stepEvents:true carries five events that say what the turn is doing beyond working. Their fields are codes, counts, names and ids, never wording, so a client keys its chips and notices on them and not on the notice text the platform also appends to the reply. The schemas are the agentStep_* messages of docs/api/asyncapi.json.

typeFieldsWhen
finishfinishReason: stop, length, safety, malformed_tool_call, empty or interrupted; optional detail (the provider's word for a refusal)Once per reply, before run_finished. Offer "Continue" on length and interrupted; show the refusal notice on safety.
retryattempt (the try about to run, 1 is the first), maxAttempts, reasonThe same model is called again after a failure before its first token, or an unusable tool call is asked for again.
failovertoModel (absent when not known by name), reasonThe turn moved to the agent's fallback provider or to the next model of the platform-hosted pool.
continuationpass, maxPasses, reason: cut_off, unfinished_step or approvalAnother model pass carries the reply on.
approval_requiredtool, approvalIdA tool call was recorded as a pending approval; sent after that call's tool_result.

reason on retry and failover is one of rate_limited, timeout, provider_unavailable, auth_failed, billing, model_not_found, provider_error or, on retry only, malformed_tool_call. A client without a finish event (an older core) falls back to recognising the reply-text markers.

Run control

Verb and pathBehaviour
POST /api/agents/{agentId}/chat/sessions/{sessionId}/cancelRequires RUNNER. Records a Redis flag polled by the running turn (about every 500 ms); replies 202 with a map containing cancelRequested: true. Works across replicas.
POST /api/agents/{agentId}/chat/sessions/{sessionId}/rewindBody SessionRewindRequest; semantics not documented on this page.

AG-UI (POST /api/agents/{agentId}/agui/run)

A protocol-shaped alternative that lets any AG-UI client (for example @ag-ui/client's HttpAgent, CopilotKit, or a hand-written SSE reader) drive an agent with no VegaDūta-specific code. It is strictly additive: same authorization, session semantics, moderation, guardrails, metering and persistence as /chat; threadId is the sessionId.

Request (RunAgentInput)

Fields: threadId, runId, parentRunId, state, messages[] (id, role, content, toolCalls, toolCallId, name), tools[], context[], forwardedProps; unknown fields are ignored. The controller runs the last user message's text (string content, or the joined text parts of an array). No user text: 400. Over the controller's maximum message length: 400. Missing threadId/runId are generated as UUIDs. A supplied runId must be 1 to 128 letters, digits, - or _ (otherwise 400). In the controller code read for this page, only the last user message and the ids are passed to the turn; tools and state are not forwarded to it.

Event sequence

RUN_STARTED{threadId,runId}
STATE_SNAPSHOT{snapshot:{sessionId,agentId}}
  TEXT_MESSAGE_START{messageId,role:"assistant"}
  TEXT_MESSAGE_CONTENT{messageId,delta}            (one per non-empty token)
  TEXT_MESSAGE_END{messageId}
  TOOL_CALL_START{toolCallId,toolCallName,parentMessageId}
  TOOL_CALL_ARGS{toolCallId,delta}                 (whole redacted args JSON as one delta)
  TOOL_CALL_END{toolCallId}
  TOOL_CALL_RESULT{messageId,toolCallId,content,role:"tool"}
  CUSTOM{name:"plan"|"guardrail"|"handoff",value:{...}}
RUN_FINISHED{threadId,runId}   or   RUN_ERROR{message,code}   (never both)

Resume

GET /api/agents/{agentId}/agui/runs/{runId}/events replays buffered events after a position, then attaches to the live tail if the run executes on this pod. Position: ?after=<seq> or the standard Last-Event-ID header (after wins). If the run is on another pod or its live window passed you receive the buffered history and a clean end of stream; poll again until the buffer contains RUN_FINISHED or RUN_ERROR. A rejoin is scoped to the agent in the path and to the signed-in user who started the run: for anyone else, or another agent, the stream is empty, exactly as for a run that does not exist. Companion: GET /api/agents/{agentId}/agui/tools (JSON, VIEWER) lists the agent's tools as AG-UI Tool objects (name, description, permissive parameters schema).

sequenceDiagram
  participant U as AG-UI client
  participant A as AgUiRunController
  participant S as AgentChatService
  participant B as Run buffer (Redis)
  U->>A: POST /agui/run (Bearer JWT, Accept: text/event-stream)
  A->>A: requireAccess RUNNER
  A->>S: chat(..., stepEvents active)
  S-->>A: tokens + step events
  A->>B: record(event) returns seq
  A-->>U: data: {RUN_STARTED} id: 1
  A-->>U: data: {TEXT_MESSAGE_CONTENT} id: n
  Note over U,A: connection drops
  U->>A: GET /runs/{runId}/events?after=n
  A->>B: readAfter(n)
  A-->>U: buffered events, then live tail

Client guidance

Evidence & references