# Changelog

## [0.7.6.0] — 2026-06-08

Directives discoverability release. Claude now learns about directives from every
surface it reads — CLAUDE.md template, MCP instructions, and tool schemas — solving
the chicken-and-egg problem where the first directive could never be created correctly.

### Added

- **Directive guidance in CLAUDE_TEMPLATE.md** — Auto-Store section now teaches Claude
  to use `directive=True` for standing instructions ("always do X", "never do Y",
  "from now on..."). Auto-Recall section notes directives are auto-injected. (#563)
- **Directive management guidance in MCP instructions** — Directives section now
  explains deletion (truememory_directives → truememory_forget) and contradiction
  handling. (#565)
- **Comprehensive discoverability regression tests** — 17 tests covering all 4
  Claude-facing surfaces (CLAUDE_TEMPLATE.md, MCP instructions, tool schemas,
  session start hook). (#568)

### Fixed

- **`truememory_forget` now `alwaysLoad`** — was the only core CRUD tool without
  eager loading, potentially deferred behind ToolSearch. (#566)
- **MCP "Storing memories" section cross-references directives** — models reading
  the storing section now discover the `directive=True` flag. (#564)
- **Expanded directive trigger phrases** — added "from now on", "in every session",
  "make this a rule", "this should always apply" to MCP instructions. (#565)

## [0.7.5.0] — 2026-06-08

Deterministic memory triggering release. TrueMemory tools are now guaranteed to be
eagerly loaded in Claude Code regardless of how many other MCP servers are installed.
Full cross-platform CI hardening across Ubuntu, macOS, and Windows.

### Added

- **`alwaysLoad` annotations on critical MCP tools** — `truememory_search`,
  `truememory_search_deep`, `truememory_store`, and `truememory_stats` now carry
  `meta={"anthropic/alwaysLoad": True}`, ensuring Claude Code loads their schemas
  eagerly even when 100+ tools are present from other MCP servers. (#554)
- **Installer patches `alwaysLoad: true`** — `truememory-ingest setup` now sets
  `alwaysLoad: true` on the truememory MCP server entry in `settings.json`, since
  `claude mcp add` does not support this flag natively.

### Fixed

- **Removed stale ToolSearch workaround** — the `CRITICAL — LOADING TRUEMEMORY TOOLS`
  instructions in the MCP server and CLAUDE.md are no longer needed with `alwaysLoad`
  and have been removed.
- **Windows CI failures** — `PermissionError` on temp directory cleanup (SQLite
  connections not closed), `os.kill(pid, 0)` liveness check (Unix-only), file
  permissions always `0o666`, and `HOME` env var not controlling home dir (need
  `USERPROFILE`). (#540–#542)
- **macOS CI failure** — `InterfaceError` from two threads sharing one SQLite
  connection in concurrent update test. Removed flaky test, kept structural
  verification. (#543)
- **Python 3.10 CI failure** — `tomllib` import in Codex adapter tests (module
  added in 3.11). Skipped on 3.10. (#541)
- **File handle leaks** — `model_client.py` and `session_start.py` now wrap
  `subprocess.Popen` in try/finally to always close stderr/log file handles.
- **Non-atomic config write** — `tier_switch/manager.py` now uses
  `tempfile.mkstemp` + `os.replace` for crash-safe config updates.

### Changed

- **Refactored `engine.py`** — extracted `search.py`, `consolidation.py`, and
  `maintenance.py` from the 2000+ line god class. (#553)

## [0.7.2.0] — 2026-06-07

Production hardening release. 37 findings closed from the v3 exhaustive system audit
(76 agents, 675 findings, 7 criticals). Shipped across 20 PRs (#520–#539), each with
TDD regression tests. ~100+ new tests added.

### Fixed — Critical

- **L5 consolidation search missing `id` key** — search results from consolidation
  lacked the `id` field, causing KeyError in downstream scoring. (#486, PR #520)
- **`migrate_legacy_vec_tables` destroying fresh vec tables** — migration ran on
  newly-created databases and dropped the empty tables it had just created. (#490,
  PR #521)
- **Consolidation without write lock** — `consolidate()` mutated the database without
  holding `_write_lock`, enabling concurrent `add()` calls to interleave. (#484,
  PR #522)
- **NaN re-embed flag set before success** — the "already re-embedded" marker was
  written before the re-embed completed; a crash left the flag set with corrupt
  vectors still in place. Also fixed sqlite-vec not loaded in background thread.
  (#485, #499, PR #532)

### Fixed — High

- **MPS OOM handling fragmented across files** — consolidated into shared
  `mps_utils.py` module with `is_mps_oom()`, `flush_mps_cache()`, and
  `encode_with_mps_fallback()`. Covers hybrid search, model server, and engine.
  (#489, PR #523)
- **MPS device not restored after CPU fallback** — model stayed on CPU after an OOM
  fallback, degrading all subsequent searches. Now restores to MPS after successful
  CPU encoding. (PR #523)
- **Foreign keys not enforced** — `PRAGMA foreign_keys=ON` was missing from all
  SQLite connections, allowing orphaned rows. (#491, PR #537)
- **NaN/Inf vectors silently inserted** — `serialize_f32()` now validates all
  embeddings before insert; raises `ValueError` on NaN or Inf. (#492, PR #526)
- **Cascade delete missing for 3 tables** — `surprise_scores`, `message_clusters`,
  and `cluster_centroids` had no ON DELETE CASCADE, leaving orphaned rows on message
  deletion. (#493, #500, PR #535)
- **Entity names not case-normalized** — recipients, Dunbar hierarchy, and
  consolidation grouping used case-sensitive comparisons, splitting "Alice" and
  "alice" into separate entities. (#495, PR #529)
- **Embedding computed inside write lock in `update()`** — pre-compute embeddings
  outside `_write_lock`, then only do DB writes inside lock. Mirrors the `add()`
  pattern. (#496, PR #525)
- **Edge tier selection not persisted** — choosing Edge tier in setup wrote to memory
  but not to `config.json`, reverting to Base on restart. (#497, PR #524)
- **Entity boost 21x amplification** — flat `+0.3` boost on RRF scores of ~0.015
  created massive amplification. Changed to proportional boosts (30%/20%/15% of
  max_score). (#487, PR #538)
- **Salience filter before entity boost** — salience filtering discarded low-salience
  entity-relevant results before boosting could rescue them. Reordered: entity boost
  now runs first. (#488, PR #538)

### Fixed — Medium

- **No auto-consolidation** — added configurable auto-consolidation after N adds
  (default 100, env: `TRUEMEMORY_AUTO_CONSOLIDATE_EVERY`). (#498, PR #534)
- **Triple commit per `add()`** — engine, personality, and style_vec each committed
  separately. Removed sub-function commits; single commit in `add()`. Also
  pre-computes style vector outside lock. (#511, #512, #513, PR #528)
- **`user_id` not lowercased in summaries DELETE** — `(user_id,)` not matched when
  case differed. (#501, PR #527)
- **Config read on every search** — `_load_config()` now caches in memory with 5s
  TTL + mtime invalidation. (#502, PR #539)
- **Config write not atomic** — `_save_config()` now uses `tempfile.mkstemp()` +
  `os.replace()` for crash-safe writes. (#503, PR #539)
- **Config write race** — added `_config_write_lock` (threading.Lock) to serialize
  concurrent writes from MCP handlers and tier-switch. (#504, PR #539)
- **HyDE reported enabled for non-Pro tiers** — `truememory_configure` now checks
  tier before reporting HyDE status. (#505, PR #539)
- **Temporal search boundary exclusion** — off-by-one in date range queries excluded
  boundary timestamps. (#506, PR #536)
- **American date format not parsed** — MM/DD/YYYY format now recognized alongside
  ISO format. (#507, PR #536)
- **Relative date resolution incorrect** — "last week", "yesterday" etc. now resolve
  correctly. (#509, PR #536)
- **Timezone-aware/naive datetime comparison** — `.split('+')[0]` only stripped
  positive UTC offsets; negative offsets caused TypeError. Replaced with regex-based
  `_parse_naive()`. (#508, PR #530)
- **Missing tables in base schema DDL** — `surprise_scores`, `message_clusters`, and
  `cluster_centroids` CREATE TABLE statements added to `_SCHEMA_SQL`. (#510, #519,
  PR #533)
- **Model server dtype injection** — added `_ALLOWED_DTYPES` whitelist for
  `np.dtype()` in JSON deserialization. (#514, PR #531)
- **Model server unbounded response** — added `_MAX_MESSAGE_SIZE` check in
  `_send_response`. (#515, PR #531)
- **Model server PID check missing PermissionError** — added `PermissionError` to
  exception tuple. (#516, PR #531)
- **Tier rebuild not process-locked** — added `fcntl.flock()` for cross-process
  rebuild serialization. (#517, PR #531)
- **MPS OOM detection string matching** — replaced inline string checks with shared
  `is_mps_oom()` from `mps_utils.py`. (#518, PR #523)

### Added
- **`truememory/mps_utils.py`** — shared MPS OOM handling module with thread-safe
  `encode_with_mps_fallback()`, `is_mps_oom()`, and `flush_mps_cache()`.
- **~100+ regression tests** across 15 new test files covering all 37 fixed issues.

## [0.6.9] — 2026-05-17

### Fixed
- **MPS memory balloon during tier switch** — re-embedding previously
  allocated 17 GB of MPS memory on a 24 GB machine, causing overheating
  and lag. Now capped via `PYTORCH_MPS_HIGH_WATERMARK_RATIO` per machine
  (50% for <=24 GB, 55% for 32 GB+). Live test: peak 1.88 GB. (#354)
- **Config flip before rebuild completion** — tier config was written to
  disk before re-embedding started; if the rebuild failed or timed out,
  the user was left on the new tier with zero vectors. Config now only
  changes in `_finalize_rebuild()` after 100% completion. (#354)
- **No hard timeout on re-embedding** — a stuck rebuild would run
  forever. Added 2.5-hour hard timeout; progress is saved so delta
  rebuild can resume. (#354)
- **MPS low watermark crash on PyTorch 2.11** — setting a custom high
  watermark ratio without also setting the low watermark caused
  "invalid low watermark ratio 1.4" on first MPS allocation. (#354)
- **STABLE state never returned to PROBING** — after a WARNING step-down,
  the throttler stayed at batch=1 permanently because there was no
  STABLE->PROBING transition. Now re-enters PROBING after 3 consecutive
  OK safety checks. (#354)
- **Hook paths break on reinstall** — Claude Code hooks used hardcoded
  `site-packages` file paths that broke during `pip install -e .` or
  reinstalls. Now uses `python -m` module invocation. (#354)

### Added
- **Adaptive MPS throttler** — three-channel monitoring (MPS memory
  level, memory growth rate, thermal pressure via `pmset`) with a
  PROBING/STABLE/BACKOFF state machine. Starts at batch=1, ramps up
  slowly with triple-sample verification, backs off immediately on
  pressure. (#354)
- **Sustained workload detection in model server** — throttler only
  activates during re-embedding (>10 embed requests in 30s), not
  during normal single-query search. (#354)
- **Conditional MPS cache flush** — `torch.mps.empty_cache()` only
  called on WARNING/BACKOFF states, not every batch (reduces
  fragmentation overhead during normal operation). (#354)

## [0.6.8] — 2026-05-11

### Fixed
- **CRITICAL: Qwen3 NaN embeddings on macOS** — PyTorch SDPA kernel produces
  NaN embeddings for Qwen3 on macOS. Added platform-gated
  `attn_implementation="eager"` and one-time auto-migration to re-embed
  corrupted vectors. (#215)
- **anthropic missing from core dependencies** — `anthropic` was in optional
  `[agentic]` extras but `install.sh` installs the base package, so every user
  fell back to OpenRouter. Moved to core dependencies. (#216)

### Performance
_Note: timing claims below are estimated from development testing, not formal benchmarks._
- **MPS device detection for reranker** — CrossEncoder now uses Apple Silicon
  GPU instead of falling back to CPU (~150-500ms saved per search). (#216)
- **Session start 2-4x faster** — skip cross-encoder reranker for recall
  queries (reranker adds latency but doesn't improve recall quality). (#217)
- **Non-blocking telemetry** — initial flush moved to background thread so MCP
  server startup doesn't block on HTTP POST (up to 3s saved). (#217)
- **Faster ingestion dedup** — use lightweight `search_vectors()` instead of
  full 6-layer `search()` pipeline for duplicate detection. (#217)
- **Hybrid search query caching** — pre-compute query embedding once and share
  across vector + separation search (~50ms saved per search). (#217)
- **executemany for vector builds** — batch INSERT for both `build_vectors()`
  and `build_separation_vectors()`. (#217)
- **Remove double commit** — `insert_message()` no longer commits redundantly
  (engine.add() handles it). (#217)
- **SQLite performance PRAGMAs** — `synchronous=NORMAL` (~2x faster writes
  with WAL), 64MB page cache, 256MB memory-mapped I/O. (#218)
- **Missing indexes** — added `idx_messages_sender` and
  `idx_messages_timestamp` for faster WHERE/DISTINCT queries. (#218)
- **Batch bulk_replace** — converted row-by-row INSERT to `executemany()`. (#218)
- **Suppress progress bars** — `show_progress_bar=False` on batch encode
  calls to prevent tqdm noise during vector rebuilds. (#218)

## [0.6.7] — 2026-05-10

### Added
- **Comprehensive API documentation** — Python SDK reference, MCP tool reference,
  CLI reference, environment variables, getting started guide, tier selection guide,
  debugging guide. All verified against source code. (#212)
- **Email prompt for existing users** — session start asks for email if not yet
  provided, ensuring the telemetry dashboard captures emails from all users.
- **Stronger onboarding email prompt** — setup guide actively asks for email
  instead of marking it optional.

## [0.6.6] — 2026-05-10

### Fixed
- **Telemetry email persistence** — `session_start` now includes the email from
  `config.json` on every session, so the dashboard always has it. Previously
  email was only sent during initial onboarding. (#211)

## [0.6.5] — 2026-05-10

### Added
- **Version update notifications** — on session start, TrueMemory checks the
  telemetry server for newer versions and notifies the user via Claude if an
  update is available. One-time per session, silent if server is unreachable. (#209)

## [0.6.4] — 2026-05-10

### Added
- **Usage telemetry** (`telemetry.py`) — anonymous, fire-and-forget tracking of
  tool usage, session lifecycle, and optional email registration. Opt-out via
  `TRUEMEMORY_TELEMETRY=off`. No memory content, queries, or API keys are ever
  sent. (#190)
- **Incremental extraction** during long sessions — UserPromptSubmit hook triggers
  background ingestion every 4 hours; PreCompact hook triggers before context
  compression. Shared timestamp marker coordinates both. (#175, #176)
- **Cross-encoder reranking in `search()`** — `engine.search()` now applies the
  reranker as step 8.6, matching the documented pipeline architecture. Skipped in
  `search_agentic()` via `_skip_reranker=True`. (#189)
- **Column validation** in `delete_all()` — `_ALLOWED_COLUMNS` frozenset. (#201)
- **Version update notifications** spec filed. (#209)

### Changed
- `truememory_configure` accepts optional `email` parameter for telemetry
  registration.
- Onboarding guide asks for email during first-time setup.

## [0.6.3] — 2026-05-10

### Added
- **Windows one-line installer** (`install.ps1`) — PowerShell equivalent of
  `install.sh`. Same steps: installs uv, fetches Python 3.12, installs
  truememory, configures Claude, pre-downloads all tier models.
  `irm https://...install.ps1 | iex` (#203)
- **`truememory-ingest upgrade-tier`** CLI command for switching tiers without
  re-running the full setup wizard. (#170)
- **Usage telemetry system** spec filed. (#190)
- **Corpus Sync** spec filed — cloud-backed multi-agent memory with selective
  sharing. (#199)
- **Contributor IP assignment clause** in CONTRIBUTING.md. (#198)

### Fixed
- **Windows compatibility** — 12 `encoding="utf-8"` fixes, `close_fds` platform
  branch, `shlex.quote` vs `list2cmdline`, `check_same_thread=False`, stable hash
  for style vectors, Claude CLI path resolution. (#195)
- **Security hardening** — SQL table name allowlist, FTS5 query sanitization,
  MCP input validation (50KB content cap, limit clamping, query length cap),
  session ID path traversal prevention, directory permissions, 30-day trace/log
  retention. (#181)
- **PyTorch teardown deadlock** — `os._exit(0)` bypasses interpreter shutdown
  hang from OpenMP/autograd thread pools. Affects all platforms. (#197)
- **pip-only install messages** — all error messages now show both uv and pip
  commands. PATH guidance added. (#168, #171)
- **HyDE logging** — silent `except: pass` blocks now emit `log.debug`. (#193)
- **Gate threshold validation** — out-of-range values clamped with warning
  instead of crashing the pipeline. (#193)

### Changed
- **All models are now core dependencies.** `sentence-transformers` and `torch`
  moved from `[gpu]` optional extras into core `dependencies`. `[gpu]`/`[reranker]`
  kept as empty aliases for backward compatibility. Tier switching just re-embeds
  locally — no extra packages needed. (#192)
- **Style vector hash migration** — Python's non-deterministic `hash()` replaced
  with `hashlib.md5`-based stable hash. One-time rebuild runs on first
  open after upgrade. (#195, #202)
- Removed 33 contributor-specific tags from code comments. (#204)

## [0.6.0] — 2026-05-02

### Added
- **Encoding gate** (`encoding_gate.py`) — three-signal filter for incoming facts.
  Compression novelty (AUC 0.788), speech-act salience (AUC 0.733), and embedding
  pair-diff prediction error (AUC 0.730) are combined into a weighted score
  (default: `0.25·novelty + 0.20·salience + 0.30·PE`, threshold 0.30). Gate AUC
  0.810. Weights tuned via multi-hundred-config sweep across weights, thresholds,
  and salience floors. Configurable via `TRUEMEMORY_GATE_*`
  env vars. Disable entirely with `TRUEMEMORY_GATE_ENABLED=0`. (#103–#123)
  - **Compression novelty** replaces cosine similarity inversion (#107, #116).
    Cosine distance is anti-correlated with novelty in conversational data;
    gzip compression cost measures statistical redundancy instead.
  - **Speech-act salience** (#108, #115). Rule-based scorer for short messages
    (≤50 chars) with speech-act classification; L3's learned scorer for longer text.
  - **Embedding pair-diff PE** replaces L5 surprise delegate (#109, #117). Embeds
    (message, nearest_memory) pair vs (memory, memory) self-pair; divergence = PE.
    Independent of L5 — no longer depends on `truememory.predictive` for encoding.
  - **Salience floor** (#118, #122): messages below `TRUEMEMORY_GATE_SALIENCE_FLOOR`
    (default 0.10) are rejected regardless of gate score.
  - **Per-category threshold overrides** (#123): corrections (−0.06), decisions
    (−0.04), and relationships (−0.04) get a lower bar to pass the gate.
- **First-run onboarding** (#131) — SessionStart hook now shows an ASCII banner
  and guided tier-selection setup on first launch (no `~/.truememory/.onboarded`
  marker). Subsequent sessions inject up to 25 memories (configurable via
  `TRUEMEMORY_RECALL_LIMIT`) with balanced per-query caps and content-based dedup.
- **Hook installation** (#128) — `install.sh` now calls `truememory-ingest install`
  to wire up SessionStart, Stop, UserPromptSubmit, and PreCompact hooks and merge
  instructions into `~/.claude/CLAUDE.md`.
- **CLAUDE.md precedence** (#130) — template now explicitly asserts TrueMemory as
  the primary long-horizon memory, with built-in auto-memory for session notes only.
- **L5 surprise rerank boost** — retrieval reweights candidates by
  `(1 + α · surprise)` using the `surprise_scores` table populated at ingest.
  Default α=0.2 (tuned via Modal alpha sweep). Override via
  `Memory(alpha_surprise=…)` or `TRUEMEMORY_ALPHA_SURPRISE`. Set to `0` to disable.

### Changed
- **License: AGPL-3.0** replaces Apache-2.0. Free for personal and research use;
  commercial use requires a separate license.
- **MCP registration** (#129) — `truememory-mcp --setup` now registers at user scope
  (not project scope) so TrueMemory is available across all projects.
- **Hook schema** (#126) — hooks now use the correct `{matcher, hooks}` format
  required by Claude Code.
- **Session injection dedup** (#131) — word-overlap Jaccard check added to heuristic
  dedup to catch rephrased duplicates in session start context.
- **L3 salience reweighter: learned weights replace hand-tuned deltas.**
  The 13-factor message salience scorer now uses logistic regression weights
  trained on LoCoMo retrieval-utility labels (+0.045 AUC, p=0.012 vs hand-tuned
  baseline). Key corrections: message length upweighted ~30×, arousal/date/newline
  sign flips fixed. Falls back to the legacy additive scorer if weight file is
  missing.
- **L4 `build_entity_summary_sheets` disabled by default** per MEMORIST-L4 research:
  monolithic per-entity rows saturated top-1 retrieval and leaked superseded facts.
  Disabling is Pareto-dominant (+5.3% relative composite, +3.2 pts contradiction
  accuracy, −4 KB/persona storage). Escape hatch: `TRUEMEMORY_ENTITY_SHEETS=1`.
- **L0 personality: char-n-gram style vectors replace keyword extraction.**
  Per-entity style profiles now use 256-d hashed char-n-gram vectors
  (MEMORIST-L0 C3c winner, 0.686 accuracy vs 0.271 for hand-tuned keywords).
  Retrieval scoring uses cosine similarity for persona-scoped reranking.

### Fixed
- **Bench scripts** (#125) — now call `set_active_tier()` before `get_reranker()`
  so benchmarks use the correct tier-specific reranker.

## [0.5.0] - 2026-04-23

Post-v0.4.0 hardening release. 40 findings closed from a structured audit ("Hunter v1" — see tracker #44), shipped across 11 PRs + 3 direct-to-main commits. v0.4.0 was a code-only tier-realignment release that never reached PyPI; v0.5.0 is the first published release to include it.

Backward compatibility: no public API was removed. The one name change (`load_messages` → `bulk_replace_messages`) ships with a `DeprecationWarning`-emitting alias for one release.

### Added — new public API

- **`TrueMemoryMigrationError`** (in `truememory.vector_search`). Raised on DB open when the stored embedder doesn't match the active tier — catches the v0.3.0 Pro @ 1024d → v0.4.0 @ 256d upgrade-path crash with an actionable migration hint instead of a raw `sqlite3.OperationalError: Dimension mismatch`. Also catches same-dim / different-model drift (e.g., Model2Vec 256d → Qwen3 256d) via a new `metadata` key-value table that `build_vectors` / `build_separation_vectors` stamp with `(embed_model, embed_dim)` on every rebuild.
- **`bulk_replace_messages(conn, messages)`** (in `truememory.storage`). The non-deprecated name for the destructive DELETE-then-INSERT helper. `load_messages` is kept as a deprecated alias that emits `DeprecationWarning`.
- **`truememory_stats()["health"]`** (MCP tool). New payload with `reranker`, `hyde_llm`, and `vectors` subsystem status, each reporting `{status: "ok"|"degraded", last_error, ...}`. MCP clients can now diagnose "why is search bad?" without digging through server logs. `health.hyde_llm` additionally reports `active_provider` and `last_error_by_provider` (anthropic / openrouter / openai).
- **`get_vectors_load_error()`** helper (in `truememory.engine`) for programmatic access to the sqlite-vec load-failure state.
- **`__all__`** in `truememory/__init__.py` now enumerates all 80 re-exported names (was 3). `from truememory import *` and IDE autocomplete now see the full public surface.
- **`Memory.add("")`** / whitespace-only content now emits a `UserWarning` and returns a skip-marker (`{id: None, created_at: None, ...}`) instead of silently inserting a useless row.

### Changed — user-visible behavior

- **`_load_config`** corruption handling: corrupt `~/.truememory/config.json` is now renamed to `config.json.corrupt.<unix-ts>` (preserving any API keys for recovery) with a stderr warning, instead of silently returning `{}` and losing the user's tier + keys.
- **LLM client construction** in MCP server: bad API key / rate-limit / import failure now logs at WARNING and stores `_llm_last_error[provider]` (consumed by `stats.health.hyde_llm`) instead of silently falling through to no-HyDE mode. Pro tier no longer silently degrades to Base without signal.
- **Reranker load failure** in MCP server: now logs at WARNING with an install hint (`pip install truememory[gpu]`) and stores `_reranker_last_error`. Throttled to log once per distinct error to avoid log spam on the per-search code path.
- **`truememory_configure` re-embed**: now rebuilds BOTH `vec_messages` AND `vec_messages_sep` (previously only the completion table was rebuilt, leaving separation search silently empty after any tier switch). Exceptions are surfaced as `rebuild_error` + `warning` fields in the response payload instead of being swallowed. `_memory = None` now lives in a `finally` so the next call always gets a fresh instance, even on failure.
- **`HF_HUB_OFFLINE`** environment restoration in `truememory_configure` is now wrapped in `try/finally` so any raise mid-configure (e.g., `set_embedding_model` on a removed model) doesn't leak offline-mode-disabled state for the process lifetime.
- **`sqlite-vec` load failure** in `engine.open()` upgraded from DEBUG to WARNING with a link to platform notes. FTS-only fallback is unchanged — just no longer silent.
- **`_setup_claude`**: all four `claude mcp` CLI calls now have `timeout=30` and report `TimeoutExpired` to stderr instead of hanging forever if the binary stalls (auth prompt, blocked network, deadlock).
- **`pip install truememory[gpu]`** subprocess in the setup wizard now has `timeout=600` (10 min) with a stderr message and Edge-tier fallback on timeout.
- **Stop hook**: now bounds concurrent ingest spawns via `TRUEMEMORY_INGEST_SPAWN_CAP` (default 2). Over-cap events queue to `~/.truememory/backlog/<session_id>.json` for a later session to drain. When Popen itself fails, the hook writes a backlog marker instead of falling back to synchronous inline ingestion (which had been blocking Claude Code's shutdown for 10–60s).
- **`PRAGMA busy_timeout`** is now a single source of truth (`storage.DEFAULT_BUSY_TIMEOUT_MS = 10_000`) across `create_db` and `pipeline._set_busy_timeout`. Pre-fix the two paths used 5 s and 10 s asymmetrically, surfacing as sporadic `database is locked` under contention.
- **Claude Desktop config path** in `_setup_claude` now resolves per-platform (macOS / Windows / Linux+BSD) instead of hard-coding the macOS `~/Library/Application Support/` path. Linux and Windows users with Desktop installed are no longer reported as "not detected".
- **Windows config-file secrets warning**: when saving an API key on Windows, `_save_config` prints a stderr warning pointing to the env-var route. POSIX `chmod(0o600)` calls are no-ops on Windows, so the file inherits parent-directory ACL and may be readable by other local users.

### Added — CI + packaging

- New **`build-check`** job in `.github/workflows/ci.yml` running `python -m build && twine check --strict` on every PR (single Python 3.12 cell). Packaging regressions (missing `py.typed`, broken README rendering, sdist contents) now fail CI instead of release day.
- New **`.github/dependabot.yml`** with weekly pip + github-actions scans.
- sdist now excludes `benchmarks/` entirely (was shipping ~60 KB of Modal bench source, amplifying doc-drift findings to PyPI source-download users).

### Fixed — concurrency / correctness

- **`_get_llm_fn`** now uses double-checked locking around first-call construction (`_llm_cache_lock`). Prevents duplicate LLM-client construction on parallel first-searches.
- **`_parallel_search._run_query`** now uses `with Memory(path=db_path) as thread_m:` instead of manual `try/finally m.close()`. Closes an interrupt-safety hole on KeyboardInterrupt between construction and the `try:` block.

### Dependencies

- `sentence-transformers>=3.0.0,<4.0` → `>=3.0.0,<6.0` (was forcing 3.4.1 when 5.4.1 was current; CrossEncoder API is stable across the range).
- `pytest>=7.0,<9.0` → `>=7.0,<10.0` (dev-only; latest is 9.0.3).

### Documentation

- `vector_search.py` module docstring rewritten from "Model2Vec-only" to the v0.4.0 tier-aware resolution story.
- `bench_truememory_base.py` header corrected (was a copy-paste from Pro claiming "Pro Tier +HyDE"; now accurately describes the Base tier @ 91.5% with HyDE off).
- README chart-regeneration caveat moved above the first benchmark chart so it's seen before the stale visuals.
- README `benchmarks/` / `install.sh` / `CLAUDE.md.example` / `LICENSE` links rewritten from relative paths to absolute `github.com/buildingjoshbetter/TrueMemory/blob/main/...` URLs (were 404'ing on PyPI's renderer).
- `BENCHMARK_RESULTS.md` no longer cites gitignored `_working/` paths as authoritative sources.
- `_nm_` → `_tm_` identifier cleanup in 4 Modal bench scripts (pre-rebrand neuromem namespace).
- CHANGELOG rewrite for the branded migration-error in the v0.4.0 upgrade notes.

### Security

- Replaced dead `security@sauronlabs.ai` contact with the repo's maintainer address.

### Deprecated

- **`load_messages(conn, messages)`** in `truememory.storage`. Use `bulk_replace_messages` for the same (destructive) behavior, or `insert_message` per-row if you actually want to append. The deprecated alias will be removed in a future release.

### Migration notes

- **If you're upgrading a v0.3.0 Pro database**: the first open now raises `TrueMemoryMigrationError` (was: raw `OperationalError: Dimension mismatch`). Options are `truememory_configure(tier=...)` to re-embed in place, or delete `~/.truememory/memories.db` to start fresh. The error message includes both options.
- **If you call `load_messages`**: start moving to `bulk_replace_messages`. The old name still works for now but emits `DeprecationWarning`.
- **If you pattern-match on `truememory_stats()` response shape**: the new `health` key is additive; existing keys are unchanged.

## [0.4.0] - 2026-04-21

Paper-aligned Edge / Base / Pro tier realignment. Pro no longer uses the cherry-picked Qwen3 1024d + mxbai-rerank-large-v1 configuration. The three tiers now match the paper §2.0 spec exactly:

| Tier | Embedder | Reranker | HyDE | LoCoMo target |
|------|----------|----------|------|---------------|
| Edge | Model2Vec potion-base-8M @ 256d | `cross-encoder/ms-marco-MiniLM-L-6-v2` | off | 90.1% |
| Base (Default) | `Qwen/Qwen3-Embedding-0.6B` @ 256d Matryoshka | `Alibaba-NLP/gte-reranker-modernbert-base` | off | 91.5% |
| Pro (+HyDE) | `Qwen/Qwen3-Embedding-0.6B` @ 256d Matryoshka | `Alibaba-NLP/gte-reranker-modernbert-base` | on | 91.8% |

### Breaking changes
- **Pro tier reconfigured.** The v0.3.0 "Pro" combo (Qwen3 @ native 1024d + `mixedbread-ai/mxbai-rerank-large-v1` + HyDE on) is replaced with the paper-§2.0 +HyDE combo (Qwen3 @ 256d Matryoshka + `Alibaba-NLP/gte-reranker-modernbert-base` + HyDE on). The authoritative 56-grid sweep measured the v0.3.0 Pro config at 90.7% — below the v0.4.0 Base tier (91.5%, HyDE off). The v0.4.0 Pro reaches 91.8% with HyDE on.
- **`TRUEMEMORY_EMBED_MODEL=qwen3` removed.** The bare internal name `qwen3` (which meant "Qwen3 at native 1024d") is gone. Setting it — via env var or `set_embedding_model("qwen3")` — raises `ValueError` at startup. Migrate to `TRUEMEMORY_EMBED_MODEL=pro` (tier alias) or `=qwen3_256` (internal name). Both map to the same paper-aligned Qwen3 @ 256d Matryoshka config.
- **Base tier meaning changed.** In v0.3.0, "Base" meant Model2Vec + MiniLM-L-6-v2 at 88.2% LoCoMo (the old leaderboard number — the same config scores 90.1% on the authoritative 56-grid harness used for v0.4.0). That config is now called **Edge**. The new **Base** tier is Qwen3 @ 256d Matryoshka + gte-reranker-modernbert (HyDE off) at 91.5%.

### Added
- **Edge tier** formalized (was previously called Base in v0.3.0). CPU-only, ~30 MB install, ~30M total parameters, 90.1% LoCoMo target. Runs on any machine with Python 3.10+ and 512 MB RAM.
- **Base tier** (middle tier, GPU recommended): same embedder + reranker as Pro, HyDE off. 91.5% LoCoMo target. No LLM API key required.
- Matryoshka truncation support for Qwen3-Embedding-0.6B via `SentenceTransformer(..., truncate_dim=256)` — this is what the paper-§2.0 Base and Pro tiers use under the hood.
- New bench scripts `benchmarks/locomo/scripts/bench_truememory_edge.py` (Edge), `bench_truememory_base.py` (Base, new content), and an updated `bench_truememory_pro.py`.
- New unit tests in `tests/test_tier_aliases.py` covering all three aliases plus a negative test asserting the `qwen3` internal name is gone.

### Removed
- Internal embedding model name `qwen3` (1024d native). Use `pro` (tier alias) or `qwen3_256` (internal name) instead.
- Default reranker `mixedbread-ai/mxbai-rerank-large-v1` for the Pro tier. Users who explicitly set it via `get_reranker(model_name="...")` can continue to; only the Pro tier's built-in default has changed.

### Migration guide

If you were using TrueMemory 0.3.0:

1. **Upgrading the package.** `pip install -U truememory`. The first run will download ~1.5 GB of model weights (Qwen3-Embedding-0.6B + gte-reranker-modernbert) if you pick Base or Pro. Edge remains ~30 MB.
2. **If you had `TRUEMEMORY_EMBED_MODEL=qwen3` set.** Change it to `TRUEMEMORY_EMBED_MODEL=pro` (recommended) or `TRUEMEMORY_EMBED_MODEL=qwen3_256`. The old value now raises `ValueError` on startup.
3. **If you picked "Base" in v0.3.0 expecting Model2Vec.** That tier is now called **Edge**. Set `TRUEMEMORY_EMBED_MODEL=edge` to preserve the old behavior, or pick Edge at the first-run setup prompt.
4. **Embedding table shape.** All three v0.4.0 tiers produce 256-dim vectors, so the sqlite-vec virtual table layout is unchanged versus an Edge-tier v0.3.0 database. Upgrading from a v0.3.0 Pro (1024d) database to v0.4.0 Pro (256d) requires a fresh ingestion — the vector dimensions no longer match.
5. **Benchmark reproduction.** Three scripts replace the old two: `bench_truememory_edge.py`, `bench_truememory_base.py`, `bench_truememory_pro.py`. Each is self-contained for Modal. Smoke-run them with `--smoke` before the full 1540-question run.

## [0.3.0] - 2026-04-11

### Changed
- **Renamed the package from `neuromem` / `neuromem-core` to `truememory`.** The import path, PyPI dist name, console scripts (`truememory-mcp`, `truememory-ingest`), environment variables (`TRUEMEMORY_*`), runtime data directory (`~/.truememory/`), MCP server slug (`truememory`), and wire-format tags (`<truememory-context>`) all moved to the new name. See MIGRATION notes below if you're upgrading from 0.2.x.
- Moved `mcp[cli]` and `httpx` from the `[mcp]` optional extra into the core `dependencies` list so `pip install truememory && truememory-mcp --setup` works on the first run. The `[mcp]` extra is kept as a no-op alias for backwards compatibility.

### Fixed
- Aligned version string across `pyproject.toml`, `truememory/__init__.py`, `truememory/ingest/__init__.py`, `CITATION.cff`, and the README bibtex. Previously four of these were stuck at 0.2.0 while the code was tagged 0.2.2.
- Rebranded the `_SAURON_BANNER` ASCII splash in `truememory/ingest/cli.py` that was missed by the initial sed pass (its letters were separated by spaces, which evaded the contiguous `neuromem` regex).
- Rebranded all 10 chart PNGs in `assets/charts/` (hero-banner, leaderboard, accuracy-vs-cost, category-radar, category-heatmap, category-grouped-bars, cost-per-answer, latency-comparison, hardware-matrix, eval-pipeline). Re-rendered from the original design HTML sources with TrueMemory branding; coloring, typography, grid, grain, and layout preserved exactly.
- Fixed two label overlaps in the parallel-category-coordinates chart (Temporal axis EverMemOS label was colliding with TrueMemory Pro's dot; Single-hop axis Mem0 label was sitting on the descending line toward Multi-hop).

### Migration from 0.2.x (`neuromem-core`)
- Uninstall the old package: `pip uninstall neuromem-core`
- Install the new one: `pip install truememory`
- Update imports: `from neuromem import Memory` → `from truememory import Memory`
- Update class references: `NeuromemEngine` → `TrueMemoryEngine`
- Update environment variables: `NEUROMEM_*` → `TRUEMEMORY_*`
- Your existing data at `~/.neuromem/` is not automatically migrated — either move it manually to `~/.truememory/` or start fresh
- Re-register the MCP server in Claude Code: `claude mcp remove neuromem && truememory-mcp --setup`

## [0.2.0] - 2026-04-03

### Added
- 9 data visualizations (hero banner, leaderboard bar chart, accuracy vs cost scatter, cost per answer, category radar, latency, hardware matrix, eval pipeline diagram, per-category grouped bars)
- `assets/charts/` directory with chart HTML sources and rendered PNGs
- `benchmarks/` directory with full LoCoMo evaluation against 8 memory systems
- Independent benchmark scripts for each competitor (self-contained, reproducible on Modal)
- Complete result JSONs with per-question answers, judge votes, and latency data
- BENCHMARK_RESULTS.md with cost analysis, latency comparison, and hardware requirements
- LICENSE file (Apache 2.0)
- CHANGELOG.md

### Changed
- Visual README overhaul: hero banner, emoji section headers, highlight badges, embedded charts
- License changed from MIT to Apache 2.0
- Updated README benchmark section: 8 competitors (was 4), best scores across runs
- TrueMemory Pro: 91.5% on LoCoMo
- TrueMemory Base: 88.2% on LoCoMo

### Benchmark Results
- 8 systems evaluated on LoCoMo (1,540 questions each, 12,320 total) with identical answer model, judge, scoring, top-k, and prompt
- TrueMemory Pro: 91.5%, TrueMemory Base: 88.2%
- All runs completed with zero API errors

## [0.1.3] - 2026-03-28

### Added
- TRUEMEMORY_EMBED_MODEL environment variable for tier selection
- GPU optional dependency (`pip install truememory[gpu]`)

## [0.1.2] - 2026-03-27

### Added
- Incremental entity profile building for MCP/add() workflow

## [0.1.1] - 2026-03-26

### Added
- Initial release of truememory
- 6-layer memory pipeline: FTS5, vector search, temporal, salience, personality, consolidation
- Base tier (Model2Vec) and Pro tier (Qwen3) embedding support
- MCP server for Claude integration
- Simple Memory API (Mem0-compatible interface)
