Agent Harness: What It Holds, and What It Can’t
What an agent harness is, what belongs in it (loop, tools, context assembly, guardrails), and why state shared by many agents has to live in storage instead.
What an agent harness is, what belongs in it (loop, tools, context assembly, guardrails), and why state shared by many agents has to live in storage instead.
TL;DR: An agent harness is everything around the model: the loop, the tools, context assembly, retries, guardrails and the handling of per-run memory. It is the right home for behavior. It is the wrong home for state that many agent instances share, because each instance runs its own copy of the harness. Limits, claims and records that every instance must respect have to live in storage that every harness reads and writes, with the check and the write done together.
Your agent worked in the demo. Then the harness grew. You added a retry wrapper for flaky tools, a summarizer for long threads, a guardrail that blocks refunds over a daily cap, a check that stops the agent from emailing a customer twice. Every fix went into the code around the model, because that’s where you could reach it.
Then you ran it in production with dozens of instances at once: chat, voice and a background queue, all pointed at the same customers. The guardrails still ran, and still passed, and the cap was still crossed. Every instance enforced the rule correctly against what it could see. None of them could see what the others had just done.
This page defines the agent harness neutrally, lists what belongs in it, and draws the line where harness logic stops being enough.
An agent harness is the software around a language model that turns it into an agent: the loop that calls the model, the tools it can use, the logic that assembles its context, retries, guardrails, and management of the current run’s memory. The model decides what to do next; the harness executes it, feeds back the result and enforces the rules.
LangChain’s The Anatomy of an Agent Harness (Vivek Trivedy, March 10, 2026) puts it in one line: “If you’re not the model, you’re the harness.” The same post defines a harness as “every piece of code, configuration, and execution logic that isn’t the model itself,” and lists system prompts, tools, bundled infrastructure such as a filesystem or sandbox, orchestration logic, and hooks or middleware.
LangChain’s docs separate three related terms. Agent frameworks “provide abstractions that make it easier to get started when building with LLMs.” Agent runtimes “provide the tooling for running agents in production.” And agent harnesses are “opinionated, batteries-included frameworks with built-in tools and capabilities for building sophisticated, long-running agents.”
The term also appears in writing about coding agents. Mitchell Hashimoto’s My AI Adoption Journey (February 5, 2026) has a step titled “Engineer the Harness” and calls the practice “harness engineering.” This page is about production agents that act on customers, money and records, where the harness questions are different.
Anthropic’s Building effective agents describes agents as “typically just LLMs using tools based on environmental feedback in a loop.” Almost everything in that sentence except “LLMs” is harness.
Look at the last column. Every piece of harness logic is scoped to one run, one turn or one call. That’s why harnesses are easy to test and swap. It is also the limit.
| Component | What it does | Scope |
|---|---|---|
| The loop | Calls the model, runs the tool it picked, appends the result, repeats until done or a stop condition | One run |
| Tools | Schemas, auth, timeouts, the code that calls the API or database | One call |
| Context assembly | Decides what goes in the prompt: instructions, retrieved records, prior turns, summaries | One turn |
| Retries and fallbacks | Re-runs failed tool calls, switches models, handles rate limits | One call |
| Guardrails | Validates inputs and outputs, blocks disallowed actions, asks for approval | One action |
| Memory management | Decides what to keep, summarize or drop from message history and scratchpad; the memory itself is stored in a checkpoint or database | One run or thread |
| Observability | Traces of model calls and tool calls | One run |
Three vendors’ descriptions of their own harnesses show what the term covers in practice.
Claude Agent SDK. In Building agents with the Claude Agent SDK (September 29, 2025), Anthropic wrote that “the agent harness that powers Claude Code (the Claude Code SDK) can power many other types of agents, too,” and renamed it the Claude Agent SDK. It frames the loop as “gather context -> take action -> verify work -> repeat.”
Deep Agents. LangChain’s Deep Agents docs describe it as an “agent harness”: “the same core tool calling loop as other agent frameworks,” with built-in tools, subagent spawning, summarization and human-in-the-loop approval. Its virtual filesystem “can be backed by different pluggable backends,” and it uses “the LangGraph runtime for durable execution.”
Microsoft Agent Framework. Microsoft’s Agent Harness in Agent Framework (March 12, 2026) calls the harness “the layer where model reasoning connects to real execution: shell and filesystem access, approval flows, and context management across long-running sessions.”
The common thread is behavior: what the agent may do, what it sees next and when it asks a human. Where Deep Agents needs persistence, it plugs in a backend or a runtime. The harness decides; storage holds.
A production deployment doesn’t run one harness. It runs one harness per conversation, per job, per worker process. A support agent might have hundreds of instances alive at once, in different processes on different machines, and two of them can be acting for the same customer: one on the phone, one in chat.
Whatever a harness keeps in memory belongs to its own instance. A counter in a Python object, a “recently contacted” set, a lock from your language’s standard library: none of them exist for the instance next door. Run memory that is persisted to a checkpoint is still scoped to its thread. LangChain’s docs describe runtimes offering “Thread-level and cross-thread persistence for state management,” and cross-thread persistence is, by definition, storage that many runs read and write. The harness is the client. The state lives somewhere else.
So the question for any rule you put in a harness is simple: does it depend only on this run, or on what other runs are doing? “Don’t call the same tool five times in a row” is a run rule. The harness can enforce it. “Don’t refund this customer more than 200 dollars today” is a shared rule. The harness can only ask.
Here is an illustrative scenario. The numbers are constructed, not measured.
A support platform caps refunds at 200 dollars per customer per day. The guardrail lives in the harness: before calling the issue_refund tool, it reads refunded_today for the customer and blocks the call if the new total would exceed the cap. refunded_today comes from a counter that a pipeline refreshes from the payments system every 30 seconds.
t = 0 s Customer 8812 has 80 dollars refunded today (counter is current)
t = 2 s Chat harness: reads refunded_today = 80. Request 100. 180 <= 200 -> allow
t = 3 s Voice harness: reads refunded_today = 80. Request 110. 190 <= 200 -> allow
t = 9 s Chat harness issues refund of 100
t = 11 s Voice harness issues refund of 110
t = 30 s Counter refreshes: refunded_today = 290
Both pre-checks read the same 80. Neither could see the other’s refund, because it hadn’t happened yet.
Both guardrails passed. The customer received 290 dollars against a 200 cap, and nothing in either harness was wrong.
A faster pipeline doesn’t fix this. At t = 3, the chat refund hadn’t been issued yet, so no pipeline, however fast, could have shown it to the voice harness. The window that matters is between the check and the write, and in agents that window is mostly the model thinking. It is the shape described in agentic TOCTOU and race conditions between AI agents.
The fix is to stop treating the shared rule as a pre-check in the harness and make it a property of the write. The harness still calls a tool; the tool does the check and the write in one step against storage every instance shares.
The gap that matters is between the check and the write. Closing it means doing both in one step.
For a single counter row in PostgreSQL, a conditional update is enough:
-- One row per customer per day. Create it if this is the first refund today;
-- a concurrent insert of the same row waits, then does nothing.
INSERT INTO refund_budget (customer_id, day, used, cap)
VALUES ($1, current_date, 0, 200)
ON CONFLICT (customer_id, day) DO NOTHING;
UPDATE refund_budget
SET used = used + $2
WHERE customer_id = $1
AND day = current_date
AND used + $2 <= cap
RETURNING used;
-- Zero rows returned: the cap would be exceeded. Don't issue the refund.
This works under READ COMMITTED because of how PostgreSQL handles concurrent updates to the same row. Its isolation docs say the second updater “will wait for the first updating transaction to commit or roll back (if it is still in progress).” If the first commits, “The search condition of the command (the WHERE clause) is re-evaluated to see if the updated version of the row still matches the search condition.” In the timeline above, the voice harness’s update runs after the chat harness’s has committed, checks used + 110 <= 200 against 180, and returns zero rows. If the two updates had arrived at the same instant, the second would have waited for the first and re-checked, and one of the two refunds would have been refused.
That covers one row. Real guardrails rarely stay that simple. “No more than three refunds across all of a customer’s linked accounts this week, and none if a chargeback is open” reads several tables and a derived count. Each of those reads has to come from the same moment, and the claim has to be recorded where the next check will see it. The same docs call READ COMMITTED “unsuitable for commands that involve complex search conditions”; in PostgreSQL, a check like that holds against concurrent runs only under SERIALIZABLE isolation or with explicit row locks.
| Harness logic | Shared state, kept in storage |
|---|---|
| The loop, prompts and tool schemas | Budgets, quotas and velocity counters |
| Retries, timeouts, fallbacks | Claims and leases on tasks or entities |
| Output validation and formatting | Holds placed on money or inventory |
| Deciding what a run remembers and compacts | Records of what each agent decided and did |
| Rules about this run only | Rules about what other runs are doing |
Context assembly is the harness’s most important job, and it hides the second failure. To answer “can I refund this order?”, a harness might call four tools: the order from the commerce database, the customer’s tier from the CRM, refund history from a warehouse table, and similar past tickets from a vector index. Each call returns a correct answer about a different moment. The order is current. The refund history is from last night’s load. The vector index reflects whatever its sync last caught.
The model then reasons over a combination that never existed at any single point in time. This is the context gap: the decision acts on context that is incomplete, inconsistent or outdated when it executes. More prompt engineering in the harness can’t repair it, because the inconsistency is in where the reads come from, not in how they’re phrased. Why AI Agents Act on the Wrong Context goes deeper on this failure.
Tacnode is not a harness. It doesn’t run the loop, call models or execute tools. Every harness instance reads from it and writes to it through ordinary tool calls.
The Tacnode Context Lake™ holds the decision context for agents: everything a decision reads before it acts, and everything it decided once it did. Each agent’s output is written back and becomes context for the next agent’s check, so every instance works from the same shared context.
State the agents own. Refund budgets, per-agent limits, holds, claims and records of agent actions can live in Tacnode as the system of record. There, a check and its write run in one ACID transaction, at serializable isolation by default, including checks that span several tables and derived counts, so two harness instances drawing down the same budget at once are serialized: one commits and the other sees the conflict.
State your systems own. Orders, payments and CRM records stay in the systems you already run and arrive by change data capture. For that data, Tacnode gives the harness’s context assembly an internally coherent view: raw rows, on-demand aggregates and similarity search computed against the same set of ingested events, through one PostgreSQL-compatible interface. It doesn’t enforce anything inside a payments ledger it doesn’t hold.
If you run one agent per user, nothing is shared between runs, and the agent mostly retrieves documents, you don’t need this. A vector index on Postgres with pgvector handles that well.

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