--- report_only_agents: - koby # ⛔ KOBY IS NEVER REPAIRED (Rule 17, 2026-08-17) — detect + report, never fix on .129 kind: pattern name: memory-fixer description: > Auto-fix low-hanging fruit in the RA-H OS knowledge graph. No judgment calls — only deterministic Level 1 operations. Escalate anything that needs Kwame's input. Executes confirmed Kwame decisions to completion (state + updated_at). version: 2.1.0 --- --- # Memory Fixer > **Canonical copy:** `/root/.hermes/contracts/memory-fixer-v3.md` (used by the `memory-fixer-daily` cron job). This file is the institutional record of the same contract. When the two diverge, treat the v3 source in `/root/.hermes/contracts/` as executable truth. ## Purpose Auto-fix low-hanging fruit in the graph. No judgment calls — only deterministic Level 1 operations. Escalate anything that needs Kwame's input. When Kwame replies to an escalation, **execute the decision to completion** (update state and timestamps), never leaving a node in review-pending forever. **Type:** Write-only (Level 1 fixes only) **Scope:** RA-H OS knowledge graph (192.168.68.65) **Schedule:** Daily at 8 AM ET **Escalation:** Level 2+ to Kwame as task items ## Key Design Decision The `updateNode` tool's `metadata` field performs a **restricted merge** — the `state` key only accepts `'processed'` or `'not_processed'`. Additionally, new metadata keys cannot be added via the merge. **Solution:** Use the `description` field to tag stale nodes with review actions, since `description` is a simple string overwritable via `updateNode`. **Tag Format:** `[REVIEW: action] original description text...` Where `action` is one of: - `archive` — node is stale and should be archived - `refresh` — node is stale and should be refreshed (infrastructure) - `keep` — node has been confirmed as current - `merge` — node is a duplicate candidate **Query for finding review-tagged nodes:** ```sql SELECT id, title, description FROM nodes WHERE description LIKE '[REVIEW:%'; ``` ## Level 1 Auto-Fixes (No Kwame Decision Needed) ### 1. Missing `type` Auto-Classification ```sql SELECT id, title, CASE WHEN title LIKE '%infrastructure%' OR title LIKE '%proxmox%' OR title LIKE '%setup%' THEN 'infrastructure' WHEN title LIKE '%skill%' OR title LIKE '%how to%' OR title LIKE '%guide%' THEN 'skill' WHEN title LIKE '%doc%' OR title LIKE '%template%' OR title LIKE '%brand%' THEN 'documentation' WHEN title LIKE 'WAL:%' OR title LIKE 'TASK:%' THEN 'note' WHEN title LIKE '%[LEARN]%' THEN 'documentation' ELSE 'note' END as auto_type FROM nodes WHERE json_extract(metadata, '$.type') IS NULL; ``` ### 2. Missing `namespace` Auto-Population ```sql SELECT id, title, json_extract(metadata, '$.tenant') as tenant FROM nodes WHERE json_extract(metadata, '$.namespace') IS NULL; ``` ### 3. Staleness Review Tagging (refresh-suggested nodes only) Using the type-based windows from the memory-monitor contract, tag nodes stale beyond their window. **Only process a maximum of 10 nodes per run** to avoid overwhelming Kwame. Prioritize infrastructure first, then dynamic, then ephemeral. **Archive-suggested nodes are NO LONGER tagged — they are archived outright (see Level 1 fix 4).** Tagging with `[REVIEW: refresh]` applies only to living nodes (infrastructure, deployment, system, system-health, business, philosophy, research, learning, investigation, analysis, project, agent, registry, policy). **Exclusion Rules:** - Nodes with `state` = `review_pending`, `deprecated`, `archived`, or `not_processed` are NOT processed - Nodes whose `description` already starts with `[REVIEW:` or `[ARCHIVED]` are NOT re-processed ```sql SELECT id, title, json_extract(metadata, '$.type') as node_type, CAST(julianday('now') - julianday(updated_at) AS INTEGER) as days_stale, CASE WHEN json_extract(metadata, '$.type') IN ('infrastructure', 'deployment', 'system', 'system-health') THEN 'refresh' WHEN json_extract(metadata, '$.type') IN ('business', 'philosophy', 'research', 'learning', 'learn', 'investigation', 'analysis', 'project') THEN 'refresh' ELSE 'archive' END as suggested_action FROM nodes WHERE updated_at < datetime('now', CASE WHEN json_extract(metadata, '$.type') IN ('infrastructure', 'deployment', 'system', 'system-health') THEN '-14 days' WHEN json_extract(metadata, '$.type') IN ('skill', 'documentation', 'template', 'protocol-enforcement', 'prd', 'architecture') THEN '-90 days' WHEN json_extract(metadata, '$.type') IN ('note', 'wal', 'WAL', 'task', 'TASK', 'event') THEN '-30 days' WHEN json_extract(metadata, '$.type') IN ('business', 'philosophy', 'research', 'learning', 'learn', 'investigation', 'analysis', 'project') THEN '-120 days' WHEN json_extract(metadata, '$.type') IN ('deprecated-relay', 'audit', 'audit-report', 'incident', 'incident-report') THEN '-3650 days' ELSE '-45 days' END ) AND json_extract(metadata, '$.state') NOT IN ('review_pending', 'deprecated', 'archived', 'not_processed') AND (description IS NULL OR description NOT LIKE '[REVIEW:%') ORDER BY days_stale ASC LIMIT 10; ``` For each identified node, call `updateNode(id, { description: "[REVIEW: action] " + originalDescription })`. ### 4. Stale-Node Archiving (Level 1 — standing Kwame directive, 2026-09-11) **Kwame's standing directive: stale nodes CAN be archived by the fixer. No per-batch escalation, no `[REVIEW: archive]` tagging — archive them.** For every node whose suggested action is `archive` (i.e. its type is NOT one of the living types in fix 3), archive it in a **single** `updateNode` call: ```python updateNode(id, { "description": "[ARCHIVED] " + originalDescriptionWithoutReviewTag, "metadata": {"state": "archived"} }) ``` - `state` transitions **DO work through `updateNode`** (`archived`, and back to `active`). The former "state only accepts processed/not_processed, use SSH" claim was wrong — verified 2026-09-11 by archiving 7 nodes (#61, #373, #388, #465, #475, #526, #1476) over the bridge with `updated_at` auto-bumping. **SSH to the bridge host is a fallback, not a requirement**, and it is blocked from kagentz anyway. - Pass `description` and `metadata` in the **same** call, and always keep the `updates` object nested: `{"id": N, "updates": {…}}`. - Archiving is non-destructive: the node stays in the graph, marked `state: archived` + `[ARCHIVED] ` prefix. **Living nodes (refresh-suggested) are NEVER archived** without a specific Kwame decision — they are the cluster/agent/business canon. **Archive candidates are identified by the fix 3 query's `suggested_action = 'archive'` branch** (the `ELSE 'archive'` case: anything not an infrastructure/skill/documentation/strategic/audit type). ## Level 2 Escalations (Kwame Decision Required) 1. **Refresh-suggested stale nodes** flagged with `[REVIEW: refresh]` — refresh or keep? (Archive-suggested nodes are auto-archived under fix 4 and are not escalated.) 2. **Duplicate Nodes** (same title or >70% title overlap) — Merge or keep? 3. **Orphan Nodes >90 days old** — Archive or connect? ## Reporting Format The fixer reports to Kwame via this Zulip DM: ``` 🦅 Memory Fixer — [HH:MM UTC] Level 1 fixes applied: - Missing type: X nodes classified - Missing namespace: Y nodes populated Stale nodes needing review (max 10): 1. [Node #XXX] Title — X days stale, SUGGEST: refresh 2. [Node #YYY] Title — Y days stale, SUGGEST: archive ... Duplicates needing decision: 1. [Node #AAA] vs [Node #BBB] — Same title Orphans >90 days: 1. [Node #EEE] Title — X days stale, orphaned Reply with: - "archive #XXX, #YYY" to mark for archive - "archive all" to archive all stale nodes listed - "keep #XXX" to confirm a node is current - "merge #AAA into #BBB" to merge duplicates - "refresh #XXX" to mark as current ``` ## Execution on Next Run The fixer reads Kwame's previous response and **executes the decision to completion** — it must not leave a node in review-pending forever. Tagging alone is NOT enough; each confirmed decision must also update `state` and `updated_at` so the node drops out of the stale window on the next run. > ⚠️ **Corrected 2026-09-11:** `updateNode` DOES accept `state` changes — `{"updates": {"description": …, "metadata": {"state": "archived"}}}` works over the bridge, and `updated_at` bumps automatically. The old "use direct SSH + SQLite for state transitions" instruction was based on a wrong assumption; SSH is a fallback only (and is blocked from kagentz). Use one `updateNode` call for both the tag and the state. > ```bash > ssh root@192.168.68.65 "sqlite3 /root/.local/share/RA-H/db/rah.sqlite \"UPDATE nodes SET metadata = json_set(metadata, '$.state', ''), updated_at = datetime('now') WHERE id = ;\"" > ``` > Use `updateNode` only for description/source/title/link edits. Decision → completed action mapping: | Kwame reply | Description change | State | `updated_at` | |---|---|---|---| | `archive #XXX` | replace `[REVIEW: archive] ` → `[ARCHIVED] ` prefix | `archived` | bumped to now | | `keep #XXX` / `refresh #XXX` | **clear the `[REVIEW: …]` tag entirely** | `active` | bumped to now | | `merge #AAA into #BBB` | set `[REVIEW: merge_into #BBB]` on #AAA, then follow manual merge workflow | handled manually | bumped to now | | `archive all` | apply the archive row to every node listed in the prior report | `archived` | bumped to now | **Why `updated_at` must be bumped (critical):** the Level-1 staleness query keys off `updated_at < now - window`. If the fixer clears the tag but leaves a stale `updated_at`, the node is immediately re-flagged on the very next run and the cycle repeats forever. Bumping `updated_at` to now pushes the node back to the front of the window. **Exclusion after action:** once an action is applied, the node's description no longer starts with `[REVIEW:` (archive → `[ARCHIVED]`, refresh/keep → original text), so it is not re-processed. After all actions are applied, verify with: ```sql SELECT id, json_extract(metadata, '$.state') FROM nodes WHERE description LIKE '[REVIEW:%'; ``` The result must be 0 rows when all decisions are executed. Report what was done. ## Checks - **State integrity:** archived nodes have `state: archived` + `[ARCHIVED]` prefix; kept nodes are `state: active` without a `[REVIEW:]` tag. - **Auto-archive applied:** no node should ever be left tagged `[REVIEW: archive]` — that tag is retired. Any `[REVIEW: archive]` found means fix 4 was skipped; archive it and report. - **No review-pending forever:** after executing Kwame's decisions, `[REVIEW:%` node count must be 0. - **Timestamps:** every executed decision (and every auto-archive) bumps `updated_at`, so the node exits the stale window on the next run. ## Logging Every Level 1 fix logged to `~/.hermes/logs/memory-fixer/YYYY-MM-DD.md` Every Level 2 escalation logged and delivered to Kwame.