AG-UI and Web Clients

Dawn exposes AG-UI for browser clients alongside Agent Protocol. @dawn-ai/ag-ui is the pure translation library: it maps Dawn stream chunks to AG-UI events and maps RunAgentInput back to a Dawn-shaped run input. The package owns no server, transport, or agent runtime.

dawn dev and the production runtime started by dawn start use that adapter to serve an SSE endpoint. This page is the canonical request, resume, threading, and lifecycle guide. See Middleware for execution gating and Security Architecture for the service routes that require outer authentication.

The endpoint

http
POST /agui/{routeId}
content-type: application/json
accept: text/event-stream

The URL segment is the URL-encoded <routeId>#<kind> assistant id. For example, /chat#agent becomes /agui/%2Fchat%23agent. The request body is an AG-UI RunAgentInput. Dawn creates an unknown thread, invokes the same route runtime used by Agent Protocol, and streams translated events back as SSE.

Consuming it from a web UI

examples/chat/web is the canonical reference client. Its CopilotKit runtime registers an HttpAgent pointed at Dawn's encoded route URL. The browser talks to the Next.js runtime route, while only the Dawn server holds model credentials.

text
browser
  -> CopilotKit runtime
    -> HttpAgent -> POST /agui/%2Fchat%23agent
      -> Dawn route runtime
        -> AG-UI event stream

CopilotKit's sidebar uses the literal agent id default when none is supplied, so the example registers the Dawn route under that key. See examples/chat/web/README.md for the complete setup and smoke checklist. That basic client registers no activity renderers. For plan and researcher cards, use the activity-aware research recipe and its examples/research/web implementation.

Adapter API

The root package exports the transport-independent mapping surface:

ts
import { fromRunAgentInput, toAguiEvents } from "@dawn-ai/ag-ui"
 
const dawnInput = fromRunAgentInput(runAgentInput)
 
for await (const event of toAguiEvents(dawnChunks, { threadId, runId })) {
  // Send `event` through the transport chosen by the application.
}

Transport helpers are isolated behind subpaths. An SSE server can encode an event without expanding the root API:

ts
import { encodeAgUiSse } from "@dawn-ai/ag-ui/sse"
 
response.write(encodeAgUiSse(event, request.headers.accept))

Outbound events

toAguiEvents(chunks, context) is a stateful async generator. It frames Dawn's implicit assistant text and preserves upstream tool-call ids for result correlation.

Dawn chunkAG-UI event(s)
stream startRUN_STARTED
tokenTEXT_MESSAGE_START once, then TEXT_MESSAGE_CONTENT per delta
tool_callclose open text, then TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END
tool_resultclose open text, then TOOL_CALL_RESULT
root plan_updatereplacement ACTIVITY_SNAPSHOT with activity type dawn.plan
subagent.start and matching child plan/tool/result/end chunksreplacement ACTIVITY_SNAPSHOT with activity type dawn.subagent
interruptterminal RUN_FINISHED with outcome.type: "interrupt"
doneterminal RUN_FINISHED with outcome.type: "success"
upstream errorterminal RUN_ERROR

Activity snapshots

Activities use standard AG-UI framing and complete replacement snapshots. A root plan has stable message id dawn:plan:${runId}, activity type dawn.plan, and content containing only the complete todo list. Todo status is exactly pending, in_progress, or completed. Dawn emits no seeded-plan activity at run start; the first snapshot follows a valid root plan_update.

Each subagent has stable message id dawn:subagent:${call_id}, activity type dawn.subagent, and complete content with its name, positive integer depth, running/completed/failed status, an optional current todo list, up to five recent child-tool name/status summaries, and the total observed tool count. Tool-summary status is exactly running, completed, or incomplete. A failed activity can also contain a human-readable error capped at 400 characters. Because every event has replace: true, later snapshots replace the same stable activity message.

The adapter validates the full internal identity { call_id, subagent, route_id, depth } on every recognized child event. Only an exact match with the original subagent.start can update its activity; call_id is used only to form the stable standard message id, while route_id and child tool ids remain internal. None is duplicated in public content. subagent.message is consumed without emission.

This boundary excludes child reasoning and prose, prompts, tool inputs, tool outputs, final child answers, route ids, and raw runtime ids. It is an explicit allowlist rather than a generic capability mapping: unknown capability chunks retain the existing behavior of closing any open text message and then being ignored. Dawn emits neither activity deltas nor a raw advanced child stream. Activities are informational; standard interrupt UI remains the only place to resolve or cancel permission requests. No queued, waiting, cancelled, or parent-task correlation state is inferred.

Inbound input

fromRunAgentInput(input) translates every AG-UI message and preserves the original request as raw, where consumers can inspect tools, state, and context. The CLI endpoint forwards only the newest user message because Dawn owns the checkpointed conversation for the thread.

The standard top-level RunAgentInput.resume array is preserved as vocabulary-neutral Dawn resume requests:

ts
resume?: Array<{
  interruptId: string
  status: "resolved" | "cancelled"
  payload?: unknown
}>

For a permission prompt, a resolved payload can carry a Dawn decision such as "once" or "always"; cancelling the interrupt maps to denial at the runtime boundary. Every answer must address one currently pending interrupt.

Threading

AG-UI's threadId is the Dawn thread id; there is no separate mapping table. AG-UI and Agent Protocol requests execute the same route code and use the same thread store and checkpointer. A caller can inspect state through Agent Protocol while a UI drives later turns through AG-UI.

They also share the same process-local one-active-run-per-thread gate. An ordinary run-slot collision—an AG-UI turn or Agent Protocol run that reaches an occupied slot—returns 409 with error.details.code set to run_in_flight. A second concurrent resume is stopped by the shared resume claim before it reaches the run registry and returns resume_in_progress. Shared durable stores do not distribute that coordination across replicas.

Middleware and service authentication

Dawn execution middleware gates AG-UI route execution just as it gates Agent Protocol execution. It can reject the request or pass context into authored tools. It is not service-wide authentication: thread management, state, cancellation, memory-candidate management, and health routes bypass it. See Middleware for the request contract and Security Architecture before exposing the runtime outside a trusted local environment.

Disconnect and reconnect

Agent Protocol viewer disconnects continue the run. AG-UI viewer disconnects abort the run. For Agent Protocol, closing a runs/stream, runs/wait, or resume connection only detaches the viewer, so stop the run explicitly with POST /threads/:thread_id/cancel. Server shutdown also aborts in-flight work.