Skip to main content
The core policy: who authored the plan determines what failure means.

Human-authored plans never replan

When you run a plan — via plan run or as a plan__* tool — you asked for that plan. If a step fails, rewriting your workflow behind your back would be worse than failing, so graph doesn’t. You get a structured, attributable error:
  • plan run: non-zero exit, message on stderr.
  • As a plan tool: an is_error tool result — the agent explains it or works around it visibly (you’ll see any fallback tool calls in the trace).

plan_and_execute replans

Plans authored by the LLM planner are drafts. Defects trigger a replan: the error — including, for bad data references, a digest of the keys that actually exist — feeds back to the planner, which revises the plan. Executed steps are preserved and never re-run; only the broken tail is replaced. After the configured attempts (planning_attempts, default 2), it degrades to an honest error summary instead of raw failure. One idempotency caveat: “executed steps are preserved” is keyed on top-level step results. Steps inside a decide branch or a map/reduce body are scoped to that step, so a control step that fails partway through counts as not executed — a replan that re-authors it re-runs the branch from its first step (or the map from its first item), duplicating any side effects the successful prefix already had. Keep bodies idempotent, or move side-effecting sequences into a plan__* call the body invokes. The same applies to an agent step, and more sharply: its rounds are not top-level results either, so a replan re-runs the whole loop — and because the calls were chosen by the model rather than written in the plan, you cannot tell from the YAML what will be repeated. Give agents read-only tool sets unless you have a reason not to.

The three failure classes

Template resolution errors are typed (details), and each class routes differently: The EmptyData distinction is the one that matters day-to-day: a search returning zero results is an answer (“no active cycle”), not a defect. Solver-mode plans report it gracefully; automation-mode plans surface it as empty_data: true so pipelines can branch on it.

Where validation happens

  1. Load time (automatically on every load, everywhere): YAML shape, template syntax, forward references (E0 referencing E2), id format, control-step inputs (a decide gate’s arity, a map’s body, an agent’s output_schema and budget — inside bodies too), malformed tool names — including workbench__*, which is rejected outright: those tools exist only inside the workbench TUI’s chat agent, never in the plan runtime, so a step naming one is always a defect. This layer is static and context-free.
  2. Catalog resolution (plan validate, plan run, and workbench validation and runs — anywhere the full config context exists): every step tool name, control-step bodies included, is resolved against what is actually loadable — builtin__* against the enabled packs, user__* against [tools].paths, plan__* against the plan catalog (followed transitively through composed plans). An agent step’s tools: patterns are resolved the same way, wildcards included — linear__* requires [mcp.linear], user__* must match at least one loadable user tool — so a plan that names tools which cannot load fails here rather than mid-agent. MCP server__tool names are verified at the server level — the server must be configured under [mcp.*] — because listing a server’s real tools would mean connecting, which validation never does; the individual tool name is still checked at dispatch. An unresolvable tool fails the run before any step executes, so no tool calls or LLM money are spent on a plan that cannot finish. The one soft case: a server that is unconfigured but declared in requires_servers is a note, not an error, when validating a plan file — the file is portable and its dependency is declared — but the plan is hidden from the catalog and running it by name reports the missing servers.
  3. Input time: JSON Schema validation of inputs, before any step runs.
  4. Run time: everything that depends on real data — path existence against actual results, tool errors, emptiness, and MCP tool existence within a verified server.
The design accepts that run-time is where data-shaped errors belong: with MCP tools that don’t declare output schemas, pretending to verify paths statically would be false confidence. Instead, run-time errors are made maximally actionable (key digests) and the shape cache shrinks the unknown over time. Planner-authored plans (plan_and_execute) skip catalog resolution: the planner draws from the live registry, and execution plus replanning already handle its misfires.