Why Long-Horizon Agents Need a Harness: How Deep Agents Manages State and Context
Why long-horizon agents need a harness: how LangChain Deep Agents fights context rot with four pillars, middleware, filesystem backends and isolated subagents.
This article is published in English.
Sources: The Deep Agents Architecture, Part 1: Understanding the Agent Harness
The most basic agent design is a loop around tool calls. The model reads the request, picks a tool, runs it, reads the output, chooses again and continues until it stops calling tools. LangChain is candid about where this design tops out: such agents are "shallow," unable to plan and act over long, intricate tasks. That is not a flaw peculiar to one framework. It is the outcome of leaving nothing between the model and a context that only grows.
The best sign that the ceiling is structural comes from three teams that never coordinated. Anthropic hit it while building Claude Code, OpenAI hit it with Deep Research, and Butterfly Effect hit it so hard that Manus was rebuilt four times. Each landed on the same four components: a planning tool, delegation to subagents, filesystem access and a detailed prompt. Independent efforts at independent companies reaching one architecture suggest the problem dictated it, not anyone's taste.
Context rot is the real mechanism
It is tempting to say long agent runs fail because the context window fills up. That is not the mechanism.
A model deep into a long conversation reasons worse than the same model on a short one, even with most of its window still free. The attention available to any single token thins as the surrounding token count rises, and the shrinking signal-to-noise ratio hurts output quality well before the window is full. The effect is documented, not anecdotal: models reliably retrieve information from the middle of a long context less well, preferring tokens near the start and the end.
This changes what a fix looks like. If capacity were the failure, a bigger window would cure it. The failure is dilution: the model is still capable, but what it needs is buried under everything that came earlier. That is a hygiene problem and not a size problem, and extra window space does nothing for hygiene.
The gap that neither LangChain nor LangGraph was built to close
LangGraph concentrates on durable execution: checkpoints, streaming, human review and persistence across threads. Its real counterpart is Temporal, not other agent frameworks. It ensures an agent outlives a crash, but says nothing about whether the agent is still thinking clearly after four hours. LangChain sits a layer higher and supplies the abstractions: the tool-calling loop, structured content and customization middleware. Still, the default loop built from those abstractions is the shallow one just described. Durability and abstraction are real and needed, yet neither speaks to whether the model's working context stays clean.
This can be verified from how the pieces are wired. The declared dependencies of Deep Agents name langchain, langchain-core, langchain-anthropic, langchain-google-genai and langsmith. LangGraph is not on the list. It arrives only because LangChain depends on it. The composition is strictly nested, Deep Agents on LangChain on LangGraph, and not three peers added together. Nothing at the runtime or framework layer ever owned context hygiene, because the layer meant to own it sits above both.
The thesis: a state problem, not a reasoning problem
Complex work is, for one specific and well-defined class of complexity, a state-management problem and not a reasoning problem.
There is a simple way to tell which kind of problem a task is. Picture a skilled colleague who loses all memory every ten minutes. If that colleague fails the job, the job was about state, and notes, delegation and a setup built around the memory loss will cure it. If a person who held the entire problem in mind throughout would still flounder, notes will not rescue anything, since the difficulty was reasoning all along.
Every mechanism a harness adds, whether a filesystem for offloading, subagents for isolation or a planning tool for tracking progress, treats the amnesia case. None of them makes any single reasoning step smarter. State management is therefore not a rival explanation to reasoning. It is the base that lets reasoning build up over many steps and not reset at each one. A model that reasons correctly at every step can still fail a two-hundred-step task if step 150 cannot see what was decided at step 12. The reasoning was not wrong. It never had continuity to work with.
This foundation is not sufficient by itself, and pretending it is would weaken the rest of the argument. Sticking with a failing strategy while the correct, complete history sits in plain view is a pure reasoning failure, and flawless state handling does not mend it. Likewise, a model whose own retelling of events drifts from what actually happened has a reasoning problem even when the true record is intact and retrievable. The fair version of the claim reads like this: for the failures a harness targets, meaning work that degrades because it outlives one clean context, managing state is the dominant lever, while reasoning quality is the harder remainder that scaffolding cannot touch. The sections below cover what three teams built to pull that lever.
Four pillars with many parts
What Deep Agents is
Deep Agents ships as an open-source Python package under the MIT license, installed through pip, currently at 0.7.5. Around it sit a TypeScript port, a terminal CLI called deepagents-code and a hosted, managed edition still in private beta. It is not a peer of LangChain and LangGraph. As shown earlier, it depends on LangChain directly and only picks up LangGraph through that route. Calling create_deep_agent() ends up in LangChain's create_agent(), which produces a LangGraph graph, so any durability the agent enjoys is inherited from whatever LangChain passes along.
The four pillars and the stack beneath them
According to LangChain, four ingredients make an agent deep: a planning tool, subagent delegation, filesystem access and a rich prompt. Harrison Chase put forward that framing after noticing the same shape in Claude Code, Deep Research and Manus. As a mental model it works well, because those four categories really do mark the difference between a shallow loop and an agent that can finish a long job.
Four was a starting description, not the final blueprint. create_deep_agent() assembles each capability from a larger and still-growing set of middleware that can be combined freely. The list covers files, subagents, summarization, patching of tool calls, assorted prompt-caching variants tied to specific providers, memory, skills and human approval, plus any custom layer a developer writes. The pillars name what the agent can do. The middleware stack is how those abilities are actually produced, and it keeps shifting.
Only two parts of the stack are structurally required, and that matters more than any count. FilesystemMiddleware is required because it enforces the permission rules that gate file access, which a source comment calls a security guarantee. SubAgentMiddleware is required because it is what the task tool calls. Excluding either raises an error and does not degrade silently. Everything else is optional and only assembled on request.
Anyone building on it now should know that two of the original four categories have become opt-in. As of v0.7.0, the planning tool (write_todos) is not part of the default stack, and the base system prompt ships empty, per the deprecation notice. Each takes one line of configuration to restore. A plain create_deep_agent() call gives file access and subagent delegation out of the box, and gives planning and a detailed prompt only on request.
Harness profiles, the tuning layer a newer model does not get
Under the middleware lies a separate tuning mechanism called a harness profile. A profile attaches extra system-prompt text and removes certain tools, selected by the precise model string handed to the agent. Do not confuse it with ProviderProfile, which governs how the model object is constructed. A HarnessProfile shapes behavior after construction.
Lookup requires the exact registered provider:model key, with a provider-wide entry as the fallback when there is none. Out of the box, profiles exist for the Codex family from OpenAI and for three Claude generations: Haiku 4.5, Sonnet 4.6 and Opus 4.7. The Sonnet 4.6 profile is more than generic prompting advice. It follows Anthropic's own published guidance so closely that comments in the code say as much, and it deliberately omits the Opus-oriented warnings about eagerness and heavy subagent use, which were judged irrelevant for Sonnet.
A model released one generation after the registered ones gets nothing. When checked against the resolver, claude-sonnet-5, anthropic:claude-sonnet-5 and plain anthropic each return no profile. With no exact hit, and with no provider-wide Anthropic entry to fall back on (only profiles pinned to individual releases exist), nothing matches. The miss is entirely silent. By the resolver's own documentation, the only log line is a debug note emitted when a provider-level fallback is used. If both lookups fail, no log is written at all.
An agent built on the newest model in a fast-moving family therefore gets exactly the tuning of an agent built on a model the framework has never heard of, which is none, with no sign that this is what happened.
How it runs
One abstraction under everything
All of this middleware rests on a single mechanism that belongs to LangChain and not to Deep Agents. LangChain 1.0 added points where code can run when a run starts or ends, before and after each model call, and around every tool call, which lets a single agent loop support quite different designs. Before-hooks fire in the order they were registered, and after-hooks fire in the opposite order, which mirrors how web frameworks nest their middleware. A wrapping hook can retry a call, serve it from a cache or intercept it entirely. Supervisor setups, swarms, reflection loops and deep agents all turn out to be one loop with different hooks attached, and create_deep_agent() is simply one opinionated selection of them.
Files: two backends, two guarantees
Files can live in two places, and which is active changes what an agent's write actually means.
StateBackend, the default, holds files in a channel of LangGraph state: graph data with its own merge rules, not a disk. It works only during a live graph run. Whether the contents outlive a single call depends on having both a checkpointer and a stable thread ID. Without them every invocation begins blank, which explains why a barecreate_deep_agent()always shows an empty working directory.FilesystemBackendperforms genuine disk reads and writes, bringing durable storage along with genuine danger. Its source states plainly that it suits local development and CI/CD and does not suit anything exposed to users, since the agent can read whatever it can reach, secrets included, and its edits cannot be undone. With the defaultvirtual_mode=True, access stays inside a root directory, traversal is rejected on both the raw and the resolved path, and real absolute paths are hidden from the agent's output so the root's location is not revealed.
One permission system covers both. A rule pairs an operation with a path pattern and one of three modes: allow, deny or interrupt. Deletion is evaluated against the write rules, with care. A recursive delete is refused if any deny rule overlaps anything beneath the target, not just the first rule that matches, so an early broad allow cannot authorize erasing something a later, narrower rule was written to protect.
Subagents, isolated by design
Subagents are reached the same way as any tool. When the model calls task, a completely separate agent starts, equipped with its own model, tools and middleware. Its model setting accepts either a provider:model string or an already built model object, so placing a subagent on a different provider than the parent takes one line of configuration, not an engineering effort.
Memory does not carry over automatically. The subagent spec has no memory field, and the logic that passes permissions down has no counterpart for memory. The isolated, clean context is intentional, which means anything saved through the parent's memory is invisible to a subagent unless it is given its own memory middleware.
Memory is revisable, not authoritative
A memory file, once loaded, does not go straight into the system prompt. It arrives wrapped in a clearly marked block, with framework-added instructions telling the model the content is reference material that may be stale or authored by someone else, not a command to follow without question. That is a lower trust level than the system prompt, and it fits the intended role: something the agent reads and amends while working, not a rulebook a developer writes once and expects to carry full authority.
A scaling detail
Conversation state relies on a custom reducer named DeltaChannel, designed so that checkpoint storage grows linearly, O(N), as a conversation lengthens, instead of quadratically, O(N²).
Behavior at the edges
Two conditions deserve understanding before deciding how heavily to lean on subagents in production, both checked against the current repository.
One failure, many casualties (issue #694). If several subagents run side by side and one throws an exception, the rest are cancelled with it. The batch succeeds or fails as a unit, with no isolation between members. The report dates from January 2026 and an early release, and the issue has since been closed, although the code change that fixed it cannot be pinpointed in either the subagent code or LangChain's tool execution layer. Even so, plan around it: treat any fan-out of independent subtasks as able to fail partially until confirmed otherwise on the version actually deployed.
Errors surfacing to the wrong reader (#947). After a tool returns an error, the model should normally retry or adjust. In some observed runs, though, it addressed the end user directly with text that was meant as an internal correction, so a person saw an error message never intended for them. The issue was still open when checked. A separate report notes that after a later framework update, certain tool exceptions stop the agent outright instead of reaching the model as a retryable message, where earlier versions carried on past them.
Two older reports in the same area are closed: subagents ignoring the parent's recursion limit (#1698) and interrupt resume breaking for edit and reject decisions (#554). The recursion-limit fix was also verified against the installed source. They show how fast problems on this surface tend to get fixed, so they are no reason to alter a design.
Practical alternatives
Three practical options exist today for building a long-horizon agent: Deep Agents, the Claude Agent SDK and the OpenAI Agents SDK. Each reflects a different choice about what its team owns and what it pushes outward, and none is an incomplete version of the others.
One distinction is confused more than any other, the difference between a handoff and a subagent. Two of the three options use the same underlying pattern, reached independently by two teams. The third is a genuinely different mechanism.
Choosing among them comes down to two questions and not a ranking, and neither has a universal answer. Each can only be answered against the real constraints of a specific system.
What to take away
Long-horizon agents need scaffolding because accumulated context dilutes the signal long before a window fills. Deep Agents assembles that scaffolding under a four-pillar framing, backed by a changing stack of middleware, with a filesystem, subagents and memory that each carry specific guarantees and specific gaps. Knowing the gaps, such as silent profile misses for newer models, memory invisible to subagents, all-or-nothing parallel failures and tool errors leaking to users, is what separates using the framework deliberately from trusting its defaults.