# 001: Cellblock Phase 1 — Containerized Agents via workmux ## Goal `workmux add feature-x` opens a tmux window where claude-code runs inside a Docker container with the worktree mounted. SSH works. Nix flakes work. Config controls what each project can access. ## What We Build 1. **Docker image** — Alpine + git + ssh + tools + fuse-overlayfs (~73MB) 2. **`cellblock` CLI** — bash prototype (Go rewrite planned), config → `docker run` 3. **Config** — `~/.config/cellblock/config.yaml`, per-project permissions 4. **Nix provider shim** — pluggable nix base layer (linux-volume now, host-bind future) 5. **Per-project COW overlays** — overlayfs for /nix isolation 6. **workmux integration** — `agent: cellblock run claude-code` in `.workmux.yaml` ## Core Design: Copy-on-Write Isolation Everything uses the same pattern: **read-only shared base + per-project COW upper**. ### /nix — two-layer overlay ``` /nix (overlayfs merge inside container) ├── upper: cellblock-nix- per-project COW (Docker volume, tiny deltas) └── lower: shared nix store (read-only) ``` **Providers** (shim abstraction for future Apple Containers): | Provider | Lower layer | When | |---|---|---| | `linux-volume` | `cellblock-nix-linux` Docker volume | Now: Docker on macOS (host /nix has Mach-O) | | `host-bind` | Host `/nix` bind-mount | Future: Apple Containers or Linux hosts | - `linux-volume`: one-time `cellblock nix init` runs nix inside a Linux container to install agents into a shared Docker volume. All projects share it read-only. - `host-bind`: direct bind-mount, zero copy. Works when container arch matches host. - The overlay upper is identical either way — only the base source changes. ### ~/.claude — copy-on-start - Host `~/.claude` mounted read-only at `/opt/claude-config` - Entrypoint copies to `/home/cellblock/.claude` (writable, per-container) - Isolated, ephemeral — changes die with container ### Networking — bridge (default) - Default Docker bridge — containers isolated from each other - Host services via `host.docker.internal` - Per-project `network: host` option ## Files ``` cellblock/ plans/ # phase plans (001, 002, 003) flake.nix # nix pkg + dev shell bin/cellblock # CLI (bash prototype) lib/common.sh # logging, path utils lib/config.sh # YAML config reader (yq) lib/resolve.sh # project/branch detection lib/docker.sh # docker run arg assembly lib/nix.sh # nix provider shim + overlay management image/Dockerfile # Alpine + git + ssh + tools image/entrypoint.sh # UID mapping, overlayfs, config copy config/example.yaml # example config tests/ # bats tests ``` ## CLI ``` cellblock run [-- args...] # run agent in container for cwd project cellblock shell [project] # interactive shell in container cellblock stop [project] # stop container cellblock status # list running containers cellblock nix init [packages...] # init shared Linux nix store (one-time) cellblock nix status # list nix volumes cellblock nix reset-project [proj] # reset per-project nix overlay cellblock image build # build the docker image ``` ## Config `~/.config/cellblock/config.yaml`: ```yaml defaults: image: cellblock:latest ssh_agent: true nix_provider: linux-volume # "linux-volume" or "host-bind" network: bridge # "bridge" or "host" claude_config: ~/.claude projects: treehouse: path: ~/work/treehouse env: [MIX_ENV=dev] petal_pro: path: ~/work/petal_pro env: [MIX_ENV=dev, DATABASE_URL] mounts: - ~/work/shared-libs:/shared-libs:ro ``` ## What `cellblock run claude-code` Executes For cwd `/Users/ijcd/work/treehouse`, branch `main`, provider `linux-volume`: ```bash docker run --rm -it \ --name cellblock-treehouse-main-claude-code \ --label cellblock=true \ --label cellblock.project=treehouse \ --cap-add SYS_ADMIN \ -v /Users/ijcd/work/treehouse:/workspace \ -v cellblock-nix-linux:/nix-base:ro \ -v cellblock-nix-treehouse:/nix-overlay \ -v /Users/ijcd/.claude:/opt/claude-config:ro \ -v /run/host-services/ssh-auth.sock:/run/host-services/ssh-auth.sock \ -e SSH_AUTH_SOCK=/run/host-services/ssh-auth.sock \ -e USER_UID=$(id -u) -e USER_GID=$(id -g) \ -e MIX_ENV=dev \ cellblock:latest \ /nix/var/nix/profiles/default/bin/claude-code ``` ## Build Order 1. ✅ image/Dockerfile + entrypoint.sh 2. ✅ lib/common.sh + lib/config.sh 3. ✅ lib/resolve.sh 4. ✅ lib/docker.sh 5. ✅ bin/cellblock — CLI wiring 6. ✅ lib/nix.sh — provider shim + overlay management 7. ✅ flake.nix 8. 🔄 Integration testing (shell, overlay verified; nix init next) 9. ⬜ workmux integration — end-to-end test ## Verification 1. ✅ `cellblock image build` succeeds 2. ✅ `cellblock shell` → `git --version` works inside container 3. ✅ Overlay mount confirmed: `overlay on /nix type overlay (rw,...)` 4. ✅ `~/.claude` copy-on-start works, isolated per container 5. ✅ SSH agent socket mounted and accessible 6. ⬜ `cellblock nix init` populates Linux nix store 7. ⬜ `cellblock run claude-code` starts agent in container 8. ⬜ Inside: `ssh -T git@github.com` succeeds 9. ⬜ Inside: `nix develop` works on a flake project 10. ⬜ `workmux add test-feature` → tmux window with containerized agent 11. ✅ 36 bats tests passing ## Not in Phase 1 - Pluggable isolators / Apple Containers / remote (Docker only, but shim ready; see plan 004) - Remote isolators: Fly.io, E2B, cloud-vm (see plan 004) - Treehouse network identity - Multi-project `cellblock up` - Orchestrator tier - Proxy tracking - Go rewrite (planned after API stabilizes) - Watchdog interceptor (see plan 002) - LiveView workmux UI (see plan 003) ## Resolved 1. **~/.claude isolation** — copy-on-start. Mount ro, copy in entrypoint. Ephemeral. 2. **Networking** — bridge default. `host.docker.internal` for host access. 3. **Nix architecture mismatch** — host /nix has macOS binaries, containers are Linux. Solved with provider shim: `linux-volume` (shared Linux nix Docker volume) for now, `host-bind` (direct mount) when Apple Containers make arch match. 4. **Nix isolation** — two-layer overlayfs. Shared base (ro) + per-project COW upper. Base protected. Per-project writes persist. `nix develop` works.