emem-referential-drift

Exact captured content · path plugins/emem/skills/emem-referential-drift/SKILL.md · sha256 71c982a977dc820b… · revision 9cbc309515f2fc194e7d68f0732e6b7ef5659b9a

Download the exact captured file

---
name: emem-referential-drift
description: Stops two agents, or one agent across two sessions, from reporting different numbers for the same reading. Pins a value to a citation token another party resolves to byte-identical signed bytes, grades a value about to be said against what was signed, asks why a number moved when the words held, and finds where the corpus disagrees with itself. Use when a figure is carried between contexts, when two sources report different values for one place, when a number changed and nobody can say what changed, or before publishing a number the agent did not read from a token in this turn.
---

# emem-referential-drift

> **Network use.** The commands in this skill send and receive JSON (and, where a step says so, an image or a raster file) to `https://emem.dev` only, the service the plugin's MCP server connects to. Nothing they download is executed. This skill ships no scripts. Its commands read no environment variable, key or credential, and the only local files they read are ones an earlier step saved from `https://emem.dev`.

Referential drift has two sides. **Words move**: "the north field",
"plot 14" and a cell64 turn out to be one object, or one phrase ends up
meaning two. **Values move**: a number is copied, rounded, paraphrased
and re-summarised until it arrives somewhere as a fact nobody can trace.

The words side is [`emem-shared-identity`](../emem-shared-identity/SKILL.md).
This skill is the values side.

Each call below is a tool of the plugin's MCP server. Without MCP, send
the same JSON body to the REST route the text names.
`emem_change_attribution` is outside the short tool list the server
shows. The server runs it by name through `tools/call`. If your host
calls only listed tools, use the REST route.

## Carry a token to the number, not the number

Every fact a recall returns already carries its citation in
`memory_token`. Take the one for the reading you mean; `current_by_band`
names the latest. Call `emem_recall` (REST
`POST https://emem.dev/v1/recall`) with:

```json
{"cell": "defi.zb493.zezo.zcb35", "bands": ["weather.temperature_2m"]}
```

Save the result's JSON text as `recall.json`, then pick the token:

```sh
jq -r '.current_by_band["weather.temperature_2m"] as $c
       | .facts[] | select(.fact_cid == $c) | .memory_token' recall.json
```

`facts[]` is ordered oldest first, so `.facts[0]` is the oldest reading,
not the current one. If you hold a cell and a fact cid from somewhere
else, `POST /v1/memory_token {"cell":..,"fact_cid":..,"band":..}` builds
the same token (field `memory_token`) and adds the band's provenance
block.

The token is `emem:fact:<cell64>:<fact_cid>`. Any agent resolves it to
the byte-identical signed object. Call `emem_memory_token_resolve`
(REST `POST https://emem.dev/v1/memory_token/resolve`) with:

```json
{"token": "emem:fact:defi.zb493.zezo.zcb35:nflpddk7zsncywguwjzk5koksseqfyx4jnngkuryrnd4aykqlpfq"}
```

Read `value_verbatim`, `unit`, `band` and `kind` in the result.

Recorded on 2026-09-28: `{"value_verbatim":"28.0","unit":"degC","band":"weather.temperature_2m","kind":"primary"}`.
Quote `value_verbatim`, the exact decimal string that was signed;
re-typing the JSON number is where precision gets lost.
`POST /v1/memory_token/resolve_many` resolves a list in one call.

## Grade yourself before you speak

`POST /v1/echo_verify` (MCP `emem_echo_verify`) compares the value you
are about to emit with the fact your token names. Call it with:

```json
{"token": "emem:fact:defi.zb493.zezo.zcb35:nflpddk7zsncywguwjzk5koksseqfyx4jnngkuryrnd4aykqlpfq", "claimed_value": 0.74}
```

Read `matches` and `drift` in the result.

Recorded on 2026-09-28: `claimed_value` 28.0 gave `matches: true`;
0.74 gave `matches: false, drift: "wrong"`.

This is the cheapest check here and the one a language model needs
most, because the failure it catches is one you cannot feel: you
paraphrased a number three turns ago and have carried the paraphrase
since. Run it on any figure you did not read in this turn from a
resolved token.

## When the number moved and the words did not

`POST /v1/change_attribution` (MCP `emem_change_attribution`) answers
"why is this readout different from last year's" as a per-term
**evidence ledger**:

- `terms.env`: label-free index pairs (NDVI, NBR, NDWI) with raw deltas
  and both fact cids.
- `terms.sensor`: what each visit was observed through, scene id per
  band, and whether the path changed between visits.
- `observed`: the year-over-year embedding change.

Call it with the cell, and read `observed` and `terms`:

```json
{"cell": "defi.zb493.zezo.zcb35"}
```

Expect `compute_timeout` on a cold cell: it materialises both visits
across several bands, which can pass the 40 s transport budget (it did
on 2026-09-28). Retrying the same call at once does not help. Recall the
cell's index bands first, then ask again once their background fetch has
landed. This tool does not take an MCP `task` param: only `emem_eudr_dds`
and `emem_hunt` run as tasks.

Two limits, both load-bearing:

- **There is no numeric split.** The ledger is evidence per term, not
  shares of the change. Do not report "60% environmental".
- **`observed` needs a retired band.** The embedding encoders are
  withdrawn on emem.dev, so `observed` exists only where a vector was
  stored. `terms.env` comes from Sentinel-2 indices and is unaffected;
  lean on it.

## Where the corpus disagrees with itself

`POST /v1/memory_contradictions` (MCP `emem_memory_contradictions`)
surfaces two or more independent attesters who signed **different
values for the same place, band and time**, with a 0 to 1 severity.
Call it with:

```json
{"cell": "defi.zb493.zezo.zcb35", "min_severity": 0.2, "limit": 20}
```

Read the number of entries in `contradictions`, `corpus_scanned` and
`scan_truncated`.

Every field is optional: `cell_prefix` scopes to a region, `band` to one
measurement, `window_unix_s` to a period. Read `scan_truncated` before
you report "none found".

A contradiction is not an error to hide. Report both signatures and who
signed them rather than picking a winner or averaging; a mean of two
disagreeing signatures is signed by neither.

## When you need to prove the ledger itself

If the question becomes "could this responder have shown someone else a
different history", use the transparency log:
[`emem-transparency-log`](../emem-transparency-log/SKILL.md).

## Pitfalls

- **Quoting a number without its token.** Once the number leaves with no
  handle, nothing downstream can detect drift.
- **Resolving once and paraphrasing after.** The paraphrase is the
  drift. Re-resolve, or quote `value_verbatim`.
- **Taking `facts[0]` as current.** It is the oldest reading.
- **Reading a split out of change_attribution.** It returns evidence per
  term, not shares.