Memory Fact-Check
Origin: the skill of the same name in leeyudok/doksam-skills.
Deliberately forked for template bundling — not auto-synced; good improvements are cherry-picked manually.
Memory decays. A fact that was true when written becomes false as code, schema and
infrastructure move on. Stale memory is worse than no memory — an agent reads it and
confidently does the wrong thing.
This is not a structural hygiene pass. Orphan files, duplicate entries, index bloat,
broken internal links — those checks compare memory files to each other. This skill
compares each memory to the world it describes: the code, the database, the issue
tracker, the filesystem. A memory can be perfectly well-formed, correctly indexed, recently
touched — and completely false.
Automatic deletion is forbidden. Only a source-of-truth comparison can tell what is dead,
and losing an incident lesson is expensive. Hence semi-automatic: correct freely, archive
only with approval, never delete.
1. Locate and inventory
Find the memory set. Common locations, in order:
- a path declared by the project's agent instructions (
AGENTS.md / CLAUDE.md — e.g.
"memory SSOT is .claude/memory/"). A declared path wins over every default.
.claude/memory/ in the repo (team-shared, committed)
- the host's per-project memory dir (e.g.
~/.claude/projects/<slug>/memory/)
Collect for each file: frontmatter (name/description/type), and last modification date
(git log -1 --format=%cs -- <file> for committed memory, stat otherwise). Note the index
file (MEMORY.md) if one exists — it is audited too, but it is an index, not a memory.
Personal files are out of scope — anything the project marks personal (user_*.md or
equivalent) belongs to its owner. Leave untouched.
Reading many files: dumping 50 memories into context at once blows the tool output cap
and wastes the budget. Concatenate to one scratch file with ########## <filename> headers,
then page through it. Do not skim — a stale claim is usually one clause inside an otherwise
correct paragraph.
2. Extract load-bearing claims
Per file, pick the 1–3 claims that change what an agent would do. Ignore prose, rationale
and background; a memory is only as stale as its actionable assertions.
Load-bearing looks like: "X lives at path P" · "table T has N rows" · "issue #N is still open"
· "feature F does not exist yet" · "library L is not installed" · "the fix for this is still
pending" · "run command C to verify".
3. Verify against the source of truth
Work cheapest-and-highest-yield first. In practice the ranking below holds: issue status
is one API call and catches the largest share of stale claims, because memories are written
mid-work and the work then finishes without anyone going back to edit the memory.
| Order |
Claim type |
How to verify |
| 1 |
Issue/PR state ("#N open", "waiting on #N", "decision pending") |
forge CLI/API — gh issue view N --json state / glab api projects/<enc>/issues/N. Batch them in one loop |
| 2 |
Path/URL (script locations, deploy paths, endpoints) |
ls, test -f, curl -s -o /dev/null -w '%{http_code}' |
| 3 |
Code (file/class/config exists, behaves a certain way) |
grep/Read the current tree — the code is the SoT, not the memory |
| 4 |
Data/schema (tables, columns, row counts) |
read-only queries via the project's DB tool. Prefer catalog estimates first (pg_class.reltuples, information_schema.columns), exact count(*) only when the estimate is the disputed claim |
| 5 |
Runtime/host (cron jobs, services, logs) |
ssh <host> 'ls …; crontab -l; tail <log>' — a job's last log line dates the claim precisely |
Parallelize independent verifications. If a source is unreachable, say so explicitly in the
report — never silently downgrade "couldn't check" to "checked".
4. Classify
- fresh — every claim holds. Don't touch it.
- stale — some claim is outdated (moved path, changed number, closed issue, implemented
gap). → Correct the body now, with the measured value and the date. Corrections are
within autonomous scope; they are additive truth, not deletion.
- dead — the core premise is gone (library removed, feature retired, wholly superseded).
→ Mark as an archive candidate only.
5. Stale patterns worth hunting
Beyond "the number changed", these recur and are easy to miss:
- Fixed-gap drift — the memory documents a missing capability ("there is no startup
reconcile", "no rate limiting yet") and it has since been built. This is the most dangerous
class: the agent re-implements or re-reports work that already shipped. Check the linked
issue and grep for the symbol.
- Cross-memory contradiction — two memories disagree (one says a script is the way to do
X, another says that script was retired). At least one is stale by definition. Compare
claims across files, not only file-by-file.
- Scale drift — "table T has ~8M rows" written months ago now off by 25%. Harmless as
trivia, harmful when the memory derives advice from it (batch sizes, timeout budgets,
"this query takes 19s").
- Recipe rot — the memory stores a command/query as a verified recipe, and the recipe no
longer works at today's data volume or API version. Re-run stored recipes; a recipe you
did not execute is unverified.
- Progress-state drift — a long-running job/backfill memory whose "current status" section
is weeks behind, sometimes with two internally contradictory status sections stacked up.
Date each section, keep the newest, mark superseded ones.
- Identity mismatch —
name/description says one thing, the body says the opposite
(e.g. a file named *-via-toolX whose body records that toolX was abandoned). Recall
matches on description, so the file gets loaded for the wrong reason, or missed entirely.
6. Report, then apply
Report as a table before changing anything — file · class · one-line evidence · action:
| File |
Class |
Evidence |
Action |
reference_x.md |
stale |
script moved scripts/ → data/ |
path corrected |
project_y.md |
stale |
claims "#302 reconcile missing"; JobRunHistoryReconciler exists, #302 closed |
rewritten as done |
project_z.md |
dead candidate |
feature from #N removed in #M |
awaiting approval |
Then:
- Apply corrections to stale bodies — measured value + date, keeping the original
observation where it still carries a lesson ("was 8M as of , 9.9M as of ").
- Archive dead candidates only after explicit user approval:
git mv into
<memory>/archive/ and add archived: <date> <reason> to the frontmatter. Never rm.
- Sync the index — reflect corrections, drop archived entries from
MEMORY.md.
- Commit through the project's normal workflow (issue → branch/worktree → PR/MR). Memory
is team-shared content; it does not get direct-to-main commits.
Judgment rules — stay conservative
- Unverifiable ⇒ fresh. If the source of truth is unreachable, leave the memory alone and
say the check was skipped. Unknown is not dead.
- Incident lessons stay fresh even when the code moves. A memory recording why something
broke exists to prevent recurrence, not to snapshot a call site. Correct its stale path
references; do not retire the lesson because the file was renamed.
- Report the drift, don't invent the cause. If a count inverted or a number moved
inexplicably, record the measurement and flag it as unexplained. A plausible story written
into memory becomes tomorrow's false fact.
- Prefer merge over create. Two memories on one topic → propose merging into the existing
one.
- Non-obvious facts discovered by the audit itself become new memories — the audit is
itself a source of ground truth.
Field notes
- Fresh timestamps prove nothing. A file committed this week can carry a claim that was
already false when written; a file untouched for months can be perfectly true. Verify
claims, never sort by date and trim the tail.
count(*) on a large table may exceed the DB tool's statement timeout — that failure is
a finding when the memory claims the query is fast.
- Quote globs when shelling out from zsh (
grep --include="*.java"), or the shell eats them
and the check silently returns nothing — a false "fresh".
- An issue being closed does not by itself prove the described work shipped. For fixed-gap
claims, confirm with a grep for the symbol as well.
1---2name: memory-factcheck-33description: Fact-check an agent's persistent memory against ground truth — verify each memory's load-bearing claims against code, database, issue tracker and filesystem, correct the stale ones, and report dead ones as archive candidates. Use when memory grows past ~30 files, right after a big stack/infra change (library swap, version upgrade, server migration, schema drop), when two memories seem to contradict each other, or on a "clean up / audit my memory" request.4---56# Memory Fact-Check78> Origin: the skill of the same name in [leeyudok/doksam-skills](https://github.com/leeyudok/doksam-skills).9> **Deliberately forked** for template bundling — not auto-synced; good improvements are cherry-picked manually.1011Memory decays. A fact that was true when written becomes false as code, schema and12infrastructure move on. **Stale memory is worse than no memory** — an agent reads it and13confidently does the wrong thing.1415This is **not** a structural hygiene pass. Orphan files, duplicate entries, index bloat,16broken internal links — those checks compare memory files *to each other*. This skill17compares each memory **to the world it describes**: the code, the database, the issue18tracker, the filesystem. A memory can be perfectly well-formed, correctly indexed, recently19touched — and completely false.2021Automatic deletion is forbidden. Only a source-of-truth comparison can tell what is dead,22and losing an incident lesson is expensive. Hence semi-automatic: **correct freely, archive23only with approval, never delete.**2425## 1. Locate and inventory2627Find the memory set. Common locations, in order:2829- a path declared by the project's agent instructions (`AGENTS.md` / `CLAUDE.md` — e.g.30 "memory SSOT is `.claude/memory/`"). A declared path wins over every default.31- `.claude/memory/` in the repo (team-shared, committed)32- the host's per-project memory dir (e.g. `~/.claude/projects/<slug>/memory/`)3334Collect for each file: frontmatter (`name`/`description`/`type`), and last modification date35(`git log -1 --format=%cs -- <file>` for committed memory, `stat` otherwise). Note the index36file (`MEMORY.md`) if one exists — it is audited too, but it is an index, not a memory.3738**Personal files are out of scope** — anything the project marks personal (`user_*.md` or39equivalent) belongs to its owner. Leave untouched.4041**Reading many files**: dumping 50 memories into context at once blows the tool output cap42and wastes the budget. Concatenate to one scratch file with `########## <filename>` headers,43then page through it. Do not skim — a stale claim is usually one clause inside an otherwise44correct paragraph.4546## 2. Extract load-bearing claims4748Per file, pick the **1–3 claims that change what an agent would do**. Ignore prose, rationale49and background; a memory is only as stale as its actionable assertions.5051Load-bearing looks like: "X lives at path P" · "table T has N rows" · "issue #N is still open"52· "feature F does not exist yet" · "library L is not installed" · "the fix for this is still53pending" · "run command C to verify".5455## 3. Verify against the source of truth5657Work **cheapest-and-highest-yield first**. In practice the ranking below holds: issue status58is one API call and catches the largest share of stale claims, because memories are written59mid-work and the work then finishes without anyone going back to edit the memory.6061| Order | Claim type | How to verify |62| --- | --- | --- |63| 1 | **Issue/PR state** ("#N open", "waiting on #N", "decision pending") | forge CLI/API — `gh issue view N --json state` / `glab api projects/<enc>/issues/N`. Batch them in one loop |64| 2 | **Path/URL** (script locations, deploy paths, endpoints) | `ls`, `test -f`, `curl -s -o /dev/null -w '%{http_code}'` |65| 3 | **Code** (file/class/config exists, behaves a certain way) | `grep`/`Read` the current tree — *the code is the SoT, not the memory* |66| 4 | **Data/schema** (tables, columns, row counts) | read-only queries via the project's DB tool. Prefer catalog estimates first (`pg_class.reltuples`, `information_schema.columns`), exact `count(*)` only when the estimate is the disputed claim |67| 5 | **Runtime/host** (cron jobs, services, logs) | `ssh <host> 'ls …; crontab -l; tail <log>'` — a job's last log line dates the claim precisely |6869Parallelize independent verifications. If a source is unreachable, say so explicitly in the70report — never silently downgrade "couldn't check" to "checked".7172## 4. Classify7374- **fresh** — every claim holds. Don't touch it.75- **stale** — some claim is outdated (moved path, changed number, closed issue, implemented76 gap). → **Correct the body now, with the measured value and the date.** Corrections are77 within autonomous scope; they are additive truth, not deletion.78- **dead** — the core premise is gone (library removed, feature retired, wholly superseded).79 → Mark as an archive **candidate** only.8081## 5. Stale patterns worth hunting8283Beyond "the number changed", these recur and are easy to miss:8485- **Fixed-gap drift** — the memory documents a missing capability ("there is no startup86 reconcile", "no rate limiting yet") and it has since been built. This is the most dangerous87 class: the agent re-implements or re-reports work that already shipped. Check the linked88 issue *and* grep for the symbol.89- **Cross-memory contradiction** — two memories disagree (one says a script is the way to do90 X, another says that script was retired). At least one is stale by definition. Compare91 claims across files, not only file-by-file.92- **Scale drift** — "table T has ~8M rows" written months ago now off by 25%. Harmless as93 trivia, harmful when the memory derives advice from it (batch sizes, timeout budgets,94 "this query takes 19s").95- **Recipe rot** — the memory stores a command/query as a verified recipe, and the recipe no96 longer works at today's data volume or API version. **Re-run stored recipes**; a recipe you97 did not execute is unverified.98- **Progress-state drift** — a long-running job/backfill memory whose "current status" section99 is weeks behind, sometimes with two internally contradictory status sections stacked up.100 Date each section, keep the newest, mark superseded ones.101- **Identity mismatch** — `name`/`description` says one thing, the body says the opposite102 (e.g. a file named `*-via-toolX` whose body records that toolX was abandoned). Recall103 matches on description, so the file gets loaded for the wrong reason, or missed entirely.104105## 6. Report, then apply106107Report as a table before changing anything — file · class · one-line evidence · action:108109| File | Class | Evidence | Action |110| --- | --- | --- | --- |111| `reference_x.md` | stale | script moved `scripts/` → `data/` | path corrected |112| `project_y.md` | stale | claims "#302 reconcile missing"; `JobRunHistoryReconciler` exists, #302 closed | rewritten as done |113| `project_z.md` | dead candidate | feature from #N removed in #M | awaiting approval |114115Then:1161171. **Apply corrections** to stale bodies — measured value + date, keeping the original118 observation where it still carries a lesson ("was 8M as of <date>, 9.9M as of <today>").1192. **Archive dead candidates only after explicit user approval**: `git mv` into120 `<memory>/archive/` and add `archived: <date> <reason>` to the frontmatter. Never `rm`.1213. **Sync the index** — reflect corrections, drop archived entries from `MEMORY.md`.1224. **Commit through the project's normal workflow** (issue → branch/worktree → PR/MR). Memory123 is team-shared content; it does not get direct-to-main commits.124125## Judgment rules — stay conservative126127- **Unverifiable ⇒ fresh.** If the source of truth is unreachable, leave the memory alone and128 say the check was skipped. Unknown is not dead.129- **Incident lessons stay fresh even when the code moves.** A memory recording *why something130 broke* exists to prevent recurrence, not to snapshot a call site. Correct its stale path131 references; do not retire the lesson because the file was renamed.132- **Report the drift, don't invent the cause.** If a count inverted or a number moved133 inexplicably, record the measurement and flag it as unexplained. A plausible story written134 into memory becomes tomorrow's false fact.135- **Prefer merge over create.** Two memories on one topic → propose merging into the existing136 one.137- **Non-obvious facts discovered by the audit itself become new memories** — the audit is138 itself a source of ground truth.139140## Field notes141142- Fresh timestamps prove nothing. A file committed this week can carry a claim that was143 already false when written; a file untouched for months can be perfectly true. Verify144 claims, never sort by date and trim the tail.145- `count(*)` on a large table may exceed the DB tool's statement timeout — that failure *is*146 a finding when the memory claims the query is fast.147- Quote globs when shelling out from zsh (`grep --include="*.java"`), or the shell eats them148 and the check silently returns nothing — a false "fresh".149- An issue being closed does not by itself prove the described work shipped. For fixed-gap150 claims, confirm with a grep for the symbol as well.