grafeo-memory
AI memory layer powered by GrafeoDB, an embedded graph database with native vector search.
No servers, no Docker, no Neo4j, no Qdrant. One .db file + one LLM.
Typical memory stack: Containers with Neo4j + Qdrant, Embedding API + LLM
grafeo-memory stack: grafeo (single file) + LLM
Install
uv add grafeo-memory # base (bring your own LLM + embedder)
uv add grafeo-memory[mistral] # + Mistral embeddings
uv add grafeo-memory[openai] # + OpenAI embeddings
uv add grafeo-memory[anthropic] # + Anthropic embeddings
uv add grafeo-memory[mcp] # + MCP server for AI agents
uv add grafeo-memory[all] # all providers
Or with pip:
pip install grafeo-memory[openai]
Quick Start
OpenAI
from openai import OpenAI
from grafeo_memory import MemoryManager, MemoryConfig, OpenAIEmbedder
embedder = OpenAIEmbedder(OpenAI())
config = MemoryConfig(db_path="./memory.db", user_id="alice")
with MemoryManager("openai:gpt-4o-mini", config, embedder=embedder) as memory:
# Add memories from conversation
events = memory.add("I just started a new job at Acme Corp as a data scientist")
# -> [ADD "alice works at acme_corp", ADD "alice is a data_scientist"]
events = memory.add("I've been promoted to senior data scientist at Acme")
# -> [UPDATE "alice is a senior data scientist at acme_corp"]
events = memory.add("I left Acme and joined Beta Inc")
# -> [DELETE "alice works at acme_corp", ADD "alice works at beta_inc"]
# Search
results = memory.search("Where does Alice work?")
# -> [SearchResult(text="alice works at beta_inc", score=0.92, ...)]
Mistral
from mistralai import Mistral
from grafeo_memory import MemoryManager, MemoryConfig, MistralEmbedder
embedder = MistralEmbedder(Mistral())
config = MemoryConfig(db_path="./memory.db", user_id="alice")
with MemoryManager("mistral:mistral-small-latest", config, embedder=embedder) as memory:
events = memory.add("I just started a new job at Acme Corp as a data scientist")
results = memory.search("Where does Alice work?")
How It Works
grafeo-memory implements the reconciliation loop, the intelligence layer that decides what to remember:
- Extract facts from conversation text (LLM call)
- Extract entities and relationships (LLM tool call)
- Search existing memory for related facts (vector + graph)
- Reconcile new facts against existing memory (LLM decides ADD/UPDATE/DELETE/NONE)
- Execute the decisions against GrafeoDB
┌──────────────────────────────────────────┐
│ grafeo-memory │
│ │
│ Extractor -> Reconciler -> Executor │
│ (LLM) (LLM) (GrafeoDB) │
└──────────────────┬───────────────────────┘
│
┌─────────┴──────────┐
│ GrafeoDB │
│ Graph + Vector │
│ + Text (optional) │
│ single .db file │
└────────────────────┘
Multi-User Isolation
config = MemoryConfig(db_path="./chat_memory.db")
with MemoryManager("openai:gpt-4o-mini", config, embedder=embedder) as memory:
# Each user's memories are isolated
memory.add("I love hiking in the mountains", user_id="bob")
memory.add("I prefer beach vacations", user_id="carol")
bob_results = memory.search("vacation preferences", user_id="bob")
# -> hiking, mountains
carol_results = memory.search("vacation preferences", user_id="carol")
# -> beach vacations
Supported LLM Providers
grafeo-memory uses pydantic-ai model strings, so any provider pydantic-ai supports works out of the box:
# OpenAI — use OpenAIEmbedder for embeddings
MemoryManager("openai:gpt-4o-mini", config, embedder=OpenAIEmbedder(OpenAI()))
# Anthropic — pair with OpenAI or custom embedder
MemoryManager("anthropic:claude-sonnet-4-5-20250929", config, embedder=embedder)
# Groq — pair with OpenAI or custom embedder
MemoryManager("groq:llama-3.3-70b-versatile", config, embedder=embedder)
# Mistral — use MistralEmbedder for embeddings
MemoryManager("mistral:mistral-small-latest", config, embedder=MistralEmbedder(Mistral()))
# Google — pair with OpenAI or custom embedder
MemoryManager("google-gla:gemini-2.0-flash", config, embedder=embedder)
Built-in Embedders
| Class | Provider | Default Model | Install Extra |
|---|---|---|---|
OpenAIEmbedder |
OpenAI | text-embedding-3-small |
[openai] |
MistralEmbedder |
Mistral | mistral-embed |
[mistral] |
Both accept an optional model parameter to override the default.
Which model works best?
| Provider | Combined extraction | Individual extraction | Notes |
|---|---|---|---|
| OpenAI (gpt-4o-mini) | Works | Works | Recommended default |
| Anthropic (claude-sonnet) | Works | Works | Untested in CI |
| Mistral (mistral-small) | Falls back | Works | 3 API calls instead of 1 |
| Groq | Unknown | Unknown | Needs testing |
| Unknown | Unknown | Needs testing |
grafeo-memory tries a single "combined" LLM call first (facts + entities + relations in one schema). If the model cannot handle the combined schema, it falls back to separate calls automatically. Mistral models typically trigger this fallback, which means 3 API calls per add() instead of 1. The fallback is silent (logged at DEBUG level, no traceback printed).
Custom Embeddings
Implement the EmbeddingClient protocol to use any embedding provider:
from grafeo_memory import EmbeddingClient
class MyEmbedder:
def embed(self, texts: list[str]) -> list[list[float]]:
# Call your embedding API
return [...]
@property
def dimensions(self) -> int:
return 1024 # your model's output dimensions
memory = MemoryManager("openai:gpt-4o-mini", config, embedder=MyEmbedder())
MCP Server
grafeo-memory includes a built-in MCP server so AI agents (Claude Desktop, Cursor, etc.) can use it as a tool.
uv add grafeo-memory[mcp]
# or: pip install grafeo-memory[mcp]
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"grafeo-memory": {
"command": "grafeo-memory-mcp",
"env": {
"GRAFEO_MEMORY_MODEL": "openai:gpt-4o-mini",
"GRAFEO_MEMORY_DB": "./memory.db"
}
}
}
}
Available Tools
| Tool | Description |
|---|---|
memory_add |
Add a memory by extracting facts from text |
memory_add_batch |
Add multiple memories in one batch |
memory_search |
Search memories by semantic similarity and graph context |
memory_update |
Update an existing memory's text |
memory_delete |
Delete a single memory |
memory_delete_all |
Delete all memories for a user |
memory_list |
List all stored memories |
memory_summarize |
Consolidate old memories into topic-grouped summaries |
memory_history |
Show change history for a memory |
Environment Variables
| Variable | Default | Description |
|---|---|---|
GRAFEO_MEMORY_MODEL |
openai:gpt-4o-mini |
pydantic-ai model string |
GRAFEO_MEMORY_DB |
(in-memory) | Database file path |
GRAFEO_MEMORY_USER |
default |
Default user ID |
GRAFEO_MEMORY_YOLO |
(off) | Set to 1 for all features |
Transport
Supports stdio (default), SSE and streamable HTTP:
grafeo-memory-mcp # stdio (default)
grafeo-memory-mcp sse # SSE
grafeo-memory-mcp streamable-http
Note: This is different from grafeo-mcp, which exposes the raw GrafeoDB database. grafeo-memory-mcp wraps the high-level memory API (extract, reconcile, search, summarize).
Observability
grafeo-memory supports OpenTelemetry instrumentation via pydantic-ai. When enabled, all LLM calls (extraction, reconciliation, summarization, reranking) are traced automatically.
config = MemoryConfig(instrument=True) # uses global OTel provider
For custom providers:
from grafeo_memory import InstrumentationSettings
config = MemoryConfig(instrument=InstrumentationSettings(
tracer_provider=my_tracer_provider,
include_content=False,
))
Why grafeo-memory?
| Traditional stack | grafeo-memory | |
|---|---|---|
| Infrastructure | Neo4j + Qdrant (Docker) | Single .db file |
| Install size | ~750MB (Docker images) | ~16MB (uv add) |
| Offline/edge | Requires servers | Yes |
| Graph + vector | Separate services | Unified engine |
| LLM providers | Varies | pydantic-ai (OpenAI, Anthropic, Mistral, Groq, Google) |
| Embeddings | External API required | Protocol-based (any provider) |
API Reference
MemoryManager
MemoryManager(model, config=None, *, embedder): create memory manager.modelis a pydantic-ai model string (e.g."openai:gpt-4o-mini").add(messages, user_id=None, session_id=None, metadata=None, *, infer=True, importance=1.0, memory_type="semantic")→AddResult(list ofMemoryEvent).search(query, user_id=None, k=10, *, filters=None, rerank=True, memory_type=None)→SearchResponse(list ofSearchResult).update(memory_id, text)→MemoryEvent: update a memory's text directly.get_all(user_id=None, memory_type=None)→list[SearchResult].delete(memory_id)→bool.delete_all(user_id=None)→int(count deleted).summarize(user_id=None, *, preserve_recent=5, batch_size=20)→AddResult.history(memory_id)→list[HistoryEntry]: returns change history sorted by timestamp (oldest first).set_importance(memory_id, importance)→bool.close(): close the database
Use as a context manager: with MemoryManager(...) as memory:. Multiple sessions in the same process are supported.
MemoryConfig
db_path: path to database file (None for in-memory)user_id: default user scope (default"default")session_id: default session scopeagent_id: default agent scopereconciliation_threshold: minimum cosine similarity (0.0 to 1.0) for a memory to be considered a reconciliation candidate duringadd(). Lower values find more candidates, higher values require closer matches. At 0.3 (default), loosely related memories are considered. At 0.7+, only very similar memories trigger UPDATE/DELETE decisionssearch_min_score: minimum score for search results, 0.0 returns everything (default 0.0)agreement_bonus: score boost when both vector and graph find the same memory (default 0.1)embedding_dimensions: vector dimensions (default 1536)enable_importance: enable composite scoring with recency/frequency/importance (default False)weight_topology: topology score weight for graph-connected memories (default 0.0, requiresenable_importance)enable_topology_boost: re-rank search results by graph connectivity, no LLM call (default False)topology_boost_factor: strength of topology boost (default 0.2)consolidation_protect_threshold: protect well-connected memories from summarize (default 0.0, off)instrument: OpenTelemetry instrumentation,TrueorInstrumentationSettings(default False)
EmbeddingClient (Protocol)
.embed(texts: list[str]) -> list[list[float]]: generate embeddings for a batch of texts.dimensions -> int: return the embedding vector dimensionality
Return Types
AddResult: list subclass ofMemoryEvent, with.usagefor LLM token countsSearchResponse: list subclass ofSearchResult, with.usagefor LLM token countsMemoryEvent:.action(ADD/UPDATE/DELETE/NONE),.memory_id,.text,.old_textSearchResult:.memory_id,.text,.score,.user_id,.metadata,.relations,.memory_typeHistoryEntry:.event(ADD/UPDATE/DELETE),.old_text,.new_text,.timestamp(epoch ms),.actor_id,.role. Returned sorted by timestamp ascending (oldest first)
Iteration
# AddResult is iterable:
for event in memory.add("text"):
print(event.action, event.text)
# SearchResponse is iterable:
for result in memory.search("query"):
print(result.text, result.score)
Ecosystem
grafeo-memory is part of the GrafeoDB ecosystem:
- grafeo: Core graph database engine (Rust)
- grafeo-langchain: LangChain integration
- grafeo-llamaindex: LlamaIndex integration
- grafeo-mcp: MCP server for raw GrafeoDB access
- grafeo-memory-mcp (built-in): MCP server for the memory API (
uv add grafeo-memory[mcp]orpip install grafeo-memory[mcp])
All packages share the same .db file. Build memories with grafeo-memory, query them with grafeo-langchain, expose them via MCP.
Requirements
- Python 3.12+
License
Apache-2.0
Metadata
Release files for grafeo-memory 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| grafeo_memory-0.2.2.tar.gz | 224.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| grafeo_memory-0.2.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 302.8 kB
Release files / grafeo_memory-0.2.2.tar.gz
| Download URL | grafeo_memory-0.2.2.tar.gz |
|---|---|
| Size | 224.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
5b56442af7f186d76403832d37b8371c116f96c42e75f9d6b14f538542035bbf
|
|
BLAKE2b-256 checksum How to use checksums |
42a7ae0ff4ee90455b1936b0565c5cf78abfd20f4e398ec8255e5c1640f69fc3
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 8, 2026.
Transparency logRelease files / grafeo_memory-0.2.2-py3-none-any.whl
| Download URL | grafeo_memory-0.2.2-py3-none-any.whl |
|---|---|
| Size | 78.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
92a1b4e4e4be4f20d9ba93a1d0d6da9dec51af69ba0eb6aa7b3037f4fa02bd53
|
|
BLAKE2b-256 checksum How to use checksums |
37430502be31bc702bb0482a85090794c2a94e260f50171a2aa5637ff52085ee
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 8, 2026.
Transparency log