Files
pi-devbox/rootfs/usr/local/share/pi-devbox/skills/VENDORED.md
T
joakimp 4efa84bb42
Lint / hadolint (push) Successful in 9s
Lint / skill-floor (push) Successful in 10s
Lint / doc-drift (push) Successful in 13s
Lint / actionlint (push) Successful in 29s
Publish Docker Image / lint-gate (push) Successful in 25s
Publish Docker Image / resolve-versions (push) Successful in 20s
Publish Docker Image / base-decide (push) Successful in 14s
Publish Docker Image / build-base (push) Successful in 42m31s
Publish Docker Image / smoke (push) Failing after 5m30s
Publish Docker Image / build-variant (push) Has been skipped
Publish Docker Image / update-description (push) Has been skipped
Publish Docker Image / promote-base-latest (push) Has been skipped
Publish Docker Image / smoke-studio (push) Failing after 8m38s
Publish Docker Image / build-variant-studio (push) Has been skipped
release(v1.11.1): pi 1.2.0; CI builds the pinned pi-obsmem SHA
- pi 1.1.0 -> 1.2.0, audited on tor-ms22 from an isolated prefix: none of
  the four breaking changes reaches the image (3 before_agent_start
  handlers checked, no triggerTurn user, pi-studio themes load); agent-core
  dist byte-identical; turn cap N=19 honoured; atelier paints in both
  renderers; obsmem /om:* registered; real-config startup clean.
- resolve-versions reads ARG PI_OBSMEM_REF from Dockerfile.variant instead
  of resolving obsmem master (which overrode the pin as --build-arg),
  requires a 40-char SHA, and warns when master moves past the pin. Block
  tested in isolation: real pin, short/branch/missing pin, older pin,
  GitHub unreachable.
- CHANGELOG: Unreleased -> v1.11.1 — 2026-10-14. README pin table,
  BASE_REBUILD_DATE marker (base rebuilds anyway), stale VENDORED.md pointer.
2026-10-14 01:17:51 +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 v1.8.8 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.