Reverses the previous §7.3 on review. It said "stamp added_by everywhere,
now, because it cannot be backfilled" and was about to become a skill
instruction telling agents to do it. Both halves were wrong.
Wrong on ownership: provenance answers "which device asserted this?", so
only a party that can verify the answer should write it. An agent must shell
out to read env, can forget, and will improvise when the values are absent —
the worst possible stamper, and its claim is unverifiable by anyone. Under
the §4 design every write reaches the primary over an authenticated channel,
including offline ones at outbox-flush time, so the primary can stamp the
complete set with no client cooperation. Provenance is a property of the sync
channel, not of the record's author. §7.3.2 adds the trust ladder; edge-side
stamping is demoted to an advisory interim, because 3.6.0's serve takes a
single shared bearer token (mcp_server.py:5291-5293) and the package has zero
device/origin concept, so authoritative stamping needs the per-device
credentials of §6 — Phase 4, not Phase 0.
Wrong on backfill: a solitary devbox is a single-origin store by definition,
so origin is a property of the whole palace and can be assigned wholesale at
import (one --origin-device flag) at the moment it stops being solitary. Bulk
attribution is strictly more reliable than per-record stamping since it
cannot be partially applied. Per-record provenance is only needed where
origins interleave, which is only the primary. Solitary containers therefore
stamp nothing and lose nothing — more R1-compliant than the previous draft,
which quietly asked users who had opted out to carry metadata for the
feature. Multi-harness-on-one-host stays solved by added_by = agent name.
Keeps the verified mechanics (fixed metadata schema, argument whitelisting at
mcp_server.py:4777 silently dropping unknown fields, the diary agent_name →
wing_pi@host trap, kg_add having no provenance slot, added_by absent from
search results) and the identity findings (a container cannot discover its
host's identity; hostnames are neither unique nor stable; rename splits one
device's history in two). §7.3.5 keeps the fail-closed rule for whichever
component does stamp.
Phase 0 drops its provenance item accordingly, and §6.2 now specifies
server-side stamping from the authenticated credential.
Also flags in-document that these notes are a poisoning vector: a future
agent reading them out of the palace must not conclude it should hand-stamp.
Adds §1.1 (R1–R6) and §1.2 as a hard constraint on the design rather than a
preference. A shared palace is valuable only for the multi-machine /
multi-container pattern; for most users of the published pi-devbox and
opencode-devbox images it is useless overhead. Evidence: all three sampled
opencode-devbox deployments contain zero mempalace references.
Requirements: solitary operation stays the default and stays byte-identical
(no extra process, no outbox, no network calls); opt-in via docker-compose.yml
+ .env only, never an image rebuild; credentials only in .env, never in
compose, the image, a command line, or a log; no new required services (the
primary stays a separate standalone compose project); degrade-not-fail where
mempalace is absent; fully reversible.
§1.2 extends the convention pi-devbox already ships (.env.example:12-23,
"local by default" with commented MEMPALACE_REMOTE_URL/_TOKEN) into a
three-state ladder — local stdio (default, unchanged) / direct remote
(unchanged) / edge (new, MEMPALACE_EDGE=1) — instead of inventing a new
mechanism. Notes that the devbox-palace volume coupling reverses under edge
mode: the local palace holds the outbox, so persisting it becomes required
rather than irrelevant.
Consequence recorded in §4.1: mempalace-edge must be selected at registration
time, not left always-in-path to decide by env at runtime, since that would
insert a process and a failure mode into every solitary user's setup. pi
branches in createClient(); opencode needs its static MCP JSON templated at
container start, which is new open question §9.6.
Also: adds an R1 acceptance test, marks pi-devbox/.env.example:21 as stale
(advertises mempalace-mcp --transport http rather than mempalace serve
--token/--tls-cert), and extends the evidence index.
Design for moving from one MemPalace per machine per harness to a single
primary palace with per-machine offline fallback, plus the source-verified
archaeology behind it.
Key decisions recorded:
- Sync operations (MCP tool calls), not databases. Embeddings are computed
client-side and are not portable across architectures; the KG `triples`
table has no UNIQUE(subject,predicate,object,valid_from) and its ids embed
datetime.now(), so row copies duplicate facts. Replaying tool calls is
idempotent where it matters.
- Implement as `mempalace-edge`, a local stdio MCP proxy (child mempalace-mcp
+ HTTPS to the primary + outbox.sqlite), not as per-harness patches. Needs
zero mempalace internals, so it serves pi, opencode and the CLI alike and
survives mempalace upgrades.
- Merged reads (query both, re-sort, dedupe by drawer id) give fleet-wide
recall without replication — which is why Phase 3 (pull replication) is
deferred: "own writes plus whatever it can reach" is good enough.
- Per-wing replication policy: curated/content-addressed wings replicate,
mined code/docs stay local (derived, re-mineable, path-dependent ids).
Also documents two silently destructive footguns to avoid during rollout:
`--palace` vs MEMPALACE_PALACE_PATH (the KG follows the flag only, so
`mempalace serve` can start with a silently empty knowledge graph), and
`mempalace sync`, which is gitignore-aware drawer deletion rather than
replication and would wipe fleet memory when run from a host lacking the
repos. Notes that mempalace 3.6.0's `serve` already ships token auth + TLS,
making the "unauthenticated, front it with a proxy" notes elsewhere stale.
Includes an evidence index mapping each claim to file:line in mempalace
3.6.0, and four upstream candidates (origin_host provenance, per-wing ACL,
mempalace_kg_supersede missing from service.py WRITE_TOOLS, and a
sync --refuse-shared guard).