This package provides tool support for inspect_ai without requiring custom Docker images or Dockerfiles. It uses an executable injection approach to deploy tool functionality directly into running containers.
Some tools can be implemented without the need for any in-process state. For those tools, the tool code will be executed within the inspect-sandbox-tools process.
For tools that require the maintenance of state over the lifetime of a sandbox, this system marshals tool calls into a long running process via JSON RPC to a server process. That server then dispatches tool calls to tool specific @method handlers.
Each tool should have its own subdirectory that contains the following files:
-
json_rpc_methods.pyThis module contains all of the JSON RPC
@methodfunctions — one for each tool (e.g. the web browser tool is actually a set of distinct tools). It is responsible for unpacking the JSON RPC request and forwarding the call to a transport-agnostic, strongly typed, stateful controller. -
tool_types.pyThis module includes the
pydanticmodels representing the types for tool call parameters and results. -
controller.pyThis is transport-agnostic, strongly typed code that manages the tool specific in-process state and performs requested commands.
The inspect_sandbox_tools package is part of a split architecture that separates tool support into two independent systems:
- Legacy system (
inspect_tool_support): Temporarily handles web browser functionality. Uses JSON-RPC communication but deploys code via Docker images built from Dockerfiles until the engineering to get Playwright included in the PyInstaller bundled executable works robustly. - This system (
inspect_sandbox_tools): Handles all other tools (bash_session, text_editor, MCP). Uses JSON-RPC communication with runtime executable injection for deployment.
Portable Linux executables are built via PyInstaller + StaticX for cross-distribution portability. Build scripts live in src/inspect_ai/tool/_sandbox_tools_utils/ and output to src/inspect_ai/binaries/ (amd64/arm64). See RELEASING.md for build, validation, and release commands.
When a tool needs to run in a container, the system automatically injects the appropriate executable:
- Tool requests a sandbox via
container_tools_sandbox() - System checks if
/opt/inspect-sandbox-toolsexists in the container - If missing, the injection process:
- Detects container architecture (amd64/arm64)
- Selects the appropriate pre-built executable from binaries
- Writes executable to
/opt/inspect-sandbox-toolsin container - Sets execute permissions
The system includes fallback mechanisms to download executables from S3 or build them locally if needed.
Tools communicate through a two-layer RPC architecture:
Layer 1 - Host to Container (stateless):
- Tool creates JSON-RPC request on host
SandboxJSONRPCTransportexecutes:sandbox.exec(["/opt/inspect-sandbox-tools", "exec"], input=json_rpc_request)- JSON-RPC payload passed via stdin to the injected executable
- Response returns via stdout
Layer 2 - Container Internal (stateful operations):
- When stateful execution is needed, the injected executable acts as a client
- It starts a server process if not already running
- Sends JSON-RPC requests to the server via HTTP over Unix socket (
~/.cache/container-tools.sock) - Server maintains state across requests and returns responses
- The stateless executable forwards the response back through Layer 1
See RELEASING.md for the end-to-end process for building, publishing, and distributing new sandbox tools versions.
When running pytest with inspect to test interactions with this package, you may wish to test your local version of the inspect_tool_support code instead of the latest published package. Passing the flag --local-inspect-tools to pytest when running tests from test_inspect_container_tools.py will build and install the package from source, for example:
pytest tests/tools/test_inspect_container_tools.py --runslow --local-inspect-tools