The Agent Loop: Where Each Step Gets Its Context
How the agent loop works, from the ReAct paper to production code, and why each turn’s reads can describe a world that never existed by the time the agent acts.
How the agent loop works, from the ReAct paper to production code, and why each turn’s reads can describe a world that never existed by the time the agent acts.
TL;DR: The agent loop is the cycle at the core of every tool-using agent: the model reasons, calls a tool, reads the result, and repeats until it can answer or act. Each turn reads context from somewhere, at some moment. When the turns read different systems at different moments, and the earliest observations sit in the transcript while the world moves on, the agent acts on a picture of the world that was never true all at once. The fix is to know when each observation was true and to re-check what the action depends on at the moment it’s taken.
Most agent bugs you can see in a trace. The model picked the wrong tool, misread a field, looped forever. Those show up as obvious mistakes in the transcript.
The harder bug looks like a perfect trace. Every tool returned correct data. Every reasoning step follows from the one before. The final action is a sensible conclusion from what the agent observed. And it was still wrong, because what the agent observed at turn two had stopped being true by turn six, and what it observed from two different tools was never true at the same time.
That bug lives in the loop itself, in where each step gets its context and when.
The agent loop is the repeating cycle an AI agent runs to complete a task: the model reasons about the current state, chooses an action such as a tool call, the system runs it and returns the result as an observation, and the model reasons again. The loop ends when the model answers, acts, or hits a stop condition.
A widely cited early statement of the pattern is ReAct, from ReAct: Synergizing Reasoning and Acting in Language Models by Shunyu Yao, Jeffrey Zhao, Dian Yu, Nan Du, Izhak Shafran, Karthik Narasimhan and Yuan Cao, first posted to arXiv in October 2022. The paper explores using LLMs “to generate both reasoning traces and task-specific actions in an interleaved manner.” Reasoning helps the model “induce, track, and update action plans as well as handle exceptions,” while actions let it “interface with external sources, such as knowledge bases or environments, to gather additional information.”
The paper tested this on question answering and fact verification with a simple Wikipedia API, and on two interactive benchmarks, ALFWorld and WebShop, where the abstract reports ReAct outperforming imitation and reinforcement learning methods “by an absolute success rate of 34% and 10% respectively.”
Later descriptions are close to the original. Anthropic’s Building effective agents says agents “are typically just LLMs using tools based on environmental feedback in a loop,” and that it’s “crucial for the agents to gain ’ground truth’ from the environment at each step (such as tool call results or code execution) to assess its progress.”
That phrase, ground truth from the environment at each step, is the subject of this page. Ground truth has a timestamp.
Stripped down, most agent loops look like this:
messages = [system_prompt, user_request]
for turn in range(MAX_TURNS):
reply = model.generate(messages, tools=TOOLS)
if reply.is_final:
return reply
messages.append(reply) # the model's turn, with its tool calls
for call in reply.tool_calls:
result = run_tool(call) # a read (or a write) against some system
messages.append(observation(call, result))
raise RuntimeError("hit MAX_TURNS")
Three things about this code matter for context.
Each tool call is a read against a system, at a moment. run_tool might query an operational database, a cache, a feature store, a warehouse table or a vector index. Each has its own freshness. The observation carries the value but rarely when it was true.
Observations accumulate. Turn one’s result is still in messages at turn eight. The model treats the whole transcript as its picture of the present. Nothing marks the old observation as old.
The action is just another tool call. When the model finally calls approve, refund or update_limit, nothing in the loop re-checks the observations it relied on. The decision was made on the transcript; the write lands on the world.
Agent SDKs ship this loop so you don’t write it by hand, and their docs describe it in nearly the same terms.
OpenAI Agents SDK. The running agents docs lay it out step by step. The runner calls the LLM. If the output is final, “the loop ends and we return the result.” If the model requests a handoff, “we update the current agent and input, and re-run the loop.” If it produces tool calls, “we run those tool calls, append the results, and re-run the loop.” Past max_turns, the runner raises MaxTurnsExceeded.
Claude Agent SDK. Anthropic’s Building agents with the Claude Agent SDK describes agents operating in “a specific feedback loop: gather context -> take action -> verify work -> repeat.”
In both, an observation is appended to the transcript and fed back to the model. The loop’s work ends there. Recording when that observation was true, and checking it again before the action, is left to the tools you write.
Staleness enters the loop in two ways, and they compound.
Across sources, within a turn. Many models can emit several tool calls in one turn, and harnesses can run them in parallel. One hits the primary database and returns the current row. One hits a feature store refreshed by a streaming job a few seconds behind. One hits a vector index synced every few minutes. One hits a warehouse table loaded overnight. The model gets four true answers about four different moments and combines them as if they were one.
Across turns, within a run. A loop with eight turns, each waiting on a model generation and a few tool calls, can easily take tens of seconds. By the end, the first observation describes the world as it was at the start. Other agents, other services and humans have written in between. This is context lag: the delay between a change in the world and the moment the agent’s context reflects it. Context Rot vs. Context Lag compares it with the other way long runs degrade.
Neither shows up as an error. Every value in the transcript is something a system really returned.
Here is an illustrative scenario. The numbers are constructed, not measured.
An account-operations agent handles a request to raise a card’s credit limit. Its loop runs five turns.
What happened in the world while the loop ran: at t = 5 s, six authorization attempts hit the card from a new merchant category, and at t = 11 s the cardholder’s other card was reported stolen and a fraud flag was set on the customer.
Every read was correct when it was taken. None of them described the world at 16 s, when the agent acted.
The loop saw none of it. The balance and flags were read at t = 0, before the burst. The transaction count came from a feature computed twenty seconds before the loop started. The dispute check at t = 9 ran before the fraud flag existed and, in any case, looked at disputes, not flags. At t = 16 the agent raised the limit on a customer who had been flagged five seconds earlier.
Each tool was correct. The trace reads cleanly. The decision was made on a combination of states that was never true at any one moment: the account as of t = 0, the velocity as of t = −20, and the case history as of four minutes ago.
| Turn | Tool | Source | Value returned | True as of |
|---|---|---|---|---|
| 1 | get_account | Operational database | Balance 1,200, limit 3,000, no flags | t = 0 s |
| 2 | get_risk_features | Feature store fed by a stream job | 2 transactions in the last hour | t = −20 s |
| 3 | search_similar_cases | Vector index synced every 5 minutes | 14 similar approvals | t = −4 min |
| 4 | get_open_disputes | Disputes service | None | t = 9 s |
| 5 | update_limit(6,000) | Card system | Limit raised | t = 16 s |
There’s no single fix, but there are four habits that remove most of the risk.
Gather the decision’s context in one read. Where the context a decision needs can be fetched together, fetch it together: one read against one consistent snapshot beats four tools against four systems. The model still reasons over it, but the inputs agree with each other.
Stamp every observation. Return as_of with every tool result: the commit time, a row version, or the refresh time of the feature or index it came from. The model can then reason about staleness, and your traces can show it.
Re-check at the action. Make the action tool take the versions it was decided on, and refuse if they’ve changed. In SQL this is a conditional write:
UPDATE card_account
SET credit_limit = $new_limit,
version = version + 1
WHERE account_id = $id
AND version = $version_read_at_turn_1
AND NOT EXISTS (SELECT 1 FROM customer_flags f
WHERE f.customer_id = card_account.customer_id
AND f.active)
RETURNING credit_limit;
-- Zero rows: something changed since the agent looked. Re-run the loop.
The version check catches changes to the account row. The flag check re-reads the condition that matters in the same statement as the write. Under PostgreSQL’s default READ COMMITTED, a query sees “only data committed before the query began,” per the isolation docs, so the check sees every flag committed before the statement starts. In the scenario above, the flag set at t = 11 would have blocked the update at t = 16.
Bound the loop. Anthropic notes it’s “common to include stopping conditions (such as a maximum number of iterations) to maintain control.” A turn budget also bounds how old the oldest observation can get.
The loop’s weak point is that a decision’s context is assembled from several systems at several moments. The Tacnode Context Lake™ holds the decision context for that loop, so one read can return all of it from one consistent snapshot. That is what decision coherence means for a single turn.
Source data from your systems. Accounts, transactions and case records stay in the systems you run and arrive by change data capture. Within Tacnode, the raw rows, aggregates computed on demand and vector similarity search all resolve against the same set of ingested events, through one PostgreSQL-compatible interface. Expensive aggregates can be kept as incremental views, refreshed on a schedule you set in SQL, typically every few seconds. Most reads run on demand against the latest committed data.
Decision state the agents write. Holds, per-agent limits, claims and records of each agent’s actions can live in Tacnode as the system of record. A check and the write that depends on it run in one ACID transaction, and each decision written back is current context for the next loop’s read.
If your agent answers questions from documents and nothing it reads changes during a run, none of this matters. A vector index with pgvector is the right tool there.

Former Meta and Microsoft. Built distributed query engines at petabyte scale. Author of the Composition Impossibility Theorem (arXiv:2601.17019).
Fresh Context Newsletter
The argument behind the posts — what breaks when AI acts on stale data, from the team building the Context Lake. About twice a month.
Book a demo and discover how Tacnode can power your AI-native applications.
Book a Demo