Memory System
NeuralCleave maintains three distinct memory tiers — Redis (hot session cache), Qdrant (semantic vector store), and SQLite (long-term structured store) — plus session compaction, an archiver, and per-node isolation for multi-agent deployments.
How the tiers work together
When the agent needs context for a new message, MemoryManager queries all three tiers and merges the results into the model's context window:
Inbound message
│
▼
MemoryManager.retrieve(session_id, query)
│
├── Redis: recent messages (rolling window, TTL=1h)
├── Qdrant: semantic search (top-k ANN, cosine similarity)
└── SQLite: long-term recall (importance score ≥ threshold, tag filter)
│
▼
Merged context → ModelRouter → ReflectionEngine → Response
│
▼
MemoryManager.store(session_id, message, response)
│
├── Redis: append to rolling window
├── Qdrant: embed + upsert (MD5 dedup)
└── SQLite: store with importance score (0.0–1.0) + tags
Redis — short-term memory
Redis is the hot tier. Every session has a dedicated key namespace (nc:session:{session_id}:*) with a configurable TTL (default: 1 hour). When a session expires, its Redis keys are deleted automatically.
What's stored
- Rolling message window (recent N turns, LIFO)
- Active tool state (e.g. browser session cursors)
- Pending async futures for voice/Twilio Voice multi-turn
- Temporary flags (privacy mode, forced model)
Fallback behaviour
If Redis is unavailable at startup, MemoryManager falls back to a Python dict in-process. No configuration change is required — a warning is logged and the gateway continues normally. The fallback does not persist across restarts.
Configuration
[memory]
redis_url = "redis://localhost:6379/0" # omit to use in-memory fallback
session_ttl_seconds = 3600 # default: 1 hour
rolling_window_size = 50 # max messages kept hot
Qdrant — semantic / vector memory
Qdrant provides ANN (approximate nearest-neighbour) search over past conversations and documents. NeuralCleave stores each assistant turn as a vector embedding (via the configured embedding provider — Ollama by default, OpenAI text-embedding-3-small as alternative).
Deduplication
Before upserting a new vector, MemoryManager computes MD5(content). If a point with that MD5 already exists in the collection, the upsert is skipped. This prevents identical messages from growing the index unnecessarily.
Retrieval
On each inbound message, the query is embedded and searched with cosine similarity. The top-k results (default: k=5) above a minimum score threshold (default: 0.6) are included in the model context.
Configuration
[memory]
qdrant_url = "http://localhost:6333"
qdrant_collection = "neuralcleave_memory" # created automatically if missing
vector_top_k = 5
vector_min_score = 0.60
SQLite — long-term memory
SQLite is the durable tier. Every message and response is stored in ~/.neuralcleave/memory.db with:
importance(float 0.0–1.0) — computed by a quick LLM pass; higher-importance items are recalled firsttags(comma-separated) — extracted from content; filterable via APIsession_id,role(user/assistant/tool),channel,timestamp- Full-text search via SQLite FTS5
typefield — distinguishesmessage,archive_summary,tool_result
REST endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/memory | List memory rows (filter by session, tag, type, limit) |
| GET | /api/v1/memory/search | Full-text search |
| POST | /api/v1/memory | Store a memory row manually |
| DELETE | /api/v1/memory/{id} | Delete a specific row |
| DELETE | /api/v1/memory/session/{id} | Delete all rows for a session |
Session compaction
When the rolling message window reaches 50% of the model's token budget (configurable), the compactor triggers automatically:
- Sends the full message history to the LLM with a summarisation prompt
- Replaces the rolling window in Redis with the compact summary
- Stores the full history in SQLite (type =
archive_summary) before discarding it from Redis
Compaction is also available on demand:
curl -X POST http://localhost:7432/api/v1/memory/compact \
-H "Content-Type: application/json" \
-d '{"session_id": "abc123"}'
Session archiver
The archiver is a scheduled task that periodically finds sessions inactive for longer than a configured threshold (default: 24h) and:
- Generates an LLM summary of the session
- Stores the summary in SQLite as
archive_summary - Deletes the Redis keys for that session
Configure the archiver schedule in config:
[memory]
archive_after_hours = 24 # sessions idle longer than this get archived
archive_cron = "0 3 * * *" # run archiver at 03:00 daily
Per-node memory isolation
When using AgentOrchestrator, each node gets a private MemoryNamespaceStore — an ordered LRU key-value store independent of the shared SQLite/Redis memory.
MemoryNamespaceStore API
from neuralcleave.orchestrator.memory import MemoryNamespaceManager
manager = MemoryNamespaceManager()
ns = manager.namespace("agent-alpha")
ns.put("last_user", "alice", tags=["context"])
ns.get("last_user") # "alice"
ns.search("alice") # [MemoryEntry(...)]
ns.list_by_tag("context") # [MemoryEntry(...)]
ns.stats() # {"size": 1, "max_entries": 1000}
Shared namespaces
Two or more nodes can share a memory pool by setting the same memory_namespace in their config:
[orchestrator.nodes.agent-alpha]
memory_namespace = "shared-pool"
[orchestrator.nodes.agent-beta]
memory_namespace = "shared-pool" # same namespace → shared memory
REST endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/orchestrator/nodes/{name}/memory | Namespace stats for a node |
| DELETE | /api/v1/orchestrator/nodes/{name}/memory | Clear a node's namespace |
| GET | /api/v1/orchestrator/namespaces | All namespaces + aggregate stats |