context.sandboxes. Both managed frameworks use the same API.
Enable container execution
From the agent folder, install the Fused-maintained Docker Harnest Extension, then resolve it into the agent’s runtime lock: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
Assign it to your agent
Addsandboxes=["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
sandboxes assignment has no named sandbox access. The same declarations and assignments work on ADK and LangGraph.
Call it from a business tool
Createtools/total_values.py. Harnest exposes this tool to the model; the sandbox itself stays behind the tool:
tools/total_values.py
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
-
Start Docker and check that
docker versioncan reach its daemon. - Configure your agent’s model in the serving process environment.
-
Add guidance to the agent’s
instructions.md, for example:instructions.md -
From the agent folder, run
harnest test ., thenharnest serve .. -
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}.
Assign more than one sandbox
Add another declaration such assandbox/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:
Keep sandbox selection inside your business tool. Expose arbitrary code or provider selection to the model only if that broader authority is intentional.
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 default Docker sandbox starts a fresh container for each execution with networking disabled and bounded CPU, memory, processes, output, and scratch space. Choose reuse scope and networking only when your workload needs them; see execution budgets for defaults and image requirements. Retained topologies are scoped caches, not durable storage. They may be evicted, and a session ID never grants reuse across users or agents. Use application storage for persistence. Timeouts, cancellation, and output overflow discard the container; replacement waits for confirmed cleanup. An unavailable Docker daemon cannot prove that a container stopped. The Docker provider does not transfer input or output files. Use stdout or a provider with an explicit file contract. Docker shares the host kernel, so choose stronger isolation when your workload requires it. See Docker cleanup and security before deployment.Framework behavior
Queued calls recheck invocation authority before execution, so cancelled, expired, or revoked requests cannot start.
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 implementsSandboxBackend: its execute(request: SandboxRequest) method returns SandboxResult. Pass a lazy factory to Sandbox.provider(...):
sandbox/research.py
sandboxes=["research"]. CompanyBackend is your SDK adapter. Put connection settings and credentials in its factory, which runs on first use.
Implement SandboxBackend using the request, context, result, and file contracts. Keep provider-specific settings inside the factory.
Providers enforce timeout and isolation. Reject missing identity if your provider requires it; revoked managed contexts never become anonymous. File names do not authorize host-path access.
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
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 contains immutable JSON properties, separate from SDK configuration and omitted from object representations. Invalid JSON values are rejected.
Declaration metadata reaches request.metadata. Provider result metadata reaches your tool; return only fields the model needs.
Wrap the provider to return measured details as SandboxResult metadata. Keep secrets in configuration and declare SDK dependencies in pyproject.toml. Never implement isolation with host-side exec(), eval(), or an unisolated subprocess.
Handle failed execution
Checkresult.status, not whether stderr is empty. A program can write warnings to stderr and still succeed, or exit nonzero without an error message.
Use SandboxStatus to distinguish program failure, timeouts, cancellation, and output-budget exhaustion. The execution status reference explains each result.
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
Nested execution failures and shorter deadlines affect only their scope. Recoverable errors can be retried within the parent budget; cancellation and revocation block further execution. Import provider controls withfrom 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. Cleanup has a finite deadline that nested scopes cannot extend. It forbids new execution and preserves the original cancellation state.
Cleanup deadlines are cooperative. Apply the remaining budget to SDK calls and cleanup.acquire(lock). If removal is uncertain, retain the resource handle and block replacement until confirmed.