Skip to content

mrankitvish/lcme-mcp

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

L.C.M.E.

Local Cognitive Memory Engine
Conversational memory for AI agents. Local. Trainable. Zero dependencies.

License: MIT Python 3.10+ PyTorch 2.0+ Zero External Dependencies 8 Languages

L.C.M.E. vs Mem0 on constrained hardware


The Problem

You're running a 3B-8B model locally on consumer hardware (CPU-only, 8-16 GB RAM). Your context window is 4K-8K tokens. Every token counts. You need long-term memory, but every other memory system (Mem0, Letta, Graphiti) requires a second LLM running alongside your agent — for extraction, summarization, or memory management. On hardware that's already at capacity running one model, that's not an option.

The Solution

LCME gives AI agents long-term memory without requiring an LLM for memory operations. It uses regex extraction, 10 small neural networks (303K params total, <1ms inference), and multi-signal retrieval to store and recall memories at 28ms per ingest and 14ms per query. No second model. No API keys. No external servers. Just pip install and it works on the same hardware your 3B model already runs on.

Your agent (Qwen-3B, Llama-8B, Phi-3, Gemma...)
    │
    ├── ingest() ──→  LCME (28ms, +17MB RAM, CPU)
    │                   └── regex extraction + neural scoring
    │                   └── SQLite + vectors + knowledge graph
    │
    └── retrieve() ←── LCME (14ms)
                        └── keyword + semantic + graph fusion
                        └── learned neural re-ranking

Every other memory system requires this:

Your agent (Qwen-3B)           ← already using most of your RAM/CPU
    │
    ├── ingest() ──→  Mem0
    │                   └── calls Qwen-3B AGAIN for extraction  ← 12s per item
    │                   └── ChromaDB                            ← +93MB RAM
    │
    └── retrieve() ←── Mem0 (vector-only search)

On a machine running a 3B model, you can't afford to call that model a second time for every memory operation. LCME's 303K-param networks do the job in <1ms.

from lcme import LCME, LCMEConfig

memory = LCME(LCMEConfig(data_dir="./memory"))

memory.ingest("Alice is a backend engineer at Google.", origin="user")
memory.ingest("The auth service runs on port 8080.", origin="user")

results = memory.retrieve("Where does Alice work?")
# [{'content': 'Alice is a backend engineer at Google.', 'confidence': 0.8, ...}]

context = memory.get_context_string("Tell me about the auth service")
# "[certain] The auth service runs on port 8080."

How It Works

LCME Architecture

Mermaid diagram (interactive)
flowchart LR
    subgraph Ingest
        A[Text] --> B[Claim Extraction<br><i>regex, no LLM</i>]
        B --> C[Entity Recognition]
        C --> D[Neural Scoring<br><i>importance + emotion</i>]
        D --> E[Interference Check]
    end

    subgraph Store
        E --> F[(SQLite + FTS5)]
        E --> G[(Vector Store<br><i>all-MiniLM-L6-v2</i>)]
        E --> H[(Knowledge Graph)]
    end

    subgraph Retrieve
        I[Query] --> J[FTS5 Keyword Search]
        I --> K[Vector Similarity]
        J --> L[RRF Rank Fusion]
        K --> L
        L --> M[Graph Expansion]
        M --> N[Neural Re-ranking]
        N --> O[Results]
    end

    subgraph Learn
        P[Consolidation] --> Q[Train 10 Networks<br><i>from usage patterns</i>]
        Q --> R[Maintenance<br><i>decay, prune, graduate</i>]
    end
Loading

Ingestion extracts claims and entities via regex (no LLM needed), computes embeddings, and scores importance/emotion with two small neural networks. Storage writes to three layers: SQLite with FTS5 for keywords, a vector store for semantic search, and a knowledge graph for relationships. Retrieval fuses keyword and semantic rankings via Reciprocal Rank Fusion, expands through the graph, and applies learned neural weights. Consolidation periodically trains all networks from accumulated access patterns and prunes stale memories.

What the Neural Networks Actually Do

LCME has 10 small PyTorch networks (~303K parameters total) that run on CPU in <1ms per call. They start with rule-based defaults and gradually take over as they accumulate training data.

flowchart TB
    subgraph cortex["Neural Cortex — 77K params"]
        MIS["<b>MIS</b><br>Memory Importance<br><i>~1K params</i><br>Learns which memories<br>get retrieved later"]
        ET["<b>ET</b><br>Emotional Tagger<br><i>~13K params</i><br>Tags valence + arousal<br>from embeddings"]
        RWL["<b>RWL</b><br>Retrieval Weights<br><i>~200 params</i><br>Learns optimal signal<br>weights per query"]
        AS["<b>AS</b><br>Associative Strengthener<br><i>~25K params</i><br>Hebbian learning on<br>graph edge confidence"]
        CG["<b>CG</b><br>Consolidation Gate<br><i>~13K params</i><br>Decides keep / compress<br>/ forget per memory"]
        ID["<b>ID</b><br>Interference Detector<br><i>~25K params</i><br>Detects contradicting<br>memory pairs"]
    end

    subgraph hippo["Hippocampus — 226K params"]
        QUM["<b>QUM</b><br>Query Understanding<br><i>~58K params</i><br>Extracts intent, temporal<br>and emotional signals"]
        AMM["<b>AMM</b><br>Associative Memory<br><i>~89K params</i><br>Hopfield network with<br>temporal binding"]
        CDM["<b>CDM</b><br>Correlation Discovery<br><i>~53K params</i><br>Cross-attention<br>re-ranker"]
        RCM["<b>RCM</b><br>Response Composer<br><i>~26K params</i><br>Dedup + compression<br>+ confidence calibration"]
    end
Loading

Cold start behavior: All networks use a 4-phase warmup. Steps 0-50: 100% rule-based. Steps 50-150: 50/50 blend. Steps 150-300: 75% neural. Steps 300+: 95% neural. The system works from step zero and improves with use.

Training: Networks train during consolidation cycles (configurable, default every 6 hours). MIS learns from access logs (which memories get retrieved). RWL learns from retrieval feedback (which weight combinations produce useful results). Training runs in <100ms on CPU.

Benchmarks

Note

Benchmarked for the target scenario: local agents on consumer hardware (CPU-only, 8-16GB RAM) running 3B-8B models. This is not a benchmark for cloud deployments with 70B+ models and unlimited API budgets. LCME is built for the machine that's already at capacity running your agent.

Hardware: AMD Ryzen 9 7940HS, 24.4 GB RAM, CPU-only. LCME: 30 items, 20 queries, 3 runs averaged. Mem0: 200 items, 15 queries, local Qwen-3B via llama.cpp. Graphiti/Letta: could not run (require Neo4j / Letta server).

Ingest Speed Retrieval Quality

Scalability (LCME, tested at 100 / 500 / 1000 items, 15 queries):

Items P@1 P@5 MRR Ingest/item Retrieval P50 Disk
100 1.000 1.000 1.000 166 ms 12 ms 1.4 MB
500 0.933 1.000 0.967 171 ms 12 ms 2.4 MB
1000 1.000 1.000 1.000 392 ms 12 ms 4.0 MB

Concurrent access (1 writer + 2 readers): 0 errors.

Head-to-head vs Mem0 (at 1000 items):

Metric LCME Mem0 v1.0.7 Graphiti v0.28 Letta v0.16
Precision@5 1.000 0.867 needs Neo4j needs server
MRR 1.000 0.800 " "
Ingest/item 392 ms 11,837 ms " "
Retrieval P50 12 ms 11 ms " "
Needs second LLM No Yes Yes Yes
External deps None ChromaDB + LLM Neo4j + LLM Server + PostgreSQL

Extended metrics (500 items, 15 queries):

Metric Score
nDCG@5 0.978
Recall@1 1.000
Recall@5 0.960
Contradiction handling 7/10 (70%)
Long conversation (200 turns, recall early facts) P@5 = 0.875
Concurrent (1 writer + 2 readers) 0 errors
Write throughput 8.8 items/sec
Memory growth (1000 items) +77 MB RAM, 3.6 MB disk

Why this matters on 3B hardware: Mem0's 11.8s per ingest means your Qwen-3B is busy doing memory extraction instead of responding to the user. On a single-model machine, memory and inference compete for the same compute. LCME's ingest runs between calls without the user noticing.

Full methodology: Benchmark Paper.

Installation

pip install lcme
# Or from source
git clone https://github.com/gschaidergabriel/lcme.git
cd lcme
pip install -e .

Tip

Requirements: Python 3.10+, PyTorch 2.0+, sentence-transformers 2.2+. All run on CPU.

API

Core

from lcme import LCME, LCMEConfig

config = LCMEConfig(
    data_dir="./memory",          # Where to store everything
    vector_model="all-MiniLM-L6-v2",  # Embedding model
    default_limit=5,              # Results per query
    auto_maintenance=True,        # Hourly decay + pruning
    auto_consolidation=False,     # Set True for automatic neural training
    consolidation_interval_hours=6.0,
)
memory = LCME(config)
Method Returns Description
ingest(text, origin, confidence) dict Store a memory. Returns claims, entities, topics.
retrieve(query, limit, context) list[dict] Retrieve relevant memories.
get_context_string(query, limit) str Formatted context block for LLM injection.
forget(node_id) bool Remove a memory (if not protected).
protect(node_id) bool Protect from automatic pruning.
consolidate() dict Trigger neural training + maintenance.
get_stats() dict Node, edge, vector, claim counts.
health_check() dict System status.

Convenience Functions

from lcme import remember, recall, get_context, forget, protect

remember("The user prefers dark mode.")        # Global singleton, ~/.lcme/data/
results = recall("user preferences")
context = get_context("What does the user like?")

Origin Types

Origin Confidence Use for
user 0.8 Things the user stated directly
code 0.95 Facts from code analysis
observation 0.7 Behavioral observations
inference 0.5 AI-generated conclusions
memory 0.6 Recalled from prior memory

Design Decisions

Claims, not facts. Every stored item is a claim with a confidence level and origin. Nothing is treated as ground truth. Confidence decays with a 7-day half-life. Old memories fade unless they prove useful (get accessed), in which case they graduate to permanent protection.

Regex extraction, not LLM. Ingestion uses pattern matching to extract entities and subject-predicate-object claims. This is less accurate than LLM extraction but 430x faster and requires no external model. The tradeoff is intentional: for agents that ingest continuously, speed matters more than extraction quality.

Three retrieval signals, not one. FTS5 keyword search finds exact matches. Vector similarity finds semantic matches. The knowledge graph finds structural relationships. Reciprocal Rank Fusion combines all three rankings. This avoids the failure mode where vector-only search returns semantically similar but factually wrong results.

10 small networks, not 1 large one. Each network has a single job (importance scoring, emotion tagging, retrieval weighting, etc.) and trains independently from its own data source. This means each network can reach maturity on different timescales, and a failure in one doesn't affect the others.

Multilingual Support

FTS stop-word filtering, temporal detection, emotion detection, and negation detection support 8 languages:

EN DE ES FR PT ZH HI AR
Stop words ~80 ~70 ~55 ~50 ~45 ~45 ~35 ~35
Temporal 25 22 19 16 16 26 26 24
Emotion 27 23 22 22 22 23 21 23
Negation 7 8 8 7 7 7 7 8

The embedding model (all-MiniLM-L6-v2) supports 100+ languages for semantic similarity.

Integrations

All examples use local 3B-8B models. For 3B models, we provide an optional LoRA adapter that makes tool calling reliable (95% accuracy on Qwen 2.5 3B). For 7B+ models, system prompt injection alone is usually sufficient.

Setup Models Example Guide
LoRA + llama-server Qwen 2.5 3B (merged GGUF) examples/openai_compatible.py LoRA adapter
LoRA + Python Qwen 2.5 3B (PEFT) examples/ollama_chat.py LoRA adapter
Ollama qwen2.5:3b, llama3.1:8b, phi3, gemma2 examples/ollama_chat.py Guide
llama-server Any GGUF examples/openai_compatible.py Guide
LangChain Ollama / LlamaCpp examples/langchain_memory.py Guide
LlamaIndex Ollama -- Guide
Raw Python Any HTTP endpoint -- Guide

Full guide: docs/INTEGRATIONS.md

LCME LoRA Adapter (Optional)

LCME works out of the box with system prompt injection — no fine-tuning required. But for models that struggle with tool-calling syntax (especially 3B models), we provide a LoRA adapter that teaches the model when and how to call lcme.ingest() and lcme.retrieve().

What it is

A PEFT/LoRA adapter (rank 16, 120MB) trained on 788 curated examples that teaches a model to:

  • Call <tool>lcme.ingest|text|origin</tool> when the user shares personal info
  • Call <tool>lcme.retrieve|query</tool> when the user asks about stored info
  • Not call any tool for generic questions (math, coding, trivia, greetings)

Test results: 95% accuracy (38/40) — Ingest 92%, Retrieve 100%, No-tool 92%.

What it is NOT

  • Not a personality. It teaches tool-calling only. No persona, no style, no opinion injection.
  • Not a full model. It's a 120MB adapter that sits on top of an existing base model.
  • Not required. LCME works without it. The adapter just makes small models more reliable at deciding when to use memory tools.
  • Not model-specific. Trained on Qwen 2.5 3B but the tool-calling pattern generalizes to other Qwen sizes.

Base model

Qwen/Qwen2.5-3B-Instruct (or any Qwen 2.5 variant). The adapter was trained on Qwen2.5-3B-Instruct-abliterated-hf but works with the standard Instruct version.

Installation (Python / PEFT)

# Reassemble the adapter (split due to GitHub's 100MB file limit)
cd models/lcme-lora-qwen3b-v2
bash reassemble.sh
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

# Load base model
model = AutoModelForCausalLM.from_pretrained(
    "Qwen/Qwen2.5-3B-Instruct",
    torch_dtype="auto", device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-3B-Instruct")

# Load LCME LoRA adapter
model = PeftModel.from_pretrained(model, "models/lcme-lora-qwen3b-v2")
model.eval()

Installation (llama.cpp / merged GGUF)

If you want a single GGUF file for llama-server, merge the adapter into the base model first:

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-3B-Instruct", torch_dtype="auto", device_map="cpu")
model = PeftModel.from_pretrained(model, "models/lcme-lora-qwen3b-v2")
model = model.merge_and_unload()
model.save_pretrained("lcme-merged")
AutoTokenizer.from_pretrained("Qwen/Qwen2.5-3B-Instruct").save_pretrained("lcme-merged")

Then convert to GGUF with llama.cpp's convert_hf_to_gguf.py and run:

llama-server --model lcme-merged.gguf --port 8080

Retraining

The training data generator and training script are included:

cd models/
python generate_lcme_data.py   # Generates 788 train/eval examples
python train_lcme_lora.py --lr 8e-5 --epochs 4  # Requires GPU (tested on RTX 5070)

Data Storage

~/.lcme/data/
├── lcme.db              # SQLite: nodes, edges, events, claims, FTS5 index
├── lcme_vectors.npz     # Compressed embeddings (384-dim per memory)
├── lcme_vector_ids.json # Node-to-vector mapping
├── hippocampus.db        # Retrieval training logs
└── models/
    ├── lcme_cortex.pt       # Cortex checkpoint (6 networks)
    └── lcme_hippocampus.pt  # Hippocampus checkpoint (4 networks)

License

MIT. See LICENSE.

About

Neural-enhanced conversational memory for AI agents — 10 micro-networks, tri-hybrid storage, bio-inspired retrieval

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Python 99.7%
  • Other 0.3%