EngramX — The open-source agent memory framework with pluggable storage, lifecycle policies, observability, and framework adapters.
Project description
The Open-Source Agent Memory Framework
Why EngramX?
An engram (from the Greek engramma, "that which is written on") is the neuroscience term for the physical trace a memory leaves in the brain. EngramX brings the same idea to AI agents: structured, durable, queryable memory that works across any framework or backend.
The current ecosystem for agent memory is fragmented, framework-locked, and under-tooled. Every platform defines memory differently, uses incompatible schemas, and provides minimal support for debugging, governance, or cross-framework composability. EngramX is the neutral abstraction layer that sits under or beside any of them.
How EngramX Compares
| Dimension | Mem0 | Zep | Letta/MemGPT | LangChain | EngramX |
|---|---|---|---|---|---|
| Common memory schema | Proprietary | Proprietary | Proprietary | Per-pattern | Canonical |
| Episodic / Semantic / Procedural | Partial | Partial | Partial (tiers) | Partial | First-class |
| Pluggable backends | Limited | No | No | Partial | 9 drivers |
| Declarative lifecycle policies | Partial | No | No | No | YAML DSL |
| Retrieval explainability | Dashboard | No | ADE only | No | Built-in API |
| Right-to-be-forgotten | Cloud tier | No | No | No | Policy-driven |
| Framework adapters | SDK only | API only | No | Native only | LangChain, LlamaIndex, AutoGen |
| Built-in test harness | No | No | No | No | MemoryHarness |
| Composable with others | No | No | No | No | Wraps Mem0/Zep |
Features
- Canonical schema —
MemoryRecordwith episodic, semantic, procedural, and meta types - 9 storage backends — SQLite, PostgreSQL + pgvector, ChromaDB, Qdrant, Redis, Neo4j, Mem0, Zep, and in-memory
- Policy engine — Declarative YAML rules for extraction, retention decay, summarization/promotion, and GDPR governance
- Observability — Retrieval traces, memory timelines, output attribution, and
explain()as a first-class API - Hybrid retrieval — Lexical + vector + importance + decay scoring with configurable modes
- Embedding providers — OpenAI, Gemini, Cohere, Mistral, Together AI, LiteLLM (100+ providers), Sentence Transformers, and built-in hash embedder
- LLM reflection — Pluggable reflectors (OpenAI, Anthropic, LiteLLM, or any OpenAI-compatible provider via
base_url) for summarizing episodic memory into durable knowledge - Framework adapters — Thin integrations for LangChain, LlamaIndex, and AutoGen
- Agent runner —
EngramAgentwithrun_with_attribution()for tracking which memories influenced output - Background jobs —
PolicyJobSchedulerfor scheduled decay, promotion, and governance enforcement - Test harness —
MemoryHarnessfor testing memory behavior in CI - Async-first — Full async API with sync wrappers for convenience
Installation
# pip
pip install engramx
# uv
uv add engramx
Install latest alpha directly from GitHub:
# pip
pip install git+https://github.com/TechyNilesh/EngramX.git
# uv
uv add git+https://github.com/TechyNilesh/EngramX.git
Install with optional backends:
pip install engramx[postgres] # PostgreSQL + pgvector
pip install engramx[chroma] # ChromaDB
pip install engramx[qdrant] # Qdrant
pip install engramx[redis] # Redis
pip install engramx[neo4j] # Neo4j
pip install engramx[mem0] # Mem0 delegation
pip install engramx[zep] # Zep delegation
pip install engramx[openai] # OpenAI embeddings + reflection
pip install engramx[anthropic] # Anthropic reflection
pip install engramx[litellm] # LiteLLM (100+ providers)
pip install engramx[sentence-transformers] # Local embeddings
pip install engramx[langchain] # LangChain adapter
pip install engramx[llamaindex] # LlamaIndex adapter
pip install engramx[autogen] # AutoGen adapter
pip install engramx[langgraph] # LangGraph agent memory
pip install engramx[all] # Everything
Install from source (development):
git clone https://github.com/TechyNilesh/EngramX.git
cd EngramX
pip install -e .[dev]
Quick Start
import asyncio
from engramx import MemoryClient
async def main() -> None:
client = MemoryClient(driver="memory")
# Store a memory
memory_id = await client.add(
type="semantic",
scope="user",
user_id="u-123",
content="User prefers metric units for measurements.",
source="conversation",
importance_score=0.8,
sensitivity_level="internal",
)
# Search memories
results = await client.search(
query="unit preferences",
filters={"scope": "user", "user_id": "u-123", "type": "semantic"},
top_k=5,
)
# Explain retrieval
trace = await client.explain(
query="unit preferences",
filters={"user_id": "u-123"},
)
print(results[0].record.content)
print(trace.to_markdown())
asyncio.run(main())
Storage Backends
EngramX decouples the API from storage via a BaseDriver interface. All 9 drivers implement the same contract.
| Driver | Backend | Best for | Install extra |
|---|---|---|---|
InMemoryDriver |
Python dict | Tests, prototyping | — |
SQLiteDriver |
SQLite | Local dev, single-agent | — |
PostgresDriver |
PostgreSQL + pgvector | Production, vector search | postgres |
ChromaDriver |
ChromaDB | Lightweight semantic search | chroma |
QdrantDriver |
Qdrant | High-performance vector search | qdrant |
RedisDriver |
Redis / Valkey | Fast short-term memory, caching | redis |
Neo4jDriver |
Neo4j | Graph-structured memory, temporal edges | neo4j |
Mem0Driver |
Mem0 API | Delegate to Mem0 + EngramX observability | mem0 |
ZepDriver |
Zep API | Delegate to Zep's temporal knowledge graph | zep |
from engramx import MemoryClient
# Use any driver by name
client = MemoryClient(driver="sqlite")
client = MemoryClient(driver="postgres")
client = MemoryClient(driver="chroma")
client = MemoryClient(driver="qdrant")
client = MemoryClient(driver="redis")
client = MemoryClient(driver="neo4j")
# Or pass a driver instance directly
from engramx import PostgresDriver
client = MemoryClient(PostgresDriver(dsn="postgresql://localhost/engramx"))
Memory Types
EngramX enforces meaningful separation of three cognitive memory types:
Episodic — Raw experiences: conversation turns, tool calls, agent decisions.
await client.add(type="episodic", scope="session", content="Payment API returned 402.", source="tool_call")
Semantic — Distilled facts: user preferences, domain knowledge, stable truths.
await client.add(type="semantic", scope="user", content="User prefers metric units.", source="reflection")
Procedural — Learned skills: action templates and decision heuristics from repeated success.
await client.add(type="procedural", scope="agent", content="Check visa before searching flights.", source="reflection")
Core API
All methods are async-first with sync wrappers (add_sync, search_sync).
| Method | Description |
|---|---|
add() |
Store a memory |
search() |
Hybrid search (lexical + vector + importance + decay) |
get() |
Retrieve by ID (with access control) |
update() |
Patch memory fields |
delete() |
Delete by filter expressions (GDPR bulk delete) |
explain() |
Retrieval trace with scores, decay factors, matched terms |
timeline() |
Ordered history of all memory operations |
ingest_event() |
Process a signal through the policy engine |
promote() |
Promote episodic patterns to procedural memory |
apply_decay() |
Apply retention decay to all memories |
forget_user() |
Right-to-be-forgotten workflow |
run_with_attribution() |
Execute task and track which memories influenced the output |
Policy Configuration
Lifecycle policies are declarative YAML — no imperative code required.
# engramx.yaml
driver:
kind: sqlite
path: .engramx/engramx.db
policies:
extraction:
- name: extract-user-preferences
trigger: conversation_turn
conditions:
- content_matches: [prefer, always, never, like]
create:
type: semantic
scope: user
importance_score: 0.8
sensitivity_level: internal
retention:
- name: session-episode-decay
applies_to: { type: episodic, scope: session }
decay_function: exponential
half_life_days: 7
floor_score: 0.1
summarization:
- name: promote-repeated-success
applies_to: { type: episodic }
trigger: "same_action_success_count >= 3"
promote_to:
type: procedural
scope: agent
content: "Confirm prerequisites before executing repeated workflows."
governance:
- name: gdpr-user-data
applies_to:
scope: user
sensitivity_level: [confidential, restricted]
retention_days: 30
on_user_deletion: delete_all
audit_log: true
from engramx import MemoryClient
client = MemoryClient(config="engramx.yaml")
Embedding Providers
EngramX ships with a dependency-free hash embedder and supports real embedding models. The OpenAI embedder uses the OpenAI SDK format which works with any compatible provider via base_url.
from engramx import create_embedder
# Default: hash-based (no dependencies, deterministic)
embedder = create_embedder("hash")
# OpenAI
embedder = create_embedder("openai", model="text-embedding-3-small")
# Google Gemini (OpenAI-compatible endpoint)
embedder = create_embedder("openai",
model="gemini-embedding-001",
api_key="GEMINI_KEY",
base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
)
# Cohere (OpenAI-compatible endpoint)
embedder = create_embedder("openai",
model="embed-english-v3.0",
api_key="COHERE_KEY",
base_url="https://api.cohere.ai/compatibility/v1",
)
# Mistral
embedder = create_embedder("openai",
model="mistral-embed",
api_key="MISTRAL_KEY",
base_url="https://api.mistral.ai/v1",
)
# Together AI
embedder = create_embedder("openai",
model="togethercomputer/m2-bert-80M-8k-retrieval",
api_key="TOGETHER_KEY",
base_url="https://api.together.xyz/v1",
)
# LiteLLM — unified interface for 100+ providers
embedder = create_embedder("litellm", model="cohere/embed-english-v3.0")
embedder = create_embedder("litellm", model="gemini/gemini-embedding-001")
embedder = create_embedder("litellm", model="bedrock/amazon.titan-embed-text-v1")
# Sentence Transformers (local models)
embedder = create_embedder("sentence_transformers", model_name="all-MiniLM-L6-v2")
# Pass to client
client = MemoryClient(driver="memory", embedder=embedder)
| Provider | Via | Install |
|---|---|---|
| OpenAI | create_embedder("openai") |
pip install engramx[openai] |
| Google Gemini | create_embedder("openai", base_url=...) |
pip install engramx[openai] |
| Cohere | create_embedder("openai", base_url=...) |
pip install engramx[openai] |
| Mistral | create_embedder("openai", base_url=...) |
pip install engramx[openai] |
| Together AI | create_embedder("openai", base_url=...) |
pip install engramx[openai] |
| LiteLLM (100+) | create_embedder("litellm") |
pip install engramx[litellm] |
| Sentence Transformers | create_embedder("sentence_transformers") |
pip install engramx[sentence-transformers] |
| Hash (local, no deps) | create_embedder("hash") |
— |
LLM Reflection
Summarize episodic memory into durable knowledge using LLM-powered reflection:
from engramx import create_reflector
# OpenAI
reflector = create_reflector("openai", model="gpt-4o-mini")
# Any OpenAI-compatible provider via base_url
reflector = create_reflector("openai",
model="gemini-2.0-flash",
api_key="GEMINI_KEY",
base_url="https://generativelanguage.googleapis.com/v1beta/openai/",
)
# Anthropic
reflector = create_reflector("anthropic", model="claude-sonnet-4-20250514")
# LiteLLM — any of 100+ providers
reflector = create_reflector("litellm", model="anthropic/claude-sonnet-4-20250514")
reflector = create_reflector("litellm", model="gemini/gemini-2.0-flash")
reflector = create_reflector("litellm", model="cohere/command-r-plus")
Framework Adapters
LangChain
from engramx import MemoryClient
from engramx.adapters import EngramChatMemory
client = MemoryClient(driver="sqlite")
memory = EngramChatMemory(client=client, user_id="u-123")
# Works as a LangChain memory drop-in
context = memory.load_memory_variables({"input": "What units do I prefer?"})
memory.save_context({"input": "Use metric"}, {"output": "Noted!"})
LlamaIndex
from engramx import MemoryClient
from engramx.adapters import EngramMemoryBlock
client = MemoryClient(driver="sqlite")
block = EngramMemoryBlock(client=client, user_id="u-123")
context = block.get("unit preferences")
block.put("User prefers metric units.")
AutoGen
from engramx import MemoryClient
from engramx.adapters import EngramAutoGenMemory
client = MemoryClient(driver="sqlite")
memory = EngramAutoGenMemory(client=client, user_id="u-123")
results = memory.query("payment preferences")
memory.add("User prefers credit card payments.")
LangGraph
Use EngramX as long-term memory in LangGraph agents. EngramX handles cross-session memory while LangGraph checkpoints handle short-term thread state.
from langgraph.graph import StateGraph, START, END
from langgraph.prebuilt import ToolNode
from langchain_openai import ChatOpenAI
from typing import Annotated, TypedDict
import operator
from engramx import MemoryClient
# Initialize EngramX for long-term memory
engramx = MemoryClient(driver="sqlite")
model = ChatOpenAI(model="gpt-4o-mini")
# 1. Define graph state
class AgentState(TypedDict):
messages: Annotated[list, operator.add]
memory_context: str
user_id: str
# 2. Memory retrieval node — runs before LLM
async def recall_memories(state: AgentState) -> dict:
"""Retrieve relevant EngramX memories and inject into context."""
user_msg = state["messages"][-1].content if state["messages"] else ""
user_id = state.get("user_id", "default")
memories = await engramx.search(
query=user_msg,
filters={"user_id": user_id},
top_k=5,
)
if memories:
context = "\n".join(f"- {m.record.content}" for m in memories)
else:
context = "No relevant memories found."
return {"memory_context": context}
# 3. LLM node — uses memories as context
async def call_llm(state: AgentState) -> dict:
"""Call LLM with memory-augmented context."""
system_msg = {
"role": "system",
"content": (
"You are a helpful assistant with memory.\n"
f"Relevant memories:\n{state.get('memory_context', 'None')}"
),
}
response = await model.ainvoke([system_msg] + state["messages"])
return {"messages": [response]}
# 4. Memory persistence node — runs after LLM response
async def persist_memories(state: AgentState) -> dict:
"""Store the conversation turn as episodic memory in EngramX."""
user_id = state.get("user_id", "default")
messages = state["messages"]
if len(messages) >= 2:
user_msg = messages[-2].content
ai_msg = messages[-1].content
await engramx.add(
type="episodic",
scope="user",
user_id=user_id,
content=f"User: {user_msg}\nAssistant: {ai_msg}",
source="conversation",
importance_score=0.7,
)
return {}
# 5. Build the graph
graph = StateGraph(AgentState)
graph.add_node("recall", recall_memories)
graph.add_node("llm", call_llm)
graph.add_node("persist", persist_memories)
graph.add_edge(START, "recall")
graph.add_edge("recall", "llm")
graph.add_edge("llm", "persist")
graph.add_edge("persist", END)
agent = graph.compile()
# 6. Run
# result = await agent.ainvoke({
# "messages": [HumanMessage(content="What units should I use?")],
# "user_id": "u-123",
# })
LangGraph + EngramX architecture:
┌─────────────────────────────────────────────────┐
│ LangGraph Agent │
│ │
│ START → [recall] → [llm] → [persist] → END │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ EngramX │ │ EngramX │ │
│ │ search() │ │ add() │ │
│ │ (long-term) │ │ (long-term) │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ Short-term state: LangGraph checkpoints │
│ Long-term memory: EngramX (any backend) │
└─────────────────────────────────────────────────┘
Agent with Attribution
Track which memories influenced agent output:
from engramx import MemoryClient
client = MemoryClient(driver="memory")
async def my_llm(prompt, memories):
return "Response based on memories"
response, attribution = await client.run_with_attribution(
"Book a flight to Tokyo",
user_id="u-123",
runner=my_llm,
)
print(attribution.memories_used) # ["mem-abc", "mem-def"]
print(attribution.policies_fired) # ["extract-user-preferences"]
Or use the standalone EngramAgent with OpenAI:
from engramx import MemoryClient, EngramAgent
client = MemoryClient(driver="sqlite")
agent = EngramAgent(client=client, model="gpt-4o-mini")
response, attribution = await agent.run_with_attribution(
"What units should I use?",
user_id="u-123",
)
Observability
Every retrieval is traceable:
trace = await client.explain(query="unit preferences", filters={"user_id": "u-123"})
print(trace.to_markdown())
Output:
# Memory Trace
- Query: `unit preferences`
- Filters: `{'user_id': 'u-123'}`
| Memory ID | Score | Decay Adjusted | Matched Terms | Policy Filters |
| --- | ---: | ---: | --- | --- |
| `mem-abc-123` | 0.870 | 0.740 | metric, unit | scope=user |
Timeline of all operations:
events = await client.timeline(user_id="u-123", types=["episodic", "semantic"])
for event in events:
print(f"{event.timestamp} {event.operation} {event.memory_id}")
Background Jobs
Schedule automatic decay, promotion, and governance enforcement:
from engramx import MemoryClient, PolicyJobScheduler
client = MemoryClient(config="engramx.yaml")
scheduler = PolicyJobScheduler(
driver=client.driver,
policy_engine=client.policy_engine,
interval_seconds=300, # Run every 5 minutes
)
# Run once
report = await scheduler.run_once()
print(f"Decayed: {len(report.decay_updated_ids)}")
print(f"Promoted: {len(report.promoted_ids)}")
print(f"Deleted: {len(report.deleted_ids)}")
# Or run continuously
await scheduler.start()
# ... later ...
await scheduler.stop()
Chat Example
Example chat loop with persistent memory:
from openai import OpenAI
from engramx import MemoryClient
openai_client = OpenAI()
memory = MemoryClient(driver="sqlite")
def chat_with_memories(message: str, user_id: str = "default_user") -> str:
relevant = memory.search_sync(query=message, filters={"user_id": user_id}, top_k=3)
memories_str = "\n".join(f"- {m.record.content}" for m in relevant) or "- None yet."
response = openai_client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": f"You are a helpful AI.\nUser Memories:\n{memories_str}"},
{"role": "user", "content": message},
],
)
reply = response.choices[0].message.content or ""
memory.add_sync(
type="episodic", scope="user", user_id=user_id,
content=f"User: {message}\nAssistant: {reply}",
source="conversation", importance_score=0.7,
)
return reply
while True:
user_input = input("You: ").strip()
if user_input.lower() == "exit":
break
print(f"AI: {chat_with_memories(user_input)}")
Testing
pip install -e .[dev]
python -m pytest -q
The test suite covers storage drivers, the policy engine, test harness, adapters, background jobs, retrieval features, and optional driver dependency checks.
Development
git clone https://github.com/TechyNilesh/EngramX.git
cd EngramX
python -m venv .venv
source .venv/bin/activate
pip install -e .[dev]
python -m pytest -q
Architecture
engramx/
schema.py # MemoryRecord, MemoryType, MemoryScope, SensitivityLevel
client.py # MemoryClient — unified API
config.py # YAML config loading
policy.py # PolicyEngine — extraction, decay, promotion, governance
lifecycle.py # Lifecycle functions — extract, decay, summarize, governance
observability.py # MemoryTrace, ScoredMemory, OutputAttribution, MemoryTimelineEvent
embedding.py # Hash, OpenAI, SentenceTransformer embedders
reflection.py # LLM reflectors (OpenAI, Anthropic)
agent.py # EngramAgent with run_with_attribution()
jobs.py # PolicyJobScheduler for background lifecycle jobs
models.py # MemorySignal (event model)
testing.py # MemoryHarness for CI
storage/
base.py # BaseDriver abstract interface
memory.py # InMemoryDriver
sqlite.py # SQLiteDriver
postgres.py # PostgresDriver (asyncpg + pgvector)
chroma.py # ChromaDriver
qdrant.py # QdrantDriver
redis.py # RedisDriver
neo4j.py # Neo4jDriver
mem0.py # Mem0Driver (delegation)
zep.py # ZepDriver (delegation)
ranking.py # Hybrid scoring (lexical + vector + importance + decay)
adapters/
langchain.py # EngramChatMemory
llamaindex.py # EngramMemoryBlock
autogen.py # EngramAutoGenMemory
chat.py # EngramChatAdapter
Core Contributor
Nilesh Verma
Citation
@software{verma2026engramx,
author = {Nilesh Verma},
title = {EngramX: The Open-Source Agent Memory Framework},
year = {2026},
url = {https://github.com/TechyNilesh/EngramX},
version = {0.1.0}
}
License
MIT
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 engramx-0.1.0.tar.gz.
File metadata
- Download URL: engramx-0.1.0.tar.gz
- Upload date:
- Size: 61.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a9c9644993da5afade3a2b84a868ca2f633d3d52d4fe10600f34ff22d7b6b559
|
|
| MD5 |
d4a00f0814d26aaccad04e62c744fad1
|
|
| BLAKE2b-256 |
3f37f8c5dcf6a926e0a5d123bfe4af8608a9d03263e5e1dbc50c205ea0086b29
|
File details
Details for the file engramx-0.1.0-py3-none-any.whl.
File metadata
- Download URL: engramx-0.1.0-py3-none-any.whl
- Upload date:
- Size: 68.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.26 {"installer":{"name":"uv","version":"0.9.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f45d7071f29b71934a001e6b4fdfd7a45af34ca5acddecb7c3e913d60ee6b473
|
|
| MD5 |
305301fa1fdf7101f0fdca45c6983b1b
|
|
| BLAKE2b-256 |
b2a497e50cebe1e945efab8ed2b1209b0c0ef5a82c7e2985866ae64c1add384b
|