State
Every route has a state type declared in a colocated state.ts file. State is the contract between the caller, the route's entry, and any tools the entry invokes.
The shape
// src/app/(public)/hello/[tenant]/state.ts
export interface HelloState {
readonly tenant: string
readonly greeting?: string
}
HelloState is imported into index.ts and used as the first parameter of the workflow function (or the equivalent graph state type).
import type { HelloState } from "./state.js"
export async function workflow(state: HelloState, ctx) {
// state.tenant is string
// state.greeting is string | undefined
}
Dynamic segments become state fields
If your route's directory contains a dynamic segment like [tenant], that segment's value is populated on the state field of the same name at runtime.
src/app/(public)/hello/[tenant]/ → state.tenant populated from the pathname
Rules
- 1
State must be JSON-serializable
Dawn serializes state across runtime boundaries (dev server, scenario tests, LangGraph Platform). Use primitives, plain objects, arrays. No classes, no Dates, no Maps.
- 2
Prefer readonly
Mark fields
readonly. The entry function returns a new state object rather than mutating the input. This keeps scenario tests deterministic and LangGraph checkpoint-safe. - 3
Outputs accumulate
A workflow returns
{ ...state, newField }— merging new outputs into the existing state. Fields added during the run are typed as optional (greeting?: string) so the caller isn't forced to provide them.
State flow
caller stdin → state → workflow/graph/chain → new state → stdout
↓
ctx.tools
The state is the only thing that crosses the runtime boundary. Everything else — tool results, intermediate values — lives inside the entry for the duration of a single run.