Each recipe assumes you already installed the tool (see the getting-started
tutorial) and gets you to one concrete outcome. For exhaustive flags and contracts, see
Reference; for the why, see Explanation.
A quick safety model that applies to every recipe below:
Report by default; mutate only on --apply
Mutating engines do nothing destructive until you pass --apply. Bulk vault writes additionally
refuse to run while your notes app is open (a linter running in the app can mangle frontmatter
mid-write). Close the app first, or pass --force to override deliberately. For mutating engines,
git is the audit trail - commit before, review the diff after.
Reconcile vault tags
Goal: find morphological tag variants (case / plural / hyphen / underscore / slash forms of one
root) and converge them on a single canonical spelling.
Point the engine at your vault and run it read-only to see the proposed merge groups:
export VAULT_ROOT="/path/to/your/notes"neurokeeper tag-reconcile # proposal only - writes nothingneurokeeper tag-reconcile --json # same proposal, machine-readable
Review the groups. The engine detects and proposes; it does not decide for you. Genuine synonyms
(two different words meaning the same thing) are intentionally out of scope - supply those yourself.
Apply the merge. Prefer your notes app’s own tag-rename tool for the write - it uses the app’s
parser, updates every usage (including nested tags), and avoids the linter race. The engine’s
--apply is a guarded fallback for bulk runs:
# close your notes app first, then:neurokeeper tag-reconcile --apply
Review git diff, then commit. The commit is the audit record.
Run the memory-audit
Goal: get a health report on a file-based memory store (orphans, broken links, staleness, a
multi-metric score) and an evidence-backed consolidation proposal.
Tell the engine where the memory store lives, then run the deterministic analyzer (read-only):
export CLAUDE_MEMORY_DIR="~/.claude/memory"neurokeeper memory-consolidate # human-readable reportneurokeeper memory-consolidate --json # machine-readableneurokeeper memory-consolidate --terse # one-line health summary (good for a session hook)neurokeeper memory-consolidate --lint # advisory: is the always-loaded index within its cap + tight?
--lint is the check to wire into a session-start hook or pre-commit for the index file itself:
it flags an index that has grown past the harness read cap (so the tail is silently dropped) or
drifted from the one-line-per-entry telegraphic style. It never blocks (exit 0) - a nudge, not a gate.
Read the proposal. Every number is computed from the real filesystem, so it is reproducible and
cannot be fabricated - this is the whole point of pushing the counting into an engine.
To act on it with judgment + confirmation, use the memory-audit capability through an adapter
(for example the Claude Code skill). The adapter runs this same engine, then applies the
consolidation prompt, gates each change, and writes an append-only audit entry.
Why a hash-chained audit for memory, but git for the vault?
Different substrates get different audit trails. Markdown notes → git (the diff is the record).
A memory store consolidated by an agent → an append-only, hash-chained log. See
the explanation for the reasoning.
Lint or reconcile frontmatter
Goal: validate your notes’ frontmatter against your schema-as-code, then optionally reconcile
off-vocabulary values.
Point the engines at your vault and at a schema file. The frontmatter engines need a schema —
copy the shipped example and edit it for your own vocabularies:
export VAULT_ROOT="/path/to/your/notes"cp config.example/frontmatter-schema.example.yaml my-frontmatter-schema.yaml # then editexport FRONTMATTER_SCHEMA="$PWD/my-frontmatter-schema.yaml"
Review git diff, then commit. The commit is the audit record.
If you skip the schema, the engine tells you
If FRONTMATTER_SCHEMA is unset and no schema is found, the frontmatter engines print a one-line
message explaining how to set it (and fall back to the bundled example where one is locatable),
instead of failing with a traceback.
Audit vault references
Goal: find broken links, orphans, dead-ends, broken .canvas/.base references, and orphan media.
Read-only - nothing is changed.
Point it at your vault and run it:
export VAULT_ROOT="/path/to/your/notes"neurokeeper ref-audit # human reportneurokeeper ref-audit --json # machine-readable
Read the report. Unresolved wikilinks are informational - in Obsidian a [[link]] to a
not-yet-created note is a legitimate forward-reference. Only broken .canvas/.base refs (a board or
base pointing at a deleted file) fail --check; orphans, dead-ends, and orphan media are surfaced for
review but not gated.
Gate it in CI or a pre-commit hook:
neurokeeper ref-audit --check # exit 1 only on broken canvas/base refsneurokeeper ref-audit --check --strict # also fail on unresolved links (for strict vaults)neurokeeper ref-audit --check --since origin/main # only fail on defects in notes changed vs main
--since <git-ref> scopes the reported findings (and the --check gate) to notes changed since the
ref, so a PR is judged on the debt it introduces, not the vault’s whole backlog. The scan stays
graph-global; a bad ref or non-git tree exits 2 rather than silently scanning the wrong scope.
Adopt on an already-dirty vault without a wall of failures: baseline the current debt once, then
gate on net-new only.
neurokeeper ref-audit --write-baseline .neurokeeper-baseline # accept today's findings, onceneurokeeper ref-audit --check --baseline .neurokeeper-baseline # CI fails only on NEW debt
Commit the baseline file. The run tells you how many baselined findings you have since fixed, so
you can re-run --write-baseline to shrink it, rather than letting it become permanent debt.
Surface findings in GitHub’s Security tab (code-scanning) via SARIF:
--sarif renders through the Findings IR, so the same findings can later drive JUnit or a Bases view
without touching the engines. It composes with --since / --baseline (the SARIF reflects the
scoped set).
Run one aggregate health check (doctor)
Goal: one command + one honest exit code over all the read-only checks - the thing to wire into CI.
Run it (read-only):
export VAULT_ROOT="/path/to/your/notes"neurokeeper doctor # consolidated reportneurokeeper doctor --json # machine-readable
Read the tri-state. Each engine is ok, fail, or skipped. skipped means its config is not set
(e.g. no FRONTMATTER_SCHEMA, no CLAUDE_MEMORY_DIR) - it is not counted as a pass. The roll-up exit
asserts “an engine errored or a real gate failed,” not “the vault is healthy”: advisory checks
(taxonomy-inventory, frontmatter-lint) contribute numbers but cannot fail it.
Gate CI on it:
neurokeeper doctor --check # exit 1 iff a gating engine failed or any engine erroredneurokeeper doctor --check --strict # also fail on unresolved links (forwarded to ref-audit)
Set FRONTMATTER_SCHEMA / CLAUDE_MEMORY_DIR to bring those engines into the gate; leave them unset to skip.
Gate a vault repo in CI (pre-commit + GitHub Action)
Goal: fail a commit / PR when the vault has real reference defects - composing neurokeeper with the
existing markdown ecosystem instead of duplicating it.
pre-commit - in your vault repo’s .pre-commit-config.yaml:
Understand what fails it. The exit code follows the doctor contract: broken .canvas/.base refs or an
engine error fail it; advisory findings and skipped (unconfigured) engines do not. Set
frontmatter-schema / memory-dir inputs to widen the gate. Full guide: docs/ci-adapters.md.
Try it on the bundled example vault
examples/vault/ is a tiny synthetic vault; VAULT_ROOT=examples/vault neurokeeper doctor shows a
clean run (and is the fixture the project’s own CI smoke-tests).
Offload cheap work to a self-hosted model (two lanes)
Goal: keep hard agentic work on your normal Claude lane, but route mechanical, high-volume turns
(commit messages, summaries, extraction, classification, formatting) to a self-hosted open model so they
cost ~nothing - without changing your default claude.
Stand up an endpoint that speaks the Anthropic /v1/messages API (vLLM-native, or a LiteLLM /
claude-code-router gateway in front of an OpenAI-only model). Serving recipes are in the in-repo
docs/two-lane-model-handoff.md.
Configure the cheap lane (copy the example; never commit a real internal endpoint):
Run cheap work through the wrapper - it sets ANTHROPIC_BASE_URL to your endpoint for that
invocation only:
claude-cheap -p "write a conventional-commit message for the staged diff"
Two warnings that matter
Billing: pointing at a paid Anthropic-compatible gateway with a credential moves you off your
subscription onto per-token billing - the point here is that traffic goes to your box (~zero marginal
cost). Data egress: everything in this lane goes to your endpoint - keep it on a host you control
for sensitive content; never send regulated data or private model weights to a cloud you don’t control.
Add a new engine
Goal: add a new portable capability that the registry will pick up automatically.
Write the engine first. A single deterministic script that computes facts/candidates (or applies
a deterministic transform). Make it speak the contract: a --json flag and meaningful exit codes,
and report-by-default (no writes unless --apply).
Classify it on two axes - compute (deterministic / llm / hybrid) and effect
(read-only / mutating). That decides which layers you need; see
the capability typology.
If it mutates: wire in the forbidden-zones check, an operator confirmation (per-row diff for
multi-item changes), the audit write, a post-write verify, and any substrate preflight guard.
Add the metadata header at the top of the script (the @capability / @compute / @effect /
… block - see the Reference: metadata-header spec). This is what makes the engine
discoverable.
Regenerate the registry so the catalog reflects the new engine:
Document it in the docs site. Any user-facing capability must land in the wiki, not just the
README - add a Reference catalog entry and a How-to recipe. Docs that lag the tool
are worse than no docs.
Add a test/fixture. An engine without a test is experimental until it has one.
Add the adapter you use now (e.g. a Claude Code skill that defers all logic to the engine). Add
an MCP binding only if a graduation trigger fires - see the MCP ladder.
Make an engine cross-platform
Goal: ensure a new engine runs on Windows / MSYS as well as POSIX.
Force UTF-8 on subprocess text: pass encoding="utf-8", errors="replace" to subprocess.run —
the OS default codepage will choke on non-ASCII bytes in command output.
Never pass leading-slash paths to git/MSYS tools: MSYS rewrites /foo into a Windows path. Use
bare/relative paths.
File I/O: always open(..., encoding="utf-8"); write with newline="" to preserve the file’s
existing line endings instead of reflowing them.