Files
pi-devbox/rootfs/usr/local/share/pi-devbox/skills/VENDORED.md
T
joakimp 36e65fe657
Lint / hadolint (push) Successful in 8s
Lint / actionlint (push) Successful in 18s
skills: add credential-incident-response, and assert it stays baked
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.
2026-08-30 00:50:11 +02:00

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 time Dockerfile.variant copies /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. Keep evaluate-extension-usage.py alongside SKILL.md — the skill calls it via ./.

  • mempalace — Option 2 only. The mempalace consumer skill lives only in the private skillset repo (the mempalace-toolkit repo 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>, not cp. 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 in scripts/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_REF in Dockerfile.variant manifest skillset_snapshot_ref + OCI label se.jordbo.pi-devbox.skillset-snapshot-ref a claim about which commit these bytes are
    sha256sum of this file, measured in the manifest layer manifest skillset_snapshot_sha256 the bytes that actually shipped

    The script writes both together, refuses when the upstream file has uncommitted modifications (no commit describes those bytes), and --check verifies the claim against a real clone. Deliberately an ARG default rather than a CI-resolved value: no credential for a private repo, no change at any of the four Dockerfile.variant build call sites, and a local docker build records 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 --check for a maintainer, and pi-devbox-version's skills: 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:

  1. user override — a real directory, or a symlink pointing outside the baked tree; never touched by anything
  2. live skillset clone — but only for names in skillset-owned.txt
  3. 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.