Skip to content

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}")
A-101: PermissionError: refunds above 200 need approval; ticket opened
checked 2 orders, 1 damaged

Your rule held: no refund was issued. Then the model tries a shortcut and opens the database itself:

OperationalError: unable to open database file
at agent.py:2

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:

def test_cleanup_gone_wrong(self):
    shutil.rmtree(os.path.expanduser('~/important-notes'))
pynox -m unittest -q
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.

mean: 13
stored: {"mean": 13, "count": 3}

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 in pynox.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.