DEV Community

Cover image for AI Agents in Practice — Part 6: Building the Production Agent Loop
Gursharan Singh
Gursharan Singh

Posted on Edited on Originally published at aiinpracticehub.com

AI Agents in Practice — Part 6: Building the Production Agent Loop

Part 6 of 8 — AI Agents in Practice

Previous — Workflow, Agent, or Single LLM Call — How to Decide (Part 5)

The demo loop is not enough

Part 3 showed how the loop works: observe, decide, act, check, repeat. Part 5 helped you decide whether you needed that loop at all, or whether a workflow or a single call would do the job for less.

Say you have made that decision. The task genuinely needs an agent: the next step has to be chosen at runtime, inside boundaries you set. Now you have to build it, and the gap between an agent that works in a demo and one that holds up in production is wider than the loop diagram suggests.

In a demo, the loop is enough: the model observes, decides, calls a tool, sees a result, continues. In production, the same five words have to carry more weight. A production agent is not just a model and some tools. It is a loop wrapped in the things that keep the loop honest: working state that survives across turns, tool contracts that say what each call promises, a procedure for recurring work, an approval boundary in front of the actions that warrant one, a verification step that confirms an action actually landed, a budget and stop rules that bound the cost of going wrong, and a trace that records what happened.

This article focuses on the shape of a production loop, not on a framework or platform. It uses one running example to show where a demo implementation needs to become stricter.

The loop still holds

Here is the loop from Part 3, unchanged:

observe → decide → act → check → repeat

Nothing about that changes in production. What changes is what each word contains.

In a demo, observe is "read the last tool result." In production, observe reads from working state, not just the conversation. In a demo, act is "call the tool." In production, act calls a tool with a contract and an idempotency key. In a demo, check is "did the tool return something." In production, check sometimes has to ask a harder question: did the world actually change the way the tool said it did? A 200 OK can mean "request accepted," not "done." Strictly, 202 Accepted is the HTTP status defined for a request accepted but not yet completed. In practice, production APIs also return 200 OK with an application status such as accepted, and the agent has to honor the contract of the API it actually receives. For an action like a refund, that gap matters.

That last one is the heart of this article. But the shape of the loop is exactly what it was three parts ago.

The loop did not change. The implementation got stricter.

A small build: cancel, then refund

Take one concrete task. A TechNova customer writes in about order #4482: they want to cancel it and get their money back. The order is still awaiting shipment, so the cancellation is legitimate. The agent's job is to cancel the order and issue the refund.

The naive loop is short. The agent calls cancel_order, gets back 200 OK, and calls issue_refund. Two actions, one after the other, both successful. In a demo, this works every time.

In production, this is a real risk, and it often stays invisible until it fails in a costly way.

The problem is what this API's 200 OK actually guarantees. It does not guarantee that the order is cancelled. In TechNova's contract, the response says the cancellation request was accepted. Those are not the same statement. Between accepted and cancelled, a number of things can happen: the cancellation might be queued behind other work and still pending. It might have been accepted by one service and not yet propagated to the one that owns refunds. It might have hit a retry and partially applied. It might be sitting in a fraud-review hold the agent cannot see. The response told you about the request you made. It did not tell you about the state of the world that resulted.

So the naive loop issues a refund against an order that may not actually be cancelled yet. Now you have moved money on the basis of an action that had not landed. That is the failure this article is built around, and it is worth stating as a principle:

A tool response describes the request, not necessarily the world. For an irreversible action, the agent must confirm the world before it acts on the assumption that the action succeeded.

We'll fix this later, when we tighten the loop. For now, hold the puzzle: the agent believed a 200 OK and acted on it, and belief is not confirmation. The diagram below shows TechNova's API contract: an HTTP 200 OK carrying an application-level accepted status.

Cancel-Then-Refund: Naive vs Safe Path

The production agent architecture map

Before fixing the loop, it helps to see what this agent needs around it. The demo has a model and a tool. The production agent has more, and each piece earns its place.

Production agent architecture map showing the loop surrounded by working state, skills, tools, gates, limits, and trace records. Tool calls cross into an authoritative application boundary that revalidates preconditions, enforces rules at write time, and owns the final world state.

The map has these parts:

  • User request / input: the task that enters the loop.
  • Working state: what the loop knows between steps, separate from the conversation text.
  • Model decision: the step where the agent chooses the next action.
  • Tool registry: the set of tools the agent is allowed to call. Not "any function." It is a scoped set. Scoping the menu the model sees is not the same as scoping what the systems behind those tools will permit; that boundary is Part 8.
  • Tool schemas / contracts: for each tool, what it takes, what it returns, how it fails, and how to verify it worked.
  • Authoritative application: the system that owns the state the agent is acting on. It revalidates preconditions and enforces the rule at write time. It is not part of the agent; it is what the agent's tool calls reach.
  • Skill / procedure: packaged knowledge of how a recurring task should be done.
  • Approval gate: a boundary in front of the actions a risk policy says need a person, where a human signs off on intent.
  • Verification gate: the step that confirms an action landed before the loop continues.
  • Stop rules / budget: limits on steps, cost, time, and silence.
  • Trace log: a record of every step, for the agent now and for diagnosis later.
  • Final response: what goes back to the customer.

None of these pieces is unusual, but each one does a specific job. The difference between a demo agent and a production one is almost entirely in this surrounding structure, not in the model. We will not build all of these boxes in code. We will examine the ones the cancel-then-refund case actually exercises (tool contracts, the verification gate, and state) and describe the rest. Approval is part of the production design discussed here, but the companion lab demonstrates verification and runtime control and does not implement an approval gate. Do not read the repo as evidence that the human gate is exercised.

Note what is not on the map: this build never needs external reference knowledge (policy rules, product docs, anything it would have to retrieve), so there is no knowledge-retrieval (RAG) component here. A task that needed that reference material would add RAG alongside the tools; this one does not, and leaving it out is the point. The map shows what this agent uses, not everything an agent could.

Tool contracts

In a demo, a tool is often just a function with a one-line description in the prompt: "cancels an order." That is enough for the model to call it. It is not enough to call it safely.

A production tool needs a contract. Not necessarily a heavyweight spec. But at minimum, for every tool, it answers six questions:

  • When to use: the name and description the model decides against. What the tool is for, and when not to reach for it. The remaining fields protect what happens after the model decides.
  • Input shape: what arguments, in what form. (order_id, an idempotency_key.)
  • Output shape: what comes back on success, structurally. (A status, an identifier: something machine-checkable, not free text.)
  • Failure modes: what the tool does when it cannot do the thing. (Does it raise? Return an error status? Time out silently?)
  • Idempotency expectation: is calling it twice safe? For anything that moves money or mutates state, the contract should require an idempotency key so a retry does not double-apply. Two things about that key matter. First, its shape: a bare order_id is too weak, because the same order can carry more than one legitimate intent. Scope the key to the operation, not the resource (something like intent, action, and resource combined, or an identifier minted once for the run). Second, who owns it: the model may propose the operation, the runtime preserves that operation's identity across retries, and the backend enforces idempotency. Retrying the same business intent reuses the identity; starting a genuinely new operation does not. A key the model regenerates per attempt is not an idempotency key, it is a new request wearing the same name.
  • Verification method: how you confirm the action actually happened. For cancel_order, that is "re-read order status and check it reached a terminal cancelled state."

That last field is the one demos skip and production cannot. A tool's own response is a claim about the request; the verification method is how you check the claim against the world.

This is also the point where a schema stops being documentation and becomes a control surface. A typed output shape, checked at runtime, does not just describe what the tool returns. It constrains what the agent is allowed to treat as a valid result, and it lets the next step check the result mechanically instead of hoping the prose lined up. Defining the contract is part of building the tool, not an afterthought to it.

The skill: packaging the procedure

Tools tell the agent what it can call. Knowledge retrieval (RAG, from earlier in the series) tells the agent what it knows. A skill tells the agent how a recurring piece of work should be done: the procedure, in order, with the checks that matter.

For the cancel-then-refund task, that procedure is worth packaging, because it is not obvious and it will be reused. Here is a compact version:

# cancel_order_skill

name: cancel_order
description: Cancel an order and issue any refund owed, safely, when cancellation is legitimate.

procedure:
  1. Confirm the cancellation is allowed (order not yet shipped; request is legitimate).
  2. Get human approval for the refund amount if it crosses the approval threshold.
  3. Call cancel_order with an idempotency key.
  4. Verify the cancellation landed: re-read order status; proceed only if terminal-cancelled.
  5. Issue the refund with an idempotency key, but only after step 4 confirms.
  6. Verify the refund landed: re-read refund status.
  7. If any verification fails: do not continue. Wait, retry with backoff, or escalate.
Enter fullscreen mode Exit fullscreen mode

Two things about this file are deliberate.

First, it is short. A skill is the title, the description, and the procedure, not a manual. In practice only the name and description need to sit in front of the agent at all times; the body is pulled in when the agent recognizes the task. The procedure carries the steps that are easy to get wrong (the verify steps), not the steps the model already knows.

Second, a useful way to create a skill is to write it after a successful manual run, not before. Run the task once, note where it fails or becomes unclear, and then capture the steps that worked. A skill written from imagination encodes what you think the work is. A skill written from a successful run encodes what the work actually turned out to be. The procedure should reflect what worked in practice, not only what you expected to work.

Tool, skill, and retrieval are the conventions this series uses, not the categories every agent system is built from. Retrieval in particular can be a tool the model calls or context the harness injects before the model runs, and in the second case the agent never "sees" RAG at all.

State

State is what the loop knows between steps that is not in the conversation text. The conversation is where the customer's words live; state is where the facts the loop has established live. Keeping them separate is what stops the agent from re-deriving the situation from chat history on every turn, and from believing something just because it was said.

For the cancel-then-refund task, the working state holds at least:

  • order_id
  • customer_intent
  • approval_status
  • cancellation_status
  • refund_status
  • verification_status
  • step_count
  • budget_remaining
  • last_tool_result

Those fields are not all the same kind of thing, and it helps to say so. Some are runtime control state that the loop owns outright (step_count, budget_remaining). Some are model-visible context (last_tool_result). And some are observations of business state that lives somewhere else (cancellation_status, refund_status). The last group is the one to be careful with. cancellation_status is not "what the tool returned," and it is not the order's state either. It is the most recent verified observation of a system that remains authoritative, and it can become stale the moment it is written. Copying a fact into working state does not make the loop the owner of that fact.

This working state is short-term memory in the sense Part 4 defined: the application-managed context for the current case, discarded when the case closes. It is deliberately not long-term memory. Nothing here is persisted across cases. What an agent should remember between cases, and what it is allowed to keep about a person, is a governance question we take up in Part 8.

Approval and verification are two different things

It is easy to assume that a human approval step makes a consequential action safe. It does not, on its own. Approval and verification answer different questions.

Approval is about intent. A human looks at "refund $740 on order #4482" and decides whether that should happen.

It is worth being precise about what triggers that gate, because "this action has consequences" is too broad a test. A system can reasonably auto-refund $5 and require sign-off at $5,000. Both are consequential; the difference is the risk policy, not the category of action. Risk here means impact, how reversible the action is, how ambiguous the case is, whether it crosses an authorization boundary or makes an external commitment, and what the organization's own rules require. Read the running example against that test and the two actions come apart. Cancelling order #4482 before it ships is usually cheap to undo and may move no money at all. The $740 refund is the step where money leaves. If there is a human gate here, it belongs on the refund, and plausibly only above a threshold, not on both actions simply because both have consequences.

Verification is about outcome. It asks whether the action that was supposed to happen actually happened in the world. Approval cannot answer that, because at approval time the action has not run yet.

So a system can have a human approve the refund, fire it, and still be wrong, if it issued the refund on the basis of an unverified cancellation. Approval signs off on the plan; verification confirms the result. A consequential action that requires approval needs both, in that order: approve the intent, take the action, verify the outcome, then continue.

Check becomes verify-before-commit

Now resolve the puzzle from the cancel-then-refund case.

For most steps, check is cheap. The agent read some data; the check is "did I get a sane result." For some reversible, low-stakes actions, the tool response may be enough. For actions such as cancel, refund, delete, or publish, it is not.

For irreversible or asymmetric-stakes actions, check has internal structure. It is not one question, it is two:

  • verify: did the world actually change the way the tool's response implied?
  • commit: given that it did, is it now safe to take the next consequential step?

The five-word loop runs unchanged across the top: observe, decide, act, check, repeat. Below it, the check step expands. After verification succeeds, the loop attempts the commit, but the world can change before the write. The backend revalidates the precondition and either applies the action or rejects it. Failed verification leads to wait, retry, or escalation.

This is not a new loop word. The loop is still observe → decide → act → check → repeat. What changed is that check, for a consequential action, expanded from a glance into a gate. The loop did not change. The implementation got stricter.

Verification only means something if it is independent of the step it checks. If the same call that performed the action also reports whether the action succeeded, the check inherits whatever blind spot produced the result in the first place. It confirms the request was made, which is the thing we already doubted. Independence is the property that makes verification worth doing.

There are three ways to get it, in rough order of cost and independence:

  1. Schema / postcondition check. Does the response structurally match what success looks like? Cheap, and worth doing always, but it only catches malformed responses. It cannot catch "the tool reported success while the world did not change," because it never looks at the world.
  2. Ground-truth re-read. After the action, query the state directly. For the cancellation, re-read the order status. The read path is separate from the write path, so it can catch the gap between "request accepted" and "state actually changed," provided the read reaches authoritative state. A second endpoint is not independent merely because it is a separate call. In practice the thing that breaks this is ordinary infrastructure rather than anything exotic: a read replica lagging behind the primary, or a cache that has not yet been invalidated, will answer confidently and answer wrong, and the loop will conclude the action landed when it has not. Verification should target the authoritative source or a sufficiently consistent read path, and where the underlying system exposes versioning or consistency tokens, use them to strengthen the check. For consequential state changes, a ground-truth re-read is a strong default.
  3. Independent verifier. A separate judgment, sometimes from a different model, decides whether the action achieved its purpose. The most expensive option, and the only one available when there is no clean ground-truth to re-read; a second model can still share the first one's blind spots (for example, "did this message actually persuade the customer").

For cancel-then-refund, the answer is mechanism two. The agent calls cancel_order, then re-reads get_order_status, and issues the refund only if the order has reached a terminal cancelled state. The refund tool must still revalidate that precondition when it runs. This is worth stating as an invariant rather than a footnote: the agent's verification gate prevents bad sequencing, while the authoritative application boundary enforces the rule at the point where state changes. The loop decides what to attempt. The application boundary enforces what is allowed to happen. A guarantee that exists only in the loop can be bypassed by another caller, a retry, or a race, which is where Part 1 started.

There is a gap this gate cannot close on its own. Verification and the action that follows it are two separate calls, and the world does not hold still between them. A warehouse worker, a support override, or another workflow can move the order after the re-read confirms it and before the refund fires. Client-side verification is what stops the agent from sequencing badly. It is not a transaction. For the refund to be safe, the backend has to make the mutation conditional on the state it expects to find at write time, so that a refund against an order that changed underneath the check fails at the boundary rather than succeeding on a stale premise.

If the re-read comes back pending, unknown, or blocked, the loop does not commit. It waits and re-reads with backoff, keeping the same operation identity rather than re-firing the mutation, or escalates to a human.

A reader's comment on Part 3 helped sharpen this framing: confirm the world changed, not just what the tool reported.

The code shape

Here is the safe path as a structural sketch. This is the shape, not the implementation. The working version, with real error handling and backoff, lives in the repo, not the article.

# observe → decide → act → check(verify → commit) → repeat
# Consequential path: cancel an order, then refund, only after verifying.

cancel = call_tool("cancel_order", order_id=order_id,
                   idempotency_key=key("cancel", order_id))

# check: verify the world, not the tool's claim
status = verify_action_landed(
    read=lambda: call_tool("get_order_status", order_id=order_id),
    expected="cancelled",          # terminal state, not "accepted"
    retries=3, backoff="exponential",
)

if status == "verified":
    # commit: now it is safe to take the next consequential step
    call_tool("issue_refund", order_id=order_id,
              idempotency_key=key("refund", order_id))
else:
    escalate(order_id, reason="cancellation not verified")
Enter fullscreen mode Exit fullscreen mode

Three things in this sketch are the whole point: the idempotency keys (so a retry does not double-apply), the verify_action_landed re-read between the two consequential actions, and the else branch that refuses to commit when verification fails. Everything else is detail.

One scoping note: the companion lab uses a deterministic controller so its traces are stable and it runs without an API key. Under Part 5's definition that controller is workflow-shaped: code owns the next-action choice. It is the production structure around the loop; swap the decision seam for a model and the choice becomes agentic while the state, contracts, verification gate, budgets, and traces stay the same.

You can run the complete deterministic example, compare the safe and naive traces, and inspect the tests in the Part 6 companion lab.

Budget and stop rules

A loop that can call tools can also run away. Budget and stop rules are how you bound the cost of that.

Stop rules cover four things, not one: step count (the loop has tried too many times), cost (it has spent its budget), time (it has run too long), and silence. The last is the one demos forget. A loop can hang on a step that never errors and never returns, a call that goes idle without closing, and a stop rule that only watches for errors and step caps will wait forever. Production loops need a watchdog that treats silence as a failure too.

Budget is worth thinking of as a blast-radius control, not just a bill. A per-run ceiling, checked before expensive calls rather than reconciled afterward, turns a worst case from a runaway into a clean stop. It is a boundary on what the agent can do, written in cost instead of permissions, the same family as scoping tools and gating consequential actions.

Observability

Everything the loop does should leave a trace. For each step, log: what the agent observed, what it decided, which tool it called, what the tool returned, what the verification read came back with, the resulting state, the cost and latency, and, when the loop ends, why it stopped.

The one line worth singling out is the gap between the tool response and the verification read. That gap is where the cancel-then-refund failure lives. A trace that records both, "tool said accepted; re-read said still pending," lets you see it later instead of guessing. This trace is also what Part 7 will build its diagnostics on; for now, the point is to capture it.

Bounded autonomy

It is tempting to read a working build and decide the lesson is "give the agent more freedom" or "add more agents." It is not. Everything that made this agent safe was a constraint, not a capability. The loop followed a fixed control structure, but the next action was chosen at runtime from the current state. Each tool was scoped to one job and carried a contract. Consequential actions were bounded by idempotency, approval where needed, and verification. The verification step refused to commit on an unconfirmed result. The stop rules were deliberate. In a production build the model chooses actions; the structure decides which actions are possible at all. In production, reliability depends less on giving the model more freedom and more on placing clear boundaries around it.

What this article does not build

  • This is not a framework. It is the shape of a loop; the working pieces live in a repo.
  • This is not a vendor-specific platform. Native tool calling is the production baseline here; a protocol layer like MCP standardizes how tools are exposed; a hand-rolled ReAct loop is a teaching artifact, not a production recommendation.
  • This is not a multi-agent system. One bounded agent is the whole scope here. When a second agent is justified is outside the scope of this article.
  • This is not a durable execution runtime. The sketch here assumes the loop stays alive while it waits. Real settlement often does not cooperate: a cancellation or a refund can take minutes or hours to reach a terminal state, and a loop holding an open process through that window will lose its state to a timeout or a restart. If a wait can outlive the process, the execution state has to survive the process, which means an orchestration layer that persists workflow state across restarts. The structure in this article does not change when you put it there. Where the waiting happens does.
  • This is not the diagnostics story (Part 7) or the governance story (Part 8). When the verification read comes back wrong, why it went wrong and how you classify it is Part 7. Who is allowed to touch what, and the audit trail, is Part 8.

What this article builds is the production loop shape: the five words, unchanged, with the implementation made strict enough to trust.

Three takeaways

  1. A production agent is the loop plus its scaffolding.
    A loop alone is a demo. A production agent is the loop plus working state, tool contracts, a packaged procedure, an approval boundary, a verification gate, a budget with stop rules, and a trace. The model is only one part of the system.

  2. Tool success is not ground truth.
    A tool response describes the request, not the world. For irreversible actions, check has internal structure: verify that the world changed, then commit to the next step. Verification only counts when it is independent of the action it checks. For consequential state changes, a ground-truth re-read is a strong default.

  3. The safest agent is the most bounded one.
    Reliability does not come from more freedom or more agents. It comes from constraints: scoped tools, contracts, idempotency, approval, verification, budgets, and stop rules. The model chooses actions; the structure decides which actions are possible at all.


Next: when the verification read comes back wrong, what kind of failure is it, and how do you tell? That is Part 7.


Source note: this article builds on the "keep it simple" and bounded-autonomy principles from Anthropic's Building Effective Agents (Schluntz & Zhang). The verify-before-commit gate, the production architecture map, and the "a tool response describes the request, not the world" framing are this series' own synthesis.

Part of the AI Agents in Practice series, originally published on AI in Practice Hub alongside the full series and companion code.

Top comments (0)