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
Writes made through the API/MCP persist their observations and relations immediately, not on
the next file re-index (fix(core): persist observations and relations on DB-first accepted writes #1079 , closes DB-first note writes omit observations and relations until a later file re-index #1076 ).
Note materialization publishes through a db_version compare-and-swap; entity read locks and
SELECT FOR UPDATE are gone from the publish path (fix(core): remove entity read locks from materialization publish #1227 , closes Remove SELECT FOR UPDATE from note materialization publish; rely on the db_version CAS with guarded writes #1224 ; feat(core): relay self-supersede on stale base + db_version provenance #1144 , feat(core): relay persists become unconditional versioned exports #1146 add
db_version provenance and self-supersede on stale base).
Relation rows are generation-versioned — stale indexing passes can no longer deadlock on or
overwrite the relation table (fix: generation-versioned relation persistence #1220 , closes Generation-versioned relation persistence: eliminate indexing deadlocks on the relation table #1213 ).
Observation persistence is fenced behind the note-content generation (fix(core): fence observation persistence behind note content generation #1228 , closes Generation-versioned observation persistence (companion to relation deadlock fix) #1214 );
one-time migration dedupes historical duplicate observations and purges their orphaned FTS
rows (migration 2d26b287813b).
Entity↔NoteContent lock order is enforced during materialization (fix(core): enforce materialization lock order #1193 , closes Prevent Entity-NoteContent lock-order deadlocks during materialization #1187 );
the entity is locked before observation replacement (fix(core): lock entity before observation replacement #1202 , closes Fix Entity/Observation lock-order inversion between indexing and accepted writes #1199 ); together with the
above this closes the pessimistic-locking removal issue Remove pessimistic Entity locking from note indexing and defer relation resolution #1209 .
Relation target resolution is batched instead of serial per-target lookups (fix(core): batch relation target resolution #1204 , closes
Batch relation resolution instead of serial per-target lookups #1201 ); relation-resolution writes batched (perf(core): batch relation resolution writes #1132 ); vector prepare transactions batched
(perf(core): batch semantic vector prepare transactions #1131 ).
Relation-derived search refreshes are durable work items that survive read failures and
retries (fix(core): keep relation search refresh retryable #1165 , closes Keep relation-resolution search refresh retryable after file read failures #1163 ; migration q0l1m2n3o4p5), and pending relations refresh from
accepted content (fix(core): refresh pending relations from accepted content #1161 , closes Background relation resolution reads notes before async materialization #1159 ) — forward references now back-resolve when their
target note is created ([BUG] Forward references are not back-resolved when their target note is created (only on reindex) #1015 ) without a full reindex.
Moves record a durable vacate marker keyed on content checksum, so a byte-identical copy at
the old path indexes as a new note instead of being skipped (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 ; migration
o8j9k0l1m2n3).
Stale file reconciliation is rejected (fix(core): reject stale file reconciliation #1149 ); note-file cleanup delete is guarded against a
check→delete TOCTOU (fix(core): guard note-file cleanup delete against a check→delete TOCTOU #1169 ); superseded vector-manifest generations are treated as stale work
(fix(core): defer superseded vector generations #1203 , closes Treat deleted superseded vector-manifest generations as stale work #1200 ).
New materialization_workers setting bounds concurrent write materializations (default 4).
Windows: MCP server no longer dies at startup on a log-cleanup race (fix(core): survive concurrent Windows log cleanup #1218 , closes Windows: MCP server can die at startup on a log-cleanup race in _cleanup_windows_log_files #1211 );
note content can no longer crash loguru message formatting during index retries (fix: keep customer content out of loguru message formatting during index retries #1216 ,
closes Indexing crashes with KeyError when note content reaches loguru message formatting #1212 ).
SQLite write-path tuning exposed: sqlite_synchronous, sqlite_mmap_size,
sqlite_wal_autocheckpoint, sqlite_page_size.
Milestone also closes [BUG] PermissionError during project scan triggers mass index deletion #1007 (PermissionError during project scan no longer triggers mass
index deletion) and [BUG] Watch service crashes every cycle when a project lives on a Windows mapped network drive (relative_to mixes UNC and drive-letter paths) #1047 (watch service crash on Windows mapped network drives).
Semantic search & retrieval
Opt-in cross-encoder reranking of vector/hybrid candidates (feat(core): add optional cross-encoder reranker stage to search #1143 ; closes Add a rerank stage to search: ~half of LoCoMo benchmark misses are ranking failures, and there is ~20x latency headroom #950 , Add reranking step to search pipeline (local cross-encoder) #618 , Retrieval pipeline improvements: reranking, length normalization, noise filtering, adaptive recall #666 ):
reranker_enabled (default off), providers fastembed (local ONNX) or litellm
(Cohere/Jina/Voyage/self-hosted), with candidates/timeout/char-cap/api tuning knobs.
Coverage, real-model smoke, latency benchmark, and quality regression harness added (test(core): close reranker phase-1 coverage gaps #1232 ,
test(core): add reranker real-model smoke and quality harness #1233 , closes Reranker test plan: Postgres parity, live-provider smoke, quality regression harness (post-#1143) #1231 ); reranker defaults/timeout alignment and retryable search outages also
landed in the test(core): add reranker real-model smoke and quality harness #1233 batch.
Pluggable semantic vector indexes on Postgres (SPEC-81, feat(core): add pluggable semantic vector indexes #1141 ): semantic_vector_index
chooses pgvector (default) or milvus; SQLite keeps sqlite-vec. First-party
Milvus/Milvus Lite/Zilliz Cloud adapter (feat(core): add optional Milvus vector index #1158 ) via pip install basic-memory[milvus];
existing collections reload after restart (fix(core): load existing Milvus collections after restart #1185 ). End-to-end verified in Milvus adapter end-to-end tire-kicking session (SPEC-81 verification) #1235 : identical
rankings across sqlite-vec/pgvector/Milvus Lite.
Vector index identity + readiness manifest (migration p9k0l1m2n3o4) — the foundation for
detecting the "configured index has no ready rows" degraded state (Vector search silently degrades when configured index has no ready manifest rows #1236 , closed).
bm reindex --embeddings is honest: nonzero exit and surfaced failures when entities fail to
embed, and it reports which index identity it wrote (fix(cli): surface reindex embedding failures and index identity #1240 , closes reindex --embeddings reports success (exit 0) when every entity fails to embed #1237 ); warns when the
project has no synced entities instead of silently no-opping (fix(cli): warn when embedding reindex has no entities #1190 , closes bm reindex --embeddings silently no-ops on an unsynced project (0 embedded, no warning) #1184 ).
File-watcher (direct on-disk) edits are now vector-embedded — externally edited notes no
longer missing from semantic search until a reindex ([BUG] File-watcher (direct on-disk) writes are not vector-embedded; externally-edited notes silently missing from semantic search until reindex #1016 , closed by the vector-sync
overhaul refactor(core): extract semantic vector synchronization #1129 –perf(core): batch semantic vector prepare transactions #1131 , feat(core): add pluggable semantic vector indexes #1141 ).
FastEmbed embeddings L2-normalized for non-bge models (e.g. multilingual-mpnet), so semantic
search no longer silently degrades to FTS-only ([BUG] FastEmbed embeddings are not L2-normalized → semantic search silently degrades to FTS-only for non-bge models (e.g. multilingual-mpnet) #1023 ).
LiteLLM embedding provider: custom api_base and direct api_key support for
OpenAI-compatible/self-hosted servers (feat(core): add LiteLLM API base and API key config #1043 , closes [FEATURE] Add api_base support for LiteLLM semantic embedding providers #1005 ); literal document/query text
prefixes for prefix-sensitive asymmetric models (feat(core): add semantic embedding text prefixes #1044 , closes [FEATURE] Add role-specific text prefixes for semantic embedding queries and documents #1008 ).
SQLite FTS searches complete note content — previously content beyond ~6000 chars was
invisible (fix(core): search complete SQLite note content #1071 , closes SQLite text search misses all content beyond ~6000 chars (content_stems truncated, content_snippet never matched) #1065 ); CJK terms work in the relaxed FTS fallback (fix(core): support CJK terms in relaxed FTS fallback #1022 );
unknown semantic/hybrid totals are explicit in structured responses (fix(mcp): expose unknown search totals #1069 , closes Make unknown semantic and hybrid search totals explicit in structured responses #1068 ).
Legacy pgvector embeddings schema no longer breaks bm project info (fix(core): handle legacy pgvector storage status #1196 , closes Project info fails on legacy pgvector embeddings schema #1195 ).
Operator surface (CLI & config)
New bm config group: list (effective values, env overrides marked), get, set
(validated through the config model), unset (feat(cli): add bm config command group for get/set/list/unset #1088 , closes Add 'bm config' command group (get/set/list) for managing config.json from the CLI #991 ).
bm tool interactive commands (search-notes, read-note, build-context, recent-activity)
render Rich panels/tables/trees on a TTY; cli_output_style picks rich/plain, --plain and
--json override per-invocation; piped output stays machine-readable (feat(cli): add Rich human-readable output to bm tool commands #967 , closes CLI: Human-readable output for bm tool commands (demo-ready formatting) #678 ).
bm status redesigned around project index status (refactor(core): add shared runtime orchestration #1002 ); the config keys
sync_delay/sync_changes are renamed index_delay/index_changes with automatic
migration of both config.json keys and legacy env vars.
Local Postgres backend is usable end to end: migrations, connection pooling, default-project
resolution (fix(core): make local Postgres backend usable (migrations, pooling, default project) #1018 ).
bm doctor never prints a blank failure message (fix(cli): never print a blank doctor failure message #1058 ) and migrations adapt to existing
event loops (fix(migrations): improve async migration handling by adapting to existing event loops #1094 , closes [BUG] basic-memory-doctor-event-loop-bug-report #1027 doctor event-loop bug).
bm project list shows configured/cloud-mode projects even when uncredentialed (fix(cli): show configured project in list when uncredentialed (#1003) #1010 ,
closes [BUG] bm project list shows empty table while bm project add reports the project already exists (configured/cloud-mode project never rendered) #1003 ); deleting the default project auto-reassigns the default instead of refusing
(fix: auto-reassign default project on delete instead of refusing #1139 ).
created/modified timestamps in frontmatter are honored as note timestamps (feat(core): honor note timestamps from frontmatter #1100 , closes
Feature Request: Support custom timestamps in note frontmatter #238 ).
Config load no longer recreates an empty ~/basic-memory directory as a side effect (fix(core): avoid phantom project directory on config load #1080 ,
closes [BUG] Empty ~/basic-memory directory recreated on every config load (mkdir side effect in config validator) #1029 ); large projects no longer crash reindex on SQLite's bound-parameter limit
(fix(core): chunk select_by_ids to stay under SQLite bound-parameter limit #1057 , closes reindex --embeddings crashes with "too many SQL variables" on large projects (unbatched IN clause in select_by_ids) #1045 ); retired doc links replaced (fix(cli): replace retired Basic Memory links #1083 ).
MCP tool surface
First-connect onboarding: server instructions, empty-state guidance, getting_started
prompt; cloud_info and release_notes tools removed as part of it (feat(mcp): first-connect onboarding — instructions, empty-state guidance, getting_started prompt #1145 ).
New basic_memory_diagnostics tool: version and system info for bug reports (feat(mcp): add basic_memory_diagnostics tool for version and system info #963 , closes
[FEATURE] Add diagnostic tool for version and system information #187 ).
Tool annotations are MCP-directory compliant (fix(mcp): add directory-compliant tool annotations #1031 ), destructive hints are explicit (fix(mcp): set explicit destructive hints #1036 ),
and edit_note exposes its operation enum (fix(mcp): expose edit_note operation enum #1037 ).
ChatGPT-compat search/fetch are gated by clientInfo — non-OpenAI MCP clients get a clear
rejection instead of the compatibility shim (fix(mcp): gate ChatGPT compatibility tools by clientInfo #1035 ).
edit_note accepts a metadata param to update frontmatter fields (feat(mcp): add metadata param to edit_note for frontmatter updates #1090 , closes [FEATURE] Add a way to update frontmatter fields via edit_note #1011 );
replace_section is heading-level-aware with a replace_subsections opt-out — it no longer
silently preserves content past the next h2 (fix(core): make replace_section level-aware with replace_subsections opt-out #1063 , closes [BUG] replace_section silently preserves content past the next h2 when replacement adds new h2 headings #1012 ).
list_directory results are bounded and paginated (fix(mcp): paginate directory listings #1082 , closes Bound and paginate list_directory MCP results #1048 ).
move_note returns the previous accepted path (fix(core): expose previous accepted move path #1075 , closes Expose transaction-local previous path in accepted move results #1072 ); scoped memory:// URL
paths are preserved (fix(mcp): preserve scoped memory URL paths #1092 ); edit_note with a memory:// URL routes to the target project
instead of creating phantom notes (fix(mcp): prevent phantom memory URL edits #1073 , closes [BUG] edit_note with memory:// URL silently creates phantom notes instead of routing to the target project #1066 ).
write_note rejects filename-convention twins (kebab-case vs Title Case) instead of creating
duplicates (fix(core): reject equivalent note filenames #1081 , closes [BUG] write_note creates duplicate notes when existing file uses a different filename convention (kebab-case vs Title Case) #1077 ).
Identifier resolution: ambiguous strict resolution fails loud instead of returning the wrong
entity (fix(core): fail loud on ambiguous strict identifier resolution #1151 , closes Non-exact identifier resolves to the WRONG entity when duplicate permalinks exist #1148 ); project-scoped entity resolution is separated from
cross-project wikilink resolution (fix(api): separate entity and link resolution #1192 , closes Separate project-scoped entity resolution from cross-project wikilink resolution #1170 ); the dead regular-file permalink
lookup is removed (fix(core): remove regular-file permalink lookup #1191 , closes Remove the dead resolve_permalink() call for regular files, and document the no-permalink invariant #1182 ); note-type filters are canonicalized so
Person/person are one population (fix(core): canonicalize note type filters #1189 , closes [BUG] write_note lowercases note_type while type matching stays case-sensitive, splitting one logical type into two populations #1180 ).
external_id exposed in list_directory/recent_activity (feat(mcp): expose note external_id in list_directory and recent_activity output #1040 ), search results (feat(api): add owning-entity external_id to search results #1101 ),
and search_notes markdown output (feat(mcp): emit external_id in search_notes markdown output #1103 ).
schema_validate with no arguments validates all schema-covered types, not type "unknown"
(fix(mcp): validate all schema-covered types when schema_validate gets no arguments #1060 , closes [BUG] schema_validate with no identifier or note_type defaults to validating type 'unknown' instead of all types #1013 ); schema validation modes are validated instead of silently degrading
unknown modes to warn (fix(core): validate schema validation modes #1223 , closes Schema validation mode is stringly-typed: unknown modes (including 'error') silently degrade to warn #1222 ).
Recreated projects are indexed (fix(mcp): index recreated projects #1085 , closes Re-adding a retained cloud project does not index its existing notes #1084 ); cloud project delete errors are
surfaced, with delete_notes support (fix(mcp): surface cloud project delete errors and support delete_notes #1062 , closes [BUG] DELETE /knowledge/entities/{id} returns 500 for non-markdown (file-type) entities #1033 /[BUG] Large cloud-project deletion errors opaquely; no documented way to purge a removed cloud project's data (hosted-tenant retention gap) #1034 ) and a visible deletion job
(fix(mcp): surface cloud project deletion job #1074 ).
Runtime: FastMCP 4.0.0b1 + MCP SDK v2 adopted (feat(mcp): adopt FastMCP 4 beta #1198 ); tools raise the FastMCP runtime
ToolError (fix(mcp): raise the FastMCP runtime ToolError instead of the legacy MCP SDK class #1197 ).
Harness capture & plugins
Cloud, teams & performance
Correctness long tail & maintenance
Sync preserves malformed frontmatter instead of prepending a second frontmatter block that
shadows name/description (fix(core): preserve malformed frontmatter during sync #1188 , closes [BUG] Sync can prepend a SECOND frontmatter block, shadowing name/description from any parser that reads the first #1171 ).
Timestamp-prefixed transcript lines and checkbox markers ([x], [/], [>], [?], [X])
no longer mint junk observation categories (fix(core): reject timestamp and checkbox-marker observation categories #1239 , closes [BUG] Timestamp-prefixed transcript lines are parsed as observations #1219 and Extended checkbox markers ([/], [>], [?], [X]) mint junk observation categories #1241 ).
DCO/CLA documentation inconsistencies fixed (docs(core): clarify DCO and CLA requirements #1025 , closes [DOCS] Contributor agreement: inconsistencies between CONTRIBUTING.md and CLA.md, and within CLA.md #1024 ).
Dependency refreshes: LiteLLM capped <1.92 for Python 3.14 wheels (chore(deps): refresh dependencies with LiteLLM compatibility cap #1051 ), FastMCP unpinned
then moved to 4.0.0b1 (chore(deps): unpin and refresh FastMCP #1052 , feat(mcp): adopt FastMCP 4 beta #1198 ), ruff/ty upgraded with strict checking (chore(deps): upgrade ruff and ty with strict checking #1167 ).
Internal architecture campaign (no direct user impact, large diff): shared runtime
orchestration (refactor(core): add shared runtime orchestration #1002 ), structure campaign (refactor(core): structure campaign — collapse seams, honest types, typed boundaries #1054 , closes Post-#1002 structure campaign: collapse single-impl Protocol seams and dict-shaped boundaries #1053 ), arch-review cleanups —
canvas/resource-write removal (refactor(mcp): remove canvas tool and resource write endpoints #1111 , closes Remove resource write endpoints after canvas tool removal; document the write invariant #1106 ), dead shims (refactor(core): delete dead post-seam compatibility shims #1112 , closes Post-seam cleanup: delete legacy shims, stale trees, and resolve naming twins #1107 ), DI
consolidation (refactor(api): collapse dead v1/_v2 DI provider tiers #1113 , refactor(api): inject config from composition roots into the DI graph #1114 , closes Consolidate _v2/_v2_external DI provider triplication; stop reading ConfigManager below the composition root #1109 ), god-file splits (refactor(core): split search query and row hydration #1122 , refactor(core): split project workflow planning #1133 –refactor(mcp): split project context helpers #1136 , refactor(core): split note content planning by state #1229 ,
closes Split core god files (search_repository_base, project_context, config, runtime protocols) #1108 ), legacy_router drop + picoschema rename (refactor(core): rename picoschema package and drop legacy routes #1121 , closes Drop the pre-v0.18.0 legacy_router; rename schema/ package #1116 ), vector planner
unification (refactor(core): extract semantic chunk planning #1128 –refactor(core): consolidate semantic vector planning #1130 , closes Indexing consolidation: unify vector planners, clarify reindex mechanisms, micro-batching #1110 ), lifecycle/protocol simplification (refactor(core): simplify v0.23 lifecycle and vector surfaces #1162 , refactor(core): simplify v0.23 runtime protocols #1164 ),
Vulture dead code removal (refactor(core): remove Vulture-confirmed dead code #1153 ).
CI/workflow: Auto BM webhook validation (ci(ci): validate Auto BM webhook #1019 , ci(ci): remove legacy Basic Memory workflow #1020 ), Claude workflow write access +
toolchain (fix(ci): grant Claude Code workflow write access to open PRs #1087 , fix(ci): drop broken pull_request_target trigger + provision toolchain for Claude workflow #1091 ), triage focus and determinism (fix(ci): keep issue triage focused on triage #1086 , fix(ci): make issue triage labeling deterministic #1206 ), Git Bash from MinGW
path on Windows runners (test(ci): locate Git Bash from MinGW Git path #1221 ), benchmark suite vendored (test(benchmarks): vendor benchmark suite and add write-load harness #1021 ).
Milestone triage closed long-standing issues as already satisfied by prior releases rather
than by new v0.23 PRs: Enhancement: Cross-Project Search and Entity Linking #123 (cross-project search), Enhancement: DXT (Desktop Extensions) packaging support #193 (DXT packaging), Bug: Tag search syntax 'tag:tagname' returns empty results #354 (tag search),
Retrieval pipeline improvements: reranking, length normalization, noise filtering, adaptive recall #666 (retrieval pipeline), Refactor repositories to support caller-owned AsyncSession flows #750 (repository sessions), Design: Team-safe bidirectional sync using Tigris snapshots #862 (team-safe sync design), Review-memory layer for adversarial-review (supplement to #865 memory bridge) #869
(review-memory layer). Do not list these as new v0.23 features.
Breaking changes & migration notes
CLI surface (what a v0.22.1 user notices)
Correction to a working assumption : the top-level bm sync verb was not removed in
this window — it was already absent at v0.22.1 (only bm cloud sync exists there; verified
against v0.22.1:src/basic_memory/cli/). The v0.22.1 → v0.23 top-level delta is purely
additive: new bm config group (feat(cli): add bm config command group for get/set/list/unset #1088 ) and new bm hook group (feat(core): add bm hook producer front door for harness capture #1070 ). Existing verbs
(status, reset, reindex, doctor, mcp, orphans, format, update, workspace, import, cloud, ci,
man, tool, project, schema) all remain.
New cloud subcommands: bm cloud share create|list|update|revoke (feat(cli): add bm cloud share command group (create/list/update/revoke) #965 ), bm cloud prune
(fix(cli): remove newly-ignored files from cloud on one-way sync and add bm cloud prune #1061 ).
bm status output is redesigned (project index status instead of the old sync report tree)
(refactor(core): add shared runtime orchestration #1002 ) — scripts that scraped the old output will break.
bm tool commands render Rich formatted output when stdout is a TTY (feat(cli): add Rich human-readable output to bm tool commands #967 ). Piped/non-TTY
output is unchanged; scripts should pass --json for a stable contract.
bm reindex --embeddings now exits nonzero when embedding fails (fix(cli): surface reindex embedding failures and index identity #1240 ) — CI wrappers that
relied on unconditional exit 0 must handle real failures.
MCP surface
HTTP API
Config file
sync_delay → index_delay, sync_changes → index_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
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.
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.
Milvus requires pip install basic-memory[milvus]; Redis caching requires
basic-memory[redis].
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:
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 ).
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.
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 ).
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 ).
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:
Local-Postgres quickstart — a user-facing "run Postgres locally" page
(BASIC_MEMORY_DATABASE_BACKEND/DATABASE_URL, docker-compose example); today all
Postgres framing is test infrastructure (fix(core): make local Postgres backend usable (migrations, pooling, default project) #1018 makes it supportable).
Fresh-switch reindex requirement — flipping semantic_vector_index requires
bm reindex --embeddings (incremental suffices); document the pre-reindex degraded
window (vector empty, hybrid silently FTS-only, Vector search silently degrades when configured index has no ready manifest rows #1236 ).
Milvus setup page — install basic-memory[milvus]; Milvus Lite .db URI realities
(suffix required, parent dir must exist, creates a directory tree); milvus_database
ignored by Lite; stale pgvector projection rows remain after a switch (manual cleanup /
disk cost); positive claims worth stating: identical rankings across backends, good
dimension-mismatch and missing-extra errors, 30s timeout honored, collections reload
(fix(core): load existing Milvus collections after restart #1185 ).
Reranker setup & tuning — enabling, provider matrix (fastembed vs litellm), model
selection, reranker_candidates/max_document_chars latency tradeoffs, first-run model
download (feat(core): add optional cross-encoder reranker stage to search #1143 ).
New/updated pages derived from the rest of the release:
bm config CLI reference (get/set/list/unset, env override marking) (feat(cli): add bm config command group for get/set/list/unset #1088 ).
bm hook + harness capture: lifecycle verbs, capture WAL, flush/status/install/remove,
captureEvents opt-out, standalone (non-plugin) install flow (Capture Claude/Codex harness events as Basic Memory producer envelopes #997 /feat(core): add bm hook producer front door for harness capture #1070 /feat(plugins): surface hook capture setup and health #1119 ).
bm cloud share reference (create/list/update/revoke) (feat(cli): add bm cloud share command group (create/list/update/revoke) #965 ).
bm cloud prune + the new one-way-sync deletion of newly-ignored files — prominent
behavior-change callout (fix(cli): remove newly-ignored files from cloud on one-way sync and add bm cloud prune #1061 ).
Redis read cache for standalone MCP (basic-memory[redis], redis_url) (perf(api): add optional Redis read caching #1168 /perf(api): add Redis-cached QUERY and MCP reads #1172 ).
MCP tool reference: add basic_memory_diagnostics; remove canvas, cloud_info,
release_notes; ChatGPT tools OpenAI-client-only; edit_note metadata param and
replace_section/replace_subsections semantics; list_directory pagination;
external_id fields (feat(mcp): add basic_memory_diagnostics tool for version and system info #963 , refactor(mcp): remove canvas tool and resource write endpoints #1111 , feat(mcp): first-connect onboarding — instructions, empty-state guidance, getting_started prompt #1145 , fix(mcp): gate ChatGPT compatibility tools by clientInfo #1035 , feat(mcp): add metadata param to edit_note for frontmatter updates #1090 , fix(core): make replace_section level-aware with replace_subsections opt-out #1063 , fix(mcp): paginate directory listings #1082 , feat(mcp): expose note external_id in list_directory and recent_activity output #1040 /feat(api): add owning-entity external_id to search results #1101 /feat(mcp): emit external_id in search_notes markdown output #1103 ).
Config reference: full new-key table above; sync_* → index_* rename note; removed
thread-pool keys; SQLite pragma tuning guidance.
Note format docs: frontmatter created/modified honored (feat(core): honor note timestamps from frontmatter #1100 ); observation category
rules (timestamps/checkbox markers rejected, fix(core): reject timestamp and checkbox-marker observation categories #1239 ); filename-twin rejection (fix(core): reject equivalent note filenames #1081 ).
bm tool output styles (cli_output_style, --plain, --json) (feat(cli): add Rich human-readable output to bm tool commands #967 ).
Upgrade guide for v0.23: migrations (incl. the one-time observation dedupe), reindex
requirement on index switch, extras, FastMCP 4 note.
Release-notes skeleton (draft ordering)
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 )
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 )
A real operator console — bm 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 )
Cloud sharing & hygiene — bm 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 )
Harness memory capture — bm 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 )
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 )
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)
Fixes — the long tail (double frontmatter, phantom notes, duplicate filenames, Windows
races, Hermes env/process leaks, doctor/event-loop, project list, etc.)
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 hookwork (#997) andtwo 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_versioncompare-and-swap withno 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 newindex-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) andasymmetric-model text prefixes (#1044/#1008). Honesty work:
bm reindex --embeddingsnow failsloudly 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 configgroup forget/set/list/unset with validation (#1088/#991), Rich human-readable output for interactive
bm toolcommands with acli_output_stylesetting and--plain/--jsonoverrides(#967/#678), a
basic_memory_diagnosticsMCP tool reporting version/system info (#963/#187),and a redesigned
bm statusbuilt on project index status (#1002). The local Postgres backendis now actually usable (migrations, pooling, default project — #1018), SQLite gains four tunable
pragmas, and
bm doctornever prints a blank failure (#1058) nor trips over existing eventloops (#1094/#1027).
4. Harness capture & plugins: bm hook, Codex/Claude health, skills
SPEC-55 landed: a
bm hookfront door (#1070/#997) moves plugin hook logic into the package —session-start/pre-compact/stoplifecycle verbs, default-on bounded envelope capture into alocal WAL inbox,
bm hook flush|status|install|remove, andcaptureEvents: falseto 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
~/.claudesettings (#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|revokelands (#965/#880). One-way cloud sync now removesnewly-
.bmignored files from the cloud, with a newbm cloud prunecommand (#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_startedprompt (#1145), whilecanvas,cloud_info, andrelease_notestools wereremoved (#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_notegained ametadataparam (#1090/#1011) and level-awarereplace_section(#1063/#1012);list_directoryis bounded and paginated (#1082/#1048);external_idis exposed acrosslisting, 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 frontmatterblocks (#1188/#1171), and junk observation categories from transcripts and checkboxes
(#1239/#1219/#1241).
Per-theme change lists
Indexing & persistence concurrency
the next file re-index (fix(core): persist observations and relations on DB-first accepted writes #1079, closes DB-first note writes omit observations and relations until a later file re-index #1076).
db_versioncompare-and-swap; entity read locks andSELECT FOR UPDATE are gone from the publish path (fix(core): remove entity read locks from materialization publish #1227, closes Remove SELECT FOR UPDATE from note materialization publish; rely on the db_version CAS with guarded writes #1224; feat(core): relay self-supersede on stale base + db_version provenance #1144, feat(core): relay persists become unconditional versioned exports #1146 add
db_version provenance and self-supersede on stale base).
overwrite the relation table (fix: generation-versioned relation persistence #1220, closes Generation-versioned relation persistence: eliminate indexing deadlocks on the relation table #1213).
one-time migration dedupes historical duplicate observations and purges their orphaned FTS
rows (migration 2d26b287813b).
the entity is locked before observation replacement (fix(core): lock entity before observation replacement #1202, closes Fix Entity/Observation lock-order inversion between indexing and accepted writes #1199); together with the
above this closes the pessimistic-locking removal issue Remove pessimistic Entity locking from note indexing and defer relation resolution #1209.
Batch relation resolution instead of serial per-target lookups #1201); relation-resolution writes batched (perf(core): batch relation resolution writes #1132); vector prepare transactions batched
(perf(core): batch semantic vector prepare transactions #1131).
retries (fix(core): keep relation search refresh retryable #1165, closes Keep relation-resolution search refresh retryable after file read failures #1163; migration q0l1m2n3o4p5), and pending relations refresh from
accepted content (fix(core): refresh pending relations from accepted content #1161, closes Background relation resolution reads notes before async materialization #1159) — forward references now back-resolve when their
target note is created ([BUG] Forward references are not back-resolved when their target note is created (only on reindex) #1015) without a full reindex.
the old path indexes as a new note instead of being skipped (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; migration
o8j9k0l1m2n3).
check→delete TOCTOU (fix(core): guard note-file cleanup delete against a check→delete TOCTOU #1169); superseded vector-manifest generations are treated as stale work
(fix(core): defer superseded vector generations #1203, closes Treat deleted superseded vector-manifest generations as stale work #1200).
materialization_workerssetting bounds concurrent write materializations (default 4).note content can no longer crash loguru message formatting during index retries (fix: keep customer content out of loguru message formatting during index retries #1216,
closes Indexing crashes with KeyError when note content reaches loguru message formatting #1212).
sqlite_synchronous,sqlite_mmap_size,sqlite_wal_autocheckpoint,sqlite_page_size.index deletion) and [BUG] Watch service crashes every cycle when a project lives on a Windows mapped network drive (relative_to mixes UNC and drive-letter paths) #1047 (watch service crash on Windows mapped network drives).
Semantic search & retrieval
reranker_enabled(default off), providersfastembed(local ONNX) orlitellm(Cohere/Jina/Voyage/self-hosted), with candidates/timeout/char-cap/api tuning knobs.
Coverage, real-model smoke, latency benchmark, and quality regression harness added (test(core): close reranker phase-1 coverage gaps #1232,
test(core): add reranker real-model smoke and quality harness #1233, closes Reranker test plan: Postgres parity, live-provider smoke, quality regression harness (post-#1143) #1231); reranker defaults/timeout alignment and retryable search outages also
landed in the test(core): add reranker real-model smoke and quality harness #1233 batch.
semantic_vector_indexchooses
pgvector(default) ormilvus; SQLite keeps sqlite-vec. First-partyMilvus/Milvus Lite/Zilliz Cloud adapter (feat(core): add optional Milvus vector index #1158) via
pip install basic-memory[milvus];existing collections reload after restart (fix(core): load existing Milvus collections after restart #1185). End-to-end verified in Milvus adapter end-to-end tire-kicking session (SPEC-81 verification) #1235: identical
rankings across sqlite-vec/pgvector/Milvus Lite.
detecting the "configured index has no ready rows" degraded state (Vector search silently degrades when configured index has no ready manifest rows #1236, closed).
bm reindex --embeddingsis honest: nonzero exit and surfaced failures when entities fail toembed, and it reports which index identity it wrote (fix(cli): surface reindex embedding failures and index identity #1240, closes reindex --embeddings reports success (exit 0) when every entity fails to embed #1237); warns when the
project has no synced entities instead of silently no-opping (fix(cli): warn when embedding reindex has no entities #1190, closes bm reindex --embeddings silently no-ops on an unsynced project (0 embedded, no warning) #1184).
longer missing from semantic search until a reindex ([BUG] File-watcher (direct on-disk) writes are not vector-embedded; externally-edited notes silently missing from semantic search until reindex #1016, closed by the vector-sync
overhaul refactor(core): extract semantic vector synchronization #1129–perf(core): batch semantic vector prepare transactions #1131, feat(core): add pluggable semantic vector indexes #1141).
search no longer silently degrades to FTS-only ([BUG] FastEmbed embeddings are not L2-normalized → semantic search silently degrades to FTS-only for non-bge models (e.g. multilingual-mpnet) #1023).
api_baseand directapi_keysupport forOpenAI-compatible/self-hosted servers (feat(core): add LiteLLM API base and API key config #1043, closes [FEATURE] Add api_base support for LiteLLM semantic embedding providers #1005); literal document/query text
prefixes for prefix-sensitive asymmetric models (feat(core): add semantic embedding text prefixes #1044, closes [FEATURE] Add role-specific text prefixes for semantic embedding queries and documents #1008).
invisible (fix(core): search complete SQLite note content #1071, closes SQLite text search misses all content beyond ~6000 chars (content_stems truncated, content_snippet never matched) #1065); CJK terms work in the relaxed FTS fallback (fix(core): support CJK terms in relaxed FTS fallback #1022);
unknown semantic/hybrid totals are explicit in structured responses (fix(mcp): expose unknown search totals #1069, closes Make unknown semantic and hybrid search totals explicit in structured responses #1068).
bm project info(fix(core): handle legacy pgvector storage status #1196, closes Project info fails on legacy pgvector embeddings schema #1195).Operator surface (CLI & config)
bm configgroup:list(effective values, env overrides marked),get,set(validated through the config model),
unset(feat(cli): add bm config command group for get/set/list/unset #1088, closes Add 'bm config' command group (get/set/list) for managing config.json from the CLI #991).bm toolinteractive commands (search-notes, read-note, build-context, recent-activity)render Rich panels/tables/trees on a TTY;
cli_output_stylepicks rich/plain,--plainand--jsonoverride per-invocation; piped output stays machine-readable (feat(cli): add Rich human-readable output to bm tool commands #967, closes CLI: Human-readable output forbm toolcommands (demo-ready formatting) #678).bm statusredesigned around project index status (refactor(core): add shared runtime orchestration #1002); the config keyssync_delay/sync_changesare renamedindex_delay/index_changeswith automaticmigration of both config.json keys and legacy env vars.
resolution (fix(core): make local Postgres backend usable (migrations, pooling, default project) #1018).
bm doctornever prints a blank failure message (fix(cli): never print a blank doctor failure message #1058) and migrations adapt to existingevent loops (fix(migrations): improve async migration handling by adapting to existing event loops #1094, closes [BUG] basic-memory-doctor-event-loop-bug-report #1027 doctor event-loop bug).
bm project listshows configured/cloud-mode projects even when uncredentialed (fix(cli): show configured project in list when uncredentialed (#1003) #1010,closes [BUG] bm project list shows empty table while bm project add reports the project already exists (configured/cloud-mode project never rendered) #1003); deleting the default project auto-reassigns the default instead of refusing
(fix: auto-reassign default project on delete instead of refusing #1139).
created/modifiedtimestamps in frontmatter are honored as note timestamps (feat(core): honor note timestamps from frontmatter #1100, closesFeature Request: Support custom timestamps in note frontmatter #238).
~/basic-memorydirectory as a side effect (fix(core): avoid phantom project directory on config load #1080,closes [BUG] Empty ~/basic-memory directory recreated on every config load (mkdir side effect in config validator) #1029); large projects no longer crash reindex on SQLite's bound-parameter limit
(fix(core): chunk select_by_ids to stay under SQLite bound-parameter limit #1057, closes reindex --embeddings crashes with "too many SQL variables" on large projects (unbatched IN clause in select_by_ids) #1045); retired doc links replaced (fix(cli): replace retired Basic Memory links #1083).
MCP tool surface
getting_startedprompt;
cloud_infoandrelease_notestools removed as part of it (feat(mcp): first-connect onboarding — instructions, empty-state guidance, getting_started prompt #1145).basic_memory_diagnosticstool: version and system info for bug reports (feat(mcp): add basic_memory_diagnostics tool for version and system info #963, closes[FEATURE] Add diagnostic tool for version and system information #187).
and
edit_noteexposes its operation enum (fix(mcp): expose edit_note operation enum #1037).search/fetchare gated by clientInfo — non-OpenAI MCP clients get a clearrejection instead of the compatibility shim (fix(mcp): gate ChatGPT compatibility tools by clientInfo #1035).
edit_noteaccepts ametadataparam to update frontmatter fields (feat(mcp): add metadata param to edit_note for frontmatter updates #1090, closes [FEATURE] Add a way to update frontmatter fields via edit_note #1011);replace_sectionis heading-level-aware with areplace_subsectionsopt-out — it no longersilently preserves content past the next h2 (fix(core): make replace_section level-aware with replace_subsections opt-out #1063, closes [BUG] replace_section silently preserves content past the next h2 when replacement adds new h2 headings #1012).
list_directoryresults are bounded and paginated (fix(mcp): paginate directory listings #1082, closes Bound and paginate list_directory MCP results #1048).move_notereturns the previous accepted path (fix(core): expose previous accepted move path #1075, closes Expose transaction-local previous path in accepted move results #1072); scopedmemory://URLpaths are preserved (fix(mcp): preserve scoped memory URL paths #1092);
edit_notewith amemory://URL routes to the target projectinstead of creating phantom notes (fix(mcp): prevent phantom memory URL edits #1073, closes [BUG] edit_note with memory:// URL silently creates phantom notes instead of routing to the target project #1066).
write_noterejects filename-convention twins (kebab-case vs Title Case) instead of creatingduplicates (fix(core): reject equivalent note filenames #1081, closes [BUG] write_note creates duplicate notes when existing file uses a different filename convention (kebab-case vs Title Case) #1077).
entity (fix(core): fail loud on ambiguous strict identifier resolution #1151, closes Non-exact identifier resolves to the WRONG entity when duplicate permalinks exist #1148); project-scoped entity resolution is separated from
cross-project wikilink resolution (fix(api): separate entity and link resolution #1192, closes Separate project-scoped entity resolution from cross-project wikilink resolution #1170); the dead regular-file permalink
lookup is removed (fix(core): remove regular-file permalink lookup #1191, closes Remove the dead resolve_permalink() call for regular files, and document the no-permalink invariant #1182); note-type filters are canonicalized so
Person/personare one population (fix(core): canonicalize note type filters #1189, closes [BUG] write_note lowercases note_type while type matching stays case-sensitive, splitting one logical type into two populations #1180).external_idexposed inlist_directory/recent_activity(feat(mcp): expose note external_id in list_directory and recent_activity output #1040), search results (feat(api): add owning-entity external_id to search results #1101),and
search_notesmarkdown output (feat(mcp): emit external_id in search_notes markdown output #1103).schema_validatewith no arguments validates all schema-covered types, not type "unknown"(fix(mcp): validate all schema-covered types when schema_validate gets no arguments #1060, closes [BUG] schema_validate with no identifier or note_type defaults to validating type 'unknown' instead of all types #1013); schema validation modes are validated instead of silently degrading
unknown modes to warn (fix(core): validate schema validation modes #1223, closes Schema validation mode is stringly-typed: unknown modes (including 'error') silently degrade to warn #1222).
surfaced, with
delete_notessupport (fix(mcp): surface cloud project delete errors and support delete_notes #1062, closes [BUG]DELETE /knowledge/entities/{id}returns 500 for non-markdown (file-type) entities #1033/[BUG] Large cloud-project deletion errors opaquely; no documented way to purge a removed cloud project's data (hosted-tenant retention gap) #1034) and a visible deletion job(fix(mcp): surface cloud project deletion job #1074).
ToolError (fix(mcp): raise the FastMCP runtime ToolError instead of the legacy MCP SDK class #1197).
Harness capture & plugins
bm hookfront door (SPEC-55, feat(core): add bm hook producer front door for harness capture #1070, closes Capture Claude/Codex harness events as Basic Memory producer envelopes #997):session-start,pre-compact,stoplifecycle verbs behind per-harness stdin adapters; default-on bounded envelope capture into a
local inbox WAL;
bm hook flusharchives locally;bm hook statusshows the surface;bm hook install|removewire hooks into user-level harness config with ownership-taggedsurgical merging;
captureEvents: falsedisables capture;BM_BINoverrides the uv-managedenvironment for development.
basic-memory hookin-process (Capture Claude/Codex harness events as Basic Memory producer envelopes #997 work); plugins surface hook capture setup and health(feat(plugins): surface hook capture setup and health #1119, closes feat(plugins): surface hook capture setup and health in Codex and Claude #1117).
~/.claudesettings (feat(plugins): fall back to user-level ~/.claude settings for Claude Code hook config #924); hook tests areWindows-portable (fix(plugins): make Claude hook tests portable on Windows #1055).
(fix(plugins): make Codex hooks and memory notes reliable #1123); checkpoints authored and prompted after compaction (fix(plugins): author Codex checkpoints after compaction #1138, fix(plugins): prompt Codex checkpoints after compaction #1142); hooks pinned to
the merged runtime (fix(plugins): pin Codex hooks to merged runtime #1140); checkpoints directly resumable (feat(plugins): make Codex checkpoints directly resumable #1147); shared hook defaults
(feat(plugins): share Codex hook defaults #1137); coding-session paths normalized to POSIX (fix(core): normalize coding-session paths to POSIX form #1127).
(feat(plugins): add bm-writing skill and coding setup to Claude Code plugin #1124) and bm-decide/bm-orient (feat(plugins): add bm-decide and bm-orient skills to Claude Code plugin #1126); shared memory-onboarding skill (feat(skills): add memory-onboarding skill #1050) with a
published archive on main pushes (ci(skills): publish onboarding archive on main pushes #1067).
(CHANGELOG Unreleased).
Cloud, teams & performance
bm cloud sharecommand group: create/list/update/revoke (feat(cli): add bm cloud share command group (create/list/update/revoke) #965, closes Addbm cloud sharecommand (create / list / unshare) #880).bm cloud sync) removes newly-.bmignored files from the cloud, andbm cloud prunepurges them on demand (fix(cli): remove newly-ignored files from cloud on one-way sync and add bm cloud prune #1061, closes [BUG] .bmignore shields already-synced content from removal (no supported deletion path) #1032).redis_url/redis_max_connections,basic-memory[redis]extra; QUERY and MCP read paths cached(perf(api): add optional Redis read caching #1168, perf(api): add Redis-cached QUERY and MCP reads #1172, closes Upstream a BMQ3-style read cache: MCP tool latency is product-defining for multi-agent use #980).
bm mcpchild processes no longer leak (fix(integrations): stop leaking bm mcp children from the Hermes provider #1059, closes [BUG] integrations/hermes: bm mcp processes leak #1017) and bmsubprocesses no longer inherit Hermes's Python env/PYTHONPATH (fix(integrations): stop bm subprocesses inheriting Hermes's Python env #1179, closes Hermes provider inherits PYTHONPATH and fails to start bm MCP with mixed Python versions #1093).
Correctness long tail & maintenance
shadows
name/description(fix(core): preserve malformed frontmatter during sync #1188, closes [BUG] Sync can prepend a SECOND frontmatter block, shadowingname/descriptionfrom any parser that reads the first #1171).[x],[/],[>],[?],[X])no longer mint junk observation categories (fix(core): reject timestamp and checkbox-marker observation categories #1239, closes [BUG] Timestamp-prefixed transcript lines are parsed as observations #1219 and Extended checkbox markers ([/], [>], [?], [X]) mint junk observation categories #1241).
then moved to 4.0.0b1 (chore(deps): unpin and refresh FastMCP #1052, feat(mcp): adopt FastMCP 4 beta #1198), ruff/ty upgraded with strict checking (chore(deps): upgrade ruff and ty with strict checking #1167).
orchestration (refactor(core): add shared runtime orchestration #1002), structure campaign (refactor(core): structure campaign — collapse seams, honest types, typed boundaries #1054, closes Post-#1002 structure campaign: collapse single-impl Protocol seams and dict-shaped boundaries #1053), arch-review cleanups —
canvas/resource-write removal (refactor(mcp): remove canvas tool and resource write endpoints #1111, closes Remove resource write endpoints after canvas tool removal; document the write invariant #1106), dead shims (refactor(core): delete dead post-seam compatibility shims #1112, closes Post-seam cleanup: delete legacy shims, stale trees, and resolve naming twins #1107), DI
consolidation (refactor(api): collapse dead v1/_v2 DI provider tiers #1113, refactor(api): inject config from composition roots into the DI graph #1114, closes Consolidate _v2/_v2_external DI provider triplication; stop reading ConfigManager below the composition root #1109), god-file splits (refactor(core): split search query and row hydration #1122, refactor(core): split project workflow planning #1133–refactor(mcp): split project context helpers #1136, refactor(core): split note content planning by state #1229,
closes Split core god files (search_repository_base, project_context, config, runtime protocols) #1108), legacy_router drop + picoschema rename (refactor(core): rename picoschema package and drop legacy routes #1121, closes Drop the pre-v0.18.0 legacy_router; rename schema/ package #1116), vector planner
unification (refactor(core): extract semantic chunk planning #1128–refactor(core): consolidate semantic vector planning #1130, closes Indexing consolidation: unify vector planners, clarify reindex mechanisms, micro-batching #1110), lifecycle/protocol simplification (refactor(core): simplify v0.23 lifecycle and vector surfaces #1162, refactor(core): simplify v0.23 runtime protocols #1164),
Vulture dead code removal (refactor(core): remove Vulture-confirmed dead code #1153).
toolchain (fix(ci): grant Claude Code workflow write access to open PRs #1087, fix(ci): drop broken pull_request_target trigger + provision toolchain for Claude workflow #1091), triage focus and determinism (fix(ci): keep issue triage focused on triage #1086, fix(ci): make issue triage labeling deterministic #1206), Git Bash from MinGW
path on Windows runners (test(ci): locate Git Bash from MinGW Git path #1221), benchmark suite vendored (test(benchmarks): vendor benchmark suite and add write-load harness #1021).
than by new v0.23 PRs: Enhancement: Cross-Project Search and Entity Linking #123 (cross-project search), Enhancement: DXT (Desktop Extensions) packaging support #193 (DXT packaging), Bug: Tag search syntax 'tag:tagname' returns empty results #354 (tag search),
Retrieval pipeline improvements: reranking, length normalization, noise filtering, adaptive recall #666 (retrieval pipeline), Refactor repositories to support caller-owned AsyncSession flows #750 (repository sessions), Design: Team-safe bidirectional sync using Tigris snapshots #862 (team-safe sync design), Review-memory layer for adversarial-review (supplement to #865 memory bridge) #869
(review-memory layer). Do not list these as new v0.23 features.
Breaking changes & migration notes
CLI surface (what a v0.22.1 user notices)
bm syncverb was not removed inthis window — it was already absent at v0.22.1 (only
bm cloud syncexists there; verifiedagainst
v0.22.1:src/basic_memory/cli/). The v0.22.1 → v0.23 top-level delta is purelyadditive: new
bm configgroup (feat(cli): add bm config command group for get/set/list/unset #1088) and newbm hookgroup (feat(core): add bm hook producer front door for harness capture #1070). Existing verbs(status, reset, reindex, doctor, mcp, orphans, format, update, workspace, import, cloud, ci,
man, tool, project, schema) all remain.
bm cloud share create|list|update|revoke(feat(cli): add bm cloud share command group (create/list/update/revoke) #965),bm cloud prune(fix(cli): remove newly-ignored files from cloud on one-way sync and add bm cloud prune #1061).
bm statusoutput is redesigned (project index status instead of the old sync report tree)(refactor(core): add shared runtime orchestration #1002) — scripts that scraped the old output will break.
bm toolcommands render Rich formatted output when stdout is a TTY (feat(cli): add Rich human-readable output to bm tool commands #967). Piped/non-TTYoutput is unchanged; scripts should pass
--jsonfor a stable contract.bm reindex --embeddingsnow exits nonzero when embedding fails (fix(cli): surface reindex embedding failures and index identity #1240) — CI wrappers thatrelied on unconditional exit 0 must handle real failures.
MCP surface
canvastool removed (refactor(mcp): remove canvas tool and resource write endpoints #1111, closes Remove resource write endpoints after canvas tool removal; document the write invariant #1106) — Obsidian canvas generation is gone, alongwith the API resource write endpoints backing it. Not yet mentioned in CHANGELOG Unreleased.
cloud_infoandrelease_notestools removed (feat(mcp): first-connect onboarding — instructions, empty-state guidance, getting_started prompt #1145). Also unmentioned in thechangelog.
search/fetchrefuse non-OpenAI MCP clients (fix(mcp): gate ChatGPT compatibility tools by clientInfo #1035). Any non-ChatGPTclient that called these compatibility tools must switch to
search_notes/read_note.list_directoryoutput is now paginated/bounded (fix(mcp): paginate directory listings #1082) — consumers expecting one exhaustivelisting must page.
write_noteerrors on filename-convention twins instead of creating a duplicate (fix(core): reject equivalent note filenames #1081).replace_sectionsemantics changed: heading-level-aware; nested subsections are replacedunless
replace_subsections=false(fix(core): make replace_section level-aware with replace_subsections opt-out #1063).mcp>=2(feat(mcp): adopt FastMCP 4 beta #1198):downstream embedders pinning fastmcp 3.x must upgrade together.
HTTP API
dropped (refactor(core): rename picoschema package and drop legacy routes #1121, closes Drop the pre-v0.18.0 legacy_router; rename schema/ package #1116). Old clients still calling legacy routes get 404s. Neither is
in the changelog yet.
external_idon search results (feat(api): add owning-entity external_id to search results #1101), previous acceptedmove path (fix(core): expose previous accepted move path #1075), unknown search totals (fix(mcp): expose unknown search totals #1069), invalid schema modes rejected (fix(core): validate schema validation modes #1223).
Config file
sync_delay→index_delay,sync_changes→index_changes(refactor(core): add shared runtime orchestration #1002). Legacy config.jsonkeys and legacy
BASIC_MEMORY_SYNC_*env vars are auto-migrated; new names win when bothare present. No user action needed.
sync_thread_pool_sizeandsync_max_concurrent_fileswere removed with no alias;configs that set them are silently ignored (
extra="ignore"). The closest replacement knobis
materialization_workers. Worth a release-note line.Behavior a v0.22.1 user must know
bm cloud sync(one-way mirror) now deletes newly-ignored files from the cloud(fix(cli): remove newly-ignored files from cloud on one-way sync and add bm cloud prune #1061). Previously
.bmignore-ing a synced file left it on the cloud forever; now themirror honors the ignore by removing it remotely. Intentional, but destructive relative to
prior behavior — call out prominently.
created/modifiedfrontmatter timestamps are now authoritative (feat(core): honor note timestamps from frontmatter #1100) — recency ordering(
recent_activity, sorts) can change after the first re-sync for notes carrying historicaldates.
longer indexed as observations (fix(core): reject timestamp and checkbox-marker observation categories #1239). Task lists like
- [x] donestop mintingxcategories; notes that (unintentionally) depended on that will index differently.
Personandpersonnow match the samepopulation (previously split).
Required / recommended upgrade actions
dedupe migration is a one-time data repair; back up
memory.dbif paranoid — files remainsource of truth and the index is rebuildable.
semantic_vector_index(e.g. pgvector → milvus), runbm reindex --embeddings— until then vector search returns nothing and hybrid silentlyserves 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.
pip install basic-memory[milvus]; Redis caching requiresbasic-memory[redis].bm config set reranker_enabled true(first use downloads the model).New configuration surface
All keys settable via
bm config set <key> <value>(#1088) orBASIC_MEMORY_<KEY>env vars.reranker_enabledfalsereranker_providerfastembedfastembed(local ONNX cross-encoder) orlitellm(Cohere/Jina/Voyage/etc. via API).reranker_modeljinaai/jina-reranker-v1-tiny-enprovider/modelform for litellm (e.g.cohere/rerank-v3.5).reranker_candidates20reranker_max_document_chars0reranker_timeout30.0reranker_api_baseNonereranker_api_keyNonesemantic_vector_indexpgvectorpgvectorormilvus(SPEC-81, #1141). SQLite always uses sqlite-vec.milvus_uriNonemilvus_tokenNonemilvus_timeout_seconds30.0milvus_collection_prefixbasic_memorymilvus_databasedefaultsemantic_embedding_api_baseNonesemantic_embedding_api_keyNonesemantic_embedding_document_prefixNonesemantic_embedding_query_prefixNoneredis_urlNoneredis_max_connections20materialization_workers4sqlite_synchronousNORMALPRAGMA synchronous(OFF/NORMAL/FULL/EXTRA); NORMAL is safe with WAL.sqlite_mmap_size268435456(256 MB)PRAGMA mmap_sizein bytes; 0 disables mmap I/O.sqlite_wal_autocheckpoint1000PRAGMA wal_autocheckpointin pages; higher = fewer writer stalls, larger WAL.sqlite_page_size4096PRAGMA page_size; only effective on a fresh DB or after VACUUM.index_delay1000sync_delay(auto-migrated).index_changestruesync_changes(auto-migrated).cli_output_stylerichbm toolTTY output style:richorplain; per-invocation--json/--plainoverride (#967).Removed without replacement or alias:⚠️ silently ignored if present in config.json).
sync_thread_pool_size,sync_max_concurrent_files(
Database migrations
Five new revisions since v0.22.1, applied automatically in order:
o8j9k0l1m2n3_add_note_file_vacate_table— createsnote_file_vacate, a durable proof thata 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).
p9k0l1m2n3o4_add_vector_index_manifest_state— adds vector index identity and readiness tothe 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.
q0l1m2n3o4p5_add_relation_search_refresh_table— createsrelation_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).
r1m2n3o4p5q6_add_relation_generation— addsgenerationtorelation(backfilled fromthe source note's accepted generation) and
publication_generationtorelation_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).
2d26b287813b_dedupe_observations_and_purge_stale_— one-time data repair: deletesduplicate
observationrows (keeping the lowest id perentity/category/content/context/tags group, with Postgres- and SQLite-specific grouping)
and purges orphaned
type='observation'rows from thesearch_indexFTS 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:
(
BASIC_MEMORY_DATABASE_BACKEND/DATABASE_URL, docker-compose example); today allPostgres framing is test infrastructure (fix(core): make local Postgres backend usable (migrations, pooling, default project) #1018 makes it supportable).
semantic_vector_indexrequiresbm reindex --embeddings(incremental suffices); document the pre-reindex degradedwindow (vector empty, hybrid silently FTS-only, Vector search silently degrades when configured index has no ready manifest rows #1236).
basic-memory[milvus]; Milvus Lite.dbURI realities(suffix required, parent dir must exist, creates a directory tree);
milvus_databaseignored by Lite; stale pgvector projection rows remain after a switch (manual cleanup /
disk cost); positive claims worth stating: identical rankings across backends, good
dimension-mismatch and missing-extra errors, 30s timeout honored, collections reload
(fix(core): load existing Milvus collections after restart #1185).
selection,
reranker_candidates/max_document_charslatency tradeoffs, first-run modeldownload (feat(core): add optional cross-encoder reranker stage to search #1143).
New/updated pages derived from the rest of the release:
bm configCLI reference (get/set/list/unset, env override marking) (feat(cli): add bm config command group for get/set/list/unset #1088).bm hook+ harness capture: lifecycle verbs, capture WAL, flush/status/install/remove,captureEventsopt-out, standalone (non-plugin) install flow (Capture Claude/Codex harness events as Basic Memory producer envelopes #997/feat(core): add bm hook producer front door for harness capture #1070/feat(plugins): surface hook capture setup and health #1119).bm cloud sharereference (create/list/update/revoke) (feat(cli): add bm cloud share command group (create/list/update/revoke) #965).bm cloud prune+ the new one-way-sync deletion of newly-ignored files — prominentbehavior-change callout (fix(cli): remove newly-ignored files from cloud on one-way sync and add bm cloud prune #1061).
basic-memory[redis],redis_url) (perf(api): add optional Redis read caching #1168/perf(api): add Redis-cached QUERY and MCP reads #1172).basic_memory_diagnostics; removecanvas,cloud_info,release_notes; ChatGPT tools OpenAI-client-only;edit_notemetadataparam andreplace_section/replace_subsectionssemantics;list_directorypagination;external_idfields (feat(mcp): add basic_memory_diagnostics tool for version and system info #963, refactor(mcp): remove canvas tool and resource write endpoints #1111, feat(mcp): first-connect onboarding — instructions, empty-state guidance, getting_started prompt #1145, fix(mcp): gate ChatGPT compatibility tools by clientInfo #1035, feat(mcp): add metadata param to edit_note for frontmatter updates #1090, fix(core): make replace_section level-aware with replace_subsections opt-out #1063, fix(mcp): paginate directory listings #1082, feat(mcp): expose note external_id in list_directory and recent_activity output #1040/feat(api): add owning-entity external_id to search results #1101/feat(mcp): emit external_id in search_notes markdown output #1103).sync_*→index_*rename note; removedthread-pool keys; SQLite pragma tuning guidance.
created/modifiedhonored (feat(core): honor note timestamps from frontmatter #1100); observation categoryrules (timestamps/checkbox markers rejected, fix(core): reject timestamp and checkbox-marker observation categories #1239); filename-twin rejection (fix(core): reject equivalent note filenames #1081).
bm tooloutput styles (cli_output_style,--plain,--json) (feat(cli): add Rich human-readable output to bm tool commands #967).requirement on index switch, extras, FastMCP 4 note.
Release-notes skeleton (draft ordering)
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)
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)
bm configget/set/list/unset; Richbm tooloutput;basic_memory_diagnostics; redesignedbm status; usable local Postgres; SQLite tuningpragmas. (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)
bm cloud share; one-way sync honors.bmignoredeletions +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)
bm hookfront door, default-on envelope capture, pluginhealth 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)
edit_notemetadata; paginatedlist_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)
ChatGPT tool gating, config renames/removals, one-way-sync deletion semantics, frontmatter
timestamps, observation category strictness, reindex-on-index-switch. (section above)
races, Hermes env/process leaks, doctor/event-loop, project list, etc.)