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.

  1. 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))
    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))
  2. Run the agent:

    uv run hello_agent.py
    Sample 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 called invoke(), the agent processed your message and returned a dictionary with a messages key containing the conversation history.

    Notice the tools listed in the output — these map directly to three of the four pillars:

    • Pillar 2 — Planning: write_todos creates and manages a structured task list to keep the agent organized on complex, multi-step work

    • Pillar 3 — Sub-agents: task spawns 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, grep give the agent full access to read, write, search, and navigate files

      And 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:claude-sonnet-4-6"

Anthropic (fast/cheap)

"anthropic:claude-haiku-4-5-20251001"

OpenAI

"openai:gpt-4o"

Ollama (local)

"ollama:llama3.1:8b"

Ollama (local, larger)

"ollama:qwen2.5:32b"

Remote endpoint (vLLM, LiteLLM)

"openai:your-model-name" + set OPENAI_API_BASE

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 deepagents (no extra install)

OpenAI

uv add langchain-openai

Ollama

uv add langchain-ollama

Google

uv add langchain-google-genai

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.

  1. 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()
    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()
  2. Run the streaming agent:

    uv run streaming_agent.py
    Sample output (your results may vary)
    Code flows through loops,
    Indentation guides the way,
    Pythonic and clear.

    Instead of invoke(), you used stream(). 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.

  1. 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)
    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)
  2. Run the script to see the full toolbox:

    uv run explore_tools.py
    Sample 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.

  1. Start the CLI:

    deepagents
    Sample output (your results may vary)
    Deep Agents CLI v0.2.1
    Using model: anthropic:claude-sonnet-4-6
    
    > _

    You’ll see a prompt.

  2. 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.

  3. When you’re done exploring, type /exit to 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.