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
POST /agui/{routeId}
content-type: application/json
accept: text/event-streamThe 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.
browser
-> CopilotKit runtime
-> HttpAgent -> POST /agui/%2Fchat%23agent
-> Dawn route runtime
-> AG-UI event streamCopilotKit'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:
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:
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 chunk | AG-UI event(s) |
|---|---|
| stream start | RUN_STARTED |
token | TEXT_MESSAGE_START once, then TEXT_MESSAGE_CONTENT per delta |
tool_call | close open text, then TOOL_CALL_START, TOOL_CALL_ARGS, TOOL_CALL_END |
tool_result | close open text, then TOOL_CALL_RESULT |
root plan_update | replacement ACTIVITY_SNAPSHOT with activity type dawn.plan |
subagent.start and matching child plan/tool/result/end chunks | replacement ACTIVITY_SNAPSHOT with activity type dawn.subagent |
interrupt | terminal RUN_FINISHED with outcome.type: "interrupt" |
done | terminal RUN_FINISHED with outcome.type: "success" |
| upstream error | terminal 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:
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.