Skip to content

Proposal: make --clone a neutral acq option — emulate disposable-primary semantics on msb via a managed host-side scratch clone #403

Description

@basiliskus

Problem

--clone is the reason teams with disposable-primary workflows can't leave sbx. On sbx, the primary workspace is an isolated in-container clone: the agent branches/commits/pushes without touching the host checkout, build/database state stays in the sandbox, and finished work comes back through an explicit git fetch sandbox-<name>. On msb, workspaces are direct host mounts (rw or :ro) — the agent edits the real checkout, which our ADR pinning sbx calls the load-bearing gap (the rest of our kit now runs on msb unchanged; we verified files, volumes:, and the toolchain end-to-end this week).

This proposes --clone as neutral acq vocabulary: passthrough on sbx (today's behavior), emulated by the msb adapter. Same trajectory as --image/ADR-0022 and volumes:/ADR-0023 — take a capability one backend has natively and make it a neutral knob the other backend implements its own way.

Prior art: disposable working copies are the industry default

  • Cloud agents all do clone-per-task into a fresh VM with an explicit git crossing back: OpenAI Codex cloud (repo cloned at a branch/commit into an isolated microVM), Cursor cloud agents (per-agent Firecracker VMs, repo cloned, snapshot-cached), Google Jules (clone into a GCP VM, PR back).
  • Sandbox platforms (E2B/Firecracker, Modal/gVisor, Daytona) bring code in by in-sandbox clone or upload — none mount a host checkout rw.
  • OpenHands offers a copy-on-write :overlay mount mode — clone semantics at the filesystem layer (agent edits a CoW layer; originals untouched).
  • Git worktrees (container-use, Agent Canvas) are the parallelism pattern but not an isolation boundary: the worktree's .git is a pointer into the main repo's .git, so a sandboxed agent would need rw access to the real object store, refs, hooks, and config — hooks/config being host-executed code paths.

sbx's distinctive contribution is the source: cloning from the local host checkout (uncommitted branches included, no forge round-trip) with a path-remote for fetch-back. That's the local-first variant of what every cloud agent does — worth making neutral rather than leaving as an sbx exclusive.

Proposed design (msb emulation)

At create, when --clone is passed on msb:

  1. Clone the primary into a managed scratch dir, e.g. $XDG_STATE_HOME/acq/clones/<sandbox>/<repo>, and mount the scratch dir rw as the primary instead of the real path. Secondaries are unchanged (:ro as before).
  2. git clone --no-hardlinks (or via file://) — this is load-bearing: a same-filesystem clone hardlinks object files by default, so an agent with write access to the scratch .git could modify inodes shared with the real repo's object store. The copy must be physical.
  3. Register a sandbox-<sandbox> remote in the host checkout pointing at the scratch dir, mirroring sbx's recovery UX exactly: git fetch sandbox-<name> pulls agent branches back as hash-verified objects (hooks/config never transfer over fetch — the crossing is inert data).
  4. acq rm deletes the scratch dir and the remote, warning about unfetched commits first (the same warning sbx prints today).

Deliberate divergence from sbx, proposed as the better default: a git clone carries no gitignored/untracked state. sbx's --clone copies gitignored files, which is how host-side build state (e.g. a macOS-initialized Postgres cluster in .devenv/state) poisons sandboxes — a documented trap in our kit's TROUBLESHOOTING. Workflows that need a specific ignored file (.env) already copy it in explicitly with acq cp; the doc story stays the same on both backends.

Tradeoff to state openly: the scratch clone lives on host disk (unlike sbx's in-guest clone), so agent writes land on the host — but confined to the managed dir, which is disposable by construction and never executed by the host's git.

Alternatives considered

  • In-guest clone from an :ro mount: closer to sbx's placement (working copy dies with the sandbox), but recovery is worse — the host can't fetch from guest storage, so commits leave via push (currently also blocked by the kit-hosts :22 egress gap, msb: kit caps.network.allow hosts get no port-22 egress — SSH push/clone to an allowed host times out despite the forwarded agent #402) or bundles copied out. Could be a later refinement; the host-side scratch dir gets the semantics right with the simplest lifecycle.
  • CoW overlay mount (OpenHands-style): perfect state fidelity including ignored files, but merge-back is diff-shaped rather than git-native, and ignored-state fidelity is exactly the contamination the git clone avoids.
  • Clone-from-forge in-guest (the Codex/Jules pattern): works today over HTTPS, but loses uncommitted local work and adds a forge round-trip — not a substitute for local-first workflows.

Why acq-side

msb gaining a native clone feature would also close this, but the acq emulation is backend-neutral vocabulary (consistent with ADR-0022/0023), lands without an upstream msb dependency, and gives both backends identical UX (--clone, sandbox-<name> remote, rm warning). If msb later grows a native mechanism, the adapter can switch to it behind the same flag.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions