AI memory management: from 30 KB to 11 KB without losing facts

✓ Verified on domain_state preview, 33-fact coverage check, domain tests and projection smoke tests · 2026-11-06

The fact was on disk. Twice. Our first winback draft included a customer segment that our current records did not support. That was the moment we stopped treating this as a copy problem and started treating it as a memory architecture problem.

We found 30,195 bytes in our central L2 file: 28 completed items, 28 migration pointers, and a legacy error log. We rebuilt it into an 11,228-byte view with 33 facts intact. This is what changed, what we measured, and why deleting was not the point.

Disclosure: Wallaby provides inference-as-a-service for curated large language models. This is a field note from the memory system we use for our own work, not a claim about customer outcomes.

The mistake was not missing memory

The relevant customer records were explicit: the proposed segment was not eligible for this campaign. Yet the first draft still had a "previously topped up, now inactive" segment. That segment did not match the people the message was meant to reach.

The correct state existed in two domain records. The draft had not used it. This was not merely stale copy. It was a retrieval and lifecycle problem: the right state was present, but the task that needed it had no reliable way to see it.

A better search engine might have found the right line. It would not have told us which line should still be visible, which domain owned it, or when it had expired.

The audit showed a role problem

The central file had grown to 179 lines and 30,195 bytes. The raw counts were useful because they showed that this was not simply a file with too many words.

Content Count Bytes Why it was there
Struck-through completed items 28 9,359 Kept after closure as if the active page were an archive
Migration pointers 28 4,059 Preserved after domain states moved, with repeated boilerplate
Legacy error summaries 12 lines 2,815 A second, older failure register
Overlong entries 13 lines — Process and validation details appended over time

The file was trying to be five things at once: a cross-domain baseline, a current work board, a domain-state copy, a history archive, and a routing index. It also held a sixth role, the old error register, which had its own proper home elsewhere.

This explained the bloat, but not the missed fact. For that, we had to look at the lifecycle.

Completed work was marked with a strike-through, not moved out of the current view. Migration to five domain READMEs had left behind repeated pointers. Our status checker still searched the old locations rather than all five domains. Dates told us when something had been written, not whether it remained true. And the old error summaries had never been formally retired.

The issue was not a lack of rules. The rules were spread across the system, but they did not form a usable page.

More search would have been the wrong fix

Our first temptation was to make retrieval smarter. That would have addressed the visible symptom and hidden the architecture problem.

We already had event logs, domain projections, an append-only write path, and a query command that returned JSON or one-line Markdown. We did not need another memory store, a vector index, or a new semantic query layer.

We needed four decisions instead:

  1. Cross-domain facts stay substantive. The current customer-segment state must be readable in the central page, not reduced to a pointer under an incident.
  2. Single-domain detail stays on demand. Domain READMEs and domain_state query are better places for implementation detail than a central copy.
  3. Completed work exits, it does not disappear. The latest shift remains visible. Older work moves to the log and the closed view rather than being deleted.
  4. The central page is for people and agents. A person should understand the current situation and next decision. An agent should get the same fact with a source reference and an event sequence number.

The central L2 became a shared current page for humans and AI, not a second task database and not a smaller copy of everything that had ever happened.

From a mixed memory file to a shared current page

We iterated on the projector we already had

The implementation reused domain_state.py, domains.json, the existing event writer, and the existing tests. The only new pieces were a composition entry point and a curated list of sections.

{
  "title": "Cross-domain business baseline",
  "items": [
    {"domain": "finance", "anchor": "revenue-baseline"},
    {"domain": "finance", "anchor": "pricing-current"}
  ]
}

That preview command assembled the anchors into readable Markdown. It did not write L2 or create a second source of truth. Missing anchors failed closed. valid_until exposed stale states. Closed items left the preview after one shift.

The first preview had three anchors: the revenue baseline, current pricing, and the current product homepage. That was enough to prove the mechanism. It was not enough to replace the page.

Next, we migrated the current focus, in-flight work, recently completed items, and account states. The expanded list covered 33 display anchors. The candidate and production files then matched byte for byte, which let us check the replacement instead of assuming it was faithful.

A few implementation details mattered more than the framework:

  • Existing long entries were shortened to a current conclusion plus an evidence pointer.
  • valid_until was exposed by the state query and checked by the preview.
  • Old section names used by l2_update remained valid, including "this week's sprint".
  • A rollback copy preserved the human-edited L2 before the switch.
  • The generated preview was never allowed to become a second hand-edited fact source.

Expected effects, measured effects

We wrote the acceptance criteria before calling the work complete. They were intentionally unglamorous.

Expected result Measured result
A smaller, readable L2 30,195 bytes became 11,228 bytes, a 62.8% reduction; 179 lines became 94, a 47.5% reduction
No lost facts The coverage script reported 33 of 33 required facts present, with zero missing
Readable by people and agents The preview returned human-readable Markdown with status, source reference and sequence number
Event-driven updates A test anchor moved from version one to version two, and the preview changed accordingly
Closed items exit The closed test anchor was visible in the current shift and absent when the clock advanced 25 hours
Stale states are visible valid_until=2000-01-01 produced an explicit STALE warning and a failed result
Recovery works Moving the date to 2999 restored ok=true with an empty warning list
Old write paths remain valid l2_update still accepted the "this week's sprint" section
A safe rollback The pre_full backup preserved the previous human-edited L2
A real replacement, not a preview The full candidate and production L2 had the same SHA-256 after replacement

The projection and freshness tests passed 35 of 35. The wider domain-state regression passed 51 of 51. The index tests passed 10 of 10, and the unified-write integration tests passed 5 of 5. After we fixed the false positive described below, the domain-state suite was 54 of 54.

These are not claims about memory accuracy or business results. They are claims about what the implementation and fixtures demonstrated.

Closeout and freshness lifecycle

The false positive was useful evidence

One remaining drift warning would have been easy to misread as a migration failure. It pointed at or-application in the growth README.

The current state was fine. The warning came from an older activation log containing or-application as its anchor. Our legacy-anchor scanner was treating an audit trail as if it were a duplicate current state.

We changed the scanner to exclude proj:activation audit views from the legacy-anchor check. The check still fails on a genuine old anchor outside the projection. It just no longer mistakes an audit log for one.

That small fix produced drift=[] and brought the domain-state suite to 54 passing tests. More importantly, it kept the distinction between a current-state projection and an append-only audit record. Confusing those two would have recreated the original retrieval problem in a new form.

What this does not prove

We have not measured a change in customer conversion, memory quality, or long-term maintenance cost. A smaller file is not automatically a better memory system. A passing test suite is not proof that the next real task will retrieve the right context every time.

The current evidence supports narrower conclusions. The central page is smaller. The facts we required are present. The preview updates from events. Closed items leave on schedule. Stale states fail visibly. The old write path still works, and the rollback file exists.

The next checks are equally concrete: whether new events update the central page without hand copies, whether closed items keep leaving on schedule, and whether a real task can answer the current baseline without opening unrelated domain files. If those checks fail, the response should be to inspect the curated list and read path, not to restore the old pile of narrative.

For us, the useful change is not less memory. It is less ambiguity about what is current, who owns it, and when it should stop being shown as current.

Related reading

Our earlier posts cover the adjacent layers: why memory needs an audit between its log and current state, how we grouped 31 control points into 9 and compared shadow projections, and why a saved status is not evidence of a durable write. The broader upgrade and verification flow is in AI agent memory governance.

Like the Dispatch? Add Wallaby as a preferred source on Google, or subscribe via RSS.
Did this help?