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:

text
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

toml
[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

toml
[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 first
  • tags (comma-separated) — extracted from content; filterable via API
  • session_id, role (user/assistant/tool), channel, timestamp
  • Full-text search via SQLite FTS5
  • type field — distinguishes message, archive_summary, tool_result

REST endpoints

MethodPathDescription
GET/api/v1/memoryList memory rows (filter by session, tag, type, limit)
GET/api/v1/memory/searchFull-text search
POST/api/v1/memoryStore 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:

  1. Sends the full message history to the LLM with a summarisation prompt
  2. Replaces the rolling window in Redis with the compact summary
  3. Stores the full history in SQLite (type = archive_summary) before discarding it from Redis

Compaction is also available on demand:

bash
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:

  1. Generates an LLM summary of the session
  2. Stores the summary in SQLite as archive_summary
  3. Deletes the Redis keys for that session

Configure the archiver schedule in config:

toml
[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

python
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:

toml
[orchestrator.nodes.agent-alpha]
memory_namespace = "shared-pool"

[orchestrator.nodes.agent-beta]
memory_namespace = "shared-pool"   # same namespace → shared memory

REST endpoints

MethodPathDescription
GET/api/v1/orchestrator/nodes/{name}/memoryNamespace stats for a node
DELETE/api/v1/orchestrator/nodes/{name}/memoryClear a node's namespace
GET/api/v1/orchestrator/namespacesAll namespaces + aggregate stats