skills: add pi-devbox-firstboot, and point the managed AGENTS block at it

The fleet's first-boot acceptance round is now scoped by published state
(policy v2, artifact art_20261023T204306_b388e93c3d64): a booting container
reads two claim keys -- firstboot-release-<TAG> and
firstboot-image-<TAG>-<ARCH> -- and derives whether it owes the release tier,
the per-architecture image tier, or only the device tier.

Nothing delivered that procedure to a device that does not already have a
coordination chain. The gate deliberately needs no roster, because fleet
membership changes in both directions, so no message addresses a new device and
nothing enrolls it. The consequence is that a BRAND-NEW device -- empty
mailbox, no chain, and the mempalace skill correctly telling it that an empty
mailbox is a fine answer -- had no path to the round at all. Historically a
peer planted the newcomer's first ask cross-device, which is exactly the roster
dependency the state design removes. This skill is how a device learns to plant
its own.

The skill is a FINDER, not the policy: it locates the newest policy.update on
topic=firstboot-acceptance and fetches the artifact, and says plainly that the
artifact wins on disagreement. A policy baked into an image is pinned to that
image; keeping only the lookup here means a revision needs no release and a
device on an older image still reaches the current policy.

Baked canonical, like pi-devbox-environment, rather than skillset-owned. The
round verifies the image, so pinning it to the image is right -- and a
skillset-owned skill needs the vendoring machinery (ARG SKILLSET_SNAPSHOT_REF,
the manifest/label pair, a per-skill refresh script) that today exists only for
mempalace. No wiring was needed: entrypoint-user.sh and pi-devbox-version both
enumerate the baked skills directory.

smoke-test.sh asserts the AGENTS.md pointer BY PATH, not by prose. A new device
reaches the round only through that line, so rewording the paragraph must not
be able to cut it silently. Both skill-resolution loops gain the new name.
This commit is contained in:
pi@mbp-m1-2020
2026-10-24 00:31:15 +02:00
parent 779efbf4fe
commit 5d6ff537c4
6 changed files with 295 additions and 3 deletions
+9 -2
View File
@@ -291,12 +291,19 @@ run "image-baked pi-devbox-environment skill" \
"test -f /usr/local/share/pi-devbox/skills/pi-devbox-environment/SKILL.md"
run "image-baked credential-incident-response skill" \
"test -f /usr/local/share/pi-devbox/skills/credential-incident-response/SKILL.md"
run "image-baked pi-devbox-firstboot skill" \
"test -f /usr/local/share/pi-devbox/skills/pi-devbox-firstboot/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"
run "mempalace session-start pointer merged into global AGENTS.md" \
"grep -q 'load the mempalace skill' /opt/pi-toolkit/pi-global-AGENTS.md"
# The firstboot pointer is asserted by PATH, not prose: a brand-new device with
# an empty mailbox reaches the acceptance round only through this line, and
# rewording the paragraph must not be able to silently break the link.
run "firstboot skill pointer merged into global AGENTS.md" \
"grep -q 'pi-devbox-firstboot/SKILL.md' /opt/pi-toolkit/pi-global-AGENTS.md"
# Vendored fallback skills (so a no-skillset container still resolves the
# AGENTS.md 'read the pi-extensions skill' pointer).
run "image-baked pi-extensions fallback skill" \
@@ -811,7 +818,7 @@ exec_test "mempalace skill snapshot is current" 'f=$HOME/.agents/skills/mempa
# Link TARGETS, not just link existence: with no skillset mounted (as here) the
# baked tree must be what resolves, for all four vendored skills.
exec_test "vendored skills resolve to the baked tree (no skillset mounted)" \
'for s in mempalace pi-extensions pi-devbox-environment credential-incident-response; do
'for s in mempalace pi-extensions pi-devbox-environment credential-incident-response pi-devbox-firstboot; do
case "$(readlink -f $HOME/.agents/skills/$s)" in
/usr/local/share/pi-devbox/skills/$s) ;;
*) echo "$s resolves to $(readlink -f $HOME/.agents/skills/$s)" >&2; exit 1 ;;
@@ -825,7 +832,7 @@ exec_test "vendored skills resolve to the baked tree (no skillset mounted)" \
exec_test "pi-devbox-version reports skill sources (all baked, no skillset here)" \
'out=$(pi-devbox-version)
echo "$out" | grep -q "skills:" || { echo "no skills section" >&2; exit 1; }
for s in mempalace pi-extensions pi-devbox-environment credential-incident-response; do
for s in mempalace pi-extensions pi-devbox-environment credential-incident-response pi-devbox-firstboot; do
echo "$out" | grep -qE "^ $s +baked( \([^)]*\))?$" \
|| { echo "$s not reported as baked" >&2; exit 1; }
done; echo ok'