1 row per label · never auto-distilled
1 row per message · FTS5 indexed · recent-window into prompt
emotional_impact in [-10, +10] and up to 3 relational tags from a closed vocabulary.
concept_nodes WHERE type=EVENT · embedded via sqlite-vec
3/24h), an LLM reflects on recent L3 events and produces 1–2 thoughts. Every thought must cite its source event IDs via filling[].
concept_nodes WHERE type=THOUGHT · edges in concept_node_filling
L2 captures every incoming and outgoing message before the LLM streams a reply. This is the only write that happens on the turn's critical path. Latency matters here; keep it fast.
L1 is also a write target, but only by admin edits and the onboarding bootstrap — never per-turn.
After a session idle-closes (default 30min), a background worker pulls L2 messages and asks a small LLM to extract 0–3 events (L3). If the reflection gate allows, a medium LLM then reflects on recent events to produce 1–2 thoughts (L4), each with explicit filling[] evidence.
At assembly time, every new user message triggers: L1 read (all 5 blocks), L2 read (recent window), and an L3+L4 vector search with ranking formula:
total = 0.5·recency + 3·relevance + 2·|impact| + 1·relational_bonus
Nothing ever filters by channel_id (iron rule D4).
The retrieval scoring is a direct descendant of Stanford's 2023
Generative Agents paper
(Park et al., "Interactive Simulacra of Human Behavior").
In their implementation at
reverie/backend_server/persona/cognitive_modules/retrieve.py,
the new_retrieve() function fuses three signals:
recency × 0.5, relevance × 3.0, importance × 2.0.
EchoVessel's weights are lifted verbatim from there.
Four deliberate divergences from the Stanford original:
relational_bonus × 1.0 — a closed-vocabulary tag bonus (identity-bearing / unresolved / vulnerability / turning-point / correction / commitment) tuned for long-term companionship scenarios, where "core facts about who the user is" deserve extra pull.decay^i where i is rank in last-accessed order — being retrieved refreshes the memory (cognitive-psychology "rehearsal" effect). EchoVessel: 14-day half-life from created_at, access never refreshes. Diary-style vs brain-style.emotional_impact ∈ [-10, +10] instead of their unsigned poignancy ∈ [1, 10]. Lets grief (-9) and joy (+9) stay separated in retrieval; the magnitude drives salience, the sign drives valence grouping.
Park's original comment in the reference code: "these weights should likely be learned, perhaps through an RL-like process".
For now both projects hand-tune to [0.5, 3, 2].
If you find a better set via eval, the whole ranker is just four floats in memory/retrieve.py.