Ahmed Doghri Logo Image
Ahmed Doghri

agentmem

Your agent does not need to remember that you said "ok sounds good" on a Tuesday. I built a salience gate to keep exactly that out, then found it writing 37% of pure filler to memory anyway, because "I" always looks like a proper noun to it.

agentmem, bounded self-consolidating memory for LLM agents

Your Agent Does Not Need to Remember "ok sounds good"

Here is what passes for agent memory right now. Shove every message into a vector store and pray retrieval untangles the gold from the small talk later. That is not memory. That is a junk drawer with embeddings, and it grows forever whether or not a single thing inside it was worth keeping.

Real agents run on a budget. Context windows are not infinite and neither is your patience for retrieval that returns three "sounds good"s before the one fact that mattered.

agentmem has opinions about what to keep. Filler gets bounced at the door. Retrieval weighs relevance, recency, and importance together. And when the drawer fills up, it merges related memories into a summary before dropping the weakest, instead of blindly forgetting the oldest thing.

Memory With a Doorman

Zero dependencies, zero API keys, one method to swap in the real thing.

The gate: it scores every write and drops the filler on the way in, instead of letting retrieval clean up the mess later.

Dedup: say the same fact twice and it reinforces the existing memory instead of cloning it.

Decay-aware recall: it ranks on similarity, recency, and importance, and the act of remembering something makes it harder to forget, like an actual brain.

Consolidate, then evict: at capacity it fuses related memories into a summary before cutting the weakest, so you lose noise, not signal.

The Benchmark You Can't Argue With

The gate was easy. The honest benchmark was the hard part. It would have been trivial to cherry-pick a memory size where the numbers glow. Instead it streams 200 real facts past the agent with junk chatter mixed in, caps memory at a fixed number of slots, then quizzes at random. Turn the capacity dial and watch recall move. No hiding behind one flattering config.

I also left the default embedder deliberately dumb. A hashing embedder is less accurate than a real model, but it means the whole thing runs with zero keys and zero network calls, which matters more for something people are supposed to learn from than a slightly prettier recall score.

The Number

Stream 200 facts past a 32-slot memory and it consolidates down to exactly 32, holding 52% recall@5 on a random quiz. Raise the cap and recall climbs toward 100%. The tradeoff is right there in the open, not buried under one hand-picked setting.

The Gate Never Actually Gated Anything

I noticed the benchmark writes facts with salience=0.9 and chatter with salience=0.05, both hand-set, and runs with write_threshold=0.0. Nothing in that setup ever asks the salience scorer to make a decision. "Salience-gated writes" is the first thing this project's own README promises, and its flagship benchmark never lets the gate reject a single thing.

So I ran the real scorer, no overrides, over the same synthetic stream. 37% of pure filler chatter got written to memory anyway.

The cause: the scorer rewards a sentence for containing a mid-sentence capitalized word, on the theory that capitalization marks a named entity, "Toronto," "UA482." "I" is always capitalized, whether or not it introduces anything worth remembering. "Hmm, I'm not sure about that." contains "I'm" and scores 0.596, comfortably clearing the 0.35 write threshold, for exactly the kind of small talk this project's own hero line uses as its running example of what an agent shouldn't remember.

One Fix Wasn't Enough

Stripping the false proper-noun credit off "I" and its contractions helped, but the same sentence still scored 0.396, a hair over the threshold, carried by length and word density alone. "not," "sure," and "about" were never stopwords in the original list, so a five-word hedge like "I'm not sure about that" reads to the scorer as five words of content. The fix adds the actual vocabulary people use to fill space while thinking out loud to the stopword list: "honestly," "basically," "I guess," "probably," and the rest.

Measured on labeled sentences: gate precision 53% to 100% on an adversarial set built independently of the scorer's code. Zero false positives, zero false negatives. And held out, written after the fix was frozen and evaluated exactly once: 40% to 100%, same clean result.

End to end, on the real stream: leak rate 37% to 0%, with fact recall unchanged. MemoryStore defaults to the fixed scorer now; the original stays exported for anyone reproducing the benchmark above.

One smaller thing turned up along the way: the benchmark's own consolidations field was hardcoded to 0, so the "self-consolidating" half of this project's name never had a real number attached to it in its own output, even though 8 to 10 merges were actually happening per run. MemoryStore tracks a running merge count now, and the benchmark reports it honestly. 29 tests, and CI fails the build on a gate precision regression or a nonzero leak rate.

Tools Used

Python
Hashing Embeddings
Decay-Aware Retrieval
pytest
Ruff
Held-out evaluation
PyPI Packaging