Repository navigation
Description
scripts/check-memory-index.py measures an index in three directions: its line count, its character budget, and each row's length and links. The third resolves a row's ](target) against the directory beside the index, so a row pointing at a file that is gone is reported.
Nothing measures the opposite direction — a detail file that no row names. An entry can be written and never indexed, and the guard still prints OK with exit code 0.
Observed
2026-11-10, this host's agent memory store (~/.emrg/evolution/emrg/.emrg/memory/): a detail file was written at 17:52 with status: active. Running uv run --no-sync python3 scripts/check-memory-index.py .emrg/memory/MEMORY.md two minutes later printed
lines 88 of 100 - within
chars 40323 of 52224 - within
rows 81, longest 512 chars, over 512: 0
row links 102, unresolved: 0
OK: 1 index(es) within the three numbers the rule names ...
and exited 0, while the store held 97 files declaring status: active against 82 rows. One active entry was named nowhere in the index. The same reading over the whole directory: of the four active files carrying no ](target) link, three are still followed by a reader because a row names them inside an inline code span; exactly one was named by nothing at all.
Expected
emrg/server/prompts/system.j2:61 states the rule — "A write is two steps: the file, then its row in the index" — and emrg/server/evolution_prompt.md repeats it under R9. A rule the prompt states and no reading takes is prose that decays, and this failure is silent in the direction that matters: the file exists, the work was done, and the fact is unreachable, because the index is the only map of the store.
What the fix must satisfy
- A fourth reading in
scripts/check-memory-index.py: every detail file beside the index whose frontmatter saysstatus: activeis named somewhere in the index. The directory is the one the existing resolution reading already assumes, since rows name their siblings. - Named, not linked: a link and an inline code span both count. R9's compaction protocol requires a merged row to "still name every id and file that line replaces", and this host's index does exactly that inside backticks — so a link-only predicate would report a shape the rule prescribes.
- A file whose status is
merged,superseded, or absent is out of scope: the index lists active entries only, so retiring an entry is a legitimate way to leave it unnamed. - Exit 1 when an active detail file is named nowhere, with a line naming the file and the remedy. Exit 0 keeps its meaning only where the reading had a subject: an index with no active detail file beside it says so — in the per-index report and in the summary line — rather than closing with a pass it did not take.
- The docstring's exit codes, its scope section and its argparse description and epilog carry the new reading. A reading added without them is one a later reader cannot see, which is the defect this tool exists to prevent one level down.
- Tests in both directions, with mutation arms: a file named by a row (clean) · a file named nowhere (exit 1, naming it) · a file named only inside a code span (clean) · a file with
status: merged(clean) · an index with no detail file beside it (no subject, exit 0).
Not in scope
- Repairing any store's existing index. A row summarises its detail file's content, which is the agent's judgement, not a reading.
- Any change to what
emrg/memory.pywrites, or to the store's own thresholds.
Activity
argszero commentedon Nov 10, 2026
Handled by #2798