Home / Articles

Deep Agents on LangChain: Planning, Sub-Agents and a Banking Gateway

How the deepagents harness adds planning, a file system and sub-agents to a tool loop, how it differs from ReAct, and how to split tools across domain agents behind a gateway.

Publisher: REACTAPP.TOP9 min read2002 words

This article is published in English.

Sources: Beyond Simple Agents: Engineering DeepAgents for Enterprise with LangChain

Plenty of teams can get an agent working on a laptop. Far fewer can keep one alive in production, and that gap is where deep agents earn their keep. The idea isn't a buzzword. It is a response to a concrete limit: a plain tool-calling loop falls apart once the work gets long, multi-part or messy.

The limit of the simple loop

The most basic agent is an LLM running in a loop and calling tools. Such an agent tends to be shallow, meaning it can't plan across a long stretch of work or finish a complicated, many-step task. It handles a single question fine and stumbles on anything bigger.

Products like Deep Research, Manus and Claude Code got past this with four ingredients: a planning tool, sub-agents, a file system and a detailed prompt. The deepagents project grew out of an attempt to understand what made Claude Code so general, and to push that further. LangChain's term for agents of this kind is "deep agents", because they can plan complicated work and carry it out over longer periods. In effect it is LangChain's route to Claude Code-style autonomous agents that can handle real, multi-step tasks, not only one-off questions.

deepagents is a standalone library built from LangChain's core agent building blocks and run on the LangGraph runtime, which supplies durable execution, streaming and human-in-the-loop support. A good description is an agent harness: the same tool-calling loop as other frameworks, packaged with extra built-in tools and capabilities.

The four ingredients

Planning. A built-in write_todos tool lets the agent split a big job into discrete steps, track progress and revise the plan as new information arrives.

A file system for context. Tools like ls, read_file, write_file and edit_file let the agent push bulky context out to memory or disk, so the context window doesn't overflow and tool results of any size can be handled. Older messages are summarized automatically once the conversation grows long.

Sub-agents. A built-in task tool spawns specialist sub-agents with isolated context. The main agent stays uncluttered while a helper goes deep on one subtask.

Long system prompts. The prompts rebuilt from Claude Code are lengthy and full of instructions on tool use, plus few-shot examples of how to act in specific situations. Without them these agents would be far less capable. Prompting still matters.

Where it sits in the LangChain stack

There are three layers, each building on the one below. LangGraph is the agent runtime, suited to mixing workflows and agents. LangChain is the agent framework, suited to using the core loop and writing prompts and tools from scratch. Deep Agents is the harness, suited to autonomous, long-running agents that benefit from built-in planning, a file system and similar extras. The harness is built on LangChain's agent abstraction, which in turn sits on LangGraph.

A banking example

Imagine a bank exposing five domain tools: find ATMs by ZIP code, list investment officers in a town, fetch the companies in the bank's investment portfolio, give details on retirement plans, and find branches across regions. Each handles one narrow job well. The trouble starts when a user asks something spanning several of them at once:

Can you list ATM centers for XYZ Bank along with the companies in its investment portfolio, and also give the locations of its investment officers in Chicago?

Answering that means detecting several intents in one request, choosing and coordinating the right tools, keeping context across different data domains and putting the results together into one coherent reply. Architectures built for single-step or linear flows often stumble here. This is the situation deep agents are made for.

The sequence runs like this.

  1. Plan. The main orchestrator reads the compound question and uses write_todos to break it into three tasks: get the ATM centers, get the portfolio companies and get the officers in Chicago.
  2. Spawn. Instead of working through all three serially in one bloated context, it starts three sub-agents, one per task, each with its own context window so they don't interfere.
  3. Call tools. Each sub-agent uses precisely the right tool of the five: get_atm_centers(zipcode=...), get_trusted_portfolio_companies() and get_investment_officers(city="Chicago").
  4. Synthesize. Results go back to the orchestrator, which merges them into one well-formatted answer and ticks off the todos.

A basic tool-calling agent would handle all three in sequence inside one context. It could forget early results, lose track of which question it was answering or hit the context limit. A deep agent keeps the concerns separate and follows progress through the todo list, so even if one call fails or returns partial data, the orchestrator knows where it stands.

The wiring is small:

from deepagents import create_deep_agent

agent = create_deep_agent(
    tools=[
        get_atm_centers,         # your ATM tool
        get_investment_officers, # your officers tool
        get_portfolio_companies, # your portfolio tool
        get_retirement_plans,    # your retirement tool
        get_bank_locations,      # your locations tool
    ],
    system_prompt="You are XYZ Bank's AI assistant. Always answer completely and cite sources."
)

result = agent.invoke({"messages": [{"role": "user", "content": "Can you give me a list of ATM centers for XYZ bank and also list the name of companies XYZ has in its investment portfolio? Also can you give me the location of XYZ investment agents in Chicago?"}]})

The agent decides which tools to call, in what order and how to combine what comes back. You write no routing logic yourself, and that is the real value of the harness.

ReAct against deep agents when a tool is missing

Take the same request and add a fourth question for which no tool exists: a list of credit card options for students. Here is how each design copes.

A deep agent checks its tool registry and builds the todo list before it acts, so it sees at the start that nothing covers credit cards. It launches sub-agents for the three answerable tasks, which run in parallel and finish normally, marks the credit card item as "no tool available" and returns one synthesized answer. That answer fully covers the three questions and tells the user plainly that no credit card tool is configured and that one may need adding. Nothing breaks, and the partial success is reported cleanly.

ReAct, short for Reasoning plus Acting, follows a strict Thought, Act, Observe cycle one step at a time with no upfront plan. That causes three problems:

  • It works sequentially, so ATMs, then portfolio, then officers. The credit card gap only appears at the very end, after everything else is done.
  • It never scans the available tools. It finds the missing one at runtime when it tries to call it, and depending on the LLM and implementation it may stop, retry with an invented tool name or, worst of all, make up a believable list of credit cards because it expects an answer to exist.
  • Because the failure comes mid-loop, nothing guarantees the earlier three results reach the user cleanly. The agent might simply error out and return nothing.

ReAct suits short, well-defined jobs where every tool is known to exist. Deep agents are for the messier real world, where questions are compound, tools can be absent and you still want to deliver as much value as you can from what you have.

The design question: one agent with all the tools?

Does this mean we should write lots of tools and expose them all through one agent? No. A single agent holding 50 or more tools is an anti-pattern. The better shape is a hierarchy of agents. Picture an agent whose configuration lists hundreds of tools, from web search and email to flight booking and rocket launches, with an instruction to "figure it out". It might work some of the time, and now and then it answers a late-shipment complaint by booking a flight, filing taxes and ordering a pizza. Nobody wants that.

Domain ownership

The guiding rule is that each domain agent owns a small, cohesive group of tools, ideally three to five, from a single business area. A gateway agent never touches tools. It only reads intent, routes to the right domain agent and stitches the answers together. It is the same separation of concerns you'd use with microservices.

In the banking case the natural split is:

  • A branch and ATM agent with get_atm_centers and get_bank_locations, covering physical presence: ZIP codes, cities, opening hours.
  • An investments agent with get_portfolio, get_officers and get_retirement_plans, covering money management, including financial regulations, officer certifications and plan types.
  • A products agent with get_credit_cards once it exists, plus loans and savings accounts, covering the product catalog. When a tool is missing, only this agent reports the gap and the others carry on untouched.

The code mirrors that layout:

from deepagents import create_deep_agent
# Each domain agent is self-contained
branch_agent = create_deep_agent(
    tools=[get_atm_centers, get_bank_locations],
    system_prompt="You are the Branch & ATM specialist for XYZ Bank..."
)
investments_agent = create_deep_agent(
    tools=[get_portfolio, get_officers, get_retirement_plans],
    system_prompt="You are the Investments specialist for XYZ Bank..."
)
products_agent = create_deep_agent(
    tools=[get_credit_cards],  # sparse now, grows over time
    system_prompt="You are the Products specialist for XYZ Bank..."
)
# Gateway knows nothing about tools — only about agents
gateway = create_deep_agent(
    tools=[
        branch_agent.as_tool(name="branch_atm_agent"),
        investments_agent.as_tool(name="investments_agent"),
        products_agent.as_tool(name="products_agent"),
    ],
    system_prompt="""You are the XYZ Bank assistant gateway.
    Parse the user's intent, delegate to the right domain agent(s),
    and synthesize a unified response. Never answer directly — always route."""
)

Why this scales

As the bank adds capabilities, the gateway and the other domain agents stay unchanged. You add a tool to the right domain agent, or create a new one such as Loans or Fraud and register it with the gateway. Any change is contained to one domain.

Compare a forest of tools. An agent with 30 of them has no sense of domain and might call get_retirement_plans while answering a question about ATM hours merely because both are in scope. Domain agents avoid that by giving each one a tight identity through its system prompt and toolset. An agent that knows who it is reasons more accurately in its own lane.

A useful test: if you're arguing about whether a tool belongs to agent A or agent B, the tool is either doing too much and should be split, or you need a third domain. The boundary should feel as obvious as knowing whether a question goes to the branch manager or the investment advisor.

Open questions

Two things remain: whether deep agents depend heavily on the choice of underlying model, and whether a full end-to-end solution can be built with them. The answer to the second is yes, and the natural next step is a hands-on build of this banking scenario.