Cersei

Memory (cersei-memory)

Three-tier memory system — graph DB, flat files, CLAUDE.md hierarchy, session persistence.

cersei-memory

Three-tier memory system designed to outperform Claude Code's file-based approach. An embedded graph database (Grafeo) handles relationship-aware recall in 98 microseconds — replacing the LLM call that Claude Code makes every turn.

Graph memory is a compile-time feature. Enable it with cersei-memory = { features = ["graph"] }. It's ON by default in the cersei facade and the Abstract CLI.


Architecture

Tier 1: Graph Memory (Grafeo)     — Relationship-aware indexed recall
Tier 2: Flat Files (memdir)       — Claude Code compatible .md files
Tier 3: CLAUDE.md Hierarchy       — 4-level instruction loading
       + Session Storage (JSONL)  — Append-only transcripts

When you call recall(), the MemoryManager queries the graph first. If the graph returns results, they're used directly. If not (or if graph isn't enabled), it falls back to scanning flat files with text matching.


MemoryManager

The unified interface that composes all three tiers.

use cersei::memory::manager::MemoryManager;

let mm = MemoryManager::new(project_root)
    .with_graph(Path::new("./memory.grafeo"))?;

Constructor and Configuration

Prop

Type

Context Building

These methods build the memory content that gets injected into the system prompt:

MethodReturnsDescription
build_context()StringFull system prompt section: CLAUDE.md hierarchy + MEMORY.md index merged
scan()Vec<MemoryFileMeta>All memory files with frontmatter metadata, sorted by recency
load_file(path)Option<MemoryFile>Load a specific memory file with content (frontmatter stripped)
// Build the complete memory context for the system prompt
let context = mm.build_context();

// Scan all memory files
for meta in mm.scan() {
    println!("{}: {} (type: {:?})", meta.filename, meta.description.unwrap_or_default(), meta.memory_type);
}

Recall and Query

MethodReturnsDescription
recall(query, limit)Vec<String>Query-based recall. Graph first, text fallback.
by_type(MemoryType)Vec<String>Filter by type (graph only, empty without graph)
by_topic(topic)Vec<String>Filter by topic tag (graph only)
// Recall — graph indexed lookup in 98us, or text scan in 1.3ms
let results = mm.recall("testing preferences", 5);

// Filter by type
let user_memories = mm.by_type(MemoryType::User);

// Filter by topic
let rust_memories = mm.by_topic("rust");

Graph Operations

These only work when graph is enabled. They're no-ops otherwise.

MethodReturnsDescription
store_memory(content, type, confidence)Option<String>Store a memory node. Returns UUID.
tag_memory(id, topic)()Associate a memory with a topic. Creates the topic if needed.
link_memories(from, to, relationship)()Create a RELATES_TO edge between two memories.
has_graph()boolWhether graph is enabled.
graph_stats()GraphStatsCounts: memories, topics, relationships, sessions.
// Store a memory
let id = mm.store_memory(
    "User prefers functional patterns over OOP",
    MemoryType::User,
    0.9,  // confidence 0.0 to 1.0
);

// Tag it
if let Some(ref id) = id {
    mm.tag_memory(id, "coding-style");
    mm.tag_memory(id, "preferences");
}

// Link two memories
mm.link_memories(&id_a, &id_b, "contradicts");
mm.link_memories(&id_c, &id_d, "extends");

// Check stats
let stats = mm.graph_stats();
println!("{} memories, {} topics, {} relationships", stats.memory_count, stats.topic_count, stats.relationship_count);

Session Operations

MethodReturnsDescription
write_user_message(session_id, msg)io::Result<String>Append user message to JSONL. Returns UUID.
write_assistant_message(session_id, msg, parent)io::Result<String>Append assistant message. Optionally links to parent UUID.
load_session_messages(session_id)Result<Vec<Message>>Load all messages, applying tombstones.
list_sessions()Vec<SessionInfo>List all session files.
session_path(session_id)PathBufGet the JSONL file path for a session.
// Write
let user_id = mm.write_user_message("session-1", Message::user("Hello"))?;
let asst_id = mm.write_assistant_message("session-1", Message::assistant("Hi!"), Some(&user_id))?;

// Load
let messages = mm.load_session_messages("session-1")?;
assert_eq!(messages.len(), 2);

Graph Schema

The Grafeo graph stores three node types and three edge types:

Node Types:
  (:Memory) { id, content, mem_type, confidence, created_at, updated_at }
  (:Topic)  { name }
  (:Session) { session_id, started_at, model, turns }

Edge Types:
  (:Memory) -[:RELATES_TO { relationship, weight }]-> (:Memory)
  (:Topic)  -[:TAGGED]->                              (:Memory)
  (:Session) -[:PRODUCED]->                           (:Memory)

Memory Types

Prop

Type

Performance

OperationLatency
Store single node30us
Store 100 nodes (bulk)86us/node
Tag memory1241us
Link memories2681us
Query by topic77us
Recall (indexed)98us
Stats480us

Graph recall is 92.5% faster than text-matching recall because it queries an index instead of scanning every file. See Graph Benchmarks.


Memdir (Flat Files)

Compatible with Claude Code's memory directory format.

Location: ~/.claude/projects/<sanitized-root>/memory/

File format:

---
name: Memory Title
description: One-line description for recall
type: user
---

Actual memory content here.

Limits:

  • MEMORY.md index: max 200 lines or 25KB
  • Memory files scanned: max 200, sorted newest-first
  • Types: user, feedback, project, reference

CLAUDE.md Hierarchy

Loaded in priority order (highest to lowest):

ScopePathPurpose
Managed~/.claude/rules/*.mdOrganization rules (alphabetically sorted)
User~/.claude/CLAUDE.mdUser-level preferences
Project{root}/CLAUDE.mdProject-level instructions
Local{root}/.claude/CLAUDE.mdLocal overrides (gitignored)

All levels are merged into a single context string. Supports @include <path> directives (max depth 10, max 40KB per include, circular reference detection). Frontmatter is stripped automatically.


Session Storage (JSONL)

Append-only JSONL transcripts compatible with Claude Code's session format.

Entry types:

TypeDescription
UserUser message with UUID, timestamp, parent link
AssistantAssistant message with UUID, timestamp, parent link
SystemSystem message
SummaryCompaction summary (replaces multiple messages)
TombstoneSoft-delete marker (skipped on load)

Location: ~/.claude/projects/<sanitized-root>/<session-id>.jsonl

Max 50MB per session file. Malformed lines are skipped silently.


Auto-Dream (Background Consolidation)

Three-gate system that runs after each agent turn to decide if memory consolidation is needed:

GateConditionDefault
TimeHours since last consolidation24 hours
SessionNew sessions since last run5 sessions
LockNo concurrent consolidationStale after 1 hour

All three gates must pass before consolidation triggers. The process runs as a background task — it doesn't block the user.

let dreamer = AutoDream::new(memory_dir, conversations_dir);

if dreamer.should_consolidate() {
    dreamer.acquire_lock()?;
    // Run consolidation agent...
    dreamer.update_state()?;
    dreamer.release_lock()?;
}

Session Memory Extraction

Automatic extraction of memories from conversations. Triggers after 20+ messages and 3+ tool calls since last extraction.

Categories extracted:

CategoryConfidence RangeExample
UserPreference0.7-1.0"Prefers functional patterns"
ProjectFact0.8-1.0"API uses REST with JSON"
CodePattern0.5-0.8"Uses builder pattern for config"
Decision0.7-0.9"Chose PostgreSQL over SQLite"
Constraint0.8-1.0"Must support Python 3.8+"
// Check if extraction should run
if session_memory::should_extract(&messages, &state) {
    // Parse extraction output from the model
    let memories = session_memory::parse_extraction_output(&model_output);

    // Persist to memory directory
    session_memory::persist_memories(&memories, &memory_path)?;
}

On this page