Skip to main content
Plan-and-Solve synthesizes a structured plan before the orchestrator starts dispatching. ReWOO (Reasoning Without Observation) extends it: the plan uses #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

The PlannerAgent, 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>:
Use .head=N for large tasks (e.g. “given this log #E1.head=500, diagnose”) to bound the context the next step sees.

Complexity heuristic

ComplexityScore(task) → int[0,10] is calibrated to fire Plan-First only when it pays off. The score blends three signals:

Examples

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 FinalReport with 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”:
This mirrors how the orchestrator already reacts to per-agent errors today (continues, summarizes, lets the model decide). The 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.
Recommended flow for large changes: /plan preview <task> first → review the JSON → if approved, run /plan coder <same task>.

Environment variables

Override via persona (CHATCLI_AGENT_PLANNER_*)

PlannerAgent respects the usual per-agent overrides:

Observability

Each run emits structured logs:
And prints a friendly one-liner to the terminal:

Spinner during Plan-First

The “planning structured steps…” spinner shows only during the pure PlannerAgent call (step 1). During PlanRunner execution (step 2), the spinner is intentionally off and a static status line is emitted:
This is critical for safety: steps can trigger interactive approvals (ShellAgent exec, CoderAgent write) via the policy engine. If the spinner stayed active during execution, its \r\033[K repaint would overwrite the approval prompt and block the response. Ctrl+C still cancels normally — the cancelled context propagates through dispatcher and PlanRunner between steps.

Dry-run: what gets rendered

/plan preview <task> renders:
If ParsePlan fails (planner returned malformed JSON or prose), the raw output is printed with a warning — never silent.

When to turn off

Plan-First adds +1 LLM call (the planner) per fired turn. In budget-tight environments, off or threshold=8 save money.

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.