skill(pi-devbox-environment): resolve a skill symlink before editing it
Lint / hadolint (push) Successful in 8s
Lint / actionlint (push) Successful in 19s

~/.agents/skills/ lives in the ephemeral container layer and is rebuilt by
entrypoint-user.sh on every start from two sources, so an edit made through the
symlink may vanish on the next recreate. Adds to §1 (persistence tiers):

- `readlink -f ~/.agents/skills/<name>` as the first move, with a tier table:
  resolves under /workspace/skillset → edit in place; resolves under
  /usr/local/share/pi-devbox/skills → image layer, edit the canonical repo and
  `sudo cp` to activate for the running session.
- Canonical owner per baked skill (pi-devbox-environment → this repo;
  pi-extensions → the package repo's skill/, plus this repo's floor snapshot;
  mempalace → the private skillset repo), pointing at VENDORED.md as
  authoritative.
- The shadowing gotcha: image-baked links are created first and only when
  absent, and deploy-skills.sh --prune-stale leaves foreign links alone, so for
  a name present in BOTH sources the image copy wins and a skillset edit has no
  effect in the container. Documented with the live example found while writing
  this: the baked mempalace snapshot carries a Temporal grounding section
  (904fe85) that skillset at its snapshot point (8e8db64) lacks.
- Checklist gets a matching line.

Found while adding session findings to the pi-extensions skill (e73cb9f), where
the same resolve-first step was what kept the edit out of the image layer.
This commit is contained in:
2026-07-29 19:37:59 +02:00
parent d5c5da3f6c
commit 4d4abd9a9f
@@ -75,6 +75,45 @@ Practical consequences:
belongs under an image path like `/usr/local/...` or `/opt/...` and is linked 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. in by the entrypoint — not dropped into a home directory that a volume covers.
### Editing a skill: resolve the symlink before you touch it
`~/.agents/skills/` itself is in the **ephemeral container layer**, rebuilt by
`entrypoint-user.sh` on every start from two sources — so *where a skill really
lives* decides whether your edit survives:
```sh
readlink -f ~/.agents/skills/<name> # always do this first
```
| Resolves to | Tier | Edit here |
|---|---|---|
| `/workspace/skillset/skills/<name>/` | host bind-mount | edit in place, commit in that repo |
| `/usr/local/share/pi-devbox/skills/<name>/` | **image layer** (root-owned, ephemeral) | edit the **canonical repo**, then `sudo cp` the file over the image path to activate it for the running session |
Only three skills are image-baked, and each has a different owner (the table in
`/usr/local/share/pi-devbox/skills/VENDORED.md` is authoritative):
| Baked skill | Canonical source to edit |
|---|---|
| `pi-devbox-environment` | `pi-devbox` repo → `rootfs/usr/local/share/pi-devbox/skills/pi-devbox-environment/` (authored there; this file) |
| `pi-extensions` | the `pi-extensions` **package** repo → `skill/`. `Dockerfile.variant` copies it over the vendored snapshot at build, so also refresh `pi-devbox`'s `rootfs/.../pi-extensions/` copy to keep the fallback floor from diverging |
| `mempalace` | the private `skillset` repo → `skills/mempalace/` (manual snapshot refresh per release) |
**Editing through the symlink into `/usr/local/...` is silently lost on the next
recreate** — and worse, it diverges from the canonical repo that every *other*
consumer (host pi, opencode) reads.
**Shadowing gotcha:** image-baked links are created **first** and only when the
name is absent, and the later `deploy-skills.sh --bootstrap --prune-stale` pass
treats them as foreign links and leaves them alone. So for a name present in
**both** the image and `skillset` — currently `mempalace` and `pi-extensions`
**the image copy wins**, and a `skillset` edit to that skill has no effect in
the container. Verified 2026-07-29: the baked `mempalace` snapshot carries a
*Temporal grounding* section (`pi-devbox` `904fe85`) that the `skillset` copy at
its snapshot point (`8e8db64`) lacks — containers load the richer baked text
while `skillset` consumers get the older one. When you change one of those two,
decide deliberately which copy is canonical and sync the other.
## 2. Interactive shell vs. your tool shell (a real footgun) ## 2. Interactive shell vs. your tool shell (a real footgun)
The conveniences below are defined in `~/.bash_aliases` and **only exist in an The conveniences below are defined in `~/.bash_aliases` and **only exist in an
@@ -220,4 +259,5 @@ hardcode. Details are in the `mempalace` skill.
- [ ] Assuming a hostname / domain / nameserver / host OS? → stop, detect it. - [ ] Assuming a hostname / domain / nameserver / host OS? → stop, detect it.
- [ ] "Resolves but won't connect"? → check route *and* DNS (§3 + §4). - [ ] "Resolves but won't connect"? → check route *and* DNS (§3 + §4).
- [ ] `apt`/toolchain install? → tell the user it's ephemeral unless imaged. - [ ] `apt`/toolchain install? → tell the user it's ephemeral unless imaged.
- [ ] Editing a skill? → `readlink -f ~/.agents/skills/<name>` first (§1).
- [ ] Touching tmux indexing? → don't (§5). - [ ] Touching tmux indexing? → don't (§5).