Sandbox¶
Filesystem explains where an agent’s “files and commands” come from. When these operations must be isolated from the host process, executed in a replaceable execution environment (local Unix, Docker, etc.), and able to restore the same workspace state across multiple calls, use the sandbox mode described here (filesystem(SandboxFilesystemSpec)).
1. What Sandbox Solves¶
Execution boundary: the model operates on files and commands through the same
AbstractFilesystem/ShellExecuteToolinterfaces, but the actual IO and processes happen in an isolated environment managed by the sandbox client — suitable for scenarios where user input cannot be fully trusted, or where decoupling from the production host is required.Recoverable work unit: unlike “single HTTP requests”, multi-turn
calls should be able to continue in the same logical workspace.SandboxManagerpersists sandbox-side state after eachcallends (viaSandboxStateStore), and looks it up byIsolationScope+sessionId/userIdkeys on the nextacquire.Relationship to the Harness workspace: the host still has a
WorkspaceManagerroot directory; what the sandbox sees is defined byWorkspaceSpecand workspace projection mechanisms (syncing/mounting certain host paths into the sandbox at startup).
2. Assembly in Harness¶
When sandbox mode is enabled, HarnessAgent.Builder will:
Call
SandboxFilesystemSpec#toSandboxContext(hostWorkspaceRoot)to get aSandboxContext(containingSandboxClient, isolation scope, snapshot spec,WorkspaceSpec, etc.), while packing host-side paths to project into the sandbox (AGENTS.md,skills/,subagents/,knowledge/) into aWorkspaceProjectionEntry(see §7 Workspace Projection).Use
SandboxBackedFilesystemas the agent’sAbstractFilesystemimplementation (transparent to upper layers).Construct
SandboxManager(client, stateStore, agentId); when no explicitSandboxFilesystemSpec#sandboxStateStoreis configured, defaults toSessionSandboxStateStore(effectiveSession, agentId), tying sandbox metadata to the currentSession.Register
SandboxLifecycleHook(sandboxManager, filesystemProxy)(priority50): on everyPreCallit acquires →start()(including the 4-branch workspace initialization, see §6 Snapshot and 4-Branch Recovery); onPostCall/Erroritstop()(persist snapshot) → persist state → release and clears the active session on the proxy.
Only when the backend implements AbstractSandboxFilesystem does HarnessAgent register ShellExecuteTool; in sandbox mode, files and shell commands go through the sandbox — the host is unaffected.
For steps to implement a custom non-Docker isolation backend (SandboxClient, SandboxState, SandboxFilesystemSpec, etc.) and a self-check checklist, see §5 Extending Custom Sandbox Execution Environments.
3. Isolation Dimensions (IsolationScope)¶
IsolationScope controls the persistence key for sandbox state (sandbox mode) and the namespace prefix for shared storage (store mode, see Filesystem mode 1). Both modes share the same enum with consistent semantics.
Scope |
Persistence key source |
Behavior when missing |
Typical use case |
|---|---|---|---|
|
|
Skip state lookup, create new sandbox |
Each session has its own sandbox/memory; conversation isolation |
|
|
Warn and fall back to new |
Same user shares workspace or memory across sessions (including distributed) |
|
Agent name (fixed at build time) |
— |
All users and sessions of a single agent share the same workspace |
|
Fixed value |
— |
All agents/users/sessions globally share within one store |
3.1 SESSION — Conversation Isolation (Default)¶
Each conversation has its own independent sandbox with no interference. Suitable for multi-user SaaS where each session’s temporary work files and installed dependencies are isolated.
HarnessAgent agent = HarnessAgent.builder()
.name("code-agent")
.model(model)
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
.snapshotSpec(new OssSnapshotSpec(...)))
// isolationScope defaults to SESSION; this line can be omitted
.filesystem(dockerSpec.isolationScope(IsolationScope.SESSION))
.build();
// Different sessionId on each call → independent sandbox
agent.call(msgs, RuntimeContext.builder().sessionId("user1-session1").build()).block();
agent.call(msgs, RuntimeContext.builder().sessionId("user1-session2").build()).block();
3.2 USER — User-level Sharing (Recommended for Distributed Memory)¶
Most common distributed scenario: multiple Pods/processes serving multiple sessions for the same user in parallel, but the user’s long-term memory (MEMORY.md, memory/) must stay consistent across all replicas.
Sandbox mode + USER: different sessions (different Pods) write the latest snapshot reference to the same state slot (key = userId) after each conversation ends. The next time any replica handles the same user, it restores the same workspace from that snapshot. Note this is sequential reuse, not concurrent sharing: concurrent requests each get independent containers, but both update the same state slot on stop(), with last-write winning. For AGENT / GLOBAL scopes requiring strong mutual exclusion, see §9 Concurrency Control.
Remote mode + USER (equivalent without sandbox): RemoteFilesystemSpec uses userId as the KV namespace prefix, so all reads/writes routed to MEMORY.md, memory/, etc. land under the same store key — enabling memory sharing across distributed replicas without snapshots.
// Sandbox + USER isolation: same user shares snapshots across Pods
HarnessAgent agent = HarnessAgent.builder()
.name("assistant")
.model(model)
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
.snapshotSpec(new OssSnapshotSpec(...))
.isolationScope(IsolationScope.USER))
.sandboxDistributed(SandboxDistributedOptions.oss(redisSession, ossSnapshotSpec))
.build();
RuntimeContext ctx = RuntimeContext.builder()
.userId("alice") // same userId → same state slot → recoverable workspace
.sessionId("session-xyz")
.build();
agent.call(msgs, ctx).block();
// Remote mode + USER isolation: lightweight distributed memory sharing (no sandbox)
HarnessAgent agent = HarnessAgent.builder()
.name("assistant")
.model(model)
.filesystem(new RemoteFilesystemSpec(redisStore)
.isolationScope(IsolationScope.USER))
.build();
// All replicas with the same userId share MEMORY.md / memory/ directory
3.3 AGENT — Agent-level Sharing¶
All users and sessions of the same agent (by name) share the workspace snapshot or storage namespace. Suitable for “public knowledge base” agents: a single global workspace with write order determined by call sequence; suitable for tool-type, read-only, or admin scenarios.
3.4 GLOBAL — Global Sharing¶
The widest sharing scope within a store/workspace instance. Use with caution.
4. Custom Sandbox Instances and Lifecycle Management¶
By default, SandboxManager fully manages sandbox create / start / stop / shutdown (self-managed). When you need to reuse an existing container, share a sandbox across multiple agents, or manage the container lifecycle yourself, you can hand lifecycle control back to the caller in two ways.
4.1 Passing an Existing Sandbox Instance (User-Managed, Highest Priority)¶
On each call, bring in an already-started Sandbox object via SandboxContext in RuntimeContext:
// Pre-create and start sandbox (container lifecycle managed by caller)
Sandbox mySandbox = dockerClient.create(workspaceSpec, snapshotSpec, options);
mySandbox.start();
// Inject this instance on each call
SandboxContext callCtx = SandboxContext.builder()
.client(dockerClient) // same client used at agent build time
.externalSandbox(mySandbox) // ← explicitly tell Manager: this is user-managed
.build();
RuntimeContext ctx = RuntimeContext.builder()
.sessionId("my-session")
.sandboxContext(callCtx) // overrides the defaultSandboxContext from build time
.build();
agent.call(msgs, ctx).block();
// SandboxLifecycleHook will call mySandbox.stop() (persist snapshot)
// but will NOT call mySandbox.shutdown() — container keeps running
Behavior rules (SandboxManager.acquire’s 4-level priority):
Priority |
Condition |
Behavior |
|---|---|---|
1 (highest) |
|
Use directly, mark user-managed; |
2 |
|
Restore from specified state, self-managed |
3 |
Persisted state exists in |
Restore by |
4 (default) |
None of the above |
Create new sandbox, self-managed |
4.2 Passing Serialized State (Precise Snapshot Recovery)¶
If you already have a SandboxState JSON string saved from a previous call, you can bypass SandboxStateStore automatic lookup and directly specify the state to restore:
// Load previously serialized state (e.g., from a database or request parameters)
String savedStateJson = db.load("checkpoint-2026-04-28");
SandboxState savedState = dockerClient.deserializeState(savedStateJson);
SandboxContext callCtx = SandboxContext.builder()
.client(dockerClient)
.externalSandboxState(savedState) // ← specify state; SDK handles resume + lifecycle
.build();
RuntimeContext ctx = RuntimeContext.builder()
.sandboxContext(callCtx)
.build();
agent.call(msgs, ctx).block();
5. Extending Custom Sandbox Execution Environments¶
When your application needs an isolation backend other than Docker (custom remote executor, commercial sandbox API, local mock, etc.), no changes to the Harness source are required: implement the contract types below and plug them in via HarnessAgent.Builder#filesystem(SandboxFilesystemSpec). Overall assembly still follows §2: SandboxContext → SandboxBackedFilesystem → SandboxManager → SandboxLifecycleHook.
5.1 Extension Points Overview¶
Extension Point |
Responsibility |
Contract with Framework |
|---|---|---|
|
create / resume in isolated environment, serialize/deserialize sandbox state |
|
|
Backend-specific fields to persist across |
Base class only has |
|
|
Typically extends |
|
Connect client, options, default |
Implement |
5.2 Implementing SandboxClient and Options¶
Define
MySandboxClientOptions extends SandboxClientOptions:getType()returns a stable string (e.g.acme), consistent with thetypein persistence/config. If you need to deserialize options from YAML/JSON, add Jackson registration forSandboxClientOptionspolymorphism (see framework’sDockerSandboxClientOptionsand the base class’s@JsonTypeInfo); skip this if configuring from Java code only.Implement
SandboxClient<MySandboxClientOptions>: increateconstruct aSandboxthat has not yet beenstart()ed;serializeState/deserializeStatemust be consistent with theSandboxStatesubclass fields;deletecan be a no-op if there are no additional resources.If
deserializeStateusesobjectMapper.readValue(json, SandboxState.class), thatObjectMappermust registerHarnessSandboxJacksonModule(which includes built-inNamedTypefordockeretc.) and yourNamedType(MySandboxState.class, "acme"). No-argnew DockerSandboxClient()automatically registersHarnessSandboxJacksonModule;new DockerSandboxClient(customMapper)requires manuallyregisterModule(new HarnessSandboxJacksonModule())andregisterSubtypes, otherwise reading back persisted state will fail.
5.3 SandboxState and Jackson¶
Design intent: the subtype table is provided at runtime via Module /
registerSubtypes, making it easy for the same application or downstream jars to extend state without modifyingSandboxState.java.This repo: in
io.agentscope.harness.agent.sandbox.json.HarnessSandboxJacksonModule, addregisterSubtypes(new NamedType(XxxSandboxState.class, "xxx"))for new official backends.Application-private subclasses: call
mapper.registerSubtypes(new NamedType(MySandboxState.class, "acme"))on anyObjectMapperthat holds sandbox JSON, and ensureSandboxManager/SandboxStateStorepaths use the same mapper configuration as theSandboxClient.
5.4 Implementing SandboxFilesystemSpec¶
createClient(): return yourSandboxClient(or create it viaoptions.createClient(), same pattern asDockerFilesystemSpec).clientOptions(): return the mutable configuration object.snapshotSpec()/workspaceSpec(): can provide defaultNoopSnapshotSpecandnew WorkspaceSpec(); callers can still override withSandboxFilesystemSpec#snapshotSpec(...)before building the agent.
Reference implementation: InMemorySandboxFilesystemSpec in the repo (agentscope-harness test support, harness-example-sandbox example project) simulates a sandbox with a temp directory, requires no Docker, and is a good minimal skeleton to copy and modify.
5.5 Enabling¶
Same as Docker, pass your spec to HarnessAgent.builder():
HarnessAgent agent = HarnessAgent.builder()
.name("my-agent")
.model(model)
.filesystem(new MySandboxFilesystemSpec()
.isolationScope(IsolationScope.SESSION))
.build();
When distributed Session or OSS/Redis snapshots are needed, continue configuring sandboxDistributed(...) as needed (see §10); snapshot spec and isolated execution backend are chosen independently on SandboxFilesystemSpec.
5.6 Self-Check Checklist¶
[ ] JSON written by
Sandbox#getState()can be losslessly read back bySandboxClient#deserializeState.[ ]
SandboxClientOptions#getType()and thetypeinSandboxStateJSON do not conflict globally.[ ] All **
ObjectMapper**s that doreadValue(..., SandboxState.class)have the correspondingNamedTyperegistered (includingHarnessSandboxJacksonModuleif Docker-compatible data must be read).[ ] If using
AbstractSandboxFilesystem,ShellExecuteTooland file APIs are routed to yourSandbox#execand sandbox-internal path conventions.
5.7 Optional Sandbox Backends: Kubernetes / Daytona / E2B (agentscope-harness subpackages)¶
These three remote sandbox implementations are in agentscope-harness’s io.agentscope.harness.agent.sandbox.impl.* subpackages (alongside io.agentscope.harness.agent.sandbox.impl.docker), available within the same artifact; dependencies like fabric8 and protobuf-java are declared in agentscope-harness’s pom.xml.
Java Package |
Description |
|---|---|
|
Uses Pod as remote host: fabric8 Exec + container-side |
|
Daytona HTTP API: |
|
E2B: |
Jackson assembly: register at least HarnessSandboxJacksonModule on any ObjectMapper holding sandbox state JSON, then register each backend’s SimpleModule as needed (e.g., new KubernetesHarnessSandboxJacksonModule()), consistent with §5.3. Do not add @JsonSubTypes on core SandboxClientOptions for optional backends — this would cause harness to reverse-depend on optional modules creating Maven cycles; optional backends are wired on the application side via their own FilesystemSpec + SandboxClientOptions subclass.
Dependency coordinates: depend only on agentscope-harness (or harness coordinates managed by agentscope / BOM); agentscope-all includes these implementation classes alongside harness.
5.8 Workspace Bind Mount (BindMountEntry)¶
Place io.agentscope.harness.agent.sandbox.layout.BindMountEntry (Jackson polymorphic name bind_mount) in WorkspaceSpec#getEntries(): the key (map key) is the relative mount point under workspace root (POSIX style, e.g. data → {root}/data), hostPath is the absolute path on the host (Docker machine / K8s node), readOnly controls whether read-only.
Backend |
Behavior |
|---|---|
Docker |
Adds **`-v host:container:rw |
Kubernetes |
Adds a HostPath |
Daytona / E2B |
Cannot mount your host directory to a remote cloud sandbox; WARN at startup if spec contains bind mounts; entries do not take effect. |
Snapshots and tar: when persisting workspace, the framework appends arguments like tar --exclude=./ plus the entry relative path to avoid including external directories under mount points in the archive (aligned with the Python reference implementation). If you want a path not in snapshots but still written by the applier as initial content, use an ephemeral regular file/directory entry rather than a bind mount.
Security: when hostPath comes from configuration or upstream input, restrict it to trusted directories; bind mounts are equivalent to letting container-side processes directly access the host path.
6. Snapshot and 4-Branch Recovery¶
Sandbox.start() decides how to initialize the workspace via 4 branches, ensuring correct recovery under all combinations of “whether the container is still available” and “whether a snapshot is available”:
Branch A: workspaceRootReady=true & directory still exists in container → only re-apply ephemeral entries (fastest, warm start)
Branch B: workspaceRootReady=true & directory lost in container → restore from snapshot + re-apply ephemeral entries
Branch C: workspaceRootReady=false & snapshot available → restore from snapshot + re-apply all entries
Branch D: workspaceRootReady=false & no snapshot available → full initialization from WorkspaceSpec (cold start)
When Sandbox.stop() executes, if SandboxSnapshotSpec has persistence enabled, the workspace is tarred and stored in the snapshot backend (OSS, Redis, local file, etc.), and workspaceRootReady is set to true. This tar is the archive used by Branch B/C for the next recovery.
WorkspaceEntry.ephemeral flag: each entry in WorkspaceSpec can be marked as ephemeral (re-written on every start) or non-ephemeral (saved with snapshot, only written on cold start). For host-side files that may be updated at any time like skills/ and AGENTS.md, use WorkspaceProjectionEntry (next section) rather than the ephemeral flag.
Available snapshot spec types:
Spec |
Storage Location |
|---|---|
|
No persistence; cold start from WorkspaceSpec after container rebuild |
|
Host local file (suitable for single-machine long-running) |
|
OSS / S3-compatible storage (suitable for multi-replica) |
|
Redis (suitable for low-latency, small workspaces) |
7. Workspace Projection and Skills Sync¶
Workspace projection (WorkspaceProjectionEntry) is the mechanism by which Harness syncs specific directories/files from the host workspace into the sandbox at every sandbox startup — the foundation for Skills and other capabilities running inside the sandbox.
7.1 Projection Scope¶
When SandboxFilesystemSpec builds SandboxContext, it defaults to packing the following host paths into the projection:
AGENTS.md ← agent identity and instructions
skills/ ← directory for all Skills in SkillBox (includes SKILL.md and script files)
subagents/ ← subagent spec files
knowledge/ ← domain knowledge files
You can customize which root paths to project via SandboxFilesystemSpec#workspaceProjectionRoots(List<String>), or completely disable projection via workspaceProjectionEnabled(false).
7.2 How Projection Works¶
WorkspaceProjectionApplier runs at the end of Sandbox.start():
Iterates all
WorkspaceProjectionEntrys, collects the host-side file set, sorts by path, and computes a SHA-256 content hash.Packs these files into a tar, and decompresses them to the corresponding paths inside the sandbox workspace via
Sandbox.hydrateWorkspace(archive).Stores the current hash in
SandboxState.workspaceProjectionHash; on the next startup, if the hash is unchanged, skips the projection (avoiding redundant transfers).
This means: when skills/ content on the host is updated, the hash changes on the next sandbox start, and new files are automatically synced in; changes to skill files inside the sandbox are not synced back to the host.
7.3 How Skills Execute Inside the Sandbox¶
Harness’s SkillBox mechanism injects the instructions from workspace/skills/<skill-name>/SKILL.md into the agent’s system prompt; after the model understands “this skill is needed”, it executes scripts or commands in the skill directory via ShellExecuteTool. In sandbox mode, all of this happens inside the sandbox:
host workspace/skills/pytest/
│── SKILL.md # description: how to run pytest
└── run_tests.sh # actual script
▼ projection (at every startup)
sandbox /workspace/skills/pytest/
│── SKILL.md
└── run_tests.sh
agent thinks and calls shell_execute:
"bash /workspace/skills/pytest/run_tests.sh tests/"
↓
ExecResult(exitCode=0, stdout="5 passed")
Benefit: scripts run in the isolated container; pip install, apt-get, rm -rf and other operations only affect the sandbox workspace — the host is unaffected. After the sandbox is snapshotted, installed dependencies are archived along with the workspace, so the next restore can use them directly (Branch A/B/C) without reinstalling.
7.4 State Persistence of Shell Commands and Scripts¶
ShellExecuteTool calls AbstractSandboxFilesystem.execute(cmd, timeout) → Sandbox.exec(cmd, timeout), executing commands in the sandbox. All filesystem changes from commands (new files, installed packages, written logs, etc.) are retained inside the sandbox’s overlay/container. When stop() runs, these states are persisted with the tar snapshot and restored on the next start().
Therefore, state is fully preserved across calls:
call 1: shell_execute("pip install pandas") → pandas installed in sandbox
call 2: shell_execute("python analyze.py") → directly available, no reinstall
call 3: shell_execute("cat results.csv") → reads file produced in call 2
8. State: SandboxStateStore and Session¶
SandboxStateStore: abstracts persistence of “sandbox metadata (sessionId + snapshot reference) bound to an isolation key”. Easy to replace with a custom implementation; configured onSandboxFilesystemSpec#sandboxStateStore(defaults when not set).Default
SessionSandboxStateStore: depends on theSessionselected at build time (the session abstraction shared withSessionPersistenceHooketc.; if you use a distributedSessionlike Redis, sandbox metadata becomes visible across processes).WorkspaceSessionremains responsible for per-session configuration under the workspace layout; do not conflateWorkspaceSession’s JSON with “sandbox state JSON” — the sandbox resume data source of truth isSandboxStateStore.
9. Concurrency Control: SandboxExecutionGuard¶
9.1 Concurrency Safety Boundaries¶
Different IsolationScopes have different concurrency safety guarantees:
Scope |
Concurrency Safety |
Notes |
|---|---|---|
|
✅ Naturally isolated |
Each session has its own state slot; requests from different sessions do not interfere |
|
⚠️ Needs extra guarantee in multi-replica |
Multiple sessions of the same user are reused sequentially; |
|
⚠️ Needs extra guarantee |
All users/sessions share the same state slot; concurrent writes in multi-replica may cause snapshots and state to overwrite each other |
|
⚠️ Needs extra guarantee |
Same as |
For SESSION scope, single-instance checkRunning=true (default) is sufficient; USER, AGENT, and GLOBAL in multi-replica deployments should all explicitly configure SandboxExecutionGuard to serialize access to shared slots.
9.2 SandboxExecutionGuard Interface¶
@FunctionalInterface
public interface SandboxExecutionGuard {
// Block until execution rights for the key's slot are obtained; return the held lease
SandboxLease tryEnter(SandboxIsolationKey key) throws InterruptedException;
// Default: no-op; behavior identical to not being configured
static SandboxExecutionGuard noop() { ... }
}
Lifecycle: the guard intervenes before acquire; lease.close() is automatically called by Harness after release() completes, covering the entire call window:
tryEnter(key) ← may block here until the previous call completes
└─ acquire / resume sandbox
└─ sandbox.start()
└─ [agent call executing]
└─ persistState()
└─ sandbox.stop() + shutdown()
lease.close() ← release execution rights; next waiter can enter
Priority 1 (externalSandbox) and Priority 2 (externalSandboxState) bypass the guard — user-managed sandboxes handle concurrency control on the caller side.
9.3 Built-in Redis Implementation¶
RedisSandboxExecutionGuard uses Redis SET NX PX leases for distributed mutual exclusion, sharing the same UnifiedJedis instance as RedisSnapshotSpec without introducing extra dependencies. It encodes IsolationScope into the lock key, so USER, AGENT, and GLOBAL each land on their own independent distributed lock:
UnifiedJedis jedis = new JedisPooled("redis-host", 6379);
// Share the same jedis instance with RedisSnapshotSpec
SandboxExecutionGuard guard = RedisSandboxExecutionGuard.builder(jedis)
.leaseTtl(Duration.ofMinutes(30)) // must be greater than worst-case call duration
.retryInterval(Duration.ofMillis(500)) // polling interval
.keyPrefix("myapp:sandbox:lock:") // optional; use for multi-environment isolation
.build();
HarnessAgent.builder()
.name("shared-agent")
.model(model)
.filesystem(new DockerFilesystemSpec()
.image("ubuntu:24.04")
.isolationScope(IsolationScope.AGENT)
.snapshotSpec(new RedisSnapshotSpec(jedis, null, null))
.executionGuard(guard)) // ← serialize concurrent access at AGENT dimension
.sandboxDistributed(SandboxDistributedOptions.builder()
.session(redisSession)
.requireDistributed(true)
.build())
.build();
Redis key format: <keyPrefix><scope_lower>:<value>, for example:
myapp:sandbox:lock:user:alicemyapp:sandbox:lock:agent:shared-agentmyapp:sandbox:lock:global:__global__
If you change isolationScope to USER, the same guard should be reused; the lock key is then bucketed by userId, protecting the shared sandbox state slot for the same user across multiple replicas.
TTL note: TTL is a safety valve, not a correctness guarantee. If a call exceeds the TTL, Redis automatically releases the lock so the next waiter can enter — this prevents permanent deadlock from process crashes, but cannot guarantee the state safety of the timed-out call itself. Set leaseTtl to a reasonable upper bound of actual call duration (including LLM latency and retries).
9.4 Custom Implementation Reference¶
SandboxExecutionGuard is a @FunctionalInterface; any lock backend can be connected:
// Example: JVM in-memory Semaphore (single-process multi-thread scenario)
Map<String, Semaphore> semaphores = new ConcurrentHashMap<>();
SandboxExecutionGuard jvmGuard = key -> {
Semaphore sem = semaphores.computeIfAbsent(key.toString(), k -> new Semaphore(1));
sem.acquire(); // block until available, respects InterruptedException
return sem::release; // SandboxLease: release semaphore
};
Implementation contract:
tryEnterblocks until execution rights are obtained or the thread is interrupted (throwsInterruptedException)lease.close()must be idempotent and must not throw exceptions (failures can only be internally logged)Implementation must be thread-safe
10. Distributed Operation and sandboxDistributed¶
When multi-replica or stateless workers need to share the sandbox recovery capability of the same logical session, you need:
A distributed
Session(e.g.,RedisSession), not just the defaultWorkspaceSessionfile backend; andA non-no-op
SandboxSnapshotSpec(archives the workspace for re-fetching), which passes the “must be distributed” validation.
HarnessAgent.Builder#sandboxDistributed(SandboxDistributedOptions) can uniformly configure:
Override
snapshotSpec(if provided);IsolationScopeis only configured onSandboxFilesystemSpec, not repeated here;Explicitly specify the
Sessionused for the sandbox (if different from the mainsession) in the options;Use
SandboxDistributedOptions#oss/#redisetc. helper constructors for common combinations (see class JavaDoc).
If requireDistributed is true but the current effectiveSession is still WorkspaceSession or the snapshot is no-op, the build fails fast.
11. Choosing Between the Three Filesystem Modes¶
Sandbox is one of three declarative configuration options. For a full comparison see Filesystem; here are the key decision points:
You primarily need |
Recommended mode |
|---|---|
Multi-instance sharing of |
|
Single-process/local, trusted shell, without a separate sandbox |
|
Isolated execution, commands and files in sandbox, long-session recovery, optional snapshots + cluster |
|
12. Subagents¶
When SubagentsHook is enabled, if the parent agent is built in sandbox mode, subagents’ filesystem reuses the same SandboxBackedFilesystem session-binding strategy (per current implementation, facilitating environment sharing within the same orchestration tree). Subagents remain independent ReActAgent instances; the isolation boundary is consistent with the parent agent’s sandbox spec.