NeuralMind v4.5.0 — numbers you can trust, measured on your project
| Type: Minor release | Theme: measurement |
v4.4.0 made the index trustworthy. v4.5.0 makes the numbers NeuralMind prints about a project mean something, and keeps them comparable from run to run:
- The index covers what git covers.
.gitignoreis honoured by default. - Measuring doesn’t train. Queries can be read-only, and every measurement command is.
- One eval, one baseline.
neuralmind eval .scores retrieval against gold answers you write for your repo, keeps a history, and every reduction ratio now divides by the measured size of your code instead of a fixed 50K guess.
It also stops a cost a new benchmark turned up: through v4.4.0, the Read, Bash and Grep hooks sold as tool-output compression added tokens instead of saving them (section 5).
1. .gitignore is honoured by default
On a real repository, gitignored projects/ copies of examples/ were indexed:
the same symbols ranked twice and pushed real hits out of the four-slot search
layer.
- Inside a git repo the file list comes from git itself
(
git ls-files --cached --others --exclude-standard), so nested.gitignorefiles,.git/info/excludeand your global excludes apply exactly as git applies them. - Tracked files that match an ignore rule are excluded too. On that repo
the copies had been force-added (
git add -f), so a listing of what git tracks would still have included them. The ignore rule states the intent. - Outside git, the top-level
.gitignoreis applied with the same pattern rules (!negation,/anchoring,**). .neuralmindignorenow uses gitignore semantics.docs/matches a directory at any depth,/docs/only at the root,!docs/keep.mdre-includes, andsrc/*.pyno longer matchessrc/deep/a.py.- A built-in graph drops nodes from files that are now excluded on the next build (the v4.4.0 orphan purge then removes their vectors).
build --dry-runshows the effect before you build:Excluded by .gitignore: ~300 nodes in 30 files (projects/ 30).- The first build after upgrading says what changed, once:
Honoring .gitignore (new in v4.5.0): 30 files (projects/ 30) kept out of the index. …
Opt out or widen it in .neuralmind.yaml:
respect_gitignore: false # index ignored files again (v4.4 behaviour)
include_ignored: # or pull specific ignored paths back in
- "generated/api/**"
Git can’t re-include a file under an excluded directory. To ignore a directory except one file, write
projects/*then!projects/keep.py, notprojects/— NeuralMind follows git here, exactly.
2. Read-only queries
Every query normally reinforces the synapse layer. That is the point in daily use, and exactly wrong for measurement: an eval run weekly would strengthen the very edges it measures.
NeuralMind.query(..., learn=False)/search(..., learn=False), CLIneuralmind query --no-learn, MCPlearn: falseonneuralmind_queryandneuralmind_search, andNEURALMIND_NO_LEARN=1for a whole process (CLI, MCP server and hooks).- A read-only query reads the learned layer — synapse recall still shapes
the result, so it gets exactly the context a normal query would — but
reinforces nothing, writes no query logs, and opens the synapse database
with SQLite’s
mode=ro, so even a bug can’t write. It never builds: on a project with no index it says so instead. benchmark,probeandevalare read-only with no flag.- The audit trail still records read-only queries (marked
"learn": false), andsavingsno longer counts them as usage.
Verified: 100 read-only queries leave synapses.db and its WAL byte-identical,
and return the same context as a learning query on the same database.
3. neuralmind eval . — your questions, your gold files
Write the questions your team actually asks, with the file that answers each,
in .neuralmind.eval.yaml at the repo root, and commit it:
- q: How are refunds issued when an order is cancelled?
gold: [app/refunds.py]
neuralmind eval . # run it (read-only), append to the history
neuralmind eval . --report # the history as a markdown table
neuralmind eval . --show-questions # include each question, gold file and rank
neuralmind eval . --suggest --write # draft 10 questions to edit
Example output (illustrative numbers):
NeuralMind eval — myrepo (10 questions, read-only)
hit@1 40% · hit@5 60% · MRR 0.50
avg context 1,180 tokens · 6.2× vs gold files · 1,101.3× vs all indexed code (1,299,512 tokens, measured)
- hit@1, hit@5, MRR rank the files the answer drew on by their best hit
score — the same relevance sidecar
query --relevancereturns. - Two reductions: vs the gold files (what a perfect retriever would load) and vs all the code the index covers (the measured baseline below).
- History: each run appends date, commit, NeuralMind version, node count
and the metrics to
.neuralmind/eval_history.jsonl.--reportprints metrics and the question count only, so questions that name internal code stay out of pasted issues unless you pass--show-questions. - The questions file is tracked (a shared team baseline); the history lives in
the untracked
.neuralmind/because it is machine-specific. --suggestdrafts questions from module docstrings and README headings, with the defining file as gold — a starting point for a human to edit.- The faithfulness and onboarding self-tests that
neuralmind evalused to run areneuralmind eval --suite faithfulness|onboarding;--onboarding,--selfcheckand a bareneuralmind evalstill run them.
4. One baseline everywhere
Before v4.5.0, on the same repo, benchmark divided by a fixed 50,000-token
guess (on a ~1.3M-token repo), build --dry-run used a lines × 25 estimate, and
probe resampled whenever the index changed: three commands, three
baselines, no two runs comparable.
buildmeasures the token count of the code the graph covers and caches it in.neuralmind/baseline.json:- Code only. Markdown, reStructuredText and plain text are left out
whenever the index holds code — on
psf/requestsitsHISTORY.mdchangelog alone was 15,088 tokens, 14% of an all-files count — so prose can’t pad a ratio, andbuild --dry-run(which always counted code only) now agrees withbuild. An index with no code (a book, a docs corpus, a set of SQL, Protobuf or OpenAPI schemas) is measured over its documents. - In the context’s own units. It is counted at the ~4 chars/token every
context layer is counted in, so the ratio is a ratio of characters. With
tiktoken installed it used to switch tokenizers and read
psf/requests’ code 8.5% lower than the context’s units — one index, two ratios, depending on an optional package. - Only inside the project. A graph path that resolves outside it
(
../, an absolute path, a symlink out) is never opened, and images or PDFs a graphify graph names are never read as text.
- Code only. Markdown, reStructuredText and plain text are left out
whenever the index holds code — on
benchmark,savings,costandbuild --dry-runall divide by it, and say so:Baseline: measured: 94,069 tokens in 36 indexed code filesonpsf/requestsv2.32.3. So do the per-query ratiosquery,query --explain,wakeupand the MCP tools print. An index built before v4.5.0 has nothing cached;benchmarkmeasures it on the spot (writing nothing) and the rest use the fixed estimate until the nextbuild.benchmarkprints the old ratio beside the new one —Legacy reduction: 41.8x (vs the fixed 50K-token estimate used before v4.5.0)— and--jsoncarries both:full_codebase_tokens(the measured code) andlegacy_avg_reduction_ratioagainstestimated_full_codebase_tokens(always 50,000).--naive-50kon each of them keeps the old fixed estimate, labelled, for comparison with older numbers.benchmark --contributewrites a v2 community entry:avg_reduction_ratiostays on the fixed estimate every row of the table compares on, andmeasured_avg_reduction_ratio+full_codebase_tokensadd the ratio against your code. Itsverification_commandnow carries--naive-50k, so a reviewer re-running it gets the submitted number; and it fillsnodesand whole-numberavg_query_tokens, without which no entry it printed had passed the schema.benchmarkuses the questions in.neuralmind.eval.yamlwhen there is one, instead of five generic ones.
Expect different ratios. On a large repo the measured baseline is far
bigger than 50K, so ratios go up; on a small one they go down. Neither is a
change in retrieval — it’s the numerator becoming true. On psf/requests
v2.32.3 the same five questions read 41.8× against the fixed estimate and
78.6× against the 94,069 tokens of code the index covers.
Stable probe sampling
probe now ranks symbols by a hash of (seed, node id) and keeps the lowest
--sample-size, instead of a seeded random draw over the current index. An
unchanged symbol stays in the sample across rebuilds (two runs either side of
a 5% index change share ≥ 90% of their sample), and --baseline compares only
the symbols both runs sampled, reporting how many were added or dropped.
5. The Read, Bash and Grep hooks stop adding tokens
The Read, Bash and Grep PostToolUse hooks were described as compressing tool
output “before the agent sees it”. A new benchmark that follows Claude Code’s
documented hook protocol shows they never did: Claude Code adds a hook’s
additionalContext next to the tool result rather than replacing it, so Claude
got the full output plus NeuralMind’s compressed copy.
| Tool call | Calls | Tokens, no hook | Tokens, v4.3.4 hooks | Change |
|---|---|---|---|---|
| Read (whole file) | 136 | 597,002 | 597,002 | +0.0% |
| Bash | 16 | 32,581 | 38,296 | +17.5% |
| Grep, content mode | 48 | 55,800 | 68,113 | +22.1% |
| Grep, files_with_matches (default) | 48 | 2,062 | 2,062 | +0.0% |
Measured on v4.3.4; v4.3.5 and v4.4.0 shipped the same hooks.
- Bash: every successful command got a second copy. It was the whole output
again when nothing was compressed (
pip list, a shortpytest -q: +100%), and errors plus the tail when it was. - Read: the hook never fired. Claude Code’s Read payload nests the text
under
file.content, which the hook didn’t read. - Failed commands fire
PostToolUseFailure, notPostToolUse, so the Bash hook never saw a failing test run, lint error or crash. Those are the outputs its “errors + summary” design was built for.
Method, per-command results and raw data:
docs/benchmarks/compression.md ·
bench/compression/results-v4.3.4.json.
What changed
neuralmind _hook compress-read,compress-bash,cap-searchandoffloadreturn nothing. Claude sees exactly the tool result.- The Bash hook still writes the latest successful command’s output,
credentials redacted, to
.neuralmind/last_output.json, soneuralmind lastworks as before. A failing command firesPostToolUseFailure, so its output never reached this hook, then or now. - Every other hook is unchanged: session memory (SessionStart), prompt-time recall (UserPromptSubmit), the stale-decision guard (PreToolUse), reuse feedback (PostToolUse on Edit/Write), and the session digest (Stop, SessionEnd).
Not changed
- The compressor functions (
compress_bash,compress_read,cap_search_results,offload_if_large) stay in the Python API. The benchmark measures them too: they would cut 66–87% of tokens if they replaced a result, but keep 0% of a file’s source lines and 0% of a diff’s changed lines. That is why nothing replaces tool output until a design keeps what the agent needs, measured by the same benchmark. NEURALMIND_BASH_TAIL,NEURALMIND_BASH_MAX_CHARSandNEURALMIND_BASH_SMALLnow only tune those Python functions.NEURALMIND_BYPASS=1still switches off every NeuralMind hook action.
New in the repo
evals/compression/: the benchmark. It drives the real hook with payloads shaped like Claude Code’s (Agent SDKsdk-tools.d.ts) and applies each response per the documented protocol. The Bash corpus is 16 real command outputs with pre-registered must-keep lines.tests/test_compression_benchmark.py: recomputes the Bash results on every CI run and fails if the hooks stop doing whatbench/compression/results.jsonrecords, or if the benchmark page stops quoting it.- Docs, README, site and
llms.txtno longer describe tool-output compression as a feature.
What the agent actually sees post-install
| Agent | Before | After |
|---|---|---|
| Claude Code (MCP + hooks) | Indexed gitignored copies; every query and hook taught the synapse layer, including eval scripts; Bash and content-mode Grep results were followed by a system reminder holding NeuralMind’s copy of the same output | Index covers what git covers; learn: false and NEURALMIND_NO_LEARN=1 give read-only queries; hooks skip learning under the env switch; the Read/Bash/Grep hooks add nothing to a tool result |
| Cursor / Cline / Continue (MCP) | Same indexing; no way to query without training | Same indexing fix; learn: false on neuralmind_query / neuralmind_search |
| Generic MCP client | neuralmind_query always wrote |
Read-only on request; the tool schema documents learn |
| CI | benchmark numbers against a fixed 50K; evals trained on themselves |
neuralmind eval . read-only with history; NEURALMIND_NO_LEARN=1 for the whole job |
Upgrade notes
- Results change for repos with gitignored code. The first build prints
what was excluded. To keep v4.4 behaviour:
respect_gitignore: false. .neuralmindignoresemantics tightened to gitignore’s: a pattern with a/in the middle is anchored to the root, and*no longer crosses directories. Most files mean the same thing; check anysrc/*.py-style line.- Reduction ratios change as described above.
--naive-50kreproduces the old numbers. neuralmind eval <path>now runs the project eval; the old suites are under--suite.- The hook change needs no reinstall. The registered hooks are the same;
they just stop returning context.
neuralmind lastworks as before.
Related
- Use case: Measure retrieval on your own repo
- Benchmark: Tool-output compression
- CLI reference:
eval,query,benchmark,probe