agentic-memory
README.md
<p align="center">
<img src="assets/banner.svg" alt="Agentic Memory β A Persistent, Bitemporal, Injection-Safe Memory MCP for AI Agents, by Verace Pvt. Ltd." width="100%">
</p>
<p align="center"><b>A Persistent, Bitemporal, Injection-Safe Memory MCP for AI Agents</b></p>
> A memory server for coding agents (Claude Code, Claude Desktop, Cursor, Antigravity, Codex CLI, or any
> MCP-compliant client) that goes beyond the naive "five separate buckets" model of agent memory:
> non-destructive belief revision, injection-safe context synthesis, real embeddings with ANN search,
> multi-tenant namespace isolation, and a choice of a zero-dependency SQLite backend or a
> Postgres + pgvector + Row-Level-Security backend for real deployments.
---
## ποΈ Architecture
Six memory layers behind one unified retrieval call. Diagram renders natively on GitHub (Mermaid) β
open this file on github.com if you're viewing raw markdown:
```mermaid
flowchart TD
ENV(["Real-Time Environment<br/>user queries / tool output"])
subgraph L1["1 Β· SENSORY MEMORY"]
L1D["Ring buffer, saliency-scored<br/>Retention: ms β minutes"]
end
subgraph L2["2 Β· WORKING MEMORY"]
L2D["Token-budgeted session context<br/>chronological, auto-compressing<br/>Retention: minutes β hours"]
end
subgraph L3["3 Β· EPISODIC MEMORY"]
L3D["Causal event trajectories<br/>vector similarity retrieval<br/>Retention: weeks β months"]
end
subgraph L4["4 Β· SEMANTIC MEMORY"]
L4D["Subject-predicate-object graph<br/>non-destructive belief revision<br/>hybrid vector + text + PPR search"]
end
subgraph L5["5 Β· LONG-TERM MEMORY"]
L5D["Ebbinghaus-decay retrievability<br/>Truth Maintenance System<br/>Retention: days β years"]
end
subgraph L6["6 Β· PROCEDURAL MEMORY"]
L6D["Registered skills / action recipes<br/>success-rate tracked"]
end
UQ{{"unified_query()<br/>ranks + fuses all 6 layers by one<br/>relevance function (similarity +<br/>recency + importance + graph proximity)"}}
OUT(["One synthesized,<br/>injection-safe context block"])
ENV --> L1
L1 -- "promotion on high saliency" --> L2
L2 --> L3
L2 --> L4
L3 <-.-> L4
L3 --> L5
L4 --> L5
L1 --> UQ
L2 --> UQ
L3 --> UQ
L4 --> UQ
L5 --> UQ
L6 --> UQ
UQ --> OUT
classDef sensory fill:#d1f5d3,stroke:#2e7d32,color:#1b1b1b
classDef working fill:#e3d9f7,stroke:#6a3fb5,color:#1b1b1b
classDef episodic fill:#c9dcf7,stroke:#2451a8,color:#1b1b1b
classDef semantic fill:#c3ecd9,stroke:#1b7a4d,color:#1b1b1b
classDef longterm fill:#cfe8ff,stroke:#1565c0,color:#1b1b1b
classDef procedural fill:#ffe6cc,stroke:#e07b00,color:#1b1b1b
classDef fusion fill:#111111,stroke:#111111,color:#ffffff
class L1,L1D sensory
class L2,L2D working
class L3,L3D episodic
class L4,L4D semantic
class L5,L5D longterm
class L6,L6D procedural
class UQ,OUT fusion
```
A single `unified_query` call ranks and fuses all six layers by one consistent relevance function
(embedding similarity + recency + importance + graph proximity), then returns **one** synthesized,
injection-safe context block β not six separate results the caller has to stitch together itself.
### What makes this different from a naive per-layer memory store
- **Non-destructive belief revision.** Updating a fact never silently overwrites it. The old belief is
marked superseded (with a timestamp and a pointer to what replaced it) rather than deleted β full
audit history via `get_belief_history()` / `get_semantic_belief_history()`.
- **Injection-safe context synthesis.** Every retrieved memory item is wrapped in
`<memory trust="..." source="...">` tags with an explicit preamble telling the LLM that memory content
is untrusted historical data, not instructions. **All** angle brackets in stored content are escaped
(not just the `<memory>` tag itself) β so a stored item can't forge *any* tag, including ones like
`<system>`, to break out of its fence.
- **Write-time trust isn't blind.** Every stored item carries a `trust` level. Self-reported, unverified
claims (an agent's own confidence/success/reward score) are ranking-discounted relative to system- or
user-provided facts β an agent can't inflate its own memory's influence just by claiming high
importance. A `verify_memory()` call lets a separate, more-trusted reviewer corroborate an item after
the fact and restore full ranking weight; the original writer can never call it on its own write.
- **Contradiction flagging, not silent overwrite.** A cheap antonym-predicate heuristic flags directly
conflicting facts about the same subject (e.g. "Dan LIKES pizza" vs "Dan HATES pizza") for review
instead of guessing which one is right β this is *not* general semantic contradiction detection (an
open NLP problem), just a fast, honest, zero-model check for the specific opposite-predicate case.
- **Real embeddings, with a safety net.** Defaults to `sentence-transformers` (all-MiniLM-L6-v2) when
installed; falls back to a deterministic hash embedder otherwise. The vector index dimension is
auto-detected from whichever embedder is actually active β mixing a database built with one embedder
and a process running another raises a clear, actionable error at startup instead of silently
corrupting or crashing mid-session.
- **Real ANN search, with a safety net.** Uses `hnswlib` HNSW when installed; falls back to brute-force
NumPy cosine search otherwise β and near-exhaustive queries route to brute-force even when HNSW is
available, since HNSW is optimized for k βͺ n and is measurably *slower* than a vectorized brute-force
pass once k approaches the corpus size.
- **Multi-tenant namespace isolation.** Every memory item carries a `namespace`. On the Postgres backend
this is enforced by Postgres Row-Level Security β not just an app-layer filter that a bug could skip.
Consolidation, reflection, and graph export are all namespace-scoped too β none of them mix or leak
data across tenants.
- **Two backends, one API.** SQLite by default (zero external dependencies, single-process). Postgres +
pgvector + RLS for real multi-writer, multi-tenant deployments β same `MemoryEngine` API either way.
Optional Qdrant and Milvus vector-store connectors are also included for teams that want a dedicated
vector database instead.
---
## π¦ Installation
One package, every feature included by default (real `sentence-transformers` embeddings, `hnswlib` ANN
search, and Postgres/pgvector support are all installed out of the box β no extras to pick). Not on
PyPI yet β install straight from GitHub for now:
```bash
pip install "agentic-memory @ git+https://github.com/Verace-Pvt-Ltd/agentic-memory.git"
```
Or clone and install locally:
```bash
git clone https://github.com/Verace-Pvt-Ltd/agentic-memory.git
cd agentic-memory
pip install -e .
```
### Connect it to your AI coding tool
**Claude Code (recommended β plugin marketplace, gets autonomous hooks too):**
```
/plugin marketplace add Verace-Pvt-Ltd/agentic-memory
/plugin install agentic-memory@agentic-memory
```
Same two steps non-interactively (CI, setup scripts) via the CLI:
```bash
claude plugin marketplace add Verace-Pvt-Ltd/agentic-memory
claude plugin install agentic-memory@agentic-memory
```
This registers the MCP server through Claude Code's [plugin marketplace system](https://code.claude.com/docs/en/plugin-marketplaces)
and auto-registers 9 autonomous hooks (`hooks/hooks.json`) that read and write memory on every turn
without the agent needing to call an MCP tool explicitly β see
[Autonomous operation via hooks](#autonomous-operation-via-hooks) below. Verify with
`claude plugin details agentic-memory@agentic-memory`, which should list `Status: enabled` and
`Hooks (9)`. The Python package still needs to be installed separately first (see
[Installation](#-installation) above) β the plugin distributes the MCP registration, not a bundled
Python runtime.
**Any other tool (Cursor, Antigravity, Codex CLI, Claude Desktop, or any other MCP-capable agent):**
see **[SETUP.md](SETUP.md)** β copy one prompt into your agent's chat and it detects your tool and
configures itself, including autonomous hooks where that tool supports them.
<details>
<summary>Manual installation per tool (if you'd rather not use the plugin marketplace or SETUP.md prompt)</summary>
**Claude Code (manual, no plugin system β MCP tools only, no autonomous hooks):**
```bash
claude mcp add agentic-memory -- agentic-memory --transport stdio
```
**Any other MCP client** (Claude Desktop, Cursor, Antigravity, Codex CLI β MCP tools only, no
autonomous hooks unless configured per [SETUP.md](SETUP.md)):
```bash
# Auto-install into Claude Desktop's config
agentic-memory --setup-claude
# Print copy-paste config snippets for Claude Desktop, Cursor, Antigravity, and Codex CLI
agentic-memory --print-config
```
</details>
MCP (Model Context Protocol) is a shared standard across all of these β the same server works with any
of them via:
```json
{
"mcpServers": {
"agentic-memory": {
"command": "agentic-memory",
"args": ["--transport", "stdio"]
}
}
}
```
---
## πͺ Autonomous operation via hooks
MCP tools require the agent to decide to call them. Hooks make memory read/write happen
automatically, tied to the host tool's own lifecycle events (a new session starting, a prompt
being submitted, a tool call succeeding or failing, the session stopping) β no explicit tool
call needed. Four tools have a real hook/lifecycle system this project integrates with, each
adapter living in its own module (`memory/hooks.py`, `memory/hooks_antigravity.py`,
`memory/hooks_cursor.py`, `memory/hooks_codex.py`) with a matching config template under
`hooks/`. Confidence differs per platform β verified against real installed binaries where one
was available in this environment, doc-derived only where it wasn't:
| Tool | Events wired | Confidence |
| :--- | :--- | :--- |
| **Claude Code** (plugin marketplace) | SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, PostToolUseFailure, Stop, TaskCompleted, PreCompact, SessionEnd | **Confirmed working end-to-end** β verified via `claude plugin details`, which reports `Status: enabled` and `Hooks (9)` against a real installed instance. |
| **Antigravity** (`agy`) | PreToolUse, PostToolUse, PreInvocation, Stop | **Confirmed to execute** β a real tool-triggering prompt through a live `agy` install produced the hook's own stderr output (model load) in `agy`'s logs, with no error. The resulting database write location wasn't independently confirmable from outside `agy`'s process (possible subprocess sandboxing), so treat as working-but-not-fully-proven rather than broken. |
| **Cursor** (`cursor-agent` / Cursor IDE) | sessionStart, beforeSubmitPrompt, beforeShellExecution, afterShellExecution, beforeMCPExecution, afterMCPExecution, afterFileEdit, afterAgentResponse, stop, sessionEnd | **Schema confirmed valid** against Cursor's own docs; **not confirmed to fire** β `cursor-agent` requires an interactive login this environment didn't have. A community report also states several of these (sessionStart, beforeSubmitPrompt, stop, afterAgentResponse) are documented but don't currently execute in Cursor CLI specifically, only in the Cursor IDE β CLI users may see partial coverage until this is independently confirmed. |
| **Codex CLI** | SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, PreCompact, SessionEnd | **Doc-derived only** β no `codex` binary was available in this environment to verify against, so neither the schema nor the firing behavior has been live-tested. |
Claude Desktop and any other generic MCP client have no hook/lifecycle system to integrate
with β they get the 16 MCP tools only, called explicitly by the agent.
See [SETUP.md](SETUP.md) for the exact install steps per platform, including where each
`hooks/*.json` template needs to be copied.
---
## π Optional: Postgres / Supabase backend
For a real deployment with concurrent multi-process writers and enforced multi-tenant isolation, run
Agentic Memory against Postgres + pgvector instead of SQLite. Self-hosted via Docker, no cloud account
required:
```bash
docker compose up -d # starts Postgres + pgvector (bare, unconfigured)
python -m memory.backends.postgres --migrate --dsn postgresql://postgres:postgrespassword@localhost:5433/memory
```
The migration step auto-detects the correct vector column dimension from whichever embedder is active
in your Python environment (via `get_embedding_dim()`) and generates a secure app-role password if you
don't supply one β nothing about the schema is hand-edited or hardcoded.
```python
from memory.backends.postgres import PostgresBackend
from memory.engine import MemoryEngine
backend = PostgresBackend(dsn="postgresql://postgres:postgrespassword@localhost:5433/memory")
engine = MemoryEngine(backend=backend)
```
Row-Level Security policies enforce `namespace` isolation at the database engine level β a query for
`namespace="tenant_a"` genuinely cannot see `tenant_b`'s rows, even if application code has a bug.
**Known limitations of the self-hosted Docker setup**: no automated backups/point-in-time recovery, no
high-availability/failover (single-container Postgres), and no encryption-at-rest beyond whatever the
underlying disk provides β all buildable, none included out of the box today.
---
## π MCP Tools
| Tool | Purpose |
| :--- | :--- |
| `sensory_ingest` | Ingest a real-time event into sensory memory |
| `working_memory_update` | Add a turn to the active session's working memory |
| `episodic_record` | Log a task trajectory; pass `episode_id` from an earlier call to update it instead of creating a duplicate |
| `semantic_upsert` | Add/revise a subject-predicate-object fact (flags antonym contradictions) |
| `long_term_store` | Store a durable preference or fact (Truth Maintenance on update) |
| `procedural_register` | Register a reusable skill/action recipe (upserts by name β won't duplicate) |
| `unified_query` | Fused, ranked, injection-safe context across all layers; optional `top_k` cap |
| `query_as_of` | Point-in-time retrieval using bitemporal fields |
| `get_belief_history` | Full revision history for a long-term memory key |
| `get_semantic_belief_history` | Full revision history for a semantic fact |
| `verify_memory` | Mark a fact/episode/skill as corroborated by a trusted reviewer β never by its own writer |
| `forget` | GDPR-style hard delete (distinct from non-destructive supersede) |
| `evict` | Namespace-scoped capacity/decay-based pruning |
| `reflect` | Heuristic pattern synthesis over recent episodes/facts, namespace-scoped, deduplicated |
| `graph_export_dot` | Export the semantic knowledge graph as Graphviz DOT, namespace-scoped |
| `trigger_consolidation` | Run the sensoryβworkingβepisodicβsemanticβlong-term promotion cycle, namespace-scoped |
| `inspect_memory_health` | Per-layer counts and operational metrics |
Resources: `memory://sensory/stream`, `memory://working/active`, `memory://semantic/graph.dot`,
`memory://longterm/user`. Prompt: `synthesize_context`.
---
## π Python SDK Quickstart
```python
from memory import MemoryEngine
engine = MemoryEngine(db_path="memory.db") # SQLite by default
engine.sensory_ingest("User clicked urgent alert", source="ui_event")
engine.working_update("user", "Can you deploy the payments service to prod?")
engine.working_update("assistant", "Running the blue-green deploy pipeline now.")
engine.episodic_record(
title="Payments deploy",
goal="Deploy payments service to prod",
context="prod cluster",
steps=[{"action": "run_pipeline", "input": {}, "output": {"status": "ok"}, "success": True}],
outcome="success",
)
engine.semantic_add("User", "PREFERS", "blue-green deploys", confidence=0.9)
engine.long_term_store(
"user_preference", "deploy_strategy", "blue-green",
summary="User always wants blue-green deploys for prod",
importance=0.9, is_pinned=True,
)
result = engine.query("deploy payments service to production")
print(result.synthesized_prompt_context) # one injection-safe, ranked context block
stats = engine.consolidate() # promote sensory -> working -> episodic -> semantic -> long-term
```
Every write accepts `namespace=` (default `"default"`) and `trust=` (default `TrustLevel.USER`) for
multi-tenant scoping and provenance tracking.
---
## π§ͺ Testing
```bash
python3 -m pytest tests/ -v
```
98 tests covering persistence across restart, non-destructive belief revision, prompt-injection
fencing (including non-`<memory>` tag names), namespace isolation across every subsystem (query,
consolidation, reflection, DOT export, belief history), trust-weighted ranking and the verification
mechanism, contradiction flagging, ANN index correctness (with and without `hnswlib` installed),
embedding-dimension mismatch fail-fast behavior, eviction, Postgres dialect translation logic, and
the Claude Code / Antigravity / Cursor / Codex CLI hook adapters. The
Postgres backend has been verified against a real running instance (writes, reads, RLS enforcement,
`forget`, `evict`) β not just reviewed statically.
---
## β οΈ Known limitations
- No published benchmark comparison (LoCoMo, LongMemEval, or similar) against Mem0, Zep/Graphiti, or
MemGPT/Letta exists yet β retrieval quality claims should be treated as unvalidated against other
systems until that exists. Internal, judge-free retrieval measurements against the MemFail benchmark
(arXiv:2605.26667) are available in `benchmarks/`.
- `reflect()` and episodicβsemantic extraction (`trigger_consolidation`) use real LLM synthesis when
called live through an MCP client that supports
[MCP sampling](https://modelcontextprotocol.io/docs/concepts/sampling) β the server asks the
*connected client's own model* to synthesize insights / extract triples (`ctx.sample(...)` in
`mcp_server.py`), so no separate API key or subscription is needed. This only works for live tool
calls, since sampling requires an active client connection: headless paths (autonomous hooks, the
background consolidation loop) have no live client to sample from and fall back to
`HeuristicReflector`/`HeuristicExtractor` (template sentences / regex patterns) β real coverage,
but not LLM-quality synthesis. If a sampling call fails or the connected client doesn't support it,
the same heuristic fallback applies automatically, so `reflect()`/`trigger_consolidation()` never
produce nothing. `LLMReflector`/`LLMExtractor`/`StructuredPromptExtractor` (in `consolidator.py` and
`utils.py`) remain available as a separate, explicitly-opt-in path for anyone who wants headless
LLM-backed extraction/reflection backed by their own `Callable[[str], str]` (e.g. a direct API call) β
not wired in by default.
- Contradiction detection only catches the specific antonym-predicate-on-same-subject case (Tier 1). It
does not detect general semantic contradictions between differently-worded facts β that remains an
open problem no memory system solves generically without an expensive, unreliable LLM pass at write time.
- Postgres ANN search currently loads embeddings into an in-process index rather than querying
pgvector's native index directly β works well at moderate scale, doesn't yet solve the
"dataset too large for one process's memory" case that's part of the reason to use Postgres at all.
- Qdrant/Milvus connectors are real (genuine client calls, no fallback stub) but have only been exercised
in local/embedded mode (`:memory:` Qdrant, `milvus-lite`) β not against a remote/distributed cluster.
---
## π€ Contributing
Issues and pull requests are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
## π License
MIT License β see [LICENSE](LICENSE). Β© Verace Pvt. Ltd.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues