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

37 lines
1.7 KiB
Python

#!/usr/bin/env python3
"""Phase 3 smoke test — verify ACT-R re-rank works."""
import json
import urllib.request
def call(query, limit=5, with_actr=True):
body = {"query": query, "limit": limit}
if not with_actr:
body["weights"] = {"ump": 1.0, "vector": 1.0, "graph": 1.0} # ACT-R off via env, not weights
req = urllib.request.Request(
"http://127.0.0.1:4380/recall",
data=json.dumps(body).encode(),
headers={"content-type": "application/json"},
)
with urllib.request.urlopen(req, timeout=60) as r:
return json.loads(r.read())
def show(label, q, actr=True):
print(f"\n=== {label}: query={q!r} (actr={actr}) ===")
r = call(q, 5, actr)
print(f"phase={r.get('phase')} rerank_applied={r.get('rerank_applied')} pool={r.get('rerank_pool')}")
print(f"channels={[(c['name'], c['hit_count']) for c in r.get('channels',[])]}")
print(f"actr cfg: {r.get('actr')}")
for h in r.get("hits", []):
urn = h["urn"][:50]
rrf = h.get("rrf_score", h.get("score", 0))
actr_s = h.get("actr_score")
final = h.get("final_score")
rank = h.get("final_rank", "?")
bc = list(h.get("by_channel", {}).keys())
actr_str = f"{actr_s:.3f}" if isinstance(actr_s, (int, float)) else "n/a"
final_str = f"{final:.4f}" if isinstance(final, (int, float)) else "n/a"
print(f" rank={rank} rrf={rrf:.4f} actr={actr_str} final={final_str} via={bc} {urn}...")
show("Entity-rich (expect ACT-R to promote graph-discovered hits)", "DNS2 ollama Triangles")
show("Generic (ACT-R should be near no-op since all candidates have similar metadata)", "the and of")
show("Recent concept (ACT-R's age term should favor recent)", "2026-07-12")