Shallow agents stop too early
A ReAct loop can call several tools and still be shallow. There is no written plan and nowhere to put a page of notes. A question like “today’s AI news, and how it touches energy prices” never gets broken into smaller questions unless you add those pieces.
Starter
def shallow(question: str) -> str:
if "weather" in question.lower():
return "Call the weather tool once."
return "Answer from the prompt."
print(shallow("Weather in Paris?"))
Example
“What is the temperature in Paris?” is shallow. “Plan four days in Paris under a budget” is not.
A weather question is a shallow job: one tool, one answer. A ReAct loop can call several tools in a row and still be shallow. There is no written plan, nowhere to put a page of notes, and a question like “today’s AI news, and how it touches energy prices and chip supply” never gets broken into smaller questions.
Claude Code, deep research products, and coding agents all grew the same four extras because long jobs fall over without them.
Plan, files, subagents, instructions
Planning is opt-in in current deepagents releases: you add a to-do middleware. The filesystem tools (`ls`, `read_file`, `write_file`, `edit_file`, `glob`, `grep`) hold notes that do not fit the window. A task tool hands one item to a specialist. The system prompt is yours. It runs on LangGraph, so you still get streaming and interrupts. Any tool-calling model can sit underneath, which is how this differs from a vendor coding agent locked to one model.
Starter
parts = ["plan", "files", "subagents", "instructions"]
print("a deep agent adds:", ", ".join(parts))
Example
Claude Code, deep research, and a trip planner all grew a plan, files, and specialists. The brand on the model is the part you can swap.
| Part | What it fixes | In deepagents 0.7 |
|---|---|---|
| Planning | The model forgets step 4 while doing step 2. | TodoListMiddleware. Planning is opt-in in 0.7. Older videos show it always on. |
| Filesystem | Notes bigger than the context window. | ls, read_file, write_file, edit_file, glob, grep. |
| Subagents | One prompt trying to be researcher, budgeter, and writer. | A task tool that hands work to a helper agent. |
| System prompt | Vague “be helpful” behaviour. | You pass instructions. 0.7 does not ship a long built-in prompt. |
create_deep_agent returns an agent on LangGraph, so you also get streaming, saved state, and interrupts. It works with any tool-calling chat model: OpenAI, Anthropic, Gemini, Groq, or a local model. That is the split with the Claude Agent SDK, which is built around Claude’s own runtime.
Example: a trip inside a budget
`create_deep_agent` takes the model, your tools, and a system prompt that forbids invented prices and forbids booking. A captured style of run writes day-by-day lines and a cost table that stays under the cap only when every number came from the tool. Add the to-do middleware once you confirm its import for the version you installed.
Starter
CATALOG = {"hotel": "7500 INR per night", "flight": "42000 INR"}
def search_travel(item: str) -> str:
return CATALOG.get(item, "Not in the catalog. Do not invent a price.")
print(search_travel("hotel"))
print(search_travel("balloon"))
Example
Three nights in Paris under 100,000 INR is the test. A hot-air balloon that is not in the catalog must be omitted, not priced from memory.
The brief: three nights in Paris, four days, budget 100,000 rupees, flying from Delhi. A shallow agent blurts an itinerary and invents prices. A deep agent writes a to-do list, searches a catalog, saves a cost table to a file, and waits before it books.
from deepagents import create_deep_agent
from langchain.tools import tool
# Planning is opt-in in 0.7. Add TodoListMiddleware once you confirm
# its import path for the deepagents version you installed.
CATALOG = {
"flight": "Delhi → Paris → Delhi, 42000 INR",
"hotel": "Hotel Lumiere, 7500 INR per night",
"eiffel": "Eiffel Tower summit, 3100 INR",
"louvre": "Louvre, 2000 INR",
}
@tool
def search_travel(item: str) -> str:
"""Look up a flight, hotel, or sight in the small travel catalog."""
return CATALOG.get(item, "Not in the catalog. Do not invent a price.")
agent = create_deep_agent(
model="groq:openai/gpt-oss-120b",
tools=[search_travel],
# middleware=[TodoListMiddleware()],
system_prompt=(
"Plan a 4-day, 3-night Paris trip from Delhi under 100000 INR. "
"Use search_travel for every price. Write the itinerary and a cost table "
"to /itinerary.md. Never book. Stop and ask before spending money."
),
)
# result = agent.invoke({"messages": [{"role": "user", "content": "Plan my Paris trip."}]})
print(search_travel.invoke({"item": "hotel"}))
A captured run of this style of planner produces a day-by-day plan and a cost table that adds up (flights, hotel, food, sights) and stays under the cap — for example a grand total around 85,700 INR when the catalog prices are respected. The point of the exercise is the addition, not the poetry. If a price is not in the tool result, it must not appear in the table.
Why the file and the subagent exist
Give the researcher the search tool and the editor no search tool, so the editor cannot sneak in a price. Interrupt before pay, delete, or send. A general write_file on a developer laptop is a coding agent. The same tool on a server with customer data must be rooted in a per-run temp directory.
Starter
notes = "hotel: 7500 INR per night\nflight: 42000 INR\n" # editor is not allowed to add a line that is not in notes print(notes)
Example
Research briefs, coding sessions, and procurement comparisons all want notes in files and a pause before money moves.
The cost table is awkward inside the chat. The agent writes /itinerary.md, the next step reads it back, and a summarizer can shrink the old tool chatter. You still have the file.
Two helpers beat one generalist:
- Researcher only calls
search_traveland writes/notes.md. - Editor only reads notes and writes the final markdown. It cannot search, so it cannot sneak in a new price.
Other jobs with the same shape
- Research brief: to-dos for sources, notes per source in files, a writer subagent that may only quote those files.
- Coding: the same harness Claude Code uses — read the repo, edit files, run tests, keep a plan. Deep Agents is the open version you can point at any model.
- Procurement: compare three vendors in files, then interrupt before anyone sends a purchase order.
When a deep agent is too much
Start shallow. Add planning when steps are skipped. Add files when notes no longer fit. Add a subagent when two skills need different tools. Sandbox the filesystem.
Starter
def harness(tool_calls: int, notes_too_big: bool) -> str:
if notes_too_big:
return "deep agent with files"
if tool_calls <= 1:
return "plain function or shallow agent"
return "tool loop"
print(harness(1, False))
Example
A weather answer does not get a to-do list. A migration across twenty files does.
If the task is one tool call, the to-do list is theatre and the extra model turns are latency. Start shallow. Add planning when you see the agent skip a step or contradict itself. Add files when the notes no longer fit the window. Add a subagent when two skills need different tools and different instructions.
Give the filesystem a sandbox. A general write_file on a developer laptop is a coding agent. The same tool on a server with customer data is an incident unless the root is a per-run temp directory.
Practice
- List the to-dos you would expect before any flight is chosen.
- Add a catalog miss (“hot-air balloon”) and write the instruction that forbids an invented price.
- Split researcher and editor so only one of them can call
search_travel. - Name the step that must interrupt, in one sentence a reviewer can test.