memtrails: open-source memory for AI agents

memtrails is an MIT-licensed npm library that makes any text searchable at once, on your machine. How to use it, how it works, and how it compares to Mem0.

Abstract illustration: several trails of dots converge into one path that ends in an accent circle

memtrails is an open-source TypeScript library, MIT-licensed and on npm, that gives an AI agent a memory. Anything you add is searchable the moment it is added, on your own machine, with no server and no language model. Add a model and it also distils what it holds into typed facts, dated events and profiles an agent can explore.

It comes out of the same research as the long-term memory in Tesseract, which we described in how AI agents remember across sessions. This post covers what the library does, how to install and use it, how it works inside, and what its public benchmarks show. Every number here comes from the project’s README and its benchmark write-up, with the method next to it.

What problem does memtrails solve?

Agents forget. Each new session starts empty, and the obvious fix, pasting more history into the prompt, works badly once the history is long. So most agent memory systems do something heavier: they send every message through a language model, extract “memories”, and store those. That costs a model call per record, takes time before anything is searchable, and sends your data to a model provider.

memtrails starts from the other end. Raw text is kept, for good, and is searchable at once, with a small embedding model and a keyword index running locally. A language model is optional and works on top: it consolidates, it does not gate. That makes the library useful beyond chat history: an e-mail archive, a Slack export, Git commit messages, a folder of notes, or the conversations your own agent has.

Four properties define it:

  • Searchable at once. No ingestion pipeline and no model call per record.
  • Local. The embedding model (EmbeddingGemma-300m, 4-bit, about 220 MB) and the index (Qdrant) run on your machine and are fetched on first use.
  • Any language. Multilingual embeddings and keyword folding for any script, so a question in one language can find a memory written in another.
  • Built for agents. Three ready-made tools, memory_search, memory_open and memory_explore, with zod and JSON schemas.

How do you install it?

npm install memtrails

The requirements, from the README:

RequirementDetail
Node.js22 or newer
Operating systemmacOS or Linux, x64 or arm64
First runDownloads the embedding model (218 MB) and the Qdrant binary (about 30 MB) into ~/.cache/memtrails, each checked against a pinned SHA-256
Cache locationMEMTRAILS_CACHE moves it
LicenceMIT

The current release on npm is 0.3.0, published on 2 November 2026. 0.1.0, the first public release, came out on 31 October.

How do you add and search memories?

A record is anything with text; only id and text are required. Optional fields such as kind, author, title, at, thread and collection help scoping and dating. A short example:

import { openMemory } from 'memtrails';

const memory = await openMemory({ home: './memory' });

await memory.add([
  { id: 'n-1', kind: 'note', title: 'Release plan', at: '2026-10-28',
    text: 'We ship the beta on Friday; the changelog still needs the API section.' },
  { id: 'n-2', kind: 'note', title: 'Retro', at: '2026-11-01',
    text: 'The beta slipped a day because the API section of the changelog was late.' },
]);

const hits = await memory.search({ query: 'why did the beta slip?', limit: 5 });
await memory.close();

Adding the same id again updates the record, and memory.remove([...ids]) forgets. Conversations go in as turns with addConversation; each time the person speaks becomes one record together with what was answered.

For bulk data, streaming importers cover the common sources: fromMbox for e-mail, fromSlackExport, fromGit for commit messages and changed files, fromFiles for Markdown, text and HTML, and fromJsonl. memory.import takes any async iterable, so a database cursor works the same way, and unchanged records cost nothing.

Search has a few shapes, each for a different reader:

  • search returns ranked hits, filterable by collection, kind and date range.
  • searchText returns the same results as one line per memory, best first or oldest first.
  • room fills a fixed number of characters with the best sentences found, for a small context window.
  • open returns one item whole, with the records it was learned from.
  • explore walks the concept graph from a topic to related items a word search would not find.

When a query names dates, results inside the range come first and the rest still follow: a question that names a time is rarely only about that time.

How does search work inside?

How memtrails works: records go to an append-only log and a local index; a search runs dense and keyword channels, fuses them 0.7 to 0.3, and reorders the best 30 word by word; an optional language model consolidates, sleeps and gathers

Every record goes to two places. An append-only log is the truth. A Qdrant index is derived from it and rebuilt whenever it is lost or out of date. For each record the index holds three things: a key vector of its start, its whole text in chunks of about 96 words, and a BM25 keyword vector.

A search runs every channel at once and fuses the results at the level of items, by score, with weights of 0.7 for the dense channels and 0.3 for keywords.

Then comes the trick that replaces a reranker. A single vector per memory averages it, so one exact detail in a long memory gets drowned out. The embedding model already computes a vector per token before averaging, so memtrails keeps those token vectors, stored at one bit per number, and re-scores the best 30 hits with MaxSim: for each query token, take its best match among the memory’s tokens, and sum. It is the late-interaction scoring from ColBERT, with no second model and no training.

The measured effect, over 2,011 benchmark questions: the answer’s words reached the reader in the top five for 53.1% of questions, against 50.3% for plain search, and scoring 30 memories took 2.6 ms at the median on one CPU thread. Against a cross-encoder reranker on a harder set of 557 questions, word-by-word scoring got most of the reranker’s gain (+3.8 points at five against +4.0) with no second model, so the library uses word-by-word scoring by default. A rerank server can also be plugged in.

What does the optional language model add?

Records are kept as they are. With a language model configured, memtrails also:

  • Consolidates. It reads threads in windows and keeps what matters as typed items: facts, decisions, procedures, pitfalls, preferences, dated observations and kept episodes, reconciled against what it already knows so nothing is stored twice.
  • Sleeps. A daily pass merges near-duplicates, retires what was superseded, links related items, and writes a profile of each person or thing many items mention.
  • Gathers. gather searches widely, has the model read the candidates and keep what an answer needs, and, with notes, write down what the kept memories say, each instance with its date. This is aimed at counting and listing questions (“every doctor I saw this year”).
const memory = await openMemory({
  home: './memory',
  llm: { model: 'your-model', baseUrl: 'https://your-openai-compatible-endpoint/v1', apiKey: process.env.LLM_KEY },
});
await memory.consolidate();   // after adding
await memory.sleep();         // once a day
const set = await memory.gather({ query: 'every trip I planned this year' });

Any OpenAI-compatible endpoint works, including local servers, or you can pass your own function. Every call asks for strict structured output, and token usage is reported per call. Secrets such as keys, tokens and credentials in URLs are scrubbed from what is sent to a model and from what consolidation keeps.

How do you give it to an agent?

memory.tools() returns the three tools with a description, a zod input schema, a JSON schema and a handler, so they plug into the Model Context Protocol TypeScript SDK or any function-calling API:

for (const t of memory.tools()) server.tool(t.name, t.description, t.inputSchema, t.handler);

The agent then searches when it needs to, opens an item whole when a hit looks right, and explores the concept graph when a search for the words does not find what it needs.

How does memtrails compare with Mem0 and Graphiti?

The repository compares memtrails with two open memory systems, Mem0 and Graphiti, on two public benchmarks: LoCoMo (paper), ten very long two-person conversations with questions, and LongMemEval. LoCoMo is licensed CC BY-NC 4.0, so memtrails does not ship it; you download it yourself to reproduce the results.

The method. Every system ingests the histories its own way and returns context for each question, up to the same budget. The same reader model (GPT-6 Luna, medium effort) answers for every system. A strict judge (GPT-5.5 with LongMemEval’s official yes/no prompts) and a lenient judge (GPT-5.5 with Mem0’s own LoCoMo judge) grade the answers. Ideas were tuned on a development part and confirmed once on held-out data. Pairs are compared per question with a two-sided sign test, reported as +a/−b.

The headline results, on the 1,307 held-out LoCoMo questions with a small room of 6,000 characters, read and judged once:

Setting (LoCoMo, 1,307 held-out questions, 6,000 characters)memtrailsMem0Reading
search alone, no model when searching, strict77.476.5Level, with no model at search time (+97/−85, p 0.42)
gather at 40, strict79.576.5Ahead (+110/−71, p 0.005)
gather at 40 with notes, strict80.976.5Ahead (+113/−55, p < 0.001)
gather at 40 with notes, lenient91.987.8Ahead (+78/−25, p < 0.001)

Bar chart of LoCoMo strict scores at 6,000 characters on 1,307 held-out questions: Mem0 76.5, memtrails search alone 77.4, memtrails gather 79.5, memtrails gather with notes 80.9

At the larger room of 28,000 characters, the budget from Mem0’s published protocol, and on an earlier build (27 October):

Setting (28,000 characters)memtrailsMem0Graphiti
LoCoMo, an agent using each system’s own tools79.077.147.0

On retrieval alone, LongMemEval-S with the strict “every answer session in the first five distinct sessions” measure over 467 questions, memtrails scores 96.15 with word-by-word scoring and no reranker. The gbrain-evals project publishes 95.96 on the same count.

One more number lives in the repository after the 0.3.0 release: an independent audit of LoCoMo found 80 of the held-out questions with a wrong golden answer. Dropping them for both systems gives 84.8 against Mem0’s 79.9 (+109/−49, p < 0.001).

Questions

Does memtrails work without a language model? Yes. Adding, searching, opening and exploring work with the local embedding model and index alone. A language model adds consolidation, sleep and gather.

Does my data leave my machine? Records stay in the folder you give as home. Nothing is sent anywhere unless you configure a language model, an embedding server or a rerank server; then they receive what they need for their step.

Is it better than Mem0? With gather and notes, it was ahead on 1,307 held-out LoCoMo questions beyond chance. With search alone, it matches Mem0 with no model at search time.

Can I reproduce the benchmarks? Yes. The harness is in the repository’s bench/ folder, and the protocol is written out step by step in the benchmark write-up. You download LoCoMo and LongMemEval yourself.

How does it relate to Tesseract? It comes out of the same memory research as Tesseract’s long-term memory, which runs inside the desktop app for every agent framework it drives. memtrails is the standalone library: no app, no account, MIT-licensed.

Get memtrails

Install it with npm install memtrails, or read the code and the full results on GitHub and npm. To see a memory like it working inside an agent app, try Tesseract, where long-term memory is on by default.

អត្ថបទផ្សេងទៀតក្នុងប្លុក