Skip to main content
Use a sandbox when your authored Agent Tool needs to run Python outside the agent server. Define the provider, assign it to the agents that need it, and call it from the tool through context.sandboxes. The same API works with managed ADK and LangGraph.
An assignment does not create a model-callable execution tool. You choose the business tool’s inputs, the code it submits, and the result it returns. Only that submitted code runs in the sandbox; the surrounding tool function and the agent server do not.

Enable container execution

From the agent folder, install the Fused-maintained Docker Harnest Extension, then resolve it into the agent’s runtime lock:
Create one file per sandbox, then choose which sandboxes each agent can use. For example, sandbox/calculations.py must define a variable named calculations. If initialization generated sandbox/_example.py, rename it to calculations.py; the example already uses that variable name. Underscore-prefixed examples remain ignored.
sandbox/calculations.py
Docker must be available when code executes. The extension owns the Docker SDK and provider implementation; Harnest core supplies the framework-neutral sandbox contract. Compilation and agent startup do not start a container, and there is no fallback to executing submitted code in the agent server. The extension declares sandbox.provider, creates no browser or model tool, and works for ordinary Python workloads on managed ADK and LangGraph.

Assign it to your agent

Add sandboxes=["calculations"] to the existing Agent(...) declaration in agent.py. Keep its other settings, such as the model, unchanged. A minimal complete example is:
agent.py
Discovering a sandbox file does not give any agent permission to use it. An agent with no sandboxes assignment has no named sandbox access. The same declarations and assignments work on ADK and LangGraph.

Call it from a business tool

Create tools/total_values.py. Harnest exposes this tool to the model; the sandbox itself stays behind the tool:
tools/total_values.py
Use execute(code) in a synchronous tool or await aexecute(code) in an asynchronous tool. Both return SandboxResult with stdout, stderr, output_files, and metadata. Your tool chooses what to return to the model; the example returns only the total, not the provider response. The API also accepts keyword-only input_files=() for providers that support file transfer. The Docker extension rejects input files and does not mount host files. The example passes serialized values in the code instead.

Try it with your agent

  1. Start Docker and check that docker version can reach its daemon.
  2. Configure your agent’s model in the serving process environment.
  3. Add guidance to the agent’s instructions.md, for example:
    instructions.md
  4. From the agent folder, run harnest test ., then harnest serve ..
  5. Open the playground at http://127.0.0.1:1907/ and ask: “Use total_values to add 17, 23, and 41.” Check the tool event as well as the final answer; the expected tool result is {"total": 81}.
Offline tests validate your project but do not prove Docker execution works. The playground check uses your configured model and the Docker extension. See Serving agents for server settings and authentication.

Assign more than one sandbox

Add another declaration such as sandbox/research.py, defining research = Sandbox.provider(...) or using another installed extension’s factory. Then add its name only to the agents that should use it: The model chooses among your business tools, not among automatic sandbox tools. Your tool chooses an assigned sandbox in its implementation. Do not add a model-controlled provider name or arbitrary code parameter unless that broad execution capability is intentional. Provider configuration and invocation identity remain outside the model’s control. Names must be Python-style ASCII identifiers, at most 47 characters: start with a letter, then use letters, digits, or underscores. Names beginning with _ are ignored examples, not active sandboxes. The filename and variable name must match. Put helpers in lib/, not in extra public files inside sandbox/. The root sandbox/ catalog is available to all agents in the same project, including folder-owned, flat-file, graph, and code-defined SubAgents. Each agent must still declare its own sandboxes list: a parent’s assignment never grants access to a child. A folder-owned SubAgent can add names in its own sandbox/; those names are local to that scope and its descendants and cannot duplicate an ancestor’s names.
Agent(sandbox=...) has been removed. Put each provider in sandbox/<name>.py, assign sandboxes=["<name>"] to every allowed agent, and call context.sandboxes["<name>"] from an authored tool. A single sandbox uses the same one-element array.

Docker scopes and budgets

The Docker extension works with both frameworks and does not use ADK’s sandbox executor. Defaults provide a fresh container per execution, disabled networking, a non-root user, a read-only root filesystem, and bounded writable /tmp scratch space.
sandbox/calculations.py
Retained scopes reuse the primary container and its /tmp scratch files while that identity remains cached, but not live Python processes or durable storage. Every successful call stops primary-container processes, including detached children. Declared service containers remain running for the retained topology. Topologies are scoped caches and may be evicted; use application storage for persistence. Invocation/session scopes reject requests without the full required identity. A session ID alone never permits reuse across users or agents. The Docker extension can also create an extension-owned bridge with bounded service containers, internal DNS aliases, readiness checks, and independent budgets. It never publishes declared service ports on the host. See Docker extension for the topology API and cleanup contract. The extension starts Docker only on execution. Timeouts, cancellation, and output overflow remove the owned container. Replacement waits for confirmed cleanup; ambiguous creation or failed cleanup blocks further admission until ownership is reconciled. Application shutdown closes created providers and removes all retained containers. Initial image preparation and Docker control-plane calls use SDK transport timeouts. Harnest checks admission and the execution deadline again when those calls return. An unavailable Docker daemon cannot prove that a container stopped. The Docker extension does not support input-file transfer or output artifacts. Use stdout or a custom provider with an explicit file contract. Docker shares its host kernel; use a stronger isolation provider if that boundary is insufficient for your workload. options retains the allowlisted parsing/retry fields only for callers explicitly constructing a native ADK adapter with to_adk_executor(). Those fields do not change Docker limits or add model tools to named sandbox assignments. Your authored tool decides whether to retry.

Framework behavior

The Docker extension uses Harnest’s shared provider contract underneath either integration. Queued calls are checked again before execution, so cancelled, expired, or revoked requests cannot start later merely because the queue advances. You keep one authoring API; Harnest adapts it to the selected framework’s execution loop. Named assignments do not populate ADK’s code_executor field or create an automatic LangGraph execution tool. Both frameworks access the same sandbox contract through authored tools. context.sandboxes is available only during a managed invocation. Looking up an unassigned name raises ContextResourceError. Using a handle from a different or ended invocation raises ContextUnavailableError; do not cache handles or pass them between agents.

Bring your own provider

A portable provider implements SandboxBackend: its execute(request: SandboxRequest) method returns SandboxResult. Pass a lazy factory to Sandbox.provider(...):
sandbox/research.py
Assign this provider with sandboxes=["research"]. CompanyBackend represents your adapter around a provider SDK, not a Harnest built-in. Put SDK-specific connection settings and credentials in that factory. Harnest calls it when execution first needs the backend, not during compilation. Import these types from harnest.sandbox. Your provider must enforce the requested timeout and its own isolation policy. Invocation identity can be absent when an adapter is called outside the runtime; reject such requests if your provider requires a user or session identity. A revoked managed context fails closed rather than becoming anonymous. File names are transport values, not permission to access host paths. The Docker extension’s guard cannot terminate arbitrary third-party SDK calls. Custom providers remain responsible for stopping active work, bounded output collection, and resource cleanup. Harnest checks invocation validity before admitting their calls.

Declare provider-enforced network policy

Use a Harnest network policy only when the provider enforces it below agent code. The provider declares its supported controls, and Harnest fails before the first execution if any requested guarantee is missing:
sandbox/research.py
Modes are none, unrestricted, and allowlist. Allowlist entries are exact DNS names or IP literals, not URLs or wildcards. Providers must enforce them on every connection and resolution, including redirects and DNS changes, and must apply the optional port restriction. block_private_networks=True covers private, loopback, link-local, and metadata destinations. Application request validation does not satisfy this contract. If a backend does not expose typed sandbox_capabilities, omit network_policy and treat network behavior as entirely provider-owned. Native ADK executors cannot accept a Harnest network policy. Reusable Harnest Extensions implementing a sandbox provider declare the sandbox.provider extension capability.
Named sandbox capabilities require the neutral SandboxBackend contract on both frameworks. Wrap a native-only provider in that contract before assigning it by name. The removed Agent(sandbox=...) argument is not a migration path.

Provider metadata

metadata accepts arbitrary JSON properties separately from native SDK configuration. Harnest copies and deeply freezes these properties, preserves JSON value types, and omits them from declaration, request, and result representations. Unsupported objects, non-string object keys, nonfinite numbers, and cycles are rejected rather than converted to strings. Declaration metadata reaches request.metadata; the named capability API does not let the model override it. Your provider can return separate SandboxResult(metadata=...) properties, such as execution status or usage figures. These reach your authored tool unchanged; return only the fields the model needs. The Docker extension does not automatically echo declaration metadata into results. Use a provider wrapper when you want to return measured or provider-supplied details. For a remote provider, translate the SDK response into SandboxResult. Keep secrets in provider configuration, not returned metadata. Add the provider package to your project’s pyproject.toml. Do not implement execute() with host-side exec(), eval(), or an unisolated subprocess.
Your authored tool controls whether provider metadata becomes model-visible. Do not return credentials, secrets, or private identifiers. Legacy model execution adapters expose result metadata automatically. In the legacy ADK code-execution loop, nonempty metadata wraps stdout as JSON with stdout and metadata keys because that native result has no metadata field. Empty metadata leaves stdout unchanged; stderr keeps ADK’s native error behavior.

Handle failed execution

Check result.status, not whether stderr is empty. A program can write warnings to stderr and still succeed, or exit nonzero without an error message. exit_code is the process exit code when available. Custom providers must set failure status explicitly: SandboxResult(status="failed", exit_code=1, stderr="..."). The default status is succeeded; a nonzero exit code cannot claim success. Sanitized SandboxExecutionError also exposes a status field. Provider exceptions are exposed as sanitized SandboxExecutionError. Diagnose infrastructure failures with trusted provider diagnostics; do not log generated code, credentials, or complete result payloads as routine audit data.

Failure recovery and cleanup

An ordinary exception escaping a nested execution scope fails that scope and blocks its retained workers. It does not cancel the parent operation. Your provider can catch a recoverable error, such as a missing container, and retry within the remaining parent budget. A shorter helper deadline also stays local. Explicit cancellation and invocation revocation still block further execution. Import provider controls with from harnest.sandbox import control. Use control.execute(timeout_seconds=...) for nested execution scopes and control.current() to inspect the active control. Use control.cleanup(...) to release resources after cancellation or invocation revocation:
client.remove represents your provider SDK’s resource-removal call; use its actual timeout parameter. Cleanup has its own finite deadline, and nested cleanup cannot extend it. New sandbox execution is forbidden inside cleanup. Returning restores the original execution control without clearing its cancellation; retained cleanup contexts are then revoked. This is a cooperative deadline, not a mechanism for killing Python threads. Check before each cleanup operation and apply the remaining budget to SDK I/O and lock acquisition (cleanup.acquire(lock)). A blocking SDK that ignores timeouts can still overrun the budget. Treat timeout or uncertain removal as incomplete cleanup, retain the owned resource handle, and prevent replacement until cleanup is confirmed.

Security ownership

Harnest enforces named agent grants, revocable invocation handles, request validation, and deadline propagation. The Docker extension enforces identity-scoped containers, kernel CPU/memory/process/scratch budgets, host deadlines, bounded output, and failure-safe cleanup. It does not mount host paths and rejects image-declared volumes. Custom providers remain responsible for their own tenant separation, filesystem/network policy, resource limits, output bounds, cancellation, and cleanup. Harnest validates their requests and results and checks admission; it cannot enforce Docker limits on an arbitrary remote SDK. Deployment permissions do not replace an enforcing provider.