#E1, #E2 placeholders resolved at runtime with the outputs of previous steps. Combined, they avoid the expensive pattern of “dispatch an agent, wait, think again, dispatch another”.
When it fires:
/plan <task> forces it; CHATCLI_QUALITY_PLAN_FIRST_MODE=always always; auto (default) triggers when ComplexityScore(task) >= 6. To review the plan before executing, use /plan preview <task> (dry-run).Plan format
ThePlannerAgent, when it receives the PlannerStructuredOutputDirective marker at the start of the task, emits strict JSON:
ReWOO placeholders
Any step can inject the output of earlier steps via#E<n>:
Complexity heuristic
ComplexityScore(task) → int[0,10] is calibrated to fire Plan-First only when it pays off. The score blends three signals:
Examples
- Trivial (score 1)
- Multi-action (score 6+)
- PT-BR (score 6+)
auto does not fire Plan-First. Orchestrator handles directly.Execution flow
1
User fires /agent or /coder
The task goes into
AgentMode.Run().2
runPlanFirstIfApplicable
Checks
cli.pendingPlanFirst (one-shot) or quality.ShouldPlanFirst(cfg, userQuery).3
Planner dispatch
agentDispatcher.Dispatch([{Agent: planner, Task: PlannerStructuredOutputDirective + userQuery}]).4
ParsePlan
Tolerant to markdown code fences and trailing prose. Validates: unique IDs, deps point to earlier steps,
#E<n> placeholders point to declared IDs.5
TopologicalOrder + Execute
Stable topological order (lex sort on ties) guarantees reproducibility. Each step: resolve placeholders → dispatcher.Dispatch → store output.
6
Inject report
Two synthetic messages go into
cli.history:- assistant: the plan JSON (the model sees what was attempted)
- user: deterministic
FinalReportwith task/agent/status/output per step + handoff
The second message is role=user (not
system) since the April 2026 fix. Models like Claude Sonnet 4.6 refuse completion when the conversation ends on assistant (“This model does not support assistant message prefill”). The synthetic user turn closes the conversation correctly and gives the orchestrator an explicit anchor to finalize.7
ReAct loop continues (or is skipped, if dry-run)
With the report in history, the orchestrator finalizes without re-executing completed steps. In dry-run mode (
/plan preview), the loop is skipped via planDryRunHandled — no orchestrator call is made.Fault tolerance
A failing step does not abort the run. The behavior is “continue downstream with substituted error”:HadErrors flag is set so Reflexion can decide to escalate.
/plan — manual invocation
/plan accepts six forms. Autocomplete (Tab) offers the subcommands.
Behavior matrix
The
cli.pendingPlanFirst flag is consumed and cleared on the first subsequent /agent or /coder invocation. The cli.pendingPlanDryRun flag (exclusive to preview/dry modes) is cleared at the same point and makes AgentMode.Run return before the ReAct loop via planDryRunHandled.
Environment variables
Override via persona (CHATCLI_AGENT_PLANNER_*)
PlannerAgent respects the usual per-agent overrides:
Observability
Each run emits structured logs:Spinner during Plan-First
The “planning structured steps…” spinner shows only during the purePlannerAgent call (step 1). During PlanRunner execution (step 2), the spinner is intentionally off and a static status line is emitted:
Dry-run: what gets rendered
/plan preview <task> renders:
ParsePlan fails (planner returned malformed JSON or prose), the raw output is printed with a warning — never silent.
When to turn off
See also
#3 Reflexion
Reflexion consumes
HadErrors from the plan runner to generate lessons when steps fail.Multi-Agent Orchestration
The dispatcher that PlanRunner reuses is the same as the standard orchestrator.
PlannerAgent (pre-PR)
The agent existed before the pipeline — this pattern formalizes how to invoke it deterministically.
Full configuration
All env vars and slashes in one place.