← Back ← Back

VibeChat Autonomy Plan (2026-07-20)

What the operator wants

Scaffolding principles for weak models

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

New architecture

Phase 1: PLAN (single LLM call, no tools)

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

Phase 2: APPROVAL (operator)

Chat shows the plan in a structured block with [Approve] [Refine] [Cancel] buttons. Operator clicks one. No LLM call here.

Phase 3: EXECUTE (deterministic state machine, NOT an LLM loop)

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.

Concrete changes

1. New "plan mode" entry point

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

2. Plan executor (deterministic)

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' };
}

3. Failure recovery (server-side heuristics)

4. Progress UI

Replace 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

5. Plan-mode system prompt

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.
```

Why this works for weak models

  1. Planning is easier than execution. A weak model can write "1. read X, 2. write Y, 3. build" reliably. It CANNOT do this when interleaved with execution.
  2. Tool calls become data, not decisions. Once the plan is approved, the tool calls are JSON. The server runs them. The model doesn't have to "remember" to do step 2.
  3. Failures are deterministic. A weak model loops when it sees a failure. The server can retry / skip / escalate based on simple rules.
  4. Context stays clean. The model only sees the plan, the result of each step (summarized), and the final outcome. No 18-message-long mess.
  5. Approval gates the cost. Operator sees the whole plan before any tools run. No "I just wrote 5 files, are you sure?"

Migration plan

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

Expected outcome