Long-term memory for AI agents — connect a client, call the API, and your agent remembers across sessions. Grounded, graph-based recall with automatic tiering and consolidation.
A long-term memory engine for AI agents — modeled on how human memory works, not on vector search alone.
Every memory is a typed, scored node in a concurrent graph, connected to other memories by weighted, typed edges. Retrieval runs on spreading activation: a query lights up the most relevant memories, then activation flows outward across the graph so that causally, semantically, and temporally related memories surface too — even when their wording doesn't match the query.
Memories live in retention tiers (Buffer → Episodic → Semantic → Core) and are automatically promoted, demoted, or forgotten based on how important they are and how often they're used. Layered on top: consolidation (offline pattern extraction), reinforcement from feedback, safety filters (PII redaction + threat quarantine), and a metacognition layer that judges its own confidence. Every operation is isolated by namespace.
Spreading activation over weighted edges recalls related memories that vector similarity alone would miss.
Memories are promoted from Buffer to Core (or forgotten) automatically, based on salience and use.
Your key maps to your namespace. Activation re-checks namespace on every edge — data never crosses tenants.
Three ways to use it: connect an MCP client (Claude Code, Cursor), call the HTTP API directly, or use an SDK. Start with the Quickstart.
From zero to your first recall in three steps.
Create an account (or log in). You'll get an isolated namespace and your first key — it starts with mem_dev_ and is shown once. Generate more anytime from your account.
Add the Memoria MCP server to Claude Code with one command:
claude mcp add -s user --transport http memoria https://brain.kolivri.com/v1/mcp \ --header "Authorization: Bearer mem_dev_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
…or paste this into your MCP client config (Cursor, Claude Desktop, etc.):
{
"mcpServers": {
"memoria": {
"type": "http",
"url": "https://brain.kolivri.com/v1/mcp",
"headers": { "Authorization": "Bearer mem_dev_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }
}
}
}Your agent can now store and retrieve memories in your namespace. Or hit the HTTP API directly:
# store a memory
curl -X POST https://brain.kolivri.com/v1/memories \
-H "Authorization: Bearer $MEMORIA_KEY" -H "Content-Type: application/json" \
-d '{"content":"We deploy on Fridays after 2pm","type":"Episodic","namespace":"org:acme-3f9c2a"}'
# recall what you know
curl -X POST https://brain.kolivri.com/v1/memories/recall \
-H "Authorization: Bearer $MEMORIA_KEY" -H "Content-Type: application/json" \
-d '{"query":"when do we deploy?","namespace":"org:acme-3f9c2a"}'Use your own namespace (shown in your account) in the namespace field — your key is scoped to it.
The ideas that make recall work. You don’t need all of this to start — but it explains why Memoria surfaces what it does.
Every memory has a retention tier. Migration between tiers is automatic, driven by salience and access patterns.
| Tier | Retention | Promotes when | Notes |
|---|---|---|---|
| Buffer | ~6h half-life | accessed ≥2× or salience > 0.6 | Garbage-collected once decay < 0.01 and ≥1 day old. |
| Episodic | ~7d half-life | ≥5 accesses, or 14d with salience > 0.4 | Demoted to Buffer if salience < 0.1. |
| Semantic | ~90d half-life | salience > 0.9 and ≥20 accesses | Distilled factual knowledge. |
| Core | permanent | — | Never decays, demotes, or GCs. Pinned memories are frozen at any tier. |
The type you assign at encode time. Over the HTTP API and TypeScript SDK these are PascalCase (Episodic); over MCP, the Python SDK, and the CLI they're lowercase (episodic).
| Type | Meaning |
|---|---|
Buffer | Short-lived scratch memory awaiting promotion. |
Episodic | An event or experience carrying temporal context. |
Semantic | Distilled factual knowledge. |
Core | Immutable foundational knowledge. |
Procedural | A learned action sequence or workflow. |
Prospective | A future-oriented intention or reminder. |
Edges are typed and weighted. When activation spreads across an edge it is multiplied by the edge weight and the edge type's decay factor — so a strong Causal link (0.80) carries recall much further than a weak Contradicts link (0.30).
| Edge type | Decay | Meaning |
|---|---|---|
Causal | 0.80 | One memory caused/led to another. Carries activation the furthest. |
DerivedFrom | 0.80 | Source was derived from the target — a provenance trail. |
DependsOn | 0.75 | Source requires the target to hold. |
Assumes | 0.75 | Source depends on the truth of a target assumption. |
Supersedes | 0.70 | Source replaces the target; superseded memories are down-ranked. |
Evidence | 0.70 | Source provides evidence supporting the target. |
Semantic | 0.60 | Shared meaning/topic. Created automatically at encode time. |
CoRetrieval | 0.55 | Frequently retrieved together; strengthened on co-occurrence. |
Schema | 0.50 | Linked through a shared category schema. |
Contains | 0.50 | Structural containment (project → dir → file → definition). |
Predicts | 0.45 | Source predicts the target will be needed. |
Temporal | 0.35 | Sequential proximity in time. Created automatically. |
Contradicts | 0.30 | The two conflict; invalidating one weakens the other. |
Recall runs in two stages. Seeding: the query is matched to memories in your namespace by cosine similarity, plus BM25 keyword seeds (discounted to 80%). Spreading: activation flows outward from those seeds across edges — at each hop, activation = source activation × edge weight × edge-type decay × any intent boost. The spread is bounded (≈3 hops, with per-wave and total node caps and an activation floor). Every edge crossing re-verifies namespace membership, so activation can never leak into another tenant's data. The payoff: a memory whose text doesn't match your query can still be recalled because it's causally or semantically connected to something that does.
Each activated candidate's final score multiplies several factors (in log space): activation level × salience × freshnessintent × tier boost × utility. Freshness decays with time since last access. The tier boost depends on query intent — factual queries favour Semantic (1.5×) and Core (2.0×). A popularity anti-bias multiplier gently penalises memories retrieved often but rarely confirmed useful. Superseded memories are down-ranked to 5% of their score.
Salience is a memory's importance (0–1). An explicit value you pass always wins. Otherwise it's computed from novelty (how different this is from what's stored), consequence (high-stakes keywords like production, incident, outage, security, breach, critical), and any hint — the loudest signal dominates, others nudge. Default with no signal: 0.3. Salience feeds scoring, decay resistance, and tier-promotion thresholds.
An offline replay pass (analogous to sleep). Over a recent window it runs four detectors: repetition (near-duplicate clusters distilled into one Semantic summary, linked by Supersedes), co-access (frequently co-retrieved pairs reinforced), temporal burst (memories created close in time grouped as an episode), and contradiction (conflicting near-identical memories flagged). It turns a pile of raw episodic captures into deduplicated semantic knowledge over time.
Memoria learns from use. Every recall strengthens CoRetrieval edges among top results and bumps access counts. Explicit feedback marks a memory useful or not: a prediction-error update nudges its expected-utility (and, on a big surprise, its salience), and reward is propagated back along the activation path that produced the hit. Conversely, dampening quietly suppresses query–memory pairs that repeatedly prove unhelpful — without ever deleting anything.
Two layers run before storage. PII redaction replaces emails, phone numbers, API keys, credit-card and Social-Security numbers, IPs, URLs, and names with reversible placeholders ([EMAIL_1]). Quarantine classifies content for credential patterns, instruction-injection attempts (EN + HE), and suspicious URLs; confidence rises with signal count (1→0.4, 2→0.7, 3+→0.95) and flagged content is marked quarantined.
Retrieval judges itself. It auto-detects a context mode from your namespace shape (organizational, support, research, personal…), assesses its own confidence (High / Moderate / Low) from the score distribution, detects re-querying (a sign the last answer missed), and can intervene mid-pipeline — broadening when seeds are too few, narrowing when too many, or returning empty when nothing clears the bar.
One key per request, scoped to your namespace.
Authenticate every request with your key as a Bearer token:
Authorization: Bearer mem_dev_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Keys look like mem_dev_ followed by 43 URL-safe characters. The raw key is shown once at creation and never stored — only a SHA-256 hash is kept, so if you lose a key you mint a new one. The token is opaque: it carries no embedded namespace; your scope is looked up server-side.
Each account gets one organization with a unique namespace, auto-generated as org:<you>-<hex> (e.g. org:alice-3f9c2a) — shown in your account. Your key resolves to that namespace, and all reads/writes are scoped to it. Spreading activation re-checks namespace membership on every edge, so one tenant's data can never surface in another's query. Pass your namespace in the namespace field of API calls.
Mint additional keys and revoke any key instantly from your account. Revocation takes effect on the very next request (nothing is cached). You can only see and revoke keys under your own account.
Requests are rate-limited per key (a token bucket). Exceed it and you get 429 Too Many Requests with a Retry-After header and a JSON body {code:"RATE_LIMIT_EXCEEDED", status:429, message}. Each organization also has a rolling usage quota; once it's exhausted, requests return 402 Payment Required until the window advances. The exact throughput and quota scale with your plan — check your account.
The Model Context Protocol is the fastest path — your agent gets memory tools automatically.
Add Memoria to Claude Code:
claude mcp add -s user --transport http memoria https://brain.kolivri.com/v1/mcp \ --header "Authorization: Bearer mem_dev_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
Or configure any MCP client (Cursor, Claude Desktop, VS Code) with:
{
"mcpServers": {
"memoria": {
"type": "http",
"url": "https://brain.kolivri.com/v1/mcp",
"headers": { "Authorization": "Bearer mem_dev_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" }
}
}
}The connection speaks JSON-RPC 2.0 over streamable HTTP (protocol 2024-11-05). Namespace is chosen per call via the namespace argument (or the x-memoria-namespace header default).
| Tool | Params | Does |
|---|---|---|
recall_memories | query, namespace?, token_budget?, mode? | Retrieve relevant memories with metacognition. |
build_context | query, namespace?, token_budget?, mode? | Assemble a token-budgeted context string for a query. |
encode_memory | content, type?, namespace?, salience?, context? | Store a memory (type: episodic·semantic·buffer·core). |
forget_memory | id | Delete a memory by UUID. |
feedback | id, useful | Mark a recalled memory useful / not useful. |
consolidate | namespace | Merge duplicates, detect patterns & edges. |
stats | namespace | Per-namespace memory count + tier breakdown. |
health | — | Engine-wide health across namespaces. |
Index a repository into a code/<project> namespace and these become available:
| Tool | Does |
|---|---|
search_code | Semantic search over indexed code definitions. |
explain_code | What a symbol is, who calls it, its tests. |
find_references | Every reference to a symbol (graded). |
find_file | Locate a file/dir by path substring. |
impact | Transitive blast radius of changing a symbol. |
call_tree | Caller / callee tree for a symbol. |
dead_code · test_gaps | Uncalled definitions · definitions with no test. |
rename · diff_risk | Rename worklist · callers at risk for changed symbols. |
implementors | Types implementing a trait/interface. |
explain_diff · explain_error | Analyse a unified diff · an error/stack trace. |
navigate · get_hierarchy | Walk the containment map · a node’s full vertical context. |
The same tools are reachable over plain HTTP JSON-RPC at POST /v1/mcp:
curl -X POST https://brain.kolivri.com/v1/mcp \
-H "Authorization: Bearer $MEMORIA_KEY" -H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"recall_memories",
"arguments":{"query":"auth flow","namespace":"code/myapp"}}}'Successful results come back as {content:[{type:"text", text:<json>}]}. Call tools/list to see everything the server advertises.
Everything the agents do, over plain REST. Base URL https://brain.kolivri.com, all routes under /v1, Bearer auth.
/v1/memories{ content, type, namespace, salience?, context?, links? }201 { id, created_at, salience, tier, quarantined, is_duplicate, duplicate_of? }curl -X POST https://brain.kolivri.com/v1/memories \
-H "Authorization: Bearer $MEMORIA_KEY" -H "Content-Type: application/json" \
-d '{"content":"user prefers dark mode","type":"Episodic","namespace":"org:acme-3f9c2a","salience":0.8,"context":{"user_id":"john-123"}}'/v1/memories/recall{ query, namespace, token_budget?=4096, min_salience?=0, context?, mode? }200 { results:[{ memory, score }], total_candidates, latency_ms, metacognition? }curl -X POST https://brain.kolivri.com/v1/memories/recall \
-H "Authorization: Bearer $MEMORIA_KEY" -H "Content-Type: application/json" \
-d '{"query":"authentication tokens","namespace":"org:acme-3f9c2a","token_budget":4096}'/v1/memories/context{ query, namespace, token_budget?=4096, format?=Structured (Structured|Narrative|Raw), context?, mode? }200 { context, memories_used }/v1/memories/recall/enhanced{ query, namespace, token_budget?, diversity?=0.7, min_salience?, mode? }200 { results, total_candidates, latency_ms, cache_hit, trace_id?, metacognition? }/v1/memories/:id(none)200 { deleted }/v1/memories/:id/neighbors(none)200 { edges:[{ target_id, edge_type, weight }] }/v1/memories/batch{ memories:[{ content, type?, salience?, context?, namespace }] }201 { ids, encoded, failed, compressed_size, raw_size }/v1/memories/recall/batch{ queries:[{ query, namespace, token_budget? }] }200 { results: MemoryResult[][] }/v1/memories/by-context{ namespace, context_key, context_value }200 { deleted }/v1/consolidate{ namespace }200 { patterns_found, edges_created, memories_created, contradictions }/v1/feedback{ memory_id? | recall_id?, useful?, score? }200 { updated }/v1/intentions{ trigger_type, trigger_data, action, salience, expires_at?, namespace }201 { id }/v1/intentions?namespace=NS(query)200 { intentions:[{ id, trigger_type, action, salience, expires_at?, created_at, namespace }] }/v1/intentions/:id(none)200 { deleted }/v1/health(none)200 { memory_count, edge_count, tier_distribution, namespace_count }/v1/stats?namespace=NS(query)200 (MemoryStats)/v1/meta/knowledge-map?topic=T&namespace=NS(query)200 (KnowledgeMapEntry)/v1/meta/contradictions?namespace=NS(query)200 { contradictions:[…] }/v1/graph/dot?namespace=NS&max_nodes=50(query)200 text/plain (DOT)/v1/trace/:recall_id(none)200 (RecallTrace)/v1/events/stream(none)text/event-stream/v1/events?since=RFC3339(query)200 [{ event_type, data, timestamp, id }]An interactive OpenAPI spec is served at /v1/openapi.json (Swagger UI at /v1/docs). Auth/rate-limit errors return {code, status, message}; most other errors are a bare status code.
Index a repository into a code/<project> namespace and Memoria becomes a queryable map of it — definitions, references, call graphs, blast radius.
| Method · Path | Purpose |
|---|---|
POST /v1/code/search | Semantic search over indexed definitions (optional type filter). |
POST /v1/code/explain | What a symbol is, who uses it (live scan), and its tests. |
POST /v1/code/references | Every reference to a symbol — call / definition / mention, graded. |
POST /v1/code/find | Find files/dirs whose path matches a name, shallowest first. |
POST /v1/code/impact | Blast radius: transitive reverse-call walk of what could break. |
POST /v1/code/call-tree | Nested caller (in) or callee (out) tree. |
POST /v1/code/dead-code | Definitions with no in-namespace caller (removal candidates). |
POST /v1/code/test-gaps | Non-test definitions with no direct test caller. |
POST /v1/code/rename | Reviewed line-level edit worklist for a rename (not an auto-patcher). |
POST /v1/code/diff-risk | For a set of changed symbols, their direct callers at risk. |
POST /v1/code/implementors | Types whose impl Trait for Type names a symbol (Rust today). |
POST /v1/code/diff · /error | Natural-language analysis of a unified diff · an error/stack trace. |
POST /v1/code/build-edges | Materialise Contains + DependsOn edges after indexing (idempotent). |
curl -X POST https://brain.kolivri.com/v1/code/impact \
-H "Authorization: Bearer $MEMORIA_KEY" -H "Content-Type: application/json" \
-d '{"namespace":"code/myapp","symbol":"calc_total","max_depth":5,"limit":200}'
# -> { symbol, affected:[{ qualified_name, file, line, depth, min_confidence }], total, truncated }All code tools are also exposed as MCP tools (see Connect via MCP) and in the TypeScript SDK under memoria.code.*. Findings are graded (exact / heuristic / ambiguous) with confidence scores — treat rename and dead-code output as a reviewed worklist, not an automatic edit.
A thin, zero-dependency fetch client for the HTTP API (Node 18+ or the browser). Enum values are PascalCase (Episodic).
import { Memoria } from "@memoria/sdk";
const memoria = new Memoria({
baseUrl: "https://brain.kolivri.com",
apiKey: process.env.MEMORIA_KEY, // -> Authorization: Bearer <key>
namespace: "org:acme-3f9c2a", // required; default for every call
});
const { id, tier } = await memoria.encode({ content: "Deployed v2 to prod", type: "Episodic" });
const hits = await memoria.recall({ query: "deployment", token_budget: 2048 });
const { context } = await memoria.buildContext("deployment", 4096);
// code intelligence over an indexed namespace:
const refs = await memoria.code.findReferences("calc_total");| Method | Endpoint |
|---|---|
encode(req) | POST /v1/memories |
recall(req) | POST /v1/memories/recall |
buildContext(query, budget?, format?) | POST /v1/memories/context |
forget(id) · neighbors(id) | DELETE · GET /v1/memories/:id[…] |
encodeBatch(…) · recallBatch(…) | POST /v1/memories/batch[…] |
consolidate(ns?) · feedback(req) | POST /v1/consolidate · /v1/feedback |
deleteByContext(req) · health() | DELETE /v1/memories/by-context · GET /v1/health |
navigate(…) · hierarchy(node) | POST /v1/navigate · /v1/hierarchy |
code.search / explain / findReferences / impact / callTree / … | POST /v1/code/* |
Non-2xx responses throw MemoriaError { message, status, body }.
A native (PyO3) module that links the engine in-process — embedded, no server or HTTP. Build with maturin develop. Enum-like strings are lowercase (episodic). Surface: encode · recall · build_context · forget · health.
from memoria import Memoria
m = Memoria() # in-process; wraps the engine directly
mid = m.encode("Deployed v2 to prod", "episodic", "myapp")
out = m.recall("deployment", "myapp", token_budget=2048)
for r in out["results"]:
print(r["score"], r["content"])
print(m.build_context("deployment", "myapp", 4096)["context"])The Python SDK is for embedding the engine locally. For the hosted cloud service, use MCP, the HTTP API, or the TypeScript SDK.
The memoria binary runs against a local SQLite database (default ~/.memoria/memoria.db). Global flags: --db PATH, --format text|json, --verbose.
| Command | Does |
|---|---|
memoria health | Graph stats: memory/edge counts, tier distribution. |
memoria encode --namespace NS --content T --type episodic | Encode a memory; prints its id. |
memoria recall --namespace NS --query T [--budget 4096] | Recall ranked results for a query. |
memoria search --namespace NS --query T | BM25 lexical search (top 20). |
memoria forget --id UUID | Delete a memory by UUID. |
memoria stats --namespace NS | Per-namespace stats + health assessment. |
memoria consolidate --namespace NS | Run offline consolidation. |
memoria migrate --namespace NS | Run tier promotion/demotion/GC. |
memoria contradictions --namespace NS | Detect contradictory memory pairs. |
memoria meta --namespace NS --topic T | Metamemory knowledge-map for a topic. |
memoria export --namespace NS | Dump all memories in a namespace as JSON. |
memoria import json|csv --file F --namespace NS | Import memories from a file. |
memoria graph dot|ascii … | Export the graph (Graphviz DOT / ASCII neighborhood). |
memoria backup|restore --file F | Checksummed JSON backup / restore. |
memoria trace --last | Show the most recent recall trace. |
memoria pii-check --content T | Scan text for PII. |
--db must come before the subcommand: memoria --db /path.db health. Memory types accept lowercase values.
Yes. Your key resolves to your own namespace, and spreading activation re-verifies namespace membership on every edge it traverses — activation can never cross into another tenant’s data, even if two memories share a term.
You can’t recover it — only a hash is stored. Mint a new key from your account and revoke the old one. Revocation is effective on the next request.
No. Promotion, demotion, and garbage-collection between Buffer → Episodic → Semantic → Core happen automatically from salience and access patterns. Pin a memory or set it to Core to keep it forever.
Up to 1000 memories per /v1/memories/batch call; the decompressed payload must be ≤64 MiB. For larger loads, send multiple batches.
Requests are rate-limited per key (429 with a Retry-After header when you exceed it), and each org has a rolling usage quota (402 when it’s exhausted, until the window advances). The exact numbers scale with your plan — see your account.
PascalCase (Episodic) over the HTTP API and TypeScript SDK; lowercase (episodic) over MCP, the Python SDK, and the CLI.
Index a repo into a code/<project> namespace, then call the /v1/code/* endpoints or the corresponding MCP tools. Findings are graded by confidence — treat rename/dead-code as a reviewed worklist.
Still stuck? Your account shows your namespace and keys, and the Quickstart gets you connected in a minute.