project_status — a real plan — from scratch. The workflow generalizes: probe the tools, chain the steps, shape the finish.
1. Probe the tools you’ll use
Before writing any step, learn each tool’s input schema and actual output shape:{{Ex.path}} references, and every probe feeds the shape cache.
2. Header and inputs
plan run exits with code 3 and prints the schema; in chat, the agent receives the error, asks the user, and re-calls with complete inputs.
3. Steps
Steps run sequentially; each input may reference plan inputs and any earlier step. Tool names are namespaced catalog names, plus the bare control steps —exit, decide, map/reduce, agent, and ask — and plan_and_execute, the reserved planner tool.
Step ids are identifiers — letters, digits, and _, not starting with a digit — unique across the plan (body sub-steps included). The planner’s E0, E1, … convention and descriptive names like fetch_project are equally valid. Ids may not shadow the reserved template roots input, item, index, accumulator, or length.
- One deep reference, reused. Only
{{E0.projects.0.name}}reaches into a result; if the project isn’t found, that produces one clean “empty data” outcome instead of four different failures. - Types survive templating.
limit: 5stays an integer; a string that is exactly one tag splices the raw JSON value. See Template language. - Steps are LLM-free. However many steps you add, execution costs zero inference.
4. The finish
Choose how the plan ends — a solver report, structured output, or silence (details):datais what the solver sees — whole results spliced by template. Oversized payloads are automatically truncated/sampled.query_to_answeris itself a template: conditional sections (like the milestones block above) adapt the instructions to the data.- The tighter the output spec, the more consistent the report run-over-run. For strict formats, write the structure as a grammar — see
sprint_analysisin the cookbook.
5. Validate and run
plan validate / plan run additionally resolve every step tool against what is actually loadable, refusing to start before any step executes when a tool can’t resolve. What no static layer can catch — a path that doesn’t exist in a tool’s real output — surfaces at run time as a typed error with the available keys listed. The full layering, including how requires_servers declarations behave, is in where validation happens.
For interactive authoring, the plan workbench wraps this whole loop in a TUI: the chat agent drafts into a side pane, validation runs on every change, and a gated run pauses before each tool call so you can test a plan before its writes are trustworthy.
The authoring loop in one screen: the draft's step tree, its validation verdict, and the agent that edits it.
Authoring from the command line
The same loop runs as individual commands — useful in scripts, and the surface a plan-managing agent drives. Each command applies one edit to the file and writes it back; there is no session to hold open.graph plan draft "<goal>" is available and mirrors the workbench’s drafting (per-step validation, salvage on exhaustion), but it is the only authoring command that costs inference. When you already know the steps — or an agent does — building them directly is free, deterministic, and reviewable as a diff.
Two properties make this safe to script against:
- Edits can only improve things. An edit is rejected if it introduces a validation problem, and a rejected edit leaves the file untouched — so
step rmfails when a later step still references the step, naming the template that would dangle. Problems that were already there never block an edit, which is what keeps a half-built plan (a freshplan newhas no steps yet) editable. - Nothing is clobbered silently. A write refuses a file that holds a different plan, and changing
identifierwrites a new file rather than overwriting the old one.
--json to any of these for a machine-readable envelope — including on rejection, where the problem list is what you want most. See the scripting contract.