How It Works
Memory lifecycle
memory_add(text)
│
▼
1. Hash dedup ──── duplicate? ──► skip
│
▼
2. Note Construction (LLM)
├── keywords (3–7 key terms)
├── tags (2–4 category tags)
├── context (one-sentence summary)
└── category (Technical / Business / Personal / ...)
│
▼
3. Link Generation
├── retrieve top-6 candidates (embedding similarity > 0.3)
└── LLM verifies each: link bidirectionally if relevant
│
▼
4. Memory Evolution
├── up to 3 linked notes get attributes updated
└── may trigger additional link candidates
│
▼
5. Save to QdrantRetrieval pipeline
memory_search(query)
│
▼
1. Embed query (local ONNX, 384-dim)
│
├──► BM25 ranking (Jieba tokenized for CJK)
└──► Dense vector cosine similarity
│
▼
2. RRF fusion (k=60)
Final Score = BM25_rank⁻¹ + Vector_rank⁻¹
│
▼
3. Heat boost
Score × (1 + 0.05 × ln(1 + retrieval_count) / (age_days + 1))
│
▼
4. 2-hop BFS expansion
├── Walk link graph up to 2 hops from top-K anchors
└── Admit only nodes with cos-sim ≥ 0.25 vs query
│
▼
5. Return merged, deduplicated resultsTemporal invalidation
When a memory is updated or contradicted, the old note is marked is_active: false and excluded from all future searches via Qdrant payload filtering. No data is ever hard-deleted — the full history is preserved.
Daily consolidation
At 02:30 AM (in-process scheduler), the plugin:
- Groups all active notes by
category - Within each group, finds pairs with cosine similarity ≥ 0.75
- Merges duplicates into a single unified note
- Cascades all link references from soft-deleted notes to the merged note
This prevents memory bloat from semantically redundant facts accumulated over days.
