diff --git a/AGENTS.md b/AGENTS.md index 9de3ce6..09b94a4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,15 +14,23 @@ re-brand of opencode-devbox's `pi-only` variant. - `Dockerfile.variant` — `FROM base-`, adds pi + companions (`pi-toolkit`, `pi-extensions`, `pi-fork`, `pi-observational-memory`) and, when `INSTALL_STUDIO=true`, vendors `pi-studio` to `/opt/pi-studio` - (`-studio` variant). + (`-studio` variant). Also appends the pi-devbox managed block from + `pi-global-AGENTS.append.md` onto pi-toolkit's `pi-global-AGENTS.md` (the + single global instruction slot pi loads) so containers proactively load the + baked `pi-devbox-environment` skill. Idempotent via a marker grep. - `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-studio pi-install, - optional `studio-expose` bridge (when `STUDIO_EXPOSE=1`), skillset - deploy. + optional `studio-expose` bridge (when `STUDIO_EXPOSE=1`), image-baked + skills symlink-in, skillset deploy. - `rootfs/` — files baked into the image (bash aliases, inputrc, - setup-lan-access.sh, `studio-expose` helper). + setup-lan-access.sh, `studio-expose` helper). Also + `usr/local/share/pi-devbox/skills//SKILL.md` — image-baked agent + skills (e.g. `pi-devbox-environment`) symlinked into `~/.agents/skills/` by + the entrypoint, available with or without a mounted skillset — plus + `usr/local/share/pi-devbox/pi-global-AGENTS.append.md` (the global-AGENTS + pointer concatenated in `Dockerfile.variant`). - `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 → @@ -33,7 +41,8 @@ re-brand of opencode-devbox's `pi-only` variant. ## Versioning scheme - Tags follow semver. **v1.0.0** is the first decoupled release; future - minor bumps add variants (`-studio`, `-studio-tex`); patch bumps follow + minor bumps add variants (`-studio`, `-studio-tex`) or significant base + additions (e.g. v1.2.0 image-baked agent skills); patch bumps follow pi npm version updates and small fixes. - Docker Hub tags: `joakimp/pi-devbox:vX.Y.Z` + `joakimp/pi-devbox:latest` + (since v1.1.0) `joakimp/pi-devbox:vX.Y.Z-studio` + diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cd3c59..79b9c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,52 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`). --- +## v1.2.0 — 2026-06-22 + +Minor release: **image-baked agent skills** — a new base mechanism that ships +skills inside the image (independent of any mounted skillset repo) — plus the +first such skill, `pi-devbox-environment`, and pi `0.79.9` → `0.79.10` +(auto-resolved from npm `latest` at build). + +### Added + +- **Image-baked agent skills.** Skills under + `/usr/local/share/pi-devbox/skills//` are now symlinked into + `~/.agents/skills/` by `entrypoint-user.sh` on every start, making them + available **with or without** a mounted `skillset` repo. The symlink points + at the image path (so it survives volume recreate, unlike anything baked + under a home dir a named volume would shadow) and is created only when + absent, so a same-named skillset skill or user override is never clobbered. + The skillset deploy classifies these as foreign-links and its `--prune-stale` + pass leaves them untouched. +- **`pi-devbox-environment` skill** (the first image-baked skill). Teaches + agents the container-shaped facts that are easy to get wrong: the + persistence/ephemerality tier model (what survives `down -v` / image + update), host + LAN SSH reachability and ControlMaster, split-horizon DNS + *mechanisms*, the interactive-vs-tool-shell alias gotcha (`dssh`/`dscp`/ + `cat`→`bat` don't exist in the non-interactive bash tool), the tmux 0-index + constraint, uv-first Python, and pi-studio reachability. Deliberately + environment-agnostic — host OS, hostnames, internal domains, and nameservers + are discovered at runtime, never hardcoded. +- **Proactive skill awareness via the global `AGENTS.md`.** `Dockerfile.variant` + appends a short, gated pointer (`pi-global-AGENTS.append.md`) onto + pi-toolkit's `pi-global-AGENTS.md` — the single global instruction slot pi + loads at startup — so containers load the `pi-devbox-environment` skill + proactively rather than only on description match. The pointer fires only + inside a pi-devbox container (checks for `/usr/local/lib/pi-devbox/`). + Build-time append is idempotent via a marker grep; runtime is unaffected + (the file is root-owned and re-symlinked by pi-toolkit each boot). +- **Smoke-test coverage** for the new mechanism: build-time presence of the + baked skill + append snippet + the merged marker in `pi-global-AGENTS.md`, + and a runtime assertion that `~/.agents/skills/pi-devbox-environment` is + linked after the entrypoint runs. + +### Bumped: pi 0.79.9 → 0.79.10 + +Resolved from npm `latest` at build (v1.1.7 shipped `0.79.9`). See the +[pi changelog](https://github.com/earendil-works/pi/blob/main/CHANGELOG.md) +for the upstream `0.79.10` notes. + ## v1.1.7 — 2026-06-21 Patch release: pi `0.79.8` → `0.79.9` (auto-resolved at build), plus the @@ -111,8 +157,6 @@ Notable upstream changes (from [pi releases](https://github.com/earendil-works/p reproducibility. Resolution now validates each result is a 40-hex commit SHA (and pi a real semver) and aborts the release otherwise. ---- - ## v1.1.5 — 2026-06-18 Patch release: SSH ControlMaster read-only-socket fix + pi `0.79.6` → `0.79.7` @@ -507,7 +551,7 @@ dependencies. ### Future work - v1.1.0: `:latest-studio` variant (adds [pi-studio](https://github.com/omaclaren/pi-studio)). -- v1.2.0: `:latest-studio-tex` variant (adds texlive-xetex for PDF export). +- v1.3.0: `:latest-studio-tex` variant (adds texlive-xetex for PDF export). ## v0.79.0 — 2026-06-08 diff --git a/DOCKER_HUB.md b/DOCKER_HUB.md index 0e18fd0..495ed12 100644 --- a/DOCKER_HUB.md +++ b/DOCKER_HUB.md @@ -51,6 +51,7 @@ Full setup guide — authentication for each provider (Anthropic, OpenAI, Gemini - **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 user-facing extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive` - **`fork`** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall`** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) tools - **mempalace bridge** — MCP extension auto-symlinked so pi reads/writes the host-mounted palace +- **image-baked agent skills** — skills under `/usr/local/share/pi-devbox/skills/` (e.g. `pi-devbox-environment`, which teaches agents the container's persistence/networking/DNS/tmux/REPL specifics) are symlinked into `~/.agents/skills/` on start, available with or without a mounted skillset repo The entrypoint deploys/registers all of these on first container start. Re-running is idempotent and preserves user edits. diff --git a/Dockerfile.base b/Dockerfile.base index eb94a27..52bb4d5 100644 --- a/Dockerfile.base +++ b/Dockerfile.base @@ -481,6 +481,11 @@ COPY rootfs/home/developer/.inputrc /etc/skel-devbox/.inputrc # ── Entrypoint ──────────────────────────────────────────────────────── COPY rootfs/usr/local/lib/pi-devbox/ /usr/local/lib/pi-devbox/ +# Image-baked skills + the global-AGENTS append snippet. Under /usr/local so a +# named volume over a home dir can't shadow them; linked into ~/.agents/skills +# by entrypoint-user.sh, and the snippet is concatenated onto the global +# AGENTS.md in Dockerfile.variant (after pi-toolkit, which owns that file). +COPY rootfs/usr/local/share/pi-devbox/ /usr/local/share/pi-devbox/ COPY rootfs/usr/local/bin/studio-expose /usr/local/bin/studio-expose COPY rootfs/usr/local/bin/dot-watch /usr/local/bin/dot-watch COPY entrypoint.sh /usr/local/bin/entrypoint.sh diff --git a/Dockerfile.variant b/Dockerfile.variant index 3d3db31..e3fa88a 100644 --- a/Dockerfile.variant +++ b/Dockerfile.variant @@ -94,6 +94,24 @@ RUN set -e && \ echo "pi-fork at $(cd /opt/pi-fork && git rev-parse --short HEAD)" && \ echo "pi-observational-memory at $(cd /opt/pi-observational-memory && git rev-parse --short HEAD)" +# ── pi-devbox awareness: append our pointer to the global AGENTS.md ── +# pi loads a SINGLE global instruction file (~/.pi/agent/AGENTS.md), which +# pi-toolkit's install.sh re-symlinks to /opt/pi-toolkit/pi-global-AGENTS.md on +# every container start. There is no second global slot, and that file is +# root-owned (not writable by the runtime user), so we compose at BUILD time: +# append the pi-devbox managed block to pi-toolkit's file here, after the clone. +# Idempotent via a marker grep so a rebuilt layer never double-appends. This +# makes every container proactively aware of the pi-devbox-environment skill; +# the snippet itself is gated (only fires when /usr/local/lib/pi-devbox exists). +RUN if [ -f /opt/pi-toolkit/pi-global-AGENTS.md ] && \ + ! grep -q 'pi-devbox:managed-block' /opt/pi-toolkit/pi-global-AGENTS.md; then \ + printf '\n' >> /opt/pi-toolkit/pi-global-AGENTS.md && \ + cat /usr/local/share/pi-devbox/pi-global-AGENTS.append.md >> /opt/pi-toolkit/pi-global-AGENTS.md && \ + echo "appended pi-devbox block to pi-global-AGENTS.md" ; \ + else \ + echo "pi-devbox block already present or pi-global-AGENTS.md missing (skipped)" ; \ + fi + # ── Optional: pi-studio (:latest-studio variant) ───────────────────── # pi-studio (omaclaren/pi-studio) is a pi-package + theme providing a # two-pane browser workspace: prompt/response editor, KaTeX/Mermaid live diff --git a/README.md b/README.md index 3c9db4a..8813c25 100644 --- a/README.md +++ b/README.md @@ -442,6 +442,38 @@ session/docs mining; the 29 MCP tools (search, kg-query, drawer-add, diary-write, etc.) are wired into pi automatically by the pi-extensions mempalace bridge. +## Agent skills + +pi discovers skills under `~/.agents/skills/`. Two delivery paths feed that +directory, and they compose: + +- **Image-baked skills (always present).** Skills shipped *inside* the image + live under `/usr/local/share/pi-devbox/skills/` and are symlinked into + `~/.agents/skills/` by `entrypoint-user.sh` on every start. They need no + external mount, survive volume recreate (the source is an image path, not a + home dir a named volume would shadow), and are created only when absent so a + same-named skillset skill or user override is never clobbered. The bundled + **`pi-devbox-environment`** skill is delivered this way — it teaches agents + the container's persistence model, host/LAN SSH reachability, split-DNS + mechanisms, the interactive-vs-tool-shell alias gotcha (`dssh`/`dscp`), + tmux 0-indexing, uv-first Python, and pi-studio reachability, all as + *mechanisms* (deployment-specific hostnames/domains/nameservers are + discovered at runtime, never hardcoded). +- **Skillset repo (optional).** If a `skillset` repo is mounted (at + `$HOME/skillset` or `/workspace/skillset`, or via `SKILLSET_CONTAINER_PATH`), + `deploy-skills.sh` symlinks its skills in too. Image-baked skills are + classified as foreign-links by its `--prune-stale` pass and left untouched. + +To make agents *proactively* load a baked skill at session start (rather than +only on description match), the image appends a short, gated pointer to the +global `AGENTS.md` at build time (see `pi-global-AGENTS.append.md`). The +pointer fires only inside a pi-devbox container (it checks for +`/usr/local/lib/pi-devbox/`). + +To add another image-baked skill: drop a `SKILL.md` under +`rootfs/usr/local/share/pi-devbox/skills//`; the `COPY` in +`Dockerfile.base` and the entrypoint symlink loop pick it up automatically. + ## SSH and ControlMaster The base image preconfigures `Host *` ssh defaults: diff --git a/entrypoint-user.sh b/entrypoint-user.sh index cd3622f..6a0fda0 100755 --- a/entrypoint-user.sh +++ b/entrypoint-user.sh @@ -170,6 +170,28 @@ case "${STUDIO_EXPOSE:-}" in ;; esac +# ── Image-baked skills: link into ~/.agents/skills ─────────────────── +# Skills shipped IN the image (under /usr/local/share/pi-devbox/skills/) are +# made available regardless of whether a skillset repo is mounted. We symlink +# each into ~/.agents/skills/ so pi discovers it. Pointing at the image path +# (/usr/local/...) means the skill is always fresh from the image and survives +# volume recreate (unlike anything baked under a home dir, which a named volume +# would shadow). Created only when absent, so a same-named skillset skill or a +# user override is never clobbered. The skillset deploy below classifies these +# as foreign-links (symlinks not pointing into the skillset repo) and leaves +# them alone — only dangling symlinks are pruned by --prune-stale. +DEVBOX_SKILLS_SRC=/usr/local/share/pi-devbox/skills +if [ -d "$DEVBOX_SKILLS_SRC" ]; then + mkdir -p "$HOME/.agents/skills" + for _sk in "$DEVBOX_SKILLS_SRC"/*/; do + [ -d "$_sk" ] || continue + _skname=$(basename "$_sk") + if [ ! -e "$HOME/.agents/skills/$_skname" ]; then + ln -s "${_sk%/}" "$HOME/.agents/skills/$_skname" + fi + done +fi + # ── Skillset: deploy skills/instructions from mounted skillset repo ── # When the skillset repo is mounted (at $HOME/skillset or /workspace/skillset), # run the deploy script to create relative symlinks for skills and instructions. diff --git a/rootfs/usr/local/share/pi-devbox/pi-global-AGENTS.append.md b/rootfs/usr/local/share/pi-devbox/pi-global-AGENTS.append.md new file mode 100644 index 0000000..3399a1c --- /dev/null +++ b/rootfs/usr/local/share/pi-devbox/pi-global-AGENTS.append.md @@ -0,0 +1,20 @@ + + +## Running inside pi-devbox + +If the directory `/usr/local/lib/pi-devbox/` exists (or your shell prompt is +prefixed `[devbox]`, or `~/.ssh-local/config` is present), you are in a +**pi-devbox container** — a Docker environment whose persistence, networking, +DNS, host/LAN reachability, tmux, and Python/REPL behaviour differ from a normal +workstation. Before any task that touches **reaching the host or its LAN, SSH, +DNS/name resolution, what survives container recreate, running Python/REPLs, +tmux, or pi-studio**, read `~/.agents/skills/pi-devbox-environment/SKILL.md`. + +Key reflex from that skill: **the deployment specifics are not universal** — the +host OS, hostnames, internal domains, and nameservers vary per instance and must +be discovered at runtime, never assumed. And interactive shell aliases +(`dssh`, `dscp`, `cat`→`bat`) do **not** exist in your non-interactive bash +tool, so spell out the underlying command (e.g. +`ssh -F "$HOME/.ssh-local/config" mac …`). diff --git a/rootfs/usr/local/share/pi-devbox/skills/pi-devbox-environment/SKILL.md b/rootfs/usr/local/share/pi-devbox/skills/pi-devbox-environment/SKILL.md new file mode 100644 index 0000000..0fc40f5 --- /dev/null +++ b/rootfs/usr/local/share/pi-devbox/skills/pi-devbox-environment/SKILL.md @@ -0,0 +1,207 @@ +--- +name: pi-devbox-environment +description: >- + Operate correctly inside a pi-devbox container. Load when running inside + pi-devbox (detection: the directory `/usr/local/lib/pi-devbox/` exists, the + shell prompt is prefixed `[devbox]`, or `~/.ssh-local/config` is present) and + the task touches any of: reaching the Docker host or its LAN, SSH, DNS name + resolution, what survives container recreate (persistence vs ephemerality), + running Python or other REPLs, tmux, or the pi-studio browser UI. Covers the + persistence model, the interactive-vs-tool-shell alias gotcha + (dssh/dscp/cat=bat exist only in interactive bash), host + LAN SSH + reachability and ControlMaster, split-horizon DNS mechanisms, the tmux + 0-index constraint, uv-first Python, and pi-studio reachability. This skill + teaches MECHANISMS only — concrete hostnames, usernames, internal domains, + nameservers, and even the host OS vary per deployment and MUST be discovered + at runtime, never assumed or hardcoded. +--- + +# pi-devbox environment + +You are (or may be) running inside **pi-devbox**: a Docker container that ships +pi, MemPalace, and a curated tool stack, with the host source tree mounted at +`/workspace`. This skill is about the *container-shaped* facts that change how +you should act — things that are easy to get wrong because they differ from a +normal workstation shell. + +> **Golden rule: this environment is a template, not a fixed deployment.** +> The host could be macOS, Windows, or Linux. There may or may not be LAN +> peers, a VPN, split-DNS, a skillset mount, or the `-studio` variant. Detect +> and verify the specifics live (commands below) — do **not** assume any +> particular hostname, domain, nameserver, or OS. Where this skill shows +> example values they are illustrative placeholders. + +## 0. Am I in pi-devbox, and what's true *here*? + +Cheap detection signals (any one is sufficient): + +```sh +[ -d /usr/local/lib/pi-devbox ] && echo "pi-devbox image" +[ -r "$HOME/.ssh-local/config" ] && echo "LAN/host SSH sidecar present" +case "$PS1" in *'[devbox]'*) echo "interactive devbox shell";; esac +``` + +Then orient before acting: + +```sh +cat /etc/os-release | head -2 # container distro (usually Debian) +ls -la /usr/local/lib/pi-devbox/ # which devbox helpers exist +sed -n '/^Host /,$p' ~/.ssh-local/config 2>/dev/null # host/LAN reachability, if any +mount | grep -E ' /workspace | /home/\S+/\.ssh ' # what's bind-mounted +``` + +## 1. Persistence vs ephemerality — know before you write + +The container has **three storage tiers with very different lifetimes**. Pick +the right one or work is silently lost on the next recreate/update. + +| Tier | Examples | Survives `down`? | Survives `down -v`? | Survives image update / `--force-recreate`? | +|---|---|---|---|---| +| **Host bind-mount** | `/workspace`, usually `~/.ssh` (ro), often `~/.mempalace` | yes | yes (lives on host) | yes | +| **Named volume** | `~/.pi`, `~/.ssh-local`, `~/.cache/bash`, `~/.local/share/{uv,nvim,zoxide}` | yes | **no** | yes | +| **Writable container layer** | anything else: `sudo apt install …`, `rustup`/`ghc`/`R` toolchains, files in `/tmp`, `/opt` edits | yes | **no** | **no** | + +Practical consequences: + +- **Durable work goes in `/workspace`** (it's the host filesystem, UID-aligned — + what you write appears with the user's normal ownership on the host). +- **Runtime-installed system packages and language toolchains are ephemeral.** + If a task needs them reproducibly, it belongs in the image (Dockerfile) or a + project manifest, not an ad-hoc `apt install`. Tell the user when you install + something that won't survive. +- **`~/.pi` is a named volume**, so things baked into the *image* under + `/home//...` are **shadowed** by the volume on existing containers and + only seen on a fresh volume. Image-owned content that must always be live + belongs under an image path like `/usr/local/...` or `/opt/...` and is linked + in by the entrypoint — not dropped into a home directory that a volume covers. + +## 2. Interactive shell vs. your tool shell (a real footgun) + +The conveniences below are defined in `~/.bash_aliases` and **only exist in an +interactive login shell.** Your `bash` *tool* runs non-interactively, so these +are "command not found" there — you must spell out the underlying command. + +| Interactive alias | Non-interactive equivalent to actually run | +|---|---| +| `dssh ` | `ssh -F "$HOME/.ssh-local/config" ` | +| `dscp …` | `scp -F "$HOME/.ssh-local/config" …` | +| `cat file` (→ `bat`) | `cat file` works, but output differs; use `command cat` for raw | +| `ll`, `la` (→ `eza`/`ls`) | `ls -lh`, `ls -lha` | + +If a command "works in my terminal but not when the agent runs it," this alias +gap is the first thing to suspect. + +## 3. Reaching the Docker host and its LAN over SSH + +When the host is VM-backed (e.g. OrbStack / Docker Desktop on macOS) the +entrypoint's `setup-lan-access.sh` writes a **writable SSH sidecar** at +`~/.ssh-local/config`. It always provides: + +- A `Host *` block redirecting `ControlPath` into the writable `~/.ssh-local/cm` + (because `~/.ssh` is typically bind-mounted **read-only**, so a master socket + can't be created under it), plus `Include ~/.ssh/config`. +- Aliases **`host` / `mac`** → `host.docker.internal` (user comes from + `HOST_SSH_USER`) — i.e. SSH back into the Docker host. +- On VM-backed hosts only: an **SSH-jump-via-host** block so the container can + reach the host's directly-attached LAN peers (`ProxyJump host`). On a native + Linux host the LAN is usually reachable directly and this jump block is + omitted — **so don't assume a jump path exists; read the sidecar.** + +Use it (remember §2 — spell it out in tool bash): + +```sh +ssh -F "$HOME/.ssh-local/config" mac 'hostname; whoami' # reach the host +ssh -F "$HOME/.ssh-local/config" '…' # reach a LAN peer (if configured) +``` + +Two related mechanisms (don't reinvent them): + +- **ControlMaster multiplexing** is preconfigured (`/tmp/sshcm/`) to survive + CGNAT per-destination flow caps on residential ISPs. If `~/.ssh/config` pins + a `ControlPath` under the read-only `~/.ssh`, override with + `-o ControlPath=none` (or use the sidecar, which already redirects it). +- **`pi --ssh `** rewires pi's own read/write/edit/bash tools to run on a + remote host; it has its own writable-socket fallback. See the `pi-extensions` + skill for that path. + +## 4. DNS / name resolution — environment-specific, verify live + +How a name resolves here is **not universal** and depends on the host's +networking. The container's own resolver is just `/etc/resolv.conf`, but the +*host* (which you reach via §3, and whose DNS the container may inherit) can use +**split-horizon DNS** to send certain internal domains to specific nameservers +while everything else goes to a default resolver/VPN gateway. The mechanism is +OS-specific and **may not be present at all**: + +- **macOS host:** per-domain files in `/etc/resolver/`, each listing + `nameserver` lines. Reading them (over `ssh … mac`) is a fine way to learn the + real split-DNS map — *for that one machine.* +- **Linux host:** typically `systemd-resolved` split DNS (per-link `Domains=` + routing) or `/etc/resolv.conf` `search`/`nameserver`. +- **Windows host:** the NRPT (Name Resolution Policy Table) plays the per-suffix + role; WSL2 inherits host resolution via mirrored networking + DNS tunneling. + +Operating rules: + +1. **Never hardcode a domain→nameserver mapping or a specific nameserver IP** — + it is per-deployment and changes between users and even VPN states. +2. **Verify by reading the live config**, e.g. `cat /etc/resolv.conf` in the + container, or `ssh … mac 'cat /etc/resolver/* 2>/dev/null'` on a macOS host. +3. **Reachability needs both DNS *and* a route.** A name resolving to an + internal address is useless if packets to that subnet don't have a path + (e.g. via the VPN or the §3 jump). Check both when something "resolves but + won't connect." +4. If you discover deployment-specific facts (a domain, a nameserver, a + reachable peer), prefer recording them in MemPalace over baking them into + code or this skill. + +## 5. tmux is 0-indexed — don't change it + +The image ships `/etc/tmux.conf` with `base-index 0` / `pane-base-index 0` +because **pi-studio hard-codes its tmux send target to `:0.0`.** If you +(or a user `~/.tmux.conf`) set `base-index 1`, pi-studio fails with "can't find +window: 0". Leave the indexing alone in this environment. + +## 6. Python and other languages: uv-first, toolchains are ephemeral + +- A system `python3` exists, but **prefer `uv`** for REPLs and project envs — + it's installed and its store (`~/.local/share/uv`) is a persisted volume. + - Throwaway REPL: `uv run --with ipython ipython` + - Project env: `cd /workspace/proj && uv init && uv add && uv run …` + (the `pyproject.toml` + `uv.lock` travel with the repo — the durable choice). +- Other language toolchains (Rust via rustup, R, GHC, Clojure, Go) are + **runtime opt-ins on the ephemeral layer** unless baked into the image — they + do not survive `down -v` or an image update. Flag this when installing. + +## 7. pi-studio reachability (only in the `-studio` variant) + +Present only if `/opt/pi-studio` exists / the `studio_*` tools are in your tool +list. pi-studio **binds to `127.0.0.1` inside the container** with no host-bind +flag, so a plain `docker -p` publish can't reach it. Two supported paths: + +- **Host networking** (`network_mode: host`): container loopback == host + loopback; open the tokenized URL on the host. (Changes + `host.docker.internal` semantics — weigh against §3 LAN jump.) +- **`studio-expose` bridge** (`STUDIO_EXPOSE=1` or run `studio-expose &`): a + `socat` relay from the container's external interface to its loopback, so a + published `127.0.0.1:PORT` + `ssh -L PORT:127.0.0.1:PORT host` reaches it. + +The real auth token comes from the `/studio` slash command (`/studio --status` +to reprint), **not** from `studio-expose`. For Graphviz, use `dot-watch` → +PNG (Studio renders Mermaid natively and previews PNG, but not SVG/DOT). + +## 8. MemPalace is the shared brain + +MemPalace data is usually a **host bind-mount**, so a pi on the host and a pi in +this container share one palace (SQLite WAL: many readers, one writer). Use it +to persist the deployment-specific facts this skill deliberately refuses to +hardcode. Details are in the `mempalace` skill. + +## Checklist before acting in this environment + +- [ ] Writing durable output? → `/workspace`, not the ephemeral layer. +- [ ] Using `dssh`/`dscp`/`ll` in the bash tool? → spell out the real command. +- [ ] Assuming a hostname / domain / nameserver / host OS? → stop, detect it. +- [ ] "Resolves but won't connect"? → check route *and* DNS (§3 + §4). +- [ ] `apt`/toolchain install? → tell the user it's ephemeral unless imaged. +- [ ] Touching tmux indexing? → don't (§5). diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 0ff4465..f9bc9b1 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -80,6 +80,12 @@ run "yq" "yq --version" run "tldr (tealdeer)" "tldr --version" run "socat" "socat -V" run "studio-expose helper" "test -x /usr/local/bin/studio-expose" +run "image-baked pi-devbox-environment skill" \ + "test -f /usr/local/share/pi-devbox/skills/pi-devbox-environment/SKILL.md" +run "global-AGENTS append snippet present" \ + "test -f /usr/local/share/pi-devbox/pi-global-AGENTS.append.md" +run "pi-devbox block merged into pi-global-AGENTS.md" \ + "grep -q 'pi-devbox:managed-block' /opt/pi-toolkit/pi-global-AGENTS.md" # ── tmux 0-indexing (required for pi-studio variants) ───────────────── echo "" @@ -177,6 +183,7 @@ exec_test "keybindings.json (pi-toolkit)" 'test -L $HOME/.pi/agent/keybi exec_test "extensions ≥ 4 (pi-extensions)" 'count=$(ls -1 $HOME/.pi/agent/extensions/*.ts 2>/dev/null | wc -l); [ $count -ge 4 ] && echo "$count extensions"' exec_test "mempalace.ts bridge" 'test -L $HOME/.pi/agent/extensions/mempalace.ts && echo ok' exec_test "settings.json bootstrapped" 'test -f $HOME/.pi/agent/settings.json && echo ok' +exec_test "pi-devbox-environment skill linked" 'test -L $HOME/.agents/skills/pi-devbox-environment && test -f $HOME/.agents/skills/pi-devbox-environment/SKILL.md && echo ok' # pi-fork + pi-observational-memory are registered by entrypoint-user.sh via # `pi install /opt/`, which runs slightly after the keybindings marker.