Skip to main content

ADR-0003: Pluggable Local Container Runtime Abstraction

Context and Problem Statement

When a human submits feedback, Stet dispatches an AI agent to do the work. That agent must run somewhere isolated: it executes arbitrary tool calls (shell, file writes, test runs) against a checkout of the user's repository. For privacy, cost, and the product's "local" thesis, this must happen on the user's Mac, in a sandbox, never in our cloud.

Two credible local runtimes exist on macOS 26 + Apple silicon: Apple's container / Containerization framework (per-container lightweight VMs, sub-second starts, license-free, in-process Swift API) and Docker (ubiquitous, what most open harnesses target out of the box). We must decide how to depend on them.

Decision Drivers

  • Isolation strength. Each dispatched agent should get a fresh, disposable environment with its own filesystem and network boundary.
  • Native-first, no forced third-party install. Users shouldn't be required to install Docker Desktop to use Stet.
  • Compatibility with existing harnesses. Many harnesses (ADR-0004) assume a Docker-compatible runtime; we shouldn't strand them.
  • Future flexibility. The runtime is an implementation detail that will change; the rest of the app should not couple to it.
  • Security. Credentials and repo contents inside the sandbox must be scoped and revocable.

Considered Options

  • Option A: Apple container native default, Docker fallback.
  • Option B: Pluggable runtime abstraction — Apple container and Docker both first-class behind one protocol, no privileged default.
  • Option C: Docker-first, add Apple container later.

Decision Outcome

Chosen option: Option B — a pluggable ContainerRuntime protocol with Apple container/Containerization and Docker as equal, first-class backends. The application depends only on the protocol (create sandbox, mount worktree, run process, stream I/O, tear down); backends are selected by availability and user preference. Apple's Containerization Swift package is embedded in-process where available (ADR-0001); Docker is driven via its API/CLI.

A pluggable abstraction from day one is worth the extra upfront design because the runtime is the single most volatile external dependency, and neither backend should be privileged in the codebase.

Consequences

  • Good, because the app never couples to a specific container implementation.
  • Good, because users with neither preference get native, license-free execution; users invested in Docker keep their setup.
  • Good, because per-dispatch sandboxes (fresh VM/container + isolated worktree + scoped credential) give strong, disposable isolation.
  • Bad, because two backends must be built and tested from the start (more upfront work than a single default).
  • Bad, because Apple Containerization is a 1.0 framework; edge cases (networking, entitlements) will need workarounds.
  • Neutral, because a capability-detection layer is required to pick a backend and degrade gracefully when neither is present.

Confirmation

  • A ContainerRuntime protocol exists; no app code outside its implementations references Apple container or Docker types directly.
  • Both backends can: create a sandbox, mount a git worktree, run a process, stream stdout/stderr, and tear down.
  • Each dispatch runs in a fresh sandbox with a branch-scoped credential that is revoked on teardown.
  • With no runtime available, the app surfaces a clear, actionable error rather than failing opaquely.

Pros and Cons of the Options

Option A: Apple native-first, Docker fallback

  • Good, simplest "it just works" default.
  • Bad, implies a privileged backend in code; Docker path risks being a second-class afterthought.

Option B: Pluggable, both first-class

  • Good, no coupling, no privileged backend, cleanest long-term.
  • Bad, most upfront work.

Option C: Docker-first

  • Good, fastest path to a working agent against existing harnesses.
  • Bad, forces Docker Desktop dependency; least differentiated; contradicts the native thesis (ADR-0001).

More Information

Apple Containerization: https://github.com/apple/containerization (Swift package; modules include Containerization, ContainerizationOCI, ContainerizationEXT4). See ADR-0004 (harness adapter that runs inside these sandboxes) and SPEC-0003.