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

162 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 run**`ump-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.