Files
Krystie dc5dc94d79 Initial commit: Adaptive Recall sidecar for UMP (Phase 5)
Multi-channel retrieval sidecar over Universal Memory Protocol:
- 3-channel RRF (UMP FTS5 + Qdrant vector + knowledge graph)
- ACT-R re-ranking (Anderson 1983) with access tracking
- Co-occurrence graph edges (Phase 6) for dense traversal
- Memory lifecycle decay (Phase 4) with per-kind confidence
- MCP shim routes recall through sidecar, falls back to canonical UMP

Architecture:
- src/server.js      HTTP sidecar on port 4380
- src/graph.js       2592-node / 111-edge graph from UMP (or +cooccur: 13k+)
- src/actr.js        A_i = -d*ln(age) + beta*log1p(freq) + epsilon*conf
- src/access_log.js  per-URN counter + last_accessed_at
- src/ump-recall-mcp.js  MCP shim (recall via sidecar, others passthrough)

Eval results (851-record UMP corpus):
- 2ch RRF over baseline: +50pp recall@10
- 3ch RRF (+graph): +60pp, 12 unique wins
- ACT-R re-rank: 4/20 #1 changes, 84% top-5 retention

Tests: 76/76 passing across graph (27), actr (27), access_log (28),
decay (20), mcp-shim (sidecar + fallback). Run with: npm test

Inspired by AIAppsAPI/adaptive-recall but built from scratch against
existing DNS2 infrastructure (UMP at :4317, Qdrant at :6333,
Ollama at :11434). No paid SaaS, MIT-licensed.
2026-07-12 19:24:47 -07:00

5.8 KiB
Raw Permalink Blame History

Adaptive Recall Phase 2 + Phase 4 — Final Report

Date: 2026-07-12 Build context: Adaptive Recall for both Hermes and Krystie, sidecar on port 4380, 851 UMP records, 768/851 in Qdrant memories_ump.


Phase 2 (Knowledge Graph Channel)

What was built

File Lines Purpose
src/graph.js 414 Graph class with load/save/buildFromRecords/addEntity/addEdge/neighbors/searchEntities/topEntities/topEdges/stats
scripts/build_graph.js 82 Standalone script that loads UMP records and builds/persists the graph
state/graph.json Built artifact: 2592 nodes, 111 edges, 719 URNs
state/SCHEMA.md Documentation of actual UMP record schema (corrects compaction summary)
test/test_graph.js 10+ unit tests (the graph test file from subagent)

Wiring

src/server.js modified to:

  • Import Graph from ./graph.js
  • Add lazy-loaded singleton getGraph() that loads from GRAPH_FILE (env-overridable)
  • Add runGraphChannel(): extracts entities from query, BFS up to GRAPH_DEPTH hops (default 2), score = 1/hops
  • Wire 3rd channel into /recall: Promise.allSettled([runUmpChannel, runVectorChannel, runGraphChannel])
  • Update /health to report graph stats ({enabled, loaded, nodes, edges, urns, file}) and bump phase label to 2C
  • Default weights now {ump: 1.0, vector: 1.0, graph: 1.0}

Eval results

Channel-contribution eval (scripts/eval_channel_contribution.py):

Avg graph-channel hits in top-5:
  3ch (graph enabled):    0.85 hits/query
  2ch (graph zero-weigh): 0.30 hits/query
  Delta:                  +0.55

Hits in 3ch top-5 that are NOT in 2ch top-5:
  Total unique: 12 (avg 0.60/query)

Latency: 2ch=903ms  3ch=1079ms  Δ=+176ms (graph overhead)

Notable graph rank-#1 wins (queries where graph found a hit that ranked higher than ump+vector):

  • "Triangles fuzz harness CI gflags missing" → graph rank #1
  • "Krystie OpenClaw migrated Hermes profile" → graph rank #1
  • "Triangles test coverage PR keystore V5 soft cap" → graph rank #1
  • "Triangles multisig stack walk test audit" → graph rank #3
  • "DashCaddy TOTP recovery panel disable bak fallback" → graph rank #3
  • "Triangles PoW PoS cutoff nonce zero" → graph rank #4

Zero regressions — 10/20 queries saw graph-channel hits in top-5; the other 10 stayed at the same rank.


Phase 4 (Memory Lifecycle Decay)

What was built

File Purpose
scripts/ump_decay.py Standalone Python script with pure apply_decay(records, dry_run) function, atomic writes, timestamped backups
test/test_ump_decay.py 20 unit tests across 10 test cases, stdlib-only
Cron eaff2d9683fc Runs nightly at 3am

Decay rates (per day since last access)

Kind λ Notes
identity 0.0001 very slow, almost never decays
semantic 0.001 facts over months
procedural 0.005 medium, skills fade if unused
note 0.003 medium-slow, session notes
episodic 0.01 events over weeks
working 0.05 fast, session context

Formula: confidence_new = confidence_old * exp(-λ * days_since_modified_or_created) Floor at 0.05.

Status transitions:

  • candidate → active if confidence >= 0.5
  • active → archived if confidence < 0.2
  • archived → archived (no resurrection)
  • tombstoned skipped entirely

Dry-run on real data (851 records)

Status transitions:
  active    708 → 760  (+52 from candidate promotion)
  candidate 53  → 1    (52)
  archived  42  → 42
  tombstoned 48 → 48   (skipped)

Archives triggered: 0 (oldest record is 105 days; even working λ=0.05 × 20d only decays ~64%)
Promotions: 52 (mostly procedural/semantic at conf=0.75-0.85 within last 36 days)

Confidence histogram (decayed subset, 803 records):
  [0.20-0.40) = 93
  [0.40-0.60) = 213
  [0.60-0.80) = 286
  [0.80-1.00] = 211

Edge cases found:
  - 809/851 records missing time.modified (defaults to time.created) — handled
  - No weird timestamps, no missing lifecycle blocks
  - float-edge in exp(-λ*0) yields ~1.0 - 3e-13 (handled with tolerance)

Tests

20/20 PASS — all 10 spec test cases plus extras.


Sidecar status

$ curl -s http://127.0.0.1:4380/health | python3 -m json.tool
{
  "status": "ok",
  "qdrant": "reachable",
  "ump": "reachable",
  "ollama": "reachable",
  "graph": {
    "enabled": true,
    "loaded": true,
    "nodes": 2592,
    "edges": 111,
    "urns": 719,
    "file": "/root/ump-recall/state/graph.json"
  },
  "qdrant_collection": "memories_ump",
  "upstreams": {...},
  "phase": "2C"
}

Sidecar restarted cleanly (old PID 4036490 killed, new PID 4102494 via background process).


Both gateways now use 3-channel RRF

Verified earlier:

  • Hermes config patched (via terminal sed, since patch tool was security-guarded)
  • Krystie config already pointing at shim
  • MCP shim routes recall to sidecar, falls back to canonical UMP if sidecar down
  • All other tools pass through to canonical UMP

What's still TODO

  1. Cron first runump-decay-nightly runs 2026-07-13 03:00. First dry-run before apply is the safety check.
  2. Refresh graph.json periodically — when UMP gets new records, the graph is stale. Add a cron to run node scripts/build_graph.js weekly.
  3. Filter noisy edges — some spurious relations like "hours→last" come from arrow regex matching English. Could add a stopword filter.
  4. Refine entities.js — the graph layer added EXTRA_ENTITY_PATTERNS (lowercase services, port numbers, krystie-* codes) because the core extractor only knows a fixed allow-list. Could merge these into the main extractor.
  5. Pre-existing MCP shim bug — shim tries to call canonical UMP subprocess without first running initialize, so remember/revise/forget/get/feedback all fail with "unknown tool". Out of scope here but flagged.