Dev Server

dawn dev starts a local runtime with Agent Protocol (AP) HTTP endpoints. It also exposes an AG-UI SSE endpoint for web clients such as CopilotKit. It watches route and tool files, regenerates types when signatures change, and restarts the child route runtime for structural changes.

Starting the server

text
dawn dev

By default the server binds an ephemeral localhost port (announced on stdout as Dawn dev ready at http://127.0.0.1:<port>). Pass --port <n> for a stable port:

text
dawn dev --port 3001

The bind address is always 127.0.0.1.

Invoking a route

With the server running in one terminal, you can hit it from another:

text
echo '{"messages":[{"role":"user","content":"Hello"}]}' | dawn run '/research' --url http://127.0.0.1:3001

dawn run resolves the route id to a route key of the form <routeId>#<kind> (e.g. /research#agent), POSTs to /threads/<t-cli-uuid>/runs/wait with {route, input}, and prints the result.

Agent Protocol endpoints

The dev server exposes eight endpoints organized around a thread lifecycle: create thread → run (wait or stream) → read state → resume. Anything else returns 404. Thread state persists in SQLite under .dawn/ and survives server restarts — see Configuration for the checkpointer and threadsStore defaults and override options.

Thread lifecycle with curl

The following sequence creates a thread, runs a route, and reads the final state:

bash
# 1. Create a thread (body is optional)
curl -X POST http://127.0.0.1:3001/threads \
  -H 'content-type: application/json' \
  -d '{}'
# -> { "thread_id": "...", "created_at": "...", "updated_at": "...", "metadata": {}, "status": "idle" }
 
THREAD_ID="<thread_id from above>"
 
# 2. Run the route (blocking)
curl -X POST http://127.0.0.1:3001/threads/$THREAD_ID/runs/wait \
  -H 'content-type: application/json' \
  -d '{
    "route": "/research#agent",
    "input": { "messages": [{ "role": "user", "content": "What is LangGraph?" }] }
  }'
# -> final state JSON
 
# 3. Read the latest checkpoint state
curl http://127.0.0.1:3001/threads/$THREAD_ID/state

Readiness check. Bypasses middleware.

text
GET /healthz
-> 200 { "status": "ready" }

Returns 200 once the child runtime is up; non-200 means not-ready. dawn dev itself uses this endpoint internally to detect readiness.

The route format <routeId>#<kind> is the same format dawn build writes into the graphs map of .dawn/build/langgraph.json, so the same client request body works against dawn dev and against a deployed runtime.

AG-UI endpoint

For UI clients that speak AG-UI, dawn dev also serves:

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

The body must be an AG-UI RunAgentInput. Dawn creates the thread when the incoming threadId is new, maps the newest user message to the route input, streams the route, and translates Dawn chunks into AG-UI events with @dawn-ai/ag-ui.

The URL segment after /agui/ is the URL-encoded Dawn assistant id (<routeId>#<kind>):

text
Dawn route key: /chat#agent
AG-UI URL:      /agui/%2Fchat%23agent

The endpoint emits RUN_STARTED, assistant text events, tool call/result events, and one terminal RUN_FINISHED or RUN_ERROR. A parked run uses the standard AG-UI interrupt outcome on RUN_FINISHED; successful runs use the success outcome. Planning and subagent capability events have no v1 AG-UI mapping and are ignored.

Human-in-the-loop answers use the top-level AG-UI RunAgentInput.resume array. Every answer is addressed to one pending interrupt:

json
{
  "threadId": "thread-1",
  "runId": "run-2",
  "messages": [],
  "tools": [],
  "context": [],
  "state": {},
  "resume": [
    { "interruptId": "perm-abc123", "status": "resolved", "payload": "once" }
  ]
}

The chat example includes a CopilotKit v2 client wired through this endpoint; see examples/chat/web and the @dawn-ai/ag-ui package README.

Tracing

If LANGSMITH_API_KEY is present in the loaded environment and LANGCHAIN_TRACING_V2 is not already set, dawn dev automatically sets LANGCHAIN_TRACING_V2=true. Set LANGCHAIN_PROJECT to name the LangSmith project used for traces. See Observability for a full walkthrough, including how to read traces and the HITL interrupt gotcha.

Middleware

src/middleware.ts (default-exporting a function returned by defineMiddleware) gates Agent Protocol /runs/stream, /runs/wait, and /resume execution plus AG-UI route execution under both dawn dev and the built runtime served by dawn start. Thread create, read, delete, and state endpoints do not invoke middleware. It can short-circuit execution with reject(status, body?), or continue with allow(context?) — the optional context flows to every tool as ctx.middleware (a Readonly<Record<string, unknown>>).

MiddlewareRequest shape: { assistantId, headers, method, params, routeId, url }. See Middleware for the full reference.

Hot reload

When you save a file under src/app/, the dev server:

  1. 1

    Reclassifies the change

    Typegen-only changes (tool signatures, state schemas) trigger a debounced typegen run (~100ms) — the child does not restart. Structural changes restart the child.

  2. 2

    Restarts the child runtime when needed

    Dawn uses a parent-child process architecture. The parent owns the HTTP server and file watcher; the child owns the route graph. On a structural change the child is stop()ed (with a forced kill timeout — in-flight requests are given a brief grace window, then force-killed) and a fresh child is started.

  3. 3

    Preserves route ids

    Routes keep their stable ids (<routeId>#<kind>) across restarts, so any agent or harness holding a route key continues to work without reconnection.

Logging

The parent process keeps the HTTP server alive while child route runtimes restart. Set DAWN_DEV_SHUTDOWN_TIMEOUT_MS to override the child-restart grace window.

Related