Skip to content
agentvfs ★ GitHub

← Back to writing

Designing for agent-aware filesystem access

agentvfs / neullabs · ·
filesystemagentsruntime

A human at a keyboard and an LLM agent are not the same user. They want different things from a filesystem. The traditional answer — “just give the agent a directory” — papers over the difference and pays for it later in race conditions, opaque failures, and lost state. This post walks through what an agent-aware filesystem actually has to do, using agentvfs’s design as the concrete example.

A human wants names. An agent wants a contract.

A human types ls, reads the output, and infers what to do next from the prose. Filenames are mnemonic. Errors are sentences. State is whatever the previous shell session left in the directory. None of that is a problem for a person; it is a problem for an agent.

An agent needs:

  1. A typed surface. Every operation has a defined input and output shape. Errors are not free-form strings; they are records with a type, a message, and the fields needed to act on them.
  2. A scope it can reason about. “The current directory” is too vague. The agent needs a named container — a workspace — that is the explicit subject of every operation.
  3. A rollback primitive. Humans recover by reading the error and undoing manually. Agents recover by restoring state.
  4. A history it can query. Humans recover from confusion by remembering. Agents recover by asking the audit log.
  5. A change report after every action. Humans look. Agents need the answer to “what did I just change?” as a list, not a diff to parse.

agentvfs’s design is what falls out when you take those five requirements seriously.

Vault: the explicit container

agentvfs’s first move is to refuse the “ambient current directory” model. There is no pwd to maintain; every operation happens against a vault. A vault is a single SQLite file (by default; Sled and LMDB are pluggable) that holds files, directories, versions, metadata, snapshots, and an audit log. You name it on creation, you list vaults, you switch between them, you back one up by copying the file.

Making the workspace an explicit, named, file-backed object gives you three properties that matter for agents:

Reproducibility. A vault is a file. Two agents working on copies of the same vault start from the same state.

Portability. Copy the .avfs file to another machine, register it, and the entire workspace moves with you. The cloud-sync caveat is in the docs: don’t access the same SQLite file from two machines at once, but the file itself is the unit of motion.

Concurrency boundaries. The vault is the unit of locking, the unit of statistics, the unit of audit. When an agent asks “what did I change?”, the answer is scoped to one vault.

Forks: cheap per-task state

The single biggest gap in “give the agent a directory” is the cost of trying things. If the agent wants to attempt a refactor and roll back on failure, the human-pattern answer is git stash plus careful housekeeping. That is fine for a person; it is brittle in an agent loop where the model might propose three attempts in a row.

agentvfs answers with vault fork. The fork is a task workspace, scoped to a single attempt. It clones the source vault cheaply and lets the agent operate inside it without touching the canonical state. If the attempt works, you keep the fork or merge it back. If it doesn’t, you delete the fork. The roadmap is explicit that forks are intended to be the default unit of agent work; ephemeral per-task fork lifecycle is a near-term priority.

This single primitive — cheap, scoped, named forks — is what lets an agent loop tolerate the model’s mistakes. Each attempt gets its own state. Failures do not poison the next try.

Checkpoints: undo at a known boundary

Forks scope work. Checkpoints undo it. Inside a fork (or a vault), checkpoint save before-refactor records the current state. After a risky command, checkpoint restore before-refactor snaps back. Policy can auto-checkpoint before commands the runtime classifies as risky, so the agent does not need to remember to do it.

The combination is more powerful than either alone. The fork bounds which state can be affected. The checkpoint bounds when. Together they give the agent a coordinate system for “let me try this, and if it doesn’t work, here is where to go back to” — the workflow that, in human use, is normally distributed across git, scratch directories, and willpower.

Structured I/O, not parsed prose

Every agentvfs command supports --json. Read a file, get { path, content, size, version, modified }. List a directory, get { path, entries: [{ name, type, size, modified }] }. Write a file, get { path, size, version, created }. Errors are typed: { "error": "QuotaExceeded", "message": "...", "type": "max_file_size_mb", "requested": 15, "limit": 10 }.

This sounds obvious until you compare it to the alternative, which is the agent parsing ls output and inferring “is this a directory?” from the trailing slash. The structured contract is what makes the harness simple. The agent does not parse; it queries a field.

The proxy execution surface — the one that actually runs commands on behalf of the agent — extends this. avfs proxy exec -- cargo test returns an ExecutionEnvelope: stdout, stderr, exit code, duration, policy decision, and a ChangeSummary listing the files that changed. The agent has the information it needs to decide the next step without re-reading the workspace.

Invariants that survive concurrent agents

The other thing the agent-aware filesystem has to do is be honest about concurrency. Two agents — or one agent and a human inspecting the workspace — might hit the same vault. The design has to hold.

agentvfs’s production-features section lists the invariants in plain language:

  • Atomic transactions. Every SQLite mutation uses BEGIN IMMEDIATE / COMMIT / ROLLBACK. There is no in-flight half-written state visible to a concurrent reader.
  • Execution timeouts. proxy exec carries a configurable timeout. SIGTERM escalates to SIGKILL. Dedicated pipe-drain threads collect stdout and stderr so a hung child cannot wedge the proxy.
  • Mount session state machine. MountSession owns the FUSE mount through Validating → Mounting → Executing → Completed. Double-unmount is guarded. Automatic cleanup runs on drop.
  • Backend deduplication. A weak-reference cache ensures one backend handle per vault, so concurrent opens do not stack up resources.
  • Open-file state tracking. FUSE handles transition Open → Dirty → Persisting → Flushed, eliminating the persist races that show up when one process writes and another flushes.

These are the kinds of invariants you only notice when they fail. They are also the kinds of invariants that turn an interesting prototype into something an agent can actually run against for hours.

Quotas and audit

Two more things follow from “the user is an LLM, not a human”: bounds on resource use, and a complete record of what happened.

Quotas are configurable per vault: max_size_mb, max_files, max_file_size_mb. When the limit is hit, the agent gets a typed QuotaExceeded error with the current and limit values. This is the right shape: the agent can decide to compress, to delete, or to ask the user. It is not the agent’s job to parse “no space left on device”.

The audit log records every operation. avfs audit --json returns a list of entries with timestamp, operation, path, result, and error fields. The agent can filter by operation, by time range, by path prefix. When something went wrong three steps ago, the agent does not have to remember; it queries.

The shape of the API

Pull this together and the API surface becomes:

  • one container abstraction (vault)
  • one scoping primitive (fork)
  • one undo primitive (checkpoint)
  • one execution boundary (proxy exec)
  • one query format (JSON, always)
  • one log (audit, queryable)
  • a small set of typed errors

That is what an agent-aware filesystem actually looks like. It is not a directory with a sandbox bolted on. It is a workspace runtime where the primitives match the loop the agent is actually running. The reason the design feels different from a normal filesystem is that it is different, on purpose.

The most useful test for any agent-facing API is to write a 30-line harness in any language that can spawn a subprocess and parse JSON. If that is easy, the API is right-shaped. agentvfs is built so it is.