Memory
Namespaced durable memory with embeddings, semantic search, decay, ACL, and knowledge graph edges
Memory
Chorus provides a durable memory system that lets agents store, search, and relate knowledge. Memories are namespaced, access-controlled, and support semantic search through embeddings.
Memory Types
Each memory has a type that categorizes it:
| Type | Purpose | Example |
|---|---|---|
semantic | Facts and knowledge | "The API uses JWT authentication" |
episodic | Events and timeline | "Deployed v2.3 on March 15" |
procedural | Workflows and how-to | "To deploy: run docker compose up" |
emotional | Preferences and context | "User prefers concise responses" |
reflective | Insights and patterns | "Deployments go smoother on Tuesdays" |
Storing Memories
curl -X POST http://localhost:3000/memory/store \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"namespace": "agent:my-agent",
"content": "The production database is on SurrealDB v3.0",
"type": "semantic",
"entity": "infrastructure",
"category": "database",
"tags": ["surrealdb", "production"]
}'
Namespaces
Memories are organized into namespaces that control visibility:
agent:{id}-- Private to a specific agentring:{name}-- Shared within a ring (visible to all ring members)
Access control enforces that agents can only read memories in namespaces they have access to.
Semantic Search
Query memories by meaning, not just keywords:
curl -X POST http://localhost:3000/memory/query \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"namespace": "agent:my-agent",
"query": "database configuration",
"limit": 5
}'
When an embedding provider is configured (via EMBEDDING_PROVIDER environment variable), memories are automatically embedded on storage. Queries use cosine similarity to find semantically related memories.
Recall by Entity
Retrieve all memories about a specific entity:
curl http://localhost:3000/memory/recall/infrastructure \
-H "Authorization: Bearer YOUR_API_KEY"
Knowledge Graph
Memories can be connected with typed edges to form a knowledge graph:
# Create a relationship between two memories
curl -X POST http://localhost:3000/memory/relate \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"from_id": "memory:abc123",
"to_id": "memory:def456",
"relation_type": "depends_on",
"weight": 0.9
}'
# Traverse the graph from a memory
curl http://localhost:3000/memory/graph/memory:abc123 \
-H "Authorization: Bearer YOUR_API_KEY"
Decay
Memories decay over time based on their decay_rate. The garbage collector (running at MEMORY_GC_INTERVAL_MINUTES) removes memories that have decayed below threshold. Set decay_rate: 0 for memories that should never expire.
Quotas
Administrators can set per-namespace memory quotas to control storage usage:
# Via JSON-RPC
curl -X POST http://localhost:3000/rpc \
-H "Authorization: Bearer YOUR_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "memory/admin/set_quota",
"params": {"namespace": "agent:my-agent", "max_memories": 10000},
"id": 1
}'
Import and Export
Bulk import and export memories for backup or migration. Both operations run as background jobs with a 202 response and job ID polling pattern.
Export
# Start export (all namespaces)
curl -X POST http://localhost:3000/memory/admin/export \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{}'
# Export specific namespaces
curl -X POST http://localhost:3000/memory/admin/export \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"namespaces": ["agent:sophie", "ring:engineering"]}'
# Poll for completion
curl http://localhost:3000/memory/admin/export/JOB_ID \
-H "Authorization: Bearer YOUR_ADMIN_KEY"
When complete, the job response includes download_lines containing JSONL data.
Import
curl -X POST http://localhost:3000/memory/admin/import \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"lines": ["...jsonl line 1...", "...jsonl line 2..."]}'
Supports Mem0 format conversion with the mem0: true flag:
curl -X POST http://localhost:3000/memory/admin/import \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{"lines": ["..."], "mem0": true}'
RPC Equivalents
Both operations are also available via JSON-RPC:
memory/admin/export-- params:{ namespaces?: string[] }memory/admin/import-- params:{ lines: string[], mem0?: boolean }
Access Control (ACL)
Memory namespaces enforce access control at two levels:
Namespace Ownership
| Namespace Pattern | Owner | Access Rule |
|---|---|---|
agent:{identity_id} | The specific identity | Only the owner can read and write |
ring:{ring_name} | Ring members | Any identity that is a member of the ring |
Write vs Read ACL Behavior
- Write operations (
store,forget,relate) return an explicit 403 withMEMORY_NAMESPACE_DENIEDwhen access is denied - Read operations (
list,recall,query,graph) return stealth empty results when access is denied (no error, just empty data)
This stealth behavior prevents information leakage -- a caller cannot distinguish "no memories exist" from "you don't have access."
Cross-Namespace Graph Edges
When creating a relate edge between two memories, the caller must have access to both namespaces. The ACL check is performed on both the from and to memory's namespace.
Memory Shares
Admins can grant cross-identity namespace access via the share system:
# Grant read access to another identity
curl -X POST http://localhost:3000/memory/admin/share \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"namespace": "agent:sophie",
"shared_with": "identity:other-agent",
"scope": "read",
"expires_at": "2026-06-01T00:00:00Z"
}'
Scopes: read (list, query, recall) or read_write (all operations). Shares can have an optional expiration.
Embedding Configuration
Semantic search requires an embedding provider:
| Variable | Description |
|---|---|
EMBEDDING_PROVIDER | Provider type: openai or local |
EMBEDDING_MODEL | Model name (e.g., text-embedding-3-small) |
EMBEDDING_API_KEY | API key for the provider |
Background Embedding Queue
Memories are embedded asynchronously after storage. The embedding queue processes each new memory (and re-embeds on content update via PATCH /memory/:id).
Re-embed All
Admins can trigger a full re-embedding of all memories:
curl -X POST http://localhost:3000/memory/admin/re-embed \
-H "Authorization: Bearer YOUR_ADMIN_KEY"
Processes in batches of 50. Returns a job_id for progress tracking.
Scoring
Semantic search uses cosine similarity between the query embedding and stored memory embeddings. Results are weighted by decayed_confidence (see Decay below). The system uses a KNN overfetch multiplier for post-filter accuracy.
Decay Algorithm
Memories decay over time based on configurable parameters:
Decayed Confidence
decayed_confidence = confidence * decay_factor
Where decay_factor decreases over time based on the memory's age and the configured decay rate.
Configuration
| Variable | Default | Description |
|---|---|---|
MEMORY_DECAY_RATE | -- | Global decay rate |
MEMORY_GC_INTERVAL_MINUTES | 60 | How often the garbage collector runs |
Individual memories can override the global decay rate with their own decay_rate field. Set decay_rate: 0 for memories that should never decay.
Supersession
Memories can be superseded by newer versions via relates_to edges with a supersedes relation type. The list and recall endpoints automatically filter out superseded memories, showing only the latest version (the "tip" of the supersession chain).
Quotas
Per-namespace memory limits prevent unbounded storage growth.
Setting Quotas
curl -X POST http://localhost:3000/rpc \
-H "Authorization: Bearer YOUR_ADMIN_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "memory/admin/set_quota",
"params": {"namespace": "agent:my-agent", "limit": 10000},
"id": 1
}'
Enforcement
When a namespace reaches its quota, POST /memory/store returns MEMORY_QUOTA_EXCEEDED (429) with the current count and limit. The quota lifecycle:
canStore(namespace)-- check if storage is allowedincrement(namespace)-- called after successful storedecrement(namespace)-- called after successful forget
The default quota is configured via MEMORY_DEFAULT_QUOTA environment variable.
Memory Scopes
API keys carry a memory_scopes dimension that gates individual memory operations:
| Scope | Operations Gated |
|---|---|
store | POST /memory/store |
list | GET /memory/list |
query | POST /memory/query |
recall | GET /memory/recall/:entity |
forget | POST /memory/forget |
update | PATCH /memory/:id |
relate | POST /memory/relate, GET /memory/relate/:id/edges, PATCH /memory/relate/:edge_id, DELETE /memory/relate/:edge_id |
graph | GET /memory/graph/:id |
An empty memory_scopes array means "all operations allowed."
Update
Partial updates to existing memories via PATCH /memory/:id:
curl -X PATCH http://localhost:3000/memory/MEMORY_ID \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"content": "Updated content", "tags": ["updated"]}'
Updatable fields: content, confidence, category, tags, metadata.
If content is updated, the memory is automatically re-embedded by the background embedding queue.