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 validates tenant from the input by calling state.parse(input).

  • Route ids stay parameterized. Run this route as /hello/[tenant] with input for tenant:

    bash
    echo '{"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 tenant required. 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/wait and runs/stream call, so use primitives, plain objects, and arrays.