General purpose 'deep agent' with sub-agent spawning, todo list capabilities, and mock file system. Built on LangGraph.
Project description
🧠🤖Deep Agents
Using an LLM to call tools in a loop is the simplest form of an agent. This architecture, however, can yield agents that are “shallow” and fail to plan and act over longer, more complex tasks. Applications like “Deep Research”, "Manus", and “Claude Code” have gotten around this limitation by implementing a combination of four things: a planning tool, sub agents, access to a file system, and a detailed prompt.
deepagents is a Python package that implements these in a general purpose way so that you can easily create a Deep Agent for your application.
Acknowledgements: This project was primarily inspired by Claude Code, and initially was largely an attempt to see what made Claude Code general purpose, and make it even more so.
Installation
pip install deepagents
Usage
(To run the example below, will need to pip install tavily-python)
import os
from typing import Literal
from tavily import TavilyClient
from deepagents import create_deep_agent
tavily_client = TavilyClient(api_key=os.environ["TAVILY_API_KEY"])
# Search tool to use to do research
def internet_search(
query: str,
max_results: int = 5,
topic: Literal["general", "news", "finance"] = "general",
include_raw_content: bool = False,
):
"""Run a web search"""
return tavily_client.search(
query,
max_results=max_results,
include_raw_content=include_raw_content,
topic=topic,
)
# Prompt prefix to steer the agent to be an expert researcher
research_instructions = """You are an expert researcher. Your job is to conduct thorough research, and then write a polished report.
You have access to a few tools.
## `internet_search`
Use this to run an internet search for a given query. You can specify the number of results, the topic, and whether raw content should be included.
"""
# Create the agent
agent = create_deep_agent(
[internet_search],
research_instructions,
)
# Invoke the agent
result = agent.invoke({"messages": [{"role": "user", "content": "what is langgraph?"}]})
See examples/research/research_agent.py for a more complex example.
The agent created with create_deep_agent is just a LangGraph graph - so you can interact with it (streaming, human-in-the-loop, memory, studio)
in the same way you would any LangGraph agent.
Creating a custom deep agent
There are several parameters you can pass to create_deep_agent to create your own custom deep agent.
tools (Required)
The first argument to create_deep_agent is tools.
This should be a list of functions or LangChain @tool objects.
The agent (and any subagents) will have access to these tools.
instructions (Required)
The second argument to create_deep_agent is instructions.
This will serve as part of the prompt of the deep agent.
Note that our deep agent middleware appends further instructions to the deep agent regarding to-do list, filesystem, and subagent usage, so this is not the entire prompt the agent will see.
subagents (Optional)
A keyword-only argument to create_deep_agent is subagents.
This can be used to specify any custom subagents this deep agent will have access to.
You can read more about why you would want to use subagents here
subagents should be a list of dictionaries, where each dictionary follow this schema:
class SubAgent(TypedDict):
name: str
description: str
prompt: str
tools: NotRequired[list[str]]
model: NotRequired[Union[LanguageModelLike, dict[str, Any]]]
middleware: NotRequired[list[AgentMiddleware]]
class CustomSubAgent(TypedDict):
name: str
description: str
graph: Runnable
SubAgent fields:
- name: This is the name of the subagent, and how the main agent will call the subagent
- description: This is the description of the subagent that is shown to the main agent
- prompt: This is the prompt used for the subagent
- tools: This is the list of tools that the subagent has access to. By default will have access to all tools passed in, as well as all built-in tools.
- model: Optional model instance OR dictionary for per-subagent model configuration (inherits the main model when omitted).
- middleware Additional middleware to attach to the subagent. See here for an introduction into middleware and how it works with create_agent.
CustomSubAgent fields:
- name: This is the name of the subagent, and how the main agent will call the subagent
- description: This is the description of the subagent that is shown to the main agent
- graph: A pre-built LangGraph graph/agent that will be used as the subagent
Using SubAgent
research_subagent = {
"name": "research-agent",
"description": "Used to research more in depth questions",
"prompt": sub_research_prompt,
"tools": [internet_search]
}
subagents = [research_subagent]
agent = create_deep_agent(
tools,
prompt,
subagents=subagents
)
Using CustomSubAgent
For more complex use cases, you can provide your own pre-built LangGraph graph as a subagent:
from langchain.agents import create_agent
# Create a custom agent graph
custom_graph = create_agent(
model=your_model,
tools=specialized_tools,
prompt="You are a specialized agent for data analysis..."
)
# Use it as a custom subagent
custom_subagent = {
"name": "data-analyzer",
"description": "Specialized agent for complex data analysis tasks",
"graph": custom_graph
}
subagents = [custom_subagent]
agent = create_deep_agent(
tools,
prompt,
subagents=subagents
)
model (Optional)
By default, deepagents uses "claude-sonnet-4-20250514". You can customize this by passing any LangChain model object.
Example: Using a Custom Model
Here's how to use a custom model (like OpenAI's gpt-oss model via Ollama):
(Requires pip install langchain and then pip install langchain-ollama for Ollama models)
from deepagents import create_deep_agent
# ... existing agent definitions ...
model = init_chat_model(
model="ollama:gpt-oss:20b",
)
agent = create_deep_agent(
tools=tools,
instructions=instructions,
model=model,
...
)
Example: Per-subagent model override (optional)
Use a fast, deterministic model for a critique sub-agent, while keeping a different default model for the main agent and others:
from deepagents import create_deep_agent
critique_sub_agent = {
"name": "critique-agent",
"description": "Critique the final report",
"prompt": "You are a tough editor.",
"model_settings": {
"model": "anthropic:claude-3-5-haiku-20241022",
"temperature": 0,
"max_tokens": 8192
}
}
agent = create_deep_agent(
tools=[internet_search],
instructions="You are an expert researcher...",
model="claude-sonnet-4-20250514", # default for main agent and other sub-agents
subagents=[critique_sub_agent],
)
middleware (Optional)
Both the main agent and sub-agents can take additional custom AgentMiddleware. Middleware is the best supported approach for extending the state_schema, adding additional tools, and adding pre / post model hooks. See this doc to learn more about Middleware and how you can use it!
tool_configs (Optional)
Tool configs are used to specify how to handle Human In The Loop interactions on certain tools that require additional human oversight.
These tool_configs are passed to our prebuilt HITL middleware so that the agent pauses execution and waits for feedback from the user before executing configured tools.
Deep Agent Details
The below components are built into deepagents and helps make it work for deep tasks off-the-shelf.
System Prompt
deepagents comes with a built-in system prompt. This is relatively detailed prompt that is heavily based on and inspired by attempts to replicate
Claude Code's system prompt. It was made more general purpose than Claude Code's system prompt.
This contains detailed instructions for how to use the built-in planning tool, file system tools, and sub agents.
Note that part of this system prompt can be customized
Without this default system prompt - the agent would not be nearly as successful at going as it is. The importance of prompting for creating a "deep" agent cannot be understated.
Planning Tool
deepagents comes with a built-in planning tool. This planning tool is very simple and is based on ClaudeCode's TodoWrite tool.
This tool doesn't actually do anything - it is just a way for the agent to come up with a plan, and then have that in the context to help keep it on track.
File System Tools
deepagents comes with four built-in file system tools: ls, edit_file, read_file, write_file.
These do not actually use a file system - rather, they mock out a file system using LangGraph's State object.
This means you can easily run many of these agents on the same machine without worrying that they will edit the same underlying files.
Right now the "file system" will only be one level deep (no sub directories).
These files can be passed in (and also retrieved) by using the files key in the LangGraph State object.
agent = create_deep_agent(...)
result = agent.invoke({
"messages": ...,
# Pass in files to the agent using this key
# "files": {"foo.txt": "foo", ...}
})
# Access any files afterwards like this
result["files"]
Sub Agents
deepagents comes with the built-in ability to call sub agents (based on Claude Code).
It has access to a general-purpose subagent at all times - this is a subagent with the same instructions as the main agent and all the tools that is has access to.
You can also specify custom sub agents with their own instructions and tools.
Sub agents are useful for "context quarantine" (to help not pollute the overall context of the main agent) as well as custom instructions.
Built In Tools
By default, deep agents come with five built-in tools:
write_todos: Tool for writing todoswrite_file: Tool for writing to a file in the virtual filesystemread_file: Tool for reading from a file in the virtual filesystemls: Tool for listing files in the virtual filesystemedit_file: Tool for editing a file in the virtual filesystem
If you want to omit some deepagents functionality, use specific middleware components directly!
Human-in-the-Loop
deepagents supports human-in-the-loop approval for tool execution. You can configure specific tools to require human approval before execution using the tool_configs parameter, which maps tool names to a HumanInTheLoopConfig.
HumanInTheLoopConfig is how you specify what type of human in the loop patterns are supported.
It is a dictionary with four specific keys:
allow_accept: Whether the human can approve the current action without changesallow_respond: Whether the human can reject the current action with feedbackallow_edit: Whether the human can approve the current action with edited content
Instead of specifying a HumanInTheLoopConfig for a tool, you can also just set True. This will set allow_ignore, allow_respond, allow_edit, and allow_accept to be True.
In order to use human in the loop, you need to have a checkpointer attached. Note: if you are using LangGraph Platform, this is automatically attached.
Example usage:
from deepagents import create_deep_agent
from langgraph.checkpoint.memory import InMemorySaver
# Create agent with file operations requiring approval
agent = create_deep_agent(
tools=[your_tools],
instructions="Your instructions here",
tool_configs={
# You can specify a dictionary for fine grained control over what interrupt options exist
"tool_1": {
"allow_respond": True,
"allow_edit": True,
"allow_accept":True,
},
# You can specify a boolean for shortcut
# This is a shortcut for the same functionality as above
"tool_2": True,
}
)
checkpointer= InMemorySaver()
agent.checkpointer = checkpointer
Approve
To "approve" a tool call means the agent will execute the tool call as is.
This flow shows how to approve a tool call (assuming the tool requiring approval is called):
config = {"configurable": {"thread_id": "1"}}
for s in agent.stream({"messages": [{"role": "user", "content": message}]}, config=config):
print(s)
# If this calls a tool with an interrupt, this will then return an interrupt
for s in agent.stream(Command(resume=[{"type": "accept"}]), config=config):
print(s)
Edit
To "edit" a tool call means the agent will execute the new tool with the new arguments. You can change both the tool to call or the arguments to pass to that tool.
The args parameter you pass back should be a dictionary with two keys:
action: maps to a string which is the name of the tool to callargs: maps to a dictionary which is the arguments to pass to the tool
This flow shows how to edit a tool call (assuming the tool requiring approval is called):
config = {"configurable": {"thread_id": "1"}}
for s in agent.stream({"messages": [{"role": "user", "content": message}]}, config=config):
print(s)
# If this calls a tool with an interrupt, this will then return an interrupt
# Replace the `...` with the tool name you want to call, and the arguments
for s in agent.stream(Command(resume=[{"type": "edit", "args": {"action": "...", "args": {...}}}]), config=config):
print(s)
Respond
To "respond" to a tool call means that tool is NOT called. Rather, a tool message is appended with the content you respond with, and the updated messages list is then sent back to the model.
The args parameter you pass back should be a string with your response.
This flow shows how to respond to a tool call (assuming the tool requiring approval is called):
config = {"configurable": {"thread_id": "1"}}
for s in agent.stream({"messages": [{"role": "user", "content": message}]}, config=config):
print(s)
# If this calls a tool with an interrupt, this will then return an interrupt
# Replace the `...` with the response to use all the ToolMessage content
for s in agent.stream(Command(resume=[{"type": "response", "args": "..."}]), config=config):
print(s)
Async
If you are passing async tools to your agent, you will want to use from deepagents import async_create_deep_agent
MCP
The deepagents library can be ran with MCP tools. This can be achieved by using the Langchain MCP Adapter library.
NOTE: You will want to use from deepagents import async_create_deep_agent to use the async version of deepagents, since MCP tools are async
(To run the example below, will need to pip install langchain-mcp-adapters)
import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from deepagents import create_deep_agent
async def main():
# Collect MCP tools
mcp_client = MultiServerMCPClient(...)
mcp_tools = await mcp_client.get_tools()
# Create agent
agent = async_create_deep_agent(tools=mcp_tools, ....)
# Stream the agent
async for chunk in agent.astream(
{"messages": [{"role": "user", "content": "what is langgraph?"}]},
stream_mode="values"
):
if "messages" in chunk:
chunk["messages"][-1].pretty_print()
asyncio.run(main())
Roadmap
- Allow users to customize full system prompt
- Code cleanliness (type hinting, docstrings, formating)
- Allow for more of a robust virtual filesystem
- Create an example of a deep coding agent built on top of this
- Benchmark the example of deep research agent
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file deepagents_brim-0.0.11.tar.gz.
File metadata
- Download URL: deepagents_brim-0.0.11.tar.gz
- Upload date:
- Size: 26.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85748316e92c8db04d1048b33c4e0d9fccc94378abe582750b2bc3aeb9a274f6
|
|
| MD5 |
3c146ae1c614ae5d8afc3afb7d6d8fda
|
|
| BLAKE2b-256 |
2430ee7b77c8bff34981af751bffdbc636a037e0ba63b631b45a457430814dfe
|
File details
Details for the file deepagents_brim-0.0.11-py3-none-any.whl.
File metadata
- Download URL: deepagents_brim-0.0.11-py3-none-any.whl
- Upload date:
- Size: 27.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
931034621e11cd46f026de4f843a9bc33247c2f1561b233a944f070a6a7bf223
|
|
| MD5 |
fe5e407c014911511d4916cddf27b0cf
|
|
| BLAKE2b-256 |
309aa33bc0c5da82c682f51e91697cb20b7d759217fe132aabc6074a816dcc57
|