A 60-minute hands-on lab for Claude Managed Agents.
You've called Claude: send a request, read a response. In this lab you operate an agent instead. You start a Documentation Drift Agent that runs on Anthropic's side, in a sandbox, and you watch it, steer it, collect its work, and clean up after it.
| Calling Claude | Operating an agent | Lab step |
|---|---|---|
| A request and a response | A session with a lifecycle: create, run, idle, resume, delete | 2, 6 |
| You write the loop | The loop runs on Anthropic's side; you watch it through events | 3 |
| Stateless; you resend the history | State lives on the server: history, sandbox files, outputs | 4, 5 |
| You read the response | You stream events, steer, or interrupt mid-run | 3, 4 |
| Your code runs the tools | You decide which tools it may use, and it runs them in a sandbox | 1 |
| You manage request costs | You manage token usage, execution time, and session budgets | 2, 6 |
The task. fixture/ holds metrolen, a tiny fictional unit-conversion library at version 2, plus documentation written for version 1. The agent runs every code sample in the docs inside its sandbox, finds the ones that no longer match the code, and writes a drift report.
- Configure an agent and its sandbox with only the tools and network access a task needs.
- Start a session that loads local files into the sandbox under a spend cap, and say what each saved ID refers to.
- Follow an autonomous run through its event stream, detach and reattach without affecting it, and point to the event that shows the agent ran a sample and the error it got.
- Send the same session more work from the command terminal, and explain when a message sent mid-run takes effect.
- Download the agent's outputs and check its work against a structured contract instead of reading its prose.
- Clean up a session safely, keeping a local copy first, and say what remains afterward.
This repository includes the complete instructor-led session and hands-on lab:
- Presentation: 20-minute instructor-led introduction, with speaker notes and presentation guidance.
- Hands-on lab: 60-minute guided exercise using Claude Managed Agents.
- Instructor guide: One-page setup, delivery, troubleshooting, and handoff reference.
- Design note: Instructional decisions, AI-assisted development, pilot findings, and limitations.
- uv. It installs Python 3.12 for you if you don't have it.
- git, or a ZIP download of this repository.
- An Anthropic API key with access to Managed Agents (beta).
- Two terminals, both open in this folder: the watch terminal shows the agent's events (from step 3 on), and the command terminal runs everything else.
- Each step's script has one function for you to fill in. Until you do, the script stops with a
YOUR TURN (step N): ...message instead of running. That's expected.watch.py,check.py, andreplay.pyare complete, andsolution/has reference versions if you get stuck. - Cost: one full run on the default model (
claude-haiku-5-5) cost about $0.01 in testing. That figure is the session's own usage report (usage.list_cost): tokens and running time priced at public list rates, rounded to the nearest cent. Your bill can be lower if your organization has negotiated pricing. Step 6 prints your session's figure before deleting it. Every session is capped at $0.50.
Open two terminals now. Pick one as your command terminal (setup runs there) and the other as your watch terminal. Once the code is downloaded, cd the watch terminal into the same folder too.
Get the code and install the packages. On Windows, clone into a short path such as C:\Users\<you>\managed-agents-workshop: a deeply nested folder can hit Windows' 260-character path limit when packages install.
PowerShell (Windows):
# command terminal
cd $HOME
git clone https://github.com/kenwalger/managed-agents-workshop.git
cd managed-agents-workshop
uv syncmacOS or Linux:
# command terminal
git clone https://github.com/kenwalger/managed-agents-workshop.git
cd managed-agents-workshop
uv syncNo git? On the repository's GitHub page, choose Code, then Download ZIP, unzip it (on Windows, into a short path like C:\Users\<you>\), and cd into the unzipped folder, managed-agents-workshop-main.
The first uv sync can take a minute or two while uv downloads the packages, and Python too if it isn't already installed. It may be quiet for a while; that's normal. It's finished when your prompt comes back after lines like these:
Resolved 30 packages in ...
Installed 28 packages in ...
+ anthropic==2.5.0
...
(Your versions and counts will differ. If everything is already installed, it just prints Resolved ... and Audited ....)
Add your key and check the setup.
PowerShell (Windows):
# command terminal
Copy-Item .env.example .env
notepad .env # paste your key after ANTHROPIC_API_KEY=
uv run check.py 0macOS or Linux:
# command terminal
cp .env.example .env # then edit .env and paste your key
uv run check.py 0Expected:
PASS: API key found (not shown), anthropic 2.5.0, Managed Agents API reachable
uv run check.py summary shows your progress at any time.
Open 01_define.py and read the task card at the top. The agent has to run Python files, read files, and write two reports. It needs nothing from the internet and must not install anything.
Fill in choose_permissions(). It returns two things:
tools: the agent's tool list, built from theagent_toolset_20260401toolset. See Tools for the built-in tools and how to turn each one on or off.networking: the sandbox's network access. See Environments.
Start with the defaults to see what they allow. Return [{"type": "agent_toolset_20260401"}] and {"type": "unrestricted"}, then run, in the command terminal:
# command terminal
uv run 01_define.py
uv run check.py 1 # EXPECTED TO FAIL with the defaultsThe check explains what's wrong and why. Change your choices and run both commands again. The second run updates the same agent and gives it a new version number; it doesn't create a second agent.
Updated agent agent_01...: version 1 -> 2
Updated environment env_01... (applies to new sessions)
...
PASS: agent version 2 can run code, has no web tools, and its sandbox has no egress
Reflect (not graded): for each tool you left on, could the agent do the task without it?
Fill in create_session() in 02_start.py. The call looks like this:
return client.beta.sessions.create(
agent=agent_id,
environment_id=environment_id,
resources=resources,
title="metrolen documentation drift check",
budget={"type": "limit", "max_list_cost": {"amount": BUDGET_CENTS, "currency": "USD"}},
)The script uploads the fixture, creates the session, saves its ID to .lab-state.json, and sends the drift task. Run it in the command terminal:
# command terminal
uv run 02_start.py
uv run check.py 2Uploaded 7 files
Created session sesn_01... (agent version 2, budget $0.50, status idle)
Sent the drift task. The agent is working now, on Anthropic's side.
...
PASS: session sesn_01... is running, with 7 files mounted and a $0.50 cap
The IDs in .lab-state.json are your only handles on things that exist on Anthropic's servers. A running session costs money whether or not you're watching it.
# watch terminal
uv run watch.pyThe watcher first prints what already happened (earlier), then streams new events (live). Look for a tool: bash line running a sample and a result: ERROR ... line after it: that's the agent running code and getting an error back.
Now press Ctrl+C. On Windows it can take a few seconds to detach. Then run uv run watch.py again.
Detached. The session keeps running on Anthropic's side. Reattach: uv run watch.py
Attached to sesn_01.... Catching up on 56 earlier events:
earlier 16:47:43 tool: bash: cd /tmp && PYTHONPATH=/mnt/session/uploads/fixture python3 sample_parsing_22.py ...
earlier 16:47:44 result: ERROR TypeError: parse_quantity() got an unexpected keyword argument 'strict'
...
Your lines will differ: the agent decides how to run the samples, and sometimes batches several into one script.
The agent is fast. It often finishes in under a minute, sometimes before you reattach. That's fine: reattaching to a finished session still shows everything it did while you weren't watching, because the history lives on the server.
# command terminal
uv run check.py 3PASS: watched 2 times; the agent made 8 bash calls
INFO: tool calls the agent made while no watcher was attached: 8
INFO: first error a doc sample produced: TypeError: parse_quantity() got an unexpected keyword argument 'strict'
Fill in send_message() in steer.py:
client.beta.sessions.events.send(session_id, events=[
{"type": "user.message", "content": [{"type": "text", "text": text}]},
])Then, in the command terminal:
# command terminal
uv run steer.pyWith no arguments, it asks the agent to write corrected copies of the drifted doc pages and run their samples. Watch it in the watch terminal (uv run watch.py), then:
# command terminal
uv run check.py 4PASS: the same session handled 2 messages without starting over
INFO: your first follow-up arrived after the first run finished, so it started a new turn
When does a message take effect? If the agent is still running, your message waits until the agent's in-flight tool calls finish, then takes effect at its next step. If the agent has finished, your message starts a new turn in the same session, with the same sandbox, files, and conversation. The check tells you which happened. Both pass.
Optional drill: interrupt. Ask for something long, then stop it:
# command terminal
uv run steer.py "Re-run every doc sample 5 more times, one bash call per run."
uv run steer.py --interruptThe session goes idle within about a second, and stays usable.
Fill in download_outputs() in 03_collect.py:
saved = []
for file in client.beta.files.list(scope_id=session_id, betas=[MANAGED_AGENTS_BETA]):
if not file.downloadable: # your own uploads are listed too
continue
path = destination / file.filename
client.files.download(file.id).write_to_file(path)
saved.append(path)
return saved# command terminal
uv run 03_collect.py
uv run check.py 5PASS: both reports downloaded; drift-report.json matches the contract
AGENT: coverage: reported 7 of 7 doc samples
AGENT: drifts found: 3 of 3, kind correct for 3
AGENT: decoy: correctly reported as matching the docs
AGENT: false positives: 0
AGENT: evidence reproduced locally: 7 of 7
...
Two kinds of result:
PASS/NOT YETis about you: did you get valid reports out of the sandbox?AGENT:is about the agent. The fixture has three planted drifts and one decoy: a sample that looks outdated but still works.check.pycompares the report with what it gets by running the trusted fixture samples on your machine. It never runs anything the agent wrote.
If the agent missed a drift or flagged the decoy, you still pass this step. That miss is the point: it's why the report has a schema and why you check it rather than trusting it.
Fill in delete_session() in 04_cleanup.py:
client.beta.sessions.delete(session_id)# command terminal
uv run 04_cleanup.py
uv run check.py 6
uv run check.py summaryRecorded 96 events to recordings\sesn_01....jsonl
Session cost: $0.01 at list prices (rounded to the nearest cent), against a $0.50 budget
tokens: 36 input, 11,143 output, 21,347 cache write, 214,672 cache read; running time: 68 s
Deleted session sesn_01... (it now returns 404)
Deleted 7 uploaded files
Kept agent agent_01... and environment env_01... for next time.
PASS: session deleted (404), uploads deleted, recording kept at sesn_01....jsonl
The cost line comes from the session's own usage report (usage.list_cost), read just before the delete: tokens and running time at public list rates, rounded to the nearest cent. Deleting is permanent, so the script also records the session's events to recordings/ first. Replay them any time with uv run replay.py recordings/<file>.jsonl.
The agent and environment are kept so you can run the lab again. When you're done for good: uv run 04_cleanup.py --all. That deletes the environment and archives the agent: agents can't be deleted, and archiving is permanent.
After step 4, the agent wrote fixed-quickstart.md, fixed-formatting.md, and fixed-parsing.md, and step 5 downloaded them to outputs/.
# command terminal
uv run check.py stretchThis reads the fixed pages and confirms the drifts are gone. It doesn't run them on your machine: the agent already ran them in its sandbox, which is where code you didn't write belongs. Review outputs/fixed-*.md against fixture/docs/ yourself. Would you merge them?
| You see | What it means | What to do |
|---|---|---|
No ANTHROPIC_API_KEY found |
.env is missing or empty |
Copy-Item .env.example .env (Windows) or cp .env.example .env (macOS/Linux), then add your key |
API error 400: The request was invalid |
A malformed ID, often a copy-paste typo | Check the IDs in .lab-state.json |
NOT YET: the saved session no longer exists |
The session was deleted | uv run 02_start.py --new |
Cannot delete session while it is running |
Only idle sessions can be deleted | uv run 04_cleanup.py interrupts first; run it again |
The session reached its spend cap and paused |
The $0.50 budget was used up | Clean up and start a new session; or set LAB_BUDGET_CENTS higher in .env |
uv run watch.py prints history and exits |
The session is idle with nothing queued | Expected. Send more work with uv run steer.py |
| Ctrl+C takes a few seconds | Windows delivers it when the stream next receives data | Wait; it detaches |
| No output files in step 5 | The agent hadn't finished, or files were still indexing | Wait for idle in the watch terminal, then collect again |
| The agent says the sandbox has Python 3.11 | The sandbox's default python3 is 3.11 |
Expected. The fixture supports Python 3.10 and newer |
The agent tries pip install and fails |
Your sandbox has no internet access | Expected: that's the least privilege you chose in step 1 |
| Console link says "Session not found" | Your key isn't in the Default workspace | Set LAB_WORKSPACE in .env to your workspace ID |
- Demo fallback:
uv run replay.py recordings/demo.jsonl --speed 2replays a known-good recorded run: 3 of 3 drifts, decoy passed, a follow-up turn. No API key or network needed. - Reference solution:
solution/holds complete versions of the five learner scripts. Run them withuv run solution/02_start.pyand so on.uv run solution/make_stubs.pyregenerates the learner files from them. - Tests:
uv run pytestruns offline tests of the fixture, schema, checks, and stubs. They make no API calls.
This is an independent educational project, not official Anthropic documentation. For the platform itself, see the Managed Agents docs. Licensed under the MIT License; see also SECURITY.md, CONTRIBUTING.md, and SUPPORT.md.