.addNode(name, handler). The handler receives a context object and returns one of three things:
- A plain state update (action node).
- An interrupt signal (pause and ask).
- A widget signal (pause and delegate to a display tool).
Node context
Every handler receives a single argument with this shape:Current flow state. Partial because earlier nodes may not have filled every field yet.
The MCP request
_meta for this tool call. Rarely needed directly. Use waniwani for tracking.Helper to build an interrupt signal. Field keys are typed against the state schema. See Interrupts.
Helper to build a widget signal. Accepts a
RegisteredTool (from createTool()) or its id as a string.A session-scoped tracking client. Present when the MCP server is wrapped with
withWaniwani(). Undefined otherwise.Action nodes
An action node returns a plain state update. The engine deep-merges the update into state and advances along the node’s outgoing edge without pausing.Interrupt nodes
An interrupt node returnsinterrupt(...). The engine stores the graph step, returns status: "interrupt" to the model, and waits for a continue call. When the user answers, the node’s validators run (if any) and the engine advances.
Widget nodes
A widget node returnsshowWidget(tool, config). The engine returns status: "widget" pointing at a display tool (created with createTool()) and the flow pauses. When the user interacts, the host calls the flow back with action: "continue" and whatever values you want stored in stateUpdates.
field enables the same auto-skip behavior as interrupts: if selectedPlan is already filled when the flow reaches this node, the widget is skipped. Setting interactive: false makes the widget display-only and auto-advances after it renders.
The flow itself is a data-only tool. It never returns
structuredContent. The display tool referenced by showWidget is the one that renders the widget. Register both alongside each other with registerTools(server, [displayTool, flow]).Async handlers
Handlers can be sync or async. Most real handlers are async because they touch external services:Naming and uniqueness
Node names are internal identifiers. They appear in Mermaid diagrams (flow.graph()), compile-time validation errors, and error messages at runtime. Use snake_case, keep them short, prefer verbs for actions and ask_* for interrupts.
Node names must be unique within a flow. Adding the same name twice throws at build time. START and END are reserved and cannot be used as node names.