#AI#AI Agent

Agent Loop Design: Stopping, Reflection, Branching

An Agent is a loop at its core, but whether that loop is well-designed decides if it is intelligent or just thrashing. This article covers the key design points of the Agent loop: stop conditions (when to end), self-reflection (self-assess after each step), branching and backtracking, max steps and budgets, and adding "human checkpoints" to intercept

Key Points

  • Topic Focus: AI、AI Agent
  • Suitable for: indie hackers, full-stack engineers, AI application developers
  • Core value: quickly understand background, practical paths, common pitfalls, and engineering advice
Author @admin·Published July 26, 2026·Updated July 26, 2026

When many people write their first Agent, they finish and think "that's it?": wrap a while True, stuff in a prompt, add a few tool calls, and watch it search docs, edit code, and call APIs on its own—it does feel a bit magical. But you soon hit three classes of meltdown: it can't stop (burning money), it goes off track yet thinks it's doing great (looking dumb), or it DROPs the production database (an incident).

The root of these problems is not the model; it's the loop. Half of an Agent's "intelligence" lives inside that loop—when to stop, when to reflect, when to switch paths, when to call a human—these engineering decisions determine whether the same model ends up "usable" or "a disaster."

This article breaks down several key design points of the Agent loop, all traps I've stepped on in production.

1. The essence of the loop: think → act → observe

The simplest Agent loop is called ReAct (Reason + Act), with three phases rotating:

text
┌─────────────────────────────────────────────┐
│   Thought (think): what should I do now?     │
│        ↓                                     │
│   Action (act): call some tool               │
│        ↓                                     │
│   Observation (observe): get the tool result │
│        ↓                                     │
│   back to Thought, until the task is done    │
└─────────────────────────────────────────────┘

The pseudocode looks like this:

python
state = {"messages": [system, user]}
while True:
    msg = llm(state["messages"])
    state["messages"].append(msg)
    if msg.done:              # stop condition
        break
    for call in msg.tool_calls:
        result = run_tool(call)
        state["messages"].append(result)

It looks like ten lines, but every branch check is a design decision. A loop ready for production must clearly answer: when to stop? what if it's wrong? what about a dead end? what if the budget blows out? who blocks dangerous actions? If these five questions are not answered well, the Agent is an unstable bomb.

2. Stop conditions: when to call it

Stop conditions are the most easily overlooked and most money-burning part of loop design. I've seen too many Agents spin forever because they "don't know they're done": endlessly double-checking, endlessly "optimizing," or flipping back and forth between two states.

A robust Agent needs at least five stop signals, related by OR (any one firing stops it):

  1. Task-completion signal: the model explicitly outputs done=true / calls a finish tool / emits an agreed-upon stop token. This is "active stopping."
  2. Max-steps cap: max_steps=30, a hard backstop. The first line of "passive stopping."
  3. Budget cap: time (e.g., 10 minutes) or tokens (e.g., 200k) exceeded stops it. More precise than steps, because some steps burn tokens without burning steps.
  4. User interrupt: a "stop" button on the UI injects a cancel signal into the loop.
  5. Infinite-loop detection: the same action repeats for N consecutive steps, or state stays unchanged for a long time, so it actively breaks.

Many people skip infinite-loop detection, but I strongly recommend adding it. A simple, effective implementation:

python
recent_actions = state["recent_actions"][-5:]
if len(set(hash(a) for a in recent_actions)) == 1:
    # 5 identical steps in a row, break
    raise LoopDetected("agent stuck")

A more aggressive version looks at "state similarity": if the vectorized content of observations across 10 consecutive steps has cosine similarity > 0.95, it's basically spinning in place.

One opinion: a stop condition is not "add one and you're done," it's layered backstops. The completion signal is graceful exit; the rest are safety latches. An Agent without safety latches will, sooner or later, burn through a whole night's budget on some corner case.

3. Self-reflection: let the Agent self-assess "did I do that right?"

In the original ReAct paper, the Agent only moves forward and never looks back. But when humans solve hard problems, there's a key move: finish a step, pause, and ask "was that step right? is it enough? should I change anything?" That's self-reflection.

The empirical evidence is solid: on complex tasks like SWE-bench and WebArena, Agents with self-reflection see success rates rise 10–30%. The reason is LLMs frequently get one-shot planning wrong (especially multi-step tasks), but "looking back" often lets them spot where they went wrong—judging is easier than generating.

In implementation, reflection can be inserted at two granularities:

  • Per-step reflection: after every action, add a light self-assessment round.
  • Stage reflection: after completing a sub-goal (e.g., "fixed one file" "ran the test suite once"), do a deeper retrospective.

The prompt structure for reflection is roughly:

text
Your recent goal: <goal>
Your actions over these steps: <actions>
Observed results: <observations>
Please judge:
1. Is the goal achieved? Give a confidence level.
2. Did you take any wrong turns along the way? If so, where?
3. If not done yet, what's the best next step? Do you need to backtrack?

The product of reflection is not "a paragraph of feelings," but a structured decision: continue / backtrack / stop. That decision is written back into state and drives the loop.

text
Action → Observation → Reflect →
   ├─ continue: enter next Thought
   ├─ backtrack: return to some history node and switch paths
   └─ stop: task done or judged unsolvable

One opinion: more reflection is not always better. Per-step reflection notably increases tokens and latency, and is pure waste on simple tasks. My practice is—only turn reflection on when the expected step count is > 5, or tool results are highly uncertain (e.g., browser automation, external APIs); for simple one-shot query tasks, reflection is a negative optimization. Also, reflection should use a different prompt role (have it "play the reviewer"), otherwise the model tends to affirm itself and miss its own errors.

4. Branching and backtracking: tree-search-style Agent

ReAct goes down one path to the end, and when wrong, can only be yanked back through reflection. But some tasks (e.g., complex debugging, multi-solution exploration) naturally need "try a path, switch if it doesn't work"—this is the tree-search-style Agent.

The core idea: model the Agent's exploration as a tree, where each node is a state and each action is an edge. After taking an edge fails, backtrack to the fork and pick another edge.

text
           root
          /    \
       search-A  search-B
        / \         |
     ok   fail    ok  ← back to root, pick search-B, continue

There are three implementation essentials:

  1. State snapshot: each node stores a recoverable state (not just messages, but file changes, env vars, and other side-effect points).
  2. Scoring function: score each branch (using a small LLM, or rule-based heuristics) to decide which to expand first.
  3. Backtrack mechanism: on failure, be able to "undo" side effects—this is the hardest part. File edits can be rolled back (git is a natural state snapshot), but external API calls (emails sent, orders submitted) can't be undone, and such operations should never be placed inside backtrackable branches.
python
def explore(node, budget):
    if node.is_goal() or budget <= 0:
        return node
    for action in node.possible_actions():
        child = node.step(action)
        result = explore(child, budget - 1)
        if result.solved:
            return result
        # failed, record the reason, try the next action
    return node  # all branches failed

One opinion: tree-search Agents look stunning on public benchmarks, but use them cautiously in production. Two reasons: first, the cost is huge (the same problem may explore multiple paths, doubling tokens); second, side-effect rollback is nearly impossible to do cleanly in a real system. My advice is to use them only on pure information-space tasks (e.g., "find the answer across multiple docs" "generate several options for the user to pick"); for tasks with real-world side effects, stick to linear + reflection.

5. Budget and caps: three gates against runaway

An Agent loop with no caps is essentially a "token-metered infinite loop." LLM calls are three orders of magnitude more expensive than traditional loops; leave it alone for a few hours and it can burn a month of API quota.

Runaway control needs at least three gates, from loose to tight:

GateGranularityRole
Step capCoarsePrevents the Agent from spinning forever inside a task; easiest to implement
Time capMediumPrevents getting stuck on a slow tool (e.g., scraping an unresponsive page)
Token capFineMost precise cost control, maps directly to money

A key design point for the budget: the budget is shared globally, not one per tool. Otherwise the Agent will blow the entire budget on one tool. I usually keep a Budget object and debit every LLM call and tool call from it:

python
class Budget:
    def __init__(self, max_steps, max_seconds, max_tokens):
        self.steps = max_steps
        self.deadline = time.time() + max_seconds
        self.tokens = max_tokens

    def spend(self, steps=1, tokens=0):
        self.steps -= steps
        self.tokens -= tokens
        if self.steps <= 0 or self.tokens <= 0:
            raise BudgetExceeded()
        if time.time() > self.deadline:
            raise Timeout()

When over budget, graceful degradation beats a hard error—tell the Agent "you have 5% of the budget left, wrap up as soon as possible," and it can usually summarize current progress for the user within one step, instead of leaving a half-finished result.

6. Human checkpoints: intercept irreversible operations

The most dangerous moment for an Agent is the step where it executes an irreversible operation: rm -rf, DROP TABLE, send email, push commit, make a payment, call a production write endpoint. Once done, these can't be taken back, and LLMs are inherently prone to "confidently making mistakes."

The solution is human-in-the-loop: before such actions, force a checkpoint that pauses the loop and waits for a person to confirm.

python
DANGEROUS_TOOLS = {"delete_file", "run_migration", "send_email", "deploy"}

while True:
    msg = llm(state)
    for call in msg.tool_calls:
        if call.name in DANGEROUS_TOOLS:
            approval = await request_human_approval(call)
            if not approval:
                state.append({"role": "tool",
                              "content": "User rejected this action, please pick another approach"})
                continue
        result = run_tool(call)

Several engineering details decide whether it's usable:

  • Risk tiering: not every action needs to interrupt. Don't block read_file, block delete_file, and deploy_to_prod not only blocks but needs a second confirmation. Tag each tool with risk_level.
  • Sufficient context: at approval time don't just throw out "about to run delete_file, agree?"; show the approver the Agent's full reasoning chain (why delete, which file, what happens after), otherwise the human is just clicking blindly.
  • Timeout policy: if the human doesn't respond in 10 minutes, default deny or default allow? I strongly recommend default deny—better to stall than to auto-release.
  • Batchability: if a task needs to delete 50 files, confirming one by one will drive people crazy. Offer "blanket authorization for the same kind of operation within this task," but bound the scope (same session, same tool category, same directory).

One opinion: checkpoints don't "reduce Agent autonomy," they "expand the scope of work an Agent can be authorized to do." An Agent with checkpoints can be trusted to plug into production; one without is only dared to play in a sandbox. Checkpoints are the bridge an Agent must cross to go from demo to production.

7. Loop engineering: persistence, tracing, concurrency

Finally, three "unsexy but decide whether you can ship" engineering issues.

State persistence: in long-running loops (tens of minutes, across hours), the process can die at any time—OOM, network jitter, user closing the browser. Loop state must persist to disk and be recoverable. Serialize state to JSON in Redis/DB, checkpoint each step, and resume from the last step on restart. This is the watershed between Agent-as-toy and Agent-as-product.

python
state = load_or_init()
while not done(state):
    state = step(state)
    save(state)   # persist each step

Tracing and observability: when an Agent errs, you need to see the full reasoning chain—each step's Thought, which tool was called, what parameters were passed, what came back. This is the lifeblood of debugging. Send structured events for each step to a trace system (LangSmith, Langfuse, Arize Phoenix all work), threaded into a tree by run_id. An Agent without tracing is a black box, and a black box can't be operated.

Concurrency and parallel attempts: in tree search I mentioned "try multiple branches in parallel"; in engineering this relies on asyncio / thread pools to run multiple branches at once. But note: only pure functional, side-effect-free branches can run in parallel (e.g., letting two ideas search docs at once); parallelism over a shared environment (the same filesystem, the same DB) only causes them to trample each other. The complexity parallelism brings (result merging, resource contention, error handling) usually exceeds the payoff—default to serial, go parallel only when there's a clear benefit.

Summary

  1. The essence of the loop is think → act → observe, but "spinning" is only the passing grade. What decides whether an Agent is good is the judgment inside the loop: when to stop, when to reflect, when to switch paths.
  2. Stop conditions must be layered backstops. Completion signal + max steps + budget cap + user interrupt + infinite-loop detection, five gates OR'd together, none optional.
  3. Reflection significantly boosts success rate on complex tasks, but use a different role and look at the scenario; turning reflection on for simple tasks is a negative optimization.
  4. The three budget gates (steps/time/tokens) map to three runaway modes, must be shared globally, and must degrade gracefully.
  5. Human checkpoints are the standard for irreversible operations, not a reduction in autonomy but an expansion of the scope of work an Agent can be authorized to do.

Half of an Agent's intelligence is in the model, half in the loop. The model sets the ceiling, the loop sets the floor—and in production, the floor is the line that decides life or death.

Related

Related Content