← The Journal

Tool review8 min read6 / 10

A shared memory service for coding agents

Every new AI coding session starts with no memory of the last one. We built a small network service to fix that and measured it for three months. Search got good. Getting agents to actually use it is the unsolved part.

The problem: every session starts from zero

An AI coding agent session begins knowing only what is in its instructions and the files it reads. Anything learnt in yesterday's session, such as why a setting is the way it is, which command breaks the build, or what the person said last week, is gone unless something carries it forward.

We tried an existing plugin first, claude-mem. It worked well on one machine. Our sessions do not stay on one machine.

Why we replaced claude-mem

claude-mem is an open-source plugin for Claude Code. It records observations from each session through hooks and summarises them into a local store. For one person on one computer it is a good tool. For us it fell short in five ways:

  • Its capture hooks run per runtime. Our sessions run in a Windows shell, a Linux shell on the same machine, inside containers and on other computers. Inside containers the hooks broke, so those sessions remembered nothing.
  • Each machine and runtime had its own store. Knowledge fragmented: what one session learnt, a session on the next machine never saw. On one machine a path mix-up once quietly split a single store in two.
  • One global store mixed everything together. Where sessions did share a store, it held every project at once, and context from one project bled into sessions on unrelated projects.
  • Its observer cost real tokens. It made a call to a paid model per tool call, to write up what each session did. Over a working day that adds up.
  • Its Windows worker failed quietly. On one version the background worker died at boot without an error, and nothing was captured until we noticed.

None of these is a reason to avoid claude-mem for one machine. Our need was different. We run agents and sessions on many computers, in different physical locations, and we needed one central place for what they learn: a service on the network that a project on any machine can read from and write to. So we built one.

What we built

A small self-hosted service, which we call MemHub:

  • A .NET web service on Postgres 17 with the pgvector extension, running as its own small stack on a home server, plus a local embedding model (nomic-embed-text, 768 dimensions) to turn notes into vectors.
  • A REST API, a web page for browsing, and an MCP endpoint, so a coding agent gets three tools: memory_search, memory_recent and memory_write. Later versions added feedback and a short "project brief" at the start of a session.
  • Every note has a scope (global, tenant, project context or session) and a kind from a closed list: lesson, fact, research, state or summary. Anything else is refused.
  • Search runs a vector search and a full-text search side by side and merges the two rankings (reciprocal rank fusion). If the embedding model is down, it falls back to full-text only and says so with a degraded flag instead of failing.
  • It is append-only. A wrong note is never edited or deleted: a new note is written and the old one is marked as superseded by it, after which the old one stops appearing in search.
  • Each client has its own named token, and since October the session configuration holds no token at all: a small helper reads it from a file when the agent connects.

The design choices we would make again are the closed list of kinds, the visible degraded flag and the supersede link. The append-only rule we would think harder about, for reasons below.

What we measured

We imported about 3,500 notes from the old plugin in August 2026 and then wrote new ones from day-to-day work. We measured retrieval with sets of real questions we knew the answer to, scoring where the right note came back in the results.

WhenQuestion setResult
Mid-October 202641 questionsMean reciprocal rank 0.536. Most of the reachable notes were bulk imports, and they crowded out the useful ones
A week later, after re-ranking changesThe same 41 questions0.682. Right note first 23 times out of 41, in the top five 35 times
Late October 202630 new questions, searched within the right project0.821. Right note first 23 times out of 30, in the top five 26 times

Two cautions about those numbers. The middle row is on the same questions we tuned against, so it flatters the change; the last row is a separate set. And they are our own questions about our own work, not a public benchmark.

Two other measurements surprised us:

  • Keeping prompt history apart helps. Mixing raw conversation history into the same project context as the curated notes lowered retrieval (0.611 against 0.584 on one test), so prompts now live in their own context.
  • A search without a project context returned nothing at all. The same 30 questions, asked without saying which project, got zero hits. That was a bug in how an empty filter was matched, and it means a client that forgets the project gets silence, not an error.

The real problem: written, rarely read

The service works. The difficulty is behaviour. In September we counted fifteen repeat corrections in four weeks on one piece of work. In four of them the right answer was already stored, word for word, and the agent had not looked. Our own issue tracker calls it "write-mostly".

A health check at the end of October made the same point from the other side:

  • Automatically captured session notes were nearly worthless. Of 486 notes captured at the end of sessions, 374 (77%) had no real content, and none of the 486 had ever been returned by a search.
  • Hand-written notes earned their keep. Notes a session wrote on purpose were retrieved 14.6 times each on average; bulk-imported ones 3.5 times.
  • Nobody used the feedback. The "was this helpful?" votes, meant to let good notes rise, had never been cast once, so the trust score did nothing.

The lesson for anyone building one of these: a memory store fixes the storage problem. The recall problem, getting an agent to search before it acts, is a matter of where the lookup happens. We now push a short project brief into the start of every session and run a lookup before a task begins, rather than hoping the agent decides to search.

Faults we hit

  • The first search after a quiet spell came back degraded (full-text only) while the embedding model loaded. The flag made this visible, which is exactly why the flag exists, but early on it read as "the memory is empty". A later check could not reproduce it.
  • The embedding model was too heavy for the server we first put it on. Re-embedding the import on a four-core NAS processor timed out on every call and pinned the processor, so for a while search was keyword-only. We moved the model off that server.
  • Any query starting with "/" silently returned nothing. It took a while to notice, because nothing errored. A search that fails should say so.
  • Append-only cuts both ways. It gives a clean history, but there is no way to delete one note, only a purge of a whole project context. So a note that should never have been written stays until you purge everything around it. A scan found two notes holding text that looked like credentials. The rule for agents is now absolute: never write a secret to the memory service.
  • The first audit found no database backup. It became its own piece of work, and it is the kind of thing a "small internal service" quietly skips.

Alternatives

We kept the old plugin running alongside during the move, so we could compare. We also started a trial of another open-source memory server in October. Its first defects were serious enough for us (search ignored the project filter, so results leaked across projects, and it used one shared secret for every client), and the trial was not a fair fight, because our own service was running without its vector search when the trial began. We have no final verdict to report on that comparison, so we will not pretend to one.

What we are working on next

Not every note deserves the same place. We are working on tiered memory: tier 1 is the small set of facts every session should start with, tier 2 is what a session searches for when a task needs it, and tier 3 is the full archive, kept but rarely read. The trade-off is context against ease of access. Load too much into every session and the context bloats, costs more and crowds out the task; keep too much in the archive and the agent never finds it. We do not have the balance right yet, and we will write it up when we do.

The verdict

6 / 10. Worth running, not the whole answer. One shared, searchable memory across every shell, container and machine is a genuine improvement, and once tuned, the search finds the right note most of the time. The score is held down by the part no database can fix on its own: agents do not reliably read what they wrote. If you build or buy one, spend less effort on capturing everything and more on two things. Write fewer, better notes by hand. And put the lookup where the agent cannot skip it.

Want this on your project?

Everything in the Journal comes from software we build and run ourselves. If you want the same discipline applied to your project, tell us what you are trying to do.