# Changelog

All notable changes to SuperLocalMemory V3 will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.4.63] - 2026-05-31 — Dashboard search: fix async blocking + fast mode

Fixes "signal is aborted without reason" in dashboard search (second root cause,
different from v3.4.61's WorkerPool fix).

### Root Cause
`engine.recall()` is a synchronous blocking Python call (~2-10s). Calling it
directly inside an `async` FastAPI route blocks the ASGI event loop for the
full duration. Chrome detects a stalled HTTP connection (no response headers
being sent) and fires `controller.abort()` with no reason — producing the
"signal is aborted without reason" browser error, regardless of fetch timeout.

### Fix
1. `await loop.run_in_executor(None, lambda: engine.recall(...))` — offloads
   the blocking call to a thread pool. Event loop stays alive to send HTTP
   keepalive frames, preventing Chrome from aborting the connection.
2. `fast=True` — skips spreading_activation + Hopfield channels, reducing
   recall from 9.5s to <2s. These channels add precision for MCP/session_init
   but are unnecessary for dashboard search results.

### Result
Dashboard search: 919ms cold, 1.7s warm. Zero browser aborts.
Works immediately after `slm restart` — no wait needed.

### Changed
- `server/routes/memories.py`: `search_memories` uses `run_in_executor` + `fast=True`

---

## [3.4.62] - 2026-05-31 — Recall engine pre-warm on startup

Adds a `recall-warmup` background thread that fires one full 6-channel recall
immediately after daemon startup. This loads the graph_edges table (~100 MB,
347K rows) into SQLite's page cache before the first user query arrives.

Without this, cold first query = 15-24s (reading graph_edges from disk).
After this warmup, all queries hit warm cache at <2s — for both MCP and dashboard.
Warmup is non-blocking (daemon stays available), fires after embedding warm.

### Changed
- `server/unified_daemon.py`: `_warmup_recall()` thread fires after `_warmup_embedder()`

---

## [3.4.61] - 2026-05-31 — Dashboard search fix (in-process engine)

**Fixes dashboard search always timing out** with "signal is aborted without reason".

### Root Cause
`POST /api/search` (used by the SLM dashboard memories pane) called
`WorkerPool.shared()` — the legacy subprocess-based worker pool from v3.4.32
(pre-unified-daemon). This spawned a fresh Python subprocess and loaded the full
SLM engine cold on **every single search request**, taking 15–20s. The browser's
AbortController always fired before the response arrived.

The `/recall` HTTP endpoint (used by MCP `session_init`) uses the daemon engine
directly and is warm at <1s. The dashboard used a completely different code path.

### Fix
`search_memories` now calls `_get_engine(request).recall()` — the daemon's own
in-process engine that is already loaded and shares the warm SQLite page cache.
Falls back to direct LIKE text search if engine is unavailable during startup.

### Result
Dashboard search: **<1s warm** (was >15s → browser abort).
GitHub sync failure shown in sidebar is a separate backup connectivity issue,
unrelated to search.

### Changed
- `server/routes/memories.py`: `search_memories` uses daemon engine, not WorkerPool

---

## [3.4.60] - 2026-05-31 — Daemon OpenMP Crash Hotfix

**Hotfix for v3.4.59.** Forces `OMP_NUM_THREADS=1` and `KMP_DUPLICATE_LIB_OK=TRUE`
in the daemon subprocess environment BEFORE Python imports any C extensions that
bundle their own libomp.dylib (torch, scikit-learn, lightgbm).

### Root Cause
Setting these env vars in `superlocalmemory/__init__.py` (v3.4.58 fix) was too
late on Apple Silicon (M5 Pro). By the time `import superlocalmemory` runs, the
parent process has often already loaded one of the OpenMP-using extensions, and
that libomp's thread pool is initialized. When lightgbm later forks its worker
pool from `LGBM_DatasetCreateFromMat`, the parent's libomp thread structs are
incompatible with lightgbm's libomp → SIGSEGV at
`__kmp_suspend_initialize_thread` reading address `0x580`.

### Fix
`cli/daemon.py:_start_daemon_subprocess()` now copies `os.environ`, sets
`OMP_NUM_THREADS=1` + `KMP_DUPLICATE_LIB_OK=TRUE`, and passes it via the
`env=` kwarg to `subprocess.Popen`. The daemon Python interpreter starts
with these vars already in its environment, so C extension imports see the
correct values during their `_init` constructors.

### Why 1 thread and not 2
v3.4.58 set `OMP_NUM_THREADS=2` as a compromise. On M5 Pro the parallel-fork
race still triggered at 2. SLM's actual ML workloads (ranker retrain, dedup)
operate on datasets of 50–5,000 rows where 1-thread serial OpenMP is within
~10% of multi-threaded but eliminates the crash class entirely.

### Changed
- `cli/daemon.py`: `_start_daemon_subprocess()` injects `env={OMP_NUM_THREADS=1, KMP_DUPLICATE_LIB_OK=TRUE}` into Popen

---

## [3.4.59] - 2026-05-31 — Graph Edge Cap + Recall Reliability

**Fixes SLM falling into degraded FTS5 mode on every session start**, and stops
the knowledge graph from growing into an O(n²) edge explosion that made recall
slow as the fact corpus scaled beyond 10K facts.

### Root Causes Fixed

**1. MCP timeout too aggressive for dense graphs (degraded mode bug)**
The v3.4.57 timeout reduction (60s→8s) was correct for small graphs but
backfired at scale. With a 17K-fact corpus and 2.1M graph edges, full 6-channel
recall (including spreading activation + Hopfield) takes 13–15s. The 8s timeout
always expired → `pool_recall` raised `PoolError` → session_init fell back to
emergency FTS5 BM25. Every session started in degraded mode silently.

**Fix:** `DaemonPoolProxy.timeout_s` raised from 8s to 30s. Covers the current
worst-case (13.4s full recall) with 2× headroom. The orphan-flood concern from
v3.4.57 is mitigated by the ingest cap reductions below, which stop the graph
from growing further.

**2. Knowledge graph edge explosion (O(n²) growth)**
At 17K facts, the corpus hit 2.1M graph edges (avg 121 per node). Root cause:
`_MAX_ENTITY_EDGES_PER_ENTITY = 20` was designed for ~500-fact graphs. At scale,
hub entities (appearing in 1000s of facts) accumulated 5000+ edges per node.
Spreading activation must fan out across all edges per node, causing 9s SA time.

**Fixes:**
- `_MAX_ENTITY_EDGES_PER_ENTITY` lowered from 20 → 5
- `_MAX_CAUSAL_EDGES_PER_ENTITY` lowered from 20 → 5
- **Hub node filter added:** nodes already at ≥ 200 total edges are skipped
  during ingest. High-frequency hub nodes (e.g. a term appearing in every fact)
  link everything to everything — they are graph noise, not graph signal.
- Hub cache shared across entity/causal edge builders per `build_edges` call to
  avoid redundant DB queries.

**3. Spreading activation UNION query not using indexes (SA slow path)**
The `_get_unified_neighbors` UNION ALL query fetched all edges for a node then
sorted them, preventing the `idx_edges_source_weight` and `idx_edges_target_weight`
covering indexes from terminating early. Fix: push `ORDER BY weight DESC LIMIT ?`
inside each UNION branch (wrapped in `SELECT * FROM (...)` per SQLite compound
SELECT syntax). SQLite now stops after `max_neighbors_per_node` rows per branch
using the covering index instead of materialising the full edge set.

**4. Degree-cap pruner added to graph_pruner.py**
New `_cap_node_degree()` function using `ROW_NUMBER() OVER (PARTITION BY source_id
ORDER BY weight DESC)` — single-pass window function, no Python loops. Integrated
into `prune_graph()` as `cap_degree=True` (default). Automatically runs during
scheduled maintenance cycles to keep hub nodes bounded.

### Changed
- `mcp/_daemon_proxy.py`: `timeout_s` default 8.0 → 30.0
- `encoding/graph_builder.py`: entity + causal caps 20 → 5; hub filter at 200 edges
- `core/graph_pruner.py`: `_cap_node_degree()` added; `prune_graph()` gains `cap_degree` param
- `retrieval/spreading_activation.py`: UNION LIMIT pushed inside each branch

### Tests
4053 passed, 15 skipped — no regressions.

---

## [3.4.58] - 2026-05-30 — Permanent OpenMP SIGSEGV Fix

**Eliminates the recurring Python crash popup on macOS Apple Silicon.** Any user
who triggered a LightGBM retrain cycle (background learning after ~50 recalls)
would see a macOS crash report for `Python [PID]` with SIGSEGV at
`__kmp_suspend_initialize_thread + 32`. This release permanently fixes the root
cause in the SLM source — no system changes required.

### Root Cause
SLM's dependency set ships **three separate `libomp.dylib` binaries** on macOS ARM:
- `torch==2.11.0` bundles `/opt/llvm-openmp/lib/libomp.dylib` (860 KB)
- `scikit-learn==1.8.0` bundles its own `/opt/llvm-openmp/lib/libomp.dylib` (678 KB)
- `lightgbm==4.6.0` resolves to homebrew's `/opt/homebrew/opt/libomp/lib/libomp.dylib` (739 KB)

When `lgb.Dataset(X, ...)` called `LGBM_DatasetCreateFromMat` → OpenMP `fork_call`
with `num_threads = os.cpu_count() - 1` (9 threads on M4 Mac), the parallel
worker threads were allocated by LightGBM's libomp but attempted to synchronize
via PyTorch's libomp thread pool. The two runtimes have incompatible internal
thread structs — the barrier release read address `0x580` (null + struct offset),
causing `EXC_BAD_ACCESS (SIGSEGV)` in Thread 26.

**All macOS Apple Silicon users with the standard SLM install were affected.**
The crash fired silently in a background consolidation worker, causing the
`slm mcp` subprocess to restart repeatedly, generating the persistent crash popup.

### Fixed
- **`ranker_retrain_online.py` line 198** — `num_threads = max(1, os.cpu_count()-1)`
  changed to a safe cap of **2 threads** (configurable via `SLM_LGBM_THREADS` env
  var). With ≤2 threads, the problematic parallel fork path in
  `DatasetLoader::ConstructFromSampleData` is avoided entirely. SLM's training
  datasets (50–5,000 rows) see ~90% of max-core throughput at 2 threads — the
  difference is under 200ms per retrain cycle.
- **`__init__.py`** — `KMP_DUPLICATE_LIB_OK` changed from `os.environ.setdefault`
  (could be overridden to FALSE) to unconditional `os.environ[...] = "TRUE"`.
  Added `OMP_NUM_THREADS=2` cap (respects user override) as belt-and-suspenders
  at the OS level before any C library reads the thread count.

### New environment variables
- `SLM_LGBM_THREADS` — override the LightGBM thread count (default: `2`).
  Only increase if your system has a unified single-runtime OpenMP setup.

### Why not fix the dylib collision instead?
Patching `libomp.dylib` on users' systems via `install_name_tool` is fragile:
it breaks on package updates, requires write access to site-packages, and fails
if SIP prevents modifying signed binaries. The source fix is permanent, upgrade-safe,
and works identically on every user's machine regardless of their exact package versions.

## [3.4.52] - 2026-05-28 — Warm Memory, No Cold Starts

**Production resilience for session_init.** No quality degradation as the primary path: full 6-channel recall (semantic + BM25 + entity + temporal + Hopfield + spreading-activation, Fisher-Rao fusion) is preserved. The cold-start problem is fixed at the infrastructure layer, not by downgrading retrieval.

### Fixed
- **Ollama embedding model unloads after 5 min idle** (`core/ollama_embedder.py`) — `_call_ollama_embed` and `_call_ollama_embed_batch` did not pass `keep_alive` to Ollama, so the embedder defaulted to 5-minute residency. After idle, next call required a 20-30s model reload from disk → DaemonPoolProxy's 30s HTTP timeout occasionally aborted → MCP clients (Hermes, CommandCode) saw `session init failed (connection error)`. Now both calls pass `keep_alive: -1`, pinning `nomic-embed-text` (~274 MB) in VRAM forever. Industry-standard pattern used by Hindsight, Zep, Supermemory.
- **DaemonPoolProxy HTTP timeout increased 30s → 60s** (`mcp/_daemon_proxy.py`) — Safety net for unexpected slowness during daemon restart windows. With keep_alive=-1 in place, this almost never matters, but it removes the cliff edge.

### Added
- **Emergency FTS5 BM25 fallback in `session_init`** (`mcp/tools_active.py`) — When the daemon is completely unreachable (truly dead, not just slow), `session_init` falls back to a direct SQLite query against the existing `atomic_facts_fts` virtual table with native BM25 ranking via `ORDER BY fts.rank`. Multi-process safe via WAL mode. Response includes explicit `degraded_mode: true` and `retrieval_mode: "emergency_fts5_bm25"` flags (Zep "Memory Unavailable" pattern) so agents can surface the degraded state to the user. This is the Mem0 / Letta industry-standard fallback — real BM25 math, not keyword LIKE.
- **`/health` reports `embedding_warm` flag** (`server/unified_daemon.py`) — MCP clients can poll the daemon's health endpoint to wait for the embedding model to finish loading before issuing recall calls. Set to `true` once the async pre-warm thread completes its first `embedder.embed("warmup")` call.

### Changed
- **`session_init` reverted to full 6-channel recall** (`mcp/tools_active.py`) — v3.4.51 had downgraded `session_init` to `fast=True` (BM25 only) as a timeout workaround. v3.4.52 restores full 6-channel recall as the primary path — quality is no longer compromised. Cold-start is prevented at the Ollama layer instead.

### Why this matters
A memory system's value is its retrieval quality. Degrading to BM25-only at session start would mean every agent session begins with degraded memory — exactly the opposite of what users expect. v3.4.52 fixes the actual root cause (Ollama cold-start) and reserves the BM25 fallback for true catastrophic failures (daemon completely dead). The agent is told explicitly via `degraded_mode` when this happens.

## [3.4.51] - 2026-05-28 — Recency Intelligence

**Session context is now time-aware.** Stale memories from completed projects and old debugging sessions no longer surface at session start. Frequently-recalled architectural decisions resist decay automatically.

### Fixed
- **Exponential recency decay + FSRS stability strengthening** (`retrieval/engine.py`) — Replaced the nearly-flat linear formula (range `[0.92×, 1.1×]`, 2.3% spread) with an Ebbinghaus exponential decay enhanced by FSRS v5 access-count stabilization. Formula: `boost = 0.8 + 0.3 × e^(-(ln2/S) × age_days)` where `S = 30d × min(2.0, 1 + 0.1 × access_count)`. Effect: a 45-day-old session handoff recalled 0 times → 0.91× (was 1.075×). Same memory recalled 10 times → 0.95× — frequently-used architectural decisions naturally resist decay without any category labeling. Reference: Dae & Jarrett (2024) FSRS v5; Ebbinghaus (1885) retention curve.
- **`age_days` hardcoded to 0 in adaptive ranker** (`core/recall_pipeline.py`) — Both `apply_adaptive_ranking` and `apply_v2_adaptive_ranking` were passing `"age_days": 0` to the LightGBM ranker, making it permanently blind to memory age. Now computes real age from `fact.created_at`. The ranker can now learn age-preference signals.
- **`created_at` missing from pool recall protocol** (`server/unified_daemon.py`, `mcp/_pool_adapter.py`) — The daemon's `/recall` response omitted `created_at`. Added to both recall response serialisation paths and to `PoolFact` dataclass. All MCP-layer tools now receive real memory timestamps.
- **`session_init` age gate** (`mcp/tools_active.py`) — Added `max_age_days: int = 30` parameter. Memories older than 30 days are suppressed unless their relevance score ≥ 0.70 (architectural decisions always surface). `max_age_days=0` disables the gate. Removed `fast=True` — session context deserves full 6-channel recall.
- **`slm session-context` age gate** (`cli/commands.py`) — Fast-path SQLite query now respects `--max-age-days` (default: 30). Previously hardcoded to 7 days. CLI and MCP now apply identical age semantics.

### Added
- **`slm session-context --max-age-days N`** (`cli/main.py`) — New flag to control how far back session context reaches. Default 30. Set to 0 to disable. Consistent with MCP `session_init(max_age_days=N)`.
- **`slm session-context --full`** — Explicit flag to use the full engine path (was implicit via code). Documented.
- **`slm session-context --json`** — Agent-native JSON output, consistent with all other commands.

### Changed
- `session_init` MCP tool schema gains optional `max_age_days` parameter (default: 30). Backward-compatible.
- `PoolFact` gains `created_at: str = ""` field. Backward-compatible — defaults to empty string.
- `slm session-context` fast path changed from hardcoded 7-day window to `--max-age-days` controlled window (default 30).

## [3.4.50] - 2026-05-25 — Scale-Ready

**1 million memories. Zero slowdown.** Tiered storage, graph pruning, and optional acceleration backends for infinite scale.

### Added
- **Tiered Storage (Hot/Warm/Cold/Archive)** — Facts auto-classified by age + access patterns. Hot facts prioritized in graph/vector search. Cold facts archived but never deleted. Nightly rebalance with misfire-safe cron. `slm pin <fact_id>` to keep any fact hot forever.
- **Graph Pruning Engine** — Chain collapse, garbage entity removal, low-activity edge decay. Reduced edge count while preserving semantic connections. Safe to run repeatedly — idempotent.
- **access_count_30d** — Rolling 30-day access window with batch-flush recording. Replaces lifetime counter for accurate tier assignment (F-14).
- **Optional Graph Acceleration** — `pip install superlocalmemory[cozo]` for CozoDB embedded graph backend. Replaces NetworkX for spreading activation at 1M+ edges. Zero-config: daemon auto-migrates on restart.
- **Optional Vector Acceleration** — `pip install superlocalmemory[lancedb]` for LanceDB embedded vector backend. Cosine similarity with IVF+PQ indexing. Auto-migration from sqlite-vec.
- **Backend Status Dashboard** — `slm doctor` shows CozoDB/LanceDB migration status, tier distribution, and health warnings.
- **M014_v345_scale_ready migration** — Automatic on daemon restart. Adds `access_count_30d` column and graph edge indexes.

### Changed
- **Migration is fully automatic.** Upgrade package → restart daemon → done. No `slm migrate` needed. Schema applied silently via the migration runner. Idempotent — safe to restart multiple times.

### Fixed
- **WAL lock contention** — Multiple stale `slm mcp` processes causing 16-second health checks. Process reaper enhanced with orphan detection.
- **Graph edge index performance** — Added `idx_graph_edges_source_id` and `idx_graph_edges_target_id` for bulk import at 1M+ scale (F-20).

## [3.4.49] - 2026-05-22

### Added
- **`SLM_DAEMON_HOST` env var** — Configurable host binding for the unified daemon. Previously hardcoded to `127.0.0.1`; now reads `SLM_DAEMON_HOST` (default `127.0.0.1`). Set to `0.0.0.0` to expose the SLM API on all LAN interfaces for cross-machine mesh use.

## [3.4.48] - 2026-05-21

**Multi-Machine Mesh Coordination — M4 & M5 now work as one.**

### Added
- **`RemoteSyncClient` — cross-machine peer sync** (NEW in v3.4.48)
  - HTTP-based sync with remote SLM instances
  - Populates `broker._remote_peers` from remote `/mesh/peers` endpoint every 30s
  - Environment variables:
    - `SLM_MESH_PEER_URL`: Full URL of remote SLM (e.g. `http://192.168.1.100:8765`)
    - `SLM_MESH_SHARED_SECRET`: Shared auth secret (required for remote mode)
    - `SLM_MESH_DISCOVERY`: `'on'` (default) or `'off'` for mDNS discovery
  - **mDNS discovery (optional)**: Auto-discovers remote SLM on LAN via `_slm-mesh._tcp` service
  - **Message proxying**: `broker.send_message()` now proxies direct messages to remote peers
  - **Graceful fallback**: Network errors logged but don't crash; optional `zeroconf` dependency
  - Uses `httpx` (already in core deps) + `zeroconf>=0.140` (new, pure Python, cross-platform)

- **Auth guard on `/mesh/peers`** — Remote queries must include `Authorization: Bearer {SLM_MESH_SHARED_SECRET}`

### Changed
- `MeshBroker` now instantiates `RemoteSyncClient` when `SLM_MESH_PEER_URL` is set or in remote mode
- `broker.send_message()` checks `to_peer in broker._remote_peers` before DB lookup
  - If remote, proxies via `sync_client.send_to_remote()`
  - If local or not found, uses existing DB logic
- `broker.list_all_peers()` returns local + remote peers merged

### Tests
- 13 new tests in `tests/integration/test_remote_sync.py`
  - Init, peer sync, stale peer removal, send proxy, error handling
  - mDNS discovery callback stubs
  - Integration: broker routes sends to remote peers

All existing tests pass. No breaking changes.

---

## [3.4.46] - 2026-05-18

### Added
- **`SLM_MCP_TOOLS` env var** — Fine-grained MCP tool allowlist. Users can now
  set `SLM_MCP_TOOLS=remember,recall,search,session_init` to expose exactly
  the tools they need, reducing MCP context budget. Falls back to 25-tool
  essential set when unset; `SLM_MCP_ALL_TOOLS=1` still wins for power users.
- **`KMP_DUPLICATE_LIB_OK=TRUE`** — Set at package init to prevent OpenMP
  multi-library crashes when PyTorch, ONNX Runtime, and NumPy-MKL all load
  their own runtimes simultaneously.

### Fixed
- **WAL busy_timeout ordering** (PR #24, @kenyonxu) — `_enable_wal()` now
  sets `busy_timeout` before `journal_mode=WAL`, ensuring the 10s configured
  timeout is used instead of SQLite's default 5s during WAL initialization.
- **Engine init traceback logging** (PR #25, @kenyonxu) — `logger.exception()`
  replaces `logger.warning()` on daemon engine init failure, capturing the
  full traceback for root-cause diagnosis.
- **MCP `fast` recall wiring** (PR #22, @VikingOwl91) — `fast=True` recall
  parameter now threads through the full MCP→daemon→worker stack.
  `session_init` performs one `pool_recall(fast=True)` instead of two
  redundant recalls. Tools switch from `WorkerPool.shared()` to `choose_pool()`
  for daemon-first routing (avoids N×1.6 GB ONNX duplication across IDEs).
- **FTS trigger idempotency** — `CREATE TRIGGER IF NOT EXISTS` prevents race
  crashes on repeated schema init.

---

## [3.4.43] - 2026-05-12

Smart-hook architecture release. Replaces the time-based 15-minute recall
reminder with event-based detection that only fires when there's a real
signal to recall against. Adds a pre-web-search recall hook so SLM's local
memories are always surfaced before paying for external research.

Both additions are perf-budgeted, fail-open, and idempotent. They activate
on the next `slm hooks install` (or `slm init`); existing installations
keep working unchanged until upgraded.

### Added
- **`slm hook topic_shift`** — UserPromptSubmit handler that keeps a 5-prompt
  sliding window of content-word lists per session and emits a single-line
  recall reminder ONLY when the current prompt's content-word set has zero
  overlap with EVERY recent prompt (the strictest defensible signal for a
  genuine topic pivot). Per-prompt max-overlap algorithm; not jaccard-vs-union
  which over-fires on natural conversational drift. Stdlib-only, latency
  <10ms p99. State file at `/tmp/slm-topicstate-{sha256(session_id)[:16]}.json`,
  auto-purged after 24h. Observability log at `~/.superlocalmemory/logs/
  topic-shift.log` (TSV: timestamp, session_hash, current_words_count,
  window_depth, max_overlap, fired, prompt_preview). Disable with
  `SLM_TOPIC_SHIFT_LOG=0`. Module: `superlocalmemory/hooks/topic_shift_hook.py`.
- **`slm hook before_web`** — PreToolUse handler wired on
  `matcher="WebSearch|WebFetch"`. Extracts the search query / URL / prompt
  from Claude Code stdin, runs `slm recall <query> --limit 5`, injects
  results as a `<system-reminder>` with the standard untrusted-boundary
  markers so Claude reads local memory BEFORE the web call fires. Cost:
  ~500-800ms warm per fire, but only on web tool calls (5-20x per typical
  session). Fail-open on SLM-down / timeout / empty results. Module:
  `superlocalmemory/hooks/before_web_hook.py`.
- **`HOOKS_VERSION = "3.4.43"`** — bumped so `slm hooks status` flags
  pre-3.4.43 wirings as outdated. Run `slm hooks install` to upgrade
  to the new wiring.

### Changed
- **`_hook_checkpoint` periodic nag REMOVED.** The 15-minute "[SLM] 15+ min
  since last context refresh" and 30-minute "[SLM] Call
  mcp__superlocalmemory__get_learned_patterns" reminders previously emitted
  by `slm hook checkpoint` are gone. Time-based reminders were noisy on
  focused sessions and blind to quick topic pivots within a window. The
  event-based topic_shift hook is the replacement; on-demand
  `get_learned_patterns` MCP calls cover the learning side.
  `_hook_checkpoint`'s real value — auto-observe on file-change events —
  is unchanged. The `_RECALL_INTERVAL` and `_LEARN_INTERVAL` constants
  are retained for backward import compatibility.

### Fixed
- **`slm mode <X>` CLI no longer clobbers embedding / retrieval / evolution /
  forgetting / math settings.** Before this release the CLI handler called
  `SLMConfig.for_mode(...)` passing only `llm_*` kwargs — silently
  re-deriving every other field from mode defaults. A user with a tuned
  cross-encoder (`cross-encoder/ms-marco-MiniLM-L-12-v2`) or a custom
  embedding endpoint would lose their settings on every `slm mode b`.
  The v3.4.34 `mode_change=True` guard only protected the `mode` field
  itself; surrounding fields were lost. v3.4.43 reworks `cmd_mode` to
  mutate only `config.mode` and save — preserving all other config
  byte-for-byte. Mode-appropriate LLM defaults are populated ONLY when
  the user has no provider set (so the daemon can still come up on a
  fresh install). Tests: `tests/test_mode_switch_preservation.py` (7 new
  regression tests covering A↔B, B↔A, anchor preservation, JSON path,
  no-write-on-read, and the "Embedding model changed" warning that
  used to fire on every benign mode switch).
- **Default `PreToolUse` entry added on `slm hooks install`**. Previously
  PreToolUse was empty unless `include_gate=True`. Now it contains one
  entry (`before_web` on `WebSearch|WebFetch`) by default; gating users
  get that PLUS the firewall entry. Existing settings are merged
  idempotently — `_is_slm_hook_entry` recognises the new wiring so
  `slm hooks remove` cleans it up properly.

### Security
- **CVE-2025-69872 closed (diskcache pickle deserialization RCE).** `diskcache`
  was declared in `pyproject.toml` but never imported anywhere in `src/` or
  `tests/` — a phantom dependency. Removed entirely. The `slm doctor`
  performance-deps check no longer references it. Zero behavior change for
  users; lower attack surface; smaller install.
- **CVE-2026-1839 (transformers Trainer torch.load RCE) — UNREACHABLE in SLM,
  upstream-pinned.** The vulnerable method `Trainer._load_rng_state` is in
  training code paths. SLM is inference-only (uses `sentence-transformers`
  with ONNX backend; never instantiates `Trainer`). pip-audit flags the dep
  version because the vulnerable bytes are installed, but the code path is
  never executed by SLM. We CANNOT pin `transformers>=5.0.0` (the upstream
  fix) yet because `optimum-onnx 0.1.0` (the latest upstream release as of
  v3.4.43) caps `transformers<4.58.0` — and `embedding_worker.py` requires
  the ONNX backend. Will tighten the pin when optimum-onnx ships a
  transformers-5.x-compatible build. Tracking issue: see project changelog
  for v3.4.44+. Sentence-transformers minimum bumped to `>=5.2.0` to lock
  out 5.0.0-5.1.2 (which capped transformers `<5.0.0` even more strictly)
  and give the resolver maximum headroom for when the upstream pin lifts.

### Migration
- Existing v3.4.42 users: run `slm hooks install` (or `slm init`) once
  after upgrading to pull in the new UserPromptSubmit and PreToolUse
  entries. `slm hooks status` will flag the version mismatch.
- The settings.json merge is idempotent; running install twice is safe.
- Topic-shift detection works immediately on first new session — no DB
  or state migration required.
- `pip install -U superlocalmemory` will pull `transformers>=5.0.0` and
  drop the unused `diskcache` dep automatically.

---

## [3.4.42] - 2026-05-11

Operational reliability release. Three latent bugs in the daemon /
worker-singleton paths that surfaced together when running on a
fresh-install machine and produced misleading "failed" output despite
the system actually working. None of them affected the core recall or
remember pipelines on a healthy daemon — they only broke `slm restart`,
`slm warmup`, and `slm health` cosmetically — but the resulting noise
eroded trust and made real failures harder to diagnose. All three are
fixed without changing public APIs.

### Fixed
- **`slm restart` Step 3 false-negative.** Step 2 of `cmd_restart`
  acquires `daemon.lock` via `fcntl.flock(LOCK_EX | LOCK_NB)` to block
  other CLI/MCP processes from racing to start a daemon during the
  restart window. Step 3 then called `ensure_daemon()`, which itself
  attempts to acquire the same lock from a separate file descriptor in
  the SAME process. BSD-style flock blocks per-fd even within one
  process, so the second flock failed with `EWOULDBLOCK`,
  `ensure_daemon` fell into its "wait for someone else to start it"
  branch, timed out at 60 s, and reported "failed to start" — even
  though no actual error occurred and a follow-up CLI call would
  successfully start the daemon. Fixed by extracting
  `_start_daemon_subprocess()` from `ensure_daemon()`. The new helper
  performs the raw `subprocess.Popen` + PID/port file write +
  `_wait_for_daemon` polling without taking the lock. `cmd_restart`
  Step 3 now calls the helper directly (it already holds the lock);
  `ensure_daemon()` itself is unchanged for external callers — it
  acquires the lock and then delegates to the same helper. (`B1`)

- **`slm warmup` "embedding verification failed" when daemon is up.**
  `EmbeddingService._ensure_worker` enforces a machine-wide singleton
  via a PID file (v3.4.13): only one embedding worker can exist per
  machine, normally owned by the unified daemon. A fresh
  `EmbeddingService` started by `slm warmup` saw the singleton, set
  `_available = False`, returned `None` from `_subprocess_embed`, and
  printed "Model loaded but embedding verification failed" with a
  diagnostic that incorrectly guessed at a "Node.js wrapper Python-path
  mismatch" (no Node.js is involved when running `slm warmup` from the
  shell). Fixed by making `cmd_warmup` daemon-aware: when the daemon
  is reachable and reports `engine=initialized`, the model is already
  loaded inside the daemon's worker — print a `[PASS]` summary and
  return without spawning a redundant local worker. The original
  local-spawn path is preserved as a fall-through for the daemon-down
  case. (`B2a`)

- **Reranker false-positive "warmup failed" warning in CLI processes.**
  Any CLI process that wires a `RetrievalEngine` while the daemon is
  running (`slm health`, `slm doctor`, `slm recall`) would log
  `"Cross-encoder reranker warmup failed — recalls will use fallback
  scoring"` even though the daemon's reranker was healthy and serving
  fine. The CLI process's own warmup was correctly blocked by the
  reranker singleton, but the message did not distinguish the benign
  singleton case from a real model-load failure. Fixed in
  `engine_wiring.init_engine`: when `warmup_sync` returns `False`,
  probe `_is_reranker_worker_alive()`. If another process owns the
  worker, log an `INFO` line describing the singleton ownership;
  reserve the `WARNING` for the genuine no-owner failure case. The
  diagnostic value of the warning is preserved — only the false
  positive is removed. (`B2b`)

### Added
- 17 new unit tests covering the three fixes (`tests/test_cli/test_v3442_*`,
  `tests/test_core/test_v3442_reranker_warmup_singleton.py`). Tests are
  fully mocked (no real subprocess spawn, no DB) and run in <1 s.
- `pytest-asyncio>=0.21` added to both `[project.optional-dependencies].dev`
  and `[dependency-groups].dev` in `pyproject.toml`. `asyncio_mode = "auto"`
  configured in `[tool.pytest.ini_options]`, and the `asyncio` marker is now
  registered. Resolves a local-vs-CI environment drift where 6 async adapter
  tests (`tests/test_adapters/test_sync_loop.py`) failed locally for anyone
  who installed via `pip install -e ".[dev]"` without separately installing
  `pytest-asyncio` — the CI publish workflow installs the plugin explicitly,
  so PyPI builds were not blocked, but the failures were noisy and
  contributor-hostile.

---

## [3.4.41] - 2026-05-09

Hotfix release. Pins `tree-sitter-language-pack` to the `<1` line. The
upstream 1.x rewrite (Rust-backed) ships an incompatible Parser API — the
language-pack's bundled `Parser` no longer exposes `.parse()`, breaking the
code-graph extractor and its test suite. Pinning to the 0.x line restores
the documented API. A migration to the 1.x API will follow in a later
release once call-site changes are validated.

### Fixed
- `code_graph` extractor and tests broken by `tree-sitter-language-pack 1.x`.
  Constraint changed from `>=0.3,<2` to `>=0.5,<1`.

---

## [3.4.40] - 2026-05-09

Recall performance and entity-profile hygiene. Two scaling issues surfaced
on dense graphs: spreading-activation fan-out grew unbounded as graphs
exceeded the previous calibration target, and `entity_profiles.knowledge_summary`
grew unbounded via concatenation. This release bounds both, adds an opt-in
`--fast` recall mode, and increases the query embedding cache.

### Added
- **`slm recall --fast`** — skips the spreading-activation channel for
  faster response. The other four channels (semantic, BM25, temporal,
  hopfield) still run. Use when an agent needs recall before another
  tool call. Plumbed via a new `extra_disabled_channels` parameter through
  CLI → daemon `/recall` → `MemoryEngine.recall` → `run_recall` →
  `RetrievalEngine.recall`.

### Changed
- **Spreading-activation fan-out is bounded.** `_get_unified_neighbors`
  now applies `ORDER BY weight DESC LIMIT max_neighbors_per_node`
  (default 100). High-degree nodes previously expanded every neighbor
  every iteration. Bounded fan-out matches the SYNAPSE paper's
  sparse-graph assumption while preserving the highest-weight edges.
- **`SpreadingActivationConfig.top_m`: 20 → 10.** Compromise between the
  SYNAPSE default (7) and the prior dense-graph tuning (20).
- **`ObservationBuilder._build_summary` is now bounded.** Last 10 facts
  (was 20), 200-char cap per fact, 2048-char total cap. Previously
  `knowledge_summary` grew via concatenation and could exceed tens of
  KB on hub entities, polluting recall with stale text.
- **Query embedding LRU cache: 64 → 512 entries.** Sub-millisecond cache
  hits versus a 200–2000 ms embedding call. Memory cost is ≈1.5 MB.

### Maintenance
- `run_maintenance` now consolidates over-bound entity summaries via a
  single SQL update on the existing scheduler interval.

### Tests
- 399/399 retrieval + encoding suite passing.
- 12/12 spreading-activation unit tests passing.

### Upgrade notes
- Existing deployments with bloated `entity_profiles.knowledge_summary`
  rows will see them truncated on the next `slm consolidate` or
  scheduled maintenance run. The truncation is in-place; entity
  identity and `fact_count` are preserved.

---

## [3.4.38] - 2026-04-26

**P0 silent data loss fix.** The async `/remember` pipeline was broken since
v3.4.32 — memories were being marked "queued" and acknowledged but never
actually persisting to memory.db during runtime. Only daemon-restart drained
the pending queue (limit 20 per restart). 18 memories were permanently lost
to a NoneType iterable crash between April 15-26, 2026, all recoverable
because the content was preserved in pending.db.

### Fixed
- **Materializer `_engine` NameError** (`unified_daemon.py`). The background
  pending materializer thread referenced a module-level `_engine` global
  that was never declared. Result: every iteration threw `NameError: name
  '_engine' is not defined`, the exception was caught and logged as
  "materializer loop error", and the thread slept 5s and retried forever
  without ever processing pending memories. Bug present since v3.4.32.
  Fixed by declaring `_engine = None` at module level and assigning
  `_engine = engine` in the FastAPI lifespan after `engine.initialize()`.
- **scene_builder NoneType crash** (`encoding/scene_builder.py:assign_to_scene`).
  When the embedding worker was unavailable (cold-start timeout, crash),
  `embedder.embed()` returned None. The code checked `theme_emb is None`
  but never checked `fact_emb is None`, so `_cosine(None, theme_emb)`
  called `zip(None, theme_emb)` → `'NoneType' object is not iterable`,
  propagating up through `engine.store()` → mark_failed → permanent loss.
  Fixed by guarding `fact_emb is None` (skip scene assignment, still create
  scene) and adding defensive `None` check to `_cosine()` itself.
- **Retry-aware mark_failed** (`cli/pending_store.py`). Previously, ANY
  exception during materialization permanently marked the memory as
  failed — even transient errors like embedding worker timeout. Now uses
  the existing `retry_count` column: keeps status as `pending` until 3
  retries, only marks `failed` after all retries are exhausted.

### Added
- **Diagnostic logging in materializer** — "Materializer: waiting for
  engine to init...", "engine acquired, starting drain loop", "processing
  N pending memories" — so operators can verify the materializer is alive
  without grepping for absence of error messages.
- **`tests/test_integration/test_async_remember_e2e.py`** — full
  production pipeline test: POST `/remember` (async, default mode) →
  wait up to 60s → verify content in `memory.db` → recall returns it.
  This is the test that was missing for 8+ months. The 4,501 existing
  test functions test components in isolation (mocking `store_pending`)
  and never exercise the full async flow that real users hit.

### Recovery
On install, if you have existing failed records in `pending.db`, they will
be auto-retried on the next daemon restart by `engine._process_pending_memories()`.
To manually recover, run:
```python
import sqlite3
db = sqlite3.connect('~/.superlocalmemory/pending.db')
db.execute("UPDATE pending_memories SET status='pending', retry_count=0, error=NULL WHERE status='failed'")
db.commit()
```
Then `slm restart`.

---

## [3.4.37] - 2026-04-26

**P0 RAM fix.** Total SLM footprint reduced from ~14 GB peak to ~2.3 GB peak
(84% reduction). Idle dropped from ~2.5 GB to ~1.0 GB. Users with 16 GB
laptops can now run SLM without uninstalling.

### Fixed
- **CoreML EP allocation** — Added `ORT_DISABLE_COREML=1` to
  `recall_worker.py`, `cli/commands.py` (warmup diagnose path), and the
  Popen environment dicts in `core/embeddings.py` and
  `retrieval/reranker.py`. Previously only `embedding_worker.py` and
  `reranker_worker.py` set this. On ARM64 Mac, ONNX Runtime's CoreML
  Execution Provider allocated 3-5 GB per missing guard.
- **Duplicate MemoryEngine** — The QueueConsumer (recall_queue.db drain)
  was routing through `WorkerPool` → `recall_worker` subprocess, which
  loaded a SECOND full MemoryEngine inside the daemon. Now routes through
  the daemon's in-process engine via the new `EngineRecallAdapter`.
  Eliminates ~800 MB of duplication.
- **Eager warmup** — Removed `WorkerPool.shared().warmup()` from daemon
  startup. The recall_worker subprocess no longer spawns at boot. It
  remains available as a fallback for dashboard/chat routes.

### Changed
- **RSS limits tightened:**
  - `embedding_worker` self-kill: 4000 MB → 1800 MB
  - `recall_worker` self-kill: 2500 MB → 1500 MB
  - Daemon watchdog `MAX_WORKER_MB`: 4096 MB → 1800 MB
  - `HealthMonitor.global_rss_budget_mb`: 4096 MB → 2500 MB
- **Watchdog interval:** 60s → 15s in both daemon watchdog and
  HealthMonitor `check_interval_sec`. Catches memory spikes faster.
- **Idle timeouts:**
  - `SLM_EMBED_IDLE_TIMEOUT`: 1800s (30 min) → 300s (5 min)
  - `SLM_RERANKER_IDLE_TIMEOUT`: 1800s → 300s
  - Reduces idle RAM held by ML model subprocesses.

### Added
- **`EngineRecallAdapter`** in `unified_daemon.py` — wraps the in-process
  MemoryEngine to satisfy `RecallPoolProtocol` for the QueueConsumer.
  Eliminates the recall_worker subprocess on the hot path.

---

## [3.4.36] - 2026-04-25

Persistent hook daemon: recall latency drops from ~2.2s to sub-second by
eliminating Python subprocess startup on every prompt.

### Added
- **`hooks/hook_daemon.py`** — Unix domain socket server that keeps a
  long-lived process for recall requests. Claude Code connects via socket
  instead of spawning a fresh Python interpreter per prompt. Eliminates
  ~300-500ms of subprocess overhead. Starts/stops with the SLM daemon.
- **Auto-restart watchdog:** `ensure_hook_daemon()` checks socket health
  and restarts the daemon if it died. Claude Code hooks call this before
  connecting, so a crashed daemon is transparent to the user.
- **Graceful fallback:** if the socket is unavailable, the hook
  automatically falls back to the v3.4.35 subprocess path. Claude Code
  performance is NEVER impacted by daemon failure.
- **9 new tests** for daemon lifecycle, socket protocol, ack detection,
  watchdog, fallback, and memory safety.

### Performance
- Ack prompts: ~5ms via socket (was 30ms via subprocess)
- Substantive recall: target sub-1s (was 2.2s p50 via subprocess)
- Hook daemon RSS: ~15-20MB (no engine, no ONNX, no PyTorch)

---

## [3.4.35] - 2026-04-25

Production auto-recall: every Claude Code prompt automatically retrieves the
top relevant memories via the unified queue, so the agent has continuous-
learning context without the user invoking recall manually.

### Added
- **`hooks/auto_recall_hook.py`** — production UserPromptSubmit handler.
  Reads stdin JSON from Claude Code, detects ack prompts (silent fast path),
  enqueues substantive prompts to `recall_queue.db`, polls for the result
  with mode-aware timeout (A=10s, B=25s, C=40s), and injects the top-K
  memories as Claude Code's `hookSpecificOutput.additionalContext` envelope.
  Wraps recalled content in untrusted-boundary markers so the LLM treats
  it as data, not instructions. Fail-open on any error.
- **`core/queue_consumer.py`** — daemon background thread that drains
  `recall_queue.db`. Claims jobs atomically, routes through `pool.recall()`
  (engine never loaded in MCP/hook processes), writes results back. Priority
  lanes (high=recall, low=consolidate). Periodic cleanup of completed rows.
- **`slm hook auto_recall`** CLI subcommand wires Claude Code to the hook.
- **50 new tests** — `test_queue_consumer.py` (11) + `test_auto_recall_hook.py`
  (39). Full TDD coverage including ack detection, fencing, dedup, fail-open.

### Changed
- **`core/recall_queue.py`** — `complete()` now wrapped in `BEGIN IMMEDIATE`
  for fencing-token atomicity under multi-process access. Dedup hash
  includes `namespace` to prevent cross-namespace result collisions.
- **`server/unified_daemon.py`** — starts QueueConsumer on boot, stops on
  shutdown.
- **`hooks/hook_handlers.py`** — dispatches `auto_recall` to the new hook.

### Performance
- p50 recall latency: 1.75s (40-prompt integration test, Mode B)
- p99 recall latency: 11.83s
- Hook process RSS: ~20 MB (no engine loading, no memory blast)
- Ack prompts: 30 ms (silent, no recall)

---

## [3.4.34] - 2026-04-25

Fix: user's mode choice can no longer be silently overwritten.

### Fixed
- **Mode protection in `SLMConfig.save()`.** Any `save()` call that would
  change the mode in `config.json` is now blocked unless the caller passes
  `mode_change=True`. This prevents accidental mode resets when code creates
  a fresh `SLMConfig()` (defaults to Mode A) and calls `save()` to persist
  an unrelated field change. A warning is logged when a silent mode change
  is blocked.
- **MCP `set_mode` preserves user settings.** Previously `set_mode` created
  a fresh `SLMConfig.for_mode()` that lost all user customizations (LLM
  provider, API keys, embedding config, active profile). Now carries forward
  all settings from the existing config, matching the dashboard behavior.
- All intentional mode-change paths (`slm mode`, MCP `set_mode`, dashboard
  PUT `/api/v3/mode`, setup wizard) pass `mode_change=True`.

---

## [3.4.33] - 2026-04-25

Fix: daemon leaked SQLite connections to learning.db via bandit threadlocals.

### Fixed
- **Bandit threadlocal connection leak.** `reward_proxy.settle_stale_plays`
  creates a `ContextualBandit` that opens a threadlocal connection via
  `_conn_for`. When called from `asyncio.to_thread` (bandit_loops.py,
  every 60 s), each thread-pool thread kept its connection open for the
  process lifetime. Over 24 h this accumulated 12+ leaked file descriptors
  and ~100 MB of wasted SQLite page-cache RAM. New
  `bandit.close_threadlocal_conn()` function, called in the
  `settle_stale_plays` finally block, ensures pool threads release their
  connections immediately.
- **Corrected embedding worker memory comment.** The `~200MB footprint`
  note was written for `all-MiniLM-L6-v2`; the default model
  `nomic-ai/nomic-embed-text-v1.5` uses ~1.1 GB via ONNX.

---

## [3.4.32] - 2026-04-24

Fix: concurrent remembers no longer block recalls on the shared embedder.

### Fixed
- **Daemon `/remember` is now async by default.** Writes to the pending
  queue in under 100 ms and returns a `pending_id`; a background thread
  drains the queue in the background. Previously, the synchronous
  `engine.store()` on the FastAPI event loop could block `/search` and
  `/health` for 30+ seconds while the single embedder worker processed a
  large write. Under concurrent load the daemon could appear hung.
- **Materializer yields to active recalls.** While any `/search` is in
  flight the drainer sleeps between items, so user-initiated recalls
  always get the embedder first.
- **MCP remember tool simplified.** Writes to `pending.db` and returns;
  the daemon's materializer completes the pipeline. Removes the
  redundant in-process `pool.store` background task that previously
  contended with `/search`.
- **`pool_store` returns `["pending:<id>"]`** when the daemon is async,
  keeping a stable identifier for callers without blocking on the
  embedder.

### Added
- `?wait=true` query parameter on `POST /remember` for callers that
  need synchronous behaviour and real `fact_ids` in the response.
- `superlocalmemory.core.recall_gate` module — shared counter that lets
  the materializer detect in-flight recalls and yield priority.

### Migration notes
- **No action required.** Existing clients continue to work; the
  response shape is compatible (`ok`, `count` still present). Scripts
  that depended on `fact_ids` to validate the write should switch to
  `pending_id` or pass `?wait=true` to opt in to the legacy behaviour.

---

## [3.4.31] - 2026-04-24

Dashboard truth, memory vs fact clarity, and self-cleaning pending queue.

### Changed
- **Dashboard now shows both memory counts honestly.** Parent memories
  (what you stored) and atomic facts (what retrieval indexes) appear as
  two distinct cards with their ratio. No more "Total Memories: 6,000"
  when you actually have 2,000 memories decomposed into 6,000 facts.
- **"Browse atomic facts"** relabeled for clarity — this view lists the
  indexed atomic units.
- **Visible search box** in the Memories tab — previously hidden behind
  the Recall Lab only. Search now debounces 280 ms on input.

### Added
- **`/api/memories/{id}/detail`** — full memory + all child atomic facts
  in one call. Powers the click-to-expand modal.
- **`/api/facts/{id}`** — single atomic fact detail with source memory
  content, entities, and canonical entities.
- **Pagination UI** — Prev/Next controls show "Showing 1–50 of 6,123".
  Previously hardcoded to 50 with no navigation.
- **CSV export** — new `format=csv` option on `/api/export` plus a
  dedicated "Export All (CSV)" menu item. JSON and JSONL still work.
- **Export progress toast** — "Preparing JSON export…" notification
  before the download starts.
- **`total_facts` + `facts_per_memory`** in `/api/stats` response.
- **Pending queue auto-cleanup** — the maintenance scheduler now sweeps
  the pending queue every cycle: completed rows > 7 days, failed rows
  over retry limit, and stuck rows > 7 days are removed; a 30-day hard
  cap prevents runaway growth on any status.

### Fixed
- **Test isolation** — `pending_store` now honors `SLM_DATA_DIR`. Four
  MCP remember tests were writing to the live `~/.superlocalmemory/`
  instead of `tmp_path`. Root conftest now forces `SLM_DATA_DIR=tmp_path`
  for every test unless explicitly opted out.
- **Fact click popup** — was calling `/api/v3/recall/trace` with a text
  substring (re-query by first 100 chars) and colliding with the memory
  row click handler. Now scoped to `.fact-result-item` only, hits the
  new `/api/facts/{fact_id}` endpoint.
- **Memory modal ID confusion** — the modal labeled `mem.id` as "ID"
  regardless of whether it was a memory_id or fact_id. Now displays
  both "Memory ID" and "Fact ID" when they differ.
- **Memory modal hydration** — fetches the full memory + fact list
  asynchronously when opened, so source content and entity data appear
  even for rows that arrived from the search endpoint.

---

## [3.4.30] - 2026-04-24

Multi-IDE shared worker, silent migration, and security hardening.

### Added
- **Multi-IDE RAM sharing.** MCP processes share a single recall worker
  via the daemon. Total RSS stays below 2 GB with four IDEs open.
- **Feedback and learning signals** flow from every IDE session to the
  daemon, not just the first.
- **Setup wizard** validates the data directory at install time and
  rejects iCloud, Dropbox, OneDrive, Box, Google Drive, and
  `Library/CloudStorage` paths that silently corrupt SQLite WAL.
- **One-time upgrade banner** after `pip install -U` / `npm install -g`
  points users to `slm doctor`.
- **`docs/errors.md`** — canonical error catalog with codes, recovery
  steps, exit codes, and HTTP status mappings.
- **CI matrix** now runs on `ubuntu-22.04`, `macos-14` (Apple Silicon),
  and `windows-latest` with `portalocker`.

### Changed
- **Silent, atomic data migration** on upgrade — no manual steps.
- **Migration serialized via file lock** so parallel pip + npm installs
  cannot race.
- **Concurrent-safe MCP engine singleton** with double-checked locking.
- Pool adapter returns frozen dataclasses instead of `SimpleNamespace`.

### Security
- File permissions tightened: marker files written at 0600, parent
  directories at 0700.
- Symlink-following blocked on version marker reads.
- Cloud-synced directory detection extended to `Library/CloudStorage`
  (macOS 13+).

### Fixed
- Silent error swallows in daemon shutdown, migration probe, and banner
  emission now log at WARNING.
- Fenced-out `complete()` writes (stale worker claims) emit a WARNING
  log instead of vanishing silently.
- Daemon-start migration guarded behind `is_ready` sentinel — skips
  when already applied.

---

## [3.4.23] - 2026-04-21

Critical hotfix on top of 3.4.22 for two end-user-facing regressions.

### Fixed
- **Daemon error log no longer balloons.** A ternary passed as the
  `logger.info` format string caused a `TypeError` on every startup in 24/7
  mode. Python's logging module then dumped the full FastAPI
  `merged_lifespan` stack to stderr; over a day the LaunchAgent log grew to
  tens of MB. The call is now pre-formatted. A defensive log-rotation pass
  at startup truncates any daemon log over 10 MB so users upgrading from
  3.4.22 get a clean slate on first boot.
- **Dashboard no longer hangs after a daemon upgrade.** Static JS/CSS/HTML
  was served without cache headers, so browsers served stale modules after
  `slm restart` and the dashboard showed an infinite spinner. All static
  responses now ship `Cache-Control: no-cache, must-revalidate`, and
  `index.html` embeds the server version; on mismatch the tab clears
  `localStorage` (preserving theme) and hard-reloads once.
- **Fetches can no longer hang forever.** A global `fetch` patch attaches a
  15-second `AbortController` timeout to every relative-URL request, so a
  dead socket surfaces as a rejection instead of leaving a spinner
  spinning. No callsite changes required.

### Added
- `GET /api/version` — returns the running daemon version; consumed by the
  dashboard version-fingerprint auto-reload.

---

## [3.4.22] - 2026-04-18

Hardening release — correctness, stability, and security fixes.

### Added
- `slm benchmark` plus escape-hatch commands (`disable`, `enable`,
  `clear-cache`, `reconfigure`).
- One-time upgrade banner on first boot after install.

### Changed
- Tighter defaults for the interactive installer.
- Licence: AGPL-3.0-or-later.
- Node.js prerequisite: ≥ 18.

### Security
- Hardened redaction, path validation, and token handling per internal
  audit. No end-user-visible behaviour change.

### Compatibility
- Fully backward compatible. `atomic_facts` is never modified by any
  migration. All upgrades are additive.

---

## [3.4.19] - 2026-04-17

### Fixed
- Recall cold-start eliminated. Embedding + reranker workers stay warm for 30 minutes by default instead of 2 minutes, so bursts of recalls no longer pay a 30-60 second model-load tax on every other query.

### New environment variables
- `SLM_EMBED_IDLE_TIMEOUT` — seconds to keep the embedding worker warm (default 1800). Set to 120 to restore pre-v3.4.19 behavior.
- `SLM_RERANKER_IDLE_TIMEOUT` — same, for the cross-encoder reranker (default 1800).

---

## [3.4.18] - 2026-04-17

### Fixed
- pip and npm installs now ship identical functionality. Semantic search and cross-encoder reranking work out of the box on pip (previously required `pip install superlocalmemory[search]`).
- First pip run auto-installs Claude Code hooks when Claude Code is detected, matching the npm postinstall experience.

---

## [3.4.17] - 2026-04-17

### Fixed
- Entity Explorer no longer stuck on "No entities found" after switching operating modes.
- Engine-backed routes (entity, ingest, recall, remember, list) auto-recover after mode changes — no daemon restart required.

### Added
- Mode change audit log at `~/.superlocalmemory/logs/mode-audit.log`.
- Mode C now requires an explicit API key via Settings to prevent accidental cloud-mode writes.

---

## Author

**Varun Pratap Bhardwaj**
*Solution Architect*

SuperLocalMemory V3 - Intelligent local memory system for AI coding assistants.

---

## [3.3.28] - 2026-04-07 — Stability Hotfix

### Fixed
- **Excessive memory usage during rapid file edits** — auto-observe now reuses a single background process instead of spawning one per edit. Rapid multi-file operations (parallel agents, branch switching, batch edits) no longer risk high memory usage.
- **Observation debounce** — rapid-fire observations are batched and deduplicated within a short window, reducing redundant work.
- **Memory-aware worker management** — new safety check skips heavy processing when system memory is low.

### New Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| `SLM_OBSERVE_DEBOUNCE_SEC` | `3.0` | Observation batching window |
| `SLM_MIN_AVAILABLE_MEMORY_GB` | `2.0` | Min free RAM for background processing |

---

## [3.3.3] - 2026-04-01 — Langevin Awakening

### Fixed
- **Langevin dynamics now active** — positions were never initialized at store time, causing the entire Langevin lifecycle system to be inert (0 positioned facts). New facts now receive near-origin positions (Strategy A).
- **Backfill for existing facts** — maintenance now initializes unpositioned facts using metadata-aware equilibrium seeding (Strategy B) followed by 50-step burn-in (Strategy C). Old, rarely-accessed facts land in their correct lifecycle zones immediately.

### Improved
- Maintenance returns `langevin_backfilled` count for observability
- Health check now reports positioned facts accurately after backfill

---

## [3.3.0] - 2026-03-31 — The Living Brain

### New Features
- **Adaptive Memory Lifecycle** — memories naturally strengthen with use and fade when neglected. No manual cleanup needed.
- **Smart Compression** — embedding precision adapts to memory importance, achieving up to 32x storage savings on low-priority memories.
- **Cognitive Consolidation** — automatic pattern extraction from clusters of related memories. Your knowledge graph self-organizes.
- **Pattern Learning** — auto-learned soft prompts injected into agent context at session start. The system teaches itself what matters.
- **Hopfield Retrieval** — 6th retrieval channel for vague or partial query completion. Ask half a question, get the whole answer.
- **Process Health** — automatic detection and cleanup of orphaned SLM processes. No more zombie workers.

### New CLI Commands
- `slm decay` — run memory lifecycle review
- `slm quantize` — run smart compression cycle
- `slm consolidate --cognitive` — extract patterns from memory clusters
- `slm soft-prompts` — view auto-learned patterns
- `slm reap` — clean orphaned processes

### New MCP Tools
- `forget` — programmatic memory archival via lifecycle rules
- `quantize` — trigger smart compression on demand
- `consolidate_cognitive` — extract and store patterns from memory clusters
- `get_soft_prompts` — retrieve auto-learned patterns for context injection
- `reap_processes` — clean orphaned SLM processes
- `get_retention_stats` — memory lifecycle analytics

### Dashboard
- 7 new API endpoints for lifecycle stats, compression stats, patterns, and process health
- New dashboard tabs: Memory Lifecycle, Compression, Patterns

### Improvements
- Mode A/B memory usage reduced from ~4GB to ~40MB (100x reduction)
- Embedding migration on mode switch (auto-detects model change)
- Forgetting filter in retrieval pipeline (archived memories excluded from results)
- 6-channel retrieval (was 5)

### Migration
- Fully backward compatible with 3.2.x
- New tables created automatically on first run
- No manual migration needed

---

## [3.2.2] - 2026-03-30

### Added
- Performance improvements for retrieval pipeline
- New memory management capabilities with configurable lifecycle controls
- Enhanced dashboard with 3 additional monitoring tabs
- 9 new API endpoints for configuration and status
- 5 new MCP tools for proactive memory operations
- 5 new CLI commands for configuration management

### Changed
- Internal retrieval architecture optimized with additional search channel
- Schema extensions for improved data management (9 new tables)
- Memory surfacing engine with multi-signal scoring

### Performance
- Significant latency reduction in recall operations (vector-indexed retrieval)
- Idle-time memory optimization for large stores
- Reduced memory footprint for long-running sessions

---

## [3.2.1] - 2026-03-26

### Fixed
- **Windows `slm --version` / `slm -v`** — `.bat` and `.cmd` wrappers now intercept `--version`/`-v` directly (fast path, no Python needed) and set `PYTHONPATH` to the npm package's `src/` directory before launching Python. Previously, Windows users hitting `slm.bat` instead of the Node.js wrapper got `unrecognized arguments: --version` because Python resolved an older pip-installed version without the flag.
- **Unix bash wrapper** (`bin/slm`) — now sets `PYTHONPATH` and intercepts `--version`/`-v`, matching the Node.js wrapper's behavior. Previously relied on npm's shim always routing to `slm-npm`.
- **`postinstall.js`** — now runs `pip install .` to install the `superlocalmemory` Python package itself (not just dependencies). Prevents stale pip-installed versions from shadowing the npm-distributed source. Falls back to `--user` for PEP 668 environments.
- **`preuninstall.js`** — corrected version string from "V2" to "V3".
- **Windows Python detection** — added `py -3` (Python Launcher for Windows) as a fallback candidate in `slm.bat`.
- **Environment parity** — all three entry points (`slm-npm`, `slm`, `slm.bat`) now set identical PyTorch memory-prevention env vars (`PYTORCH_MPS_HIGH_WATERMARK_RATIO`, `TORCH_DEVICE`, etc.).

---

## [3.2.0] - 2026-03-26

### Added
- **`slm doctor` command** — comprehensive pre-flight check: Python version, all dependency groups, embedding worker functional test, Ollama connectivity, API key validation, disk space, database integrity. Supports `--json` for agent-native output.
- **`slm hooks install`** listed in CLI reference and README.
- Dashboard, learning (lightgbm), and performance (diskcache, orjson) dependencies now install automatically during `npm install`.

### Fixed
- **Warmup reliability** — increased subprocess timeout from 60s to 180s for first-time model download. Added step-by-step progress output and direct in-process import diagnostics when worker fails.
- **Mode B default model** — changed from `phi3:mini` to `llama3.2` to match `provider_presets()` and reduce first-time setup friction.
- **postinstall.js** — now installs all 5 dependency groups (core, search, dashboard, learning, performance) with clear status messages per group.
- **Error messages** — all embedding worker failures, engine fallbacks, and dashboard errors now suggest `slm doctor` for diagnosis.
- **pyproject.toml** — added `diskcache` and `orjson` to core dependencies; aligned optional dependency versions with core.

---

## [3.0.31] - 2026-03-21

### Fixed
- Profile switching and display uses correct identifiers
- Profile sync across CLI, Dashboard, and MCP — all entry points now see the same profiles
- Profile switching now persists correctly across restarts
- Resolve circular import in server module loading

---

## [2.8.6] - 2026-03-06

### Fixed
- Environment variable support across all CLI tools
- Multi-tool memory database sharing

### Contributors
- Paweł Przytuła (@pawelel) - Issue #7 and PR #8

---

## [2.8.3] - 2026-03-05

### Fixed
- Windows installation and cross-platform compatibility
- Database stability under concurrent usage
- Forward compatibility with latest Python versions

### Added
- Full Windows support with PowerShell scripts for all operations
- `slm attribution` command for license and creator information

### Improved
- Overall reliability and code quality
- Dependency management for reproducible installs

---

## [2.8.2] - 2026-03-04

### Fixed
- Windows compatibility for repository cloning (#7)
- Updated test assertions for v2.8 behavioral feature dimensions

---

## [2.8.0] - 2026-02-26

**Release Type:** Major Feature Release — "Memory That Manages Itself"

SuperLocalMemory now manages its own memory lifecycle, learns from action outcomes, and provides enterprise-grade compliance — all 100% locally on your machine.

### Added
- **Memory Lifecycle Management** — Memories automatically organize themselves over time based on usage patterns, keeping your memory system fast and relevant
- **Behavioral Learning** — The system learns what works by tracking action outcomes, extracting success patterns, and transferring knowledge across projects
- **Enterprise Compliance** — Full access control, immutable audit trails, and retention policy management for GDPR, HIPAA, and EU AI Act
- **6 New MCP Tools** — `report_outcome`, `get_lifecycle_status`, `set_retention_policy`, `compact_memories`, `get_behavioral_patterns`, `audit_trail`
- **Improved Search** — Lifecycle-aware recall that automatically promotes relevant memories and filters stale ones
- **Performance Optimized** — Real-time lifecycle management and access control

### Changed
- Enhanced ranking algorithm with additional signals for improved relevance
- Improved search ranking using multiple relevance factors
- Search results include lifecycle state information

### Fixed
- Configurable storage limits prevent unbounded memory growth

---

## [2.7.6] - 2026-02-22

### Improved
- Documentation organization and navigation

---

## [2.7.4] - 2026-02-16

### Added
- Per-profile learning — each profile learns its own preferences independently
- Thumbs up/down and pin feedback on memory cards
- Learning data management in Settings (backup + reset)
- "What We Learned" summary card in Learning tab

### Improved
- Smarter learning from your natural usage patterns
- Recall results improve automatically over time
- Privacy notice for all learning features
- All dashboard tabs refresh on profile switch

---

## [2.7.3] - 2026-02-16

### Improved
- Enhanced trust scoring accuracy
- Improved search result relevance across all access methods
- Better error handling for optional components

---

## [2.7.1] - 2026-02-16

### Added
- **Learning Dashboard Tab** — View your ranking phase, preferences, workflow patterns, and privacy controls
- **Learning API** — Endpoints for dashboard learning features
- **One-click Reset** — Reset all learning data directly from the dashboard

---

## [2.7.0] - 2026-02-16

**Release Type:** Major Feature Release — "Your AI Learns You"

SuperLocalMemory now learns your patterns, adapts to your workflow, and personalizes recall. All processing happens 100% locally — your behavioral data never leaves your machine.

### Added
- **Adaptive Learning System** — Detects your tech preferences, project context, and workflow patterns across all your projects
- **Personalized Recall** — Search results automatically re-ranked based on your learned preferences. Gets smarter over time.
- **Zero Cold-Start** — Personalization works from day 1 using your existing memory patterns
- **Multi-Channel Feedback** — Tell the system which memories were useful via MCP, CLI, or dashboard
- **Source Quality Scoring** — Learns which tools produce the most useful memories
- **Workflow Detection** — Recognizes your coding workflow sequences and adapts retrieval accordingly
- **Engagement Metrics** — Track memory system health locally with zero telemetry
- **Isolated Learning Data** — Behavioral data stored separately from memories. One-command erasure for full GDPR compliance.
- **3 New MCP Tools** — Feedback signal, pattern transparency, and user correction
- **2 New MCP Resources** — Learning status and engagement metrics
- **New CLI Commands** — Learning management, engagement tracking, pattern correction
- **New Skill** — View learned preferences in Claude Code and compatible tools
- **Auto Python Installation** — Installer now auto-detects and installs Python for new users

---

## [2.6.5] - 2026-02-16

### Added
- **Interactive Knowledge Graph** — Fully interactive visualization with zoom, pan, and click-to-explore
- **Mobile & Accessibility Support** — Touch gestures, keyboard navigation, and screen reader compatibility

---

## [2.6.0] - 2026-02-15

**Release Type:** Security Hardening & Scalability — "Battle-Tested"

### Added
- **Rate Limiting** — Protection against abuse with configurable thresholds
- **API Key Authentication** — Optional authentication for API access
- **CI Workflow** — Automated testing across multiple Python versions
- **Trust Enforcement** — Untrusted agents blocked from write and delete operations
- **Advanced Search Index** — Faster search at scale with graceful fallback
- **Hybrid Search** — Combined search across multiple retrieval methods
- **SSRF Protection** — Webhook URLs validated against malicious targets

### Improved
- Higher memory graph capacity with intelligent sampling
- Hardened profile isolation across all queries
- Bounded resource usage under high load
- Optimized index rebuilds for large databases
- Sanitized error messages — no internal details leaked
- Capped resource pools for stability

---

## [2.5.1] - 2026-02-13

**Release Type:** Framework Integration — "Plugged Into the Ecosystem"

### Added
- **LangChain Integration** — Persistent chat history for LangChain applications
- **LlamaIndex Integration** — Chat memory storage for LlamaIndex
- **Session Isolation** — Framework memories tagged separately from normal recall

---

## [2.5.0] - 2026-02-12

**Release Type:** Major Feature Release — "Your AI Memory Has a Heartbeat"

SuperLocalMemory transforms from passive storage to active coordination layer. Every memory operation now triggers real-time events.

### Added
- **Reliable Concurrent Access** — No more "database is locked" errors under multi-agent workloads
- **Real-Time Events** — Live event broadcasting across all connected tools
- **Subscriptions** — Durable and ephemeral event subscriptions with filters
- **Webhook Delivery** — HTTP notifications with automatic retry on failure
- **Agent Registry** — Track connected AI agents with protocol and activity monitoring
- **Memory Provenance** — Track who created or modified each memory, and from which tool
- **Trust Scoring** — Behavioral trust signals collected per agent
- **Dashboard: Live Events** — Real-time event stream with filters and stats
- **Dashboard: Agents** — Connected agents table with trust scores and protocol badges

### Improved
- Refactored core modules for reliability and performance
- Dashboard modernized with modular architecture

---

## [2.4.2] - 2026-02-11

### Fixed
- Profile isolation bug in dashboard — graph stats now filter by active profile

---

## [2.4.1] - 2026-02-11

### Added
- **Hierarchical Clustering** — Large knowledge clusters auto-subdivided for finer-grained topic discovery
- **Cluster Summaries** — Structured topic reports for every cluster in the knowledge graph

---

## [2.4.0] - 2026-02-11

**Release Type:** Profile System & Intelligence

### Added
- **Memory Profiles** — Single database, multiple profiles. Switch instantly from any IDE or CLI.
- **Auto-Backup** — Configurable automatic backups with retention policy
- **Confidence Scoring** — Statistical confidence tracking for learned patterns
- **Profile Management UI** — Create, switch, and delete profiles from the dashboard
- **Settings Tab** — Backup configuration, history, and profile management
- **Column Sorting** — Click headers to sort in Memories table

---

## [2.3.7] - 2026-02-09

### Added
- `--full` flag to show complete memory content without truncation
- Smart truncation for large memories

### Fixed
- CLI `get` command now retrieves memories correctly

---

## [2.3.5] - 2026-02-09

### Added
- **ChatGPT Connector** — Search and fetch memories from ChatGPT via MCP
- **Streamable HTTP Transport** — Additional transport option for MCP connections
- **Dashboard Enhancements** — Memory detail modal, dark mode, export, search score visualization

### Fixed
- Security improvement in dashboard event handling

---

## [2.3.0] - 2026-02-08

**Release Type:** Universal Integration

SuperLocalMemory now works across 16+ IDEs and CLI tools.

### Added
- **Auto-Configuration** — Automatic setup for Cursor, Windsurf, Claude Desktop, Continue.dev, Codex, Copilot, Gemini, JetBrains
- **Universal CLI** — `slm` command works in any terminal
- **Skills Installer** — One-command setup for supported editors
- **Tool Annotations** — Read-only, destructive, and open-world hints for all MCP tools

---

## [2.2.0] - 2026-02-07

**Release Type:** Feature Release — Advanced Search

### Added
- **Advanced Search** — Faster, more accurate search with multiple retrieval strategies
- **Query Optimization** — Spell correction, query expansion, and technical term preservation
- **Search Caching** — Frequently-used queries return near-instantly
- **Combined Search** — Results fused from multiple search methods for better relevance
- **Fast Vector Search** — Sub-10ms search at scale (optional)
- **Local Embeddings** — Semantic search with GPU acceleration (optional)
- **Modular Installation** — Install only what you need: core, UI, search, or everything

---

## [2.1.0-universal] - 2026-02-07

**Release Type:** Major Feature Release — Universal Integration

### Added
- **6 Universal Skills** — remember, recall, list-recent, status, build-graph, switch-profile
- **MCP Server** — Native IDE integration with tools, resources, and prompts
- **Attribution Protection** — Multi-layer protection ensuring proper credit
- **11+ IDE Support** — Cursor, Windsurf, Claude Desktop, Continue.dev, Cody, Aider, ChatGPT, Perplexity, Zed, OpenCode, Antigravity

---

## [2.0.0] - 2026-02-05

### Initial Release — Complete Rewrite

SuperLocalMemory V3 represents a complete architectural rewrite with intelligent knowledge graphs, pattern learning, and enhanced organization.

### Added
- **4-Layer Architecture** — Storage, Hierarchical Index, Knowledge Graph, Pattern Learning
- **Automatic Entity Extraction** — Discovers key topics and concepts from your memories
- **Intelligent Clustering** — Automatic thematic grouping of related memories
- **Pattern Learning** — Tracks your preferences across frameworks, languages, architecture, security, and coding style
- **Storage Optimization** — Progressive compression reduces storage by up to 96%
- **Profile Management** — Multi-profile support with isolated data

---

## Versioning

We use [Semantic Versioning](https://semver.org/):
- **MAJOR:** Breaking changes (e.g., 2.0.0 → 3.0.0)
- **MINOR:** New features (backward compatible, e.g., 2.0.0 → 2.1.0)
- **PATCH:** Bug fixes (backward compatible, e.g., 2.1.0 → 2.1.1)

**Current Version:** v3.3.0
**Website:** [superlocalmemory.com](https://superlocalmemory.com)
**npm:** `npm install -g superlocalmemory`

---

## License

SuperLocalMemory V3 is released under the [Elastic License 2.0](LICENSE).

---

**100% local. 100% private. 100% yours.**
