← Back to list

Inside Deep Agents: The Architecture Quietly Powering Every AI System in 2026

Claude Code. Deep Research. Manus. They all use the same four-pillar pattern, and you can build it in Python in under an hour.

Sanjana Dubey · 2026-06-14 10:29 · 50 claps · 11.6 min read
#deep-agent #langchain #langgraph #shallow-agent #deep-research-agent
Open on Medium ↗
Wiki topics: LLM · Large Language Models AGT · AI Agents 🔧 · Data Engineering 🏛️ · Architecture

Inside Deep Agents: The Architecture Quietly Powering Every AI System in 2026

Claude Code. Deep Research. Manus. They all use the same four-pillar pattern, and you can build it in Python in under an hour.

Source: Image by Sanjana Dubey.

Source: Image by Sanjana Dubey.

If you’ve shipped any kind of LLM agent, you’ve felt it. The demo works. The Twitter clip looks great. Then you ask it to do something real like “research our three biggest competitors, compare their pricing, and write a positioning memo” and the wheels come off. The context window fills with junk. The agent forgets what it was doing. It re-searches things it already found. By turn ten, it produces a generic summary that could’ve been written by skimming the homepages.

This isn’t a model problem. GPT-5, Claude Sonnet 4.6, Gemini 3 they’re all smart enough. It’s an architecture problem. You built a shallow agent when the task needed a deep one.

The interesting thing is, the systems that don’t fail at this Claude Code, OpenAI’s Deep Research, Manus they all use the same architectural pattern. The LangChain team called it deep agents, packaged it into a library called deepagents, and now any developer can use it.

In this post:

  1. What shallow agents are and why they fail
  2. What makes a deep agent “deep” and the four pillars
  3. The minimum viable deep agent in 3 lines
  4. Three complete worked examples with code you can run today

Let’s go.

Part 1: The Shallow Agent Problem

A shallow agent is the default thing you build when you follow any “build an AI agent” tutorial. It’s the ReAct pattern: the model thinks, calls a tool, observes the result, thinks again, calls another tool, and loops until it decides it’s done.

User → [Think → Act → Observe] → [Think → Act → Observe] → ... → Answer

That’s it. The whole “brain” of the agent lives in the conversation history inside the context window. There’s no plan, no memory outside that window, no delegation. Just a loop.

This is fine for one-shot tasks. “What’s the weather in Mumbai?” one tool call, done. “Look up this customer’s last order”, easy. But the moment the task needs more than three or four steps shallow agents start failing in predictable ways:

  • Context overflow. Every search result, every tool output, every intermediate thought stays in the conversation. By turn 15, half your context is stale search snippets, and the model can’t see its own original goal anymore.
  • No strategic planning. The agent reacts to whatever just happened. It can’t see two steps ahead, so it pursues whatever feels relevant in the moment and loses the thread.
  • Stuck-in-a-loop failure. ReAct agents frequently hit the pattern: search → not satisfied → search again → still not satisfied → search again → timeout. You need external guardrails to break them out.
  • No delegation. Every subtask runs in the same context as everything else. A 12-step task ends up with 12 steps’ worth of noise polluting the final synthesis.

Source: Image by Sanjana Dubey.

Source: Image by Sanjana Dubey.

Part 2: What Makes an Agent “Deep”

Deep agents are still LLMs in a loop calling tools. The core algorithm is identical. What’s different is what’s wrapped around the loop.

The pattern was reverse-engineered from production systems. Anthropic’s Claude Code, OpenAI’s Deep Research, and Manus all share four characteristics, and that’s what deepagents packages up for you.

1. A long, opinionated system prompt

Shallow agents get prompts like “You are a helpful assistant. Use tools when needed.” Deep agents get prompts that are several paragraphs long, with detailed instructions on how to use each tool, when to use it, and few-shot examples for tricky cases. The prompt is half the agent.

2. A planning tool (write_todos)

Before doing anything, the agent writes a TODO list. Here’s the wild part: the planning tool is essentially a no-op. It doesn’t do anything, it just records the plan. But forcing the model to write its plan down dramatically improves multi-step coherence, because the plan stays visible in context as the agent works.

3. A virtual file system

The agent has read_file and write_file tools backed by a sandboxed virtual filesystem. Instead of cramming every search result into context, the agent writes intermediate notes to files and reads only what it needs. The context window stops being a junk drawer and becomes a working memory.

4. Subagents

When a chunk of work is self-contained “go research competitor X” the main agent spawns a subagent via a task tool. The subagent has its own clean context, does the work, and returns just the summary. The supervisor never sees the noise. This is context isolation as an architectural primitive, and it's the single biggest win of the deep agent pattern.

Source: Image by Sanjana Dubey.

Source: Image by Sanjana Dubey.

Part 3: The Setup

pip install deepagents tavily-python
export ANTHROPIC_API_KEY="your-key-here"
export TAVILY_API_KEY="your-key-here"  # free tier is fine

You can swap Anthropic for OpenAI, Gemini, OpenRouter, Fireworks, or local Ollama, deepagents accepts any provider:model string LangChain supports.

A minimal deep agent is three lines:

from deepagents import create_deep_agent

agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-6",
    tools=[my_custom_tool],
    system_prompt="You are a research assistant...",
)

Planning, filesystem, subagent scaffolding, and conversation summarization are all wired in for you. But what does “wired in” actually mean? Under the hood, deepagents is built on LangGraph, and that one create_deep_agent call assembles a graph of middleware around your model. You can see it yourself:

from IPython.display import Image, display
display(Image(agent.get_graph().draw_mermaid_png()))  # renders the diagram below

Here’s what that graph looks like:

Source: Image by Sanjana Dubey.

Source: Image by Sanjana Dubey.

Let’s trace the flow:

  • **__start__**: the entry point where your message enters the graph.
  • **PatchToolCallsMiddleware.before_agent* : runs before* the model sees anything. It cleans up and normalizes tool-call formatting so different model providers behave consistently. This is the kind of plumbing you'd otherwise have to write yourself.
  • **model**: the core node: your LLM. It looks at the conversation, the plan, and the available tools, then decides what to do next.
  • From model, the graph branches two ways (the dotted lines are conditional edges):
  • **tools* : if the model wants to call a tool (search the web, read a file, write a TODO, spawn a subagent), it routes here, executes, and loops back* to model with the result.
  • **TodoListMiddleware.after_model:*runs after* each model step to manage the running TODO list, keeping the plan visible and up to date.
  • **TodoListMiddleware.after_model then either loops back to model (more work to do) or exits to `end`** (task complete).

The key insight: this is still the same think → act → observe loop a shallow agent uses, but wrapped in middleware that handles planning and tool-call hygiene automatically. That model ↔ tools cycle is the engine; the middleware nodes around it are what make the agent deep. You didn't write any of this graph. The three-line create_deep_agent call built it for you.

Now let’s build three real ones.

Example 1: Competitor Analysis Agent

Problem: Given a list of competitors, research each one, compare their pricing and features, and produce a structured positioning memo.

Why a deep agent? Each competitor needs independent, focused research. In a shallow agent, all 3 competitors’ search results would pollute one context, the model would conflate features and lose nuance. Subagents give each competitor its own clean research thread.

Source: Image by Sanjana Dubey.

Source: Image by Sanjana Dubey.

import os
from typing import Literal
from tavily import TavilyClient
from deepagents import create_deep_agent

tavily = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
def internet_search(query: str, max_results: int = 5,
                    topic: Literal["general", "news", "finance"] = "general"):
    """Run a web search."""
    return tavily.search(query, max_results=max_results, topic=topic)

# One reusable subagent that researches whatever competitor it's given
competitor_researcher = {
    "name": "competitor-researcher",
    "description": (
        "Researches a single competitor company in depth: pricing, features, "
        "target market, recent news, and strategic positioning."
    ),
    "system_prompt": (
        "You are a competitive intelligence analyst. Given a company name, use "
        "internet_search to gather: (1) pricing tiers, (2) top 5-7 features, "
        "(3) target customer segment, (4) recent product launches or news, "
        "(5) public positioning vs. competitors. "
        "Write your findings to a file named `<company>.md` and return a 3-sentence summary."
    ),
    "tools": [internet_search],
}
main_instructions = """You are a senior product strategist. Given a list of
competitors, produce a structured comparison memo.
## Workflow
1. Write a TODO list of competitors to research.
2. For EACH competitor, spawn the `competitor-researcher` subagent.
3. Once all subagents have written their notes files, read them all.
4. Produce a final memo with:
   - Side-by-side feature comparison table
   - Pricing comparison table
   - Positioning analysis (who's targeting what segment)
   - Strategic recommendations: where are the gaps?
## Important
- ALWAYS delegate per-competitor research to subagents. Do not search yourself.
- The final memo must be polished markdown.
"""
agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-6",
    tools=[internet_search],
    system_prompt=main_instructions,
    subagents=[competitor_researcher],
)
if __name__ == "__main__":
    request = (
        "Analyze our competitors in the productivity SaaS space: Notion, "
        "Coda, and ClickUp. We're a small startup building an AI-first "
        "alternative. Write a memo highlighting positioning gaps we could exploit."
    )
    result = agent.invoke({"messages": [{"role": "user", "content": request}]})
    print(result["messages"][-1].content)

What happens: The main agent writes a TODO list, then calls task three times, once per competitor. Each subagent does focused research with a clean context, dumps findings to notion.md, coda.md, clickup.md. The supervisor reads only those clean notes and produces the memo. No search-result noise reaches the synthesis step.

Example 2: Codebase Documentation Agent

Problem: Point an agent at a code repository and have it generate a README.md plus an ARCHITECTURE.md from scratch.

Why a deep agent? Real repos have dozens of files. A shallow agent dumps all the code into context and runs out of room. A deep agent walks the directory, delegates per-module analysis to subagents (each reads only its own module), and stitches the summaries into docs.

Source: Image by Sanjana Dubey.

Source: Image by Sanjana Dubey.

import os
from pathlib import Path
from deepagents import create_deep_agent

# ---- Filesystem tools scoped to a repo ----
REPO_ROOT = Path(os.environ.get("REPO_PATH", ".")).resolve()
def list_repo_files(subpath: str = ""):
    """List files and directories under the repo (or a subpath)."""
    target = (REPO_ROOT / subpath).resolve()
    if not str(target).startswith(str(REPO_ROOT)):
        return "Error: path outside repo"
    return [str(p.relative_to(REPO_ROOT)) for p in target.rglob("*")
            if p.is_file() and not any(part.startswith(".") for part in p.parts)]
def read_repo_file(filepath: str):
    """Read a single file from the repo by relative path."""
    target = (REPO_ROOT / filepath).resolve()
    if not str(target).startswith(str(REPO_ROOT)):
        return "Error: path outside repo"
    try:
        return target.read_text()
    except Exception as e:
        return f"Error reading {filepath}: {e}"

# ---- Subagent: analyzes ONE module ----
module_analyst = {
    "name": "module-analyst",
    "description": (
        "Reads all files in a single module/directory and produces a "
        "structured summary: purpose, key classes/functions, dependencies, "
        "and how it fits into the larger system."
    ),
    "system_prompt": (
        "You are a code analyst. Given a directory path, use list_repo_files "
        "and read_repo_file to understand the module. Produce: "
        "(1) one-paragraph purpose, (2) key public APIs (classes/functions), "
        "(3) external dependencies, (4) integration points with other modules. "
        "Write to `docs_<module_name>.md` and return a 2-sentence summary."
    ),
    "tools": [list_repo_files, read_repo_file],
}

# ---- Main orchestrator ----
main_instructions = """You are a senior engineer writing documentation for an
unfamiliar codebase.
## Workflow
1. Use list_repo_files to see the top-level structure.
2. Identify the major modules (directories with code).
3. Write a TODO list.
4. For EACH module, spawn the `module-analyst` subagent.
5. After all subagents finish, read their notes files.
6. Produce TWO outputs in your final message:
   - `README.md`: project overview, features, quickstart
   - `ARCHITECTURE.md`: module breakdown, data flow, key design decisions
## Important
- Never read source files directly in the main agent - delegate to subagents.
- Use the notes files as your source of truth for the final docs.
"""
agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-6",
    tools=[list_repo_files, read_repo_file],
    system_prompt=main_instructions,
    subagents=[module_analyst],
)
if __name__ == "__main__":
    # Set REPO_PATH env var to the repo you want to document
    request = "Generate README.md and ARCHITECTURE.md for this codebase."
    result = agent.invoke({"messages": [{"role": "user", "content": request}]})
    print(result["messages"][-1].content)

What happens: The main agent lists the directory, identifies modules (api/, core/, utils/, etc.), and spawns a module-analyst for each. Each subagent reads only its own module's files, summarizes, and writes a notes file. The supervisor reads the clean notes, never the raw source and produces polished docs. A 50-file repo becomes manageable because no single context ever holds all 50 files.

Example 3: Research Paper Summarizer

Problem: Given a research topic, find recent papers, summarize each one’s contribution, identify common themes, and produce a literature review.

Why a deep agent? Each paper deserves focused reading. A shallow agent would skim 5 papers in one bloated context and produce a soupy summary that conflates methodologies. With subagents, each paper gets a dedicated reader, and the supervisor synthesizes from clean per-paper notes.

Source: Image by Sanjana Dubey.

Source: Image by Sanjana Dubey.

import os
from typing import Literal
from tavily import TavilyClient
from deepagents import create_deep_agent

tavily = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
def internet_search(query: str, max_results: int = 5,
                    topic: Literal["general", "news"] = "general",
                    include_raw_content: bool = False):
    """Search the web. Set include_raw_content=True for full paper text."""
    return tavily.search(query, max_results=max_results, topic=topic,
                         include_raw_content=include_raw_content)

# Subagent 1: finds relevant papers
paper_finder = {
    "name": "paper-finder",
    "description": "Searches for recent academic papers on a research topic.",
    "system_prompt": (
        "You are an academic search specialist. Given a topic, use "
        "internet_search to find 5-8 relevant peer-reviewed papers or "
        "arxiv preprints from the last 2 years. For each, capture: title, "
        "authors, year, venue, URL, and a 1-line claim. "
        "Write to `papers_index.md` and return the list of URLs."
    ),
    "tools": [internet_search],
}
# Subagent 2: reads and summarizes ONE paper
paper_summarizer = {
    "name": "paper-summarizer",
    "description": (
        "Reads a single paper (via URL with raw content) and produces a "
        "structured summary."
    ),
    "system_prompt": (
        "You are a research analyst. Given a paper URL, use internet_search "
        "with include_raw_content=True to fetch the paper. Then produce: "
        "(1) problem being solved, (2) method/approach, (3) key results, "
        "(4) limitations, (5) how it relates to other work in the field. "
        "Write to `paper_<short_id>.md` and return a 2-sentence summary."
    ),
    "tools": [internet_search],
}

main_instructions = """You are a researcher writing a literature review.
## Workflow
1. Spawn `paper-finder` with the research topic.
2. Read `papers_index.md` to see what was found.
3. Write a TODO list - one item per paper to summarize.
4. For EACH paper, spawn `paper-summarizer` with the URL.
5. After all summaries are written, read them.
6. Produce a literature review with:
   - 2-paragraph intro framing the field
   - Thematic sections (cluster papers by approach)
   - Comparison of methods and results
   - Identified gaps and open questions
   - Properly formatted references list
## Important
- Always delegate per-paper reading to subagents. Never read papers directly.
- Cite specific papers throughout the review.
"""
agent = create_deep_agent(
    model="anthropic:claude-sonnet-4-6",
    tools=[internet_search],
    system_prompt=main_instructions,
    subagents=[paper_finder, paper_summarizer],
)
if __name__ == "__main__":
    request = (
        "Write a literature review on RLHF (reinforcement learning from "
        "human feedback) applied to code generation, covering work from "
        "2024 to 2026."
    )
    result = agent.invoke({"messages": [{"role": "user", "content": request}]})
    print(result["messages"][-1].content)

What happens: First, paper-finder runs a broad search and writes a paper index. The main agent then plans one TODO per paper and spawns paper-summarizer for each, each summarizer reads its paper in isolation and writes a structured note. The supervisor reads only the per-paper notes, clusters them by theme, and writes the review. This pattern (find → fan-out → synthesize) is the workhorse pattern of deep agents.

When Not to Use Deep Agents

Deep agents are overkill for:

  • One-shot tool calls (“what’s the weather”)
  • Simple Q&A or RAG over a single corpus
  • Anything that fits comfortably in a single prompt-and-response
  • Latency-sensitive workflows: planning + subagents = more LLM calls

Reach for deep agents when tasks are long, multi-step, and benefit from the model writing things down. All three examples above share that shape: research that fans out, analysis that needs isolation, and synthesis that depends on clean notes.

Wrapping Up

The shift from shallow to deep agents is the same kind of shift web dev made when it went from spaghetti PHP files to MVC frameworks. The core idea (LLM in a loop calling tools) doesn’t change. What changes is the scaffolding around it. Planning, memory, delegation, and detailed prompting aren’t optional polish, they’re what separates a demo from something you’d ship.

The good news: the harness is already written. Your job is to give it sharp tools, a sharp prompt, and well-scoped subagents.

If you build something with this, drop a link in the comments, I’d love to see what you make.

Found this useful? Hit the 👏 button, follow for more on building production AI agents, and check out the LangChain deepagents docs and the original LangChain blog post on deep agents for the source material.


메타데이터
post_id
b61c1486486e
slug
inside-deep-agents-the-architecture-quietly-powering-every-ai-system-in-2026-b61c1486486e
url
https://medium.com/@dubeysanjana23/inside-deep-agents-the-architecture-quietly-powering-every-ai-system-in-2026-b61c1486486e
canonical_url
https://medium.com/@dubeysanjana23/inside-deep-agents-the-architecture-quietly-powering-every-ai-system-in-2026-b61c1486486e
author_url
https://medium.com/@dubeysanjana23
status
ok
fetched_at
2026-06-26 03:39:16