One-Line Summary: Adaptive replanning enables agents to revise their execution plan on the fly when reality diverges from expectations, balancing persistence with flexibility.
Prerequisites: plan-then-execute-pattern.md, breaking-complex-tasks-into-steps.md
What Is Adaptive Replanning?
Military strategists say no plan survives first contact with the enemy. The same applies to AI agents. An agent plans to scrape a pricing page, but the page requires authentication. It expects JSON from an API, but gets XML. It plans to use a calculator tool, but the extracted numbers are in a format it cannot parse. The plan was reasonable when created, but reality has introduced information the planner did not have.
Adaptive replanning is the agent's ability to detect when its current plan is no longer viable and to construct a revised plan that accounts for new information. This is distinct from simple error handling (retrying a failed API call) -- replanning means changing the strategy itself. The agent might switch from web scraping to API access, substitute one data source for another, or skip an entire branch of the task tree that has become irrelevant.
The challenge is knowing when to replan. Too eager and the agent wastes time generating new plans after every minor hiccup. Too stubborn and it grinds through a doomed plan, wasting tool calls and tokens. Good replanning strategies define clear triggers and escalation paths.
How It Works
Three Replanning Strategies
Agents use three main approaches to replanning, each suited to different situations:
Reactive Replanning re-evaluates the plan after every step. The agent checks whether the output of the current step matches expectations and adjusts the remaining plan accordingly. This is thorough but expensive.
Triggered Replanning monitors a deviation metric and only replans when it crosses a threshold. For example, if the plan assumed 5 competitors but the search found 12, the deviation is large enough to warrant replanning. Small deviations (a URL slightly different from expected) do not trigger replanning.
Full Replanning discards the current plan entirely and generates a new one from scratch, incorporating all results gathered so far. This is the nuclear option -- expensive but necessary when the original plan's assumptions are fundamentally wrong.
Decision Flow: Replan vs Retry vs Abort
When a step fails or produces unexpected output, the agent follows a decision tree:
Step produced unexpected result
│
├── Is the result usable despite being unexpected?
│ └── YES → Continue with adjusted expectations (no replan)
│
├── Is this a transient error (timeout, rate limit)?
│ └── YES → Retry (max 2 attempts)
│ └── Retry failed → Escalate to replan
│
├── Is there an alternative tool/approach for this step?
│ └── YES → Triggered replan (substitute this step only)
│
├── Does this failure invalidate downstream steps?
│ └── YES → Full replan from current state
│
└── Is the overall goal still achievable?
├── YES → Full replan with remaining budget
└── NO → Abort with partial results and explanationImplementation with State Tracking
The key to adaptive replanning is maintaining rich state that the replanner can use to make informed decisions:
from dataclasses import dataclass, field
from enum import Enum
class StepStatus(Enum):
PENDING = "pending"
SUCCESS = "success"
FAILED = "failed"
SKIPPED = "skipped"
@dataclass
class ExecutionState:
original_goal: str
current_plan: list[dict]
step_results: dict[str, any] = field(default_factory=dict)
step_statuses: dict[str, StepStatus] = field(default_factory=dict)
replan_count: int = 0
max_replans: int = 3
total_tokens_used: int = 0
token_budget: int = 50_000
def completed_steps_summary(self) -> str:
"""Summarize what has been accomplished so far."""
lines = []
for step_id, result in self.step_results.items():
status = self.step_statuses.get(step_id, StepStatus.PENDING)
lines.append(f"Step {step_id} [{status.value}]: {str(result)[:200]}")
return "\n".join(lines)
def can_replan(self) -> bool:
return (
self.replan_count < self.max_replans
and self.total_tokens_used < self.token_budget * 0.8
)The Replanning Prompt
When replanning is triggered, the agent sends the original goal, the completed steps with their results, the failed step with its error, and asks for a revised plan:
REPLAN_PROMPT = """You are a replanning agent. The original plan has encountered
an issue and needs revision.
Original goal: {goal}
Completed steps and results:
{completed_summary}
Failed step: {failed_step}
Error: {error}
Remaining original steps (now potentially invalid):
{remaining_steps}
Generate a revised plan that:
1. Builds on the results already obtained (do NOT repeat completed steps)
2. Works around the failure using alternative approaches
3. Still achieves the original goal or the closest achievable approximation
4. Stays within the remaining budget of {remaining_budget} tokens
If the goal is no longer achievable, set "achievable": false and explain why.
Output as JSON:
{{
"achievable": true,
"revised_plan": [...],
"explanation": "Why the plan was revised"
}}
"""Integrating Replanning into the Execution Loop
async def execute_with_replanning(state: ExecutionState) -> dict:
"""Main execution loop with adaptive replanning."""
step_index = 0
while step_index < len(state.current_plan):
step = state.current_plan[step_index]
step_id = f"step_{step['step']}"
try:
result = await execute_step(step, state.step_results)
state.step_results[step_id] = result
state.step_statuses[step_id] = StepStatus.SUCCESS
# Reactive check: does this result change the plan?
if needs_reactive_replan(step, result, state):
revised = await generate_revised_plan(state, step, result)
state.current_plan = (
state.current_plan[:step_index + 1] + revised
)
state.replan_count += 1
step_index += 1
except ToolError as e:
# Retry once
try:
result = await execute_step(step, state.step_results)
state.step_results[step_id] = result
state.step_statuses[step_id] = StepStatus.SUCCESS
step_index += 1
except ToolError as retry_error:
state.step_statuses[step_id] = StepStatus.FAILED
state.step_results[step_id] = str(retry_error)
if not state.can_replan():
return {"status": "aborted", "results": state.step_results}
revised = await generate_revised_plan(
state, step, retry_error
)
if not revised.get("achievable", True):
return {
"status": "infeasible",
"reason": revised["explanation"],
"partial_results": state.step_results,
}
state.current_plan = (
state.current_plan[:step_index] + revised["revised_plan"]
)
state.replan_count += 1
# Don't increment step_index; retry at current position
return {"status": "complete", "results": state.step_results}Deviation Detection for Triggered Replanning
def needs_reactive_replan(step: dict, result: any, state: ExecutionState) -> bool:
"""Determine if a successful but unexpected result warrants replanning."""
expected = step.get("expected_output", "")
if not expected:
return False
# Ask LLM to assess deviation
assessment = llm.invoke([
SystemMessage(content="Compare the expected and actual output. "
"Rate deviation as LOW, MEDIUM, or HIGH."),
HumanMessage(content=f"Expected: {expected}\nActual: {str(result)[:500]}")
])
return "HIGH" in assessment.contentWhy It Matters
Robustness in Unpredictable Environments
Real-world tool outputs are messy. APIs change, websites go down, data formats vary. An agent without replanning fails catastrophically at the first unexpected result. An agent with replanning degrades gracefully, finding alternative paths to the goal or delivering partial results with clear explanations.
Efficient Resource Usage
Blind persistence wastes tokens and API calls on a doomed plan. Premature abandonment wastes the work already completed. Adaptive replanning finds the middle ground: invest in recovery when the goal is reachable, and cut losses when it is not.
Key Technical Details
- Replanning costs 1000-3000 tokens per invocation; cap at 3 replans per task to control costs
- Track cumulative token usage and reserve 20% of the budget for replanning overhead
- The replanner must receive completed step results to avoid repeating work -- this context is non-negotiable
- Deviation detection via LLM adds ~500ms per step; use heuristic checks first (type mismatch, empty result) before invoking the LLM
- Log every replan trigger, reason, and revised plan for post-hoc analysis and prompt tuning
- Full replanning from scratch should only occur if more than 50% of remaining steps are invalidated
Common Misconceptions
"Replanning means the original plan was bad": Not necessarily. Plans are made with incomplete information. A well-constructed plan that needs revision after discovering new facts is working as designed. The quality of a planning system is measured by how well it handles revisions, not by whether revisions are needed.
"The agent should always try to achieve the original goal exactly": Sometimes the right move is to adjust the goal. If the user asked for pricing data from five competitors but two have no public pricing, the agent should deliver results for three with a clear explanation, not hallucinate data for the missing two.
Connections to Other Concepts
plan-then-execute-pattern.md-- The base pattern that replanning extendsbreaking-complex-tasks-into-steps.md-- Replanning often involves re-decomposing remaining workdependency-graphs-for-skill-execution.md-- Replanning must account for dependency changes when steps are added or removedhuman-in-the-loop-checkpoints.md-- Major replanning decisions can be escalated to the user
Further Reading
- Shinn et al., "Reflexion: Language Agents with Verbal Reinforcement Learning" (2023) -- Self-reflective replanning where the agent critiques its own failures
- Yao et al., "ReAct: Synergizing Reasoning and Acting in Language Models" (2023) -- The interleaved approach that adaptive replanning improves upon
- Brooks, "A Robust Layered Control System for a Mobile Robot" (1986) -- Classic robotics paper on reactive planning that inspired many agent architectures