ADR 011: Agent integration contract
Status
Accepted — 2026-10-17.
Context
Agent frameworks adopt an executor through a small protocol and a working example, not by replacing the interpreter. Integrators ask for the same things: run a string of code without writing files, structured errors a model can act on, permissions decided by the host, and a warm path for many short calls. Pydantic's Monty runs a restricted Python subset for glue code and calls back into the host through external functions; it has no ecosystem, which is precisely what Pynox provides.
Decision
Pool requests accept code with an optional name instead of script.
Inline code grants no file access and reports its errors against the given
name. Requests choose permissions and limits per call, or name a profile
from the pool's configuration, within the operator's [pool.limits]
(ADR 012); the model only supplies code. Results carry exit, captured output, a structured error with type,
message, location and hint, duration and the effective policy.
Ship a Python package, pynox, as the integration surface: pynox.run(code)
returns a Result; pynox.tool(profile=...) returns a typed, documented
run_python(code) -> str that every agent framework accepts unchanged; Pool
gives explicit lifecycle. The package is a thin client of the pool protocol and
contains no agent loop, prompt or framework adapter, so Pynox serves
frameworks instead of competing with them. Its wheel is tagged per CPython
minor and embeds a matching CPython, so the sandbox imports the packages of
the environment that installed it. examples/agent_tool.py and
examples/monty_bridge.py use it and run in the test suite. Sessions
(ADR 005) are exposed as Pool.session() and tool(session=True).
Host functions let the model's code call the application's own tools, the
CodeAct pattern: a request declares functions; inside the task they exist
as pynox_host.NAME; a call travels as a JSON line on the report descriptor,
becomes a call event for the client and returns the client's reply. The
supervisor checks every call against the declared names, so forged calls
never reach the client, and bounds each call to 1 MB and each task to 1,000
calls. The functions run in the client's process, outside the sandbox, which
is the point: the host decides what they may reach. Waiting for the host
counts against the task's timeout. Next contract: network allowlists by
domain.
Alternatives considered
- Require a file per task: forces temporary files into every integration.
- Let code requests choose their own grants: the model would set its policy.
- Build a Pynox agent framework: competes with integrators instead of serving them.
- A Python subset or new language: Monty already serves that role; Pynox complements it with CPython.
Consequences
The pool protocol is a public contract and changes need a record. Examples are tests, so a dependency such as pydantic-monty lives in the integration group and is pinned. Pynox does not snapshot interpreter state mid-execution as Monty does; persistent sessions approximate it with a living process. Adoption is measured by integrations, not downloads.