Two related gaps in LIFEOS/TOOLS/MemoryReviewer.ts, both measured on a live install over 237 reviewer runs that left a prompt.user.md on disk.
1. The consolidation target is never enforced
buildReviewerUserPrompt tells the model ≥80% FULL, CONSOLIDATE BEFORE ADDING from 39 entries (renderCurrentMemory), and the prompt body repeats CONSOLIDATE FIRST. Neither is validated. The only limit the code actually enforces is ≤48.
The model does what is enforced. On this install the last four successful runs returned 47, 47, 48 and 48 entries with both actors sitting at 48/48. It swaps one-for-one at the cap and never merges. That state held for eleven days, including after #1905 was addressed locally, so the erosion guard was no longer the blocker: the door was open and nothing walked through it.
This matters because the file already contains the counter-example. The corrective-retry comment says a re-prompt carrying the exact validator error "fixes what prompt-side warnings alone demonstrably did not" (2026-08-03 incident, entry size). Same lesson, different constraint.
2. The exchange block is bounded by count, never by bytes
buildExchanges caps each message at MAX_MSG_CHARS = 2000 and returns exchanges.slice(-maxExchanges). Nothing bounds the total, and the memory block in front of it is unbounded too, so the prompt can reach any size.
Failure rate against prompt size, same install, window starting when the timeout was raised to 300s:
| prompt.user.md |
runs |
failed |
rate |
| 20-30 KB |
12 |
0 |
0% |
| 30-40 KB |
15 |
4 |
27% |
| 60-70 KB |
3 |
2 |
67% |
Overall 7 of 34 = 20.6% of curation runs lost. In the largest failure the two memory blocks were 21,887 of 34,102 characters, 64% of the prompt, both actors at 48/48 with the consolidation flag lit, which is also the most expensive reasoning path the reviewer has.
Raising the wall has been tried three times (120s → 240s → 300s, the last via #1907) and the rate did not move, because a bigger wall does not make the work smaller.
Suggested shape
- Validate the target under cap pressure. When an actor is at or above the same 39 used by the prompt and by
isConsolidation, reject an op:"set" that returns more than max(36, prior - 3) and let the existing corrective retry carry the message. A short step avoids asking for a twelve-entry merge in one pass, which is exactly when the 256-char cap breaks. The floor sits 3 below the trigger so the rule goes dormant once satisfied, and 6 above CONSOLIDATION_FLOOR = 30 so it can never push toward erosion.
- Budget the prompt by bytes, adaptively. Give the exchange block whatever a total target leaves after the memory block, dropping oldest exchanges first and always keeping the two most recent. A fatter memory then costs exchanges instead of pushing the total past the wall.
Happy to send a PR if the shape is agreeable.
Two related gaps in
LIFEOS/TOOLS/MemoryReviewer.ts, both measured on a live install over 237 reviewer runs that left aprompt.user.mdon disk.1. The consolidation target is never enforced
buildReviewerUserPrompttells the model≥80% FULL, CONSOLIDATE BEFORE ADDINGfrom 39 entries (renderCurrentMemory), and the prompt body repeatsCONSOLIDATE FIRST. Neither is validated. The only limit the code actually enforces is≤48.The model does what is enforced. On this install the last four successful runs returned 47, 47, 48 and 48 entries with both actors sitting at 48/48. It swaps one-for-one at the cap and never merges. That state held for eleven days, including after #1905 was addressed locally, so the erosion guard was no longer the blocker: the door was open and nothing walked through it.
This matters because the file already contains the counter-example. The corrective-retry comment says a re-prompt carrying the exact validator error "fixes what prompt-side warnings alone demonstrably did not" (2026-08-03 incident, entry size). Same lesson, different constraint.
2. The exchange block is bounded by count, never by bytes
buildExchangescaps each message atMAX_MSG_CHARS = 2000and returnsexchanges.slice(-maxExchanges). Nothing bounds the total, and the memory block in front of it is unbounded too, so the prompt can reach any size.Failure rate against prompt size, same install, window starting when the timeout was raised to 300s:
Overall 7 of 34 = 20.6% of curation runs lost. In the largest failure the two memory blocks were 21,887 of 34,102 characters, 64% of the prompt, both actors at 48/48 with the consolidation flag lit, which is also the most expensive reasoning path the reviewer has.
Raising the wall has been tried three times (120s → 240s → 300s, the last via #1907) and the rate did not move, because a bigger wall does not make the work smaller.
Suggested shape
isConsolidation, reject anop:"set"that returns more thanmax(36, prior - 3)and let the existing corrective retry carry the message. A short step avoids asking for a twelve-entry merge in one pass, which is exactly when the 256-char cap breaks. The floor sits 3 below the trigger so the rule goes dormant once satisfied, and 6 aboveCONSOLIDATION_FLOOR = 30so it can never push toward erosion.Happy to send a PR if the shape is agreeable.