Replace the Apple `container` CLI wrapper with Apple's `containerization`
Swift framework, driven in-process — no external CLI or daemon.
- ContainerEngine: in-process runtime (shared VZVirtualMachineManager from a
bundled kernel + runtime-pulled vminitd initfs, ImageStore, VmnetNetwork,
live-container registry, typed statistics for CPU/mem + OOM diagnosis).
Daemonless ⇒ ephemeral VMs; reconcile is on-disk GC.
- ContainerizedProcessHandle: bridges a guest LinuxProcess onto the existing
ProcessHandle contract (reusing LineSplitter), so backends stream NDJSON
identically in-container and on-host. Closes the stdio writers after wait()
to finish the line streams (the framework never calls Writer.close()).
- Sandbox image is built in CI (containers/nucleic-sandbox/Dockerfile +
.github/workflows/sandbox-image.yml) and pushed to GHCR; the app pulls +
unpacks it on first use (no on-device build, no user-installed tools). The
GHCR package may stay private — pulls authenticate with the user's GitHub
token via ContainerEngine.registryAuth (Settings → Sandbox, or
NUCLEIC_REGISTRY_USER/NUCLEIC_REGISTRY_TOKEN). vminitd is pulled from Apple's
public GHCR; only the kernel is bundled (scripts/fetch-kernel.sh, curl-only).
- ContainerManager rewired to the engine (policy preserved); ClaudeCodeBackend
execs in-container via the engine; Settings/ProviderAvailability use a static
capability check. Platform floor raised to macOS 26 (Apple silicon) + the
com.apple.security.virtualization entitlement (swift-tools 6.2).
- Verified end-to-end on macOS 27 / Apple silicon via Sources/container-spike:
pull vminitd + image, boot VM, exec, stream stdout. Builds clean; 21 tests pass.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Add a "Ship" toggle beside "Auto". When on, a session squash-merges its
branch into the project's default branch the moment the agent's turn is
classified `.completed` — no human babysitting the merge.
Safety / race handling:
- New `MergeQueue` actor serializes merges FIFO, strictly one-at-a-time per
(project, target) branch, with per-session dedupe so a re-fired `.completed`
can't double-ship. Different repos/branches still run in parallel.
- Each merge runs through the existing `integrate` path, which checks out the
live target tip and merges (never force) — a stale branch is merged into the
post-other-merges branch, never overwriting newer work.
- On conflict the merge aborts (branch intact, target tree clean), Ship turns
off for that session, and an error banner surfaces. No blind retries.
- Fix: squash-merge conflicts now reset --hard HEAD instead of `merge --abort`
(which is a no-op for --squash, leaving the main checkout dirty).
Ship implies Auto (an agent can't ship if every tool call blocks on a human).
Wiring: `Session.autoShip` (+ v9 GRDB migration), `SessionController.setAutoShip`,
`AppStore` owns the queue, injects the integrator, observes status into
`shipStatuses`, and triggers from `classifyDisposition`. Ship toggles added to
SessionDetailView (with a header status pill) and HomeView (new-chat default).
Tests: MergeQueue serialization/dedupe/status + git-backed end-to-end through
the queue (parallel ships land in order; conflicting second stops without
overwriting the first). Docs: RUNTIME_ARCHITECTURE §3.1.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>