If you have ever built an AI agent, you have already written a loop. The model thinks, it asks for a tool, your code runs the tool, the result goes back in, and around it goes again. That part is not new.
What separates a demo from something a bank will run is everything wrapped around that loop. Where does the state live? Who decides the work is finished? What stops it when it goes wrong? Those questions are loop engineering, and they are the difference between an agent that impresses in a notebook and one that survives contact with real customers.
In this blog, we will walk through the whole discipline: the mechanism you already have, the five steps that make it production-grade, where memory belongs, how to check work honestly, and the four failures every loop eventually hits. We will use banking examples throughout, because that is where the cost of getting it wrong is easy to see.

You Already Have the Loop
Let's start by being precise about what an agent loop actually is, because the picture is simpler than the vocabulary suggests.
A question arrives. The model reads it and does one of two things. Either it produces a final answer, and the loop stops. Or it produces a request for a tool, and here is the part people miss: the model does not run that tool. It cannot. It writes down what it wants, and then it waits.
Your code runs the tool. Your code takes the result and appends it to the message list. Then the whole list goes back into the model, and the cycle repeats.
# the shape of every agent loop, stripped bare
messages = [HumanMessage(question)]
while True:
reply = model.invoke(messages) # the model only writes
if not reply.tool_calls: # no tool wanted, we are done
return reply.content
messages.append(reply)
for call in reply.tool_calls:
result = run_tool(call) # YOUR code executes, not the model
messages.append(ToolMessage(result))
That gap, between the model asking and your code acting, is the only place you have any control. Every guard you want to add lives there.
Now look at what the picture does not contain. Nothing counts the iterations, so there is no natural limit. Nothing remembers anything, because everything the run knows sits in that message list, and the list dies when the run ends. Both of those are yours to fix.

Input-Side Engineering and Output-Side Engineering
Before we go further, it helps to place loop engineering on the map, because it is easy to confuse with the disciplines you already know.
Think of the model as having a before and an after. Prompt engineering shapes the instructions going in. Context engineering decides which documents and history get injected. Harness engineering sets the environment: the temperature, the tool definitions, the model choice. All three happen before the model generates anything, so we can call them input-side work.
Here, we can see that every one of those is trying to make the first attempt better. None of them can look at what came out.
Loop engineering is the output-side discipline. It starts once the model has produced something and asks a different question: is this good enough to ship, and if not, what happens next? That is why it can never replace good prompting; it catches what good prompting misses.
Let me tabulate this for your better understanding.
| Discipline | Side | What it controls |
|---|---|---|
| Prompt engineering | Input | The wording and structure of the instruction |
| Context engineering | Input | Which data and history reach the model |
| Harness engineering | Input | Environment, tools, temperature, model choice |
| Loop engineering | Output | Verification, correction, stopping, budgets |

Where ReAct Stops Being Enough
The first real agent pattern most of us learned was ReAct: reason, act, observe, repeat. It is a genuine loop and it still works.
But notice who is doing the checking. In ReAct, the model reasons about whether it is finished, then decides to stop. The same model that produced the work is also the one marking it complete. There is no independent verification anywhere in the pattern.
In simple words, ReAct gives you a loop that runs. Loop engineering gives you a loop that can be trusted to stop for the right reason. The upgrade is not a smarter model. It is moving two jobs, the check and the memory, out of the model entirely.

Five Steps, and Two of Them Are Not the Model
So, here comes the full shape of an engineered loop. There are five steps, and the important detail is that only two of them involve the model doing the work.
- Task. The goal, plus everything already known about it.
- Act. The model reasons and calls a tool. This is the maker.
- Observe. Your code captures what actually came back, including errors.
- Verify. Something other than the model that acted checks the output.
- Decide. Either stop, or send it back to step two with the reason it failed.
Step four is where most agent projects are missing a piece. Step five is where the correction gets specific: not "try again", but "the KYC field is missing, re-run the customer lookup".
Around all five sits a shared state store that lives outside the model's context window. A plan file, a verdicts file, a ticket. Every step reads from it and writes back to it. That is what lets the checker see why the maker failed rather than only that it failed.

If the Memory Is the Transcript, It Is a Chat
Now let's look at the most common structural mistake, because it is invisible until the bill arrives.
In a naive loop, memory is the message list. Turn one appends to it. Turn two appends to it. By turn nine, the model is being handed everything that ever happened, and only the last two blocks are actually new.
Two things go wrong at once. You pay for the entire history again on every single turn, so cost grows with the square of the conversation rather than in a straight line. And the useful signal gets thinner, buried under old tool dumps the model no longer needs. That second effect is often called context rot.
Here, we can see the real problem: turn nine does not need turn one in the window. It needs turn one on disk.

The Window and the Store
The fix is to split memory into two places with two different jobs.
The window holds only what this turn needs. It is small, expensive, and temporary. The store holds everything the run has learned. It is a file, a table, a document, and it survives the run.
There are three standard moves for keeping the window small while the store grows.
Compaction. Summarise the run so far into a short state description, then continue from the summary instead of the raw history.
Offloading. When a tool returns something huge, a full statement PDF or a thousand-row query, write it to the store and keep only the slice the model needs in the window.
Sub-agents. Hand a messy subtask to a separate agent with its own window. Only its clean result comes back, not the mess it waded through to get there.
# offloading: the big thing goes to the store, a small handle goes to the model
raw = fetch_statement(account_id) # 40 pages of transactions
path = store.write(f"statements/{account_id}.json", raw)
summary = summarise_transactions(raw) # a few lines
messages.append(ToolMessage(f"Saved to {path}. Summary: {summary}"))

The Maker Cannot Grade Itself
This is the heart of loop engineering, so let's slow down here.
The natural first design is to ask the model to review its own answer. It writes the loan summary, then you ask "is this correct and complete?" The model reads it back and says yes.
It will almost always say yes. The same weights that produced the answer are now judging the answer, working from the same context and the same blind spots. If the model had known the KYC field was missing, it would not have left it out in the first place. You have doubled your token cost and moved the verdict least.
The fix is separation. A maker does the work. A checker that never wrote it looks at the output cold: different prompt, different context, no memory of having produced it.
In simple words, we do not let students grade their own exam papers, and for exactly the same reason.
Something worth knowing: the checker does not need to be the expensive model. A small cheap model with a tight checklist consistently beats a large model grading its own homework, because the advantage comes from independence, not intelligence.

What Makes a Check Worth Having
Not every check is useful. There are three properties that separate a real one from theatre.
One: it returns a value, not prose. If your checker replies "this looks mostly fine, though you may want to double-check the interest calculation", your code cannot branch on that. You need something structured.
# a verdict your code can actually act on
{"passed": False, "missing": ["kyc_status"], "reason": "customer KYC not verified"}
Better still, where the task allows it, use a check that is not a model at all. A test suite. A JSON schema. A type checker. A rule that recomputes the EMI and compares. Deterministic checks do not hallucinate approval.
Two: it is not the maker. Covered above, and it is the property people quietly skip when they are in a hurry.
Three: it will be gamed. This one surprises people. Any loop that optimises against a checker will eventually learn to satisfy the checker rather than the goal. The rubric passes and the customer is still unhappy. There is no automatic defence for this. You have to periodically re-read the outputs that passed, not just the ones that failed.

Four Ways a Loop Fails, and What Catches Each
Let's see the failure modes as below, because each one has a different brake and they are not interchangeable.
It never stops. The agent keeps calling tools, burning tokens, going nowhere. The tell is that your finance team notices before your engineering team does. Caught by a maximum iteration count and a hard budget cap.
It stops too early. The agent returns something plausible but incomplete, and the loop exits satisfied. The tell is that nobody notices at all, which makes this the most dangerous one. Caught only by the completion check. There is no other brake for it.
It repeats itself. The same tool, the same arguments, the same shrug of a result, over and over. Usually it means a tool returned an empty value instead of an error. Caught by no-progress detection: did this turn make the identical call as the last one?
It learns to pass the check. The rubric goes green while the actual work degrades. There is no automatic brake. You catch it by re-reading passed outputs on a schedule.
Here is what the controls look like in LangChain 1.x. Note carefully which ones the framework gives you and which ones you write yourself.
from langchain.agents import create_agent
from langchain.agents.middleware import ModelCallLimitMiddleware
from langgraph.checkpoint.memory import InMemorySaver
agent = create_agent(
model=model,
tools=[get_statement, calculate_emi],
checkpointer=InMemorySaver(), # thread_limit needs one
middleware=[ModelCallLimitMiddleware(
run_limit=5, # this single invocation
thread_limit=20, # the whole conversation
exit_behavior="end", # stop politely, do not raise
)],
)
# a second, independent ceiling one level down in the runtime
agent.invoke(question, config={"recursion_limit": 12})
The budget cap and the no-progress check are not in the framework. You write those yourself. And here is the honest limitation: none of these limits know whether the work is finished. They only know it has run long enough that something is probably wrong. Finishing is the completion check's job, and nothing else can do it.

The Loop Runs on Three Clocks
One last idea that reframes everything above. A production system does not have one loop. It has three, running at different speeds, and each needs its own check and its own audience.
The inner loop runs in minutes. The agent acts, checks, and fixes itself. The check here is a test that passes or fails, and no human is involved.
The middle loop runs in hours. You read the output and steer the system. The check is your judgement.
The outer loop runs in weeks. Customers use the thing and react. The check is whichever number you actually care about moving.
A loop with no cadence is just a person pressing enter repeatedly. Deciding which clock a given check belongs to is most of the design work.
Recap
This is how loop engineering works. Every agent already runs a loop: the model writes a tool request, your code executes it, the result goes back in. The model never executes anything itself, which means the gap between request and execution is entirely yours, and that gap is where the discipline lives.
An engineered loop has five steps, and two of them are deliberately not the model: something else verifies the output, and something else decides whether to stop. State moves out of the message list and into a store on disk, because a transcript-as-memory dies with the run and costs more every turn until it does. The check has to return a value your code can branch on, and it must come from something that did not write the answer.
Four failures will find you eventually: never stopping, stopping too early, repeating, and learning to pass the check. Set run_limit and recursion_limit on day one, then log which brake fires. If it is not the completion check, the work was never actually finished.
The question to carry away is a simple one. Can you check the answer without asking the model? If yes, wire that check in and let the loop close itself. If no, then you are the check, so keep the loop short, keep it watched, and cap it hard.
Loops are the beginning. As agents start handing work to each other, these linear loops become graphs, and the same three questions follow us there: where does the state live, who decides it is done, and on whose clock does it repeat.