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.
2.6 KiB
2.6 KiB
UMP memory.ump.json — actual schema (verified 2026-07-12)
File: /root/.openclaw/agents/main/workspace/state/ump-local/memory.ump.json
Format: JSON array, 851 records
Total records: 851 (not 846 — was 768 in Qdrant backfill, 846 in watcher's last run; real total is 851)
Top-level fields per record
{
"ump": "0.1", // schema version (string)
"id": "urn:ump:jzbhzerwqn23hfzre...", // URN string (not "urn"!)
"kind": "procedural", // see kinds below
"body": {
"subject": "...", // short title
"text": "..." // full content
},
"scope": {
"owner": "did:key:z6Mk...", // actor DID
"project": "openclaw/workspace",
"visibility": "private",
"tags": ["..."]
},
"time": {
"created": "2026-06-06T21:49:57.806Z", // ISO 8601
"modified": "...",
"observed": "...",
"valid_from": "...",
"valid_to": null
},
"lifecycle": { // NESTED, not flat!
"status": "active", // see statuses below
"confidence": 0.6 // 0.0 - 1.0
},
"superseded_by": [], // array or null
"provenance": {...},
"integrity": {...}
}
Kinds (distribution)
| kind | count |
|---|---|
| procedural | 245 |
| semantic | 231 |
| identity | 152 |
| episodic | 152 |
| note | 42 |
| working | 29 |
(Total: 851)
Lifecycle statuses (distribution)
| status | count | meaning |
|---|---|---|
| active | 708 | normal, queryable |
| candidate | 53 | pending promotion |
| archived | 42 | kept but deprioritized |
| tombstoned | 48 | deleted logically — skip in retrieval |
Critical corrections vs spec
- Field is
id, NOTurn lifecycle.statusis nested inlifecycle.status, NOT flatstatuslifecycle.confidenceis nested, NOT flatkindvalues includenote(in addition to standard 5)- Status values:
active,candidate,archived,tombstoned(not justactive/archived) - No
topicfield — usesscope.projectandscope.tags - No
access_countorlast_accessed_atfields yet (would need to be added)
Code patterns
import fs from 'node:fs';
const data = JSON.parse(fs.readFileSync(UMP_FILE, 'utf-8'));
for (const r of data) {
const urn = r.id; // NOT r.urn
const text = r.body?.text ?? r.body?.subject ?? '';
const kind = r.kind; // procedural|note|semantic|working|identity|episodic
const status = r.lifecycle?.status ?? 'active';
const confidence = r.lifecycle?.confidence ?? 1.0;
const createdAt = r.time?.created ?? null;
}