Filesystem¶
Purpose¶
AbstractFilesystem abstracts agent access to the workspace from “must be local disk” into a unified interface: ls / read / write / edit / grep / glob / upload / download. When executing commands in an isolated environment is required, a backend additionally implements AbstractSandboxFilesystem, which is when HarnessAgent registers ShellExecuteTool.
In Harness, the filesystem serves three distinct but often confused roles:
Tool surface:
FilesystemTool(and optionalShellExecuteTool) recognize a singleAbstractFilesysteminstance; all paths and executions flow through this outlet, making it easy to swap implementations.Physical landing for workspace reads/writes:
WorkspaceManagerreads “filesystem first, fall back to local if not found”; writes and uploads always go through filesystem. Therefore where long-term memory, daily logs, and session logs ultimately land depends on which mode you choose.Multi-tenant and isolation:
NamespaceFactoryassembles a path prefix fromRuntimeContext.userIdand other sources on every operation, making the same codebase transparently switch storage shards across user / session / global boundaries;RemoteFilesystemSpecandSandboxFilesystemSpecalso connect IsolationScope to “shared KV” or “sandbox state key”, aligned with the Sandbox isolation story.
Three Declarative Modes¶
HarnessAgent.Builder accepts at most one of the filesystem(...) family (mutually exclusive with abstractFilesystem(...); the latter is an escape hatch for self-managed implementations, see next section):
Mode |
Config Method |
Typical Artifact |
Shell |
Best For |
|---|---|---|---|---|
1 — Composite + Shared Storage |
|
|
No |
Multi-replica sharing of |
2 — Sandbox |
|
|
Yes (inside sandbox) |
Isolated execution, recoverable sandbox sessions, optional snapshots and distributed sessions |
3 — Local + Shell |
|
|
Yes (host |
Single-process/local, trusted environment, simple scripts and tests |
When no filesystem(...) is called, it is equivalent to explicit filesystem(new LocalFilesystemSpec()) — mode 3, with root directory at workspace and host shell available.
Mode 1: Composite + Storage (RemoteFilesystemSpec)¶
Structure:
RemoteFilesystemSpec#toFilesystemassembles aCompositeFilesystem:Default/unmatched prefixes → plain
LocalFilesystem(noShellExecuteTool)Configured prefixes (e.g., defaults include
MEMORY.md,memory/,agents/<agentId>/sessions/+ extensible viaaddSharedPrefix) →RemoteFilesystem(onBaseStore, namespace controlled byIsolationScope: SESSION / USER / AGENT / GLOBAL)
Why not
LocalFilesystemWithShellby default: mode 1’s design goal is cross-node consistent long memory and logs while avoiding opening a shell on the host; use modes 2 or 3 when a shell is needed.
Mode 2: Sandbox (SandboxFilesystemSpec)¶
See Sandbox. Key point: still exposes
AbstractFilesystem+ optionalShellExecuteTool(viaAbstractSandboxFilesystem) to upper layers, but actual IO/processes happen on theSandboxClientside in an isolated environment;SandboxLifecycleHookacquires/persists/releases around eachcall.
Mode 3: Local + Shell (LocalFilesystemSpec or default)¶
Behavior:
LocalFilesystemWithShelluses the workspace as root, commands run as hostsh -c(configurable timeout, environment variables,virtualMode, etc.) — fundamentally different from mode 1’s “shell-free local root”.
Class Hierarchy and ShellExecuteTool Registration¶
classDiagram
class AbstractFilesystem {
<<interface>>
ls/read/write/edit
grep/glob
uploadFiles/downloadFiles
}
class AbstractSandboxFilesystem {
<<interface>>
+id() String
+execute(cmd, timeout)
}
AbstractSandboxFilesystem --|> AbstractFilesystem
class LocalFilesystem
class LocalFilesystemWithShell
class BaseSandboxFilesystem
class RemoteFilesystem
class CompositeFilesystem
class SandboxBackedFilesystem
LocalFilesystem ..|> AbstractFilesystem
RemoteFilesystem ..|> AbstractFilesystem
LocalFilesystemWithShell --|> LocalFilesystem
LocalFilesystemWithShell ..|> AbstractSandboxFilesystem
BaseSandboxFilesystem ..|> AbstractSandboxFilesystem
CompositeFilesystem ..|> AbstractFilesystem
SandboxBackedFilesystem ..|> AbstractSandboxFilesystem
CompositeFilesystemonly implementsAbstractFilesystem, notAbstractSandboxFilesystem, so it does not registerShellExecuteTool. If you need composite routing plus a shell, provide a shell-capable default backend viaabstractFilesystemor use sandbox/local mode.read(filePath, offset, limit):limit <= 0means “use the implementation-defined default line count” (may differ between local and sandbox).
Implementation Quick Reference¶
Implementation |
Description |
|---|---|
|
Local files only, no execution; |
|
Local + host shell; core of mode 3 |
|
Base class for connecting to remote Unix; most methods implemented via |
|
KV storage based on |
|
Multi-backend via longest-prefix matching; no shell capability |
|
Sandbox proxy implementing |
BaseSandboxFilesystem Default Implementation Strategy¶
Subclasses primarily implement execute / uploadFiles / downloadFiles / id; the base class typically implements ls/read/grep/glob/edit/write as remote shell and Python3 snippets, enabling quick deployment in standard Unix environments.
NamespaceFactory and Multi-Tenancy¶
@FunctionalInterface
public interface NamespaceFactory { List<String> getNamespace(); }
Called on every file operation, returns the current request’s path segments (e.g., ["users", "alice"]). When building HarnessAgent, you can use an AtomicReference linked to RuntimeContext.userId, so the same AbstractFilesystem instance routes to different subtrees for different users.
Configuration Examples¶
Recommended: choose one of the three modes first, then only touch abstractFilesystem when needed:
// Mode 3: explicit local + shell (equivalent to "no filesystem" default; useful for adjusting timeouts, etc.)
HarnessAgent agent = HarnessAgent.builder()
.name("local")
.model(model)
.workspace(workspace)
.filesystem(new LocalFilesystemSpec().executeTimeoutSeconds(120))
.build();
// Mode 1: share long-term memory to Store (no host shell)
HarnessAgent agent = HarnessAgent.builder()
.name("store")
.model(model)
.workspace(workspace)
.filesystem(new RemoteFilesystemSpec(redisStore)
.isolationScope(IsolationScope.USER))
.build();
// Mode 2: sandbox (specific spec varies by implementation, e.g. Docker)
HarnessAgent agent = HarnessAgent.builder()
.name("sandbox")
.model(model)
.workspace(workspace)
.filesystem(dockerFilesystemSpec) // extends SandboxFilesystemSpec
.build();
Escape hatch (mutually exclusive with the filesystem(...Spec) calls above):
HarnessAgent agent = HarnessAgent.builder()
.name("custom")
.model(model)
.workspace(workspace)
.abstractFilesystem(myCustomTree) // fully self-managed AbstractFilesystem tree
.build();
Manual composition (advanced): inside abstractFilesystem or a custom factory, you can still use CompositeFilesystem + LocalFilesystemWithShell, etc., but you are responsible for security boundaries and whether ShellExecuteTool should be exposed.