← Blog

Loop engineering: designing the system that drives the agent

The model is only half the system. The other half is the loop around it, observe, plan, act, verify, retry, stop, and the guardrails that keep it honest.

Pick any two agent systems built on the same model. One will be reliable, auditable, and safe to run in production. The other will hallucinate tool calls, loop indefinitely, and occasionally delete something it shouldn’t. The difference is almost never the model. It’s the loop.

The loop is the harness around the model: the code that decides when to call the model, what to feed it, what to do with the output, and when to stop. Engineers spend a lot of time picking models and almost no time designing the loop. That’s the wrong ratio.

What a control loop actually does

An agent’s control loop is a state machine with a small set of transitions. At each turn it must answer four questions: what is the current state of the world, what should happen next, did the last action succeed, and is the goal reached?

loop:
  state   = observe(environment, history, budget)
  plan    = model(state, goal, available_tools)
  action  = select_tool(plan)          # may be no-op or "done"
  result  = execute(action)            # may fail, timeout, or be denied
  verdict = verify(result, goal)

  if verdict == GOAL_MET:   return success
  if verdict == BLOCKED:    return escalate
  if verdict == RETRYABLE:  backoff(); continue
  if budget.exhausted():    return fail_safe
  continue

This is not pseudocode you would ship verbatim, but it captures the shape of every non-trivial agent. The interesting engineering lives in each of those six functions, not in the model call sitting in the middle.

The six functions worth designing carefully

Observe is where context is assembled. What resources does the agent have access to? What has it already tried? What’s its remaining budget, in tokens, tool calls, time, or dollars? A loop that feeds the model stale context or omits its own history is a loop that will thrash.

Plan is the model call. Give it a clean state description, the goal, and the list of tools that are currently available. Note currently available: the tool list should reflect what policy permits at this moment, not a static manifest dumped at session start.

Select is where the loop enforces that the model’s intent maps to a real, permitted action. An LLM might express an intent to “send the report.” Select must resolve that to a concrete tool with validated, typed parameters, or reject it. This is where schema validation happens, not inside the model.

Execute is the actual side effect. It may fail with a recoverable error, a hard constraint violation, or a timeout. The loop must distinguish these: a rate-limit is retryable, a policy denial is not.

Verify compares the result to the goal. Not “did the tool call return 200?” but “did the world change in the way the goal requires?” This is harder to write but essential, without it the loop can’t distinguish success from the appearance of success.

Budget management is the stop condition. Every production loop needs an explicit ceiling on cost, time, and re-attempts. Agents without stop conditions are not agents; they are runaway processes.

Retry and backoff are a design decision

Retry logic is where a lot of unreliable agent behavior originates. A simple “retry up to N times” strategy will happily replay a destructive action N times if the error is misclassified. Every retryable category needs its own policy: what counts as the same error, whether to replay with the same parameters or modified ones, and how long to wait. Exponential backoff with jitter is table stakes; knowing which error classes are safe to retry at all is the harder judgment.

Where the loop and the capability layer meet

The observe/select/execute triad maps cleanly onto the capability layer in an MCP-first system. Observe reads from declared resources. Select resolves plans against typed tools with risk metadata. Execute fires through the same policy engine that governs every other consumer of the system.

What the loop enforces at each step
  • Observe reads from declared resources, respects scope
  • Select validates against typed tool schemas + risk level
  • Execute passes through policy checks and confirmation gates
  • Verify compares result against goal, not just status code
  • Retry classifies errors; never replays forbidden actions
  • Stop budget ceiling enforced by the harness, not the model

This matters for safety. If the loop calls tools directly, bypassing the capability layer, then an agent with a loose system prompt can do anything the underlying service allows. If execute is always mediated by the capability layer, the agent can only do what policy permits, regardless of what the model planned.

The loop becomes the enforcement surface. Confirmation gates prevent irreversible actions without explicit approval. The audit trail records every tool call with its parameters and result. Risk metadata signals when a planned action crosses a threshold that should pause the loop and escalate to a human.

Reliability is a harness property

A well-designed loop gives you reliability properties that no amount of prompt engineering can match. Prompts are suggestions. The loop is code. It either enforces a budget or it doesn’t. It either validates tool parameters or it doesn’t. It either checks for a confirmation gate before calling a destructive tool or it doesn’t.

The model contributes reasoning quality. The loop contributes correctness guarantees. You need both, but only one of them is yours to write.

The model reasons. The loop decides whether to trust that reasoning, and what to do when it’s wrong.

The real leverage