Agent Memory Consolidation Is a Transaction, Not a Cron Job

John Rood··8 min read

Storage is the easy half of agent memory. Text goes in, embeddings get computed, retrieval comes back with something. The vault grows. Whether it gets more useful as it grows is a separate question, and most memory systems never answer it.

I have watched this play out in my own vault for months. The most useful entries in there are not raw records of conversations. They are the lines that say what a conversation settled, written by a consolidation pass that ran over the raw material afterwards. The raw text is evidence. The reflection is what a session starting this morning can actually use.

That gap is where agent memory earns or loses trust, and nearly all of the design attention goes to the wrong side of it.

The pile does not compress itself

Raw memory is a log. Every message and tool result, stored at full fidelity. That is the correct default, because you cannot know in advance which detail will matter.

The problem arrives with volume. Ask a vault of ten thousand raw moments what happened on a project and retrieval hands back the same decision six times in slightly different words, plus the setup around each one. Nothing is wrong with the ranking. The corpus simply has no layer above it.

A session that opens on that context spends its first ten minutes rebuilding what you explained last week, which is the exact cost memory was supposed to remove.

A summarizer on a timer is not the same thing

The usual fix is a background job that summarizes something every so often. That version is easy to ship and hard to trust, for reasons that have nothing to do with model quality.

A timer is not a state. If the worker dies halfway through a batch, the next run either redoes the work or skips it and nothing in the system knows which. If two sessions fire the job at once, both read the same window and you end up with two summaries of the same week. When a summary is wrong there is no way back to what it was built from, so the only repair is to delete it and hope.

Every one of those is a distributed systems problem that got handed to a language model.

Checkout, then commit

The vault holds three layers: raw moments, reflections of specific events, and high-level reflections about what matters. Consolidation moves material up one tier, and the same two calls run both hops.

MemoryRouter splits the pass into two calls. Checkout leases the oldest unconsolidated memories, returns their texts and dates plus a server-authored instruction block, and holds those rows for fifteen minutes. Commit sends back what your model wrote, and the server records which rows fed each entry.

curl -X POST https://api.memoryrouter.ai/v1/memory/consolidate/checkout \
  -H "Authorization: Bearer $MR_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "tier": 1 }'

The lease is what changes behavior. Checked-out rows are excluded from every other checkout, so a backfill worker and a live session cannot collide. If the model dies, its rows come back into the pool when the lease lapses and nothing sits there stuck. Send a batch twice and the server hands back the original ids and sets already_committed: true. The replay is not billed. Send one after expiry and you get a 409 rather than a duplicate row.

The model never sees a memory id. It gets bare texts, dates, and instructions for what a reflection should look like: standalone prose plus an importance rating from 1 to 10, somewhere around five to ten entries per batch. The server owns id association and coverage, so a model cannot invent lineage or attach a reflection to a memory that was never in its batch.

The request and response shapes, including the delete and date-search calls, are in the reflections documentation.

The reflecting runs on your inference

The server does not call a model provider for consolidation at any point. Your host's model does the writing, on the account you already pay.

That is a design decision I would want as a buyer, because the alternative is a second invoice for somebody else's summarizer, plus a summary you cannot inspect without exporting it. It also ties the quality of the result to a model you picked rather than one that shipped inside the memory layer.

What you get for running it yourself is the contract. The checkout response carries a versioned reflection_contract with the instruction text, the entry schema, and the size bounds. Pass those instructions through verbatim and skip writing your own reflection prompt. The version only moves when the wording or the meaning of an entry changes, so an integration can pin against it and drop the pass into whatever loop it already runs.

Debt is more useful than a schedule

There is a number that says when consolidation is worth running. It is not a clock.

The server tracks unconsolidated material per vault and surfaces the figure on calls you already make: search, prepare, ingest, commit, date search. Debt is counted by importance instead of row count, because four thousand trivial observations should not weigh the same as four hundred decisions. Against a threshold of 150 summed importance, tier 2 entries count at their rated value and raw tokens convert by a stated rate. When the number crosses, suggestion becomes non-null and names the tier that is behind.

Nothing polls. A connector reads the debt block on an ordinary retrieval and decides whether to spend the inference now or at the end of the session. Proxy responses in the OpenAI, Anthropic, and Google wire formats have no room for extra JSON fields, so the same block rides in an X-Memory-Reflection-Debt header.

The connectors I ship run the pass automatically: above the threshold, at most three batches per trigger, a machine-wide lock so two sessions do not double-fire, a cooldown after each run, and one config flag to turn it off if you would rather choose the moment.

What changes in the prompt

Once a vault holds reflections, retrieval stops being one question. The tiers and importance parameters on search and prepare let the caller ask a narrower one.

  • A "what matters here" question wants tier 3 alone with importance 8 or higher and a large limit.
  • A specific lookup wants the default blend with no importance filter, because the detail you need is probably a raw memory and a high threshold would hide it.
  • A reconstruction of a period wants date search, which matches reflections on the window they cover rather than the day they were written.

The filters are the difference between memory as a search box and memory as a briefing. They are also the only place where the tier structure pays for itself. Reflections nobody can filter by importance are just shorter raw memories.

If you want the general case before the mechanics, the AI memory guide covers what portable memory means across tools and where it stops being a chat history.

What inspect is for

Any reflection can be walked back to its inputs. Give inspect a memory id and it hands back the layer underneath, with the source rows, the batch they came from, and the date range that batch covered. Run it again on one of those rows and you reach the raw text.

That audit trail is the reason I would not run consolidation without it. When an agent tells you it chose Postgres because that was the decision, you can check whether the reflection says that or whether the model guessed. If a source was deleted later, the answer counts it in sources_deleted instead of presenting a smaller set as the whole story.

What this design refuses to do

Reflections layer on top of raw memories. They never replace them, and removing a raw row leaves any reflection built from it in place. If you want both gone, delete both by id at each tier.

The API will not consolidate anything on its own. That is deliberate and it is the trade: you keep the inference bill and the timing choice, and in exchange you get a pass you can audit, replay, and interrupt without corrupting the vault. If you want the automatic version, the connector packages already run it.

A vault that never consolidates is a log. The consolidation pass is what turns it into something a new session can read, and it needs the same engineering as the write path.

Start a vault and let the first reflection settle overnight.