Skip to content
agentvfs ★ GitHub

← Back to writing

Comparing agentvfs to bare Docker for agent code-execution

agentvfs / neullabs · ·
dockercompareagents

If you have wired up an agent to execute code at all, your first version probably used Docker. Spin a container, mount a directory, run the command, kill the container. It is the obvious move. It will keep working until the agent loop wants something Docker does not offer cheaply.

This post walks through the same workflow on both systems — a simple agent loop that wants to attempt a refactor in a Rust project, run cargo test, and roll back if the test fails — and shows what changes when you swap the container baseline for agentvfs’s workspace runtime. The point is not that Docker is bad. The point is to be specific about which primitives an agent loop actually uses, and where each system gives them to you.

The workflow

The agent has a Rust project. It wants to:

  1. Snapshot the project before doing anything risky.
  2. Attempt a refactor.
  3. Run cargo test.
  4. If the tests pass, keep the change. If they fail, roll back to the snapshot.
  5. Report what files changed.

Five steps. The interesting question is how many lines of harness code each step takes on each system, and what invariants you get for free.

On bare Docker

The container surface is process isolation, not workspace state, so the agent harness has to provide the workspace primitives itself.

Step 1 — snapshot. Docker has docker commit, which turns a container into a new image. It is heavy: it copies layers, registers a tag, and the resulting image is awkward to “roll back” to. The agent-shaped alternative is to copy the bind-mounted directory to a sibling location and remember the path. Workable, but not a primitive.

Step 2 — attempt the refactor. The agent writes files into the bind mount. Docker is uninvolved at this step except as the place where the container has a view of the mount.

Step 3 — run cargo test. Standard docker run. The exit code comes back. stdout and stderr stream out. So far so good.

Step 4 — roll back on failure. Restore the directory you copied in step 1. You wrote that logic. If the failed test left a target/ directory partially written, you have to decide whether the snapshot covered it. If the agent had multiple in-flight attempts, you are now juggling sibling directories with names you generated.

Step 5 — report what changed. docker diff reports container filesystem changes at a low level. You probably want a workspace-relative list. You write the diffing.

Plus the cross-cutting work:

  • A policy step before running, which Docker does not give you. You wire seccomp, or you write a shell that pre-screens the command.
  • A timeout with a clean kill. Docker has --stop-timeout, but pipe draining and SIGTERM-then-SIGKILL semantics are yours.
  • Quotas. Docker has resource limits via cgroups; the workspace-size quota you want for the agent (max files, max single-file size) is application-level.
  • A structured execution result. Docker gives you exit code and streams; the JSON envelope your harness consumes you write.

None of this is hard. All of it is glue code. The container did the part it is good at — isolating the process — and the rest of the workflow turned into harness logic.

On agentvfs

Same five steps. The primitives are the API.

Step 1 — snapshot. avfs checkpoint save before-refactor. The vault knows how to save a rollback point. The checkpoint is named, listable, restorable, deletable.

Step 2 — attempt the refactor. The agent writes files via avfs write, or it runs an editor through avfs proxy exec. The vault holds the new state.

Step 3 — run cargo test. avfs proxy exec -- cargo test. Policy classifies the command; if classified as risky, an auto-checkpoint runs first. The vault mounts as a real directory via FUSE so cargo sees a normal path. The command runs with the configured timeout (default 300 seconds), SIGTERM-then-SIGKILL on overrun, with dedicated pipe-drain threads collecting output.

Step 4 — roll back on failure. avfs checkpoint restore before-refactor. The vault snaps back. Concurrent writes during the rollback are blocked by the transaction model; the SQLite backend uses BEGIN IMMEDIATE for mutations.

Step 5 — report what changed. The ExecutionEnvelope returned by proxy exec includes a ChangeSummary. The agent reads the field; it does not parse docker diff.

The cross-cutting concerns are also primitives. Policy decisions (allow, allow_with_checkpoint, deny, require_approval) come from PolicyEngine. Quotas are vault-level (max_size_mb, max_files, max_file_size_mb) with typed QuotaExceeded errors. The audit log is queryable from the CLI. The execution timeout has the right escalation semantics. JSON output is the default mode, not an afterthought.

Where Docker still wins

A fair comparison has to name what Docker still does better.

Process isolation. A Docker container is a real process boundary, hardened over a decade against the kinds of containment escapes that matter when the binary inside might be hostile. agentvfs is explicit in its architecture docs: the proxy is a top-level command boundary, not a syscall monitor. If your threat is “the agent installed a malicious crate and ran it”, Docker (or a microVM) is the right defense and agentvfs is not.

Network isolation. Containers control network access. agentvfs does not have a network boundary.

Image distribution. Docker images are the standard distribution format for “an environment plus its dependencies”. agentvfs vaults are workspace state, not environments.

Multi-tenant code execution as a service. If you are running untrusted code from many users, you need the kernel-level boundary that containers (or microVMs) provide. agentvfs’s threat model is honest-but-fallible agents, not adversaries.

The good answer here is composition: run agentvfs inside a container. Use Docker for the lower-level isolation; use agentvfs for the agent-aware concerns. They stack cleanly because they aim at different problems.

A scorecard

Mapping the workflow primitives back to each system:

PrimitiveDocker (raw)agentvfs
Workspace as a unitBind-mounted directory you manageSingle-file vault, listable and portable
Snapshotdocker commit (heavy) or copycheckpoint save (light, named)
Per-task forkNew container; image gymnasticsvault fork in milliseconds
RollbackRestore your copycheckpoint restore
Policy on commandseccomp / externalPolicyEngine in-process
Auto-checkpointDIYPolicy decision integrates with CheckpointService
Execution timeout--stop-timeout + your drainingSIGTERM→SIGKILL with pipe-drain threads built-in
Changed-files reportdocker diff (low level)ChangeSummary in ExecutionEnvelope
Structured outputYou parse streams--json on every command
Quotascgroups (resource) + app logicVault max_size_mb / max_files / max_file_size_mb
Kernel-level isolationYes (namespaces + cgroups)No (compose with a container if needed)

What changes about the harness

The harness for the Docker version is mostly glue: copy directory, run container, parse output, copy back. The harness for the agentvfs version is mostly calls: checkpoint save, proxy exec, read fields, decide.

That is the experiential difference. On Docker the agent loop has to implement workspace semantics on top of process semantics. On agentvfs the workspace semantics are the API.

For an agent loop that runs many attempts, checkpoints often, and needs structured output to decide what to do next, that gap compounds. Each attempt is shorter to write. Each failure mode is named, not parsed. Each rollback is one call, not three.

Picking your layer

The honest recommendation is the boring one: pick the layer that matches your threat. If the threat is “the agent might propose a stupid command”, the proxy boundary catches it cheaply and reports cleanly, and the workspace primitives keep the loop tractable. If the threat is “the binary inside is hostile”, you need kernel-level isolation, and you compose agentvfs inside the container that provides it.

Most production agent setups want both. The container is the floor; the workspace runtime is the ceiling the agent talks to.