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):
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)
- Default/unmatched prefixes → plain
- 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
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
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
["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.
WorkspaceIndex and grep Semantics (Mode 1)
RemoteFilesystem optionally attaches a local SQLite WorkspaceIndex (auto-built when using RemoteFilesystemSpec) to accelerate ls / glob / exists / grep by avoiding full-store scans. The index is best-effort and may not reflect writes made by sibling replicas.
ls / glob / existsconsult the index first and fall back to a store scan when no match is found, so cross-replica visibility is preserved.grepfollows the same pattern: index-driven candidate enumeration first, store-scan fallback when the index yields zero matches. This ensuresgrepon node B still surfaces files written via node A even if B’s index has not yet been refreshed.- Callers needing authoritative enumeration (rather than fast read paths) should refresh the index via
WorkspaceIndex.rebuildFromDisk(...)or rely on the store-scan fallback path.
Configuration Examples
Recommended: choose one of the three modes first, then only touchabstractFilesystem when needed:
filesystem(...Spec) calls above):
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.
Related Pages
- Sandbox — sandbox mode principles,
SandboxStateStore, distributed options - Tool —
FilesystemTool/ShellExecuteToolparameters - Workspace —
WorkspaceManagerand two-layer reads - Architecture — collaboration with hooks and
RuntimeContext