skills: refresh vendored mempalace snapshot a12fe5e -> e9e09d9, re-pin the canary
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:
@@ -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
|
unguarded state, so a "truthy or not" test would pass on exactly the
|
||||||
configuration being guarded against.
|
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,
|
**`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
|
and this is the second time in three days this section named a wrong reason
|
||||||
for a zero.** Docs only.
|
for a zero.** Docs only.
|
||||||
|
|||||||
+1
-1
@@ -378,7 +378,7 @@ ARG MEMPALACE_TOOLKIT_REF=main
|
|||||||
# no ~67-minute base rebuild. (scripts/check-base-hash.sh scans only
|
# 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
|
# 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.)
|
# 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)"
|
# Dockerfile.base sets description="pi-devbox — base image (variant-independent)"
|
||||||
# and every variant INHERITS it, so both published images used to advertise
|
# and every variant INHERITS it, so both published images used to advertise
|
||||||
|
|||||||
@@ -587,9 +587,31 @@ Two consequences worth internalising:
|
|||||||
|
|
||||||
### Wings
|
### Wings
|
||||||
|
|
||||||
Wings are top-level categories, typically one per project or domain:
|
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`)
|
**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
|
#### 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:
|
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.
|
- **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.**
|
- **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.
|
- **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.
|
- **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
@@ -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,
|
# 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
|
# not provenance, so it still catches a re-vendored snapshot whose ref was
|
||||||
# bumped correctly but whose bytes came from the wrong place.
|
# 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
|
# Link TARGETS, not just link existence: with no skillset mounted (as here) the
|
||||||
# baked tree must be what resolves, for all four vendored skills.
|
# baked tree must be what resolves, for all four vendored skills.
|
||||||
exec_test "vendored skills resolve to the baked tree (no skillset mounted)" \
|
exec_test "vendored skills resolve to the baked tree (no skillset mounted)" \
|
||||||
|
|||||||
Reference in New Issue
Block a user