Deep AgentsDeep Agents 0.7 · Python 3.11+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
28 small wins to finish your path

Integrations: taking the trip planner to production

Integrations are the production versions of the pieces this course ran in memory: a saved checkpointer, a persistent store, a real search tool and a hosted model, each a swap of one argument.

Last updated: 29 Sep, 2026 · Deep Agents 0.7

Most lessons ran on in-memory pieces and a fixed catalog, so each output could be checked. This lesson maps each one to what the docs recommend in production, and swaps two of them for real: the checkpointer and the search tool.

What the course used and the real versions

Used in the courseIn productionPackage
InMemorySaverSqliteSaver, PostgresSaverlanggraph-checkpoint-sqlite, langgraph-checkpoint-postgres
InMemoryStorePostgresStore, or the store LangSmith Deployment provideslanggraph-checkpoint-postgres
StateBackend for filesCompositeBackend with a store route, or a sandbox backenddeepagents, sandbox integrations
search_travel on a fixed catalogTavily search, or a provider's built-in web searchtavily-python
Groq's free openai/gpt-oss-120bAny tool-calling model: OpenAI, Anthropic, Geminilangchain-openai, langchain-anthropic, ...
Printed stepsLangSmith tracingset LANGSMITH_TRACING=true

Swapping InMemorySaver for SqliteSaver

pip install "langgraph-checkpoint-sqlite==3.1.1"

SqliteSaver saves every checkpoint to a SQLite file. The test: write a plan with one agent, then build a new agent, as after a restart, and ask it about the file on the same thread.

python
from deepagents import create_deep_agent
from langchain.chat_models import init_chat_model

model = init_chat_model("groq:openai/gpt-oss-120b", temperature=0, max_retries=6)
python
import sqlite3

from langgraph.checkpoint.sqlite import SqliteSaver

def build_agent():
    saver = SqliteSaver(sqlite3.connect("trips.db", check_same_thread=False))   # a file on disk
    return create_deep_agent(model=model, checkpointer=saver,
                             system_prompt="You are a travel planner. Reply in one short sentence.")
ExampleAPI keypersist.py, continued
thread = {"configurable": {"thread_id": "paris-trip"}}
build_agent().invoke({"messages": [{"role": "user", "content": "Save a plan with one line, Day 1: Eiffel Tower, to /trip/plan.md."}]}, config=thread)

restarted = build_agent()                        # a new agent, as after a restart
result = restarted.invoke({"messages": [{"role": "user", "content": "What does /trip/plan.md say?"}]}, config=thread)
print("files:", list(result["files"]))
print(result["messages"][-1].text)

The second agent was built from scratch, yet its result lists /trip/plan.md and it answered from it: the thread's state, files included, came back from trips.db.

The drop-in for real data is web_search, the Tavily tool from Tools: a travel search the agent can call; it needs tavily-python and a TAVILY_API_KEY, both set up there. Pass it in tools next to search_travel: the catalog keeps the prices every lesson checks, and the web answers what the catalog does not hold. Start live_trip.py with search_travel, the catalog tool from the same lesson:

python
from langchain.tools import tool

CATALOG = {
    "paris": {
        "flight": ["Return flight Delhi to Paris: 42,000 rupees"],
        "hotel": ["Seine Budget Inn, Latin Quarter: 5,200 rupees a night",
                  "Hotel Lumiere, Montmartre: 7,500 rupees a night",
                  "Le Grand Opera Hotel: 16,000 rupees a night"],
        "sight": ["Eiffel Tower summit: 3,100 rupees", "Louvre Museum: 2,000 rupees",
                  "Seine river cruise: 1,500 rupees", "Versailles day trip: 2,600 rupees",
                  "Montmartre walking tour: free"],
        "food": ["Cafe breakfast and bistro dinner: 3,000 rupees a day"],
    },
}


@tool
def search_travel(city: str, kind: str) -> str:
    """Search the travel catalog. kind is "flight", "hotel", "sight" or "food". Prices are in rupees."""
    entries = CATALOG.get(city.lower(), {}).get(kind)
    return "\n".join(entries) if entries else f"The catalog has no {kind} entries for {city}."

Then web_search, as written there:

python
import os
from tavily import TavilyClient
from typing import Literal

tavily_client = TavilyClient(api_key=os.getenv("TAVILY_API_KEY"))

def web_search(query: str, max_results: int = 5,
               topic: Literal["general", "sports", "news", "finance"] = "general"):
    """Run a web search"""
    return tavily_client.search(query, max_results=min(max_results, 5), topic=topic)

The agent gets both tools, and its prompt says which one answers what. The question needs both: a price the catalog holds and an opening day it does not.

ExampleAPI keylive_trip.py, continued
from deepagents import create_deep_agent
from langchain.chat_models import init_chat_model

model = init_chat_model("groq:openai/gpt-oss-120b", temperature=0, max_retries=6)
agent = create_deep_agent(
    model=model,
    tools=[search_travel, web_search],
    system_prompt="You are a travel planner. Use search_travel for prices and web_search for anything the "
                  "catalog does not have. Use only what the tools return. Answer in two short sentences.",
)
result = agent.invoke({"messages": [{"role": "user", "content": "What is the cheapest hotel in Paris, and on which day of the week is the Louvre closed?"}]})

for message in result["messages"]:
    if message.type == "tool":
        print(f"tool  {message.name} returned {len(message.text)} characters")
    else:
        print(f"{message.type:<5}", message.text or [(c["name"], c["args"]) for c in message.tool_calls])

How the agent split the question

  • search_travel answered the price part: the catalog's three Paris hotels, 145 characters.
  • web_search answered the part the catalog does not hold, with the query "Louvre museum closed day of week"; Tavily sent back about 6,900 characters of results.
  • The answer takes the price from the catalog, the Seine Budget Inn at 5,200 rupees a night, and the closing day from the web, Tuesday.
  • Search results change from day to day, so your run will find other pages and word its answer differently.

Topics the course did not cover

TopicWhat it isDocs
Deep Agents Code (dcode)A terminal coding agent built on the SDKDeep Agents Code
SandboxesBackends that add an execute tool in an isolated machineSandboxes
InterpretersAn eval tool that runs JavaScript in QuickJSInterpreters
Async subagentsBackground subagents you can check, steer and cancelAsync subagents
Forked subagentsSubagents that inherit the parent's conversation (beta)Forked subagents
Event streaming v3Typed streams per subagent with stream_eventsEvent streaming
ProfilesPer-provider and per-model defaultsProfiles
Grading rubricsLLM-as-a-judge loops until work meets a rubricGrading rubrics
Semantic memory searchDeep Agents memory is file-based; vector search over memories lives in the LangGraph storeMemory
ACP, A2A and AG-UIServing a deep agent to editors, other agents and UIsACP

Where each swap matters

  • A checkpointer on disk or in Postgres as soon as conversations must survive a restart.
  • A persistent store once memories under /memories/ must last longer than the process.
  • Tracing from the first day real users arrive, to see every step of a slow or wrong run.
Watch out. SqliteSaver with check_same_thread=False is fine for one process; for a server with many workers use Postgres.
Try it yourself
  • Run the example twice and check that the second run still finds the plan from the first.
  • Change the thread id in the second call and read the answer.
  • Open trips.db with the sqlite3 command and list its tables.

You understood something today that you didn't yesterday.