feat(memory-fixer): add duplicate-detection phase; correct stale canonical pointer + reporting model (v2.2.0)
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 8s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 2s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 9s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 4s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 0s

Level 1 gains phase 5, duplicate-node detection: runs
memory_dup_detect.py (read-only) and reports clusters by verdict.

- WRITER-DEFECT (run_family): one task creating a node per run -> writer
  fix, never merge (per-run nodes are the audit trail)
- SAFE-MERGE: identical bodies, still needs an explicit Kwame decision
- HUMAN-DECISION: same subject, bodies differ -> connect, never merge

Replaces the naive '>70% title overlap' duplicate rule, which false-positives
on distinct work: four client workflows (#357-#361) and two machines'
migrations (#1792/#1793) score high on titles with bodies 0.1-0.3 apart.

Also corrected in the same pass:
- the 'canonical copy' pointer named /root/.hermes/contracts/memory-fixer-v3.md,
  which does not exist on kagentz (no /root access); the job's instruction set is
  inline in ~/.hermes/cron/jobs.json
- 'reports to Kwame via this Zulip DM' described the pre-2026-09-21 model; the
  report is DROPped to the gate (comms_drop.py) and exit 0 means QUEUED, not sent
- added a Checks line so a skipped phase 5 is visible in the report
This commit is contained in:
Mumuni
2026-09-26 19:32:36 +00:00
parent d99b552448
commit c460ef905c
+44 -6
View File
@@ -6,13 +6,19 @@ 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
version: 2.2.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.
> **Executable copy:** the `okyeame-memory-fixer` cron job on kagentz (`hermes cron list`) holds its instruction
> set **inline in `~/.hermes/cron/jobs.json`** (`hermes cron edit <id> --prompt …`; there is no `--prompt-file`, and
> `~/.hermes/cron/memory-fixer-prompt.md` is a synced draft, not the live instruction). This file is the institutional
> record of the same contract; when the two diverge, the job prompt is what actually runs — diff it against this file
> before claiming a prompt change landed.
> ⚠️ Corrected 2026-09-26: the previous pointer (`/root/.hermes/contracts/memory-fixer-v3.md`) does not exist on
> kagentz — no `/root` access from this container — and was verified unreachable, not merely stale.
## 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.
@@ -125,15 +131,44 @@ updateNode(id, {
**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).
### 5. Duplicate-Node Detection (Level 1 — read-only, every run)
The graph's duplicate problem is rarely an agent mistyping a title: it is **recurring writers creating a new
node per run instead of updating one**. This phase detects that class and reports it. It is read-only and
**never merges**.
```bash
python3 /home/hermes/.hermes/scripts/memory_dup_detect.py --json
```
Read-only, ~15s over the whole graph, exit 0. That script is the source of truth for the clustering logic —
do not re-implement it in the prompt or hand-count "duplicates" from titles.
Consume each `items[]` entry's `verdict` field; do not invent your own:
| `verdict` | Meaning | Required action |
|---|---|---|
| `WRITER-DEFECT` (`run_family: true`) | ONE scheduled task writes a new node per run | Report the ids, the `agents` (the writer) and `span_days`. **Never merge** — each node is that run's audit record. If the family grew since the last report, say `UNFIXED` and name the writer. |
| `SAFE-MERGE` | Bodies identical | Still requires an explicit `merge #A into #B` decision from Kwame. |
| `HUMAN-DECISION` | Same subject, bodies differ | Propose **connect (an edge)**, never merge. |
- **Title overlap alone is not duplication.** Four distinct client workflows of one family (#357-#361) and two
different machines' migrations (#1792/#1793) both score high on title tokens while their bodies sit 0.1-0.3
apart. Confirm against body similarity before calling anything a duplicate.
- Report clusters as **candidates for Kwame's decision**, never as established duplicates — a wrong auto-merge
destroys distinct content irrecoverably.
- Per-run history nodes are kept deliberately. Bulk-merging a run family destroys the audit trail the family exists for.
## 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?
2. **Duplicate Nodes** — as detected by fix 5, by `verdict`, never by raw title overlap. `WRITER-DEFECT` is a writer fix (update one canonical node), not a merge decision; `SAFE-MERGE` and `HUMAN-DECISION` clusters are escalated for merge-or-connect.
3. **Orphan Nodes >90 days old** — Archive or connect?
## Reporting Format
The fixer reports to Kwame via this Zulip DM:
The fixer does **not** send anything. Under the single-egress model (2026-09-21) every report leaves the node
through Mumuni's gate (`comms_drop.py` for the queue, `comms_gate.py` to release and read-back verify), so
exit 0 means QUEUED, never delivered. A report body is written to a file and handed to the outbox helper:
```
🦅 Memory Fixer — [HH:MM UTC]
@@ -147,8 +182,10 @@ Stale nodes needing review (max 10):
2. [Node #YYY] Title — Y days stale, SUGGEST: archive
...
Duplicates needing decision:
1. [Node #AAA] vs [Node #BBB] — Same title
Duplicate clusters (candidates — Kwame decides; the fixer never merges unilaterally):
1. [WRITER-DEFECT] #AAA/#BBB/#CCC — writer <agent>, N nodes, span Nd (UNFIXED if it grew since the last report)
2. [HUMAN-DECISION] #DDD/#EEE — same subject, bodies differ, SUGGEST: connect
3. "none" when the scan returned no clusters
Orphans >90 days:
1. [Node #EEE] Title — X days stale, orphaned
@@ -195,6 +232,7 @@ The result must be 0 rows when all decisions are executed. Report what was done.
- **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.
- **Duplicate scan ran:** every report carries the fix 5 block (`none` when there were no clusters). A report with no duplicate section means phase 5 was skipped — a silently skipped detection phase is the failure this phase exists to prevent.
- **Timestamps:** every executed decision (and every auto-archive) bumps `updated_at`, so the node exits the stale window on the next run.
## Logging