Skip to main content
This page covers the state schema: the typed fields a flow collects. A flow also needs a place to persist that state between MCP calls. See Persistence, which is required for any flow to work.

State schema

Every flow declares state as a map of field names to Zod schemas. The runtime state type is inferred from this map, so ctx.state is fully typed inside every node. Field descriptions also feed the model’s tool description, so it knows what to collect.
Inside a node:
Always add .describe() to every field. It is what the model reads when phrasing questions and deciding which values it can pre-fill. The most load-bearing piece of documentation your flow ships with.

State is always partial

At any point during execution, only the fields filled by earlier nodes are populated. The engine types ctx.state as Partial<TState>, so always guard when reading fields.

Field types

Prefer constrained types. The more constrained a field, the better the model fills it.

Enums pair well with suggestions

An interrupt that lists the same values as suggestions will lead the model to offer exactly those and validate the answer against the enum.

Nested state

Use z.object() when a sub-entity has multiple fields. Only one level of nesting is supported.
Interrupts target nested fields with dot-paths:
Action nodes that return nested objects are deep-merged, so returning { driver: { name: "John" } } does not wipe driver.licenseNumber.
driver.address.city will not work. Flatten deeper hierarchies or handle the sub-object in one question that fills the whole z.object() at once.

Pre-filling from the user’s message

When the user’s opening message already contains answers (for example, “I’m in 75011, quote me a half-day lesson”), the model can pass them in stateUpdates on the very first call:
The engine auto-skips any node whose target field is already filled (values of undefined, null, or "" do not count as filled). No extra configuration is needed. Just make sure field descriptions are clear enough for the model to map message content to field names.

How state updates happen

Nodes update state in three places:
  1. Action nodes return a plain object that is deep-merged into state.
  2. Interrupts store the user’s answer at the field key used in interrupt({ field: { question } }).
  3. Interrupt validators (see Interrupts) can return additional state updates after a user answer passes validation.
All three paths are typed against the state schema.