Carries the facts a two-day credential incident produced, not the discipline: probe the issuer FIRST (11 of 13 "exposed" credentials were already dead at the provider, which cost five HTTP requests to learn and was never checked), the 403-vs-401 trap that scoped tokens introduce into liveness probes, revocation beats deletion for anything already replicated, the three places a secret hides in a Chroma palace (FTS content, metadata, raw bytes) in coverage order, scope derivation from measured consumers, and this fleet's age store with its single-recipient weakness. Facts transfer between sessions; exhortations do not — hence a separate skill for the domain knowledge and a one-line pointer in the always-loaded block. Authored here, so baked is canonical and it is NOT added to skillset-owned.txt. Skill dirs are picked up by a glob in entrypoint-user.sh, so no registration is needed — verified rather than assumed, since an enumerated list would have left the skill inert, a fitting failure given its subject. Three smoke assertions extended so a future rebuild cannot silently drop it.
8.8 KiB
Vendored fallback skills
Most directories here are image-baked skills that entrypoint-user.sh
symlinks into ~/.agents/skills/ on container start. They are the fallback
layer: see Runtime precedence below for which copy actually wins when a
skillset repo is mounted (through v1.8.4 the answer was "always the baked
one", which was a bug).
| skill | owner | how it gets here |
|---|---|---|
pi-devbox-environment |
pi-devbox (this repo) | authored here; the canonical copy |
credential-incident-response |
pi-devbox (this repo) | authored here; the canonical copy |
pi-extensions |
the pi-extensions package repo (skill/) |
vendored fallback + refreshed at build |
mempalace |
the skillset repo |
vendored fallback (snapshot only) |
Why fallbacks exist
The pi-toolkit global AGENTS.md tells every pi session to read
~/.agents/skills/pi-extensions/SKILL.md at start (to fix fork/recall
under-utilisation). That pointer dangles in a container started without the
private skillset repo mounted. Baking the skill closes that availability
gap. mempalace is baked for the same reason (memory continuity); since
nothing in pi-toolkit's AGENTS.md points to it, the pi-devbox managed block
(pi-global-AGENTS.append.md) also adds the matching proactive-load
directive ("load the mempalace skill at session start") so a new container
actually picks it up rather than relying on description-matching.
pi-extensions's directive already ships in pi-toolkit's AGENTS.md, so only
its skill file needed baking.
Freshness model (layered — see Dockerfile.variant)
-
pi-extensions— Option 1 + Option 2. The committed copy here is the floor; at build timeDockerfile.variantcopies/opt/pi-extensions/skill/(the pinned, package-owned source) over it, so a normal build ships the fresh package copy and a stale-ref / mirror build still ships the snapshot. Keepevaluate-extension-usage.pyalongsideSKILL.md— the skill calls it via./. -
mempalace— Option 2 only. Themempalaceconsumer skill lives only in the privateskillsetrepo (themempalace-toolkitrepo ships a different skill,opencode-mempalace-bridge), so there is no public package source to copy from. This snapshot is refreshed manually per release.Refresh it with
scripts/vendor-mempalace-skill.sh <skillset-root>, notcp. Because the image cannot clone the private upstream, the snapshot used to be anonymous — nothing recorded which skillset commit the bytes came from, so the only staleness check possible was a hand-maintained phrase canary inscripts/smoke-test.sh, which by construction detects "older than the phrase I remembered to pin", never "older than skillset main". Two facts now travel with the file:Fact Where Kind ARG SKILLSET_SNAPSHOT_REFinDockerfile.variantmanifest skillset_snapshot_ref+ OCI labelse.jordbo.pi-devbox.skillset-snapshot-refa claim about which commit these bytes are sha256sumof this file, measured in the manifest layermanifest skillset_snapshot_sha256the bytes that actually shipped The script writes both together, refuses when the upstream file has uncommitted modifications (no commit describes those bytes), and
--checkverifies the claim against a real clone. Deliberately anARGdefault rather than a CI-resolved value: no credential for a private repo, no change at any of the fourDockerfile.variantbuild call sites, and a localdocker buildrecords the same thing CI does.Verifying "is this snapshot current?" is not a CI job and was deliberately not made one — see the Unreleased CHANGELOG entry for why (private repo; another repo's branch must not be able to fail this build; and the artefact it would guard is read by no host on this fleet). The check belongs where the skillset actually is:
vendor-mempalace-skill.sh --checkfor a maintainer, andpi-devbox-version'sskills:section for an agent inside a container.
Runtime precedence (v1.8.5+)
The baked links are created early in entrypoint-user.sh (before pi-deploy,
to close a smoke readiness race) with a create-only-when-absent guard, and the
skillset deploy runs last and treats them as foreign links. Through v1.8.4
that combination meant the baked snapshot always won: an edit pushed to
skillset/skills/mempalace/SKILL.md was invisible in every container until the
next image build (measured on two hosts — live md5 129bcc4752 vs baked
5236024fef, new section absent). Editing those skills appeared to work.
devbox-skill-reconcile now runs immediately after the skillset deploy and
repoints the links for skills the skillset owns, listed one per line in
skillset-owned.txt. Precedence, highest first:
- user override — a real directory, or a symlink pointing outside the baked tree; never touched by anything
- live skillset clone — but only for names in
skillset-owned.txt - baked snapshot — everything else, and every skill when no skillset is mounted
Which one won is now reportable from inside the container:
pi-devbox-version prints a skills: section naming, per vendored skill,
baked or live <repo> @ <sha> — and for mempalace whether that live copy is
identical to the baked fingerprint, at the same commit but with uncommitted
edits, or genuinely divergent. Before that, a stale baked snapshot and a current
live clone were indistinguishable from inside, which is how the freshness of
this file went unexamined for three releases. The section is suppressed with
--no-skills on the container-start banner, because entrypoint-user.sh prints
the version before the links exist and long before the reconcile below runs.
On this fleet, precedence 2 wins for mempalace on every host — all four
compose stacks mount a workspace containing the skillset — so the baked copy is
exercised only by CI and by a hypothetical no-mount container. Worth
remembering before spending effort on its freshness.
Ownership is per-skill on purpose: pi-extensions' authoritative source is the
package repo (copied over the snapshot at build), and skillset carries a
downstream copy that can lag, so handing it to the clone would regress the
skill. Only mempalace is skillset-owned today.
Verify with readlink -f ~/.agents/skills/<skill> — not by reading the
entrypoint. Smoke covers both directions (baked resolution with no skillset
mounted, plus a fabricated-skillset run of the reconciler).
Refreshing the snapshots
cp <pi-extensions-pkg>/skill/SKILL.md pi-extensions/SKILL.md
cp <pi-extensions-pkg>/skill/evaluate-extension-usage.py pi-extensions/
Copy pi-extensions from its owner in the table above — the package
repo's skill/ (since a7f3044 co-located it there; skillset also carries a
copy, but it is a downstream duplicate and can lag). Copying pi-extensions
from skillset would regress the snapshot to whatever that repo last mirrored.
mempalace is not refreshed by cp — see the Freshness model section
above: scripts/vendor-mempalace-skill.sh <skillset-root> is the only thing
that should ever touch that snapshot, because a bare copy can update the bytes
without updating the ref that claims to describe them, which produces a
manifest that confidently lies.
Neither vendored skill has a hand-maintained "last refreshed at" line here on
purpose — one previously existed (skillset 670f7f1, pi-extensions pkg
e73cb9f) and went stale within hours, because nothing forced it to move
when the ARGs did. 670f7f1 is now a cautionary example rather than a fact
worth recording: it is the commit that told agents to hand-stamp added_by,
which a later skillset commit (and the pi-devbox edge stamper) withdrew — so a
reader trusting that line would have been pointed at superseded guidance.
Both facts it tried to capture now live somewhere that cannot drift by hand:
| Fact | Where |
|---|---|
which skillset commit mempalace's bytes came from |
ARG SKILLSET_SNAPSHOT_REF (Dockerfile.variant) + skillset_snapshot_ref in build-manifest.json, written only by vendor-mempalace-skill.sh |
| which pi-extensions package commit was vendored | ARG PI_EXTENSIONS_REF (Dockerfile.variant, CI-resolved to a 40-hex commit) → OCI label se.jordbo.pi-devbox.pi-extensions-ref and build-manifest.json's components.pi-extensions, both read from the actual /opt/pi-extensions checkout, not from intent |
When you refresh the mempalace snapshot, also update the phrase asserted by
the "mempalace skill snapshot is current" smoke test — it deliberately pins the
newest section, because the previous canary grepped a phrase that survived
the very edit that made the snapshot stale, and so passed on stale content.