State

Routes may declare a state contract in a colocated state.ts file. State is the JSON payload passed to the route entry and returned by the route runtime.

The shape

When present, state.ts default-exports a Zod schema or Standard Schema value. Agent routes use it to parse defaults at runtime, and you can derive the TypeScript type via z.infer<typeof state>:

import { z } from "zod"
 
export default z.object({
  /** Accumulated context from tool call results */
  context: z.string().default(""),
})

Dynamic segments are route params

If your route's directory contains a dynamic segment like [tenant], Dawn records that segment in generated route metadata. Today, route execution is input-driven: pass the value in the JSON input you send to dawn run, /runs/wait, or /runs/stream.

text
src/app/(public)/hello/[tenant]/  →  route id /hello/[tenant]

For example:

bash
echo '{"tenant":"acme"}' | dawn run '/hello/[tenant]'

The schema covers state your entry reads or the caller supplies. Do not rely on a concrete pathname with an inline tenant value to populate tenant; the current resolver matches the parameterized route id (/hello/[tenant]) or the route entry file path.

Custom reducers

Each route may include a reducers/ directory with one file per state field. The default export is a (current, incoming) => merged function that overrides Dawn's default merge for that field.

src/app/(public)/hello/[tenant]/reducers/context.ts
export default (current: string, incoming: string) =>
  current ? `${current}\n${incoming}` : incoming

The file basename must equal the state-field name (context.ts → reduces state.context). Reducers are useful for accumulation patterns (concatenating logs, summing counters, deduplicating lists) where the default object spread is too coarse.

Rules

  1. 1

    State must be JSON-serializable

    Dawn serializes state across runtime boundaries (dev server, scenario tests, LangSmith). Use primitives, plain objects, arrays. No classes, no Dates, no Maps.

  2. 2

    Prefer readonly

    Mark fields readonly (or use Zod's .readonly()) when they should not be mutated. The entry function returns a new state object rather than mutating the input. This keeps scenario tests deterministic and LangGraph checkpoint-safe.

  3. 3

    Outputs accumulate

    A workflow can return { ...state, newField } when you want to preserve incoming state. Dawn returns the route output; it does not automatically merge workflow output with the input for you.

State flow

State crosses the runtime boundary at every entry point:

  • dawn run — JSON via stdin, JSON via stdout.
  • /runs/wait and /runs/stream (locally under dawn dev, on LangSmith in prod) — JSON over HTTP keyed by assistant_id.

Tool results, intermediate values, and other in-route data live inside the entry for the duration of a single run; only the route result crosses the boundary.

Related