diff --git a/CHANGELOG.md b/CHANGELOG.md index fb93e49..5bb7e78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 `@` 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. diff --git a/Dockerfile.variant b/Dockerfile.variant index cc57d09..7e9f8e4 100644 --- a/Dockerfile.variant +++ b/Dockerfile.variant @@ -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 diff --git a/rootfs/usr/local/share/pi-devbox/skills/mempalace/SKILL.md b/rootfs/usr/local/share/pi-devbox/skills/mempalace/SKILL.md index 192d93a..1c80ddc 100644 --- a/rootfs/usr/local/share/pi-devbox/skills/mempalace/SKILL.md +++ b/rootfs/usr/local/share/pi-devbox/skills/mempalace/SKILL.md @@ -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_` (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//pi_.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 `@` on `add_drawer`/`checkpoint`/`mine`/`event_append`/`artifact_put`, and prefixes diary entries with `HOST:|`, 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="@"` and a manual `HOST:|` 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 `@` on `add_drawer`/`checkpoint`/`mine`/`event_append`/`artifact_put`, and prefixes diary entries with `HOST:|`, 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="@"` and a manual `HOST:|` 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 `@`. 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:|` 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:` 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_.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. diff --git a/scripts/smoke-test.sh b/scripts/smoke-test.sh index 3d73807..5c91d61 100755 --- a/scripts/smoke-test.sh +++ b/scripts/smoke-test.sh @@ -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 @ +# 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)" \