TLDR: Two mechanical memory fixes are safe to auto-apply. One semantic fix is not — because "looks like a duplicate" and "is a duplicate" are different things. Know which bucket you're in before you write.

What We Were Building

Apollo (my AI coding agent) runs on an Obsidian vault as its long-term memory — hundreds of markdown files wired together with [[wiki links]]. After shipping Phases 1–3 of a memory architecture overhaul (hot.md recency cache, /mem-lint health-check, a rerank eval I measured and ultimately rejected as a no-go), it was time for Phase 4: the cleanup pass.

/mem-lint had been running. It had opinions.

Two Problems, Not One

The report flagged two totally distinct issues, and I nearly treated them as the same thing.

Problem 1: duplicate pointers in MEMORY.md (my routing index file). Same target file listed twice. Classic copy-paste drift from a dozen /apollo-save sessions. Fix: remove one. Fully mechanical. Safe to apply without reading either file.

Problem 2: malformed [[links]] — links that pointed to filenames that no longer existed, or that never exactly matched the vault's actual paths. Fix: repoint to the real target. Also mechanical. Also safe, as long as you don't confuse "repoint" with "merge." You're fixing the pointer. You're not touching the content.

Both of those went into Phase 4A and shipped clean.

The Third Bucket — This Is Where I Slowed Down

/mem-lint also runs a local-Ollama contradiction pass (Ollama being the open-source LLM runtime I use for offline analysis). It reads pairs of files and asks: are these saying contradictory or redundant things?

It came back with a list of "duplicate" candidates.

I opened the first two.

They weren't duplicates. They were complementary — one covered the what, one covered the why. Different angles on the same system, both worth keeping. Merging them would have dropped recall, not improved it.

This is exactly the failure mode I'd already documented in another rule: group and dedup, never cap — because capping silently drops the item that might matter most.

The Ollama pass over-flags. It sees overlap and calls it redundancy. Those aren't the same. So the Phase 4B contradiction findings became a propose-and-verify list, not a batch delete.

Phase 4C — the riskiest proposed merges — got gated entirely. Parked for a day I can read each one properly.

What I'd Do Differently

I went in thinking "Phase 4 = cleanup" — one job. I came out with a three-bucket policy I now apply before touching anything in the vault:

  • Duplicate pointer → dedupe immediately, no reading required
  • Malformed link → repoint immediately, no reading required
  • "Duplicate" file flagged by semantic pass → open both, read both, decide manually — the tool proposes, you confirm

The rule /mem-lint ships with is report and propose-patch only — never writes. That constraint exists for a reason. The mechanical stuff earns automation. The semantic stuff doesn't. Not yet.

Why This Matters to Me

Apollo's memory is only as trustworthy as the links inside it. A broken [[link]] is a dead reference the agent can't follow. But a merged file that used to be two is something worse: a silent information loss that looks fine on the surface.

Phase 3 taught me to measure before adopting. Phase 4 taught me to classify before deleting. Same instinct, different layer.

P.S. If you're running any kind of memory vault for an AI agent — Obsidian or otherwise — build the linter before the cleanup scripts. You can't trust what you haven't measured.