LLMs no longer live only inside chat websites
Major LLM providers—including OpenAI, Google Gemini, Anthropic Claude, and aggregators such as OpenRouter—now expose APIs that let us call their models from our own code. We no longer have to use AI only through a provider’s website. We can design an AI assistant that thinks, responds, and acts according to rules we define.
The problem is that provider APIs still differ: message formats, tool calling, streaming, and model-specific capabilities are not identical. Writing and maintaining every integration ourselves gets tiring quickly. This is where a library such as LangChain helps. Its model interface and provider integrations let more of the agent code share a common shape while still exposing provider-specific configuration when needed.
This article covers three tools in the same stack—LangChain, LangGraph, and Deep Agents—what each one is good for, how they fit together, and how I combined all three in a real project.
Version note: these APIs move quickly. The examples and observations in this article were checked against
langchain1.5.x,@langchain/langgraph1.4.x,deepagents1.13.x, and@langchain/mcp-adapters1.1.x on 4 September 2026.
The short version
- If a standard agent loop made of a model, tools, and middleware is enough, start with LangChain.
- If you need to design the workflow graph, own multi-step state, branch on conditions, or pause and resume, use LangGraph directly.
- If the work is long and complex and you want planning, a filesystem, and subagents ready to use, start with Deep Agents.
These three tools do not compete, and you do not have to adopt them one layer at a time. LangChain’s Agent API is a high-level framework that runs on LangGraph. Deep Agents is a prebuilt agent harness that assembles several capabilities for complex work. Choose the entry point that matches the control your problem needs.
- Deep AgentsHarness
Start here whenthe work is long and complex, with planning, files, or delegated subagents
Planning · filesystem · subagents · context management
Composed from LangChain building blocks and run on LangGraph
- LangChainFramework
Start here whena standard agent loop with a model, tools, and middleware is enough
Model interface · tools · middleware · agent loop
The agent API runs on LangGraph
- LangGraphRuntime
Start here whenyou need to own the nodes, edges, state, branching, or pause-and-resume flow
Graph · state · persistence · interrupts · streaming
The layered view explains how the stack fits together; each “Start here when” block identifies the first API level that fits the job.
Level 1: LangChain when a standard agent loop is enough
If you want an AI assistant that answers questions, calls a small set of tools, and follows the familiar “model thinks → calls a tool → reasons over the result” loop, LangChain’s createAgent is already a sensible place to begin.
Three pieces matter most here:
- Model interface. Select providers through a model identifier or provider adapter while keeping most of the agent code unchanged. API keys and provider-specific capabilities still need their own configuration. OpenRouter now has a dedicated
@langchain/openrouterintegration. - Tools. Declare functions with schemas that the model can call, such as checking the time, calculating a value, or searching the web.
- Agent loop.
createAgentautomatically runs the “model thinks → calls a tool → reasons over the result” loop on top of a LangGraph runtime.
Here is the smallest useful example:
import { createAgent, tool } from "langchain"
import { ChatOpenAI } from "@langchain/openai"
import { z } from "zod"
const getTime = tool(async () => new Date().toISOString(), {
name: "get_current_time",
description: "Return the current system time",
schema: z.object({}),
})
const agent = createAgent({
model: new ChatOpenAI({ model: "gpt-4o-mini" }),
tools: [getTime],
systemPrompt: "You are a general assistant. Answer briefly and clearly.",
})
const result = await agent.invoke({
messages: [{ role: "user", content: "What time is it now?" }],
})
That is enough for a general assistant that can answer questions and call simple tools. It works well for first-line support bots, summarizers, and prototypes that need to test an idea quickly. To preserve a conversation across calls, add a checkpointer and use a thread_id. In production, use a durable checkpointer rather than process memory alone.
The limitation is not that LangChain cannot do human-in-the-loop work. createAgent already supports middleware that pauses for approval. But once a workflow needs many explicit nodes, edges, states, and conditions, writing LangGraph directly makes that structure clearer and gives you finer control.
Level 2: LangGraph when you need to own the workflow and invite human decisions
LangGraph is the lower-level runtime beneath LangChain’s createAgent. It lets us define an assistant as a graph: nodes are steps, edges choose the next step, and shared state is read or updated by each node.
Compared with leaving everything to a standard agent loop, LangGraph stands out when you need:
- An explicit sequence. Classify a request first, branch into route A or B, and combine the result later.
- Human-in-the-loop control.
interrupt()pauses the graph for confirmation or missing information and resumes it after the user responds. - Checkpoints. A checkpointer records each thread’s state. With durable storage, the workflow can continue even after the process restarts.
- Several streaming modes. These include
messagesfor model tokens,customfor events you emit, andvaluesorupdatesfor state.
This simplified graph asks a person to approve the draft before execution:
import {
StateGraph,
StateSchema,
MessagesValue,
MemorySaver,
START,
END,
interrupt,
Command,
} from "@langchain/langgraph"
import { z } from "zod"
const AgentState = new StateSchema({
messages: MessagesValue,
approved: z.boolean().default(false),
})
const checkpointer = new MemorySaver()
const graph = new StateGraph(AgentState)
.addNode("draft", async (state) => {
// Assume the model has already been created.
return { messages: [await model.invoke(state.messages)] }
})
.addNode("approve", async () => {
const approved = interrupt({
question: "Do you approve this draft for execution?",
}) as boolean
return { approved }
})
.addNode("execute", async () => {
// Perform the real action.
return {}
})
.addEdge(START, "draft")
.addEdge("draft", "approve")
.addConditionalEdges("approve", (state) =>
state.approved ? "execute" : "draft"
)
.addEdge("execute", END)
.compile({ checkpointer })
const config = { configurable: { thread_id: "t1" } }
// The first call pauses at approve and waits for a person.
await graph.invoke(
{ messages: [{ role: "user", content: "Draft a project plan for me." }] },
config
)
// Resume with the same thread_id after the person responds.
await graph.invoke(new Command({ resume: true }), config)
This example uses MemorySaver to stay concise, so it is suitable for an experiment inside one process. To continue after a process restart, replace it with a checkpointer backed by a database or other persistent storage.
LangGraph fits work where the steps matter as much as the answer: document approvals, systems that collect missing information in stages, or pipelines with rules at every transition.
Level 3: Deep Agents when specialist work needs planning, delegation, and context management
Some tasks require knowledge outside the model, many tools, a multi-step plan, and sometimes file operations. This is the territory Deep Agents addresses.
Deep Agents is an agent harness built from LangChain building blocks and run on LangGraph. It adds capabilities that long, complex work commonly needs:
- Planning through the
write_todostool, which breaks work down and tracks progress. - Subagents through the
tasktool. The main assistant delegates bounded work to specialists with their own prompts and tools, then receives a summary. This also isolates the specialist’s context from the main assistant. - A virtual filesystem with tools such as
ls,read_file,write_file,edit_file,glob, andgrep. The selected backend holds or retrieves large context; it does not necessarily expose the machine’s real filesystem. - Context management such as summarizing a long history and using files as working memory.
MCP (Model Context Protocol), meanwhile, is not exclusive to Deep Agents. It is a standard for connecting tools and context from external systems. We can load MCP tools through @langchain/mcp-adapters and pass them to either a LangChain agent or a Deep Agent.
Here is a shortened example:
import { createDeepAgent } from "deepagents"
import { MultiServerMCPClient } from "@langchain/mcp-adapters"
const mcp = new MultiServerMCPClient({
mcpServers: {
catalog: {
transport: "http",
url: "https://example.internal/mcp",
},
},
useStandardContentBlocks: true,
})
const mcpTools = await mcp.getTools()
const agent = createDeepAgent({
model,
tools: mcpTools,
systemPrompt: "You analyse product information ...",
subagents: [
{
name: "product-agent",
description: "Search and compare products in the catalogue",
systemPrompt: "Search thoroughly, then return a concise shortlist.",
tools: mcpTools.filter((tool) => tool.name.startsWith("search_")),
},
],
})
Deep Agents is a good fit for assistants that analyse internal business information, conduct multi-step research, or write code by reading a project, planning the change, and editing files.
Which one should you use—and how can they work together?
The “three layers” mental model helps explain their responsibilities. In practice, though, they are different entry points into one stack, not stages you must always assemble in sequence. LangChain supplies an agent framework for models, tools, and middleware. LangGraph supplies the lower-level runtime and workflow structure. Deep Agents supplies a ready-made harness for more complex work.
| Problem | Start with |
|---|---|
| A standard agent loop with only the necessary model, tools, and middleware | LangChain |
| A custom graph with multi-step state, branching, pause and resume, or controlled durable execution | LangGraph |
| Long, complex work involving planning, context management, a filesystem, and subagents | Deep Agents |
Because the stack shares building blocks, we can also take only the piece we need: use LangChain’s createAgent with middleware that runs inside the graph, or take createSubAgentMiddleware from Deep Agents without adopting the complete createDeepAgent harness.
What happened in practice: I combined all three
My project was a customer-support chatbot for a business. It needed to answer general questions, search products and articles in a backoffice system, read account information for authenticated customers, and keep every turn within a controlled time and token budget because it served real users at scale.
LangGraph controls
- Budget
- Deadline abort
- Tool allowlist
- Sequential calls
- Output filter
Tools visible to the main agent
- Current time
- Suggested replies
- task
task delegates within a defined scope
Specialist subagents
- ProductsSearch and compare the catalogue
- Customer accountExists only when the user is authorized
- ArticlesFind content and supporting information
Catalogue · Content · Customer account
This design uses LangChain as the core, LangGraph for controls, only the subagent middleware from Deep Agents, and MCP as the backoffice boundary.
LangChain was the core. I used createAgent as the main loop for every turn and a model adapter to switch providers through configuration without changing the basic agent structure. This let me compare price and quality across several models while keeping provider-specific settings separate.
LangGraph became the control layer. Instead of leaving the loop unconstrained, I wrote several single-purpose middleware components:
- budget counted model calls and total elapsed time in one budget for the whole turn, then forced a final answer at the limit
- deadline-abort cancelled a request when it reached the time ceiling, before the reverse proxy timed out on the user
- tool-guard allowlisted the tools available for that turn and returned a refusal message instead of an error for calls outside the list
- no-parallel forced sequential tool calls because, in this project’s tests, concurrent searches interfered with one another and reduced answer quality
- model-output removed material the model accidentally exposed, such as internal JSON or a tool name, before it reached the interface
I implemented these as middleware instead of rewriting the loop because LangGraph can stream several modes together: model tokens from messages, signals I emit through custom, and state from values or updates. I could merge those events into one ordered stream for the interface and store the filtered conversation in our own database.
I did not use LangGraph’s checkpointer or interrupt() because this workflow never had to pause halfway through a graph and resume later. Our primary database already owned the chat history. That history does not automatically replace a graph checkpoint, however. If the product later needs to resume halfway through a workflow, it will still need a checkpointer.
From Deep Agents, I took only the subagent capability. I initially tested the full createDeepAgent harness. With the version and configuration used at the time, I ran into two issues. First, the main assistant received filesystem tools even though a retail bot did not need them; in my measurement, that added roughly a thousand prompt tokens per model call. Second, the setup added a general-purpose subagent with broader tools than my scope allowed.
Those constraints depend on the version. Deep Agents can now configure filesystem tools and permissions more precisely, and createSubAgentMiddleware can disable the general-purpose agent. For this project, though, I still wanted a smaller harness that I controlled completely.
I therefore added only Deep Agents’ createSubAgentMiddleware to the graph and disabled the general assistant. The main assistant held just three tools: the current time, suggested follow-up replies, and task for delegation. Specialists handled products, customer accounts, and articles. Each specialist had its own prompt and only its own tool group. Crucially, each one received the same budget, deadline, and tool-guard middleware as the main assistant, so the same boundaries applied everywhere.
MCP supplied the tools. The product search, article search, and account lookup tools did not live inside the bot. A backoffice MCP server exposed them, and the bot distributed them according to the user’s permissions for that turn. If a user was not signed in, the account specialist did not exist at all.
In the version of @langchain/mcp-adapters used during development, I had to write a wrapper that attached a user identifier to every MCP request. Version 1.1.x now supports a beforeToolCall hook that can return per-call headers for HTTP and SSE transports. The lasting lesson is to lock versions and check changelogs—not to treat that old adapter limitation as permanent.
Two lessons from this build are worth keeping.
First, the largest token reduction I measured did not come from subagents by themselves. It came from reducing the number of tools visible to the main assistant from 15 to 3. Turns that needed no search became cheaper. Turns that delegated to a specialist became slower and more expensive because they introduced another model call. Architecture is a choice about where to pay the cost.
Second, unit tests cannot catch every failure in model decisions. I once had nearly two thousand passing tests, then a real-question eval revealed that the bot had stopped calling tools entirely. A prompt rule still referred to an old tool name that had moved to a specialist. If an AI assistant matters in production, pair unit tests with a repeatable eval suite.
Conclusion
LLM APIs let us build our own AI assistants, and LangChain, LangGraph, and Deep Agents offer different entry points for that work.
- Start with LangChain when a standard loop of model, tools, and middleware is enough.
- Use LangGraph when you need to define the workflow graph, manage multi-step state, branch, or pause and resume.
- Start with Deep Agents when long, complex work requires planning, context management, a filesystem, and delegated specialists.
In the real project, I did not choose only one. LangChain formed the core. LangGraph enforced time, token, and safety controls through custom middleware. Deep Agents contributed only the subagent middleware. MCP brought in backoffice tools. That composability is the best part of the stack: it does not force us to accept the whole package, and it lets us build the assistant that the actual work requires.
For more on assistants that cross into business systems, read How to connect AI to ERP and CRM safely and What an MCP server does in a backoffice architecture.
Sources and further reading
- LangChain — Build overview
- LangChain — Agents
- LangChain — Models
- LangChain — Middleware
- LangChain — Short-term memory
- LangChain — Human-in-the-loop
- LangGraph — Interrupts
- LangGraph — Persistence
- LangGraph — Streaming
- Deep Agents — Overview
- Deep Agents — Subagents
- Deep Agents — Backends
- LangChain MCP Adapters
- OpenAI — GPT-4o Mini
- Model Context Protocol — Specification

