dc5dc94d79
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.
37 lines
1.7 KiB
Python
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") |