The plan pipeline
Plans execute as: validate → run steps sequentially → finish.- Step inputs render against prior results via the template language — typed dataflow, zero inference.
- Seven control steps are intercepted by the executor, never dispatched to a tool:
exitends the plan early,decideforks into one of two branches,filterpartitions a list with a per-item gate,map/reducerun a body once per item of a list,agentruns a bounded tool-calling loop that returns schema-conforming JSON, andaskputs a question to the person running the plan.agentis the one control step that costs inference per round;agent,ask, andfilterare the three legal inside another control step’s body (a nested filter’s{{item}}/{{index}}shadow the enclosing body’s within its gate). All defer rendering: adeciderenders only its gate up front and the non-taken branch never renders;filterrenders onlyoverup front and its gate per item;map/reducerender onlyoverup front and the body per item. Gate evaluations and body calls appear as ordinary flat tool events in traces.mapis the pipeline’s one concurrency point — itsconcurrencyknob runs independent items in parallel; everything else is sequential. - The finish is a
solverLLM call, anoutputrender (no LLM), or nothing (finish modes). - For
plan_and_execute, aplanner-role call authors the plan first, and defects loop back as replans (executed steps preserved). Human-authored plans skip planning and never replan. - Interactive callers can install an execution gate — a hook consulted before every real tool dispatch (registry tools,
plan__*,plan_and_execute, and body calls at any nesting depth) that can proceed, skip the call with an injected result, or abort the run. The gate sees the call’s fully rendered input plus the template scope it rendered against (the results map at the top level; the layered body scope withitem/index/accumulatorinside bodies). A second hook,on_tool_error, is consulted when a dispatched call fails: it can let the error propagate (the default — identical to ungated behavior), replace the error with a substitute value the run continues with (never entering the replan loop), or abort. Event ordering:tool_finishedalways reports the real call;step_finishedreports the resolution. Aborts are hard stops: no replan, no solver, no error summary — the partial run state comes back, and when the abort was triggered by a failing tool the failing tool’s error rides along with it (so interactive callers can show why the step failed), and nested-plan aborts propagate without being re-asked. Control-step evaluation (exit/decide gates, filter verdicts, map/reduce orchestration) is never gated. The plan workbench’s debug runs are built on this. - The gate is the out-of-band half of interactivity: an outside observer interrupting a run it did not plan. The in-band half is an interlocutor — a hook the
askstep uses to put a question to a human and bind the answer to a step id. Hosts implement it differently (a terminal prompt on stderr, the workbench’s answer editor, an MCPelicitation/createrequest back to the client) and a host that cannot reach anyone simply installs none. Because whether a human is reachable is a property of the host and not of the plan, eachaskstep declares its own unattended behaviour (when_unanswered), which is what lets one plan run interactively and in CI. Like every control step, anaskis never gated — it makes no tool call — and questions are serialized even under concurrentmapitems. - Sinks observe runs through result-carrying step events: every step (and body call) reports its rendered input when it starts and its full result value when it finishes, addressed by a step path (
E3,E3/then,E3/do.2/E10) plus the plan call stack.
The agent loop
ask and chat run a tool-calling loop with the chat-role model:
max_agent_iterations. Tool failures return into the loop as error results — the agent explains or works around them visibly.
The anatomy of a chat turn using a plan
plan run invokes the pipeline directly: one inference (solver) or zero (output/silent).
Cost table
Model roles
Every inference site resolves through[models] role assignment (chat, planner, solver, repair, judge — falling back to default), so cost tuning is pure config: strong model where judgment lives, fast model where volume lives. Named models extend the fixed roles with user-defined entries selectable at the point of use — per-step model routing without touching the role assignments. Structured outputs (planner, prompt tools) get one automatic repair-role fix-up attempt before erroring into the replan loop. The full map of roles, providers, and failover is Models & providers.