skills: refresh vendored mempalace snapshot a12fe5e -> e9e09d9, re-pin the canary
Lint / hadolint (push) Successful in 9s
Lint / actionlint (push) Failing after 31m52s

Folded into v1.8.13 at zero marginal cost: the snapshot is hashed into
base_tag, but Dockerfile.base already changed this release, so the ~67 min base
rebuild was already being paid. vendor-mempalace-skill.sh --check reported exit
0 (stale-but-truthful) beforehand, so skipping was sanctioned -- this is the
deliberate call the release checklist asks for. Upstream content: the bare
project-name wing convention and the <harness>@<device> added_by rule, both
downstream of the attribution defect measured on this device 2026-09-06.

The canary re-pin matters more than the refresh. Its old pair ("Provenance is
stamped for you" present / "Attribute what you file yourself" absent) still
PASSED against the new snapshot, so leaving it would have yielded a canary
green on both old and new bytes -- blind to exactly the refresh it exists to
witness, the same false-green family as the pre-v1.8.5 canary. New pair chosen
by measuring direction against both files rather than reading the diff
("Diaries self-heal; plain drawers do not" new=1/old=0; "Agent diaries live in"
new=0/old=1), then tested two-sided: PASS on refreshed bytes, FAIL on the old
bytes recovered from git.

Gates after the change: smoke-test.sh parses, vendor --check exit 0,
check-base-hash exit 0.
This commit is contained in:
2026-09-06 22:31:47 +02:00
parent 0d984b1414
commit f561acc89a
4 changed files with 63 additions and 6 deletions
+21
View File
@@ -121,6 +121,27 @@ Smoke asserts the floor is `[]` specifically, not merely falsy — `null` is the
unguarded state, so a "truthy or not" test would pass on exactly the
configuration being guarded against.
**Vendored mempalace skill snapshot refreshed `a12fe5e` -> `e9e09d9`, and the
phrase canary re-pinned with it.** Folded in at zero marginal cost: the
snapshot is hashed into `base_tag`, but `Dockerfile.base` already changed this
release, so the ~67 min base rebuild was already being paid. `--check` reported
exit 0 (stale-but-truthful) beforehand, i.e. skipping was sanctioned — this is
the deliberate decision the checklist asks for, not a drive-by. Upstream content
is the fleet wing-naming convention (bare project names, no `wing_` prefix) and
the `<harness>@<device>` rule for `added_by`, both of which came out of the
attribution defect measured on this device on 2026-09-06.
The canary re-pin is the interesting half. Its old pair — "Provenance is
stamped for you" present, "Attribute what you file yourself" absent — STILL
PASSED against the new snapshot, so leaving it in place would have produced a
canary that is green on both the old and the new bytes: blind to precisely the
refresh it exists to witness, which is the same false-green family the
pre-v1.8.5 canary died of. The replacement pair was picked by MEASURING
direction against both files rather than by reading the diff ("Diaries
self-heal; plain drawers do not" new=1/old=0; "Agent diaries live in"
new=0/old=1) and then tested two-sided: PASS on the refreshed bytes, FAIL on the
old bytes recovered from git. A canary that cannot fail is decoration.
**`credential-incident-response` §5/§6 corrected — a stated mechanism was wrong,
and this is the second time in three days this section named a wrong reason
for a zero.** Docs only.
+1 -1
View File
@@ -378,7 +378,7 @@ ARG MEMPALACE_TOOLKIT_REF=main
# no ~67-minute base rebuild. (scripts/check-base-hash.sh scans only
# Dockerfile.base, so no folding into the base hash is required — nor would
# it be correct, since this ARG changes nothing about the base's contents.)
ARG SKILLSET_SNAPSHOT_REF=a12fe5ecc71e60feb24791e3e33571105f1afba7
ARG SKILLSET_SNAPSHOT_REF=e9e09d95f92670536a199fc986dfa24d787f18d1
# Dockerfile.base sets description="pi-devbox — base image (variant-independent)"
# and every variant INHERITS it, so both published images used to advertise
@@ -587,9 +587,31 @@ Two consequences worth internalising:
### Wings
Wings are top-level categories, typically one per project or domain:
- Named after the project directory (e.g., `cli_utils`, `opencode_devbox`)
- Agent diaries live in `wing_<agent_name>` (e.g., `wing_orchestrator`, `wing_pi`)
Wings are top-level categories, typically one per project or domain.
**NAMING CONVENTION — decided 2026-09-06 by Joakim: bare project names, no `wing_`
prefix.** `home-network`, `pi-devbox`, `mempalace-toolkit` — *not* `wing_pi-devbox`. The
mass is already there (`pi-devbox` 2061 drawers vs `wing_pi-devbox` 25), and a prefix
present on some wings and absent on others turns every read into a guess about which
spelling holds the content.
- Named after the project directory or domain (e.g., `cli_utils`, `home-network`)
- **Always pass `wing` explicitly to `diary_write`.** Omitting it defaults to
`wing_{agent_name}`, which mints or feeds a *parallel* wing — this tool default, not
anyone's sloppiness, is the mechanism that produced the drift. Measured harm
(2026-09-06, `pi@mbp-m1-2020`): a diary entry written with `agent_name=pi` and no
`wing` landed in `wing_pi` while that agent's history lives in `pi-devbox`, so a
`diary_read` scoped to `pi-devbox` showed **no trace of it**. A wing-scoped read that
silently returns an incomplete history is the worst failure mode a memory store has.
- **Legacy `wing_*` wings are frozen and documented, not renamed.** `wing_conversations`
(written by the session feeders), `wing_pi`, `wing_pi-devbox`, `wing_pi-tor-ms22`,
`wing_pi-devbox-emb7kj`, `wing_mempalace`, `wing_orchestrator`, `wing_code` all still
hold real content. **When searching for history, check both spellings** — this is the
practical cost of the drift and it does not go away by decree.
- If a migration is ever done, the acceptance criterion must be at the **relationship**
level: chunk ids still resolve to their parent, and `diary_read` returns the same entry
set before and after. Per-wing drawer counts can look correct while the relationships
underneath are broken, because a count query never touches them.
#### Shared palace: multiple harnesses, and possibly multiple machines
@@ -609,7 +631,7 @@ Zechner's pi-coding-agent). Implications:
When the palace is **central** (shared across machines), these further things apply:
- **Check which machine a conversation came from.** Transcripts are fed per device, so `source_path` reads `…/mempalace-feed/<device>/pi_<uuid>.jsonl` while the displayed `source_file` is only the basename. One search can legitimately return hits from several machines at once — look at the device segment before attributing a decision to *this* project.
- **Provenance is stamped for you — leave it alone.** Drawers carry `device` and `agent_kind` metadata (plus `device_source`/`agent_kind_source` recording *how* each was determined, so an inference is never mistaken for a fact). You do **not** set these, and you no longer set `added_by` either: the pi bridge defaults the writer field to `<harness>@<device>` on `add_drawer`/`checkpoint`/`mine`/`event_append`/`artifact_put`, and prefixes diary entries with `HOST:<device>|`, from host-supplied `$MEMPALACE_PI_DEVICE`. RFC 001 §7.3.2 ranks "agent stamps it via a skill instruction" as the *worst possible* place for exactly the reason you would expect — it is per-call boilerplate that gets forgotten, and it did: the agent who wrote the previous version of this bullet then filed its own provenance drawer as `added_by=checkpoint`. **Confirm the bridge in your image actually stamps before trusting it:** the extension is baked at image build time, so a container on an image older than the stamping commit (pi-devbox < v1.8.7) stamps nothing while still satisfying both gates — the env vars are set and the code is simply absent. Check with `grep -c MEMPALACE_PI_DEVICE "$(readlink -f ~/.pi/agent/extensions/mempalace.ts)"`; zero means keep passing `added_by="<harness>@<device>"` and a manual `HOST:<device>|` diary prefix until the container is recreated on a newer image. Two things remain yours: pass `source_drawer_id` on `kg_add` (triples have no provenance field, so that pointer is the only path back to a device), and pass an explicit `added_by` **only** when deliberately filing on behalf of another device. Never invent values for `device`/`agent_kind`/`origin_device` — a fabricated value is worse than a blank, because it silently corrupts a future merge.
- **Provenance is stamped for you — leave it alone.** Drawers carry `device` and `agent_kind` metadata (plus `device_source`/`agent_kind_source` recording *how* each was determined, so an inference is never mistaken for a fact). You do **not** set these, and you no longer set `added_by` either: the pi bridge defaults the writer field to `<harness>@<device>` on `add_drawer`/`checkpoint`/`mine`/`event_append`/`artifact_put`, and prefixes diary entries with `HOST:<device>|`, from host-supplied `$MEMPALACE_PI_DEVICE`. RFC 001 §7.3.2 ranks "agent stamps it via a skill instruction" as the *worst possible* place for exactly the reason you would expect — it is per-call boilerplate that gets forgotten, and it did: the agent who wrote the previous version of this bullet then filed its own provenance drawer as `added_by=checkpoint`. **Confirm the bridge in your image actually stamps before trusting it:** the extension is baked at image build time, so a container on an image older than the stamping commit (pi-devbox < v1.8.7) stamps nothing while still satisfying both gates — the env vars are set and the code is simply absent. Check with `grep -c MEMPALACE_PI_DEVICE "$(readlink -f ~/.pi/agent/extensions/mempalace.ts)"`; zero means keep passing `added_by="<harness>@<device>"` and a manual `HOST:<device>|` diary prefix until the container is recreated on a newer image. Two things remain yours: pass `source_drawer_id` on `kg_add` (triples have no provenance field, so that pointer is the only path back to a device), and pass an explicit `added_by` **only** when deliberately filing on behalf of another device — and when you do, it **must** be `<harness>@<device>`. A bare nickname (`pi-devbox-claude`) has no `@device` to parse, so `agent_at_device` cannot attribute it and the drawer is unattributable *by rule*, not by lag: it survives every future stamp run with no `device`, and on a shared palace a device-less drawer is one nobody can later scope, audit or clean up per machine. Measured 2026-09-06: 11 drawers on `tor-ms22` were filed this way — including the credential rows, i.e. exactly where "which machine measured this?" matters most — by an agent that had passed its own chosen nickname on every call. Its *diary* entries escaped, because `HOST:<device>|` in the AAAK text recovers the device. **Diaries self-heal; plain drawers do not.** The safest habit is the one above: pass nothing and let the bridge stamp. Never invent values for `device`/`agent_kind`/`origin_device` — a fabricated value is worse than a blank, because it silently corrupts a future merge.
- **Metadata is invisible to search — so check the text, not the fields.** `search` results are built from a fixed key list and `diary_read` returns content, so neither ever shows `device`/`added_by`. Only `mempalace_get_drawer` reveals them. This is why diary entries carry an in-text `HOST:<device>` marker: it is the only attribution a reader actually sees. **A diary entry with no `HOST:` marker predates the convention and may be from any machine — do not assume it is this one's history.**
- **Mined drawers carry the MINE date, not the session date.** When history is imported, or re-mined on the palace host, `filed_at`/`created_at` is the *import* time — so sorting by them does not give chronological order. Real session time is recoverable from the UUIDv7 in `pi_<uuid>.jsonl`: the first 12 hex digits are milliseconds since the epoch (and UUIDv7 sorts lexicographically in time order, so a plain filename sort is already chronological). Agent-authored drawers and diaries have no such backdoor — for those `filed_at` is the only chronology, which is why it must never be restamped.
- **Beware the timezone mismatch when you combine those.** Palace `filed_at`/`created_at` are naive timestamps in the palace host's local time, while a UUIDv7 decodes to UTC. Comparing them directly introduces a silent offset (2 h for a CEST host). Normalise before drawing conclusions about ordering.
+15 -1
View File
@@ -596,7 +596,21 @@ exec_test "mempalace skill linked (fallback)" 'test -L $HOME/.agents/skills
# This assertion is kept because it is orthogonal and free: it pins content,
# not provenance, so it still catches a re-vendored snapshot whose ref was
# bumped correctly but whose bytes came from the wrong place.
exec_test "mempalace skill snapshot is current" 'f=$HOME/.agents/skills/mempalace/SKILL.md; grep -q "Provenance is stamped for you" "$f" && ! grep -q "Attribute what you file yourself" "$f" && echo ok'
#
# v1.8.13: RE-PINNED on refresh a12fe5e -> e9e09d9, which is the whole point of
# the mechanism — the previous pair ("Provenance is stamped for you" present /
# "Attribute what you file yourself" absent) still passed against the NEW
# snapshot, so leaving it would have produced a canary that is green on both the
# old and the new bytes, i.e. blind to precisely the refresh it exists to
# witness. Same false-green family as the pre-v1.8.5 canary this comment warns
# about. The replacement pair was chosen by MEASURING direction against both
# files rather than by reading the diff: "Diaries self-heal; plain drawers do
# not" is new=1/old=0, "Agent diaries live in" is new=0/old=1 — so each string
# discriminates on its own and the pair still fails loudly in BOTH directions
# (forgotten bump AND re-vendored stale snapshot). Upstream content behind this
# refresh: the bare project-name wing convention and the <harness>@<device>
# added_by rule.
exec_test "mempalace skill snapshot is current" 'f=$HOME/.agents/skills/mempalace/SKILL.md; grep -q "Diaries self-heal; plain drawers do not" "$f" && ! grep -q "Agent diaries live in" "$f" && echo ok'
# 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)" \