Skip to main content
Glama

๐Ÿง  Agent Memory Engine: long-term memory for AI agents

Give an AI assistant a memory that survives across conversations, keeps track of what changed, and knows when it doesn't know. Claude extracts facts from conversations, decides whether each one is new, an update or already known, and stores them with their history. A hybrid recall layer finds the right memories later. It ships as an MCP server, so Claude Code or any MCP-capable agent can use it, and it comes with a benchmark against three ways of answering without it.

CI Python Claude MCP License

Agent memory is one of the most active areas in open-source AI right now (mem0 is at ~67k GitHub stars). This project builds the core ideas from scratch, then measures them: does structured memory beat pasting the whole history into the prompt, and what does each part contribute?

How the Agent Memory Engine works: write path, read path and MCP server


Results

What we get: 100% accuracy, 9x fewer tokens than pasting the history, no invented answers, history kept

Six synthetic users, 8 conversations each, 72 questions. Every method uses the same answering prompt and model (Claude Haiku 5.5); only the context differs.

Method

Overall

Single fact

Knowledge update

History

Multi-session

Graph

Abstention

Input tokens / question

Cost / question

No memory

17%

0%

0%

0%

0%

0%

100%

433

$0.00007

Chunk RAG (BM25 over raw messages)

25%

25%

0%

0%

0%

0%

100%

501

$0.00010

Full context (whole history in the prompt)

100%

100%

100%

100%

100%

100%

100%

1,957

$0.00029

Memory engine

100%

100%

100%

100%

100%

100%

100%

628

$0.00010

Memory engine, no graph hop (ablation)

99%

100%

100%

100%

100%

83%

100%

587

$0.00009

Longer histories (24 extra small-talk conversations per user, 32 in total, same 72 questions):

Method

Overall

Input tokens / question

Cost / question

Full context

100%

5,459 (2.8ร— more than with 8 conversations)

$0.00066

Memory engine

100%

616 (flat)

$0.00009

What this shows, honestly:

  • Same accuracy as brute force, at a fraction of the context. With 8 conversations the memory engine uses 3ร— fewer input tokens than pasting the history; with 32 it's 9ร— fewer, and the gap keeps growing because recall always returns at most 8 memories.

  • Memory isn't free to build. Extraction plus decisions cost $0.0415 for 6 users ร— 32 conversations (~$0.007 per user). At these prices it pays for itself after about 12 questions per user; after that every question is cheaper.

  • At this scale, full context is a perfectly good answer. Haiku read 5,000 tokens of chat without missing anything. Memory wins on cost and on histories too long for a context window, not on accuracy here.

  • Naive RAG over chat logs fails badly (25%). "What's my pet called?" shares no words with "we adopted a beagle puppy and named him Biscuit", and "which city do I live in" retrieves the colleague who moved. Turning chats into enriched, standalone memories is what fixes it. (A dense-embedding chunk retriever would do better than this BM25 baseline; it is not included yet.)

  • The graph hop barely matters now, and that's a finding. Once the extractor sees the known people ("Diego = husband"), relationships are resolved when the memory is written, so recall rarely needs the graph. Without the graph, one answer out of 72 hedged.

  • Two bugs the benchmark caught in my own work: the extractor stored "the user's friend Diego" before known-entity context was added, and my test data called a tabby a breed. Claude answered "tabby is a coat pattern, not a breed", which was right, so I fixed the data rather than the model.

Total API cost for all the runs above: about $0.25.

Related MCP server: PseudoLife-MCP

How it works

 conversation โ”€โ”€โ–บ 1. EXTRACT  Claude turns it into standalone memories
                     "The user's husband Diego started a new job as a veterinarian."
                     + keywords (work, career, occupation) + entities (Diego: person)
                     + KNOWN PEOPLE AND PETS from memory, so a bare "Diego" is resolved
              โ”€โ”€โ–บ 2. DECIDE   for each candidate, Claude sees the related existing memories:
                     ADD (new) ยท UPDATE (replaces an outdated memory, kept as history) ยท SKIP (known)
              โ”€โ”€โ–บ 3. STORE    SQLite: memories + entities + links, superseded rows keep valid_until

 question โ”€โ”€โ”€โ”€โ”€โ–บ 4. RECALL    BM25 over memory text + LLM-written keywords, one hop through the
                     entity graph, current facts ranked above history  (no LLM call, ~1 ms)
              โ”€โ”€โ–บ 5. ANSWER   Claude answers from recalled memories only, cites memory ids,
                     and returns known=false instead of guessing

Design choice

Why

History, not overwrite

"We moved to Berlin" supersedes "lives in Pune" but keeps it, with the date. The engine answers both "where do I live?" and "where did I live before?".

Write-time enrichment instead of a vector DB

The extractor attaches synonyms and categories (a beagle gets dog, pet, puppy), so plain BM25 matches paraphrased questions. Recall needs no embedding model and no LLM call.

Known-entity context at extraction

Without it, "Diego started a new job" in a later conversation was stored as "the user's friend Diego": a wrong memory. Showing the extractor who's already known fixed it.

Structured outputs everywhere

Extraction, decisions and answers are JSON-schema constrained, so nothing has to be parsed out of free text.

Abstention is a first-class answer

known: false is part of the answer schema and is scored. An assistant that invents a sister's name is worse than one that says "I don't know".

Right to be forgotten

forget hard-deletes a memory and its entity links, in line with GDPR's right to erasure.

The benchmark

Synthetic users talk to an assistant over 8 weekly conversations (plus optional small-talk-only conversations to grow the history). Generated deterministically, no LLM involved, so every answer is known. Question types follow long-term-memory benchmarks such as LongMemEval and LoCoMo:

Type

Example

What makes it hard

single fact

"What's my pet called?"

Worded differently from "we adopted a beagle puppy and named him Biscuit"; a friend's dog is also mentioned

knowledge update

"Which city do I live in these days?"

The city, job and diet all change later; only the newest is right

history

"Where was I living before the move?"

Needs the superseded memory, not the current one

multi-session

"Which countries have I travelled to recently?"

Three trips in three different conversations; all must be listed

graph

"What does my husband do for work?"

"My husband Diego" and "Diego started as a vet" are weeks apart and share only a name

abstention

"What's my sister's name?"

Never mentioned; the right answer is "I don't know"

Scoring is deterministic string matching on the expected values; no LLM judge. Persona 0 was used while developing the prompts and retrieval; the reported numbers use personas 1-6, which were never looked at during development.

Use it from Claude Code (MCP)

pip install -e .
claude mcp add memory -- memory-mcp        # database at ~/.agent-memory.db

Tool

What it does

LLM calls

remember(text)

extract + decide + store

2 (Haiku 5.5 by default, AGENT_MEMORY_MODEL to change)

recall(query, k)

ranked memories, each marked current or "no longer true since โ€ฆ"

0

list_memories(include_history)

everything stored about a user

0

list_entities()

people, pets, places and organisations, with memory counts

0

forget(memory_id)

permanent delete

0

Quick start

git clone https://github.com/abpandey-dev/agent-memory-engine.git && cd agent-memory-engine
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

memory show-data --persona 1              # see a synthetic user's conversations and questions (free)
pytest -q                                 # 19 tests, scripted fake model, no API key needed

With Claude (needs ANTHROPIC_API_KEY; real calls cost money):

memory demo --persona 0 --model claude-haiku-5-5        # ~$0.005: build memory, answer 12 questions
memory bench --model claude-haiku-5-5 --personas 6       # ~$0.08: the full comparison
memory bench --model claude-haiku-5-5 --filler 24 --methods full_context memory   # long histories

Real output of memory demo --persona 0 (Haiku 5.5), lightly trimmed. History is kept, and Claude decided on its own that going vegetarian replaces "comfort food is lamb kebabs":

priya-s04: +1 added, 1 updated [('m-0004', 'm-0009')], 0 skipped
priya-s06: +1 added, 1 updated [('m-0003', 'm-0013')], 0 skipped
priya-s07: +1 added, 1 updated [('m-0002', 'm-0014')], 0 skipped

  m-0003 The user is based in Izmir.   [superseded 2026-07-06 by m-0013]
  m-0004 The user's comfort food is lamb kebabs, which they could eat every week.   [superseded 2026-06-22 by m-0009]
  m-0005 The user is married to a husband named Diego.
  m-0009 The user has decided to become vegetarian, mostly for health reasons, as of June 2026.
  m-0011 The user's husband Diego recently started a new job as a veterinarian with intense shifts.
  m-0013 The user moved to Berlin, Germany in late June 2026, and their new flat was full of boxes shortly after.

โœ“ Where was I living before the move?
    You were living in Izmir before the move to Berlin.  (memories: m-0003, m-0013)
โœ“ Which countries have I travelled to recently? List them all.
    Iceland (a trip for a friend's wedding, returning around June 2026), Peru (a two-week trip returning in
    early June 2026), and Kenya (a long weekend trip, returning July 12, 2026).  (memories: m-0010, m-0006, m-0015)
โœ“ What does my husband do for work?
    Your husband Diego recently started a new job as a veterinarian with intense shifts.  (memories: m-0011, m-0005)
โœ“ What's my sister's name?
    I don't know your sister's name; your memories don't mention a sister.  (memories: -)

12/12 correct; 27 calls, 23,541+4,009 tokens, $0.0044

Project layout

src/agent_memory/
  store.py        # SQLite: memories, entities, links, supersede history, forget
  retrieval.py    # BM25 + stemming, entity-graph hop, history down-weighting
  engine.py       # extract -> decide -> store; recall -> answer (prompts + JSON schemas)
  llm.py          # structured-output calls, token and cost accounting, refusal handling
  mcp_server.py   # 5 MCP tools over stdio (MCP Python SDK 2.x)
  dataset.py      # synthetic multi-session users with known answers
  bench.py        # the four methods + ablation, deterministic scoring
  cli.py          # `memory` command
tests/            # scripted fake Claude client, in-process MCP client
docs/             # architecture and results diagrams (SVG)

Limits and next steps

  • Synthetic data. Conversations are templated, so they are cleaner than real chat logs. The next step is running the public LongMemEval set.

  • Lexical recall. Write-time keywords did the job here, but a dense-embedding retriever (and a dense chunk-RAG baseline, which would beat the BM25 one shown here) should be compared at larger scale.

  • One user per question. Shared memory across a team (and access control on it) is not covered.

  • Decay and consolidation. Old, low-importance memories are kept forever; a consolidation pass that merges and summarises them is the next feature.

Built with synthetic data only. No real personal data is used.

Available Tools

5 tools
forgetA

Permanently delete one memory (for example when the user asks to be forgotten).

NameRequiredDescriptionDefault
memory_idYes

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden. It usefully discloses irreversibility ('permanently delete'), which is the key trait for a destructive tool, but says nothing about confirmation requirements, permissions, or behavior on an invalid/nonexistent memory_id.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One short sentence, front-loaded with the action and irreversibility, with the example parenthetically appended. No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values needn't be described. For a one-parameter destructive tool, the main missing piece is how to source the memory_id and any confirmation/error semantics.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0% and the description never mentions memory_id, so it adds no meaning about the identifier's format or where to obtain it (recall/list_memories). The single parameter is self-explanatory by name, preventing a lower score.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'Permanently delete one memory.' The word 'one' distinguishes it from bulk/list siblings like list_memories, though no sibling is named explicitly.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives one triggering example ('when the user asks to be forgotten'), which clarifies intent, but offers no conditions for when not to use it or which sibling to use instead (e.g., recall to find the ID first).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_entitiesB

People, pets, places and organizations in the user's memory, with how many memories mention each.

NameRequiredDescriptionDefault
user_idNodefault

Output Schema

NameRequiredDescription
resultYes

TDQS

B3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full behavioral burden. It helpfully discloses that results include mention counts per entity, but it omits pagination, result limits, sorting, and authentication/scoping behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, waste-free sentence that front-loads the entity categories. It is efficient, though it does not lead with an explicit verb like 'List'.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be explained. For a simple one-parameter list tool, the description covers the resource adequately, but gaps in usage guidance and user_id semantics keep it from being fully complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The sole parameter user_id has 0% schema description coverage, so the description should compensate. It implies user scoping via 'the user's memory' but never mentions the parameter, its default, or accepted format.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description identifies the resource precisely: people, pets, places, and organizations in the user's memory, plus mention counts. It is clear what the tool enumerates, but it does not explicitly distinguish itself from siblings like list_memories or recall.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no guidance on when to use this tool versus siblings such as list_memories, recall, or remember. The retrieval context is implied by 'user's memory,' but no alternatives or exclusions are named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_memoriesA

Everything remembered about a user (current facts only unless include_history is true).

NameRequiredDescriptionDefault
user_idNodefault
include_historyNo

Output Schema

NameRequiredDescription
resultYes

TDQS

A3.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full disclosure burden. It usefully states the default behavioral trait that only current facts are returned unless include_history is true, but it does not mention read-only status, permissions, pagination, or other operational traits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence with zero wasted words. It efficiently conveys the core resource and the key default scope caveat.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple with two parameters and an output schema, so the description needn't explain return values. Still, it leaves gaps around user_id semantics and when to choose this tool versus recall, which are relevant given the sibling-tool context and zero schema description coverage.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate. It clarifies the meaning of include_history and the default current-facts behavior, but it does not explain user_id beyond the phrase 'about a user,' leaving the identifier semantics only partially covered.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool returns everything remembered about a user, with a default current-facts-only scope. It does not explicitly distinguish itself from sibling tools like recall, but the purpose is specific enough for an agent to understand the resource being listed.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies when to use the tool by saying it returns everything remembered about a user, and it explains that history is included only when include_history is true. However, it gives no explicit guidance about when to prefer this over siblings such as recall or list_entities.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

recallB

Find memories relevant to a query. Each result says whether it is current or no longer true.

NameRequiredDescriptionDefault
kNo
queryYes
user_idNodefault

Output Schema

NameRequiredDescription
resultYes

TDQS

B3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the behavioral burden. It usefully discloses that each result indicates whether it is current or no longer true, but it omits other behavioral context such as read-only nature, permissions, or result limits.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences, front-loaded with the core purpose and no wasted wording. The description is appropriately sized for the tool.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Output schema exists, so return values need not be explained, but with no annotations and 0% schema coverage the description leaves key gaps. It does not address sibling alternatives, parameter meanings, or user scoping.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, but it does not. It only implies the query parameter and leaves k and user_id unexplained, including their defaults and scoping role.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific verb and resource: "Find memories relevant to a query." It is clear what the tool does, but it does not explicitly distinguish itself from siblings like list_memories or list_entities.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives no guidance on when to use this tool versus alternatives such as list_memories, remember, or forget. It implies retrieval use but provides no exclusions or decision criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

rememberA

Store what the user said about themselves. Facts that replace older ones (a new city, a new job) are recorded as updates and the old fact is kept as history.

NameRequiredDescriptionDefault
textYes
user_idNodefault

Output Schema

NameRequiredDescription

No output parameters

TDQS

A3.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden, and it does disclose a genuinely non-obvious behavior: superseded facts (new city, new job) are recorded as updates and the old value is retained as history, so nothing is destructively overwritten. It omits other behavioral context such as per-user scoping or duplicate handling, but the key mutation semantics are covered.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero waste, with the core action front-loaded and the update/history caveat following immediately. Nothing is repeated from the name or title.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need no explanation, and the tool is a simple two-parameter write. However, the unexplained user_id scoping and the absence of any routing guidance against forget/recall leave gaps for an agent operating in a memory-tool cluster.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate and largely doesn't. It never explains what 'text' should contain (a sentence? a fact tuple?) nor what 'user_id' scopes, even though its 'default' value implies multi-user partitioning that an agent could easily get wrong.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb (store) and a specific resource (facts the user shares about themselves), which clearly separates it from retrieval siblings like recall and list_memories. It stops short of naming any sibling explicitly, so differentiation is inferred from the verb rather than stated.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Usage is only implied: an agent can infer it should call this when the user reveals personal facts, but there is no explicit 'use when' framing, no guidance on when to prefer forget or recall, and no mention of how this interacts with list_entities/list_memories.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedforget
    • First observedlist_entities
    • First observedlist_memories
    • First observedrecall
    • First observedremember

TDQS

A3.6/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct role: remember writes, recall does query-based retrieval, list_memories enumerates all facts, list_entities enumerates people/places, and forget deletes. The recall vs. list_memories boundary (search vs. full listing) is explicitly spelled out in the descriptions, leaving no real overlap.

Naming Consistency4/5

All names are imperative lowercase verbs, which gives a readable, predictable style. However, the pattern is not strictly verb_noun: remember/recall/forget omit the object while list_memories/list_entities include one, a minor inconsistency but not confusing.

Tool Count5/5

Five tools is well-scoped for a personal memory store: one write, three read shapes, and one delete. Every tool earns its place with no redundant or speculative operations.

Completeness4/5

The lifecycle is essentially covered: remember implicitly handles updates with history, and forget handles deletion. Minor gaps exist, such as bulk deletion of all of a user's data (beyond one memory at a time) and no way to remove or merge entities explicitly.

Maintenance

ActivityMaintained
ResponsivenessNo recent community issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Gives AI persistent personal memory with hybrid search, temporal decay, and knowledge graph. Works with Claude and any MCP client.
    7 npm
    10
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables persistent long-term memory for Claude Code across sessions, with tools for storing, searching, and managing facts, knowledge graphs, and procedural lessons.
    38
    1,774 PyPI
    6
    Apache 2.0
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables AI agents to persist and retrieve structured memory across sessions and models via MCP tools for cataloging, searching, and saving facts, with automatic supersession and scope filtering.
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables all local Claude Code sessions, subagents, and other MCP clients to read and write one shared long-term memory over HTTP on localhost, so facts retained by one agent can be recalled by another. It extracts atomic facts, entities, and observations into an embedded local Postgres with ONNX embeddings and reranking, requiring no API key.
    MIT