Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d619a6e2ec | |||
| 2abfee141b | |||
| c346a106a3 | |||
| 8de0fad776 |
@@ -14,15 +14,23 @@ re-brand of opencode-devbox's `pi-only` variant.
|
|||||||
- `Dockerfile.variant` — `FROM base-<hash>`, adds pi + companions
|
- `Dockerfile.variant` — `FROM base-<hash>`, adds pi + companions
|
||||||
(`pi-toolkit`, `pi-extensions`, `pi-fork`, `pi-observational-memory`)
|
(`pi-toolkit`, `pi-extensions`, `pi-fork`, `pi-observational-memory`)
|
||||||
and, when `INSTALL_STUDIO=true`, vendors `pi-studio` to `/opt/pi-studio`
|
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.sh` — UID/GID alignment as root, then drops to `developer`.
|
||||||
- `entrypoint-user.sh` — per-container start: SSH ControlMaster socket
|
- `entrypoint-user.sh` — per-container start: SSH ControlMaster socket
|
||||||
dir, LAN-access setup, MemPalace init, pi-toolkit + pi-extensions
|
dir, LAN-access setup, MemPalace init, pi-toolkit + pi-extensions
|
||||||
deploy, mempalace-bridge symlink, fork/recall + pi-studio pi-install,
|
deploy, mempalace-bridge symlink, fork/recall + pi-studio pi-install,
|
||||||
optional `studio-expose` bridge (when `STUDIO_EXPOSE=1`), skillset
|
optional `studio-expose` bridge (when `STUDIO_EXPOSE=1`), image-baked
|
||||||
deploy.
|
skills symlink-in, skillset deploy.
|
||||||
- `rootfs/` — files baked into the image (bash aliases, inputrc,
|
- `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/<name>/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.
|
- `scripts/smoke-test.sh` — sanity checks run by CI before pushing to Hub.
|
||||||
- `.gitea/workflows/docker-publish.yml` — two-phase CI (base-decide →
|
- `.gitea/workflows/docker-publish.yml` — two-phase CI (base-decide →
|
||||||
build-base → smoke → build-variant → promote-base-latest →
|
build-base → smoke → build-variant → promote-base-latest →
|
||||||
@@ -33,7 +41,8 @@ re-brand of opencode-devbox's `pi-only` variant.
|
|||||||
## Versioning scheme
|
## Versioning scheme
|
||||||
|
|
||||||
- Tags follow semver. **v1.0.0** is the first decoupled release; future
|
- 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.
|
pi npm version updates and small fixes.
|
||||||
- Docker Hub tags: `joakimp/pi-devbox:vX.Y.Z` + `joakimp/pi-devbox:latest`
|
- Docker Hub tags: `joakimp/pi-devbox:vX.Y.Z` + `joakimp/pi-devbox:latest`
|
||||||
+ (since v1.1.0) `joakimp/pi-devbox:vX.Y.Z-studio` +
|
+ (since v1.1.0) `joakimp/pi-devbox:vX.Y.Z-studio` +
|
||||||
|
|||||||
+82
-4
@@ -11,7 +11,87 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Unreleased
|
## 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/<name>/` 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
|
||||||
|
`ssh-lan.conf` LAN-peer documentation that landed on `main` after v1.1.6.
|
||||||
|
Companion refs are auto-resolved to SHAs at build as before.
|
||||||
|
|
||||||
|
### Bumped: pi 0.79.8 → 0.79.9
|
||||||
|
|
||||||
|
Notable upstream changes (from [pi releases](https://github.com/earendil-works/pi/releases/tag/v0.79.9)):
|
||||||
|
|
||||||
|
- **Chat-template thinking compatibility** — OpenAI-compatible custom
|
||||||
|
providers can map pi thinking levels into `chat_template_kwargs`, enabling
|
||||||
|
vLLM/Hugging Face chat-template models (e.g. DeepSeek) to use
|
||||||
|
provider-native thinking controls.
|
||||||
|
- **GLM-5.2 provider improvements** — corrected Fireworks OpenAI-compatible
|
||||||
|
routing and OpenRouter `xhigh` thinking support, improving `/model`
|
||||||
|
behaviour and high-effort reasoning for GLM-5.2.
|
||||||
|
- **Fixes** — same-directory session switches now reuse imported extension
|
||||||
|
modules (fresh instances + lifecycle events preserved); deep session
|
||||||
|
branches no longer take quadratic time to build context; Markdown
|
||||||
|
streaming code-fence rendering no longer flickers on partial closing
|
||||||
|
fences; fuzzy `edit` matches preserve untouched line blocks instead of
|
||||||
|
rewriting the whole file; `/model` hides Copilot models unavailable to the
|
||||||
|
account and ranks exact provider-prefixed matches first.
|
||||||
|
|
||||||
|
### Docs: document `~/.config/devbox-shell/ssh-lan.conf` for naming LAN peers
|
||||||
|
|
||||||
|
The host-owned, bind-mounted `~/.config/devbox-shell/ssh-lan.conf` is the
|
||||||
|
intended place to add `ProxyJump host` overrides for **named** LAN peers (so
|
||||||
|
`pi --ssh <peer>` / `dssh <peer>` route through the host), but it was only
|
||||||
|
mentioned in `.env.example` and the `setup-lan-access.sh` header — never in the
|
||||||
|
README. Added a "Naming LAN peers" subsection to the README troubleshooting
|
||||||
|
block (plus a pointer from the SSH/ControlMaster section), and corrected the
|
||||||
|
stale `setup-lan-access.sh` comment that suggested editing the read-only
|
||||||
|
`~/.ssh/config` instead of `ssh-lan.conf`.
|
||||||
|
|
||||||
## v1.1.6 — 2026-06-19
|
## v1.1.6 — 2026-06-19
|
||||||
|
|
||||||
@@ -77,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
|
reproducibility. Resolution now validates each result is a 40-hex commit
|
||||||
SHA (and pi a real semver) and aborts the release otherwise.
|
SHA (and pi a real semver) and aborts the release otherwise.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## v1.1.5 — 2026-06-18
|
## v1.1.5 — 2026-06-18
|
||||||
|
|
||||||
Patch release: SSH ControlMaster read-only-socket fix + pi `0.79.6` → `0.79.7`
|
Patch release: SSH ControlMaster read-only-socket fix + pi `0.79.6` → `0.79.7`
|
||||||
@@ -473,7 +551,7 @@ dependencies.
|
|||||||
### Future work
|
### Future work
|
||||||
|
|
||||||
- v1.1.0: `:latest-studio` variant (adds [pi-studio](https://github.com/omaclaren/pi-studio)).
|
- 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
|
## v0.79.0 — 2026-06-08
|
||||||
|
|
||||||
|
|||||||
@@ -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`
|
- **[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
|
- **`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
|
- **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.
|
The entrypoint deploys/registers all of these on first container start. Re-running is idempotent and preserves user edits.
|
||||||
|
|
||||||
|
|||||||
@@ -481,6 +481,11 @@ COPY rootfs/home/developer/.inputrc /etc/skel-devbox/.inputrc
|
|||||||
|
|
||||||
# ── Entrypoint ────────────────────────────────────────────────────────
|
# ── Entrypoint ────────────────────────────────────────────────────────
|
||||||
COPY rootfs/usr/local/lib/pi-devbox/ /usr/local/lib/pi-devbox/
|
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/studio-expose /usr/local/bin/studio-expose
|
||||||
COPY rootfs/usr/local/bin/dot-watch /usr/local/bin/dot-watch
|
COPY rootfs/usr/local/bin/dot-watch /usr/local/bin/dot-watch
|
||||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||||
|
|||||||
@@ -94,6 +94,24 @@ RUN set -e && \
|
|||||||
echo "pi-fork at $(cd /opt/pi-fork && git rev-parse --short HEAD)" && \
|
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)"
|
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) ─────────────────────
|
# ── Optional: pi-studio (:latest-studio variant) ─────────────────────
|
||||||
# pi-studio (omaclaren/pi-studio) is a pi-package + theme providing a
|
# pi-studio (omaclaren/pi-studio) is a pi-package + theme providing a
|
||||||
# two-pane browser workspace: prompt/response editor, KaTeX/Mermaid live
|
# two-pane browser workspace: prompt/response editor, KaTeX/Mermaid live
|
||||||
|
|||||||
@@ -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
|
diary-write, etc.) are wired into pi automatically by the pi-extensions
|
||||||
mempalace bridge.
|
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/<name>/`; the `COPY` in
|
||||||
|
`Dockerfile.base` and the entrypoint symlink loop pick it up automatically.
|
||||||
|
|
||||||
## SSH and ControlMaster
|
## SSH and ControlMaster
|
||||||
|
|
||||||
The base image preconfigures `Host *` ssh defaults:
|
The base image preconfigures `Host *` ssh defaults:
|
||||||
@@ -479,7 +511,11 @@ this without editing the read-only config:
|
|||||||
cannot fail on the read-only socket dir.
|
cannot fail on the read-only socket dir.
|
||||||
- **`ssh -F ~/.ssh-local/config` / `dssh` / `dscp`** — `setup-lan-access.sh`
|
- **`ssh -F ~/.ssh-local/config` / `dssh` / `dscp`** — `setup-lan-access.sh`
|
||||||
redirects `ControlPath` into the writable `~/.ssh-local/cm` for every host
|
redirects `ControlPath` into the writable `~/.ssh-local/cm` for every host
|
||||||
(the sidecar is rendered on all host OSes).
|
(the sidecar is rendered on all host OSes). To name LAN peers that should
|
||||||
|
jump via the host, add `ProxyJump host` overrides in the host-owned
|
||||||
|
`~/.config/devbox-shell/ssh-lan.conf` (see
|
||||||
|
[Naming LAN peers](#naming-lan-peers)) rather than the read-only
|
||||||
|
`~/.ssh/config`.
|
||||||
|
|
||||||
## tmux and 0-indexed sessions
|
## tmux and 0-indexed sessions
|
||||||
|
|
||||||
@@ -615,6 +651,28 @@ auto-runs on container start and writes `~/.ssh-local/config` with a
|
|||||||
ssh-jump-via-host configuration. Set `DEVBOX_LAN_ACCESS=jump` and
|
ssh-jump-via-host configuration. Set `DEVBOX_LAN_ACCESS=jump` and
|
||||||
`HOST_SSH_USER=<your-mac-user>` in `.env` if auto-detection fails.
|
`HOST_SSH_USER=<your-mac-user>` in `.env` if auto-detection fails.
|
||||||
|
|
||||||
|
#### Naming LAN peers
|
||||||
|
|
||||||
|
`DEVBOX_LAN_ACCESS` / `HOST_SSH_USER` only set up the *jump* to the host. To
|
||||||
|
make a **named** peer route through it — so `pi --ssh alpserv-2`,
|
||||||
|
`dssh alpserv-2`, etc. resolve the ProxyJump — add a `ProxyJump host` override
|
||||||
|
for it in the host-owned, bind-mounted `~/.config/devbox-shell/ssh-lan.conf`
|
||||||
|
(**not** `~/.ssh/config`, which is mounted read-only):
|
||||||
|
|
||||||
|
```
|
||||||
|
Host pve pve-2 alpserv-2 lagret
|
||||||
|
ProxyJump host
|
||||||
|
```
|
||||||
|
|
||||||
|
`HostName` / `User` / `IdentityFile` are inherited from the matching block in
|
||||||
|
your real `~/.ssh/config` (first-value-wins, so only `ProxyJump` is taken from
|
||||||
|
here). This file is `Include`d *before* `~/.ssh/config` and read fresh on every
|
||||||
|
connection — newly added peers work immediately, no container or session
|
||||||
|
restart needed — and the peer names stay out of the published image (they're a
|
||||||
|
fact about your specific LAN, not the image). Alternatively, set
|
||||||
|
`DEVBOX_LAN_AUTOJUMP_PRIVATE=1` to ProxyJump *any* RFC1918 address through the
|
||||||
|
host without naming peers (see `.env.example`).
|
||||||
|
|
||||||
### Smoke-testing a local build
|
### Smoke-testing a local build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -40,6 +40,32 @@ if [ -d "$SKEL_DIR" ]; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ── 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. Done EARLY
|
||||||
|
# — before the pi-toolkit/extensions deploy below — so the symlinks exist by
|
||||||
|
# the time anything gates on "container ready": the smoke-test readiness probe
|
||||||
|
# waits on pi-deploy markers (keybindings.json, mempalace.ts) that only land
|
||||||
|
# AFTER this point, so linking here closes a sample-too-early race that failed
|
||||||
|
# the runtime skill-link assertion. Pointing at the image path (/usr/local/...)
|
||||||
|
# keeps the skill fresh from the image and surviving 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 (deployed later, at the end
|
||||||
|
# of this script) or a user override is never clobbered; the skillset deploy
|
||||||
|
# classifies these as foreign-links and its --prune-stale pass leaves them
|
||||||
|
# alone (only dangling symlinks are pruned).
|
||||||
|
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
|
||||||
|
|
||||||
# ── MemPalace: initialize palace for the workspace if mempalace is installed
|
# ── MemPalace: initialize palace for the workspace if mempalace is installed
|
||||||
# Creates the palace directory structure on first run. Idempotent — skips
|
# Creates the palace directory structure on first run. Idempotent — skips
|
||||||
# if palace already exists, so upgrades from older versions preserve
|
# if palace already exists, so upgrades from older versions preserve
|
||||||
|
|||||||
@@ -188,7 +188,9 @@ if [ -r "${HOME}/.ssh/config" ]; then
|
|||||||
|
|
||||||
# Your own target hosts. Scope reset to match-all so this Include applies to
|
# Your own target hosts. Scope reset to match-all so this Include applies to
|
||||||
# every target (an Include is otherwise scoped to the enclosing Host block).
|
# every target (an Include is otherwise scoped to the enclosing Host block).
|
||||||
# Add 'ProxyJump host' to LAN entries here (or in ssh-lan.conf above).
|
# To make a LAN peer jump via the host, add 'ProxyJump host' to its entry in
|
||||||
|
# the host-owned ~/.config/devbox-shell/ssh-lan.conf (Included above) — NOT
|
||||||
|
# here in ~/.ssh/config, which is typically bind-mounted read-only.
|
||||||
Host *
|
Host *
|
||||||
Include ~/.ssh/config
|
Include ~/.ssh/config
|
||||||
EOF
|
EOF
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<!-- pi-devbox:managed-block — appended to the global AGENTS.md at image build
|
||||||
|
time (Dockerfile.variant), after pi-toolkit is cloned. Keep this short:
|
||||||
|
it is a pointer, the depth lives in the skill. -->
|
||||||
|
|
||||||
|
## 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 …`).
|
||||||
@@ -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/<user>/...` 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 <host>` | `ssh -F "$HOME/.ssh-local/config" <host>` |
|
||||||
|
| `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" <lan-peer> '…' # 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 <host>`** 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/<domain>`, 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 `<session>: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 <pkgs> && 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).
|
||||||
@@ -80,6 +80,12 @@ run "yq" "yq --version"
|
|||||||
run "tldr (tealdeer)" "tldr --version"
|
run "tldr (tealdeer)" "tldr --version"
|
||||||
run "socat" "socat -V"
|
run "socat" "socat -V"
|
||||||
run "studio-expose helper" "test -x /usr/local/bin/studio-expose"
|
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) ─────────────────
|
# ── tmux 0-indexing (required for pi-studio variants) ─────────────────
|
||||||
echo ""
|
echo ""
|
||||||
@@ -156,6 +162,7 @@ for i in $(seq 1 45); do
|
|||||||
if docker exec "$CID" sh -c '
|
if docker exec "$CID" sh -c '
|
||||||
test -L /home/developer/.pi/agent/keybindings.json && \
|
test -L /home/developer/.pi/agent/keybindings.json && \
|
||||||
test -L /home/developer/.pi/agent/extensions/mempalace.ts && \
|
test -L /home/developer/.pi/agent/extensions/mempalace.ts && \
|
||||||
|
test -L /home/developer/.agents/skills/pi-devbox-environment && \
|
||||||
count=$(ls -1 /home/developer/.pi/agent/extensions/*.ts 2>/dev/null | wc -l) && \
|
count=$(ls -1 /home/developer/.pi/agent/extensions/*.ts 2>/dev/null | wc -l) && \
|
||||||
[ "$count" -ge 4 ]
|
[ "$count" -ge 4 ]
|
||||||
' >/dev/null 2>&1; then
|
' >/dev/null 2>&1; then
|
||||||
@@ -177,6 +184,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 "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 "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 "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-fork + pi-observational-memory are registered by entrypoint-user.sh via
|
||||||
# `pi install /opt/<pkg>`, which runs slightly after the keybindings marker.
|
# `pi install /opt/<pkg>`, which runs slightly after the keybindings marker.
|
||||||
|
|||||||
Reference in New Issue
Block a user