Persistent memory for AI agents. Three methods. Zero infra.
Project description
kemi
Persistent memory for AI agents. Three methods. Zero infra.
from kemi import MemoryService
memory = MemoryService() # SQLite + local embeddings, no API keys needed
memory.remember("user123", "User prefers dark mode")
memory.remember("user123", "User is vegetarian")
results = memory.recall("user123", "what are the user's preferences?")
# Returns ranked, deduplicated memories
memory.forget("user123") # GDPR-compliant deletion
Note:
from kemi import Memorystill works as a backwards-compatible alias. New code should importMemoryServicedirectly.
Why kemi?
Every other memory library either hosts your data on their servers, requires Docker and 4 services to run, or locks you into a specific framework.
kemi is different:
| Zero infrastructure | pip install kemi, no Docker, no cloud, no setup |
| Zero hard dependencies | Only the core library + SQLite. Optional backends live behind extras ([chroma], [qdrant], [postgres], etc.) |
| Your data stays yours | Stored in SQLite on your machine, never leaves |
| Bring your own embedding | OpenAI, local models (fastembed), or any function |
| Framework agnostic | Works with LangChain, CrewAI, AutoGen, or plain Python |
| MCP ready | Use as a memory server for Claude Desktop, Cursor, and Continue |
| 100% free | MIT license, no paid tiers, no cloud lock-in |
Install
pip install kemi # Core only — zero hard dependencies
pip install "kemi[local]" # + local embeddings (no API key, ~130MB download)
pip install "kemi[openai]" # + OpenAI embeddings (1536-dim)
pip install "kemi[postgres]" # + PostgreSQL + pgvector (ANN, FTS, hybrid search)
pip install "kemi[mcp]" # + MCP server (Claude Desktop, Cursor, Continue)
pip install "kemi[langchain]" # + LangChain BaseChatMemory adapter
pip install "kemi[chroma]" # + ChromaDB vector store
pip install "kemi[qdrant]" # + Qdrant vector store
pip install "kemi[redis]" # + Redis vector store
pip install "kemi[all]" # Everything (heavy)
Quick Start
Zero-config (local embeddings)
from kemi import MemoryService
memory = MemoryService()
memory.remember("user123", "User is vegetarian", importance=0.9)
results = memory.recall("user123", "food preferences")
With OpenAI embeddings
from kemi import MemoryService
from kemi.adapters.embedding.openai import OpenAIEmbedAdapter
memory = MemoryService(embed=OpenAIEmbedAdapter())
memory.remember("user123", "User prefers concise responses")
results = memory.recall("user123", "communication style")
With PostgreSQL + pgvector
from kemi import MemoryService
from kemi.adapters.storage.postgres import PostgresStorageAdapter
store = PostgresStorageAdapter(dsn="postgresql://user:pass@localhost:5432/kemi", embedding_dim=384)
memory = MemoryService(store=store)
memory.remember("user123", "User prefers dark mode")
results = memory.recall("user123", "color theme preferences")
Inject into system prompts
context = memory.context_block("user123", query="user preferences", max_tokens=500)
# Returns formatted string ready to paste into an LLM system prompt
Async (FastAPI)
from fastapi import FastAPI
from kemi import MemoryService
app = FastAPI()
memory = MemoryService()
@app.post("/chat")
async def chat(user_id: str, message: str):
await memory.aremember(user_id, message)
context = await memory.acontext_block(user_id, message)
return {"context": context}
GDPR-compliant deletion
memory.forget("user123") # Delete all memories for a user
memory.forget("user123", memory_id) # Delete one specific memory
Features
| Feature | What it does |
|---|---|
| Semantic deduplication | "I'm vegetarian" and "I don't eat meat" are detected as the same memory |
| Importance-weighted scoring | Recent, important memories rank higher in search results |
| Temporal decay | Memories fade if never recalled -- transitions from ACTIVE to DECAYING |
| Conflict detection | Flags contradictory memories ("I love coffee" vs "I hate coffee") |
| Hybrid search | Combines semantic (vector) search with keyword (BM25) search |
| MMR reranking | Ensures diverse results -- not 5 nearly-identical memories |
| Lifecycle management | Automatic state transitions: ACTIVE -> DECAYING -> ARCHIVED -> DELETED |
| Query decomposition | Breaks complex queries into sub-queries with Reciprocal Rank Fusion |
| Entity extraction | Zero-dependency regex or spaCy-based entity linking |
| Version history | Track changes to memories with rollback support |
| Webhooks | Dispatch lifecycle events (remembered, updated, deleted, conflict) to HTTP endpoints |
| Audit trail | Compliance-grade operation log with retention and export |
| Plugin system | Four extension points: WebhookSink, AuditSink, QueryCacheProvider, HookSink |
MCP Server
Any MCP-compatible agent (Claude Desktop, Cursor, Continue) can use kemi as its memory layer:
pip install "kemi[mcp]"
python -m kemi
Claude can then remember facts about you across sessions -- no API keys, no cloud, everything local.
Exposed tools
remember, recall, recall_stream, recall_explain, forget, context_block, prune, stats, consolidate, topics, graph, list_users
Adapters
| Type | Default | Alternatives |
|---|---|---|
| Embedding | fastembed (local, 384-dim) | OpenAI (1536-dim), custom function |
| Storage | SQLite (WAL mode) | SQLite-vec (ANN), PostgreSQL + pgvector, Redis, Qdrant, Chroma, JSON file, custom |
Integrations
LangChain
from kemi import MemoryService
from kemi.integrations.langchain import KemiMemory
memory = MemoryService()
chat_memory = KemiMemory(user_id="alice", memory=memory)
LangGraph / CrewAI / AutoGen
kemi works with any framework. Just use the core remember / recall / forget methods wherever you need persistent memory.
Export / Import
memory.export("backup.json") # backup all memories
memory.import_from("backup.json") # restore from backup
CLI
kemi remember user123 "User prefers dark mode"
kemi recall user123 "preferences"
kemi forget user123
kemi stats user123
kemi export backup.json
kemi import backup.json
Use --json for machine-readable output or --quiet to suppress info messages.
Documentation
| Guide | What you'll learn |
|---|---|
| Architecture | Module map, data flow diagrams, plugin extension points |
| Quickstart | Get running in 5 minutes |
| Recipes | Complete working examples |
| Configuration | Tuning kemi for your use case |
| Adapters | Embeddings, storage, custom implementations |
| API Stability | Stability tiers and deprecation policy |
| Contributing | How to contribute, changelog conventions |
Data Privacy
kemi is designed so your data never leaves your machine:
- All memories stored in local SQLite at
~/.kemi/memories.db - Embeddings computed locally (fastembed) or via your own API key (OpenAI)
- No telemetry, no analytics, no phone-home
- Full GDPR-compliant deletion with
memory.forget() - Optional field-level Fernet encryption for content, metadata, and user IDs
Requirements
- Python 3.10+
License
MIT -- free forever, no exceptions.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file kemi-0.4.0.tar.gz.
File metadata
- Download URL: kemi-0.4.0.tar.gz
- Upload date:
- Size: 2.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2092c8fe2e2fe9db53da3b860187de6db93e723da2dd4a72143a14ebc84ab7fd
|
|
| MD5 |
041d06625256ef0e3eb89f259a44f1a5
|
|
| BLAKE2b-256 |
82d31481acbde61237c3955eda6d1c9555cab138a0dd796b68674a933ad2214c
|
File details
Details for the file kemi-0.4.0-py3-none-any.whl.
File metadata
- Download URL: kemi-0.4.0-py3-none-any.whl
- Upload date:
- Size: 230.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
60c609b1a0fe4c93b3e676d6a95cbcb2450153565de33a3915fb983e01187959
|
|
| MD5 |
51f5627da5d689865a5401956f3de37c
|
|
| BLAKE2b-256 |
546fe9caa81de294a83bac27738a9b1c64a08e2aa0f4df64468d51461e3d082a
|