Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d619a6e2ec | |||
| 2abfee141b |
@@ -14,15 +14,23 @@ re-brand of opencode-devbox's `pi-only` variant.
|
||||
- `Dockerfile.variant` — `FROM base-<hash>`, 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/<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.
|
||||
- `.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` +
|
||||
|
||||
+47
-3
@@ -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/<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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/<name>/`; the `COPY` in
|
||||
`Dockerfile.base` and the entrypoint symlink loop pick it up automatically.
|
||||
|
||||
## SSH and ControlMaster
|
||||
|
||||
The base image preconfigures `Host *` ssh defaults:
|
||||
|
||||
@@ -40,6 +40,32 @@ if [ -d "$SKEL_DIR" ]; then
|
||||
done
|
||||
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
|
||||
# Creates the palace directory structure on first run. Idempotent — skips
|
||||
# if palace already exists, so upgrades from older versions preserve
|
||||
|
||||
@@ -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 "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 ""
|
||||
@@ -156,6 +162,7 @@ for i in $(seq 1 45); do
|
||||
if docker exec "$CID" sh -c '
|
||||
test -L /home/developer/.pi/agent/keybindings.json && \
|
||||
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" -ge 4 ]
|
||||
' >/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 "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/<pkg>`, which runs slightly after the keybindings marker.
|
||||
|
||||
Reference in New Issue
Block a user