A lightweight local memory daemon for AI agents.
Cortex-AI stores and retrieves long-term memories on-device using:
SQLite(FTS5lexical search + optionalsqlite-vec)fastembedfor local CPU embeddingsllama-cpp-pythonwith strictGBNFJSON extraction- hybrid retrieval (
BM25+ vector) withRRFand exponential time-decay
Most agent memory stacks either:
- push data to cloud vector DBs,
- lose precision on exact-string recall,
- or don't model memory freshness over time.
Cortex-AI is designed to be:
- local-first (no required cloud memory infra)
- deterministic at extraction boundaries (GBNF-constrained JSON)
- hybrid and temporal (BM25 + vector + decay)
USER INPUT
→ RAW MEMORY STORE (immutable memory_store)
→ ENTITY MENTION DETECTION (Local LLM + GBNF)
→ ENTITY RESOLUTION (Alias + Jaro-Winkler + FTS + Embedding)
→ FACT EXTRACTION + EVENT EXTRACTION
→ KNOWLEDGE LAYER (Entity Graph / Fact / Event / State)
→ State Transition Engine (conflict handling)
QUERY
→ Intent Router (State → SQL | Fact → table | Event → hybrid)
→ Context Builder
→ Local LLM → Answer
Entry point: CortexEngine in memory_engine/cortex.py.
- Raw memory — append-only
memory_store(id,text,timestamp) - Mentions —
mentions.pyGBNF array of{mention, type} - Resolve —
resolve.pyblends exact/alias → JW (>0.88) → FTS → embedding → create - Facts / events —
extractors.pySPO triples + life events (GBNF) - State —
state_engine.pyinvalidates superseded stateful predicates (studied_at,lives_in, …)
query.py: intent cues route to states / facts / events / raw memories, then answer head.
MemoryStack + HybridRetriever (BM25 ∥ vector → RRF k=15 × time-decay) remain for LoCoMo evals (evals/run_locomo.py).
memory_engine/
cortex.py # CortexEngine (remember + retrieve)
knowledge_db.py # memory_store / entities / facts / events / states
mentions.py # mention detection (GBNF + generic fallback)
resolve.py # entity resolution (injectable gazetteer)
gazetteer.py # optional alias-group loader
extractors.py # fact + event extraction
state_engine.py # conflict handling → current state
query.py # intent router + context builder
main.py # REPL + one-shot CLI
db.py / retrieval.py / extraction.py # legacy LoCoMo path
mcp_server/ # stdio MCP tools for any host model
tests/
fixtures/ # scenario aliases + mention patterns (not prod)
support.py # make_test_engine()
evals/
run_locomo.py
- Python
3.11+(recommended) - macOS/Linux
- enough RAM for chosen GGUF (7B models need significantly more than 1B)
Install:
python3 -m venv .venv
.venv/bin/pip install -r requirements.txtDefault: BAAI/bge-small-en-v1.5 via fastembed
Place a model at:
~/models/Llama-3.2-1B-Instruct-Q4_K_M.gguf
or pass --model.
Example download:
mkdir -p ~/models
cd ~/models
curl -L -o Llama-3.2-1B-Instruct-Q4_K_M.gguf \
'https://huggingface.co/bartowski/Llama-3.2-1B-Instruct-GGUF/resolve/main/Llama-3.2-1B-Instruct-Q4_K_M.gguf'.venv/bin/python -m memory_engine.main
# offline heuristics (no GGUF load):
.venv/bin/python -m memory_engine.main --mockCommands:
remember <text>— raw → mentions → resolve → facts/events → stateask <query>— intent → context → answerlist— dump states / facts / events / raw memoriesquit
Bare text is treated as remember.
.venv/bin/python -m memory_engine.main remember \
"I used to study at NTU in Taiwan. In 2026 I transferred to the University of Michigan."
.venv/bin/python -m memory_engine.main ask "Where do I study now?"
.venv/bin/python -m memory_engine.main list.venv/bin/python -m memory_engine.main --demo --mock # heuristics
.venv/bin/python -m memory_engine.main --demo # real GGUF.venv/bin/python -m unittest tests.test_layers -v- Layer 1a — entity merge:
PyTorch/py-torch/Pytorchsame;AppleInc≠apple - Layer 1b — fact extraction emits
(subject, predicate, object) - Layer 1c — state transitions for
lives_in,works_at,studies_at,relationship_status - Layer 2 — 2024 Chicago → 2025 Boston → 2026 Miami; ask “Where do I live now?”
.venv/bin/python -m unittest tests.test_scenario_11 -vCovers grew_up_in → NTU → major → interests → PyTorch → Foxconn internship → move to Ann Arbor → transfer to UMich (+ UMich alias) → Let Him Cook founded → SwiftUI/FastAPI.
Scenario-specific alias groups and mention regexes live in tests/fixtures/
(not in production memory_engine/). Tests wire them via tests.support.make_test_engine.
Cortex exposes tools over stdio MCP. The host model answers; Cortex only stores/retrieves.
# Use the project venv (macOS system `python` is often missing — use this):
.venv/bin/python -m mcp_server --mock --db ./cortex.dbTools: memory_remember, memory_retrieve, memory_get_state, memory_search_facts,
memory_search_events, memory_search_memories, memory_resolve_entity, memory_get,
memory_summary.
Cursor mcp.json snippet:
{
"mcpServers": {
"cortex-memory": {
"command": "/ABS/PATH/SQLitememo/.venv/bin/python",
"args": ["-m", "mcp_server", "--mock"],
"env": { "CORTEX_DB": "/ABS/PATH/SQLitememo/cortex.db" }
}
}
}Prefer memory_retrieve / memory_get_state over any local answer head.
Use the project venv (python alone often fails on macOS):
# Full LoCoMo (10 conversations, gpt-4o answer + judge)
.venv/bin/python run_benchmark.py \
--benchmark locomo \
--project-name memloom-eval-locomo \
--answerer-model gpt-4o \
--judge-model gpt-4o \
--provider openai
# LongMemEval (downloads dataset on first run; --mock if no local GGUF)
.venv/bin/python run_benchmark.py \
--benchmark longmemeval \
--project-name memloom-eval-longmem \
--answerer-model gpt-4o \
--judge-model gpt-4o \
--provider openai \
--max-questions 20 --mockRequires OPENAI_API_KEY. Cortex handles ingest/search; OpenAI handles answer + judge.
Run one benchmark at a time — parallel gpt-4o runs hit org TPM limits (~30k).
Note: memory-benchmarks/run_benchmark.py is a Mem0-suite dispatcher (needs Mem0 cloud/OSS). Prefer the root run_benchmark.py for Cortex.
LoCoMo repo is expected at ./locomo with data at locomo/data/locomo10.json.
.venv/bin/python -m evals.run_locomo \
--max-samples 1 \
--categories 1 2 4 \
--max-qa 30 \
--balanced \
--top-k 5 \
--llm-answer \
--model ~/models/Llama-3.2-1B-Instruct-Q4_K_M.gguf \
--out results/locomo_llama1b_opt.jsonLocal Qwen 7B:
.venv/bin/python -m evals.run_locomo \
--max-samples 1 --categories 1 2 4 --max-qa 30 --balanced \
--top-k 5 --llm-answer \
--model ~/models/Qwen2.5-7B-Instruct-Q4_K_M.gguf \
--out results/locomo_qwen7b.jsonOpenAI answer head over Cortex retrieval:
export OPENAI_API_KEY="<your-key>"
.venv/bin/python -m evals.run_locomo \
--max-samples 1 --categories 1 2 4 --max-qa 30 --balanced \
--top-k 5 --openai-model gpt-4o-mini \
--out results/locomo_gpt4o_mini_opt.jsonSetup: hybrid retrieval (BM25 ∥ vector → RRF × time-decay), no GBNF --extract, answerer + judge gpt-4o, top_k=10.
Artifacts: results/memloom-eval-locomo_locomo.json, results/memloom-eval-locomo_locomo_judged.json.
| Metric | Score |
|---|---|
| Overall token F1 | 0.605 |
| LLM judge (gpt-4o) | 0.543 (1079 / 1986) |
| Category | n | F1 |
|---|---|---|
| adversarial | 446 | 0.821 |
| single_hop | 841 | 0.682 |
| temporal | 321 | 0.460 |
| multi_hop | 282 | 0.326 |
| open_domain | 96 | 0.246 |
Published LoCoMo tables usually report LLM-judge % on categories 1–4 (often excluding adversarial). Numbers below are from the Mem0 paper / Memobase writeups and vendor corrections — treat as directional, not a controlled bake-off (prompts, judge, and category inclusion differ).
| System | Overall (LLM judge, approx.) |
|---|---|
| Full conversation in context | ~73% |
| Memobase / Zep (claimed) | ~75% |
| Mem0 Graph | ~68% |
| Mem0 | ~67% |
| LangMem | ~58% |
| Cortex (this run) | ~54% |
| OpenAI memory baseline | ~53% |
On token F1, Memobase’s own published artifact reported overall F1 ≈ 0.51 (with LLM ≈ 0.76); Cortex F1 0.61 is stronger on overlap while the judge is stricter / lower.
Reading the gap
- Cortex is competitive as local hybrid RAG (strong adversarial / solid single-hop).
- Behind graph-memory stacks on multi-hop and temporal (Mem0 temporal bar often cited ~55%).
- Closing the gap means better cross-session evidence (knowledge layer / extraction), not only a stronger answer model — gpt-4o is already used here.
| Answer model | Overall F1 | Temporal | Single-hop | Multi-hop |
|---|---|---|---|---|
| Llama-3.2-1B | 0.308 | 0.282 | 0.386 | 0.256 |
| Qwen2.5-7B | 0.393 | 0.249 | 0.540 | 0.392 |
| gpt-4o-mini (same retrieval) | 0.494 | 0.527 | 0.601 | 0.355 |
- If
sqlite-vecis unavailable in your SQLite build, Cortex still runs using local numpy cosine fallback. MockLLMexists for offline tests only (--mock), not production quality.- If you expose API keys in logs/chats, rotate them immediately.
- LoCoMo dataset and benchmark code are from Snap Research: https://github.com/snap-research/locomo