A DVD-rental analyst agent built on
managed-deepagents (MDA). Ask it a
business question and it writes and runs its own SQL and Python against sakila.db
(based on the sample Sakila dataset), inside a managed sandbox.
README.md: the workshop walkthrough plus a reference appendix.agent.py: defines the agent, its model, and its tools. Thenamehere is also the deploy id.instructions.md: the agent's system prompt, editable in Context Hub.skills/: task-specific playbooks the agent pulls in on demand, also editable in Context Hub.sandbox/__init__.py: declares the managed sandbox the agent runs code in.sandbox/setup.sh: one-time script that provisions that sandbox (loads the Sakila database, installs Python packages).sakila.db: the sample DVD-rental SQLite database the agent queries.pyproject.toml,uv.lock: project dependencies..env: API keys (LangSmith and your model provider); never commit this.artifacts/: sandbox scratch output; gitignored.images/: screenshots used in this README.
Work through these steps (in your own copy of this project).
Everything below runs in a terminal, in this project's folder, unless it says otherwise.
Open a terminal in this project's folder (widen it) and run:
Widening it keeps the Dashboard URL from being split across two lines, which makes it easier to copy/paste.
uv tool install managed-deepagents
Create a .env file in this project's folder (or use whatever opens a new file in your editor of choice):
code .env
Paste in:
LANGSMITH_API_KEY=
ANTHROPIC_API_KEY=
Fill in a LangSmith API key (Settings → API Keys) and a key for whichever model provider you want to use.
agent.py defaults to anthropic:claude-sonnet-5, so ANTHROPIC_API_KEY works as-is.
However using another provider (OpenAI, Google, etc.) is very simple!
Just change the key name in .env and the model= line in agent.py to match.
uv run mda deploy .
This builds and pushes the project to LangSmith. When it finishes (~5 mins), it prints an Agent Server URL and a LangSmith dashboard URL.
- Copy and paste the dashboard URL, it will open up in LangSmith Deployments.
- Click on the blue Connect button in the top right corner.
- Click the Open in Studio button to chat with your agent.
Type / paste these into LangSmith Studio, or write your own. Here are some example questions to use:
- What's our monthly revenue trend? Break it down as a table.
- What are the top 5 film categories by number of rentals?
- How does revenue compare between our two stores?
Ask any question and the agent writes + runs its own SQL (and Python, if the question calls for further analysis) inside the sandbox → then answers with a short written summary.
- Sandbox isolation: the agent's code can't touch your machine, other threads' data, or anything outside its own scratch space.
- Sandbox persistence: follow-up questions in the same thread reuse earlier results
instead of recomputing them, because MDA provisions one real sandbox per thread
(
scope="thread") and reuses it. Self-hosted deepagents doesn't do this automatically.
We've already written the instructions that tell this agent to run SQL and Python against
sakila.db. sandbox/__init__.py declares the sandbox it runs that code in (the following
code is the entire file):
from managed_deepagents import define_sandbox
sandbox = define_sandbox(scope="thread")One import, one function call.
Open Context Hub and go to
instructions.md, this agent's system prompt: MDA hands this to the model on every turn,
so editing it changes how the agent behaves.
Add a persona to your agent:
## Persona
Respond as a dramatic, woe-is-me Victorian-era child, mournful about every number you
uncover. Stay in character in every answer.Save it in Context Hub.
To get back to Studio: click into Deployments, click Connect, then Open in Studio.
Ask one of your earlier questions again in the same chat thread and compare.
instructions.md lives outside the deployed graph, in Context Hub, so a deployed agent
picks up an edit like this immediately, no redeploy needed.
Iterating on a self-hosted agent's behavior usually means changing code, redeploying, and restarting before you can test anything new.
Try a few more personas (a pirate, a cowboy, an alien, etc.) and watch the tone change each time, still with the same underlying SQL and Python running underneath.
The same mechanism works for real branding too, not just novelty personas: tone, formality, terminology, whatever your company's voice calls for.
Open agent.py and look at the tools=[...] list. It has one custom tool,
email_report, a plain Python function decorated with @tool.
Ask your agent (in the same chat):
Email a summary of this month's revenue to finance@ourcompany.com.
Click Graph (next to the Chat button at the top), then the trace button in the
top right corner, and expand the tools node. You'll see email_report fire, along with
the confirmation string it returned (recipient, subject, and body all chosen by the model).
This tool isn't connected to an actual email service, but the tool call itself works the same way a real one would.
Tools are the same for both open-source deep agents and MDA.
When would you split a tool into its own file?
email_report is a three-line helper, so it's defined right in agent.py. Once a tool needs
its own dependency or setup (e.g. an internet_search tool that wraps a Tavily client), it's
cleaner to give it a home in tools/search.py and import it into agent.py:
# tools/search.py
from langchain.tools import tool
from tavily import TavilyClient
@tool
def internet_search(query: str, max_results: int = 5) -> str:
"""Search the web for a query."""
client = TavilyClient()
return client.search(query, max_results=max_results)# agent.py
from tools.search import internet_search
agent = define_deep_agent(..., tools=[email_report, internet_search])Look at skills/qbr-report/SKILL.md. It's a folder of instructions for one specific
task, generating a Quarterly Business Review, that the agent pulls in only when it's
relevant (instead of always-on context like instructions.md). MDA mounts anything
under skills/ read-only at /skills/ and hands the agent the list of skills automatically.
Ask your agent:
Generate a QBR report for the most recently completed quarter, broken down by
language, category, and top actors.
The skill tells the agent which quarter to use, which tables to join for each breakdown (language, category, actor), and how to structure the write-up.
Now write your own. sakila.db has 2 staff, each tied to a store:
Mike Hillyer runs Store 1, Jon Stephens runs Store 2. Every rental and payment records which staff member handled it.
Create skills/staff-performance/SKILL.md:
---
name: staff-performance
description: Review a staff member's performance, revenue processed and rentals
handled, at their store. Use whenever someone asks for a staff or employee
performance review.
---
# Staff performance review
Query `sakila.db`, joining `payment` and `rental` to `staff` (by `staff_id`) and
`staff` to `store`, to get, per staff member: total revenue processed
(`SUM(payment.amount)`), rentals handled (`COUNT(DISTINCT rental_id)`), and their
store. Report both staff side by side so their stores can be compared.Redeploy (mda deploy .) to attach it, then ask:
Give me a performance review for our staff.
Your agent now reaches for whichever skill fits the question. Like qbr-report,
once it's deployed you can keep editing staff-performance/SKILL.md in Context
Hub with no redeploy needed.
A skill and instructions.md both live in Context Hub and both update without a
redeploy, but they're for different things: instructions.md shapes how the agent
behaves on every turn, a skill is a playbook the agent reaches for on demand.
- Which actors have appeared in the most films?
- What's the average rental duration, by category?
- Is there a relationship between a film's length and how often it gets rented?
To learn more about deep agents, continue with the full LangChain Academy Deep Agents course.
| Component | What it does |
|---|---|
agent.py |
Defines the agent, its model, and its tools |
instructions.md |
The system prompt |
sandbox/__init__.py, sandbox/setup.sh |
Declares and provisions the sandbox |
sakila.db |
The sample database the agent queries |
pyproject.toml, uv.lock |
Project dependencies |
.env |
API keys |
artifacts/ |
Sandbox scratch output |
| Component | What it does |
|---|---|
identity.py |
Adds managed auth, see Identity |
memory.py |
Adds durable memory, see Memory |
tools/ |
Organizes multiple or heavier tools as separate files, see Optional runtime pieces |
middleware/ |
Custom middleware, see Optional runtime pieces |
skills/ |
Skills synced to Context Hub, see Optional runtime pieces |
connectors/mcp.py |
Attaches MCP servers, see Optional runtime pieces |
evals/ |
Harbor evals, see Evals |
This project has no identity.py, so it runs with no managed auth. To require
callers to authenticate, add identity.py exporting an identity = define_identity(auth=...)
declaration (for example auth.langsmith_api_key()). That gives every caller private
threads and downstream credentials. See the
identity docs
for the available auth.* options.
This project declares no memory, so nothing is kept between runs. Add memory.py
exporting define_memory(scope="agent") to give the agent a persistent directory at
/memories/agent/, shared across every thread for this deployment rather than reset per run.
None of these are present in this project. Beyond sandbox/, an MDA project can also declare:
tools/: a place to define tools as separate files instead of inline inagent.py, useful once you have more than a couple, or ones with heavier dependencies.middleware/: custom middleware.skills/: skills synced to Context Hub.connectors/mcp.py: attaches MCP servers; the file must export a namedconnectordeclaration.
MDA embeds sandbox/setup.sh and runs it once, the first time this project's sandbox is
provisioned.
pip install --quiet --break-system-packages pandas matplotlibInstalls the two libraries the agent's Python analysis relies on:
pandasfor querying and shaping datamatplotlib, available for charting, though this project'sinstructions.mdcurrently tells the agent to skip charts and images and stick to text
curl -s -o /tmp/sakila-schema.sql https://raw.githubusercontent.com/jOOQ/sakila/main/sqlite-sakila-db/sqlite-sakila-schema.sql
curl -s -o /tmp/sakila-data.sql https://raw.githubusercontent.com/jOOQ/sakila/main/sqlite-sakila-db/sqlite-sakila-insert-data.sql
sqlite3 sakila.db < /tmp/sakila-schema.sql
sqlite3 sakila.db < /tmp/sakila-data.sql
rm -f /tmp/sakila-schema.sql /tmp/sakila-data.sqlDownloads the Sakila sample database's schema and its data as two SQL files, loads both into
sakila.db, then deletes the downloaded files since they've already served their purpose.
sqlite3 sakila.db <<'SQL'
UPDATE film SET language_id = 5 WHERE film_id BETWEEN 1 AND 120; -- French
UPDATE film SET language_id = 2 WHERE film_id BETWEEN 121 AND 200; -- Italian
UPDATE film SET language_id = 3 WHERE film_id BETWEEN 201 AND 250; -- Japanese
UPDATE rental SET
rental_date = datetime(rental_date, '+7595 days'),
return_date = datetime(return_date, '+7595 days');
UPDATE payment SET payment_date = datetime(payment_date, '+7595 days');
UPDATE customer SET create_date = '2026-01-01 00:00:00';
UPDATE rental SET rental_date = '2026-08-10 15:16:03' WHERE rental_date > '2026-08-15';
UPDATE payment SET payment_date = '2026-08-10 15:16:03' WHERE payment_date > '2026-08-15';
UPDATE payment SET amount = 0.99 WHERE amount <= 0;
SQLThe public Sakila sample ships with every film in English and all activity dated 2005-2006. This reassigns some films to other languages, and shifts rental/payment dates into a recent window so questions like "this quarter" resolve against real data instead of two decades ago. It also pins customer signup dates ahead of that window (the source data has them coming after the rental history, since they're really a record-creation stamp), and clears out a handful of source payments recorded at $0.00 so every payment reflects a real transaction. The rental/payment override lines fix up a batch of still-checked-out rentals that the source data stamps with its generation timestamp rather than a real date, which the shift would otherwise push into the future.
mkdir -p artifactsCreates the artifacts/ directory the sandbox writes generated files to, so the very first
write doesn't fail on a missing folder.
Compile and deploy the project to LangSmith:
mda deploy .This copies your files verbatim, generates a managed entry module, and writes a deployable
build (including langgraph.json) to .mda/build. The CLI uploads that build to LangSmith to
run your agent on the managed runtime.
Common options (each line below is a separate example, not meant to be combined):
mda deploy . --name dvd-rental-analyst-dev --deployment-type dev # custom deploy name and type
mda deploy . --workspace-id "$LANGSMITH_WORKSPACE_ID" # target a specific workspace
mda deploy . --no-wait # return immediately, don't wait for the build- Deploy prints both the Agent Server URL to call and the LangSmith dashboard URL to inspect.
mda deployloads.env, usesLANGSMITH_API_KEYfor LangSmith, and forwards model provider keys such asOPENAI_API_KEYorANTHROPIC_API_KEYas deployment secrets.- Set
LANGSMITH_WORKSPACE_ID, or pass--workspace-id, if your LangSmith API key requires a workspace selection.
Read the deployed agent's server logs:
mda logs .
mda logs . --lines 200 --level errorIn a terminal this streams new output until you press Ctrl-C. When the output is piped or redirected, it prints the most recent lines (1000 by default) and exits.
Remove the deployment and the LangSmith resources it created:
mda delete .This deletes:
- The deployment
- The tracing project created alongside it
- The Context Hub repo holding this agent's context and memory
- The managed sandboxes this agent created
It asks first; pass --yes to skip the prompt. Agent memory and thread history are not
recoverable afterwards.
Managed Deep Agent evals are Harbor evals. Author full Harbor tasks directly under
evals/tasks/<task>/. To start from a minimal task, run:
mda evals init my-taskThis creates the optional scaffold evals/scaffold/my-task/ with an instruction.md and a
language verifier. Run the same command with another name to add more scaffolds. At compile
time MDA copies selected scaffolds to evals/tasks/ and preserves every other task. Compile
the managed agent, then run Harbor yourself:
mda evals compile . # all tasks
mda evals compile . --task my-task # only my-task
# follow the printed `harbor run` command