Find Missing Note Links - alexherrero/agentm GitHub Wiki
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.
-
Preview the suggestions (optional). Run the audit and print the related-but-unlinked pairs to your terminal β
textfor skimming,jsonfor 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 Ncaps the shortlist (default 40;0= all),--min-score Xsets the TF-IDF cosine floor (default0.18), and--vault PATHpoints at a specific Obsidian root whenMEMORY_ROOTisn't set. The corpus is every.mdunder the Obsidian root except the vault root folder itself,.obsidian/,.trash/,.git/, and every area the filing contract walls withrecall_exempt_areasβpersonal/Home/Important Docson 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. -
Add the semantic signal (optional but recommended). Pass
--embeddingsto 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_DIRoverrides 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 Xsets the embedding cosine floor (default0.70). Ifsentence-transformersisn'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-runto 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. -
Write the suggestion report. Add
--reportto 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>.mdand 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--outpath that lands outside the agent-controlled vault or onto a personal note, so it can never overwrite a note. -
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--embeddingsran, 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. -
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. -
Or let it apply for you (opt-in). If you'd rather not paste by hand, pass
--applyto write the safe suggestions into a marked## Relatedsection 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 exacttar 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.
- Note relatedness signals reference β the two signals (TF-IDF + embedding) and their thresholds.
- Audit the vault β the complementary recipe that lints your agent-shaped vault-root entries.