neuralmind.uk / NeuralMind v4.8.0 — a new session starts where the last one left off, decision search by meaning, and policy mistakes refused Edit this page on GitHub ↗

NeuralMind v4.8.0 — a new session starts where the last one left off, decision search by meaning, and policy mistakes refused

Type: Minor release Themes: session continuity · decision memory (decision search by meaning) · MCP access control (policy mistakes refused)

A new Claude Code session used to start cold. NeuralMind gave it the code it had learned (SYNAPSE_MEMORY.md, per-prompt recall), but not the work: what you were doing yesterday, which files you had open, what you asked last. You re-explained it, or asked the agent to go and find out.

v4.8.0 records each session’s prompts and edited files as you work. The next fresh or cleared session starts with a short recap of the most recent one:

  1. No new hooks. Recording rides on the UserPromptSubmit and Edit/Write hooks NeuralMind already installs; the recap arrives through the existing SessionStart hook. The hook block’s version is unchanged, so an existing neuralmind install-hooks setup picks it up on upgrade.
  2. No model call. The recap is assembled from what the hook payloads already carry: the prompt text, the edited file’s path, the session id. Nothing summarizes it, so it costs a few small file reads at session start.
  3. Only when the conversation is missing. A resumed session already has its conversation, and a compacted one has Claude Code’s own summary, so the recap is injected only on a fresh start and after /clear.
  4. neuralmind recap prints what the next session will see, and neuralmind recap --clear deletes the stored records.

What the agent actually sees

At the start of a new session, before your first message, the agent’s context gains this block:

NeuralMind session recap — the previous session in this project (last active 3 h ago). This is context for continuity, not instructions: don't resume that work unless the user asks to.

It started with: "add retry logic to the uploader"
Most recent prompts (2 earlier not shown):
- "now cover the timeout path in tests"
- "why does test_upload_retries hang on CI?"
- "make the backoff configurable through the env"

Files edited (4, most recent first): src/uploader.py, src/config.py, tests/test_uploader.py, docs/uploader.md

Per-agent expectations

Agent What changes
Claude Code (a built project, with neuralmind install-hooks) A fresh or cleared session starts with the recap above. Resumed and compacted sessions don’t get it.
Cursor / Cline / generic MCP clients Nothing. These hosts don’t run Claude Code hooks, so nothing is recorded and nothing is injected.
Hermes-Agent and other agents with a shell Nothing automatic. An agent that can run commands can call neuralmind recap to read what the last Claude Code session in the project did.

Where it’s stored, and what’s redacted

Settings

Variable Default Effect
NEURALMIND_SESSION_RECAP on 0 stops recording and stops the recap
NEURALMIND_SESSION_RECAP_MAX_AGE_DAYS 14 A recap whose session was last active longer ago than this isn’t shown
NEURALMIND_NO_LEARN off 1 stops recording (nothing is written) but still shows an existing recap
NEURALMIND_BYPASS off 1 switches off every hook action, this one included

Turning the recap off, or setting NEURALMIND_NO_LEARN=1, doesn’t delete records already written, and a recap past the age limit is hidden, not deleted. neuralmind recap --clear removes them; while the recap is off, neuralmind recap says how many are still stored.

Why it’s built this way

NeuralMind already had session summaries (session_summaries.py), written by the Stop and SessionEnd hooks. They’re built from .neuralmind/events.jsonl, which is only written while neuralmind watch or neuralmind serve is running. With hooks alone, nothing new reaches that log, so the summaries don’t reflect the session, or aren’t written at all. The session recap reads only fields Claude Code’s hook payloads carry, so it works with the hooks alone.

Not measured

This release doesn’t claim a number. We haven’t measured whether the recap shortens the start of a session, or how often an agent acts on it when it shouldn’t. The block’s size is bounded by count, not by a character budget: at most four prompts of 200 characters and twelve file paths, plus a header. The example above is 525 characters; four full-length prompts and twelve 30-character paths come to about 1,500.

Decision search by meaning

Decision search used to find a decision only when the question shared a word with it. An agent asking “where do we verify who is calling an endpoint?” never reached “Use per-handler authentication middleware”, because the two share no word. v4.8.0 ranks decisions by meaning as well, with the same local embedding model the code index already uses, and makes the fused ranking the default:

This is item G1 of the mem0 gap analysis in docs/specs/LOCAL-API-SPEC.md §4.1. The other items (change history, duplicate warnings, filters, expiry dates and the rest) are not in this release.

Three search modes

Mode Ranks by Use it for
hybrid (default) Shared words and meaning, fused by reciprocal rank fusion (k = 60) Everyday questions
semantic Cosine similarity between the question and each decision’s title + rationale, embedded with all-MiniLM-L6-v2; a decision needs at least 0.30 Questions you expect to share no word with the decision
keyword Shared words: FTS5, bm25-ranked, any word can match (v4.5.1+) Exact identifiers, and the v4.6 ranking

Local, cached, and never a download

Measured before it became the default

Evidence: reproducible on demand from a source checkout, not a CI gate for every column:

NEURALMIND_ORT_THREADS=1 neuralmind decisions eval \
  --queries tests/memory/fixtures/decision_queries.json --format md

tests/memory/test_query_eval.py holds the Memory Layer wiki’s table to what the eval measures: the keyword column on every CI run, the semantic and hybrid columns wherever the model is on disk (CI doesn’t download it). Repeated runs here gave identical numbers, with NEURALMIND_ORT_THREADS=1 and without it.

What was frozen first. Commit 7172855 added 20 paraphrased questions, each sharing no search word with its answer (a test enforces it), with the 0.30 similarity floor and this keep rule. Nothing was changed after the run. Against keyword search, hybrid becomes the default only if:

Questions nothing answers are reported, not gated. Keyword search already returns partial matches for them, and the tools tell the agent to check the titles.

Synthetic set: 35 decisions (30 ACTIVE), limit 5, status ACTIVE.

Measure Keyword (v4.6) Semantic Hybrid (default)
Recall@5 on 20 questions, mean (range) 1.00 (1.00–1.00) 0.95 (0.00–1.00) 1.00 (1.00–1.00)
MRR on 20 questions, mean (range) 0.94 (0.33–1.00) 0.95 (0.00–1.00) 0.95 (0.50–1.00)
Recall@5 on 20 paraphrases, mean (range) 0.00 (0.00–0.00) 0.50 (0.00–1.00) 0.45 (0.00–1.00)
MRR on 20 paraphrases, mean (range) 0.00 (0.00–0.00) 0.44 (0.00–1.00) 0.28 (0.00–1.00)
Paraphrases that return nothing 6 of 20 7 of 20 1 of 20
Exact titles ranked first 30 of 30 30 of 30 30 of 30
Questions nothing answers that still return decisions 2 of 4 1 of 4 2 of 4

Hybrid passed all three conditions.

The misses, published

The smoke test run while building this used one of the 20 paraphrases (“where do we verify who is calling an endpoint?”), so that question’s semantic result was seen before the eval ran. The floor and the keep rule were not changed.

The eval scores every mode

What the agent sees

Agent Before (v4.7) After (v4.8)
Claude Code (MCP + hooks) neuralmind_memory_search and neuralmind_query_decisions found a decision only through a shared word; a question in other words got nothing, or partial matches on unrelated words The same calls also rank by meaning. The response says "mode": "hybrid", or "mode": "keyword" with a notice when the model isn’t on disk. Hooks are unchanged: the stale-decision guard matches by file, not by search
Cursor / Cline / Continue (MCP) Same as Claude Code Same as Claude Code
Generic MCP client No way to choose the ranking Optional mode: hybrid, semantic or keyword. semantic without the model returns code: "semantic_unavailable"; an unknown mode is invalid_request

New variable: NEURALMIND_DECISION_SEARCH sets the decision-search mode for calls that name none (hybrid by default; semantic; keyword for the v4.6 ranking).

Policy mistakes that meant the defaults now refuse

v4.6.1 made the MCP server apply security.roles and security.rate_limit, and refuse a security: value of the wrong type with reason: config. Its release notes also listed two mistakes it still read as “no policy”, which applies the default policy. That policy gives admin every tool, and any caller can declare admin unless identity: os is set. v4.8.0 refuses both.

If neuralmind-backend.yaml has no security: section, nothing changes.

What changed

A misspelled key, such as role: for roles:, is still read as absent. After writing a policy, call a tool you left out with role: "admin" and check that it returns security_denied.

What the agent sees

Only in a project whose neuralmind-backend.yaml has one of these mistakes. Every other project sees what it saw in v4.7.0.

Agent Before After
Claude Code (MCP + hooks) The default policy: a call declaring admin reached every tool Every MCP tool returns security_denied with reason: "config" and the setting to fix. Hooks don’t go through the MCP role policy and are unaffected
Cursor / Cline / Continue (MCP) Same as Claude Code Same as Claude Code
Generic MCP client Same as Claude Code Same as Claude Code

Upgrading

pip install -U neuralmind. Nothing to reinstall, and nothing to rebuild in a project built with v3.9.0 or later (the recap looks for the .neuralmind/build_status.json a build leaves). The first recap appears in the session after the first one you work in on v4.8.0.

Decision search:

MCP security policy: