The shrink guard in `LIFEOS/TOOLS/MemoryWriter.ts` only catches catastrophes. The failure mode that actually destroys memory in practice is slow erosion through LLM re-transcription, and it passes the guard cleanly every time.
What happens
The reviewer rewrites the memory list each cycle. When it re-transcribes rather than edits, a few entries quietly fail to come back. Measured on a live install over two days:
| Before |
After |
Net |
Additions present? |
Guard verdict |
| 41 |
37 |
-4 |
yes |
passed |
| 39 |
37 |
-2 |
yes |
passed |
| 40 |
37 |
-3 |
yes |
passed |
Each drop is ~10% and each carries additions, so none trips "nearly empty" or "more than 50% dropped with no additions". Twelve durable rules died in 48 hours and nothing surfaced. Several were behavioural rules the system depends on (do not claim completion without verification; re-verify a remembered state before asserting it).
The guard is doing exactly what it was written to do. The problem is that the modelled failure (a catastrophic wipe) is not the failure that occurs.
Suggested fix
Add a net-loss condition alongside the existing ones:
const netLoss = evictions.length - additions.length;
const erosion = netLoss >= EROSION_LIMIT && !consolidating; // EROSION_LIMIT = 2
with an explicit allowDrastic escape for deliberate consolidation, and an error message that names it so the caller knows the way out. A legitimate 5→2 consolidation should be possible; it just should not be the silent default.
The non-obvious half, which cost me the most time
The rejection has to be written to the write-log, not just returned. The hook spawns the reviewer with stdio: "ignore", so a rejection that only travels back in the summary object goes to /dev/null and the block is invisible to the surface that is supposed to report it. I traced the rejection into summary.failures, called it done, and only later found that summary was never read by anything.
Validation
Replayed against 94 historical writes from a real install: 5 blocked, all 5 genuine erosion (including the one that removed five honesty rules in a single pass), 0 false positives.
Happy to open a PR with the diff if useful.
The shrink guard in `LIFEOS/TOOLS/MemoryWriter.ts` only catches catastrophes. The failure mode that actually destroys memory in practice is slow erosion through LLM re-transcription, and it passes the guard cleanly every time.
What happens
The reviewer rewrites the memory list each cycle. When it re-transcribes rather than edits, a few entries quietly fail to come back. Measured on a live install over two days:
Each drop is ~10% and each carries additions, so none trips "nearly empty" or "more than 50% dropped with no additions". Twelve durable rules died in 48 hours and nothing surfaced. Several were behavioural rules the system depends on (do not claim completion without verification; re-verify a remembered state before asserting it).
The guard is doing exactly what it was written to do. The problem is that the modelled failure (a catastrophic wipe) is not the failure that occurs.
Suggested fix
Add a net-loss condition alongside the existing ones:
with an explicit
allowDrasticescape for deliberate consolidation, and an error message that names it so the caller knows the way out. A legitimate 5→2 consolidation should be possible; it just should not be the silent default.The non-obvious half, which cost me the most time
The rejection has to be written to the write-log, not just returned. The hook spawns the reviewer with
stdio: "ignore", so a rejection that only travels back in the summary object goes to/dev/nulland the block is invisible to the surface that is supposed to report it. I traced the rejection intosummary.failures, called it done, and only later found that summary was never read by anything.Validation
Replayed against 94 historical writes from a real install: 5 blocked, all 5 genuine erosion (including the one that removed five honesty rules in a single pass), 0 false positives.
Happy to open a PR with the diff if useful.