Your First Deep Agent
You’re about to build your first deep agent in under five minutes. No complex setup, no boilerplate — just a few lines of code that give you a fully-equipped AI agent with filesystem access, memory, planning, and more.
Learning Objectives
By the end of this module, you will:
-
Create and run a working deep agent with a single function call
-
Understand the structure of agent responses and how to work with message streams
-
Explore the default toolset that comes built into every deep agent
-
Run your first interactive agent session using the CLI
Exercise 1: Hello Deep Agent
Let’s create the simplest possible deep agent. You’ll see just how little code it takes to get started.
-
Create a new Python file with this code:
-
Run
-
Code Preview
cat > hello_agent.py << 'EOF' import os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") agent = create_deep_agent(model=MODEL) result = agent.invoke({"messages": [("user", "List your tools. Be brief — just name and one-line description for each." )]}) print(agent_response(result)) EOFimport os from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") agent = create_deep_agent(model=MODEL) result = agent.invoke({"messages": [("user", "List your tools. Be brief — just name and one-line description for each." )]}) print(agent_response(result)) -
-
Run the agent:
uv run hello_agent.pySample output (your results may vary)Here are my available tools: | Tool | Description | |------|-------------| | write_todos | Create and manage a structured task list | | ls | List files in a directory | | read_file | Read file contents, with pagination support | | write_file | Create and write a new file | | edit_file | Perform exact string replacements in files | | glob | Find files matching a pattern (e.g., **/*.py) | | grep | Search for a text pattern across files | | task | Spawn a subagent to handle a complex task independently |
LLM responses are non-deterministic — the agent may format or word its output differently each time you run it. The same tools will be listed, but the exact phrasing, ordering, and layout may vary. This is normal and expected throughout the workshop. What just happened? The
create_deep_agent()function gave you a fully-configured LangGraph agent. When you calledinvoke(), the agent processed your message and returned a dictionary with amessageskey containing the conversation history.Notice the tools listed in the output — these map directly to three of the four pillars:
-
Pillar 2 — Planning:
write_todoscreates and manages a structured task list to keep the agent organized on complex, multi-step work -
Pillar 3 — Sub-agents:
taskspawns an isolated subagent to handle a piece of work independently, returning only the result -
Pillar 4 — Filesystem:
read_file,write_file,edit_file,ls,glob,grepgive the agent full access to read, write, search, and navigate filesAnd Pillar 1 — the detailed system prompt? That’s what taught the agent how to describe and use all of these tools in the first place. You don’t see it in the tool list, but it’s the foundation that makes everything else work.
-
Choosing your model
You’ll notice every script in this workshop defines a MODEL constant at the top:
MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6")
This reads from the DEEPAGENTS_MODEL environment variable, falling back to Claude Sonnet if unset. To switch every script in the workshop to a different model, just set the environment variable once:
export DEEPAGENTS_MODEL="ollama:llama3.1:8b"
No code changes needed — every script picks up the new model automatically. To switch back to the default, unset it:
unset DEEPAGENTS_MODEL
Deep Agents uses a "provider:model" format that supports any LiteLLM-compatible provider. Here are some examples:
| Provider | MODEL value |
|---|---|
Anthropic (default) |
|
Anthropic (fast/cheap) |
|
OpenAI |
|
Ollama (local) |
|
Ollama (local, larger) |
|
Remote endpoint (vLLM, LiteLLM) |
|
If you’re using Ollama, make sure it’s running locally (ollama serve) and that you’ve pulled the model first (ollama pull llama3.1:8b). No API key is needed for local models. Be aware that smaller local models may struggle with complex tool-use tasks in later modules — the exercises were designed and tested with Claude Sonnet.
|
Using a remote OpenAI-compatible endpoint
If you have access to a vLLM, LiteLLM proxy, or any OpenAI API-compatible endpoint serving models, you can point the entire workshop at it using just environment variables — no code changes needed.
LangChain’s OpenAI integration honors the OPENAI_API_BASE environment variable. Set it along with your API key and model name:
export OPENAI_API_BASE="https://your-llm-endpoint.example.com/v1"
export OPENAI_API_KEY="sk-your-key-here"
export DEEPAGENTS_MODEL="openai:gpt-oss-20b"
That’s it. Every script in the workshop will now use your remote endpoint instead of the public OpenAI API. The openai: prefix tells Deep Agents to use the OpenAI provider, and OPENAI_API_BASE redirects it to your endpoint.
For example, if your team runs a LiteLLM proxy serving multiple models:
export OPENAI_API_BASE="https://litellm-prod.apps.example.com/v1"
export OPENAI_API_KEY="sk-your-team-key"
export DEEPAGENTS_MODEL="openai:llama-scout-17b"
To try different models on the same endpoint, just change DEEPAGENTS_MODEL — the endpoint and key stay the same:
export DEEPAGENTS_MODEL="openai:qwen3-235b"
This also works for DEEPAGENTS_FAST_MODEL in modules that use cheaper models for subagents:
export DEEPAGENTS_FAST_MODEL="openai:gpt-oss-20b"
Make sure langchain-openai is installed (uv add langchain-openai). The package is needed for any openai: prefixed model, whether it points at OpenAI’s API or your own endpoint.
|
Reasoning models (OpenAI o-series, gpt-oss-120b, qwen3-235b, and similar models that produce chain-of-thought tokens) may cause errors or verbose Pydantic warnings when used with Deep Agents. These models return reasoning/thinking tokens that langchain-openai cannot parse reliably — ranging from noisy warnings to TypeError crashes. Use non-reasoning models on your endpoint instead (e.g., llama-scout-17b, minimax-m2, or gpt-oss-20b). If your endpoint offers a "no-think" variant (e.g., qwen3-235b-no-think), that may also work.
|
Each provider requires its own LangChain integration package. If you switch providers and see an ImportError, install the corresponding package with uv:
|
| Provider | Install command |
|---|---|
Anthropic |
Included with |
OpenAI |
|
Ollama |
|
|
For example, if you see ImportError: Initializing ChatOllama requires the langchain-ollama package, just run uv add langchain-ollama in your project directory and re-run your script.
Throughout this workshop, we use Anthropic’s Claude Sonnet as the default because it handles tool-calling and multi-step reasoning reliably. But because we extract MODEL as a constant, you can experiment with any provider at any time.
Exercise 2: Streaming Responses
Most of the time, you’ll want to see the agent’s response as it’s being generated, not all at once. Let’s switch to streaming mode, and write a haiku about Python.
-
Create a new file:
-
Run
-
Code Preview
cat > streaming_agent.py << 'EOF' import os from deepagents import create_deep_agent MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") agent = create_deep_agent(model=MODEL) for event in agent.stream({"messages": [("user", "Write a haiku about Python programming")]}): for value in event.values(): if not isinstance(value, dict): continue messages = value.get("messages") if messages is None: continue # LangGraph may wrap messages in an Overwrite object if hasattr(messages, 'value'): messages = messages.value if not isinstance(messages, list): messages = [messages] for msg in messages: if hasattr(msg, 'content') and msg.content: print(msg.content, end="", flush=True) print() EOFimport os from deepagents import create_deep_agent MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") agent = create_deep_agent(model=MODEL) for event in agent.stream({"messages": [("user", "Write a haiku about Python programming")]}): for value in event.values(): if not isinstance(value, dict): continue messages = value.get("messages") if messages is None: continue # LangGraph may wrap messages in an Overwrite object if hasattr(messages, 'value'): messages = messages.value if not isinstance(messages, list): messages = [messages] for msg in messages: if hasattr(msg, 'content') and msg.content: print(msg.content, end="", flush=True) print() -
-
Run the streaming agent:
uv run streaming_agent.pySample output (your results may vary)Code flows through loops, Indentation guides the way, Pythonic and clear.
Instead of
invoke(), you usedstream(). This returns an event stream in LangGraph’s format — each event is a dictionary keyed by the node name that produced it (e.g.,"model","PatchToolCallsMiddleware.before_agent"). The node’s output is itself a dict that may contain a"messages"key with the actual content.The code iterates through each event’s values, checks for a
"messages"key inside, and prints any content it finds. You’ll see the haiku appear as the agent generates it.
Exercise 3: Exploring Defaults
Let’s take a closer look at those built-in tools.
-
Create another script that asks the agent to describe its capabilities:
-
Run
-
Code Preview
cat > explore_tools.py << 'EOF' import os import re from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") agent = create_deep_agent(model=MODEL) result = agent.invoke({"messages": [("user", "List your tools. For each tool give the name and a one-line description. " "Use plain text, one tool per line, format: tool_name - description" )]}) # Clean up any markdown formatting for terminal display content = agent_response(result) content = re.sub(r'\*\*([^*]+)\*\*', r'\1', content) # Remove bold content = re.sub(r'`([^`]+)`', r'\1', content) # Remove code ticks print(content) EOFimport os import re from deepagents import create_deep_agent from utils import agent_response MODEL = os.environ.get("DEEPAGENTS_MODEL", "anthropic:claude-sonnet-4-6") agent = create_deep_agent(model=MODEL) result = agent.invoke({"messages": [("user", "List your tools. For each tool give the name and a one-line description. " "Use plain text, one tool per line, format: tool_name - description" )]}) # Clean up any markdown formatting for terminal display content = agent_response(result) content = re.sub(r'\*\*([^*]+)\*\*', r'\1', content) # Remove bold content = re.sub(r'`([^`]+)`', r'\1', content) # Remove code ticks print(content) -
-
Run the script to see the full toolbox:
uv run explore_tools.pySample output (your results may vary)write_todos - Create or update a structured task list for tracking progress ls - List files and directories at a given path read_file - Read the contents of a file with optional pagination write_file - Create a new file and write content to it edit_file - Replace specific text in an existing file glob - Find files matching a glob pattern grep - Search for text across files task - Launch a subagent to handle a complex task independently
The agent will walk you through its toolbox:
-
read_file, write_file, edit_file — filesystem operations for reading, creating, and modifying files
-
ls, glob, grep — file discovery and search capabilities
-
execute — runs shell commands (with safety guardrails)
-
write_todos — persists structured memory for task tracking
-
task — decomposes complex requests into manageable subtasks
These tools aren’t just examples — they’re production-ready implementations of the four pillars. Every deep agent starts with this foundation, so it can handle real work right out of the box.
-
Exercise 4: CLI First Look
You’ve been running agents programmatically, but Deep Agents also comes with a powerful CLI for interactive sessions.
-
Start the CLI:
deepagentsSample output (your results may vary)Deep Agents CLI v0.2.1 Using model: anthropic:claude-sonnet-4-6 > _
You’ll see a prompt.
-
Type a message like "What tools do you have available?" and press Enter.
Watch the terminal as the agent streams its response. If it calls any tools, you’ll see those calls displayed in the TUI (text user interface). The CLI handles all the streaming, formatting, and event display for you.
-
When you’re done exploring, type
/exitto quit the CLI cleanly, or press Ctrl+C to force exit.We’ll dive deep into the CLI in Module 9, but you’ll see CLI examples throughout this workshop. It’s often the fastest way to test ideas and debug agent behavior.
Module Summary
In less than five minutes, you’ve:
-
Created a working deep agent with a single function call
-
Seen how agent responses are structured as message lists in a dictionary
-
Explored the default toolset that implements the four pillars
-
Run your first interactive CLI session
The key takeaway: create_deep_agent() gives you a fully-equipped agent out of the box.
It returns a LangGraph CompiledStateGraph, which means you get all of LangGraph’s features — streaming, checkpointing, human-in-the-loop, and more — without writing any graph code yourself.
The four pillars are already operational, ready to handle real tasks.
In the next module, you’ll learn how the four-pillar architecture works under the hood and why it makes deep agents different from traditional LLM applications.