Typed State
A state.ts file gives a route typed state. Here's a /hello/[tenant] workflow whose state includes its tenant:
import { z } from "zod"
export default z.object({
/** Tenant supplied in the run input */
tenant: z.string(),
/** Accumulated tool-call results */
context: z.string().default(""),
/** Number of records returned by the lookup tool */
count: z.number().default(0),
})NotesCopy link to section: Notes
-
Include route values in the schema.
[tenant]is part of the route id. The workflow validatestenantfrom the input by callingstate.parse(input). -
Route ids stay parameterized. Run this route as
/hello/[tenant]with input fortenant:bashecho '{"tenant":"acme"}' | b4 run '/hello/[tenant]'For an Agent Protocol run, send the same input in the body:
json{ "route": "/hello/[tenant]#workflow", "input": { "tenant": "acme" } }B4.run fills route state from the input, never from a concrete path like
/hello/acme. -
Parse workflow input yourself. A workflow route receives its input as is. Calling
state.parse(input)validates it and fills in fields declared with.default(...)when the caller leaves them out. -
This workflow imports and parses the schema directly, so it can keep
tenantrequired. For agent-state discovery, B4.run validates{}to extract defaults. If you reuse this schema for an agent route, make every top-level field accept missing input, or discovery skips the schema. -
State is JSON-serializable. It's serialized on every
runs/waitandruns/streamcall, so use primitives, plain objects, and arrays.