Skip to content

v0.23 release documentation: comprehensive change review (v0.22.1 → main) #1243

Description

@phernandez

v0.23 Comprehensive Change Review (v0.22.1 → main)

Scope: 227 commits, 141 merged PRs (2026-06-14 → 2026-08-12), ~80 closed v0.23-milestone issues,
5 new Alembic migrations, 28 added config fields, 2 new CLI command groups, 1 added + 3 removed
MCP tools. CHANGELOG.md's Unreleased section currently covers only the bm hook work (#997) and
two maintenance notes — nearly everything below still needs changelog entries.


Headline themes

1. Indexing & persistence concurrency overhaul

The write path was rebuilt around optimistic versioning instead of pessimistic locks. Accepted
note writes now persist observations and relations immediately instead of waiting for a later
file re-index (#1079/#1076), materialization publishes via a db_version compare-and-swap with
no SELECT FOR UPDATE (#1227/#1224), and relation and observation projections are
generation-versioned so a stale indexing pass can never clobber a newer write (#1220/#1213,
#1228/#1214). Two concrete deadlock families are gone: Entity↔NoteContent lock-order inversions
during materialization (#1193/#1187) and Entity/Observation inversions between indexing and
accepted writes (#1202/#1199). Relation resolution is batched (#1204/#1201, #1132), durable, and
retryable (#1165/#1163), moves leave a durable "vacate" marker so a byte-identical copy is not
mistaken for a lingering move source (#1152, #1160), and a one-time migration repairs duplicate
observation rows plus their orphaned FTS entries (2d26b287813b). Net user effect: no more index
deadlocks or silently missing observations/relations under concurrent agent write load.

2. Semantic search matures: reranking, pluggable vector indexes, honest degradation

Search gained an opt-in cross-encoder rerank stage (#1143, closing #950/#618/#666): local
FastEmbed ONNX by default (jinaai/jina-reranker-v1-tiny-en) or LiteLLM API rerankers
(Cohere/Jina/Voyage), off by default, with a full test/quality harness (#1232, #1233, #1231).
The vector index became pluggable (SPEC-81, #1141) with a first-party Milvus/Milvus Lite/Zilliz
adapter (#1158, #1185) behind the new basic-memory[milvus] extra, tracked by a new
index-identity/readiness manifest so vector search can tell "no ready index" from "no results".
Embedding correctness holes closed: file-watcher writes are now embedded (#1016), non-bge
FastEmbed models are L2-normalized (#1023), LiteLLM gained api_base/api_key (#1043/#1005) and
asymmetric-model text prefixes (#1044/#1008). Honesty work: bm reindex --embeddings now fails
loudly and prints which index it fed (#1240/#1237, #1190/#1184), and SQLite FTS finally searches
full note content past ~6000 chars (#1071/#1065).

3. Operator surface: bm config, diagnostics, readable output, local Postgres

Day-to-day operation got a real front door: a bm config group for
get/set/list/unset with validation (#1088/#991), Rich human-readable output for interactive
bm tool commands with a cli_output_style setting and --plain/--json overrides
(#967/#678), a basic_memory_diagnostics MCP tool reporting version/system info (#963/#187),
and a redesigned bm status built on project index status (#1002). The local Postgres backend
is now actually usable (migrations, pooling, default project — #1018), SQLite gains four tunable
pragmas, and bm doctor never prints a blank failure (#1058) nor trips over existing event
loops (#1094/#1027).

4. Harness capture & plugins: bm hook, Codex/Claude health, skills

SPEC-55 landed: a bm hook front door (#1070/#997) moves plugin hook logic into the package —
session-start/pre-compact/stop lifecycle verbs, default-on bounded envelope capture into a
local WAL inbox, bm hook flush|status|install|remove, and captureEvents: false to opt out.
Claude Code and Codex plugin hooks are now zero-logic PEP 723 uv scripts. Plugins surface
capture setup and health (#1119/#1117), Claude hooks fall back to user-level ~/.claude
settings (#924), and Codex got reliable hooks/notes (#1123), post-compaction checkpoints
(#1138, #1142), and directly resumable checkpoints (#1147). New skills: bm-writing (#1124),
bm-decide/bm-orient (#1126), memory-onboarding (#1050).

5. Cloud & teams: sharing, retention, read caching

bm cloud share create|list|update|revoke lands (#965/#880). One-way cloud sync now removes
newly-.bmignored files from the cloud, with a new bm cloud prune command (#1061/#1032) —
closing the "no supported deletion path" retention gap, and a destructive behavior change worth
calling out. Cloud project deletion errors are surfaced with an optional notes purge
(#1062/#1033/#1034) and a visible deletion job (#1074); re-adding a retained cloud project
reindexes its existing notes (#1085/#1084). Optional Redis read caching accelerates standalone
MCP reads (#1168, #1172/#980) via the basic-memory[redis] extra.

6. MCP tool surface quality & correctness long tail

First-connect onboarding gives new users server instructions, empty-state guidance, and a
getting_started prompt (#1145), while canvas, cloud_info, and release_notes tools were
removed (#1111, #1145). Resolution got strict and honest: ambiguous identifiers fail loud
instead of picking the wrong note (#1151/#1148), entity vs wikilink resolution are separated
(#1192/#1170), and note-type filters are canonicalized (#1189/#1180). edit_note gained a
metadata param (#1090/#1011) and level-aware replace_section (#1063/#1012);
list_directory is bounded and paginated (#1082/#1048); external_id is exposed across
listing, activity, and search output (#1040, #1101, #1103). The server moved to FastMCP 4 beta
and MCP SDK v2 (#1198). The long tail closes real data bugs: duplicate notes from filename-case
variants (#1081/#1077), phantom notes from memory:// edits (#1073/#1066), double frontmatter
blocks (#1188/#1171), and junk observation categories from transcripts and checkboxes
(#1239/#1219/#1241).


Per-theme change lists

Indexing & persistence concurrency

Semantic search & retrieval

Operator surface (CLI & config)

MCP tool surface

Harness capture & plugins

Cloud, teams & performance

Correctness long tail & maintenance


Breaking changes & migration notes

CLI surface (what a v0.22.1 user notices)

MCP surface

HTTP API

Config file

  • sync_delayindex_delay, sync_changesindex_changes (refactor(core): add shared runtime orchestration #1002). Legacy config.json
    keys and legacy BASIC_MEMORY_SYNC_* env vars are auto-migrated; new names win when both
    are present. No user action needed.
  • ⚠️ sync_thread_pool_size and sync_max_concurrent_files were removed with no alias;
    configs that set them are silently ignored (extra="ignore"). The closest replacement knob
    is materialization_workers. Worth a release-note line.

Behavior a v0.22.1 user must know

Required / recommended upgrade actions

  1. Migrations run automatically on first start (5 new revisions, see below). The observation
    dedupe migration is a one-time data repair; back up memory.db if paranoid — files remain
    source of truth and the index is rebuildable.
  2. If you switch semantic_vector_index (e.g. pgvector → milvus), run
    bm reindex --embeddings — until then vector search returns nothing and hybrid silently
    serves FTS-only (Vector search silently degrades when configured index has no ready manifest rows #1236, Milvus + local-Postgres documentation gaps from tire-kick session #1238). Incremental reindex suffices.
  3. Milvus requires pip install basic-memory[milvus]; Redis caching requires
    basic-memory[redis].
  4. Reranking is opt-in: bm config set reranker_enabled true (first use downloads the model).

New configuration surface

All keys settable via bm config set <key> <value> (#1088) or BASIC_MEMORY_<KEY> env vars.

Key Default What it does
reranker_enabled false Cross-encoder reranking of vector/hybrid candidates (#1143). Off: adds latency + first-run model download; requires semantic search.
reranker_provider fastembed fastembed (local ONNX cross-encoder) or litellm (Cohere/Jina/Voyage/etc. via API).
reranker_model jinaai/jina-reranker-v1-tiny-en Reranker model id; provider/model form for litellm (e.g. cohere/rerank-v3.5).
reranker_candidates 20 Top retrieval candidates rescored before returning the page; larger widens recall at latency cost.
reranker_max_document_chars 0 Char cap per candidate passed to the cross-encoder; 0 sends full matched text.
reranker_timeout 30.0 Max seconds per LiteLLM rerank request (FastEmbed ignores it).
reranker_api_base None Custom API base for litellm reranker (self-hosted rerank endpoints).
reranker_api_key None API key for the litellm reranker; env-var resolution when unset.
semantic_vector_index pgvector Postgres vector backend: pgvector or milvus (SPEC-81, #1141). SQLite always uses sqlite-vec.
milvus_uri None Milvus / Milvus Lite / Zilliz Cloud connection URI (#1158).
milvus_token None Optional Milvus/Zilliz auth token.
milvus_timeout_seconds 30.0 Per-operation Milvus client timeout.
milvus_collection_prefix basic_memory Prefix for project-isolated Milvus collections.
milvus_database default Milvus database name (ignored by Milvus Lite — see #1238).
semantic_embedding_api_base None Custom API base for LiteLLM embeddings — OpenAI-compatible local/self-hosted servers (#1043).
semantic_embedding_api_key None API key passed directly to LiteLLM embeddings; env vars still work when unset (#1043).
semantic_embedding_document_prefix None Literal prefix prepended to indexed chunks for prefix-sensitive asymmetric models (#1044).
semantic_embedding_query_prefix None Literal prefix prepended to queries for asymmetric models (#1044).
redis_url None Optional Redis URL enabling standalone MCP read caching (#1168/#1172).
redis_max_connections 20 Redis connection cap for the read cache.
materialization_workers 4 In-process workers materializing accepted note writes off the accept path; bounds DB-writer contention (local runtime).
sqlite_synchronous NORMAL SQLite PRAGMA synchronous (OFF/NORMAL/FULL/EXTRA); NORMAL is safe with WAL.
sqlite_mmap_size 268435456 (256 MB) PRAGMA mmap_size in bytes; 0 disables mmap I/O.
sqlite_wal_autocheckpoint 1000 PRAGMA wal_autocheckpoint in pages; higher = fewer writer stalls, larger WAL.
sqlite_page_size 4096 PRAGMA page_size; only effective on a fresh DB or after VACUUM.
index_delay 1000 Ms to wait after file changes before indexing. Rename of sync_delay (auto-migrated).
index_changes true Real-time indexing of local file changes. Rename of sync_changes (auto-migrated).
cli_output_style rich bm tool TTY output style: rich or plain; per-invocation --json/--plain override (#967).

Removed without replacement or alias: sync_thread_pool_size, sync_max_concurrent_files
(⚠️ silently ignored if present in config.json).


Database migrations

Five new revisions since v0.22.1, applied automatically in order:

  1. o8j9k0l1m2n3_add_note_file_vacate_table — creates note_file_vacate, a durable proof that
    a source path was vacated by a move. Lets the indexer distinguish a move's lingering source
    object (skip) from a legitimate byte-identical copy (index as new) (fix(core): gate move-orphan skip on a durable move-vacate marker #1152, fix(core): give the move-orphan gate a content-checksum source #1160).
  2. p9k0l1m2n3o4_add_vector_index_manifest_state — adds vector index identity and readiness to
    the semantic manifest; underpins pluggable index backends and honest "no ready index"
    detection (feat(core): add pluggable semantic vector indexes #1141, Vector search silently degrades when configured index has no ready manifest rows #1236). Also backfills the vacate table when a duplicate revision id ran.
  3. q0l1m2n3o4p5_add_relation_search_refresh_table — creates relation_search_refresh:
    durable, retryable work items for relation-derived search refreshes (fix(core): keep relation search refresh retryable #1165, closes Keep relation-resolution search refresh retryable after file read failures #1163).
  4. r1m2n3o4p5q6_add_relation_generation — adds generation to relation (backfilled from
    the source note's accepted generation) and publication_generation to
    relation_search_refresh; the schema behind generation-versioned relation persistence
    (fix: generation-versioned relation persistence #1220, closes Generation-versioned relation persistence: eliminate indexing deadlocks on the relation table #1213).
  5. 2d26b287813b_dedupe_observations_and_purge_stale_one-time data repair: deletes
    duplicate observation rows (keeping the lowest id per
    entity/category/content/context/tags group, with Postgres- and SQLite-specific grouping)
    and purges orphaned type='observation' rows from the search_index FTS table
    (companion to fix(core): fence observation persistence behind note content generation #1228/Generation-versioned observation persistence (companion to relation deadlock fix) #1214). Downgrade is a no-op — removed rows are not reconstructible.

Docs impact checklist (docs.basicmemory.com)

From #1238 (Milvus tire-kick, all verified hands-on) — highest priority:

New/updated pages derived from the rest of the release:


Release-notes skeleton (draft ordering)

  1. Search that ranks and scales — opt-in cross-encoder reranking (local or API); pluggable
    vector indexes with first-party Milvus/Zilliz support on Postgres; embedding correctness
    (watcher writes embedded, L2 normalization, full-content FTS, CJK); honest reindex and
    degraded-state reporting. (feat(core): add optional cross-encoder reranker stage to search #1143, feat(core): add pluggable semantic vector indexes #1141, feat(core): add optional Milvus vector index #1158, fix(core): load existing Milvus collections after restart #1185, fix(cli): surface reindex embedding failures and index identity #1240, fix(core): search complete SQLite note content #1071, [BUG] File-watcher (direct on-disk) writes are not vector-embedded; externally-edited notes silently missing from semantic search until reindex #1016, [BUG] FastEmbed embeddings are not L2-normalized → semantic search silently degrades to FTS-only for non-bge models (e.g. multilingual-mpnet) #1023)
  2. Concurrent writes without deadlocks — generation-versioned relation and observation
    persistence, CAS-based materialization, lock-order fixes, batched resolution; observations
    and relations land with the write, not the next reindex; one-time dedupe repair. (fix: generation-versioned relation persistence #1220,
    fix(core): fence observation persistence behind note content generation #1228, fix(core): remove entity read locks from materialization publish #1227, fix(core): enforce materialization lock order #1193, fix(core): lock entity before observation replacement #1202, fix(core): batch relation target resolution #1204, fix(core): persist observations and relations on DB-first accepted writes #1079)
  3. A real operator consolebm config get/set/list/unset; Rich bm tool output;
    basic_memory_diagnostics; redesigned bm status; usable local Postgres; SQLite tuning
    pragmas. (feat(cli): add bm config command group for get/set/list/unset #1088, feat(cli): add Rich human-readable output to bm tool commands #967, feat(mcp): add basic_memory_diagnostics tool for version and system info #963, refactor(core): add shared runtime orchestration #1002, fix(core): make local Postgres backend usable (migrations, pooling, default project) #1018)
  4. Cloud sharing & hygienebm cloud share; one-way sync honors .bmignore deletions +
    bm cloud prune; cloud project deletion surfaced end-to-end; optional Redis read cache.
    (feat(cli): add bm cloud share command group (create/list/update/revoke) #965, fix(cli): remove newly-ignored files from cloud on one-way sync and add bm cloud prune #1061, fix(mcp): surface cloud project delete errors and support delete_notes #1062/fix(mcp): surface cloud project deletion job #1074, perf(api): add optional Redis read caching #1168/perf(api): add Redis-cached QUERY and MCP reads #1172)
  5. Harness memory capturebm hook front door, default-on envelope capture, plugin
    health surfacing, resumable Codex checkpoints, new skills. (feat(core): add bm hook producer front door for harness capture #1070, feat(plugins): surface hook capture setup and health #1119, feat(plugins): make Codex checkpoints directly resumable #1147, feat(plugins): add bm-writing skill and coding setup to Claude Code plugin #1124,
    feat(plugins): add bm-decide and bm-orient skills to Claude Code plugin #1126)
  6. MCP quality & onboarding — first-connect onboarding; strict-but-honest resolution;
    edit_note metadata; paginated list_directory; FastMCP 4. (feat(mcp): first-connect onboarding — instructions, empty-state guidance, getting_started prompt #1145, fix(core): fail loud on ambiguous strict identifier resolution #1151, feat(mcp): add metadata param to edit_note for frontmatter updates #1090, fix(mcp): paginate directory listings #1082,
    feat(mcp): adopt FastMCP 4 beta #1198)
  7. Breaking changes & upgrade notes — removed MCP tools (canvas/cloud_info/release_notes),
    ChatGPT tool gating, config renames/removals, one-way-sync deletion semantics, frontmatter
    timestamps, observation category strictness, reindex-on-index-switch. (section above)
  8. Fixes — the long tail (double frontmatter, phantom notes, duplicate filenames, Windows
    races, Hermes env/process leaks, doctor/event-loop, project list, etc.)

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions