# AGENTS.md — pi-devbox Self-contained Docker image for the **pi coding-agent**. Decoupled from opencode-devbox at v1.0.0 (2026-06-09); previously pi-devbox was a thin re-brand of opencode-devbox's `pi-only` variant. ## Repository layout - `Dockerfile.base` — multi-arch base layer with system packages, GitHub-binary tools (fzf, eza, zoxide, neovim, bat, gosu, gitleaks, git-lfs, uv, gitea-mcp, tealdeer), AWS CLI v2, mempalace + toolkit, Node.js, Python toolchain, locales, ssh ControlMaster defaults, and `/etc/tmux.conf` with 0-indexed sessions. - `Dockerfile.variant` — `FROM base-`, adds pi + companions (`pi-toolkit`, `pi-extensions`, `pi-fork`, `pi-observational-memory`). - `entrypoint.sh` — UID/GID alignment as root, then drops to `developer`. - `entrypoint-user.sh` — per-container start: SSH ControlMaster socket dir, LAN-access setup, MemPalace init, pi-toolkit + pi-extensions deploy, mempalace-bridge symlink, fork/recall pi-install, skillset deploy. - `rootfs/` — files baked into the image (bash aliases, inputrc, setup-lan-access.sh). - `scripts/smoke-test.sh` — sanity checks run by CI before pushing to Hub. - `.gitea/workflows/docker-publish.yml` — two-phase CI (base-decide → build-base → smoke → build-variant → promote-base-latest → update-description). ## Versioning scheme - Tags follow semver. **v1.0.0** is the first decoupled release; future minor bumps add variants (`-studio`, `-studio-tex`); patch bumps follow pi npm version updates and small fixes. - Docker Hub tags: `joakimp/pi-devbox:vX.Y.Z` + `joakimp/pi-devbox:latest`. Internal tags: `joakimp/pi-devbox:base-` (content-addressed) + `joakimp/pi-devbox:base-latest` (alias of most recent base). ## Release-day checklist 1. Confirm `pi --version` resolves from npm to the expected version (`curl -sf 'https://registry.npmjs.org/@earendil-works%2Fpi-coding-agent/latest' | jq -r .version`). 2. Update `CHANGELOG.md` Unreleased → vX.Y.Z section. 3. Verify `docker compose up` works locally with the current `latest` image if you're upgrading users from a previous version. 4. Push tag: `git tag vX.Y.Z && git push origin vX.Y.Z`. 5. Watch CI: smoke job builds amd64 only and asserts size + extensions + pi version + new-base-tooling presence. Variant build is multi-arch (amd64 + arm64) only after smoke passes. 6. Verify the Hub tags appear (latest + vX.Y.Z, plus base-latest if the base was rebuilt this run). 7. **Revoke any short-lived Gitea PAT** used during the release at `gitea.jordbo.se/user/settings/applications`. ## Cache-hit footgun (must-know) `PI_VERSION` defaults to `latest` in `Dockerfile.variant` but **CI must resolve it to a concrete version string** before passing as a build-arg. Otherwise the build-arg string is byte-identical across releases → identical layer hash → registry buildcache silently reuses the old layer. `resolve-versions` job in the workflow handles this. Discovered in pi-devbox 2026-05-23 (every release v0.74.0..v0.75.5 shipped the same image bytes); preventatively fixed for `PI_VERSION` + `PI_FORK_REF` + `PI_OBSMEM_REF`. ## Smoke-test gate `scripts/smoke-test.sh` runs amd64-only against a freshly-built variant image. Verifies binaries, repo clones, runtime deployment (waits for keybindings + mempalace bridge + ≥4 extensions before sampling — fixes the parallel-build-load race documented in opencode-devbox c6f9d11 2026-06-08), and image size threshold (3500 MB; revisit after a few releases as actuals settle). If smoke fails on size threshold but build is otherwise fine: bump `SIZE_THRESHOLD_MB` in scripts/smoke-test.sh in a follow-up commit and re-run. The threshold exists to catch *runaway* growth (an accidental texlive bake-in, a forgotten chrome dependency), not to block ordinary upstream bumps. ## Build pipeline notes - **Two-phase**: base + variant. Base is rebuilt only when `Dockerfile.base`, `rootfs/`, or `entrypoint*.sh` change (CI computes a content hash and probes Hub for an existing `base-` tag). - **`base-latest` alias** is promoted from `base-` via `crane copy` (manifest copy, no rebuild) only when the base actually changed. - **`docker buildx build --push` retry**: 3 attempts with backoff for transient Hub blips. Deterministic failures fail all 3 and the job fails as expected. - **Registry buildcache disabled**: buildkit's cache-export hits HTTP 400 on Hub CDN since ~2026-05-23. Image push works fine; we pay the full base build on Dockerfile.base change, but base tags are content- addressed so unchanged bases short-circuit at the probe step. ## Decoupling history (briefly) Pre-v1.0.0 pi-devbox was `FROM joakimp/pi-devbox:base-pi-only`, where `base-pi-only` was a tag built by **opencode-devbox CI** (with `INSTALL_OPENCODE=false` in their variant Dockerfile) and pushed under the pi-devbox repo as an internal building-block tag. This setup required rebuilding opencode-devbox before pi-devbox could be tagged and meant pi-devbox docs needed cross-referencing into opencode-devbox. v1.0.0 brings pi install logic into this repo, drops the cross-repo dependency, and the `base-pi-only*` tags from opencode-devbox become deprecated artifacts (to be removed in opencode-devbox v2.0.0). ## What we DON'T install (and why) - **No texlive** (~600 MB–1 GB). Users who need PDF export from pandoc can install on demand: `sudo apt-get install texlive-xetex texlive-latex-recommended`. The planned `:latest-studio-tex` variant will bake this in. - **No pi-studio** (yet). Coming in v1.1.0 as the `:latest-studio` variant. v1.0.0 is intentionally scope-limited to "decouple, don't reshape." - **No Julia/R/GHCi/Clojure runtimes**. Use `uv run --with X` for Python REPLs; `apt install` other-language runtimes ad-hoc per container if needed. ## Backward compatibility - The host `~/.mempalace` bind-mount path is unchanged. - Volume names (`devbox-pi-config`, `devbox-bash-history`, `devbox-nvim-data`, `devbox-uv-tools`, `devbox-chroma-cache`) are unchanged. - `~/.pi/agent/` layout inside the container is unchanged; existing named volumes work without recreation. - The `:latest` and `vX.Y.Z` Hub tags continue to point at a "base + pi" image. Same tag, same shape, just built differently.