Agent harness

Deep Agents

A shallow agent is a model plus tools in a loop. A deep agent adds a plan, a place to keep notes, specialist subagents, and a detailed brief. Deep Agents is LangChain’s library for that harness, and it runs on LangGraph.

Intermediatedeepagents 0.7 · Python 3.11+

Shallow agents stop too early

AnalogyA shallow agent is a receptionist who answers one question. A deep agent is a project lead who writes a to-do list, keeps notes, and hands chapters to specialists.
Data flow
Question→Model→Maybe one tool→Answer

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

AnalogyThe four extras are a notebook, a filing cabinet, teammates, and a job description. Without the job description the teammates improvise.
Data flow
Plan→Files→Subagents→Instructions→Same tool loop

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.

PartWhat it fixesIn deepagents 0.7
PlanningThe model forgets step 4 while doing step 2.TodoListMiddleware. Planning is opt-in in 0.7. Older videos show it always on.
FilesystemNotes bigger than the context window.ls, read_file, write_file, edit_file, glob, grep.
SubagentsOne prompt trying to be researcher, budgeter, and writer.A task tool that hands work to a helper agent.
System promptVague “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

AnalogyThe trip planner is a clerk with a price list. If a price is not on the list, it is not in the itinerary. The clerk writes the table to a file so the next step can add it up.
Data flow
Brief→To-dos→search_travel→Notes file→Cost table→Ask before booking

`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

AnalogyThe file is a desk you can walk away from. The chat is a conversation that forgets the scratch paper. Subagents are coworkers who do not share a pencil.
Data flow
Researcher writes notes.md→Editor reads notes.md→itinerary.md→Human interrupt

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_travel and 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.
Interrupt on the irreversible step. Booking, paying, deleting, and emailing a customer are not tool calls you “hope the prompt avoids”. They are nodes that wait for a person. LangGraph’s interrupt is how that wait survives a restart.

When a deep agent is too much

AnalogyAdd a deep agent when you watch a shallow one skip a step or contradict itself. Until then the to-do list is theatre and extra model turns are latency.
Data flow
One tool call→Stay shallow→Notes no longer fit→Add files→Two skills→Add a subagent

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.