docs: the edge stamper and the coordination log the fleet actually uses

Two gaps, both found by using the thing rather than reading it.

PROVENANCE WAS SHIPPED UNDOCUMENTED. 553d8657 moved device attribution to the
edge — writer stamped on add_drawer/checkpoint/mine/event_append/artifact_put,
HOST:<device>| prefixed on diary entries — and this README, the file that
documents the extension, never mentioned it. So the only description of the
behaviour lived in the consumer skill, i.e. in the place the code does NOT live.
Now recorded next to Identity, with the two design points that keep getting
re-litigated: the diary marker is in the entry TEXT because diary_read returns
content only (an attribution nobody can see is not an attribution), and RFC 001
§7.3.2 ranks agent-side stamping worst — demonstrated when the agent that wrote
that skill instruction filed its own provenance drawer as added_by=checkpoint.
Includes the version gate that matters in practice: an older image satisfies both
env gates and still stamps nothing, because the extension is baked.

COORDINATION WAS UNDOCUMENTED ANYWHERE. The fleet has used the RFC 003 logstream
for real work since 2026-08-18 (patch handoff, review, a v1->v2 supersede) and no
file in this repo said so. Three facts belong here because they are mechanism:

- The stamper is what makes directed addressing possible. Where every machine is
  a thin MCP client of one shared palace, all clients report the SAME
  origin_replica, so from_agent/to_agent carry the entire distinction between
  machines. Measured 2026-08-26: mesh_peers returns peers: [] with a single
  replica id authoring every event from every machine. An unstamped client
  addressed as bare "pi" is unreachable.
- The bridge is WRITE-ONLY today: it stamps events going out and never reads the
  log. An event addressed to this machine by name reaches the agent only if the
  agent queries for it. Stated plainly because it is the current weak point, and
  it is exactly how a retraction addressed to pi@tor-ms22 sat unread while that
  agent rebuilt the thing it warned about.
- Live push is a DEPLOYMENT question. The palace implements SSE
  (GET /logstream/stream, text/event-stream in mcp_server.py) but a deployment
  may expose only /mcp: verified against mempalace.jordbo.se, where
  /logstream/events, /logstream/stream and /sync/peers all 404 while /mcp serves.
  Enabling it is a proxy route plus an auth decision, not an extension change.

Division of labour made explicit rather than implied: this file documents the
MECHANISM, the consumer skill is NORMATIVE for behaviour. Duplicating the ack
contract here would guarantee two copies that disagree.
This commit is contained in:
2026-08-26 12:49:19 +02:00
parent 553d86570c
commit e70bef2b5d
+80
View File
@@ -53,6 +53,10 @@ dependencies (~300 MB).
because it needs the LLM to compose the entry, and `session_shutdown` because it needs the LLM to compose the entry, and `session_shutdown`
fires too late to drive another LLM turn — a constraint that applies to fires too late to drive another LLM turn — a constraint that applies to
the diary specifically, not to feeding (see above). the diary specifically, not to feeding (see above).
6. **Stamps device provenance on every write** — `<harness>@<device>` as the
writer on `add_drawer`/`checkpoint`/`mine`/`event_append`/`artifact_put`, and
a `HOST:<device>|` prefix on diary entries — as of mempalace-toolkit
`553d8657` (2026-08-25). See [Identity](#identity).
## Automatic transcript feeding ## Automatic transcript feeding
@@ -242,6 +246,82 @@ to `"pi"`. First diary write against that identity creates `wing_<name>`
in the palace. Set the env var if you want to run pi under a distinct in the palace. Set the env var if you want to run pi under a distinct
identity on a given machine (e.g. `pi-laptop` vs `pi-server`). identity on a given machine (e.g. `pi-laptop` vs `pi-server`).
### Device provenance is stamped here, at the edge
As of mempalace-toolkit `553d8657` (2026-08-25) the bridge fills in *who wrote
this* so the agent never has to:
| Write | What the bridge sets |
|---|---|
| `add_drawer`, `checkpoint`, `mine` | `added_by = "<harness>@<device>"` when the caller left it unset |
| `event_append`, `artifact_put` | `from_agent` / `created_by` likewise |
| `diary_write` | prefixes the entry text with `HOST:<device>\|` |
`<device>` is `$MEMPALACE_PI_DEVICE`; `<harness>` is `pi`. **Both gates must
hold:** `MEMPALACE_PI_DEVICE` set *and* `MEMPALACE_REMOTE_URL` pointing at a
shared palace. A solitary palace stamps nothing, because there is no second
machine to disambiguate from and the annotation would be pure noise.
Two design points worth not re-litigating:
- **The diary marker is in the entry text, not metadata.** `diary_read` returns
content only, so metadata is invisible to the agent that later reads the
entry — an attribution nobody can see is not an attribution. Search results
are built from a fixed key list with the same consequence.
- **RFC 001 §7.3.2 ranks "the agent stamps it via a skill instruction" as the
worst available option**, and it was: the agent that wrote that instruction
into the consumer skill then filed its own provenance drawer as
`added_by=checkpoint`. Per-call boilerplate gets forgotten. Hence the edge.
Callers keep two responsibilities the bridge cannot infer: pass
`source_drawer_id` on `kg_add` (triples have no provenance field at all), and
pass an explicit writer **only** when deliberately filing on behalf of another
device.
Because the extension is baked into an image, *a container older than the
stamping commit satisfies both gates and still stamps nothing* — the env vars
are set and the code is simply absent. The one-line check:
```bash
grep -c MEMPALACE_PI_DEVICE "$(readlink -f ~/.pi/agent/extensions/mempalace.ts)"
```
## Agent coordination over the logstream
The palace also carries an append-only coordination log (RFC 003:
`mempalace_event_*`, `mempalace_artifact_*`) used for cross-machine delegation,
review, patch handoff and retraction. Relevant to this extension in three ways:
**1. The bridge makes directed addressing possible.** Where every machine is a
thin MCP client of one shared palace, all clients report the *same*
`origin_replica`, so the log cannot tell two machines apart by transport
identity — `from_agent` / `to_agent` carry the entire distinction. Measured
2026-08-26 from the tor-ms22 client: `mempalace_mesh_peers` returned
`peers: []` with a single replica id authoring every event from every machine.
So the stamping above is what makes `to_agent="pi@tor-ms22"` mean anything, and
an *unstamped* client addressed as bare `pi` is unreachable.
**2. The bridge is write-only today.** It stamps events on the way out and never
reads the log: there is no mailbox, no poll, no delivery. An event addressed to
this machine by name reaches the agent only if the agent runs
`mempalace_event_list` itself — which is why that query is a wake-up step in the
*consumer* skill (`~/.agents/skills/mempalace/SKILL.md`), and why the protocol
norms live there rather than here. **This file documents the mechanism; the
skill is normative for behaviour.**
**3. Live push is a deployment question, not a code one.** The palace implements
an SSE endpoint (`GET /logstream/stream`, `text/event-stream` in
`mempalace/mcp_server.py`), but a deployment may expose only the MCP endpoint
through its reverse proxy — verified 2026-08-26 against
`https://mempalace.jordbo.se`, where `/logstream/events`, `/logstream/stream`
and `/sync/peers` all return 404 while `/mcp` serves normally. Where that is the
case, polling through the existing MCP client is the only available path, and
enabling SSE means a proxy route plus an auth decision — not an extension change.
As with stamping, all of this is inert unless `MEMPALACE_REMOTE_URL` points at a
shared palace. On a solitary palace the event tools work fine and the log
contains only this machine's own events.
## Stall protection (per-request timeout) ## Stall protection (per-request timeout)
Every JSON-RPC request to `mempalace-mcp` carries a timeout. Without it, a Every JSON-RPC request to `mempalace-mcp` carries a timeout. Without it, a