Agent memory surface
RelataDB is the agentic-first ontological database — one governed, bi-temporal knowledge base that speaks every database protocol. This page covers its agent-memory surface: the governed memory layer where what an agent knew, when, and why is defensible, not just convenient.
Most agent-memory products (Mem0, RushDB, Zep) optimize for convenience: push JSON, get
semantic recall, schema-free, with embeddings computed for you. Relata optimizes for
accountability: every belief is bi-temporal, provenance-stamped, and access-controlled.
Since v1.1 embeddings are caller-supplied — either pre-compute _emb_text in the
row payload or run the embedder sidecar for async drain —
so the ingest hot path is pure throughput (no per-row model call). If you need memory that
feels smart, those are great; if the memory must answer "what did the agent know, when,
and why — and who was allowed to see it?", that's Relata.
Relata vs. the convenience-memory cohort
| Mem0 / RushDB / Zep | Relata | |
|---|---|---|
| Optimized for | Convenience, fast onboarding | Accountability, defensibility |
| History | Current state | Bi-temporal — AS OF valid + system time |
| Provenance | None / light | PROV-O per row + tamper-evident hash chain |
| Access control | ACID only | Cell-level ACL (Cedar) + org isolation + per-tenant encryption/quotas/namespaces |
| Schema | Schema-free | Schema-as-code ontology (governed) |
| Model | On Neo4j / hosted | Own Rust engine, object-store native, single binary |
| License | AGPL-3.0-only / hosted | AGPL-3.0-only (source on request) |
| Best fit | Personalization, RAG, schema-free apps | Regulated, audited, intel/LEA/FININT, court-grade |
Why Relata for agent memory
| Concern | Relata answer |
|---|---|
| Bi-temporal recall | Every memory carries valid_from/to (when it was true) and system_from/to (when Relata learned it). Query "what did the agent believe at T?" exactly. |
| Tamper-evident audit | Hash-chained commit manifests mean any deletion or mutation leaves a forensic trail. Compliance and incident-response needs are met out of the box. |
| Provenance chain | Every MemoryItem links back to the ToolCall and AgentSession that produced it. Replay exactly how a decision was reached. |
| Governed access | Cedar-inspired ABAC + PURPOSE restrict which agents can read which memories. Multi-tenant isolation is enforced at the query-planner level. |
Problems we solve
| Problem | Relata solution |
|---|---|
| Finite context window — agents forget anything that doesn't fit the prompt | External governed store + recall injects a bounded, ranked slice per turn (LIMIT N BUDGET T) |
| Memory bloat degrades recall — as memory grows, noise drowns signal | Hybrid retrieval (BM25 + vector + graph) with early pruning; consolidate supersedes stale facts instead of accumulating them |
| No "current truth" — facts change but old beliefs linger | Bi-temporal supersession: old belief closed at valid_to=now, new inserted, both AS OF-reconstructable |
| Hallucination amplification / memory poisoning — retrieved "memories" have no source | PROV-O provenance per row + tamper-evident hash chain; every result is justify-able; no source = not a memory |
| No time-travel — can't ask "what did the agent know at T?" | Bi-temporal recall … AS OF '<ts>' reconstructs the agent's state at any moment |
| Multi-agent collision / privacy leak | Per-agent/session scoping + cell-level ACL (Cedar) + org isolation + sub-tenant namespaces |
| No compliance — GDPR delete, audit, access logs | forget with retention + legal-hold, hash-chained audit, PURPOSE recording |
| Retrieval is slow / expensive | Lazy detection + deterministic canonical ops on the hot path (no LLM/GPU) + tiered cache (RAM/SSD/object-store) |
Unlimited memory
Relata gives an agent unbounded memory with bounded prompts by separating what the agent knows (unlimited) from what it sees per turn (bounded):
- Capacity is unbounded — durable storage is object-store (S3 / self-hosted S3-compatible), not RAM-bound like a vector DB. You run out of bucket, not memory.
- The agent never loads it all — each turn,
recallreturns only the small, relevant, ranked slice, capped byLIMIT N BUDGET T. A 10-year, billion-row memory and a 1 MB memory cost the same prompt budget; retrieval is selective, not exhaustive. - Cold vs hot — cold history lives on object storage; active-case data is promoted into RAM/SSD via the tiered cache. Archive-scale capacity with interactive latency.
Fast and efficient on constrained hardware
Relata is RAG-on-memory, engineered to run on a laptop or edge box with no GPU:
- No LLM on the hot path. Ingest canonicalizes + validates declared identities (deterministic, cheap). Auto-detection/extraction is lazy via materialized views — you pay detection cost only on the slice you query, not on 100% of rows at write time.
- Early-pruned retrieval. IdentityIndex bloom filters + graph pushdown + a BM25 shortlist mean the vector path scans a tiny candidate set, not the whole corpus.
- Single binary, embedded
freeprofile — one process, in-memory + optional object-store, running alongside the agent's own loop. No separate vector server, Redis, and graph DB to operate. - Token-efficient — hybrid (keyword + vector + graph) returns higher-precision results, so fewer tokens are injected for the same answer quality, and the budget operator sets a hard ceiling.
Honest scope: single-node
free/serverwith object-store + tiered cache is the shipped answer today. Multi-node cluster scale (deep partitioning) is the cluster profile and still maturing.
The five canonical memory types
| Type | Description |
|---|---|
MemoryItem | The atomic unit. Holds content, session_id, confidence [0,1], and bi-temporal timestamps. Created by remember, consumed by recall. |
DecisionRecord | A choice an agent made, with the inputs it considered and the rationale it logged. Created by justify. |
AgentSession | A bounded interaction window grouping related MemoryItems and ToolCalls. Created implicitly on first remember for a new session_id. |
ToolCall | One invocation of a tool: name, arguments, result, latency. Linked from MemoryItems so the provenance chain is complete. |
Episode | A higher-order grouping of related sessions forming a coherent narrative arc. Retrieved by episodes_in. |
The 10 cognitive verbs
| Verb | HTTP surface | Description |
|---|---|---|
remember | POST /memory/remember | Store a new MemoryItem. Returns id, confidence, valid_from. |
recall | GET /memory/recall?q=... | Semantic + temporal search over MemoryItems. Returns ranked list with provenance. |
recognize | GET /memory/recognize/:id | Fetch one MemoryItem by id with full provenance chain attached. |
episodes | GET /memory/episodes?session_id=... | List Episodes for a session, ordered by valid_from. |
justify | GET /memory/justify/:id | Trace the decision provenance for a MemoryItem — returns the chain of ToolCalls and DecisionRecords that produced it. |
consolidate | POST /memory/consolidate | Supersede an existing MemoryItem with updated content. The old item is retained in history; a new one is created with higher confidence. |
forget | DELETE /memory/forget/:id | Schedule a MemoryItem for retention-policy deletion. Does not hard-delete immediately — the item remains queryable until the retention window expires. |
associate | POST /memory/associate | Link two memory items / entities with a typed, provenance-stamped association (from_id, to_id, relation). |
resolve | GET /memory/resolve/:id | Resolve a memory reference through its supersession chain to the canonical (live) MemoryItem. |
summarise | POST /memory/summarise | Produce a governed, provenance-stamped summary of a session or topic. |
Batch helpers:
POST /memory/remember/batchandPOST /memory/associate/batchare high-throughput variants that amortise the per-record index flush across a whole batch; per-item errors are returned per element and valid items still commit.
Surface note: The cognitive verbs are MCP tools and REST endpoints — they are not SQL keywords. The
LIMIT N BUDGET Tretrieval knobs andrecall … AS OF '<ts>'time-travel are expressed as verb/REST parameters (?top_k=,?as_of=onGET /memory/recall). The underlying bi-temporalAS OFis available as real SQL over the base types (SELECT … FROM MemoryItem AS OF '<ts>').
Recall-quality knobs — tuning what comes back
Recall isn't a black box. Five keyword parameters (the "retrieval-quality operators") let you shape recall for your domain — confidence floors, memory decay, hard token budgets, Ebbinghaus forgetting, and early-cancel. All five are accepted by GET /memory/recall, the recall MCP tool, and every SDK's search(...) / search_detailed(...) method.
| Parameter | Underlying operator | What it does | Example |
|---|---|---|---|
min_confidence | CONFIDENCE(f) | Drop memories below this confidence floor. Use to keep low-quality / speculative beliefs out of the prompt. | min_confidence=0.7 |
recency_half_life_secs | RECENCY(λ) | Exponential score decay half-life, in seconds. Recent memories rank higher; old ones don't vanish, they just decay. | recency_half_life_secs=604800 (1 week) |
budget_tokens | BUDGET(t) | Hard ceiling on the cumulative token cost of returned memories. The server stops emitting once the budget is hit — your prompt literally cannot overflow. | budget_tokens=2000 |
stability_days | FORGETTING_CURVE(d) | Ebbinghaus stability parameter, in days. Mirrors human-memory reinforcement: memories that haven't been re-touched decay faster. | stability_days=30 |
cancel_threshold | CANCEL_WHEN(threshold) | Short-circuit the scan the moment a hit exceeds this score. Use when you want "the first great match, then stop." | cancel_threshold=0.95 |
Reading the effect back — search_detailed
Two response fields let you observe the knobs' effect, not just set them:
recall_cost_tokens— the running token total underBUDGET(how much of the budget was consumed).cancelled— whetherCANCEL_WHENshort-circuited the scan (true) or the full ranking ran (false).
These appear on the detailed recall envelope (Memory.search_detailed(...) in all three SDKs; GET /memory/recall?detailed=true).
SDK examples
Python
from relata import Memory
mem = Memory("http://localhost:9090", bearer_token="<token>", purpose="agent")
# Tight budget, recency-weighted, stop at the first near-certain hit
result = mem.search_detailed(
"how do we reset the IR sensor?",
top_k=10,
min_confidence=0.6, # CONFIDENCE floor
recency_half_life_secs=259200, # 3-day half-life (RECENCY)
budget_tokens=1500, # hard prompt budget (BUDGET)
cancel_threshold=0.92, # stop early on a great match (CANCEL_WHEN)
)
print(result["recall_cost_tokens"], result["cancelled"])TypeScript
const detail = await mem.searchDetailed("how do we reset the IR sensor?", {
topK: 10, minConfidence: 0.6, recencyHalfLifeSecs: 259200,
budgetTokens: 1500, cancelThreshold: 0.92,
});
console.log(detail.recall_cost_tokens, detail.cancelled);Go
res, _ := mem.SearchDetailed(ctx, "how do we reset the IR sensor?",
relata.WithTopK(10),
relata.WithMinConfidence(0.6),
relata.WithRecencyHalfLife(259200),
relata.WithBudgetTokens(1500),
relata.WithCancelThreshold(0.92),
)
fmt.Println(res.RecallCostTokens, res.Cancelled)Tips & takeaways
- Start with
budget_tokens. It's the single biggest win for agent loops — the prompt literally cannot overflow the model's context window. Pick a budget that leaves room for the system prompt + tool output + the response. - Pair
recency_half_life_secswithstability_daysonly if it matters. For most RAG use casesrecency_half_life_secsalone is enough;stability_days(Ebbinghaus) is for long-lived agents that should "remember" frequently-revisited facts. cancel_thresholdtrades coverage for latency. Set it when one excellent match is enough (FAQ retrieval, lookups); leave it unset when you want a ranked slate (brainstorming, summarization).min_confidenceis a safety net, not a ranking signal. It filters; it doesn't sort. Combine withrecency_*for the actual ranking shape you want.- Bi-temporal recall composes with all five. Add
as_of='<ts>'to reconstruct what the agent believed at T under the same quality constraints — "what would we have recalled on Tuesday under a 1500-token budget?"
Cross-ref: Concepts: Agent Memory · MCP tools · Bi-temporal queries · Limits
Quick-start via MCP
Relata exposes all the cognitive verbs as MCP tools. Point your agent's MCP client at the server and call tools directly:
// MCP initialize
POST /mcp/initialize
{"protocolVersion":"2024-11-05","clientInfo":{"name":"my-agent","version":"1.0"}}
// MCP list tools
GET /mcp/tools
// → returns the memory tools: remember, remember_batch, recall, recognize, episodes_in, justify, consolidate, forget, associate, resolve, summarise
// MCP call — remember
POST /mcp/tools/call
{
"name": "remember",
"arguments": {
"content": "User prefers concise summaries",
"session_id": "sess_abc123",
"confidence": 0.9,
"purpose": "personalisation"
}
}
// → {"isError": false, "content": [{"type":"text","text":"{\"id\":\"<uuid>\",\"confidence\":0.9,...}"}]}
// MCP call — remember_batch (high-throughput write path)
POST /mcp/tools/call
{
"name": "remember_batch",
"arguments": {
"items": [
{"content": "User prefers dark mode", "session_id": "sess_abc123", "confidence": 0.9},
{"content": "User timezone is UTC+5:30", "session_id": "sess_abc123", "confidence": 0.85},
{"content": "User speaks English and Hindi", "session_id": "sess_abc123", "confidence": 0.95}
],
"purpose": "personalisation"
}
}Quick-start via HTTP REST
Step 1 — Remember a fact
POST /memory/remember
Content-Type: application/json
{
"content": "User prefers dark mode",
"session_id": "sess_abc123",
"confidence": 0.9,
"purpose": "personalisation"
}Response:
{
"isError": false,
"content": [{
"type": "text",
"text": "{\"id\":\"550e8400-e29b-41d4-a716-446655440000\",\"confidence\":0.9,\"valid_from\":1750000000000000000}"
}]
}Step 2 — Recall related memories
GET /memory/recall?q=user+preferences&top_k=5&purpose=personalisationResponse:
{
"isError": false,
"content": [{
"type": "text",
"text": "{\"memories\":[{\"id\":\"550e8400...\",\"content\":\"User prefers dark mode\",\"score\":0.97}],\"total\":1}"
}]
}Step 3 — Justify a memory (provenance)
GET /memory/justify/550e8400-e29b-41d4-a716-446655440000?purpose=auditReturns the full ToolCall → MemoryItem → DecisionRecord provenance chain.
Step 4 — Consolidate (update with higher confidence)
POST /memory/consolidate
Content-Type: application/json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"content": "User prefers dark mode (confirmed across three sessions)",
"confidence": 0.98,
"purpose": "personalisation"
}Returns {"superseded": "<old-id>", "new_id": "<new-id>"}.
Step 5 — Schedule forgetting
DELETE /memory/forget/550e8400-e29b-41d4-a716-446655440000?retain_days=30&purpose=gdprReturns {"scheduled": true, "policy": "delete_after_30d"}.
Governance model
Every memory operation passes through three governance layers:
-
PURPOSE — The
purposefield (e.g."personalisation","audit","gdpr") is recorded in the audit log and may be required by ACL policy. WhenPURPOSEis omitted, the operation is recorded as unpurposed but still allowed inopenmode. -
ACL — Cedar-inspired ABAC rules control which agent identities can write, read, or delete memories in a given organisation. Cell-level masking applies to sensitive fields.
-
Provenance chain — Every MemoryItem references the
AgentSessionandToolCallthat produced it. The hash-chained manifest makes the chain tamper-evident.justifyreplays the chain on demand.
Multi-agent isolation
When multiple agents share one Relata instance, pass a tenant_id header:
POST /memory/remember
X-Relata-Agency: agent-team-alphaThe planner enforces tenant_id keying so agents never see each other's memories
without an explicit cross-organisation grant.
session_idis not a tenant boundary. Thesession_id(typically an agent's Ed25519 pubkey) groups a conversation — it is not an isolation key. Two organisations reusing the samesession_idare kept apart only by their tenant (org). On a multi-tenant profile (server/clusterwith more than one tenant, orRELATA_TENANCY_MODE=multi) memory writes with no tenant are rejected403. Present a per-tenant credential —X-Organization-Idor a tenant-scoped bearer token — for every memory write.