The SDK offers three execution tiers. Select one explicitly based on the access boundary you need; a working directory alone is not a security boundary.

Guarantees by tier

Tier Access Requirements
process Host filesystem and network access; temporary working directory POSIX host
docker Workdir writable, configured mounts read-only, network disabled unless enabled Docker package and running daemon
seatbelt Workdir writable, allowlisted reads, network disabled unless enabled macOS with sandbox-exec

An unavailable explicitly selected backend fails before dispatch. It never silently falls back to host execution.

Execute Python

import asyncio
from harnessx import Sandbox, SandboxConfig

async def main():
    async with Sandbox(SandboxConfig(tier="process")) as sandbox:
        result = await sandbox.execute("print(40 * 10)")
        print(result.stdout)

asyncio.run(main())

This local process example has host access. Use docker or seatbelt when an access boundary is required, and verify its configuration on the target host.

Run the bash built-in inside a sandbox

Agent(sandbox=sandbox) binds the sandbox as the engine of the bash built-in, so shell commands the model requests run inside it. The registry exposes the binding as agent.tools.sandbox.

import asyncio
from harnessx import Agent, CliPermissionManager, Sandbox, SandboxConfig

async def main():
    config = SandboxConfig(tier="process")  # docker or seatbelt for an access boundary
    async with Sandbox(config) as sandbox:
        async with Agent(
            tools=["bash"],
            sandbox=sandbox,
            permissions=CliPermissionManager(),
        ) as agent:
            result = await agent.run("List the files in the working directory.")
            result.raise_for_status()
            print(result.output)

asyncio.run(main())

The sandbox is caller-owned; the agent does not close it. Passing a ToolRegistry that is already bound to a different sandbox raises ValueError.

Observe executions

Sandbox(config, hooks=hook_manager) emits HookEvent.SANDBOX_EXEC for every execute() and execute_command() with kind, tier, exit_code, timed_out, and execution_time_ms in ctx.data (the SandboxExecData TypedDict in harnessx.hooks). Agent(sandbox=...) supplies the agent's own hooks when the sandbox has none, so agent.hooks.on(HookEvent.SANDBOX_EXEC, ...) observes the bash built-in's commands. See Middleware & hooks.

Limits and lifecycle

Timeout, CPU, and file-size limits apply as supported by the selected tier. Address-space limiting is platform-dependent and is not a universal memory isolation guarantee. Docker can enforce container memory limits.

The process backend kills and reaps its managed process group on timeout/cancellation. Deliberately detached descendants can escape that group. Cancellation does not reverse completed file or external changes.

Injected sandboxes remain caller-owned. Use their async context manager around the agent or application lifetime.

Try the coding example

uv run python -m examples.sandboxed_coder

Review the example's explicit tier and permissions before using it for an untrusted task. See Permissions for execution policy and Recovery for interrupted effects.