AgentPrep
Domain 03

Plan Mode and Iterative Refinement

Not every task needs a plan. The skill is recognizing when planning pays off and when it just slows you down — and then knowing which refinement techniques drive a task to a good result.

When to use plan mode

SituationWhy plan
Complex tasksMultiple interdependent steps
Large-scale changesTouch many files or modules
Architectural decisionsTrade-offs that need evaluation
Multi-file refactoringRisk of inconsistencies between files

Example: “Migrate authentication from a custom JWT scheme to OAuth2 with refresh tokens.” That warrants a plan because it ripples through middleware, routes, models, tests, configuration, and possibly the frontend.

When to use direct execution

SituationWhy go direct
Simple changesOne file, clear intent
Well-defined bug fixesYou know exactly what to change
Single-file changesNo cross-file dependencies
Mechanical tasksRenaming, formatting, import ordering

Example: “Fix the typo on line 42 of config.ts.” No plan needed — it is an atomic, unambiguous correction.

Plan mode enables safe exploration

The real value of plan mode is that Claude can explore without committing changes. It reads files, analyzes dependencies, and proposes a strategy before touching any code. If the plan is unconvincing, you adjust it without having modified anything — which prevents costly rework.

The Explore subagent

For heavy investigation, use an explore subagent that isolates verbose discovery:

---
context: fork
allowed-tools: ["Read", "Grep", "Glob"]
argument-hint: "topic or area of the codebase to explore"
---
# Explore codebase
1. Use Grep to locate relevant files
2. Read the main files
3. Trace data flows and dependencies
4. Return a structured summary of the findings

With context: fork, all the file reads and searches stay contained. Only the final summary reaches the main conversation, protecting the context window during multi-phase work.

Combining plan and direct execution

The most effective pattern for medium-sized tasks separates thinking from doing:

1. Plan mode -> investigation and design
   "Analyze how the current cache system is implemented"
   -> Claude explores, reads files, proposes a plan
2. Direct execution -> implementation
   "Implement step 1 of the plan: create the CacheProvider interface"
   -> Claude carries out the concrete change

Splitting the two phases reduces errors: you validate the design before any code is written.

Communicate transformations with examples

When prose can be read more than one way, concrete input/output examples remove the ambiguity. Instead of describing a transformation in words:

Avoid: "Transform the user data so the name is capitalized and the email is normalized"

give a worked example:

Input:
{ "name": "juan perez", "email": "JUAN@EXAMPLE.COM" }
Expected output:
{ "name": "Juan Perez", "email": "juan@example.com" }

Concrete cases pin down exactly which transformation to apply. Adding a few more examples clarifies the edge cases:

Input:  { "name": "", "email": "test@test.com" }
Output: { "name": "", "email": "test@test.com" }
// an empty name stays empty, not capitalized

Input:  { "name": "Maria Jose", "email": "MJ@example.com" }
Output: { "name": "Maria Jose", "email": "mj@example.com" }
// an already-capitalized name is left as is; email is always lowercased

Test-driven iteration

A highly effective development pattern is to write the tests first, then iterate by sharing the failures:

Step 1: "Write tests for a discount service that:
         - applies 10% to purchases over $100
         - applies 20% to purchases over $500
         - does not discount purchases of $100 or less
         - rejects negative amounts"
Step 2: "Run the tests"
         -> 4 tests failing (nothing implemented yet)
Step 3: "Implement the service so the tests pass"
         -> 3 passing, 1 failing
Step 4: "The negative-amount test fails: it expected an error but got 0. Fix it."
         -> 4 passing

It works because tests define expected behavior unambiguously, failures tell Claude precisely what is missing, each iteration is a short focused cycle, and the finished result has guaranteed coverage.

Share failures, not descriptions

Vague reports slow things down. Paste the actual failure instead:

Avoid: "The discount test isn't working"

Prefer: "The test fails with:
  Expected: Error('Amount cannot be negative')
  Received: 0
  At: src/services/discount.test.ts:34"

The exact error message hands Claude everything it needs to fix the problem.

The interview pattern

Rather than dumping every instruction up front, let Claude surface the questions:

User: "I need to implement a notifications system"
Claude: "Before I implement, I need to understand a few things:
1. Which channels? (email, push, SMS, in-app)
2. Are notifications real-time or batched?
3. Predefined templates or dynamic content?
4. Do you need retries on delivery failures?
5. Are there user preferences (opt-out per channel)?"

This is useful for ambiguous or incomplete requirements, design decisions with several valid options, complex features where details matter, and cases where you know the “what” but have not settled the “how.” Skip it for simple tasks with clear instructions, bug fixes with a clear reproduction, or when you already provided all the details.

Batching issues

How you group problems depends on whether they interact.

All issues in one message — appropriate when the issues affect each other:

"Fix these 3 issues in the UserForm component:
1. The validation message doesn't clear when the field is corrected
2. The submit button is enabled before all fields are valid
3. The form state doesn't reset after a successful submit"
-> Claude sees all 3 together and can build a coherent solution
   that doesn't conflict between fixes.

Sequential issues, one at a time — appropriate when the issues are independent:

Message 1: "Add lazy loading to catalog images"
-> Claude implements, you verify
Message 2: "Change the dashboard date format to DD/MM/YYYY"
-> Claude implements, you verify

The decision rule: if fixing issue A might affect the solution to issue B, send them together; if they are fully independent, send them one at a time so you can verify each.

Field Notes

  • Reach for plan mode on complex, multi-file, or architectural work; use direct execution for simple, well-scoped changes.
  • Plan mode explores without committing changes, so you can revise the design before any code is touched.
  • Concrete input/output examples communicate transformations far more reliably than prose.
  • Write tests first and iterate by sharing exact failure messages, not vague descriptions.
  • Batch interacting issues into one message; send independent issues sequentially so each can be verified.
← Back to domain 3