Why architecture matters here

Agents fail on the plan, not on the individual step. A step-by-step call to an LLM is straightforward; recognizing that Step 3 needs a different tool because Step 2 returned unexpected data is where good planners earn their keep.

The architecture matters because the loop must be observable. Without a state store, the agent forgets what it decided. Without reflection, it cannot correct. Without replan, it charges ahead when off-course. Without budgets, it never stops.

Build the loop and the agent gets much smaller — the LLM contributes one piece of judgment at a time within a reliable structure.

Advertisement

The architecture: every piece explained

The top strip is the forward path. Goal captures user intent explicitly with acceptance criteria. Planner LLM produces a high-level plan (natural language + structured steps). Decomposer breaks steps into concrete actions with dependencies. Executor invokes tools or delegates to subagents, one step at a time.

The middle row is the control loop. State store holds task board (planned/in-progress/done), facts learned so far, and current beliefs. Reflection evaluates recent progress against the plan — did the step succeed, did we get new information? Replan revises the plan when reflection reveals a gap. Completion check asks whether the goal is satisfied and returns to the user with results.

The lower rows are hygiene. Memory + summarization compacts context so long agents stay within token limits. Guardrails + policy enforce that every action passes safety checks — the governance plane from a separate article. Observability logs the plan, every step, the reflection outputs, and cost.

Agent planner — goal → plan → decompose → execute → reflect → replanthe loop that separates agents from chatbotsGoaluser intentPlanner LLMhigh-level planDecomposersteps + depsExecutortool + subagentState storetask board + factsReflectionevaluate progressReplanrevise planCompletion checkgoal satisfied?Memory + summarizationkeep context boundedGuardrails + policysafe action within limitsObservability — plan trace + step-by-step audit + budget usagerecordcheckadaptfinishcompactverifyverifyobserveobserve
Agent planner loop with reflection and safety.
Advertisement

End-to-end flow

End-to-end: a user asks an agent to "triage this bug report and propose a fix." Planner LLM outputs plan: 1) read the report, 2) find the relevant code, 3) reproduce, 4) diagnose, 5) draft fix. Decomposer maps to concrete tool calls. Executor runs step 1 (read repo); state store records key facts. Step 2 grep — one relevant file. Step 3 reproduce — test fails as expected. Step 4 diagnose — LLM analyzes and identifies a null check missing. Reflection: yes, this matches the report. Step 5 draft fix — LLM outputs a patch. Completion check confirms all steps done and returns the patch to the user. Observability shows total 5 tool calls, 12k tokens, $0.14 cost.

What the planner consumes: the goal contract

A planner is a function, and it fails at the input boundary far more often than it fails at reasoning. Four things belong in the call.

The goal, restated with acceptance criteria. Not the raw user turn - a normalised goal plus the explicit conditions that make it satisfied. Forcing the planner to emit acceptance criteria as a first-class field before any steps is the highest-leverage change available: it gives the completion check something to test against instead of letting "done" mean "the model stopped".

The tool catalog, by exact name and schema, pinned to a version. A planner shown prose capability descriptions rather than the literal names and schemas the executor accepts will confidently emit send_slack_message when the runtime only exposes notify_channel. Pin the catalog version into the plan header so a mid-run change is detectable rather than mysterious.

Constraints. Budget, deadline, which side effects are allowed without approval. A planner that does not know the budget cannot trade breadth for depth, and will emit a forty-step plan for a job priced at twelve.

Known world state. Facts already established - files read, IDs resolved, prior results - so the plan does not re-derive what the run knows. The anti-pattern is passing the whole transcript as "context": the planner then plans around the dialogue rather than the goal, and plan structure becomes unstable turn to turn.

Plan shape: flat list, graph, or hierarchy

The representation is not cosmetic. It sets an upper bound on how surgically the plan can be repaired later, because you can only re-expand a subtree if the plan has subtrees.

ShapeWhat it buysRepair granularity
Flat ordered listTrivial to generate, validate, and show to a userTwo moves only: patch a step, or discard the plan
DAG with declared dependenciesExposes independence so the scheduler can fan out - mechanics in task decompositionReplan affected descendants; other branches survive
Hierarchical (goal, subgoals, steps)Defers detail: expand a subgoal only when its turn arrivesRe-expand one subgoal, leaving the rest untouched

Whether to commit to a plan at all reduces to one criterion: commit up front only when the step set is knowable before the first observation. Goals whose next action genuinely depends on what the last one returned - exploratory research, debugging, anything driven by search results - belong in an interleaved reasoning-and-acting loop, covered in ReAct, with framework-level mode selection in ADK planning and the ADK-Java planner. The hybrid that survives production is a coarse plan of three to seven subgoals, each executed by an interleaved loop, revised only at subgoal boundaries.

One trap: do not put control flow in the plan unless the executor implements it. A step reading "if step 3 returns nothing, do X instead" is a silent no-op against an executor that walks a list, and no test catches it because the plan reads correctly to a human.

Validating the plan before a single tool fires

Every plan should pass a deterministic static check before execution starts. It costs microseconds and converts the most expensive failure mode - discovering at step 9 that step 10 was never runnable - into an instant, repairable error.

def validate(plan, catalog, budget):
    errs, produced = [], set()
    for i, s in enumerate(plan["steps"]):
        tool = catalog.get(s["tool"])
        if tool is None:                            # hallucinated tool
            errs.append("step %d: unknown tool" % i); continue
        errs += typecheck(s["args"], tool.schema)   # args vs JSON schema
        for ref in refs_in(s["args"]):              # ${step_2.result}
            if ref not in produced:                 # forward / dangling ref
                errs.append("step %d: reads undefined %s" % (i, ref))
        if tool.side_effecting and not s.get("approval_gate"):
            errs.append("step %d: unapproved side effect" % i)
        produced.add("step_%d.result" % i)
    if has_cycle(plan):                errs.append("dependency cycle")
    if est_cost(plan, catalog) > budget: errs.append("over budget")
    if not covers(plan["steps"][-1], plan["acceptance_criteria"]):
        errs.append("terminal step produces no acceptance artifact")
    return errs

In rough order of how often they fire: unknown tool name, arguments that fail to typecheck once placeholders are substituted, a step reading an output no earlier step produces, a dependency cycle, a side-effecting step with no approval gate (where the planner meets guardrails), cost over budget, and a terminal step producing nothing that matches the acceptance criteria.

On failure, return the error list to the planner as a repair prompt rather than to the user - one round fixes most schema and reference errors, because the model is told exactly what is wrong in its own vocabulary. Cap repairs at two or three: a planner that cannot produce a valid plan by the third attempt rarely has a reasoning problem, it has a catalog problem, and no re-prompting conjures a tool that does not exist.

Replanning: triggers, staleness, and loop guards

Replanning goes wrong when it is treated as an exception handler. It is a scheduled evaluation with named triggers.

Triggers worth wiring explicitly

A step fails terminally after its retry policy is exhausted. A precondition evaluates false at dispatch. A tool result contradicts a fact the plan assumed. Projected cost or wall-clock for the remaining steps breaches the budget. The catalog version changed mid-run. A verifier rejects a step result. Each is a different signal deserving a different response - collapsing them into "something broke, replan everything" is what produces runaway cost.

Detecting that a plan has gone stale

Staleness is not failure. A plan is stale when an assumption it recorded is no longer true, even though every step so far succeeded. Make assumptions explicit at plan time: each step carries the fact keys it reads and the preconditions it expects. Before dispatch, the executor re-evaluates those preconditions and compares a hash of the referenced facts against their values when the plan was built. A mismatch means the remaining suffix should be re-derived. The check is nearly free and catches the quiet failure where an agent flawlessly executes a plan for last Tuesday.

Prefer the narrowest edit, then stop

Order responses by blast radius: substitute one step's arguments, re-expand a single subgoal, replan only the unexecuted suffix, and only as a last resort replan from the goal. Preserving completed work needs durable step results, the concern of agent checkpointing - a planner that discards finished results on every replan is a cost bug wearing a correctness costume.

Then guard the loop. The classic runaway: the plan calls tool X, X is unavailable, the planner replans, the new plan calls X again - forever, because nothing ever wrote "X is unavailable" into the facts the planner reads. The primary fix is that failures must feed back as facts, not merely as retry counters. Around that, enforce a hard replan budget per run (three to five is typical) and require each new plan to differ structurally from its predecessor: an identical multiset of step signatures is oscillation, not adaptation. A monotonic-progress rule helps - satisfied acceptance criteria must never decrease across a replan. When the guards trip, hand off rather than iterate; see human-in-the-loop.