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. Each field’s description travels with the question that asks for it, as its fieldSchema, so the model knows what it is collecting. The state schema itself never appears in the tool listing.
Inside a node:
Always add .describe() to every field. The model reads it when phrasing a question and when matching something the user already said to the field it’s being asked for. 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.

Answers in the user’s opening message

When the user’s opening message already contains answers (for example, “I’m in 75011, quote me a half-day lesson”), the model sends each one as the flow asks for it. The question for postalCode comes back with its fieldSchema, and the model answers it from that message without asking the user:
No extra configuration is needed. Write field descriptions clear enough for the model to map what the user said to the field it’s being asked for. A call writes only fields the run has asked for. A field a node computes for itself, like a resolved id or a price, is never asked, so the conversation can’t set it. See What a call can write.

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.