What helps:
- One-shot planning — force the model to write a plan in iter 1, not interleave plan+exec
- Plan → approval → execute as a 3-phase state machine
- Pre-formatted tools with examples baked in
- Failure-recovery templates — "if X happens, do Y"
- Context summarization at the start so the model doesn't lose the thread
- Deterministic progress tracking — server tracks "plan steps done", not the model
What hurts (what we have now):
- Asking the model to decide everything per iteration
- Same-tool-3x detection (catches but doesn't help)
- "Be decisive" prompts (model can't follow)
- Interrupting mid-loop to ask permission
Prompt: "Given the task, write a numbered plan. Each step is one tool call. The plan must be COMPLETE — you'll execute it without further input. Don't ask questions, just plan."
Output:
## Plan
1. read_files_for_context(paths=[X, Y]) — get both files
2. write_file(X, ...) — add useState
3. write_file(Y, ...) — add :root.dark
4. run_command("npm run build")
5. Report
Chat shows the plan in a structured block with [Approve] [Refine] [Cancel] buttons. Operator clicks one. No LLM call here.
The server interprets the plan and executes each step. Between steps:
- If a step fails, server decides: retry, skip, or escalate to operator
- If a step needs more info than the plan covers, server injects a "you forgot X, add it" and continues
- Operator sees progress: "Step 2/5: write_file ChatPanel.tsx ✓"
The LLM is ONLY called when:
- The plan needs adjustment
- A step's output needs interpretation
- Generating the final Report
This means a weak model can plan OK (it's just numbered steps), the deterministic loop executes them reliably, and the model is only consulted for judgment calls.
When a task is "do X to Y", the chat enters plan mode:
1. LLM call: generate plan
2. Show plan with approval buttons
3. Operator clicks Approve
4. Server starts executing the plan
5. Server shows live progress (each step ✓/✗)
6. When done, LLM call: generate Report
7. Done
async function executePlan(plan, ctx) {
for (const step of plan.steps) {
ctx.setStatus(`Step ${step.i}/${plan.steps.length}: ${step.description}`);
try {
const result = await executeTool(step.tool, step.args);
if (!result.success && !step.optional) {
// Failure handling
const decision = await handleFailure(step, result, ctx);
if (decision === 'escalate') {
return { status: 'escalated', failedStep: step, error: result.output };
}
if (decision === 'retry') {
// try once more
}
if (decision === 'skip') {
continue;
}
}
ctx.markStepDone(step.i, result);
} catch (e) {
// catastrophic — escalate
return { status: 'escalated', error: e.message };
}
}
return { status: 'complete' };
}
read_file not found → try the alternative path the model mentionedwrite_file 403 path-not-allowed → try with the parent dirrun_command non-zero exit → read stderr, retry with --force or different flagReplace the chat bubble-per-tool-call with a step-by-step progress card:
📋 Plan: Add dark mode
✓ 1/5 read_files_for_context (got 1200 lines)
✓ 2/5 write_file ChatPanel.tsx
✓ 3/5 write_file styles.css
⟳ 4/5 run_command npm run build
⏸ 5/5 Report
When given a task, you have two modes:
- CHAT mode: short conversational reply, no tool calls
- PLAN mode: when the operator asks you to DO something (add, fix, build, deploy, create)
In PLAN mode, you must output a plan in this EXACT format:
```plan
{
"summary": "Short summary in 1 sentence",
"steps": [
{ "i": 1, "description": "Read both files", "tool": "read_files_for_context", "args": { "paths": [...] } },
{ "i": 2, "description": "Write ChatPanel.tsx", "tool": "write_file", "args": { "path": "...", "content": "..." } },
...
],
"risks": ["things that could go wrong"]
}
Don't include any text before or after the plan block. The plan will be reviewed and approved before execution.
After the operator approves, you DO NOT make any more tool calls — the server executes the plan. You only respond when the server asks you a question OR when the plan completes and you write the Report.
```
Phase 1: Build plan mode scaffolding (this week)
- Add plan-mode system prompt
- Add Plan UI component (plan card with Approve/Refine/Cancel)
- Add plan executor (deterministic)
- Add progress UI
Phase 2: Test with the dark mode prompt (next session)
- Should complete in 1 plan, 5 steps, 1 Report
- No interruptions, no loops, no 18-message mess
Phase 3: Add failure recovery
- Path-not-found retry
- Exit-code heuristics
- Escalation when truly stuck