Find Missing Note Links - alexherrero/agentm GitHub Wiki

How to find missing links among your personal notes

Note

Goal: Run the read-only personal-notes link-discovery audit, read the ranked "these two notes look related but aren't linked" report it writes under diagnostics/lint/, and add the suggested [[wikilinks]] by hand in Obsidian β€” the audit never edits a personal note. Prereqs: agentm v4.10.0+ (ships V4 #43), python3 on PATH, and a reachable Obsidian vault (MEMORY_ROOT set, or pass --vault PATH). The embedding signal additionally needs the sentence-transformers package; without it the audit runs TF-IDF-only.

This audit is the complement to Audit the vault β€” that one lints your agent-shaped vault-root entries; this one looks at your personal notes (the ones outside the vault root) and surfaces pairs that read as related but have no [[wikilink]] between them. It is strictly personal↔personal: it never suggests linking a personal note to a vault-root entry.

Steps

  1. Preview the suggestions (optional). Run the audit and print the related-but-unlinked pairs to your terminal β€” text for skimming, json for piping:

    python3 harness/skills/memory/scripts/notes_link_discovery.py --format text
    python3 harness/skills/memory/scripts/notes_link_discovery.py --format json

    Tuning flags: --top N caps the shortlist (default 40; 0 = all), --min-score X sets the TF-IDF cosine floor (default 0.18), and --vault PATH points at a specific Obsidian root when MEMORY_ROOT isn't set. The corpus is every .md under the Obsidian root except the vault root folder itself, .obsidian/, .trash/, .git/, and every area the filing contract walls with recall_exempt_areas β€” personal/Home/Important Docs on the shipped contract. The walk refuses a walled folder before reading anything in it, so those notes are never scored, reported, embedded, backed up, or written into.

  2. Add the semantic signal (optional but recommended). Pass --embeddings to run a second relatedness pass that catches related notes which don't share surface vocabulary β€” including the same note in two languages:

    python3 harness/skills/memory/scripts/notes_link_discovery.py --embeddings --format text

    This embeds each note with the local BGE model and caches the vectors at ~/.local/state/agentm/notes-embeddings.json β€” the engine state directory ($AGENTM_STATE_DIR overrides it), since a vector cache is machine state, not vault content (content-hash keyed, so re-runs only re-embed changed notes β€” the first run is slow, later runs are fast). --embed-min-score X sets the embedding cosine floor (default 0.70). If sentence-transformers isn't installed the audit prints a one-line notice and falls back to TF-IDF-only β€” never an error.

    To clear keys out of that cache without running a model, use --prune-cache (add --dry-run to look first). It drops every key the contract walls and every key naming a note the corpus no longer holds, then says how many of each went. A cache written before the wall landed, or under an older corpus root, carries both kinds.

  3. Write the suggestion report. Add --report to write the operator-review markdown instead of printing:

    python3 harness/skills/memory/scripts/notes_link_discovery.py --report --embeddings

    It writes <vault>/diagnostics/lint/notes-links-<YYYY-MM-DD>.md and prints a one-line summary (N TF-IDF + M embedding-only suggestion(s) across K notes -> <path>). The report is the only thing the audit writes β€” it refuses any --out path that lands outside the agent-controlled vault or onto a personal note, so it can never overwrite a note.

  4. Read the report. Open <vault>/diagnostics/lint/notes-links-<YYYY-MM-DD>.md. It leads with a summary, then a Shared-vocabulary links (TF-IDF) section β€” each entry carries the two notes (folder/title), the top shared distinctive terms (the why), the score, and a ready-to-paste [[wikilink]] for both directions; pairs the embedding signal also agrees on are flagged βœ“ also semantically related. When --embeddings ran, a second Semantically related (embedding signal β€” TF-IDF missed these) section lists the pairs found only by meaning. Names containing [/]/|/# (e.g. bracketed-date meeting notes) can't be valid [[wikilinks]], so those show a "link via Obsidian's [[ picker" hint instead of a broken link.

  5. Add the links by hand. Open the suggested notes in Obsidian and paste the [[wikilink]] where it reads naturally. The audit applies nothing by default β€” every suggestion is advisory and operator-gated (A3 β€” these are your notes). Re-run step 1 to confirm an applied pair drops off the list.

  6. Or let it apply for you (opt-in). If you'd rather not paste by hand, pass --apply to write the safe suggestions into a marked ## Related section at the end of each source note:

    python3 harness/skills/memory/scripts/notes_link_discovery.py --apply --embeddings

    It backs the whole corpus up first (~/.local/state/agentm/notes-backup-<date>.tar.gz, in the engine state directory; it refuses to apply if the backup fails) and prints the exact tar xzf … command to revert. It is idempotent β€” re-running merges into the one marked section, never duplicates it β€” and skips wikilink-unsafe names (bracketed meeting notes) plus links you already have. Review the result in Obsidian; remove any you don't want. This is the only mode that writes to a personal note, and only because you asked it to.

For what each relatedness signal means and how the thresholds are tuned, see Note relatedness signals.

Related

⚠️ **GitHub.com Fallback** ⚠️