Tacnode™
Back to Blog
AI Engineering

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.

Xiaowei Jiang
Xiaowei Jiang
CEO & Chief Architect
10 min read
A circular agent loop with four observation points, each stamped with a different time, and an arrow leaving the loop to an action taken at a later time.

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.

What Is the Agent Loop?

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.

The Loop in Code

Stripped down, most agent loops look like this:

python
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.

How Agent SDKs Run the Loop

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.

Every Turn Reads a Different Moment

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.

A Worked Failure: One Decision, Four Moments

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.

TurnToolSourceValue returnedTrue as of
1get_accountOperational databaseBalance 1,200, limit 3,000, no flagst = 0 s
2get_risk_featuresFeature store fed by a stream job2 transactions in the last hourt = −20 s
3search_similar_casesVector index synced every 5 minutes14 similar approvalst = −4 min
4get_open_disputesDisputes serviceNonet = 9 s
5update_limit(6,000)Card systemLimit raisedt = 16 s
A timeline. The agent’s reads sit at minus 4 minutes, minus 20 seconds, 0 and 9 seconds. A fraud flag is set at 11 seconds, after the last read, and the agent acts at 16 seconds

Making the Loop Read One Moment

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:

sql
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.

When Many Loops Share State

In a multi-agent system, one loop’s action is another loop’s observation. The limit raised at turn five is what a payments agent reads a second later. The outreach agent’s “contacted” mark is what the next outreach agent checks.

That makes the whole system one larger loop: agents read context, decide, write, and their writes become context for the next decision. The faster the inner loops run, and the more of them share the same accounts, budgets or records, the more each read is racing someone else’s write. Agent swarms and multi-agent coordination cover the patterns for that case; the short version is that coordination has to happen where the state is written, not in the transcript.

Where Tacnode Fits

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.

FAQ

AI AgentsAgent LoopReActTool CallingContext Engineering
Xiaowei Jiang

Written by Xiaowei Jiang

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.

Subscribe on LinkedIn

Ready to see Tacnode Context Lake™ in action?

Book a demo and discover how Tacnode can power your AI-native applications.

Book a Demo