Use cases
Want to see a real model do it?
Real agents runs the analyst, support and coding agents with Pydantic AI, Strands Agents and Claude, with transcripts from real runs.
Five agents people build with Pynox. Each one is a runnable file in
examples/use_cases/, exercised by the test suite on every commit, so the
output below is what the code prints. The model's code is fixed text in each
example, the kind of code a model writes for that request, so you can run
them without an API key.
PYNOX_BIN=$PWD/target/release/pynox PYTHONPATH=python:examples \
uv run python -m use_cases.data_analyst
| Agent | What Pynox gives it |
|---|---|
| Data analyst | Notebook-style state across turns, reads data/, writes out/, nothing else |
| Support agent | Calls your backend functions; your rules hold, your database stays out of reach |
| Coding agent | Runs the project's tests; a test that deletes the wrong directory fails instead |
| API agent | Network and an API key for one tool only, granted by you |
| Two-level agent | Monty runs the plan, Pynox runs the heavy code |
Data analyst
The request: "Which region grew the most last quarter? Write a report."
The model explores the data over several turns, the way a person uses a
notebook. It loads the CSV once and keeps rows and growth in memory between
calls, so later turns are fast and short.
import pynox
notebook = pynox.tool(session=True, allow_read=["data"], allow_write=["out"], timeout="20s")
agent = Agent("anthropic:claude-sonnet-4-5", tools=[notebook]) # Pydantic AI, for example
What the tool returns to the model, turn by turn:
>>> rows = list(csv.DictReader(open(DATA))) ...
400 rows; columns: region, quarter, revenue
>>> growth = {region: (q2 - q1) / q1 ...}; best = max(growth, key=growth.get) ...
south grew 31.0%
>>> with open(OUT, 'w') as f: ...
report written
>>> print(open(ENV).read()) # "let me check the data source configuration"
PermissionError: [Errno 13] Permission denied: '/work/.env'
at agent.py:1
For files, grant the specific path with --allow-read/--allow-write. ...
The report lands in out/report.md. The project's .env, one directory up
from the data, never reaches the model: the kernel denied the read, and the
model read why. Add preload=["pandas"] to a pynox.Pool and the same agent
imports pandas at no cost per turn.
examples/use_cases/data_analyst.py
"""A data analyst agent: notebook-style analysis over local files.
The model explores a CSV across several turns, keeps variables between them,
writes a report, and tries to read something it should not. The host allows
reading ./data and writing ./out, for 20 seconds per step.
"""
import csv
import random
import tempfile
from pathlib import Path
import pynox
# What a model writes, turn by turn, for "Which region grew the most? Write a report."
TURNS = [
"import csv\n"
"with open(DATA) as f:\n"
" rows = list(csv.DictReader(f))\n"
"print(len(rows), 'rows;', 'columns:', ', '.join(rows[0]))",
"from collections import defaultdict\n"
"totals = defaultdict(lambda: [0.0, 0.0])\n"
"for r in rows:\n"
" totals[r['region']][r['quarter'] == 'Q2'] += float(r['revenue'])\n"
"growth = {k: (q2 - q1) / q1 for k, (q1, q2) in totals.items()}\n"
"best = max(growth, key=growth.get)\n"
"print(f'{best} grew {growth[best]:.1%}')",
"with open(OUT, 'w') as f:\n"
" f.write('# Revenue growth Q1 to Q2\\n\\n')\n"
" for k, g in sorted(growth.items(), key=lambda kv: -kv[1]):\n"
" f.write(f'- {k}: {g:+.1%}\\n')\n"
"print('report written')",
# The model gets curious about the project's secrets "to check the data source".
"print(open(ENV).read())",
]
def make_data(root: Path) -> Path:
rng = random.Random(7)
data = root / "data"
data.mkdir()
with open(data / "sales.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["region", "quarter", "revenue"])
for region, trend in [("north", 1.05), ("south", 1.31), ("east", 0.92), ("west", 1.12)]:
for _ in range(50):
base = rng.uniform(800, 1200)
writer.writerow([region, "Q1", f"{base:.2f}"])
writer.writerow([region, "Q2", f"{base * trend:.2f}"])
(root / "out").mkdir()
(root / ".env").write_text("WAREHOUSE_PASSWORD=hunter2\n")
return data / "sales.csv"
def run(root: Path) -> list[tuple[str, str]]:
source = make_data(root)
report = root / "out" / "report.md"
notebook = pynox.tool(
session=True, allow_read=[root / "data"], allow_write=[root / "out"], timeout="20s"
)
notebook(f"DATA = {str(source)!r}\nOUT = {str(report)!r}\nENV = {str(root / '.env')!r}")
return [(code, notebook(code)) for code in TURNS]
if __name__ == "__main__":
with tempfile.TemporaryDirectory() as temporary:
for code, answer in run(Path(temporary)):
print(f">>> model code\n{code}\n<<< tool result\n{answer}\n")
print((Path(temporary) / "out" / "report.md").read_text())
Support agent
The request: "Ana says an order arrived damaged. Sort it out."
The agent needs your order system. Instead of one tool call per lookup, its code calls your functions directly and chains them, the pattern known as CodeAct. The functions run in your process, outside the sandbox, with your database connection and your business rules.
def find_orders(customer: str) -> list[dict]:
"""List a customer's orders with id, total and status."""
def issue_refund(order_id: str, amount: float) -> str:
"""Refund an order. Amounts above 200 need a human."""
run_python = pynox.tool(functions={"find_orders": find_orders, "issue_refund": issue_refund})
The model reads those signatures in the tool's description and writes:
from pynox_host import find_orders, issue_refund, HostError
orders = find_orders("ana@example.com")
damaged = [o for o in orders if o["status"] == "damaged"]
for order in damaged:
try:
print(issue_refund(order["id"], order["total"]))
except HostError as error:
print(f"{order['id']}: {error}")
Your rule held: no refund was issued. Then the model tries a shortcut and opens the database itself:
The code can only reach your system through the functions you handed it.
examples/use_cases/support_agent.py
"""A support agent that calls your backend through host functions.
The model's code chains lookups and a refund in one run. The functions run in
your process, where the database and the business rules live; the sandboxed
code can call them but cannot open the database itself.
"""
import json
import sqlite3
import tempfile
from pathlib import Path
import pynox
def make_backend(root: Path):
database = root / "orders.db"
with sqlite3.connect(database) as db:
db.execute("create table orders (id text, customer text, total real, status text)")
db.executemany(
"insert into orders values (?, ?, ?, ?)",
[
("A-100", "ana@example.com", 42.0, "delivered"),
("A-101", "ana@example.com", 310.0, "damaged"),
("B-200", "bo@example.com", 18.5, "delivered"),
],
)
refunds = []
def find_orders(customer: str) -> list[dict]:
"""List a customer's orders with id, total and status."""
with sqlite3.connect(database) as db:
rows = db.execute(
"select id, total, status from orders where customer = ?", (customer,)
).fetchall()
return [{"id": i, "total": t, "status": s} for i, t, s in rows]
def issue_refund(order_id: str, amount: float) -> str:
"""Refund an order. Amounts above 200 need a human."""
if amount > 200:
raise PermissionError("refunds above 200 need approval; ticket opened")
refunds.append((order_id, amount))
return f"refunded {amount:.2f} on {order_id}"
return database, refunds, {"find_orders": find_orders, "issue_refund": issue_refund}
# What a model writes for "Ana says an order arrived damaged. Sort it out."
MODEL_CODE = """
from pynox_host import find_orders, issue_refund, HostError
orders = find_orders("ana@example.com")
damaged = [o for o in orders if o["status"] == "damaged"]
for order in damaged:
try:
print(issue_refund(order["id"], order["total"]))
except HostError as error:
print(f"{order['id']}: {error}")
print(f"checked {len(orders)} orders, {len(damaged)} damaged")
"""
def run(root: Path) -> dict:
database, refunds, functions = make_backend(root)
run_python = pynox.tool(functions=functions)
answer = run_python(MODEL_CODE)
# The model then tries to skip the rules and edit the database directly.
bypass = run_python(
f"import sqlite3\nsqlite3.connect({str(database)!r}).execute(\"update orders set status='refunded'\")"
)
return {"answer": answer, "bypass": bypass, "refunds": refunds, "doc": run_python.__doc__}
if __name__ == "__main__":
with tempfile.TemporaryDirectory() as temporary:
print(json.dumps(run(Path(temporary)), indent=2))
Coding agent
The request: "Fix the bug in calc.py and make sure the tests pass."
Coding agents run test suites dozens of times per task, on code they just wrote. Here a test "cleans up fixtures" with the wrong path:
ERROR: test_cleanup_gone_wrong (test_calc.CalcTest.test_cleanup_gone_wrong)
...
PermissionError: [Errno 13] Permission denied: '/home/you/important-notes'
Ran 2 tests in 0.001s
FAILED (errors=1)
The real test passed, the bad one failed loudly, and ~/important-notes is
still there. -m makes the project directory readable and nothing writable;
add --allow-write=build for a build directory. It is the same command a
developer types, so the agent's tool is one line:
subprocess.run(["pynox", "-m", "pytest", "-q", "-p", "no:cacheprovider"], cwd=repo, capture_output=True)
examples/use_cases/coding_agent.py
"""A coding agent that runs the project's tests after editing it.
`pynox -m unittest` makes the project directory readable and nothing writable,
so a test, or code the model just wrote, cannot delete files or read secrets
outside the project, and cannot write inside it either.
"""
import os
import subprocess
import tempfile
from pathlib import Path
PROJECT = {
"calc.py": "def add(a, b):\n return a + b\n",
"test_calc.py": (
"import os, shutil, unittest\n"
"from calc import add\n\n"
"class CalcTest(unittest.TestCase):\n"
" def test_add(self):\n"
" self.assertEqual(add(2, 3), 5)\n\n"
" def test_cleanup_gone_wrong(self):\n"
" # A model 'cleaning up fixtures' with the wrong path.\n"
" shutil.rmtree(os.path.expanduser('~/important-notes'))\n"
),
}
def run(root: Path, binary: str) -> dict:
project = root / "project"
project.mkdir()
for name, source in PROJECT.items():
(project / name).write_text(source)
notes = root / "home" / "important-notes"
notes.mkdir(parents=True)
(notes / "plan.txt").write_text("do not delete")
env = {**os.environ, "HOME": str(root / "home")}
result = subprocess.run(
[binary, "--allow-env=HOME", "-m", "unittest", "-q"],
cwd=project,
env=env,
capture_output=True,
text=True,
timeout=60,
)
return {
"exit": result.returncode,
"stderr": result.stderr,
"notes_survived": (notes / "plan.txt").exists(),
}
if __name__ == "__main__":
with tempfile.TemporaryDirectory() as temporary:
outcome = run(Path(temporary), os.environ.get("PYNOX_BIN", "pynox"))
print(outcome["stderr"])
print("exit", outcome["exit"], "| notes survived:", outcome["notes_survived"])
API agent
The request: "What's the weather in Lisbon?"
The model writes ordinary urllib code and reads the key from the
environment. You decide which tool gets the network and which variable it
sees; the key never appears in the prompt or the model's code.
fetch = pynox.tool(allow_network=True, env={"WEATHER_KEY": os.environ["WEATHER_KEY"]})
think = pynox.tool() # same code, no network
fetch → {'city': 'Lisbon', 'temp_c': 21}
think → URLError: <urlopen error [Errno 1] Operation not permitted>
Depending on the platform, the denial can also arrive as
PermissionError: [Errno 13] Permission denied; either way the socket never
opens.
Network access is all or nothing today: a tool with the grant can reach any IPv4/IPv6 host, though never a subprocess or a host Unix socket. Give it only to the tools that need it. Per-domain allowlists are planned.
examples/use_cases/api_agent.py
"""An agent that calls an external API with a key it never sees in its prompt.
The host forwards one environment variable and turns the network on for this
profile only. Without the grant, the same code cannot open a socket.
A local HTTP server stands in for the API.
"""
import json
import threading
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import pynox
class WeatherAPI(BaseHTTPRequestHandler):
def log_message(self, *args):
pass
def do_GET(self):
authorized = self.headers.get("Authorization") == "Bearer demo-key"
body = json.dumps(
{"city": "Lisbon", "temp_c": 21} if authorized else {"error": "unauthorized"}
)
self.send_response(200 if authorized else 401)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body.encode())
def model_code(port: int) -> str:
# What a model writes for "What's the weather in Lisbon?"
return (
"import json, os, urllib.request\n"
f"request = urllib.request.Request('http://127.0.0.1:{port}/weather?city=Lisbon',\n"
" headers={'Authorization': 'Bearer ' + os.environ['WEATHER_KEY']})\n"
"print(json.load(urllib.request.urlopen(request, timeout=5)))\n"
)
def run() -> dict:
server = ThreadingHTTPServer(("127.0.0.1", 0), WeatherAPI)
threading.Thread(target=server.serve_forever, daemon=True).start()
try:
code = model_code(server.server_address[1])
key = {"WEATHER_KEY": "demo-key"}
offline = pynox.tool(env=key)(code) # same key, no network grant
online = pynox.tool(allow_network=True, env=key)(code)
finally:
server.shutdown()
return {"offline": offline, "online": online}
if __name__ == "__main__":
print(json.dumps(run(), indent=2))
Two-level agent with Monty
The request: "Average these readings and store them."
Monty runs the agent's plan in a tiny
interpreter that can pause, snapshot and resume. When the plan needs real
Python, it calls run_python, and Pynox runs that part with the full
ecosystem and the permissions the host fixed.
See Monty for the walkthrough and the code.
What every case shares
- The model only writes code. Paths, network, keys, time and memory are
arguments you pass to
pynox.tool()or profiles inpynox.toml. - The kernel enforces the policy. Landlock and seccomp apply to Python and to native code alike; there is no wrapper to talk around.
- Failures are information. The model gets the exception, the line and a hint, and its next attempt is usually right.
- It is fast enough to call freely. About 5 ms per call from a warm pool, so an agent can run code twenty times in a conversation without anyone noticing.
Read the sandbox contract for exactly what is enforced. Pynox controls automation; it is not a boundary for deliberately hostile code.