goldziher/basemind
Full AI context layer over MCP — tree-sitter code-map, document RAG (PDF/Office/HTML/email + OCR + reranker), shared agent memory, on-demand web crawl, git history + blame + per-symbol diff. 300+ languages, 10+ coding-agent harnesses, content-addressed Fjall + LanceDB.
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
[0.29.2] - 2026-10-12
Added
- Agent Plugins distribution (#69). The repository
root now carries a conformant Agent Plugins 1.0.0 package (
plugin.json,mcp.json,skills/), generated by ai-rulez (generate --plugin) and checked byvalidate --strict. Its MCP server runsbasemind servefromPATH.
Fixed
- A daemon from an older build that ignores
Stopno longer wedges new sessions with "a previous basemind comms daemon ... did not stop": the takeover now escalates to SIGTERM, then SIGKILL, by pid.
[0.29.1] - 2026-10-11
Patch release: 0.29.0 shipped everywhere except crates.io, which rejected it. No index or blob format change.
Fixed
basemind0.29.x can be published to crates.io. The vendored forksbasemind-lsp-positions,basemind-tree-sitter-graphandbasemind-tree-sitter-stack-graphshad moved to tree-sitter 0.27 without a version bump, so crates.io still served their 0.26 builds andbasemindcould not resolve a singletree-sitter. They are now 0.3.5, 0.12.1 and 0.10.1.- Windows build: an unused constant and unused test imports failed the
-D warningsbuild; unix-only test fixtures no longer compile on Windows.
[0.29.0] - 2026-10-09
0.29.0 reshapes the CLI (one home for each command, a documented exit-code contract, honest output and
daemon forwarding), makes the code map code-only with a separate document tier, adds a trigram prefilter
for code grep, MCP resources, and a more robust comms protocol (v4). Read Breaking changes and
Upgrading before installing.
Breaking changes
-
First scan after upgrading rebuilds the index.
RELEASE_MINORgoes 28 to 29, so every view'sindex.msgpackand Fjall index are reset on open and every file is re-extracted (blobs are overwritten in place at their content-hash path; the git cache refills lazily). The LanceDB document tables and the blob store are not wiped; orphaned blobs are reclaimed by the post-scan sweep. Stop the old daemon first withbasemind comms stopso the new binary starts its own. -
CLI renames and removals, no aliases kept (the MCP
admintool is unchanged):Removed Use instead admin statusstatusadmin rescanrescan(also takes--no-git-history,--rebuild-git-history; runs the root guard)admin cache-stats/admin gc/admin cache-clearcache stats/cache gc/cache clearadmin delta/admin checkpoint/admin wastedelta/checkpoint/detect-wastegraph map --include-churngraph map --no-churn(churn is on by default)serve --git-cache-mem,--no-git-cache-disk,--no-watch,--documents-*none; serveis a stdio relay to the daemon and ignored themadminkeepsrepo,telemetry,compress,tokensandeval. -
Exit-code contract:
0ok,1error,2usage,3writer lock held by another process,4required daemon unreachable,130interrupted.scanandrescanused to print the lock notice and exit0; they now exit3. An invalidrescanpath (outside the repo, or neither on disk nor indexed) and invalid tool parameters exit2. -
cache clearneeds--yes(or a terminal confirmation) for every component exceptgit-cache, refuses with exit3while a writer holds the workspace lock, and refusesblobswhile the daemon runs.blobsis the machine-global store shared by every workspace. -
cache gcdeletes;--dry-runreports. The oldadmin gcreported whilecache gcwas a no-op. -
Output changes: the human-mode timing footer moved from stdout to stderr (
--jsonkeepselapsed_us/startup_us); human output no longer truncates strings to 200 characters (only table cells are shortened, and stderr says so). -
--formataccepts onlyjsonortoononcode semanticandmemory documents; the other closed-set flags are now value enums (see Added). -
agents/workspace--jsonreshaped to the MCP response types and pretty-printed.agents listgainsdescription,skills,first_seen,last_seen; threads uselast_activity_micros; message rows gainage_secsandbody_sha; empty optionals are omitted. -
hook installis safer, so old hooks should be refreshed. It asks git for the hooks directory (honourscore.hooksPathand linked worktrees), refuses to overwrite a pre-commit hook it did not write unless--force(the old one is kept aspre-commit.bak), and the generated hook is fail-open (exit 0 whenbasemindis missing or the scan fails). Re-runhook install. -
Code map is code-only. Markdown, reStructuredText, AsciiDoc, vimdoc, CSV, JSON, YAML, TOML, INI, XML,
.properties,.env, diffs,.gitignore/.gitattributesand Fluent files go to the document tier (searchable withmemory documents), sosymbols,outlineandgrepno longer return README headings or config keys.[documents] include/excludeand the MIME allowlist gate them. Builds without thedocumentsfeature keep the old behaviour. -
code outline/symbolsno longer index names bound inside function, method or lambda bodies (locals, nested functions); module- and class-level definitions are unchanged. Value-like symbols store a shorter signature (MAX_RETRIES = 3becomes= 3; initialisers cut at 80 characters). -
MCP logging is retired (SEP-2577): no
loggingcapability, nologging/setLevel, andrescanno longer emits therescan_completelog notification. Counts are in the tool result; progress notifications are unchanged. -
MCP
memorytool schema dropped ~26documents.*/llm.*override fields (llm_api_key,llm_model,reranker_*,embedding_preset, ...). They remain CLI flags, env vars andbasemind.tomlkeys;memorymodedocumentskeepsformat,max_tokens,mime_type,scope,entity_categoryandkeywords_contains. No API key travels as a tool argument any more. -
agentswaitis capped at 40 s (was 300). -
Comms protocol version 4: requests carry ids. Stop the old daemon (
basemind comms stop) after upgrading so clients and daemon speak the same version.
Added
basemind doctor(root, config, index, grammars, pre-commit hook and daemon checked with filesystem and process probes only;--json; exits 1 when a check fails), top-levelstatus,completions <shell>(bash, zsh, fish, elvish, powershell) andman.hook install --force,cache gc --dry-run,delta --new <FILE>,detect-waste --log <FILE>.- Value-enum flags (parse-time errors listing the choices, shell completion):
graph--direction/--edges/--algorithm/--granularity/--format,code semantic--lane/--format,git search --field,git symbol-history --hash-mode,memory proposals --kind,admin telemetry --window,admin compress --level. - CLI pagination and size controls passed straight to the MCP params:
--cursoroncodesymbols / grep / files / find / references / callers / implementations,gitrecent / search / touching / by-path / blame / blame-symbol / symbol-history andmemorylist / proposals;--max-tokensoncodeoutline / symbols / grep / files / find / references / callers / implementations / semantic andmemory documents;--rerank-top-koncode semantic;--format json|toonon thecodelist modes andmemory documents. - CLI forwards to a running daemon:
memory, references / callers / graph reads and the grep prefilter no longer fail withmemory_by_key index not availableor degrade to a truncating in-RAM projection. With no daemon and another process holding the index lock, the error names the remedy and exits3. - Tool subcommand paths (including
gitpaths andgraph --focus) normalize to the repo-relative index key: absolute,./-prefixed and relative spellings are the same file.rescanalso validates paths and routes through the daemon instead of racing it. - CLI/MCP parity test (
tests/cli_parity/): a capability table maps every MCP tool and mode to a CLI command (or declares itcli_only/mcp_onlywith a reason), compares per-mode parameters against reasoned exceptions, and checks that representative reads return equal--jsonpayloads. - MCP resources
basemind://status,basemind://repo/mapand the templatesbasemind://outline/{path}andbasemind://memory/{key}(with{path}completion). Bodies equal the tool results; paths are validated against traversal; listed on demand; no subscribe support. - Human-readable
titleon each of the nine tools. agents posttakes an optionalidempotency_key(generated when omitted); the daemon stores it per (agent, thread) for an hour and returns the original message id on a repeat. The stdio relay replays keyed posts and read-only requests once on a replacement backend instead of failing withbackend_restarted.- Cancellation:
code grep,code semantic,graph map,graph calls,memory documentsandagents waithonournotifications/cancelled(answering-32800 request_cancelled), and cancelling an offloaded task reaches the running tool. Mutating modes (rescan,put, ...) always run to completion. web crawlreports MCP progress per page; taskstatusMessagemirrors rescan and crawl progress.code greptrigram bloom prefilter: one bloom filter per indexed file (newgrep_bloomkeyspace, about 12.8 % of indexed code bytes) lets grep skip files that cannot match a required literal. Results, totals, hit order and cursors are identical to the full sweep, including for files edited since the last scan. No index wipe; blooms are built on the next scan.BASEMIND_GREP_BLOOM=0forces the full sweep. See ADR-0012.EXTRACT_EPOCH: extractor-output revision on every file entry and blob. The next scan re-extracts only stale files, so a patch release can fix extraction without a minor bump. The scan summary showstier_migratedandrefreshed. Epoch 1 refreshes signatures produced before the header-only fix.- Comms:
BASEMIND_COMMS_REQUEST_TIMEOUT_SECS,BASEMIND_COMMS_HANDSHAKE_TIMEOUT_SECS(default 30 s),BASEMIND_RELAY_REQUEST_TIMEOUT_SECS; the detached daemon logs to a size-rotated<comms_dir>/daemon.log, shown bycomms doctor. BASEMIND_BLOB_GC_GRACE_SECSoverrides the 6 hour age below which the blob sweep keeps a blob.
Changed
code findandcode filescover every indexed path (code map plus document tier), labelling documents by grammar name or extension.finddrops candidates scoring under 85 % of the best match. On a 90-task eval: recall 0.59 to 0.83, hit@1 0.58 to 0.82, precision 0.28 to 0.62.- Outline eval precision rises from 0.67 to 1.00 and the mean outline response shrinks from 1384 to 747 tokens with the narrower symbol scope.
- Token-savings estimates are recalibrated from measured eval ratios (outline 1.2x, symbols 25x, references and implementations 1.2x, docs 2.5x; callers, dependents, find and files claim no saving).
- Upgrading is self-cleaning: the blob sweep is tier-aware, and
cache gcperforms it (serialised machine-wide bycache/gc.lock; blobs younger than 6 hours are kept). A scan that migrated paths, re-extracted blobs or reset a view printscleanup: reclaimed N orphaned blob(s); the daemon does the same a minute later. The MCPadminmodegcstays a non-destructive report. Seedocs/UPGRADING.md. - Performance:
symbolsanddependentsanswer from a resident term index;references,callersandimplementationsuse a resident name dictionary (needles matching over 5 % of names fall back to the full walk);code grepreuses a per-thread read buffer. Results are unchanged. - Comms daemon runtime has 8 workers (was 4) and liveness probes run off the async workers.
- The production
rmcpdependency drops its default features. Dependencies upgraded:lancedb0.37 to 0.40 (with itsremotefeature;lance10 to 13),gix0.89,oxc_*0.153,crawlberg1.10,xberg1.3.8 andtree-sitter-language-pack1.21.3 (both now chunk in linear time, so the quadratic stalls behind the Fixed entries below are fixed at the source; the cutovers stay as a backstop),zstd0.14.2, plus lockfile refreshes.arrow-*stays on 58 to matchlancedb0.40. - Eval generators follow the new symbol scope;
gen_find.pytakes--indexed FILE.
Fixed
- CLI
--format tooncrashed the renderer; human mode now renders the TOON response. code references,callersandimplementationsfrom a daemon-backed session are complete. They used to answer from an in-RAM projection capped at[resources] max_map_cache_mband returned a truncated lower bound behind aprojections_cappednotice. The scan is now forwarded to the daemon (IndexRead); the projection is a last-resort fallback only.- Ctrl-C during
scan/rescanstops at a file boundary (completed batches stay committed, the stale purge is skipped), prints the summary and exits130; a second Ctrl-C exits immediately. - Comms unresponsiveness: replies are correlated by request id, so an abandoned request (such as the
delivery-notice probe timing out) can no longer leave a stale reply misread as
unexpected response shape; requests and handshakes time out with a retryablecomms: broker unresponsive, the relay answersbackend_timeout, and a slow connect for one identity no longer blocks the others. - Comms daemon stalls under slow disk: blocking
CommsStorecalls and registry writes run on the blocking pool (mutations stay serialized through one write gate, soseqorder holds), and the hourly retention sweep moved off the runtime. - The inbox read bounds its unread scan (
unreadis a lower bound past roughly 500 rows) and the delivery probe uses its own connection, at most once every 2 s. agents waitstops as soon as the host cancels or the connection closes, and no longer returns instantly on messages a previouswaitalready returned.- A comms or shells daemon starting as the previous one exits no longer mistakes a just-released lock for a live peer and exits silently (the lock waits up to 300 ms).
- Symbol signatures in indentation-based languages end at the grammar's body field instead of embedding
the whole body; existing indexes refresh through
EXTRACT_EPOCH. - macOS: the heap no longer shows up as GBs of "GPU" memory; mimalloc's default Mach VM tag 100
(
VM_MEMORY_IOACCELERATOR) is retagged to 254 at startup. - A panic inside an embedding job no longer aborts the process; it surfaces as an
embedding worker panickederror. - Flaky
daemon_lockandstore_layouttests (a sibling test's fork/exec holding a released flock; a test mutating the process-globalBASEMIND_DATA_HOME). - Stale docs:
serveflags,memory mineflags,--jsonhelp text and the admin command tables.
[0.28.1] - 2026-10-06
Added
basemind admin eval: a repo-agnostic retrieval-quality and token-savings evaluation. It runs a JSONL task file (modessymbols,outline,references,callers,grep,find,dependents,git_search,docs) through the same in-process tool code the MCP server uses, scores answers against gold (set P/R/F1; hit@1/@5, MRR and nDCG for ranked tasks), records per-call latency and response tokens, and reports per-mode p50/p95 latency and quality as NDJSON, JSON and markdown. Tasks may carry abaseline(grep command and/or whole-file reads); its output is counted with the same tokenizer and savings are only credited when recall reaches--min-recall, then compared with the fixed multipliers insrc/mcp/savings.rs(modes deviating more than 25% are flagged).--baseline <report.json>exits non-zero on a regression beyond--tolerance/--cost-tolerance.benchmarks/eval/: stdlib-only Python gold generators (gen_symbols,gen_outline,gen_references,gen_dependents,gen_grep,gen_find,gen_git_search,gen_docs), deterministic for a fixed HEAD sha and seed, with a workflow README.
Changed
[resources] onnx_provider(new) defaults tocpu. basemind previously left ONNX Runtime on its platform default, which on macOS is CoreML. CoreML compiles a new graph per input shape and measured 7.6 GB resident after embedding one 2 KB SVG, against 0.8 GB on the CPU provider (which is also ~3x faster end to end here); a scan of a repository with images and office documents grew to 33 GB. The setting covers the embedder, reranker, layout detection and NER. Setonnx_provider = "auto"to restore the old platform default, or"coreml"/"cuda"/"tensorrt"to pin one. Existing configs keep parsing; only the resource behaviour changes.- The document tier now serialises over the memory ceiling.
FootprintGate::admit(unchanged, still advisory for the drive governor and large-file parses) admitted every waiter after five seconds, so N workers overmax_footprint_mbmeant N simultaneous extractions. Documents use the newadmit_exclusive: over the ceiling exactly one document runs at a time, and an otherwise idle process is admitted immediately instead of stalling every item for the full wait. [documents] max_archive_bytes(default 64 MiB) andmax_archive_entries(default 1000) bound archive extraction whenextract_archives = true(xberg's own defaults were 500 MB and 10,000).
Fixed
- Stack-graph (L2) resolution no longer drives scan memory: each file is abandoned past 600,000 steps, 50,000 partial paths or 3 s, and at most two builds run at once. A wall-clock budget alone let parallel workers stitching 100-200 KB files take a large-monorepo scan from ~2 GB to 9-13 GB.
- A scan could deadlock with
max_concurrent_documentsset: a scan worker holding a document slot waited on the embedding pool withThreadPool::install, which keeps running queued jobs from the worker's own pool, so it started a second document that blocked on the slot the first still held (one scan thread and one slot hangs forever; more threads hang once every slot is held by a blocked frame). The embed hand-off now blocks on a channel and never runs foreign work. - Documents whose own estimated working set exceeds
max_footprint_mbare skipped (counted as too large, with a warning naming the estimate and the limit) rather than extracted: a 7 MB-of-XML.docxmeasured 12 GB with CoreML, and a 30,000 x 30,000 pixel image header is priced from its dimensions, not its byte size. - A large text document could stall a scan for tens of minutes. xberg's markdown splitter (before 1.3.8) re-collects
and re-sorts every remaining element for each chunk it emits, so chunking time grows with the square of
the document (measured: 0.9 s at 256 KB, 3.3 s at 512 KB, 11.6 s at 1 MB, 40 s at 2 MB of log text, and 17 s at 256 KB of
densely marked-up markdown, in a debug build). 0.29.0 routes csv, json, yaml, xml, txt and similar files to the document tier, so
multi-megabyte data files reached it. Text-like documents over the new
[documents] markdown_chunk_max_bytes(default 256 KiB) /plain_text_chunk_max_bytes(default 128 KiB) cutovers are now chunked by a linear fixed-size chunker; smaller documents chunk exactly as before. Degraded documents are logged and counted asdocs_degradedin the scan summary. [documents] extraction_timeout_secsis now enforced. It was passed to xberg but could not interrupt CPU-bound work. Extraction now runs under a watchdog: an overrunning document is abandoned, logged, skipped, retried on the next scan, and counted asdoc_timeouts(also infailed).
[0.28.0] - 2026-10-04
Minor release — persisted schema bumped (
RELEASE_MINOR27 → 28). The firstbasemind scanafter upgrading wipes and rebuilds the on-disk cache (blobs, the Fjall index, vector rows) from source. No manual step is needed; a full re-scan is automatic.
Added
- Project-level
.config/convention (https://github.com/pi0/config-dir): configuration is now also discovered at.config/basemind.tomlor.config/basemind/config.tomlwhen no rootbasemind.toml(or legacy.basemind/basemind.toml) exists. The root file still wins when both are present. Any of these locations now counts as the durable "this is a basemind-managed root" marker for upward root discovery and the workspace-root guard, so a project that keeps its config under.config/no longer needs--root. basemind init --config-dir: scaffold the config under the.config/convention. Accepts.config(writes.config/basemind.toml) or.config/basemind(writes.config/basemind/config.toml); any other value is rejected, since there is no read-time config-path flag and a config written elsewhere would never be auto-discovered.[languages.<grammar>]per-grammar overrides keyed by tree-sitter-language-pack grammar name:enabled = falsestops parsing that grammar's files as code (they fall through to the document tier; fixes.txtdetected as vimdoc and.confas nginx),extensions/filenamesmap extra suffixes and exact file names onto a grammar ahead of built-in detection, andpreloadmakesbasemind lang installfetch it. An unknown grammar name is a config error with near-match suggestions.[scan] floor_allow: remove entries from the always-on exclude floor by directory, file name, glob or floor pattern..gitand.basemindcan never be allowed; an entry naming nothing in the floor is ignored with a warning.[documents] include/exclude(which non-code files become documents),max_file_bytes(per-document cap, default 50 MiB, independent of[scan] max_file_bytes), andembed_includealongside the existingembed_exclude;[code_search] embed_includewith the same semantics (allow-list,embed_excludewins, left-out files stay keyword-searchable).- Cached chunks and documents are fingerprinted against the settings that shape them, so changing a
chunk-size, page-cap, keyword, NER, summarization or
[llm] modelsetting re-processes only the affected files. The first scan after an embedding-scope change (embed,embed_include,embed_exclude, theenabledswitches) deletes the vector rows of files that are no longer eligible and rebuilds the rows of newly eligible ones from cached blobs without re-embedding. - The shared daemon loads each workspace's config once, applies operator-raisable resource ceilings
(
BASEMIND_DAEMON_MAX_SCAN_THREADS,_EMBED_THREADS,_EMBED_BATCH,_CONCURRENT_DOCUMENTS,_FOOTPRINT_MB,_MAP_CACHE_MB,_CANDIDATES,_FILE_BYTES,_DOCUMENT_BYTES,_DOCUMENT_PAGES,_EXTRACTION_SECS,_CHUNKS_PER_DOCUMENT,_CRAWL_PAGES,_CRAWL_DEPTH,_CRAWL_BODY_BYTES, and theBASEMIND_DAEMON_MIN_DEBOUNCE_MSfloor), and reloads a workspace'sbasemind.tomlon the next request after it changes. A file that no longer parses keeps the last good config and logs a warning.admin statusreportsconfig_stampto spot an edit. - Operator environment grants for settings a repository's own
basemind.tomlcannot set itself:BASEMIND_ALLOW_REPO_LLM,BASEMIND_ALLOW_FOLLOW_SYMLINKS,BASEMIND_ALLOW_EXTRA_ROOTSand, in the daemon,BASEMIND_DAEMON_ALLOW_EXTRACT_ARCHIVES.BASEMIND_ALLOW_EXTRA_ROOTStakes1/true/yesfor every workspace, or a:-separated list of absolute workspace roots that grants only those (;on Windows); relative entries are ignored with a warning. - A new linked worktree's
workingview is seeded from a sibling checkout with a copy-on-write clone, so its first scan only touches files that differ. A sibling whose workspace lock is held, or one too large to copy without reflinks, is skipped; a crashed clone's.seed-tmpdebris is swept on open.BASEMIND_NO_SEED=1opts out. - A linked worktree whose HEAD descends from the indexed git-history head now appends to the shared index instead of skipping it.
- Reserved config keys that nothing reads log a warning when set to a non-default value.
Changed
- Bare names in every glob list (
[scan],[documents],[code_search]include/exclude/embed_*) are gitignore-like:generatedmatches that path segment at any depth and everything beneath it, anddocs/apiis anchored at the root. A leading./or/and a trailing/are ignored. A negated (!) pattern, an invalid glob, or an empty[scan] includeis a config error. Forextra_rootsfiles, globs match the path relative to the extra root. - Repo-supplied
[llm] base_url, foreignapi_keyenv references,[crawl] allow_private_network,[scan] follow_symlinksand[scan] extra_rootsnow need an operator grant in the environment (BASEMIND_ALLOW_REPO_LLM,BASEMIND_ALLOW_PRIVATE_HOSTS,BASEMIND_ALLOW_FOLLOW_SYMLINKS,BASEMIND_ALLOW_EXTRA_ROOTS). Anapi_keyenv reference is still honoured for the chosen provider's standard variable andBASEMIND_LLM_API_KEY. BASEMIND_ALLOW_PRIVATE_HOSTSaccepts1,trueoryesin both the config gate and the URL guard (the URL guard previously accepted only1).- The daemon treats
[resources],[scan] max_candidates/max_file_bytes,[documents]limits,[crawl]limits and[watch] debounce_msas ceilings or floors, and clamps the per-callwebcrawlmax_pages/max_depthoverrides to the same caps. - mimalloc is the global allocator. ONNX Runtime memory is bounded in every process (no memory-pattern planning, no retained CPU arena; intra-op threads capped), and the daemon releases resident embedding engines once the last concurrent embedding pass ends.
- The daemon caps its runtime and pool threads, serializes read-stack builds, defers the startup GC, bounds fjall worker threads and journal size (flushing an oversized journal on open), and runs git-history index sync on a dedicated two-thread pool.
- Blob chunk state is read from the envelope header alone instead of decoding the blob.
- Changing a reserved key (
[documents.language] preferred_languages,[documents.ocr]) no longer re-extracts every document. basemind.tomlscaffold comments written bybasemind initdescribe the new keys, the credential floor and the operator grants.
Fixed
- Linked worktrees no longer wipe and rebuild the shared git-history index when they switch branches.
- The file watcher rebuilds its include/exclude filter when
basemind.tomlchanges, and detects an equal-length rewrite of the config inside one timestamp tick. - Incremental scans (watcher,
rescan paths) evict files that became ineligible, skip symlinks, and cap the bytes read. - The embed policy is recorded only after a complete purge, and keyword postings are rebuilt when needed; empty-scope lance deletes are skipped and large ones are batched at 2000.
- Extraction returns an error instead of panicking when its runtime cannot be built, and the git-history pool falls back to the caller's thread instead of panicking under thread exhaustion.
- Re-flushing an external-root (
extra_roots) document replaces its existing vector rows instead of leaving stale duplicates: the delete now uses the document's own per-root scope, matching the rows it inserts. - The last concurrent embedding pass releases resident engines under a lock, so a pass that starts concurrently cannot have the engines it is about to use freed underneath it.
Security
- Credential and key material is excluded from the index by default, so no secret becomes
searchable by every agent that can query it:
.envand.env.*(including.env.example),.aws/,.ssh/,.gnupg/,.npmrc,.pypirc,.netrc,.git-credentials,id_rsa/id_dsa/id_ecdsa/id_ed25519,*.pem,*.key,*.p12,*.pfx,*.jks,*.keystore.[scan] floor_allowopts a class back in, but a credential entry needs the operator'sBASEMIND_ALLOW_REPO_CREDENTIALSgrant — a cloned repository's ownbasemind.tomlcannot un-exclude the operator's untracked secrets (build-artifact entries still need no grant). follow_symlinksis an operator decision: the repository's ownbasemind.tomlcannot turn it on. Working-tree reads, including the watcher andrescan paths, refuse symlinked files and paths that resolve outside the workspace, and abasemind.tomlthat is a symlink leaving the workspace is not followed.- Repository-authored
basemind.tomlcan no longer redirect an LLM endpoint or read an arbitrary environment variable as an API key, reach private hosts, follow symlinks, index directories outside the repository, or expand archives in the daemon, without an operator grant (see Changed).extra_rootsadditionally refuses credential directories (.ssh,.aws,.gnupg,/etc), filesystem roots and roots inside the repository, grant or not. - Daemon resource ceilings bound what a hostile repository config can cost: scan and embed threads, batch size, footprint, candidates, file and document sizes, pages, extraction time, chunks and crawl pages, depth and body size.
Removed
- The TUI and agent stack was removed upstream and is not part of this release: the
basemind-tui,basemind-agent,basemind-agent-ipcandbasemind-uicrates, thebasemind uiandbasemind agentsubcommands, theagent-tui/desktop-uifeatures, and the[agent]config table (now rejected as an unknown key; delete it frombasemind.toml). None of it was ever part of a release archive: theagent-tui/desktop-uifeatures were omitted fromfulland the release workflow built only thebasemindbinary.basemind agents(multi-agent comms) is unrelated and unchanged. ADR-0006 is marked withdrawn.
[0.27.4] - 2026-09-28
Patch release —
initdefaults to local/gitignored config, a benchmark harness, and a read-only-store flush fix. No schema or MCP-surface change:RELEASE_MINORstays 27, so the on-disk blob and index formats are untouched.
Added
basemind initnow defaults every writable target to its.local/gitignored form: an ai-rulez config routes to.ai-rulez/local/rules/basemind-usage.mdinstead of the committed rules tree, and a new.gitignore-coverage check prompts (or, with--yes, auto-appends) a pattern for any.localtarget.gitignoredoesn't already cover.- A new
.claude/settings.local.jsonstep ininit, adding the basemind MCP permission entries via a surgical JSON merge (serde_json'spreserve_orderfeature) that only touches the keys basemind owns — an existingskillOverridesor other hand-ordered content in that file is left untouched. basemind admin tokens --stdin: counts stdin with the real o200k tokenizer (src/mcp/tokens.rs) and prints just the integer. Gated behind thetokenizerfeature (already implied bydocuments), not the heavierdocuments/code-searchbuilds.benchmarks/: a harness (run.sh+tasks.example.yaml) that runs a fixed task list twice — once via abasemindcommand, once via the equivalentgrep/cat/findbaseline — and reports real token counts (viaadmin tokens --stdin) and wall-clock time for each side, plus totals. Seebenchmarks/README.md.
Fixed
admin rescan/scan_and_refreshcould fail with ENOENT onviews/working/index.msgpack.tmpon a workspace's first scan, whenever theStorewas opened viaopen_read_only(which — unlikeopen_with_holder— never createsview_diron disk). The MCPadmin rescantool and its CLI shim both open theirStorethis way, so this reproduced on any workspace's first-ever scan through either surface. Fixed with a newStore::ensure_dirs(), called insidescan_and_refreshright after acquiring the write lock, so a store opened read-only still gets its rebuildable directories materialized before the firstflush().Store::open_with_holdernow acquires its lock before creating rebuildable cache subdirectories, closing a lock-ordering gap that could race a concurrent opener.
[0.27.3] - 2026-09-27
Patch release — the first publish that completes on every platform. Supersedes the unpublished v0.27.1 and v0.27.2 drafts: no crates.io, npm or PyPI version was cut from either.
Fixed
- The release helpers no longer pass
gh apian endpoint with a leading slash. Git Bash on the Windows runner rewrites an argument beginning with/into a filesystem path, sogh api "/repos/…"becameC:/Program Files/Git/repos/…and the call failed with invalid API endpoint. The Windows build job hit this on its pre-upload resolve and skipped the upload, andfinalize_releaseruns on every leg viaalways(). The endpoints now omit the leading slash, as the CLI's own error recommends; the absoluteuploads.github.comURL is unaffected.
[0.27.2] - 2026-09-27
Never published as a GitHub release. The Linux builds were fixed below, but the Windows upload and the promotion still failed, so no registry version was cut. Superseded by [0.27.3].
Fixed
- The
shellsfeature no longer fails to compile on Linux.rustixwas refreshed to 1.1.5, which renamed its experimentalruntimemodule to a rotating mangled name (runtime_448b8ad740e2a26f) and reduced the oldruntimepath to a private alias.rmux-client0.10.0 importsrustix::runtime::{…}from its Linux attach-resize path, so only the Linux builds broke (E0603); macOS and Windows were unaffected.rustixis now held at 1.1.4, the last release whoseruntimemodule is public and the versionrmux-clientitself declares.
[0.27.1] - 2026-09-27
Never published as a GitHub release. The source below was tagged and pushed, but the Linux archives failed to build and the release record was never promoted. It is superseded by [0.27.3]; no registry version was cut from it.
Patch release — memory-safety follow-ups to #62, a stricter plugin launcher, and a release-workflow fix. No schema or MCP-surface change:
RELEASE_MINORstays 27, so the on-disk blob and index formats are untouched.
Changed
- Dependency refresh:
crawlberg1.7.1 → 1.8.0,gix0.87.1 → 0.88.0,jsonschema0.56 → 0.58, andlru0.18.4 → 0.18.5.lancedbstays held at 0.37: 0.38 and 0.39 still gate theError::Httpvariant behind theirremotefeature whilejob.rsreferences it unconditionally, and enablingremotewould pull an HTTP client stack in for no benefit here.
Fixed
- Comms daemon memory growth (#62), continued:
- The per-connection comms client map is now bounded with an LRU. It is keyed by the
caller-supplied
as_agent, so it previously grew one broker socket and one notification queue for every identity a session ever named. - The pending-notification queue is now capped, dropping the oldest entries. Clients that only issue ordinary requests never drain it, so it grew for the life of the link.
- The per-connection comms client map is now bounded with an LRU. It is keyed by the
caller-supplied
- The git-cache RAM categories are now charged by bytes as well as entry count, each held to
32 MiB. A single
CommitInfoorBlameResulthas no natural size, so 1024 entries could reach gigabytes. - The file watcher's debounce queues are bounded, and overflow coalesces into a path set instead of queueing without limit. A rescan slower than the debounce interval could otherwise let batches accumulate unbounded.
gix0.88 no longer accepts theInfallibleturbofish, whichsrc/git/commit.rsstill passed.- The plugin launcher never falls back below 0.26.0, verifies each candidate is installable, and
honours
BASEMIND_BIN. With v0.27.0 incomplete, the old fallback exec'd 0.25.2, which predates the #62 memory bounds and OOM-killed the machine. - The release workflow now addresses assets by release id rather than by tag. A hand-promoted v0.27.0 draft left two records on one tag and split the archives across them; uploads replace in place, and every stage refuses an ambiguous tag.
[0.27.0] - 2026-09-18
Minor release — a toolchain dependency refresh and a lint-driven hardening pass. The agent tool surface and the config schema are unchanged, but
RELEASE_MINORadvances 26 → 27, so the first scan after upgrading rebuilds the content-addressed blob cache automatically.
Changed
- tree-sitter 0.27 (was 0.26), with tree-sitter-language-pack 1.20.0 to match. Two breaking API
moves were adapted:
QueryMatch.capturesbecame the methodQueryMatch::captures(). The L1/L2 extractors (src/extract/l1.rs,src/extract/l2.rs,src/extract/locals.rs) and the intel resolver (src/intel/stackgraph.rs) were migrated.Node::kind()now returns a tree-borrowing&str. The vendored tree-sitter-graph keepsSyntaxNodeRef's kind'staticby interning node-kind names into a small global table (crates/tree-sitter-graph/src/graph.rs).
- rmcp 3.4 (was 3.2). The handshake types that collided with the wire protocol's
serverInfo/clientInfofields were renamed:ServerInfo→ServerConfig(src/mcp/server_handler.rs) andClientInfo→ClientConfig(tests/tasks_smoke.rs). The server still advertises MCP logging; that capability line now carries#[allow(deprecated)]until the SEP-2577 deprecation removes logging in a future rmcp. - jsonschema 0.56 (was 0.52), crawlberg 1.7.1 (was 1.5.0), lru 0.18.4 (was 0.18.3),
zstd 0.14.0 (was 0.13.3), and liter-llm 2.0.2 in the agent crate (was 1.19.1).
lancedbstays pinned at 0.37: 0.38 and 0.39 still fail to compile without an HTTP-client stack basemind has no use for. - The provenance ledger in the layered config merge (
src/config/layered.rs) is no longer inlined per field. The merge now records a field'sConfigSourcethrough onerecord_provenancehelper — which also skips the ledger entirely on the MCP override path that throws it away — and the merge's cyclomatic complexity drops from 43 to under the 40 ceiling. The two override tests pin the behaviour.
Fixed
- The npm and pip installers' network paths were lint-harded:
npm-package/install.jsno longer returns from a Promise executor or reads the deprecatedRegExplast-match globals (parseInt→Number), and its thrown errors carry{ cause };pip-package/basemind/ downloader.pymoves the type-only import underTYPE_CHECKING. - Typos in tests (a misspelled
repositoriesin a test name and a stray fixture path) and a danglingreturn;inscripts/codex-mcp-launch.mjs. The opencode plugin's console fallback explains why it exists instead of carrying a bare suppression directive (no-consolewas already ignored for JS inpoly.toml, so the rule it guarded never fires). poly.tomlgained per-file ignores for the vendored fork crates and forCHANGELOG.md(length is intentional), and the shipped shell scripts were re-rendered by the shell formatter — whitespace-only, no behaviour change.
[0.26.0] - 2026-09-01
Minor release — cache rebuild, breaking changes, and a security fix. Read Breaking changes and Security before upgrading. Almost all of this release is issue #62: a comms daemon reached 43.8 GiB RSS in about two minutes on an initial scan of a large monorepo and was OOM-killed, taking a VS Code renderer with it; under a 2 GiB
MemoryMaxthe same scan died in about 50 seconds.
Breaking changes
- A workspace root must be a project. It has to be a git repository, or a directory containing
basemind.toml. Anything else is refused, and a filesystem or volume root is refused with no override at all. If you hit this: runbasemind initin the directory (writesbasemind.toml, the durable marker), or setBASEMIND_ALLOW_ANY_ROOT=1for that invocation. A root that already had a working-view index before the upgrade keeps working untouched. [scan] extra_rootsis ignored unlessBASEMIND_ALLOW_EXTRA_ROOTS=1is set in the environment that launches basemind. If you hit this: export the variable where your MCP host or shell starts basemind — it deliberately cannot be granted from the scanned repository's own config.[resources] max_footprint_mb = 0now means auto, not disabled. Every config generated before this release carries that value, so upgrading gains a memory ceiling rather than keeping none. If you want the old behaviour: writemax_footprint_mb = "off".- Caches are wiped and rebuilt on the next scan.
INDEX_PARTITION_REVISIONadvances 4 → 5, so the Fjall secondary index is recreated on next open — fjall persists a keyspace'smax_memtable_sizeat create time and ignores the create options for a keyspace that already exists, so the new per-keyspace sizing only takes effect on a recreated index.RELEASE_MINORadvances 25 → 26 with the release cut, which also invalidates the content-addressed blob cache and the git cache. No action required: rebuild is automatic, but the first scan after upgrading is a full one. - Git history is no longer indexed for a workspace root that is merely a subdirectory of an
enclosing repository. If you relied on it: set
BASEMIND_GIT_DISCOVER_PARENTS=1.
Security
- The daemon's streamable-HTTP MCP endpoint is no longer unauthenticated, and no longer starts by
default. Versions 0.23.0 through 0.25.2 bound
127.0.0.1:51786unconditionally and gated requests only with aHost/:authorityloopback check. That check defeats DNS rebinding and nothing else: any local process could open a TCP connection, sendHost: 127.0.0.1, and reach the full shared tool router — which in the release build (--features full) includes theshelltool, enabled by default. Every process on the machine that could reach the port could therefore run commands as the daemon's user. Two changes close it:- The listener is opt-in. It binds only when
BASEMIND_ALLOW_HTTPis truthy in the daemon's environment; unset, no socket exists. An environment variable rather than a config key because the daemon's environment belongs to whoever started it, whereasbasemind.tomlis authored by whoever wrote the repository. - When it does bind, every request on both routes (
/mcpand/ui) must present a bearer token — 32 bytes from the OS CSPRNG, drawn per daemon process, checked before routing and before any workspace is named or opened, compared without an early exit. The token is published as the second line of<comms_dir>/http.addr, created mode0600atopentime so it is never briefly world-readable.Authorization: Bearer <token>and?token=<token>are both accepted (a browser navigating to/uicannot set a header).basemind daemon ensurereports the token: on stderr for the human form, so the URL stays alone on stdout andURL=$(basemind daemon ensure)keeps working, and in the object for--json.
- The listener is opt-in. It binds only when
- The Claude and Cursor plugin manifests no longer point at that port. They now launch
scripts/mcp-launch.sh serve --root <project dir>over stdio. A hard-coded loopback port is not addressable to a process: whichever process bound it first is what the client talked to, so a local squatter could silently intercept every tool call, and no credential on the real daemon can fix a client that never reaches it. The stdio relay is path-addressed and0600. The root is now passed explicitly instead of being inherited from the host's working directory — a failed expansion becomes a loud refusal from the root guard, where a wrong cwd was a silent scan of the wrong tree.tests/plugin_manifest_smoke.rspins both properties, including that no manifest hard-codes51786anywhere.
Added
[scan] max_candidates(default500_000,0disables) aborts a scan from inside the walk once more than that many candidate files have been kept — before extraction, before any index write, and before the candidate list itself can grow to hundreds of megabytes. The error names the heaviest contributing directories by their first two path segments, so a vendored or generated tree that slipped past.gitignoreis a one-line fix rather than an out-of-memory kill. (#62)BASEMIND_ALLOW_ANY_ROOT=1accepts any directory as a workspace root, for the cases the new allow-list refuses. It does not override the refusal of a filesystem or volume root, and it applies only to the invocation that sets it: the index a hatched scan produces does not make the root permanently acceptable, so unsetting the variable refuses it again. (#62)[scan] max_candidatesalso bounds the walk itself, not only what it keeps: a walk that visits a generous multiple of that many filesystem entries while accepting almost none of them now aborts with its own error, because a root broad enough to enumerate tens of millions of entries burns exactly the time and syscalls the ceiling exists to prevent. (#62)- basemind now reads the memory limit it is running under, not just its own usage: cgroup v2
(
memory.max, taking the minimum over the whole hierarchy, because systemd putsMemoryMaxon a slice above the scope a process actually lives in), cgroup v1 (with its no-limit sentinel recognised as no limit rather than as an 8-exabyte ceiling),/proc/meminfoon a Linux box with no memory cgroup,hw.memsizeon macOS, andGlobalMemoryStatusExon Windows. Usage on Linux is a working set — reclaimable page cache is subtracted, as kubelet does — so having read a large repository no longer reads as being near the limit. A cgroup that sets no limit — the default for a plaindocker run— falls back to total RAM rather than reporting no ceiling, and a cgroup limit larger than the machine is not treated as the binding constraint. Issue #62 was reported from a process running under a 2 GiBMemoryMaxthat had no way to know the limit existed. (#62) [resources] max_footprint_mbaccepts"off"and"auto"alongside an integer. (#62)[resources] max_map_cache_mb(default256,0= unbounded) bounds the MCP read stack's decoded-outline cache, per workspace. Charged in bytes, not entries: a decoded outline spans three orders of magnitude (a ~1.4 KB median against a measured 697 KB maximum), so an entry cap would bound the count and not the footprint. A miss costs one blob read and never changes an answer. The code-graph memo is charged at half the same budget, and a read-only session's projected call / implementation indexes are charged against it too. (#62)[code_search] max_chunks_per_file(default2000) caps how many chunks one source file may contribute to the search index, mirroring the document tier'smax_chunks_per_document. A generated parser table or a checked-in bundle otherwise fans one file out into a chunk count bounded only by[scan] max_file_bytes, and every chunk costs a BM25 posting entry carrying its full term list, a LanceDB row, and — withembedon — an ONNX embed. An over-cap file is still chunked and cached (the.chunk.msgpacksidecar is written, so outline and grep are unaffected); what it loses is the per-chunk fan-out, with a warning naming the file. (#62)BASEMIND_NO_AUTOSPAWN=1makes basemind connect to a daemon that is already running but never start one, so an operator can guarantee the daemon exists only inside a resource-controlled unit. A working systemd user unit — with install steps, theenvironment.dwiring, and the commands that verify the ceiling is real rather than merely configured — ships atdocs/systemd/basemind-comms.service. Two deliberate exceptions:basemind comms startignores the variable (an operator who typed a start command has stated the intent it withholds from implicit callers), and an already-live daemon is still used — connect-only means do not start one, not do not use one. (#62)- A scan leaves post-mortem evidence a
SIGKILLcannot erase.<workspace_cache_dir>/scan-inflight.jsonis written before the pass starts and removed onDrop, so a surviving record proves the process never ran its destructors — the signature of an OOM kill, which is exactly the case the existinglast-fatal.jsonstructurally cannot report, because the code that writes it never runs. The record carries the pid, the build, the phase, the candidate count, and the root (a root of/is itself the finding).basemind comms doctorlists every such record whose pid is dead — filesystem-only, no RPC, so it stays usable on a wedged machine — and--clear-fatalnow acknowledges stale scan records alongside the fatal-store record. A periodicrss_mb/ceiling_mblog line makes the growth curve reconstructable after the fact. (#62) BASEMIND_GIT_DELTA_CACHE_BYTESoverrides the per-Repositorydelta-base (pack) cache bound;0disables the cache. gix's default whencore.deltaBaseCacheLimitis unset is 96 MiB per thread-localRepository, i.e. per rayon worker during a git-history build — roughly 3 GiB of decoded delta bases on a 32-core host that nothing in basemind accounted for. basemind now sets the key to 8 MiB, as a CLI-precedence override so a repository-localcore.deltaBaseCacheLimitcannot raise the bound we set for our own protection. Undersizing it costs re-decoding, never a wrong answer. (#62)BASEMIND_ALLOW_HTTPgates the daemon's streamable-HTTP MCP front-end — see Security. (#62)
Changed
-
Breaking: a workspace root must now be a git repository or contain
basemind.toml. basemind opens a root read-write and indexes everything beneath it, and root discovery falls back to the starting directory, so an MCP host launched at/could previously hand the daemon the whole filesystem — which is how a scan reached 43.8 GiB and was OOM-killed.basemind initandBASEMIND_ALLOW_ANY_ROOTare the escape hatches, and a root that already had an index on disk before the upgrade is grandfathered so upgrades do not break a working setup. The guard resolves the root — collapsing.., following symlinks — before deciding, and hands the resolved path to whatever opens the store, so a path such as/..can no longer be checked as a subdirectory and opened as the filesystem root. It now also coversbasemind admin rescan(and theadminMCP tool), which walks the whole working tree in-process, and the daemon's git-history sync. There are three refusals, and the error text names every way out of the one you hit: not a project (a plain directory — overridable), filesystem root (/,C:\, a UNC share root — never overridable, by any marker, hatch or pre-existing index), and unresolvable (relative, missing or unreadable, so the guard cannot decide about it at all). It is a misconfiguration guard, not an authorization boundary: it does not defend against a caller who controls the process's arguments or environment. (#62) -
The grandfather clause reads an explicit record rather than inferring consent from a side effect. The guard writes
root-admission.jsoninto the root's workspace cache dir, stampedpre_existing_index(a standing admission that survives with no env var — the upgrade path) orenv_hatch(never a standing admission). Without it, the mere existence of a working-view index was proof of admission, which made a singleBASEMIND_ALLOW_ANY_ROOT=1 basemind scana permanent whitelist for that root: the scan the hatch authorized left behind the very file the next hatch-free run would accept as evidence. The first verdict is the standing one and is never overwritten, and the write is best-effort — a failed write only degrades the guard to its index-means-admitted behaviour and never fails a scan. (#62) -
The HTTP transport's
?root=now runs the same root discovery the CLI does, so naming a subdirectory of a repository attaches to that repository instead of being refused — the two front-ends previously disagreed about what a root is. (#62) -
Excluded directories are now pruned during the walk instead of being filtered per file after the walker has already descended into them, so a non-gitignored
node_modulesis no longer traversed and stat'd in full. The indexed set is unchanged. This also fixes the Linux watcher, which previously registered inotify watches inside trees it was excluding. (#62) -
Breaking:
[scan] extra_rootsnow requires the operator to setBASEMIND_ALLOW_EXTRA_ROOTS=1in the environment. That key names directories outside the repository but is read from the scanned repository's ownbasemind.toml, so cloning and indexing a repo was enough to make basemind walk whatever it named —~/.ssh,~/.aws— and surface the contents through thecode grep/searchtools. The process environment is the one input a cloned repository cannot write, so it is what the grant hangs on. Extra roots also now count toward[scan] max_candidates(they were appended after the ceiling was evaluated), are refused when they resolve to a filesystem or volume root, follow symlinks only when[scan] follow_symlinksis on (it was hard-coded on), and stop when a scan is cancelled. (#62) -
The scanner bounds what it stages into the index by bytes rather than by file count. BM25 keyword postings — one entry per (term, chunk), staged on essentially every scan — previously counted toward no limit at all and could not force a commit; they now participate in the byte budget.
This is the single change that most explains issue #62's number. The old bound counted files, and postings were staged without touching that counter, so the one structure that grows with the product of files and distinct terms was the one structure nothing bounded. The other fixes in this release matter, but they are smaller than their prominence suggests: reverting the chunked drive loop to its previous whole-corpus form, measured at both arms on a 4 000-file corpus, moves peak RSS by ~38 MiB — about 10 KiB per file — because a file's postings are already cleared inside the worker before its result is returned. Worth having at monorepo scale, but not the 43.8 GiB. Directory pruning is a walk-cost win rather than a memory one. Stated explicitly so nobody re-derives the priorities from scratch next time. (#62)
-
Breaking:
[resources] max_footprint_mb = 0now means auto, not disabled. An existing config that carries the old default — as every config generated before this release does — therefore gains a memory ceiling on upgrade rather than keeping none. Auto budgets 75% of an enforced cgroup limit or 50% of total machine RAM, whichever the platform reports, floored at 512 MiB. Writemax_footprint_mb = "off"to restore the previous behaviour, or a positive integer to state a ceiling explicitly. The gate remains best-effort — it parks workers and then admits anyway after five seconds rather than failing a scan — so the change shapes peak memory and cannot make a scan that used to succeed start failing. A long-lived daemon shipping with its only memory bound disabled by default is what made 43.8 GiB reachable. (#62) -
The footprint gate works on Linux and Windows, not only macOS. Its sampler was
sysres::phys_footprint, a Darwin-only metric returningNoneeverywhere else, so on the machine that filed #62 the ceiling could not have throttled anything even if it had been configured. (#62) -
The MCP read stack no longer keeps every file's decoded outline resident for the process lifetime. The old
MapCachewas the onlyO(total symbols)structure with permanent lifetime — plus a second full copy of every import, plus a deep clone of the whole map on every watcher batch, times up to 16 hot workspaces in one daemon. It splits into a permanentO(files)symbol-free index view and a byte-charged read-through LRU governed by[resources] max_map_cache_mb, keyed by content hash so one cache is safely shared across snapshots and identical files share an entry. Building the view now decodes zero outline blobs — it projects the index and probes blob existence in parallel, one stat per file instead of a full read and decode. A read-only session whose projections are truncated by the budget reports it through the existing lifecycle notice withretry: false, because rerunning cannot widen a budget. (#62) -
Per-keyspace Fjall memtable sizing replaces the 64 MiB default on all 17 keyspaces: 16 MiB for the BM25 posting list (the densest writer by far), 8 MiB for the secondary indexes the scanner rewrites once per file, less for the keyspaces a scan barely touches — all within fjall's recommended 8–64 MiB band. This is what
INDEX_PARTITION_REVISION4 → 5 exists to force: fjall persistsmax_memtable_sizeat keyspace create time and ignores the create options on reopen, so an index built by an earlier build would have kept the 64 MiB default forever. (#62) -
The staged-byte counter now models fjall's real per-entry cost (
64 + heap(key) + heap(value), where a payload of 20 bytes or fewer inlines and anything larger carries an 8-byte refcount header). Chargingkey.len() + value.len()undercounted what aWriteBatchactually holds by 2–3×, so both the per-worker batch budget and the process-wide staging ceiling meant something other than what they said. It remains a deliberate lower bound: allocator size-class rounding andVecgrowth slack are not modelled. (#62) -
The resolve pass no longer materialises the whole corpus. It previously built every file's resolved-reference edge set at once — one edge per resolved identifier use — held them through fact staging, then transformed rather than dropped them. The per-file edge set now cannot escape the file that produced it (there is no field to retain it in, so keeping the corpus set is a compile error), and the serial index writers gained the same byte bounds the parallel workers already had, closing the last staging path with no byte bound at all. (#62)
-
basemind scan/rescanstream each file's line as the scan absorbs it instead of accumulating one result per file for the whole repository just to print it afterwards.ScanReportnow carries the summary statistics only; a caller that wants the old whole-corpus vector asks for it explicitly. (#62) -
Breaking: git history is no longer indexed for a workspace root that is a subdirectory of the repository git discovery lands on. Those commits are addressed by paths that do not exist under the root, and walking them costs the whole ancestor object database — a 4.4k-file subdirectory of a monorepo was driving gix over 275 MiB of packs and 101 655 objects. Discovery itself is unchanged (ascent is what lets
basemind scan src/lift to the repository root); only the history integration is gated.git initin the directory, orBASEMIND_GIT_DISCOVER_PARENTS=1, restores the old behaviour. (#62) -
The Claude and Cursor plugin manifests launch
scripts/mcp-launch.sh serve --root <project dir>over stdio instead of diallinghttp://127.0.0.1:51786/mcp?root=…— see Security. (#62) -
Dependency refresh:
xbergrepinned to the current 1.1.0 revision (internal extraction fixes and its own dependency updates),crawlberg1.4.2 → 1.5.0,rmcp3.1 → 3.2,tree-sitter-language-pack1.15.12 → 1.16.1,liter-llm1.18.4 → 1.19.1,smallvec1.15 → 1.16,getrandom0.3 → 0.4. Two incompatible upgrades were deliberately not taken:tree-sitterstays at 0.26 becausetree-sitter-language-pack1.16.1 still requires^0.26andtree-sitterdeclareslinks = "tree-sitter", so the graph admits exactly one version; andlancedbstays at 0.37 because 0.38.0 does not build without itsremotefeature — it gates theError::Httpvariant behind that feature while referencing it unconditionally fromjob.rs, and enablingremotewould pull an HTTP client stack in for no benefit here.
Fixed
- A scan of a repository holding exactly
[scan] max_candidatescandidate files could abort spuriously: the ceiling was checked against every walk entry, so once the candidate list reached the cap the next directory node or non-indexed file tripped it. Whether it fired depended on readdir order. (#62) - The "largest contributors" list in a
max_candidatesabort routinely named the wrong directory. The tally stopped at the breach andignore::Walkis depth-first in readdir order, so a repo withaaa/(30 files) andzzz/(5000) was told to excludeaaa. The walk now surveys a bounded number of further entries before erroring, buckets by the first two path segments so monorepos get an actionable name, and groups root-level files instead of listing them one by one. (#62) - A draining daemon can now interrupt a running file walk. Previously cancellation was only checked once the walk had finished, so a runaway scan could not be stopped. (#62)
[resources] max_concurrent_documentswas parsed and never consumed — the setting did nothing. A counting semaphore now enforces it. It is distinct from the footprint gate, which reacts to memory already allocated: a document extraction's spike (decoded page buffers, OCR bitmaps, an embedding batch) lands faster than the sampler can see it, so this bounds how many spikes may overlap before the first one starts. (#62)- Staging an entry that failed returned early without running the commit check, so bytes the failed upsert had already staged drifted out of the ledger permanently. (#62)
- Commit cadence no longer collapses at high core counts. The process-wide staging ceiling had no
hysteresis, so cadence scaled as
ceiling / workers— a commit per file on a many-core host. A 1 MiB floor on that branch bounds the worst case at about 128 MiB of resident staged index at 64 workers, and no core count now commits more often than the 8-worker cadence. (#62)
Known limitations
- Two concurrent full-tree scans of one workspace share one
scan-inflight.json. The second scan's record overwrites the first's, and the first scan's completion then removes the file while the second is still running — a window in which a genuine hard death would leave no breadcrumb. This is not reachable today (the daemon serialises a workspace's scans behind its store lock, and aserveboot's deferred and inline passes are sequential), and a per-pid filename would trade the window for records that accumulate and never get cleaned, so the single-file design stands. Recorded because the evidence is only trustworthy if its gaps are known. (#62) - A memory ceiling on the daemon can only be enforced by whoever starts it. basemind spawns the
daemon detached with
setsid(2), which changes the session and the process group and not the cgroup. On Linux a child stays in the spawning process's cgroup, so a daemon auto-spawned from an interactive shell lands in that shell's scope — outside whateverMemoryMaxan operator configured on a basemind unit — and a process cannot move its own children into a cgroup it does not control. There is no in-process fix, and none is claimed. The answer isBASEMIND_NO_AUTOSPAWN=1everywhere plus a unit that starts the daemon itself; seedocs/systemd/basemind-comms.service, which includes the two commands that check whether the ceiling is real rather than merely configured. (#62) [resources] max_footprint_mbis advisory, not structural. The gate parks workers at admit points while the process is over the ceiling and then admits anyway after five seconds, trading an overshoot for guaranteed forward progress. It shapes the peak; it cannot enforce an invariant the allocator will not, and it cannot make a scan that used to succeed start failing. The structural bounds this release adds are the ones that abort or truncate:[scan] max_candidates,[code_search] max_chunks_per_file,[resources] max_map_cache_mb, and the byte budget on staged index entries.[scan] max_candidatesdoes not bound bytes read or index size, and does not apply to theStaged/Revscan sources, which enumerate from git rather than from a walk.- Directory pruning is a walk-cost win, not a memory win. Pruned files were already rejected by
the path filter and never entered the candidate list. What pruning removes is the descent and the
per-file
stat— real, and the reason the Linux watcher stopped registering inotify watches inside excluded trees — but no claim is made that it reduced peak memory. SCANNER_STACK_SIZE(256 MiB,src/scanner_file.rs) is not a leak and was deliberately left alone. It is the per-worker stack reservation for the scanner's thread pool: lazily-committed virtual address space that never counts toward RSS or a cgroup limit. Shrinking it to "save memory" would save nothing and would reintroduce stack-overflow aborts on deeply nested syntax trees.
[0.25.2] - 2026-08-31
Added
basemind comms doctor --probeasks each comms daemon whether it can actually serve and reports the verdict per row (serving/LIVE BUT FAILING/LIVE BUT NOT RESPONDING). The default report stays RPC-free, so it remains safe to run on a wedged machine, and now states which question it answered: process liveness from the pidfile registry, not health.- A daemon that hits an unrecoverable store error records it to
<comms_dir>/last-fatal.jsonbefore exiting.comms doctorreports that record — the failing method, pid, build, and age — so the evidence outlives the process;--clear-fatalacknowledges it.
Changed
- Dependency refresh:
xbergrepinned to the current 1.1.0 revision,crawlberg1.4.0 → 1.4.2,fjall3.1.9 → 3.1.10,jsonschema0.51 → 0.52,lru0.18.2 → 0.18.3,mach20.6 → 0.7,tree-sitter-language-pack1.15.8 → 1.15.12, andliter-llm1.18.1 → 1.18.4.
Fixed
- A comms daemon whose store became permanently unusable held the singleton forever: it kept the
flock and the socket, so its pid stayed live and no replacement could take over, while every
request failed identically. It now releases the lock and exits non-zero on the first such error,
so the next
comms startgets a clean daemon (#61). basemind comms stophung indefinitely against a daemon wedged before its accept loop, because the connect never returned — the documented recovery path blocked on the very daemon it was meant to reclaim. Both stop paths now time-box the RPC and escalate to the pid. A daemon that answers with a deliberate refusal (daemon_busy, protecting the clients attached to it) is reported and left running, never terminated.comms statusreportedthreads: 0when the store could not be read, which is indistinguishable from a healthy empty broker. It now surfaces the store error instead.
[0.25.1] - 2026-08-26
Changed
- Dependency refresh:
xbergrepinned to the current 1.1.0 revision (EPUB chapter/metadata loss, PDF reading-order and CCITT image decoding, and XML/MathML hardening fixes on the document extraction path),crawlberg1.3.3 → 1.4.0,gix0.87.0 → 0.87.1,jsonschema0.50 → 0.51, andtree-sitter-language-pack1.15.7 → 1.15.8.
Fixed
- The Linux file watcher no longer aborts when a directory cannot be read (permission denied). inotify watches are now registered directory-by-directory, pruned by the same gitignore + exclude-glob filters a full scan uses, with registration failures downgraded to warnings. Directories created after startup are re-armed on create events, and the per-directory scheme is gated to Linux (macOS/Windows keep the single recursive call to avoid an FSEvents startup regression).
[0.25.0] - 2026-08-23
Added
- MCP stdio sessions now reconnect through a persistent protocol-aware relay when the shared daemon restarts, replaying initialization and returning retryable errors for interrupted requests.
- Agent comms now auto-register presence, deliver mailbox notices alongside ordinary MCP responses, notify scoped agents about discoverable threads, accept unambiguous short message references, and expose lifecycle status plus dry-run/apply retention cleanup.
- Document extraction uses xberg 1.1 chunk content, offsets, language metadata, bounded extraction time/page limits, and basemind's shared batch embedding path. Binary builds pin the unreleased xberg source to an exact public Git revision for reproducibility.
Changed
- Cache schema advances to 25. Existing machine-global index, blob, and comms cache state is rebuilt on the next scan after upgrading.
- Cache GC repairs corrupt or old rebuildable views instead of aborting the global sweep, enforces the disk budget against rebuildable workspace data, and records degraded GC health.
- Comms retention now bounds messages, archived threads, inactive agents, claims, and total threads; stale-agent cleanup removes routing state atomically while preserving authored history.
- Heavy MCP work is admitted through a daemon-wide concurrency limit and returns a retryable
server_busyresponse under sustained load.
Fixed
- Inbox cursors paginate independently per thread without repeats or undercounted unread totals.
- Message reads and acknowledgements enforce thread membership.
- Cancelled inbox waits release their subscriptions immediately, and default MCP identities no longer collide across concurrent sessions in one workspace.
- Standalone scan and MCP processes no longer hang during shutdown when Fjall's background worker teardown encounters the upstream database-drop deadlock.
[0.24.0] - 2026-08-07
Added
- Interactive UI over HTTP + the
uitool (ADR-0006). A newuiMCP tool (andquery uiCLI, in full parity) renders the code-graph and returns a URL to view and drive it: a livehttp://<addr>/ui?root=…page served by the basemind daemon's HTTP front-end when one is reachable, otherwise afile://URL to the self-contained offline export (which the tool always writes). The served page is the same zero-dependency interactive canvas asdisplay/graph_export html, so a browser — or an agent driving one — can navigate, search, pan/zoom, and screenshot it.open: falsereturns the URL without launching a viewer (agents/tests). The daemon's streamable-HTTP front-end gained aGET /uiroute alongsidePOST /mcp(both behind thecommsfeature). Both routes are loopback-only: on a loopback bind the front-end rejects any request whoseHost/:authorityis not a loopback name (DNS-rebinding protection), skipped only on an explicit non-loopback bind. - Desktop UI launch path (ADR-0006, first slice). A
basemind uisubcommand launches the desktop UI front-end (basemind-ui) by re-exec'ing the sibling binary built alongsidebasemind— mirroring howbasemind agentlaunchesbasemind-tui. Both launchers are behind thedesktop-uiandagent-tuifeatures and ship in no release archive yet (see Changed); build them locally to use them. This slice wires the launch path; today the binary opens the offline, self-contained interactive HTML code graph (the same ADR-0005 payload thedisplaytool produces). The resident Tauri window that drives the agent client/event/command seam lands behind adesktopfeature in a later slice, so default builds stay free of the per-platform webview dependency. - Inline rationale extraction (ADR-0009). L1 extraction now classifies source comments carrying
WHY:/RATIONALE:/NOTE:/TODO:/FIXME:/HACK:/XXX:/SAFETY:markers (and bare comments citingADR/RFCdecision records) intoFileMapL1.rationale, which the read-side graph build consumes to emitAnnotates/Citesedges. Blob-compatible (#[serde(default)]); no schema bump. - Single-daemon guarantees + leak safeguards. The comms daemon now takes an exclusive
single-owner lock (
daemon.lock) before binding its socket, so a redundant daemon on the same comms dir converges (exits 0) instead of racing — uniform across Unix and Windows. Every live daemon registers a pidfile under<data_home>/daemons/, and a new machine-wide ceiling (BASEMIND_MAX_DAEMONS, default 8) refuses to spawn past it rather than letting daemons pile up. Newbasemind comms doctorlists every live daemon (pid / comms dir / version / uptime, pruning dead entries) and flags a pile-up;basemind comms stop --allreclaims them all in one call.
Changed
-
Cache schema advances to 24. Existing machine-global index, blob, and comms cache state is rebuilt on the next scan after upgrading.
-
Release dependencies are current.
tree-sitter-language-packis updated to 1.14.3,crawlbergto 1.1.4, andliter-llmto 1.16.0. The optional LSP bincode integration is pinned to 2.0.1 because the published 3.0.0 crate is an intentionally non-compiling placeholder. -
Agent shell sessions now default to headless presentation. Background MCP/CLI work no longer opens terminal tabs unless
[shells].visualexplicitly selectscurrentorwindow. -
The two interactive front-ends are held back from releases. The agent TUI (
basemind-tui, ratatui) and the desktop UI (basemind-ui, tauri) are not ready to ship, so the release archive now contains thebasemindbinary alone, and the launcher subcommands that would re-exec them sit behind two new root-crate features,agent-tuianddesktop-ui, whichfulldeliberately omits. A releasedbasemindtherefore never advertises a command whose sibling binary is absent from the archive. Neither crate is affected otherwise: both remain workspace members, both still build undercargo build --workspace, thebasemind-tuireplay/PTY suite still runs in CI, and a localcargo install --path . --features full,agent-tui,desktop-uistill gets both launchers. Nothing was published from these crates before — both carrypublish = false— so no crates.io surface changes. -
Daemons self-reap sooner to stop the process/memory leak class. The idle-reap default drops from 30 min to 10 min, and a new bootstrap reaper self-terminates a daemon that no client ever connects to within ~2 min (env
BASEMIND_COMMS_BOOTSTRAP_SECS) — catching the spawn-and-abandon shape (a test or script that randaemon ensurethen exited) that previously left a daemon, and its tens of threads, resident. Behavioural only; no schema/RELEASE_MINORbump. Test binaries pin a fast reap so a parallelcargo testno longer accumulates one lingering daemon per binary. -
BREAKING:
basemind-agent's LLM-facing tool names follow the domain consolidation. The agent registered its code-map tools under the pre-consolidation flat names, so the model saw one vocabulary and the MCP surface another. They are nowdomain_modein bare snake_case:outline→code_outline,search_symbols→code_symbols,find_references→code_references,find_callers→code_callers,workspace_grep→code_grep,call_graph→graph_calls,recent_changes→git_recent,blame_symbol→git_blame_symbol,diff_file→git_diff.shell_execis unchanged — it is the agent's own tool, not an MCP domain. The underscore rather than the MCP surface's colon is a provider constraint, not a style choice: Anthropic, OpenAI and Google all validate tool names against^[a-zA-Z0-9_-]{1,128}$, socode:outlineis not a legal name to register. Only saved conversations that replay a raw tool name are affected. The token-savings estimator now rewrites the agent spelling to the MCP telemetry key at the boundary, keyed off the real mode vocabulary insrc/mcp/mode.rs, so each baseline carries a single spelling — previously it carried both, and dropping either would have silently zeroed the agent TUI's "tokens saved" readout instead of failing a test. -
call_graphis re-expressed over the sharedcodegraph. The tool no longer maintains its own call scan; it projects the resolvedCallsedges of the shared, memoized graph the other graph tools already read. One user-visible consequence: a call whose callee does not resolve to an in-repo function-like definition (an external/library call) no longer surfaces as an empty-sitescallee node —call_graphnow reports only resolved, in-repo call relationships. Thecallersdirection is unchanged. Read-side only; no blob/index schema change. -
BREAKING: the 85-tool MCP surface collapses into nine domain tools, each dispatched by a required
mode. Hosts defer MCP tools and surface them only through keyword search, so every extra tool name competed for the same query, and GH #50 showed one layer's schema defect took the whole registry down with it — the comms layer's schema broke code navigation, despite the two sharing nothing. Nine dense tools (code,graph,git,memory,admin,web,agents,workspace,shell) retrieve better than eighty-five thin ones, and each remaining description now carries the vocabulary an agent actually searches for.modeis required and never defaults — a default would let an agent omit it and silently get the wrong operation, which looks like an empty result rather than an error. The CLI collapses onto the same nine group names as real clap subcommands (basemind code outline, not a--modeflag), so every operation keeps its own--helpand argument validation;tests/cli_parity.rsnow asserts a strict(domain, mode)bijection between the two surfaces. All 85 operations survive with no deprecated shims — a name that still answered to the old spelling would keep competing in deferred-tool search, which is the cost this removes. Landed over four commits (88cd9daweb,6af6268admin/memory/workspace,36919a4git/graph,02bf4a5code/agents/shell); design and trade-offs in ADR-0011. Two costs taken deliberately: nooutput_schemaon any of the nine tools (each domain's modes return different response shapes, and SEP-2106 allows exactly one schema per tool), and per-tool annotations coarsen to the union of a domain's modes, resolving toward the side effect (e.g.shelladvertisesdestructive_hintbecausekillis one of its modes, andgraphadvertisesread_only_hint: falsebecausedisplay/openlaunch a viewer, even though seven of its nine modes are pure reads).Anyone with a saved prompt, script, or pinned tool name against the old surface needs the replacement below —
<old tool>is now<domain>mode<mode>, and every CLI invocation moves from its old subcommand path to the new one.basemind registryis also renamedbasemind workspace(its subcommands are unchanged), andbasemind commsnow holds only the broker daemon's own lifecycle (daemon/start/stop/status/doctor) — the agent-facing verbs it used to carry (register,agents,thread-*,join,leave,members,post,history,read,inbox,wait, plus a newack) moved tobasemind agents.Old MCP tool New domainmodeOld CLI New CLI outlinecodeoutlinequery outline <path> [--l2]code outline <path> [--l2]search_symbolscodesymbolsquery search <needle>(aliasquery symbol)code symbols <needle>workspace_grepcodegrepquery grep <pattern>code grep <pattern>list_filescodefilesquery list-filescode filesfind_filescodefindquery find-files <query>code find <query>goto_definitioncodedefinitionquery goto-definition <path> <line>code definition <path> <line>find_referencescodereferencesquery references <name>code references <name>find_callerscodecallersquery callers <path> <name>code callers <path> <name>find_implementationscodeimplementationsquery implementations <trait>code implementations <trait>dependentscodedependentsquery dependents <module>code dependents <module>expandcodeexpandquery expand <path> <name>code expand <path> <name>search_codecodesemanticquery search-code <query>code semantic <query>get_chunkcodechunkquery get-chunk <path>code chunk <path>call_graphgraphcallsquery call-graph <name>graph calls <name>neighborsgraphneighborsquery neighbors <name>graph neighbors <name>pathgraphpathquery path <from> <to>graph path <from> <to>subgraphgraphsubgraphquery subgraph <name>graph subgraph <name>communitiesgraphcommunitiesquery communitiesgraph communitiesarchitecture_mapgraphmapquery architecture-mapgraph mapgraph_exportgraphexportquery graph-exportgraph exportdisplaygraphdisplayquery displaygraph displayuigraphopenquery uigraph openworking_tree_statusgitstatusgit working-tree-statusgit statusrecent_changesgitrecentgit recent-changesgit recentcommits_touchinggittouchinggit commits-touching <path>git touching <path>find_commits_by_pathgitby_pathgit find-commits-by-path <pattern>git by-path <pattern>hot_filesgitchurngit hot-filesgit churndiff_filegitdiffgit diff-file <path> <old> <new>git diff <path> <old> <new>diff_outlinegitdiff_outlinegit diff-outline <path>git diff-outline <path>blame_filegitblamegit blame-file <path>git blame <path>blame_symbolgitblame_symbolgit blame-symbol <path> <name>git blame-symbol <path> <name>symbol_historygitsymbol_historygit symbol-history <path> <name>git symbol-history <path> <name>search_git_historygitsearchgit search <pattern>git search <pattern>memory_putmemoryputmemory put <key> <value>memory put <key> <value>memory_getmemorygetmemory get <key>memory get <key>memory_listmemorylistmemory listmemory listmemory_searchmemorysearchmemory search <query>memory search <query>memory_deletememorydeletememory delete <key>memory delete <key>memory_auditmemoryauditgovernance auditmemory auditsearch_documentsmemorydocumentsmemory search-documents <query>memory documents <query>proposals_minememoryminegovernance minememory mineproposals_listmemoryproposalsgovernance proposalsmemory proposalsproposal_acceptmemoryacceptgovernance accept <id>memory accept <id>proposal_rejectmemoryrejectgovernance reject <id>memory reject <id>statusadminstatusquery statusadmin statusrepo_infoadminrepoquery repo-infoadmin reporescanadminrescanbasemind rescan [paths]admin rescan [paths]cache_statsadmincache_statscache statsadmin cache-statscache_gcadmingccache gcadmin gccache_clearadmincache_clearcache clear --component <c>admin cache-clear --component <c>telemetry_summaryadmintelemetrybasemind telemetryadmin telemetrycompressadmincompressbasemind compress-outputadmin compressdeltaadmindeltabasemind deltaadmin deltacheckpointadmincheckpointbasemind checkpointadmin checkpointdetect_wasteadminwastebasemind detect-wasteadmin wasteweb_scrapewebscrapeweb scrape <url>web scrape <url>web_crawlwebcrawlweb crawl <seed-url>web crawl <seed-url>web_mapwebmapweb map <url>web map <url>agent_registeragentsregistercomms register --name <handle>agents register --name <handle>agent_listagentslistcomms agents [--thread]agents list [--thread]thread_startagentsthread_startcomms thread-startagents thread-startthread_listagentsthread_listcomms threads [--subject-contains]agents thread-list [--subject-contains]thread_joinagentsjoincomms join <thread>agents join <thread>thread_leaveagentsleavecomms leave <thread>agents leave <thread>thread_membersagentsmemberscomms members <thread>agents members <thread>thread_add_memberagentsadd_membercomms add-member <thread> <id>agents add-member <thread> <id>thread_remove_memberagentsremove_membercomms remove-member <thread> <id>agents remove-member <thread> <id>thread_archiveagentsarchivecomms archive <thread>agents archive <thread>thread_postagentspostcomms post <thread> <subject>agents post <thread> <subject>thread_historyagentshistorycomms history <thread>agents history <thread>message_getagentsmessagecomms read <id>agents message <id>inbox_readagentsinboxcomms inbox [--mark-read]agents inbox [--mark-read]inbox_ackagentsack(new — no prior tool) agents ackinbox_waitagentswaitcomms wait [--thread --timeout-secs]agents wait [--thread --timeout-secs]workspacesworkspaceworkspacesregistry workspacesworkspace workspacesworktreesworkspaceworktreesregistry worktreesworkspace worktreesbranchesworkspacebranchesregistry branchesworkspace branchesworktree_claimworkspaceclaimregistry claimworkspace claimworktree_releaseworkspacereleaseregistry releaseworkspace releaseshell_spawnshellspawnshells spawn <command>shell spawn <command>shell_sendshellsendshells send <session-id> <text>shell send <session-id> <text>shell_captureshellcaptureshells capture <session-id>shell capture <session-id>shell_killshellkillshells kill <session-id>shell kill <session-id>shell_listshelllistshells listshell listshell_broadcastshellbroadcastshells broadcast <text> --session <id>…shell broadcast <text> --session <id>…
Fixed
- Recycling the shared daemon no longer disconnects other agents.
basemind comms stopnow refuses withdaemon_busywhile another live relay is connected, instead of entering a bounded drain that severed every stdio MCP transport after ten seconds and left hosts permanently showingTransport closed. Once the other sessions exit, the same stop request succeeds normally. - EndNote XML is rejected until xberg ships its quick-xml fix. The document scanner now blocks
.enw/application/x-endnote+xmlbefore xberg 1.0.14 can route untrusted input through biblib 0.4.3's vulnerable quick-xml 0.37 attribute parser (RUSTSEC-2026-0194). The narrow guard is tracked for removal in xberg #1398. - Resolved imported calls could remain inferred in code graphs. The reference index now prefers a terminal cross-file definition over the local import binding, and code-graph proof matches the resolver's identifier byte within the outline symbol span. Python callers and goto-definition now agree on the exported target.
- Cache budget enforcement could remain over its configured limit. Eviction now prefers cold workspaces but falls back to hot ones when required, reclaims newly orphaned blobs in the same pass, and remeasures the actual cache footprint before selecting another workspace.
- Starved or failed daemon GC cycles were visible only in logs.
cache_stats.last_gcnow records the latest attempt status, timestamp, actionable diagnosis, consecutive degraded-cycle count, and budget outcome while preserving counters from the last successful sweep. - Content-addressed blobs no longer consume raw msgpack size on disk (issue #49). New
.fm,.doc,.rref, and.chunkblobs use zstd-1 envelopes. Filemap L1/L2 frames remain independent so outline reads do not decompress calls, and chunk embedding metadata stays plain for cheap cache validation. Existing uncompressed blobs remain readable and are rewritten when next produced. - Agent shell capture could return an empty screen for a successful command. The snapshot-only
path dropped the first row for fresh CLI clients and let rmux's dead-pane row push short output
out of
lines=Ncaptures. Capture now reads retained history, removes terminal synthetic and outer blank rows while preserving interior spacing, and returns a bounded recent tail, so a completed one-line command remains observable untilkill. - Shared shell sessions could start in another workspace. Relative or omitted
cwdvalues were interpreted from the machine-global rmux daemon's original process directory. Spawn now anchors every command to the calling workspace root, and acceptscwd = "."as that root. - A transient pane inspection could make
shell listfail globally. The daemon now preserves the known session list and conservatively reports liveness when rmux cannot provide one pane-state snapshot, rather than making every session undiscoverable. Its idle reaper uses strict liveness separately, so persistent inspection failure cannot pin the daemon forever. - A resident daemon running different code at the same version was invisible. After a local
rebuild or
cargo install --path ., the version check compares onlyMAJOR.MINOR.PATCH, so the daemon left over from the previous build passes every test and is reused — answering with stale code while the newly installed binary sits unused.basemind comms statusnow reports abuild=identity (a hash of the running executable, so two builds differ exactly when their bytes do) and warns explicitly when the daemon's build differs from the binary asking. The build id is diagnostic only: it deliberately does not trigger takeover, because two same-version binaries at different paths would then reclaim the socket from each other indefinitely. admin rescanreported a refreshed document in no bucket at all. A rescan that re-indexed a changed PDF/Office/HTML file returnedscanned: 3, skipped_unchanged: 2, updated: 0— the document lane's work was counted by the scanner but dropped from the response, which read as "nothing happened" for documents users. The response now carriesdocs_indexed. Indexed data was never stale; only the accounting was.agents postaccepted an emptybodyand stored a message with nothing to read. An empty string is now refused with an error naming the mode and pointing at the subject-only form; omittingbodyentirely stays legal.- The machine registry never retired a row. Workspaces were recorded on registration and nothing
ever removed them, so every throwaway checkout and test tempdir accumulated forever and buried the
live repos
workspace workspacesexists to surface. The daemon now prunes rows whose path is gone, once at startup and on its hourly maintenance pass.
[0.23.1] — 2026-07-31
Fixed
code semanticsilently returned nothing on a reader session. Both non-vector retrieval lanes — BM25 keyword and exact symbol-name — read fjall, whose directory lock is exclusive, so any session that is not the daemon opens the store without an index and both lanes returned an empty vector. That empty was indistinguishable from "searched and found nothing": the call succeeded, in microseconds, withhits: []. A reader session now forwards the two lanes to the daemon (rankings only — chunk bodies still come from content-addressed blobs the caller can already read), and a daemon-hosted session reaches the same index straight through the host seam rather than dialling the daemon it runs inside. When a lane genuinely cannot run, the response carriesdegraded_lanes+degraded_reasonand partial results are labelled as partial; when no lane can run at all the call is an error that says so, never a bare[]. Keyword-lane hits also carry theirmatched_lanes/keyword_rankprovenance, which only the fused path used to set.- The
multi-agent-roomskill now ships to every plugin tree.scripts/sync-plugin-skills.shcarried a hand-maintained list of nine skills whileskills/held ten, somulti-agent-roomwas copied into the Hermes bundle (generated separately by ai-rulez) but never into the Codex, Cursor or opencode plugins — while theagent-commsrule those plugins do ship tells the agent to consult it. The skill and command lists are now derived from the canonicalskills/andcommands/trees, so adding either ships it everywhere instead of only where someone remembered. - Documentation named tools and commands that no longer exist. The consolidation swept the
README,
llms.txt,docs/,skills/and the plugin manifests but missed thewebsite/docs site, thepre-tool-guardhook (which suggestedsearch_symbols/find_references/list_fileson every Grep and Glob), andbasemind git blame-filein the README quickstart. Everybasemind <domain> <mode>command now appearing in the docs or plugin trees is verified to resolve against the built binary. - Runaway daemon spawning under
cargo test(fork bomb).spawn_detached_daemonre-exec'dstd::env::current_exe()withcomms daemonappended. Under a test harness that executable is libtest, which reads the appended words as a test-name filter and re-runs the whole suite — and every test in that re-run reaching the spawn forked another generation. A single run reached 5610 orphaned daemons and 8256 processes, at which pointforkitself began failing. The machine-wide ceiling could not contain it becauseinit_isolated_cacheminted a fresh$BASEMIND_DATA_HOMEin every process, so each generation counted an empty registry. Now the spawn resolves a realbasemindbinary or refuses: a harness is redirected to the siblingtarget/<profile>/basemind, andBASEMIND_DAEMON_BINARYis the explicit override. The isolated test cache is stamped with a marker and re-adopted by child processes, so one registry — and one ceiling — covers the whole process tree, while an unmarked$BASEMIND_DATA_HOME(a developer's real cache) is never adopted. - Blank statusline in global-cache mode. With the index living in the machine-global cache there
is no in-repo
.basemind/for the shell statusline to read, so it fell back to a permanent "no index" hint even on a freshly-scanned repo.basemind statuslinegains a--root <path>mode that renders the rich per-repo line (file count · scan age · today's calls · tokens saved) from a cheap newstatus.jsonsidecar plus thetelemetry.jsonltail — never opening the Fjall index, so it is safe to refresh every few seconds. The sidecar is written after every working-view scan/rescan and is additive/best-effort (a missing or unrecognized-schema sidecar degrades to the "no index" hint). No blob/index schema change.
[0.23.0] — 2026-07-31
Minor release: .basemind/ is wiped and rebuilt on the next basemind scan (RELEASE_MINOR
22 → 23). Rebuild is automatic from source; no action needed.
Added
- Streamable-HTTP MCP transport (rmcp 3.0, SEP-2567), hosted by the comms daemon. The single
machine-global daemon now serves the rmcp router over a stateless loopback-HTTP front-end
(
127.0.0.1:51786by default,BASEMIND_HTTP_ADDRto override; the bound address is published to<comms_dir>/http.addr) in addition to its Unix-socket relay. HTTP-native clients dialhttp://127.0.0.1:<port>/mcp?root=<repo>directly — no per-session server process.basemind daemon ensurebrings the daemon + transport up and prints the URL. - Structured tool output (SEP-2106). Every tool advertises an
outputSchemaand returnsstructuredContentalongside the text mirror, so compliant clients get typed results. tools/list/prompts/listcache hints (SEP-2549) — the tool surface is static per process, so the daemon advertises a TTL + public cache scope and clients stop re-listing every session.- Long-running tools as pollable tasks (SEP-2663). Slow tools (
rescan, and the feature-gatedsearch_documents/web_scrape/web_crawl/web_map) run as cancellable tasks when the client declares the Tasks capability; other clients keep the synchronous path.
Changed
- rmcp 2.2 → 3.0, plus
arrow,lancedb,xberg,crawlberg, andjsonschemadependency bumps. basemind serveis now a thin stdio↔daemon relay, not a server in its own right. It ensures the daemon (the sole rmcp host and Fjall writer) and byte-pumps stdin/stdout to the daemon's relay socket. Every stdio MCP client keeps working with no config change; the daemon outlives any single client. The Claude Code and Cursor plugins switch to the HTTP transport (type: http) and dial the daemon directly.
Fixed
- The MCP transport "drops and never returns" hang.
servepreviously fell back to an in-process stdio server whose lifetime was bound to the stdin pipe, so a slow tool call that timed out and closed stdin killed the whole server — and it did not come back until the client respawned it. The daemon-hosted model decouples server lifetime from any single connection: a dropped client ends only the throwaway relay (or a single stateless HTTP request), and the warm daemon serves the next connection immediately. - Document re-embed loop follow-ups (issue #44). Three residual thrash paths on the document
tier are closed: (1) a hosted rescan now honors the daemon drain token, so
comms stop/ SIGTERM interrupts a mid-scan instead of letting it run to completion; (2) a reused document blob has its mtime refreshed on every scan, so the blob GC's grace window keeps protecting an actively-scanned doc through a rename's transient entry-less gap instead of reaping it and forcing a full re-embed; (3) a doc whose embedding deterministically yields no vectors (an unembeddable body, a missing ONNX runtime) is now recorded as attempted and no longer re-extracted + re-embedded on every scan — a content-hash or embedding-preset change re-opens the attempt.
[0.22.8] — 2026-07-27
Changed
basemind initno longer writes a committedCLAUDE.md/AGENTS.mdunasked. The rules block is a shared, version-controlled surface, so onboarding now defaults to the personal, gitignored sibling instead:--rules-target auto(the default) routes to ai-rulez when it owns governance, otherwise toCLAUDE.local.md(orAGENTS.local.mdwhen the repo uses AGENTS) — never a committed file. The/bm-initslash command asks the user where the block should go, and the interactive CLI prompts for it;--rules-targetgainsclaude-local/agents-localvalues, andclaude/agentsremain as the explicit opt-in to the committed files.
Fixed
- Comms daemon readiness flake (
spawned comms daemon did not become ready within the timeout). The Unix comms front-end peeked a freshly-accepted connection's first byte inline in the accept loop (peek_first_byte(&stream).awaitbefore the spawn), so a single client that connected but was slow or silent to send its first byte parked the entire accept loop onreadable(). Every other client then sat unaccepted in the kernel backlog — includingensure_daemon's readiness probe, whose short timeout reported a perfectly healthy, still-running daemon as unreachable, which in turn spawned a replacement that hitAddrInUseand exited (SpawnTimeout). The peek + relay/ legacy routing now runs inside the per-connection spawned task, so the accept loop onlyaccept()s andspawns and never awaits client I/O — a slow/silent client harms only its own task. This aligns the Unix path with the Windows named-pipe front-end, which already reads the first byte in-task. No blob/index schema change.
[0.22.7] — 2026-07-25
Changed
basemind serveis now a thin relay to the singleton daemon; the daemon hosts the MCP router. Every client'sserveused to open its own read-only store, build its own in-RAM code map, and lazily load its own ONNX + LanceDB (~300–370 MB RSS per process). On acommsbuild serving the working view,servenow dials the daemon, runs a tiny handshake, and pumps stdio bytes to it — the daemon hosts the rmcp router over one shared read stack per workspace (SharedReadStack), so the in-RAM map, ONNX model, LanceDB, and git caches are resident once per workspace instead of once per client.ServerStatewas split into a per-connection identity (agent_id,comms_clients,log_level) plus that shared stack, so distinct clients keep distinct memory-owner identities while sharing the heavy read state. Fallback is total: any handshake problem — no daemon, an older daemon (which rejects theRELAY_MAGICpreamble as an over-long frame), a proto/view mismatch, orBASEMIND_MCP_LEAN/ a non-working view /BASEMIND_SERVE_INPROCESS— serves in-process exactly as before, so a client is never bricked. No blob/index schema change.- Daemon-hosted git-history now runs in-process instead of the daemon dialing itself. A hosted
connection's history reads and its startup sync route through a new in-process seam
(
HistoryHost, implemented by the daemonBroker) that shares the Broker's single opengit-history.fjall/handle and per-repo build lock — sorecent_changes/commits_touching/blame_*/search_git_historyno longer pay a unix-socket round trip per call on the hosted path, while the single-holder + single-flight-build invariants are unchanged. A thindaemon_writerserve (the fallback) still forwards over the socket. - The
serverelay now works on Windows named pipes. Named pipes have no non-destructive peek (the Unix path usesMSG_PEEK), so the accept loop consumes the connection's first byte and replays it — to the relay handshake via a smallPrefixReader, or to a legacy comms link via a seeded frame buffer — routing rmcp relay connections apart from comms links without a fallback to in-process serve.
Fixed
- Daemon-hosted precise
resolved_refs(andsearch_code) no longer degrade. Hosted connections read the daemon's read-write fjall index directly in-process via a host seam, so cross-filefind_callers/goto_definitionsee therefs_by_defedges an index-less thin serve could not — without the daemon dialing itself over its own socket.
Removed
- Deleted the orphaned
monitors/monitors.json+scripts/comms-monitor.shpoll-loop scripts (referenced by no manifest; the daemon'sSubscribeInboxpush channel supersedes them).
[0.22.6] — 2026-07-25
Fixed
- The older-build daemon takeover now actually fires (#44).
singleton::roundtripdecoded a bareCommsResponse, but the daemon frames every reply as aCommsOut::Responseenvelope, so the decode always failed anddaemon_statusalways returnedNone.ensure_daemonnever entered its version gate and reused whatever daemon held the socket — so a live pre-0.22.5 runaway daemon survived the client upgrade instead of being reaped (it had to be killed by hand). The probe now decodes theCommsOutenvelope; the 0.22.5 cache/GC remediation actually reaches machines with a lingering older daemon. Regression present since the takeover was introduced.
Changed
- Refreshed the dependency lockfile (
auto_enums,derive_utils).cargo upgrade --incompatibleis a no-op: every incompatible upgrade is a deliberately pinned crate (arrow-*=58,oxc_*,tree-sitter-python).
[0.22.5] — 2026-07-25
Hardening release after a starved-GC incident: a pre-0.22.4 daemon stuck in the #44 re-embed loop held the blob-GC read lock indefinitely, which silently stopped all daemon maintenance and let the machine-global cache grow to 116 GB with no signal anywhere. Updating also remediates lingering pre-fix daemons: a 0.22.5 client stops any older-build daemon on first contact (existing older-build takeover) instead of leaving it to run away.
Added
- Cache size budget with cold-workspace eviction.
BASEMIND_CACHE_BUDGET_MB(default 20 GiB;0= unlimited) bounds the machine-global cache. When workspaces + blobs exceed it, the GC sweep evicts the coldest workspace dirs (idle ≥ 24 h, never ones another process holds locked) and reclaims the blobs they pinned — the backstop the reference-counting GC lacked, since live-but-idle workspaces pin their blobs forever. - Persisted GC observability. Every destructive sweep records its outcome (timestamp, blobs
removed, bytes freed, workspaces reaped/evicted) to
gc-state.jsonin the cache root, andcache_statssurfaces it aslast_gc— a GC that stops running is now visible instead of a silent disk leak.
Fixed
- Blob GC runs at daemon startup and on its own task (#44). It previously ran only as the last step of the hourly prune loop: a daemon restarting more often than hourly never swept once, and a single starved GC await stopped message pruning, thread archiving, and workspace eviction for the life of the process.
- The GC's lock wait is bounded (#44). Waiting for the blob-GC write lock now times out after 5 minutes (logged, retried next cycle) instead of parking forever behind a runaway rescan's read guard — which, the lock being FIFO, also queued every subsequent rescan behind the stuck writer and wedged the daemon machine-wide.
- A draining daemon refuses new rescans (
rescan_draining) instead of running doomed partial passes, and a rescan that queued behind a drain-cancelled pass fails fast instead of re-walking the tree — cancelled passes never advance the coalescing generation, so each one previously re-walked the whole monorepo.
Changed
- Upgraded
gixto 0.86 andxbergto 1.0.0-rc.37;arrow-array/arrow-schemaare pinned to=58(required by LanceDB 0.31).
[0.22.4] — 2026-07-23
Fixed
- The daemon no longer loops full document re-embed passes on churny trees (#44 — ~700 % CPU
for hours, RSS to 5.4 GB on a multi-session pnpm monorepo). Root cause:
Store::write_docwent through the schema-only skip inwrite_blob, so when the serve boot's Deferred pass had written a doc blob vectorless (embedding_dim: 0), the follow-up Inline pass re-extracted and re-embedded the document — and then silently threw the embedded result away. The blob stayed vectorless forever, and every entry-less encounter of the same content (NoCache rename remove+create pairs from #43,git worktreechurn, fresh worktree-rooted workspaces) re-ran ONNX embedding from scratch. Doc blobs now overwrite on upgrade (the same fix the code-chunk tier already had), andDocEntryrecords embed state so already-poisoned caches heal with exactly one persisted re-embed pass on the next scan — no wipe needed. The same skip also meantsearch_documentsvectors never landed in LanceDB, so document vector search now actually works for repo docs. basemind comms stopand SIGTERM now interrupt a mid-scan daemon (#44). The drain trips a cooperative per-file cancellation token threaded through the scanner, and the runtime teardown is bounded at 15 s instead of waiting forever on an in-flight rescan — previously only SIGKILL could end a scanning daemon, which fed the respawn loop. A cancelled pass commits completed files (including their LanceDB rows), never runs the stale purge, and surfaces asrescan_cancelledrather than a completed rescan.- Queued identical full rescans coalesce. N sessions forwarding full rescans of the same workspace serialized on the store lock and each re-walked the whole tree; a request that waited behind an identical-or-stronger full scan is now served that scan's stats.
- Unreferenced blobs younger than 6 h survive the daemon's hourly GC sweep, protecting the Deferred→Inline and rename windows where content-addressed doc blobs are legitimately entry-less.
Changed
- Dependencies: tree-sitter-language-pack 1.13.2 → 1.13.3, xberg rc.30 → rc.33, lockfile refresh (arrow stays on 58.x — lancedb 0.31 pins arrow 58).
Added
basemind agent— a terminal coding agent over the code map. A model-agnostic (bring-your-own-key) ratatui TUI that runs a turn-loop against basemind's in-process code-map and git tools. Launch it withbasemind agent [prompt](forwards--continue/--resume/ the positional prompt); thebasemind-tuibinary now ships alongsidebasemindin every release archive, andbasemind agentre-execs it.- Multi-agent room. Agents sharing a repo can join a room over the comms broker: a roster of
peers in the status area,
/post [subject:] <text>to broadcast,/rosterand/leavecommands, incoming peer messages plus join/leave notices in the transcript, an unread cue, and model-facingroom:*tools. The live broker room is built behind theroomfeature; its incoming stream reconnects with backoff, refreshes the roster on a cadence, and dedups by message id.
[0.22.3] — 2026-07-21
Fixed
- The file watcher no longer leaks memory or storms the CPU on large, churny trees. On
macOS/Windows the
notify-debouncer-fulldebouncer defaulted to itsFileIdMapcache, which recursively walks andstats the entire watched subtree — following symlinks, at watch time and again on every directory create/rename — into an unbounded map. On a pnpm monorepo (anode_modulessymlink farm plus in-repo git worktrees) that walk amplified without bound: RSS climbed 138 MB → 6.85 GB in ~3 minutes at 45–95 % CPU (#43, re-file of #41; CPU counterpart #33). Both the tree watcher (basemind watch/serve) and the internal view watcher now useNoCache— the same cache Linux already defaulted to — so the debouncer never walks the tree. basemind's own gitignore-awareIndexFilterstill selects what a change rescans, and renames degrade to remove-old + create-new, which the incremental scanner already handles.
[0.22.2] — 2026-07-20
Fixed
- Codex downstream workspaces now start the latest complete release reliably. Codex resolved the
old relative launcher inside its installed plugin cache, indexing the plugin instead of the user's
workspace. Its plugin now locates a bundled bootstrap, resolves the latest published GitHub tag,
and uses the serialized, checksummed launcher; simultaneous first starts cannot corrupt a shared
npxcache. - MCP tools stay available across a release window. The plugin launcher (
mcp-launch.sh) hard-pinned to the plugin's exact version and aborted if that release's platform asset 404'd — so in the window between the plugin bumping to a new version and that release finishing its per-platform binary uploads, the session had no MCP server at all. On a missing pinned asset the launcher now re-execs pinned to the newest published GitHub release: GitHub exposes only finalized, non-draft releases at/releases/latest, and the publish workflow promotes a release only after every platform asset + checksums exist, so the fallback target is always complete and safe to run — even from an empty cache. A same-schema-minor cached binary remains the offline last resort. The launcher also now discovers the plugin manifest under any harness mirror (.claude-plugin/.codex-plugin/.cursor-plugin), so one launcher serves every coding agent. - The publish workflow heals partial releases and never publishes out of order. A rerun that found any single release asset previously assumed the release was complete and skipped the build matrix, so a release missing a platform binary could never recover. It now requires the full archive + checksums set before treating a release as published, so a rerun rebuilds only the missing platforms. Publish concurrency keys on the resolved tag (a tag push and a manual dispatch for the same tag no longer race) and never cancels a publish mid-flight, and the crates.io publish waits for the release to finalize like the npm/PyPI publishes.
- Version updates no longer leak basemind processes. On update, the prior version's
serveand comms daemon kept running beside the new generation, so the machine never converged on a single daemon. The launcher now terminates every basemind process (serve and comms/write/shell daemons — all the same binary) belonging to a different cached version before exec'ing the resolved binary, and prunes leftover install lock/staging dirs. Unix best-effort; never blocks or fails the launch. No API or on-disk-format change (blob/index schema unchanged).
[0.22.1] — 2026-07-20
Fixed
- basemind is publishable to crates.io again. The 0.22.0 stack-graphs vendoring added four
in-tree forks as versionless path dependencies with
publish = false, which madecargo publishreject basemind ("all dependencies must have a version requirement specified"). The forks are renamed to owned, published crates —basemind-stack-graphs,basemind-lsp-positions,basemind-tree-sitter-graph,basemind-tree-sitter-stack-graphs— and referenced by version, socargo install basemindworks again. The publish workflow now releases the sub-crates in dependency order before basemind. No API or on-disk-format change (blob/index schema unchanged).
[0.22.0] — 2026-07-20
Index rebuild on upgrade. This release bumps the index/blob/comms schema (21 → 22), so every existing
.basemind/(legacy, per-repo) and the new global cache wipe and rebuild from source on the nextbasemind scan. The cache location itself also moves: state no longer lives under a repo's.basemind/— it lives in a single global, content-addressed store under the XDG data directory (see below). This is the intended migration path; nothing you need to do beyond letting the next scan run.
Added
- Machine-wide daemon — concurrent sessions now read AND write. A single background daemon per
machine is the sole fjall writer for the index. Previously, one
serveprocess per repo held the repo's fjall write-lock, so a second session on the same repo silently fell back to a read-only, increasingly stale view.servenow opens its store read-only (blobs only) and forwards writes (scan / rescan) to the daemon over a Unix domain socket (named pipe on Windows); the daemon starts on demand and is shared by every session on the machine. N sessions on one repo now all read and write without downgrade. - Global XDG cache +
BASEMIND_DATA_HOME. Index data (content-addressed blobs, per-workspace views, LanceDB) moves out of the repo and into~/.local/share/basemind/(overridable withBASEMIND_DATA_HOME), keyed by workspace. The blob store is now a single global, content-addressed cache that dedupes identical files across every repo and worktree on the machine, not just within one repo..basemind/is no longer created in repos — the gitignore dance frominitis gone. - Registry + worktree coordination tools. The daemon keeps a cheap, always-on registry of
repos, worktrees, and branches built from git plumbing, exposed as new MCP tools:
workspaces(known repos on the machine),worktrees(linked worktrees for a repo),branches(branches per worktree), andworktree_claim/worktree_release— an advisory claim so multiple agent sessions don't collide working the same worktree at once. find_files— fuzzy, fzf/fd-style filename/path search across indexed files, ranked by match score. Complements the existing substringlist_files.basemind statusline— a CLI subcommand that queries the daemon for the workspaces currently active and prints a compact status line; prints nothing (idle) when no daemon is running.- Thread comms replace room comms. Agent coordination now uses threads instead of rooms: each
thread is addressed by at least two of subject / path-glob / members, discovered by scope (member,
cwd path-match, or subject filter — never global), with idle threads auto-archiving. See
thread_start/thread_post/thread_list/inbox_read/agent_listin the MCP tool table. - Precise, scope- and import-aware name resolution for Python and Java (feature
code-intel-stack, included infulland the release binary). Until now only JavaScript/TypeScript resolved precisely (via oxc); every other language fell back to heuristic name matching, sofind_references/find_callers/goto_definitioncould not tell a shadowed local from an import. basemind now runs GitHub stack-graphs-style.tsgname-binding rules — executed by an in-tree, tree-sitter-0.26 fork of thetree-sitter-graph/tree-sitter-stack-graphsengines (maintained undercrates/) — to build a per-file scope/definition graph and resolve references to their true definitions. Delivers intra-file precision (shadowing, per-function/method parameter scope, Python comprehension bindings, Java field-vs-local) and cross-file resolution for Python: dotted and relative imports resolve to their target module, and each call site of an imported name resolves through the import to the exported definition (reportingresolved: true), rather than matching by name. No new MCP tools or query surface — existing navigation just gets precise for these languages. Known limitations this iteration: Java cross-file resolution of member calls on an imported type (Foo.greet()) is not yet resolved (the imported type itself is), and a variable reused after a same-name Python comprehension may bind to the comprehension's own variable. [code_intel] precise_resolutionconfig toggle (defaulttrue). Set it tofalseinbasemind.tomlto skip the precise engines (oxc + stack-graphs) and fall back to fast tree-sitterlocalsscope binding for every language. Applies to files (re)scanned after the change.[resources]governance config. A new table bounds basemind's memory and CPU footprint for fitting a large workspace on a modest host:embed_threads(caps the ONNX embedding pool; supersedes the deprecated[documents].embed_max_threadsalias),embed_batch_size(default 32),scan_threads(caps the scanner rayon pool),document_models(full|code_only|none— narrows the document tier for a code-centric workspace), andmax_footprint_mb, enforced by a best-effort backpressure gate that samples the processphys_footprint(machTASK_VM_INFO, which counts compressed and swapped-out pages) at the document-extraction and embedding admit points and parks over-ceiling workers on a bounded backoff — never failing a scan. The schema change is additive (noRELEASE_MINOR/blob bump).inbox_wait— a blocking comms tool that returns the moment a peer posts to a joined thread (or on timeout), replacing theinbox_read/thread_listpolling loop. Backed by the broker's existing push fan-out via a membership-routedSubscribeInboxsink over an ephemeral connection, so a long wait can't head-of-line-block other comms calls.- Per-call latency in every tool response. Latency-relevant tools now return
elapsed_us(tool body only — index/store lookups, git walks, ranking, response construction; excludes transport and serialization) and the CLI additionally reportsstartup_us, so query cost is observable and distinct from process startup. Microseconds because the warm code-map path is routinely sub-millisecond.
Changed
- Cache moved out of the repo. Everything the scanner and index used to write under a repo's
.basemind/now lives under the global XDG cache, keyed by workspace — a repo checkout no longer gains any basemind-owned files or directories. - Blobs are global and deduped across repos. The content-addressed blob store used to be per-repo; it is now one machine-wide store, so identical file content scanned from different repos or worktrees is extracted and stored once.
find_callersreports a complete result; precision is now additive. The name scan (the same sound scanfind_referencesruns) is the floor and definestotal/hits; import resolution can only refine it, surfacing precision as a per-hitresolvedflag plus aresolved_totallower bound. Previously resolution ran first and returned early, so a partial resolution could report a confident, complete-looking, wrong subset (see Fixed).- 1000-line module cap is now enforced.
tests/max_lines.rswalkssrc/**/*.rsand failscargo teston any file over the cap — the documentedrust-max-linespoly check never actually existed (poly's custom rules are ast-grep matches and cannot count lines). Several files that had drifted past the cap were split along real seams into sibling modules, behaviour-preserving by construction (thekeys.rssplit keepssymbol_kind_byte's persistedu8ordinals byte-identical). - Dependency upgrades. xberg
1.0.0-rc.30, crawlberg1.0.7, tree-sitter-language-pack1.13.1, and the latest compatible versions across the tree;arrow-array/arrow-schemaheld at 58 in lock-step with lancedb 0.31. stack-graphs is vendored undercrates/with two upstream panics fixed.
Removed
- Per-repo
.basemind/writes. basemind no longer creates or gitignores a.basemind/directory inside a repo;basemind initno longer adds it to.gitignore. - Room comms API.
room_list/room_join/room_leave/room_post/room_historyand the CLIbasemind comms rooms/join/leave/room-create/post/historysurface are replaced by thread comms (see Added above).
Fixed
basemind initno longer scaffolds into a parent directory.initresolved its target with the same ancestor-.basemind/walk the other commands use to attach to an existing index, so running it inside a repo whose parent already had a.basemind/wrotebasemind.toml,.gitignore, and the rules block into the parent instead of the current repo.initnow anchors to the closest enclosing git repository (falling back to the working directory when not in a repo), so it always scaffolds the project you run it in.- Root discovery no longer climbs across a nested subrepo boundary.
discover_root_with_basemindwalked up looking for an ancestor.basemind/without a ceiling, so running from inside a nested git subrepo (checked out under a polyrepo that has its own root.basemind/) wrongly attached to the parent polyrepo's index. The upward walk is now bounded by the closest enclosing git repository — the subrepo's own root is the ceiling. A non-git monorepo (no git boundary anywhere) still walks freely to an ancestor.basemind/. - Fixed Codex plugin MCP startup by using the required root
.mcp.jsonshape and resolving the launcher relative to the installed plugin instead of an unsupported${PLUGIN_ROOT}placeholder. find_callersreported 2 callers where there were 172. It ran import resolution first and returned early on any hit, so the name scan ran only as a fallback when resolution found nothing. When resolution saw 2 of 172 real call sites it reportedtotal: 2with no truncation flag — a confident, complete-looking, wrong answer an agent would act on. The name scan is now the floor and resolution only annotates it (a Python case that returned 7 of ~4,000 now returns the full set).workspace_grepreturned a fast, confident, wrong zero.scan_capbounded files visited, so a default grep over a 68k-file workspace searched ~2.9% of it in arbitrary path order and stopped — and a rare identifier, which is precisely what one greps for, does not live in the first 2.9%.limitnow caps hits, the whole corpus is scanned (rayon-parallel with a memmem literal prefilter), andtotal_matches/total_files_matchedare exact. A latent cursor bug that dropped a file's remaining matches on a mid-filelimitboundary is fixed by packing(candidate, hit ordinal)into the cursor.- The git-history index was built and read from nowhere on a
--features fullbinary. Fjall's directory lock is exclusive even for a read-only open, so serve (which opens read-only undercomms) could not hold the history DB; the handle wasNone, serve never built the index, and every git tool silently live-walked forever. The daemon now owns both the build and the reads (GitHistoryIndexis aLocal | Remotebackend enum), the per-repo build is serialized so N sessions cause one walk, and the one-shot CLI routes its history reads through the daemon instead of climbing a retry ladder and live-walking. Measured, daemon up: CLI startup ~1.56 s → ~5 ms, a git query ~1.59 s → ~10 ms. search_documentscould never see anything the web lane ingested. The scanner writes documents under the repo scope whileweb_scrape/web_crawlwrite underweb:<host>, and the search hard-filtered on the repo scope with no way to name the other namespace — so every scraped page was written, embedded, and then permanently invisible.scopeis now a search parameter (defaulting to the repo scope), and the ingest tools echo the scope they wrote under, so the pair composes.- A daemon
--features fullbuild embedded nothing it scanned. The forwarded initial scan ran aDeferred(code map + BM25 keyword) pass only; the vector-fill follow-up the non-daemon path spawns was never forwarded, so LanceDB stayed empty for every repository document and code chunk (web_scrapeworked because it embeds inline). The daemon can now run anInlineembed pass, andserveforwards a detached vector-fill follow-up after the fast initial scan — off the boot handshake, so startup never blocks on ONNX. Live watcher rescans and therescantool forward it too. - A failure in an optional lane destroyed the code map. The scanner flushed the code map after
the optional lanes (embeddings, documents, code-intel), so any lane that failed took the whole map
with it — leaving gigabytes of blobs behind a
file_countof 0 and a full re-scan on every launch. The map is now persisted before the optional lanes run, each lane runs undercatch_unwind, and a per-file panic in the resolve pass costs only that file's edges. - The machine daemon's footprint climbed to ~13 GB at monorepo scale and never released. The
Inlineembed pass accumulated every file's embedding rows (a 768-dim vector + chunk text per chunk) for the whole corpus in RAM before one LanceDB write. It now streams per file, so peak embed RAM is one file independent of corpus size (aphys_footprint-ratchet regression test pins it flat across passes). A related serve-side RSS sawtooth — a whole-map rebuild on every no-op rescan — is gone: an(path, content-hash)fingerprint proves the blobs are byte-identical and the resident map is reused. - A panicking request handler pinned its daemon forever. The idle reaper counts live links, and
the refcount was released by a plain statement after the accept loop, which an unwind walks past —
so one panicking handler left the count above zero and that daemon never reaped, stacking an
immortal process on every run. The refcount is now an RAII
LinkGuardreleased byDrop(also closing the accept/exit race), and idle detection consults in-flight work, not link count alone. - Every CLI invocation rebuilt the whole in-RAM map cache — ~3–5 s flat, up to 11 s under load,
even for
repo-info, which never reads the map. The one-shot CLI now builds it lazily at the cache barrier, only for tools that scan the corpus (repo-info~534 µs vssearch_symbols~1 s). En route this fixedcomplete()reading the map without taking the barrier every other cache-reading tool takes — an intermittent flake that would have become a permanent empty under lazy-cache. - Orphaned workspace caches pinned blobs forever. Nothing reaped the per-workspace cache dir of a deleted repo, so the global blob store could only grow (48 workspace dirs for 2 live repos). A marker-based reaper now records the canonical root and reclaims dead workspaces before the blob sweep — conservatively keeping anything it can't prove dead (held lock, or a root whose parent is also missing, e.g. an unmounted volume).
- Two agents sharing one identity shared one inbox. The CLI's persisted-id tier read the
pre-0.22 in-repo path that no longer exists, so every CLI call collapsed onto a shared
"basemind-cli"constant (andserveonto"anon"). All four resolvers are consolidated to a single chain — env, config, then a generated id persisted in the correct per-workspace cache dir, then a process-unique id — never a shared constant, and honouringcomms.agent_id. - A thread scoped to
<dir>/**was invisible to an agent whose cwd is<dir>. globset's**requires a component after the slash, so the recursive form never matched its own base — the most common cwd. The stripped base is retried, covering the root without widening the glob. - Cross-file
goto_definitionsilently dropped every tsconfig-aliased import. oxc resolves a tsconfigpathsalias against a canonicalizedbaseUrl, so on a symlinked root the resolved target's prefix didn't match and the cross-file edge was discarded. The resolver now discovers the governing tsconfig per importing file (whattscapplies) andto_repo_relativeretries against the canonical root — a no-op on non-symlinked roots. - A daemon-writer rescan didn't invalidate in-flight
search_symbolscursors on unchanged content. Thecache_generationbump was nested inside the map rebuild, which the fingerprint optimization skips on a no-op rescan, so a stale cursor read as valid — contradicting the documentedinvalidated on rescancontract. The bump is now unconditional. - Telemetry recorded call latency in milliseconds, so every sub-millisecond tool read as
0. The sink now stamps microseconds (legacyelapsed_msrows are rescaled on read), matching the response-levelelapsed_usreading. - Fjall ran on its 32 MiB default block cache against a multi-GB index. The cache is now sized at
20% of the on-disk index, clamped to [32 MiB, 256 MiB] (
BASEMIND_INDEX_CACHE_BYTESoverrides);find_references("get")on django went 907 µs → 285 µs for +15.9 MB RSS. web_crawlfollowed links off the seed host. crawlberg'sstay_on_domaindefaults off and the engine builder never set it, so a crawl wandered onto every domain it linked to. On-host scope is forced now (subdomains still excluded).web_mapreturned every URL a site has, uncapped — 482k URLs on docs.rs drove peak RSS into the multi-GB range and OOM-killed the process.limit(default 100, max 1000) now caps the response, and crawlberg 1.0.7'smap_limitbounds the sitemap fetch itself, holding peak to roughly the cap plus one child sitemap;total_urlsbecomes a floor withtruncatedset.cache_statsno longer flakes when a*.tmpfile vanishes mid size-walk, and an fsevents watcher test race (an event landing before stream registration is lost forever) is closed by re-writing until the watcher reports the path.
[0.21.1] — 2026-07-11
Changed
- Extracted the Hermes Agent plugin into a standalone
basemind-hermes-pluginPyPI package. The helper skills, slash commands,plugin.yaml, and thehermes_agent.pluginsentry point now live in the pure-Pythonpip-package-hermes/(import packagebasemind_hermes_plugin), which ships no binary and shells out to thebasemindonPATH. Thebasemindpip package is now a pure binary-wrapper (no plugin, no entry point). Install the binary via Homebrew/npm/cargo/release andpip install basemind-hermes-pluginfor the plugin, thenhermes plugins enable basemind. The entry-point name staysbasemind, so the enable command is unchanged.
Fixed
- Binary download no longer crashes when
/tmpand$HOMEare on different filesystems (#38).ensure_binarystaged the extracted binary in the system temp dir and then atomically renamed it into~/.cache/basemind, which raisedOSError: [Errno 18] Invalid cross-device linkon hosts where those are separate mounts. Staging now happens under the cache root so the rename stays on one device.
Dependencies
- Bumped
xbergto1.0.0-rc.21and rancargo upgrade --incompatible:crawlberg1.0.5,jsonschema0.47,rmcp2.2,regex1.13,lru0.18.1,tree-sitter-language-pack1.12.5.arrow-array/arrow-schemaheld at 58 to stay in lockstep withlancedb0.31's transitive Arrow. Blob + index format unchanged (patch release).
[0.21.0] — 2026-07-10
Minor bump: RELEASE_MINOR advances 20 → 21, so the .basemind/ index + blob cache is wiped and
rebuilt from source on the next basemind scan (expected — the msgpack blob store is content-
addressed and rebuilds losslessly).
Added
basemind initonboarding +/bm-initslash command. A re-runnable setup flow that scaffolds the rootbasemind.toml, gitignores.basemind/, and injects an idempotent, clearly-delimited rules block into the repo's agent guidance (CLAUDE.md/AGENTS.md, or an.ai-rulezrule file when that's the source of truth) teaching agents to prefer basemind over grep/read/git per capability. Interactive by default;--yesplus--with/--without,--rules-target,--no-rules, and--print(dry-run) drive it non-interactively. The block is spliced between stable<!-- BEGIN/END basemind -->markers so re-running never duplicates it and never touches content outside the markers.- Monorepo-aware root resolution. basemind now walks up from the working directory to the nearest
ancestor containing
.basemind/(the way git finds.git/), so running an agent from a subfolder of a monorepo attaches to the root index instead of finding nothing..baseminddiscovery takes precedence over git-root discovery;--rootstill overrides. - Index lifecycle signalling on MCP responses +
status. Read tools attach anotice(warming_up/building_index/rescanning) with an actionable retry hint so an agent never mistakes a mid-warmup or mid-scan result for "no matches".statusgainswarming+warm_ms.
Fixed
serveanswers the MCP handshake before warming the code map. The in-RAM preload (a rayonpar_iterover every L1/L2 blob) ran synchronously before.serve(transport), so on a loaded machine — several sessions' scans saturating the shared rayon pool — it could queue for minutes, blow the client's plugin-MCP startup window, and the tools would never register. The preload now runs in a background task;initialize/tools/listanswer immediately regardless of tree size or contention. Cache-reading tools await a bounded (15s) ready signal so a query issued during warmup still returns complete data rather than an empty snapshot.diff_outline,blame_symbol,memory_audit, andproposal_accept— which read the map directly — now await warm too, closing a window where they returned a wrong diff, a spurious "not indexed" error, or a false stale verdict.basemind initrefuses to splice into a file with malformed markers. A stray or unpairedBEGIN/ENDmarker (a hand-edit or bad merge) could make a re-run replace everything between an orphaned marker and an unrelated pair, silently deleting the user's own content. init now bails on any malformed marker state instead of guessing, and absorbs a CRLF trailing newline so a Windows-line-ending rules file converges to a byte-stable fixpoint (no phantom--printdiff).
[0.20.0] — 2026-07-08
Minor bump: RELEASE_MINOR advances 19 → 20, so the .basemind/ index + blob cache is wiped and
rebuilt from source on the next basemind scan (expected — the msgpack blob store is content-
addressed and rebuilds losslessly).
Added
- Root config at
<root>/basemind.toml(committable). Config moved out of the wipe-on-schema-bump.basemind/cache to the repo root so it survives cache wipes and can be committed. The legacy.basemind/basemind.tomlis still read as a fallback; the root file wins when both exist.basemind initnow scaffolds a fully-documented root config (every value is the built-in default) and adds.basemind/to.gitignore. It refuses to overwrite an existing config and refuses to shadow a legacy in-cache config rather than silently stranding your settings. scan.follow_symlinks(defaultfalse). Symlinks often escape the repo (e.g. Bazel'sbazel-*convenience symlinks); the walker no longer follows them unless you opt in.extra_rootsalways follow symlinks regardless.embed_excludeglob lists oncode_searchanddocuments. Keep a file chunked + keyword- indexed while skipping its embedding — useful for large generated or vendored files.documents.extract_archives(defaultfalse). Opt into xberg's recursive archive extractor for.zip/.tar/.jar/…; off by default so one archive can't explode into thousands of embeds. True binaries (.so/.wasm/.exe/…) are always rejected regardless of this flag.
Changed
- Code embeddings are now OFF by default (
code_search.embed = false). A general-English model embeds code weakly, and the BM25 keyword lane already serves the NL→symbol query over the same text, so code embedding isn't worth the ONNX download + ORT pass + vector store. Code is still chunked and keyword-indexed; documents and images still embed. Existing code-search users who rely on vector search over code must setcode_search.embed = trueto keep it. - Always-on exclude floor. A hard floor of near-universal build-artifact / dependency-cache / VCS
/ editor directories (
node_modules,target,dist,build,out,.venv,__pycache__,.pytest_cache,bazel-*,.git,.basemind,.idea,.DS_Store, …) is applied on top ofscan.exclude— narrowing the user list can add to it but never drops a floor entry.
Fixed
- Worktrees reuse the shared cache instead of redoing work. Scanning a fresh git worktree no
longer re-parses files whose extraction already lives in the shared (main-worktree) blob store —
the scanner deserializes the cached L1/L2 frame instead of re-running tree-sitter. The git-history
index is likewise shared: it is derived entirely from the shared
.gitobject database, so a linked worktree resolves it to the main worktree's.basemind(mirroring the blob store) rather than rebuilding its own. On a large monorepo a cold worktree scan drops from ~181s to ~25s (~7×) with ~2.6× less peak memory. The scan summary gains areusedcounter reporting cache hits. - Re-embed on same-dim preset model swaps. The per-file embedding cache keyed on vector dimension
only, but
balancedandmultilingualboth produce 768-dim vectors — so switching between them reused the old model's cached vectors. Cache reuse now also requires the embedding model/preset to match (both the code and document tiers), so a preset change cleanly drops the old vectors and re-embeds.CodeChunkBlobgains a blob-compatibleembedding_modelfield (no schema bump). - Hugging Face model downloads no longer hang forever behind a firewall. Bumped xberg to
1.0.0-rc.16, which adds connect/read timeouts to the HF model fetch so a blocked or silently dropped connection (corporate firewall, offline host) fails fast instead of stalling the embedding / OCR / NER model download indefinitely.
[0.19.2] — 2026-07-07
Added
- Intel macOS (
x86_64-apple-darwin) prebuilt binary. macOS is no longer Apple-Silicon-only — the release now ships a native Intel binary with full feature parity.orthas no static x86_64-apple-darwin ONNX Runtime (Microsoft droppedonnxruntime-osx-x86_64after 1.23), so the Intel leg builds--features full,ort-dynamic(ort/load-dynamic) andpackage-release.shvendorslibonnxruntime.dylib+ its closure next to the binary with@loader_pathinstall-names; ort resolves it relative to the executable, so ONNX loads with zero end-user setup.brew installnow works on Intel Macs too. The release completeness gate expects five archives.
Fixed
- Apple Silicon under a Rosetta x86_64 shell now installs. Rosetta masks
hw.optional.arm64, so the previous probe fell through to the Intel branch and aborted with "Intel macOS not supported" — even though the machine is Apple Silicon. All three installers (shell launcher, npminstall.js, pipdownloader.py) now also probesysctl.proc_translated(set only under Rosetta, which exists only on Apple Silicon) and hand such shells the native arm64 binary. A genuine Intel Mac gets the new x86_64 binary. /bm-statuslinenow selects the correct (highest) version. The persisted resolver ranked cachedstatusline.shcopies by mtime (ls -dt), so an older version dir touched more recently won — showing a stale bar. It now resolves the marketplace clone first, else the highest-versioned cache copy viasort -V, and never renders blank (a one-line hint replaces an empty bar).
Changed
- Installers prune stale cached binaries. The launcher and pip downloader accrued a directory per
release under
~/.cache/basemind/(bin/)<version>(users saw six). Each now removes old per-version dirs once the current version is confirmed present, so only the running version is kept. - Dependencies refreshed to latest (
cargo upgrade --incompatible):xberg→1.0.0-rc.11,fjall→3.1.6, plus transitive lockfile updates.arrow-array/arrow-schemaare intentionally held at58until lancedb's transitive arrow moves (they must stay in lockstep).
[0.19.1] — 2026-07-07
Fixed
scan.extra_rootsnow works on Windows. Files outside the repo root are keyed by their absolute path; the Windows drive-prefixed form (C:/…) is now recognized on the consuming side — previously only POSIX/…keys resolved, so external files were unqueryable there. Unix keys are unchanged (index format untouched).
Security
- Bump
crossbeam-epochto 0.9.20 for RUSTSEC-2026-0204 (invalid pointer dereference in thefmt::Pointerimpl forAtomic/Shared), pulled transitively via rayon.
[0.19.0] — 2026-07-07
Minor release — the index + blob schema version bumps (
RELEASE_MINOR18 → 19), so.basemind/is wiped and rebuilt on the next scan.
Added
- Hermes Agent plugin. basemind now integrates with Hermes Agent:
pip install basemindexposes a Hermes plugin (discovered via thehermes_agent.pluginsentry point) that bundles the helper skills, slash commands, and agent-comms notifications. Tools are wired through Hermes's MCP config (mcp_servers.basemindin~/.hermes/config.yaml) — a Hermes plugin cannot declare an MCP server. See the README "Hermes" install section. Closes #36.
Changed
- Claude status line survives version bumps.
/bm-statuslinenow writes a version-independent resolver as thestatusLinecommand (re-resolving the newest installedstatusline.shat each render) instead of a version-pinned path that broke on update. The bar also shows the running basemind version (v<version>, full/compact tiers; opt out withBASEMIND_STATUSLINE_VERSION=0).
Performance
Wide hot-path allocation + algorithmic sweep across the scanner, extraction, git, and MCP query paths. All changes are internal — no behavior, response-shape, or on-disk-format change, and the determinism assertions are unchanged.
call_graph(bfs_callees) precomputes a name→sites map once instead of re-scanning every indexed symbol for each discovered callee — O(max_nodes × symbols) → O(symbols + max_nodes).architecture_map:callee_countsno longer allocates aStringon every one of up to 4M scanned call sites (allocates only on first sight of a name); the symbol-tier fan-out set skips the allocation for duplicate callees; PageRank reuses a single accumulator buffer (20 allocations → 1). Output stays byte-identical.- Tree-sitter
QueryCursoris now pooled per thread alongside the parser pool — ~3 fewer allocations per scanned file. - git:
diff_fileno longer clones the (potentially multi-MB) file buffers just to record existence; theblame/log/commit-filesdisk-cache writers serialize from borrowed slices instead of cloning the whole payload;dependentsdrops aRelPath↔PathBufround-trip over the entire file index. - document tier:
build_doc_rowsmoves per-chunk text + embedding vectors instead of cloning them; the archive-extension check is anAHashSet(O(1)) instead of a 40-element linear scan. - paths: the scanner (
scan_paths) and watcher skip a\→/Stringallocation on Unix. - BM25 index: the posting value drops a
.to_vec()(lsm_tree::Slice: From<[u8; N]>) and the forward-map value is built in one sized allocation instead of an alloc-plus-realloc.
Internal
for_each_call_in_file(the dual-backend Fjall / in-RAM call-site scan) now lives once inmcp/helpers_calls.rs, shared byarchitecture_mapand the call-graph helpers;capture_nameis deduplicated intoextract/mod.rs. Removed a deadlast_emitted_keyinfind_implementations.
[0.18.1] — 2026-07-06
Patch release — blob + index format unchanged.
RELEASE_MINORstays 18; no.basemind/rebuild. Query-side only.
Changed
architecture_mapsymbol tier now ranks by specificity-weighted fan-in. The repo-wide symbol tier previously ranked by raw name-based call count, so ubiquitous names (new,from,default) dominated — on a real repo the top ~60 hubs were allnew, which misleads an agent routing on the result. Fan-in is now divided by the number of files defining that name (RepoGraph::def_counts), so a genuine single-definition hub outranks a name that is merely everywhere. The rawfan_incount is still reported verbatim on each node.
Fixed
architecture_mapsymbol-tierscoreis now monotonic with node order.fan_outwas computed after the knee-cut, so the blendedscorenever actually influenced selection or ordering and the emitted nodes were not sorted by their ownscore. Selection, knee-cut, andscorenow all key off the single specificity-weighted signal, matching the module/file tiers;fan_outand per-file churn remain reported but no longer pretend to affect the ranking.
[0.18.0] — 2026-07-06
Minor release — cache rebuild.
RELEASE_MINORbumps to 18, so both the Fjall index and the content-addressed blob store are wiped and rebuilt from source on the nextbasemind scan(the standard minor-release migration). No config or on-wire tool-response change.
Added
architecture_mapMCP tool — deterministic, LLM-free architecture overview. Ranks modules and symbols by graph centrality + churn and surfaces circular-dependency clusters, all derived from real call edges (never hallucinated). A whole-repo adjacency graph is built in memory at query time from the cached call sites (Fjallcalls_by_path, or the in-RAM call cache in read-only multi-session mode) — no new Fjall partition, no blob I/O. Metrics are cheap and deterministic: fan-in/fan-out degree, fixed-iteration PageRank (damping 0.85, 20 iterations, id-ordered accumulation — call-twice byte-identical), and iterative Tarjan SCC for cycle clusters. Three tiers viagranularity:module(default; files collapsed to a directory prefix atdepth),file(the base graph), andsymbol(function→function drill-down under afocus). Results are ranked, Kneedle knee-cut, then bounded by the sharedapply_budgettoken budgeter; edges are emitted only between surviving nodes. Optionalinclude_churnoverlay reuses thehot_filesaggregation. Node/edge caps settruncated+truncation_reasonon overflow. CLI:query architecture-map. New internalkneedleknee-detection helper (Satopaa 2011, dependency-free).
[0.17.0] — 2026-07-05
Minor release — cache rebuild.
RELEASE_MINORbumps to 17, so both the Fjall index and the content-addressed blob store are wiped and rebuilt from source on the nextbasemind scan(the standard minor-release migration). No config or on-wire tool-response change.
Added
- Semantic code search (Phase 1, vector-only). New
search_codeMCP tool runs vector KNN over source-code chunks andget_chunkfetches a chunk's body — the same two-call pointer pattern assearch_symbols→expand. Chunks are derived from the cached L1/L2 extraction + source bytes (no re-parse): one chunk per symbol span plus module-level gap chunks, oversized spans split with overlap. A content-addressed<hash>.chunk.msgpacksidecar caches chunks + embeddings so an unchanged file skips re-embedding. Vectors land in a new LanceDBcode_chunkstable alongside the documents/memory tables (shared embedding preset). CLI:query search-code/query get-chunk. Behind thecode-searchcargo feature (folded intofull); BM25 keyword + RRF hybrid fusion land in later phases. Thecode_chunkstable is created lazily on open, so it rides the existing minor-release.basemind/rebuild — no separate schema bump. - Keyword code search (Phase 2, native BM25).
search_codegains amodeparameter:semantic(the default vector lane) orkeyword, a native Okapi BM25 (k1 = 1.2,b = 0.75) lane over each chunk's symbol + signature + doc + body text. Postings live in two new Fjall keyspaces (code_bm25_postingsinverted +code_bm25_by_pathforward for O(prefix) re-scan deletes); term frequency and document length are inlined so scoring a term is a single prefix scan, and corpus stats (N,avgdl) are recomputed once per scan. The keyword lane reads only Fjall and the chunk sidecar — no LanceDB and no embedder — so it works even with[code_search] embed = false. RRF fusion of the two lanes plus an exact symbol lane lands in Phase 3. - Hybrid code search (Phase 3, RRF fusion + exact lane + rerank).
search_codegains a thirdmode,hybrid— now the default — that fuses three lanes via Reciprocal Rank Fusion: the vector (semantic) lane, the BM25 keyword lane, and a new exact symbol lane that resolves an identifier-shaped query against thesymbols_by_nameindex to the chunks defining that symbol (the scope-aware signal a pure vector+keyword stack can't produce). Fusion joins the lanes onchunk_idand is score-scale-agnostic, so it blends an L2 distance, a BM25 score, and a symbol match order without normalization; it degrades gracefully, dropping any lane that is unavailable (e.g. the vector lane without embeddings). An optional cross-encoder rerank pass (rerank:true/[code_search.reranker], off by default) reuses the same xberg reranker as the documents tier. No index-schema change — reuses the existing keyspaces and thecode_chunkstable. search_codewhy-matched provenance. Hybrid hits now carry per-lane provenance:matched_lanes(which ofexact/vector/keywordproduced the hit, in fixed lane order) plus the 1-basedexact_rank/vector_rank/keyword_rankthe chunk held in each contributing lane. Lets an agent tell an exact-symbol match from a semantic neighbor or a cross-lane agreement without a second call. Additive, non-breaking response fields (absent outside hybrid mode).statusreports boot-scan indexing state. Whenserveauto-scans an empty index on startup, that build cost was invisible and could fold into the first query's latency.statusnow carriesindexing: truewhile the boot scan runs andindex_build_msonce it completes — so a client can tell "index not ready yet, poll again" from "no matches", separating index-build time from query time. Additive fields, absent on the common ready path.- Code-intelligence tier: scope- and import-resolved navigation. A post-scan resolve pass links
each reference to its actual definition — scope-aware, not a name match. Intra-file resolution runs
across 80+ languages via tree-sitter
locals(with vendored queries for Python / TypeScript / TSX / Go); JS/TS additionally get cross-file resolution via oxc scope analysis +oxc_resolverimport stitching. Newgoto_definitionMCP tool +query goto-definitionCLI resolve apath:line:columnto its definition, following cross-file import bindings. Behind thecode-intel-jsfeature (folded intofull) for the JS/TS engine; thelocalstier is always on. - Document tier reuses the content-addressed cache instead of re-embedding every scan. The
documents pipeline now tracks each file in a new
Index.doc_filesmap and, before extracting, reads back the cached<hash>.doc.msgpackblob (which already carries chunks + embeddings): an unchanged doc is skipped, and byte-identical content at any other path — or, with the shared cache below, any other worktree — reuses the extraction instead of re-running xberg + ONNX. Removed docs now have their LanceDB rows and tracking entry pruned.doc_filesis#[serde(default)]— additive, no schema bump. - Shared blob cache across git worktrees. Linked worktrees now resolve their content-addressed
blob directory to the main worktree's
.basemind/blobs(via gixcommon_dir), so a file's extraction + embedding is computed once and shared across every worktree of the clone; views + LanceDB stay per-worktree. Auto-GC is disabled while a shared cache exists (a single-worktree sweep could reap a sibling's blobs). - Bounded embedding threads. All ONNX embedding now runs on a dedicated rayon pool capped at
documents.embed_max_threads(default0= automax(2, cores/4)), and xberg's internal fan-out is capped to match. Code-map extraction keeps the full pool; embedding can no longer pin every core or balloon RSS on a large monorepo. - New config:
documents.max_chunks_per_document(default 2000) caps vector rows per document;documents.extension_denylistextends the built-in archive/binary skip list;documents.embed_max_threadsbounds the embedding pool.[scan] excludegains Bazel defaults (bazel-out/,bazel-bin/,bazel-testlogs/,bazel-*/), and a large-candidate-count scan logs a warning. - mtime+size fast-path on rescan. An unchanged working-tree file is now confirmed with a single
stat()(size + nanosecond mtime match + sidecars present) instead of a full read + blake3 hash — the bulk of the per-file cost on a large-monorepo warm rescan. mtime moved to nanosecond resolution so the fast-path is effectively race-free; it's an internal comparison value, so no schema bump.
Changed
- Serve boot defers embedding to a background pass. On
serveauto-scan of an empty index, the first pass now runs in a newEmbedMode::Deferredmode: it writes the code-map, the BM25 keyword lane, and the content-addressed blobs but skips the ONNX embedding, sooutline/search_symbols/ keywordsearch_codeare queryable almost immediately instead of waiting on the embed of every file. A detached second pass then re-scans inEmbedMode::Inlineto fill the vectors in (reusing the fast pass' content-addressed caches so only not-yet-embedded content is embedded, bounded byembed.max_threads), followed by GC. The CLIbasemind scan, the watcher, and manualrescanstayInlineso scripted/CI use remains deterministic. No schema or config change. - Index schema revision — new
refs_by_def/refs_by_pathFjall partitions back the resolved edges; per-file resolution facts persist as content-addressed<hash>.rref.msgpackblobs. This ships as a minor-release cut:RELEASE_MINORbumps at release time, wiping and rebuilding every.basemind/index + blob store on the nextbasemind scan(the resolve pass repopulates the new partitions). Thecode_bm25_postings/code_bm25_by_pathpartitions for the BM25 keyword lane (INDEX_PARTITION_REVISION→ 4) ride the same index rebuild — the blobs are unchanged, so re-embedding is skipped and only the secondary index repopulates.
Fixed
- Code-search BM25 postings survive an embedder failure. With
[code_search] embed = true, a failed embedder load / inference / vector-count mismatch previously returned an error for the whole file, dropping its BM25 postings too — so a transient embedding fault silently punched keyword-lane holes. The scan now degrades to a keyword-only batch (chunks + BM25, no LanceDB rows, no embedded sidecar) and retries embedding on the next scan. BM25 is independent of embeddings and is indexed as such. - L1 extraction no longer drops (or crashes on) symbols in TSLP-fallback languages whose
tags.scmleads a definition with an auxiliary capture. The combined-L1 dispatch keyed on the match's first capture, but adapted upstream tag queries (e.g. Ruby) prepend a@doccapture for a preceding comment — so a commenteddefclassified asOther, panicking debug builds (debug_assert!) and silently dropping the symbol in release. A single such file could abort the whole parallel scan, leaving the index empty. Dispatch now scans to the first capture that classifies to a known L1 class, recovering those symbols across every fallback grammar. rescanrejects path parameters that escape the repository root. Therescantool took raw path strings and joined them straight onto the repo root, so withscan.respect_gitignore = falseapaths: ["../../etc/passwd"]traversal would read (and index) a file outside the repo, then be retrievable viaworkspace_grep. Request paths now pass through the samenormalize_query_pathroot-boundary guard asshell_spawn; an escaping path is rejected withinvalid_paramsinstead of being scanned. Defaultrespect_gitignore = truealready blocked it structurally.- Archives and binaries are no longer unpacked + embedded during a scan.
should_extract_documentrejected nothing by default, so xberg recursively unpacked.zip/.tar/.jar/.whl/...and embedded every entry, and binary blobs (.so/.wasm/.class/...) were embedded to no purpose — a large, pointless cost that pinned ~11 cores and grew.basemindto gigabytes on a monorepo. A built-in extension + MIME denylist now skips them before any xberg work (images stay allowed for OCR). - Document blobs are no longer reaped by the blob GC. Because docs weren't tracked in the index,
collect_referenced_hashesnever marked their.doc.msgpackblobs live, so the boot/background GC deleted the entire doc cache after every scan. Doc-tier hashes are now unioned into the live set. - Full scans of large repos no longer abort with a stack overflow. Tree-sitter parse trees and the msgpack (de)serialization of large extraction blobs can recurse past rayon's small default worker stack (~2 MiB) on pathological (deeply-nested / machine-generated) files, hard-aborting the process on a first full scan (e.g. a freshly-checked-out git worktree; a warm rescan hid it by skipping unchanged files). Extraction and the resolve pass now run on a dedicated rayon pool with a 256 MiB per-worker stack (reserved lazily, so it costs nothing on the common shallow path).
[0.16.0] — 2026-07-02
Minor release: RELEASE_MINOR bumps 15 → 16, so every .basemind/ index + blob store (including
git-history.fjall/) is wiped and rebuilt on the next basemind scan.
Added
- Resource footprint in
cache_stats(disk + RAM). Thecache_statsMCP tool andbasemind cache statsCLI now report atotal_bytesthat reconciles withduexactly, a per-component breakdown that includes thegit-history.fjall/index (previously uncounted — the "1.15 GB reported vs 1.5 GB on disk" gap), another_bytescatch-all so an uncounted directory can never silently vanish again, and process memory (rss_bytes+peak_rss_bytes) via a new cross-platform RSS reader (src/sysres.rs; mach on macOS,/proc+getrusageon Linux). Disk sizing now uses on-disk block counts, so Fjall's sparse journals are no longer over-reported. The Claude statusline gains the on-disk total + serve-process RSS. - Full CLI↔MCP parity. Every MCP tool an agent can call is now reachable from the CLI: new
git search(→search_git_history),governance audit(→memory_audit),query expand(→expand), and a newshellsgroup (spawn/send/capture/kill/broadcast/list→ theshell_*tools,--features shells; sessions persist via the shared rmux daemon). Three CLI-only text primitives gained MCP tools —delta,checkpoint,detect_waste. A newtests/cli_parity.rsguard enumerates the advertised MCP tool set and fails if any tool lacks a CLI counterpart. scan.extra_roots— index directories outside the repository root (#34). Point the new[scan] extra_roots = [...]config knob at absolute paths outside the repo (e.g. a Bazel external repo cache,/private/var/tmp/_bazel_<user>/<hash>/external) and their files join the code map (symbol search, references, callers, outlines) and document search. External files are keyed by their absolute path — repo files stay repo-relative, so the two namespaces never collide andfind_referencesresolves across the boundary. Missing/unreadable roots are skipped with a warning; a root inside the repo is ignored. Symlinks are followed (Bazelexternal/is symlink-heavy). Extra roots are (re-)indexed on a fullbasemind scanonly — the live watcher does not track them — and git blame short-circuits with a clear error for external (untracked) files. The feature itself changes no index/blob format; a re-scan populates external files (and this release'sRELEASE_MINORbump wipes + rebuilds.basemind/anyway).
Fixed
- Git author/keyword lookups routed to the wrong tool.
search_git_historyandrecent_changesdescriptions now state their contracts sharply — author/"what did X do"/keyword questions belong to the full-depthsearch_git_history(scans every commit reachable from HEAD), not the bounded 100-commitrecent_changeswindow. Precision is verified against realgitat full branch depth by the newtests/git_parity.rsharness (opt-in viaBASEMIND_GIT_PARITY_REPO). - Double-run lock UX.
basemind scan/rescannow pre-detect a livebasemind serve/watchholding the store lock (via a non-blockingprobe_writer_lock) and print an actionable notice naming the holder, exiting cleanly instead of colliding with a raw lock error. - Misleading
statuslatency.statususes a non-blocking read, so a multi-minute rebuild holding the write lock is no longer recorded as the call's wall-clock; it returns immediately withrebuild_in_progress: truewhen a writer is live. cache_statsno longer hard-fails on a stale/unreadable index. It reports sizes regardless and flagsblob_accounting_ok: falsewhen orphan accounting had to be skipped (e.g. a schema-version mismatch), instead of erroring out./bm-statsworks offline. The plugin command is now CLI-first (like/bm-doctor), readingbasemind cache stats/basemind telemetrywhen the MCP server is disconnected.
[0.15.1] — 2026-07-02
Patch release: docs / skills / plugin-manifest only. No schema or RELEASE_MINOR change, so
existing .basemind/ caches are untouched on upgrade.
Added
- Dedicated per-capability skills. Split the "use basemind" guidance into three new
narrowly-triggerable skills alongside the existing umbrella
basemindandbasemind-comms:basemind-code-search(symbol search / outline / references / callers / call graph / implementations /workspace_grep),basemind-git-history(history / blame / diffs / churn /search_git_history), andbasemind-documents(semantic + full-text document search, NER / keyword filters, and web ingestion). Mirrored into the Codex / Cursor / OpenCode plugin trees byscripts/sync-plugin-skills.sh. llms.txt. Root-level llmstxt.org index pointing agents at the capability skills, docs, and config schema.
[0.15.0] — 2026-07-02
Minor release: RELEASE_MINOR bumps 14 → 15, so every .basemind/ index + blob store (including
git-history.fjall/) is wiped and rebuilt on the next basemind scan — which is also how existing
repos populate the new git-history full-text search index.
Added
search_git_history— full-text search over commit history. Search commit author name + email and message summary + full body, tokenized (lowercased, split on non-alphanumeric) and matched as an AND.fieldscopes toauthor,message, orall(default);limit+cursorpaginate like the other git-log tools. Backed by two new git-history partitions — agh_term_to_ordsinverted term→commit-ordinal index (reusing the newest-first delta-varint posting encoding) and agh_commit_text_by_ordbody store kept out of the head-decode hot path. When the git-history index isn't fresh (read-only session / mid-build) the tool degrades to a bounded live walk over the recent window, flaggedpartial(author + summary only). To support it, commit extraction now capturesauthor_email+ fullbody, and the stored commit-meta head gains anauthor_emailfield (sha ‖ time ‖ author ‖ email ‖ summary ‖ files).
Fixed
- Multi-session MCP contention: the writer→read-only downgrade race. A
basemind servethat rightfully held the.basemind/.lockwrite lock could still come up read-only when a concurrent reader transiently held Fjall's single-holder index lock — leaving the repo with zero writers (no auto-scan / watcher / rescan → a silently stale index). Fixed on both single-holder Fjall stores: the writer now retries a transientLockedon open (it already owns.basemind/.lock, so the contention always clears), read-only openers probe.basemind/.lockand skip the Fjall open entirely when a writer is live (serving from the concurrently-readable blobs), and the schema-version check no longer does a throwaway double-open that widened the race window. The downgrade log now names the real lock holder instead of always blaming "another serve".
Changed
- git-history / index / comms Fjall opens collapsed to a single
Databaseopen + inline schema read (was: a throwaway peek-open before the real open).GIT_HISTORY_SCHEMAbumps+4 → +5for the FTS commit-meta layout, wiping and rebuilding.basemind/git-history.fjall/on the next scan — a no-op for released users (the git-history index only became user-visible with this release).
[0.14.0] — 2026-06-30
Minor release: RELEASE_MINOR bumps 13 → 14 (a new SymbolKind::Heading variant), so every
.basemind/ index + blob store is wiped and rebuilt on the next basemind scan — which is also how
existing repos pick up Markdown headings.
Added — Markdown / Obsidian support
Markdown files (already parsed, but previously yielding no symbols) are now a first-class surface:
point basemind at an Obsidian vault or any Markdown notes directory and the code-map tools work over
it the same way they do over source. All of this is on by default — .md is scanned like any other
file, headings ship via a src/queries/markdown.scm override, and the reference graph is harvested
by a fence-aware byte-scan in extract/l2.rs (the tree-sitter block grammar models none of these
inline constructs).
- Headings → outline. ATX (
#…######) and setext headings becomeHeadingsymbols, sooutlineandsearch_symbols(optionallykind: "heading") navigate a note's structure. - Wikilinks → backlinks.
[[Note]],[[Note#Heading|alias]], and![[Embed]]become references, sofind_references "Note"returns the notes linking to it. Targets resolve by note name (#anchorand|aliasstripped), matching Obsidian. - Standard Markdown links → backlinks.
[text](Note.md)/also become references, normalized to the same note name (directory,.md,#anchorstripped,%20decoded), so wikilink and Markdown-link vaults share one backlink graph. External URLs (http(s)://,mailto:) and bare#anchorlinks are ignored. - Tags → graph. Inline
#tag/ nested#area/suband YAML frontmattertags:(both[a, b]and- alist forms) become references keyed on the leading#, sofind_references "#project"lists every note carrying that tag. Tags inside fenced code blocks are skipped.
Not yet mapped (deliberate follow-ups): block references (^id, [[Note#^id]]), frontmatter
aliases / arbitrary properties, and task/callout items.
[0.13.0] — 2026-06-30
Minor release: RELEASE_MINOR bumps 12 → 13, so every .basemind/ index + blob store is
wiped and rebuilt on the next basemind scan (intentional; no action needed).
Fixed
servewatcher no longer pegs multi-core CPU on gitignored / nested-.basemindchurn (#33). A writerservewatching an umbrella repo woke on every filesystem event except those under its own.basemind/, then ran a no-op incremental scan that still re-serialized the index (store.flush) and rebuilt the entireMapCacheover the whole corpus — on every debounced batch. With nested child repos each flushing their own.basemind/in a mutual loop (or anynode_modules/ build churn) this rebuilt the cache indefinitely. Three fixes:- The watcher now drops every event a full scan would never index — including nested
child-repo
.basemind/writes — before waking a rescan, honoring the full nested.gitignorehierarchy (via theignorecrate) plus the configured exclude globs. The incrementalscan_pathsis now gitignore-aware too, matching full-scan semantics. scan_pathsshort-circuits when a batch changes nothing indexable — nostore.flush, no work.scan_and_refreshupdates theMapCacheincrementally for the changed paths (re-reading only those blobs) instead of rebuilding the whole corpus, and skips the rebuild and cache-generation bump entirely when nothing changed (which no longer needlessly invalidates paginating clients' cursors).- Note: on macOS, FSEvents is kernel-recursive, so excluded subtrees cannot be un-watched; their events are now filtered in-process in microseconds instead of triggering a rebuild.
- The watcher now drops every event a full scan would never index — including nested
child-repo
Changed
- Extracted the scanner's path-filtering (
Filters, the newIndexFilter) intosrc/scanner_filter.rsto keepscanner.rsunder the 1000-line cap.
[0.12.2] — 2026-06-29
Patch release: blob and index formats are unchanged (RELEASE_MINOR stays 12), so no
.basemind/ rebuild.
Changed
tree-sitter-language-pack1.9.0-rc.45→1.12.0. The grammar bootstrap (ensure_grammars) now uses tslp'sprefetch, which probes real on-disk loadability instead of the in-memoryhas_languageregistry — replacing basemind's hand-rolledDownloadManager::ensure_languagesworkaround for the pre-1.12download()short-circuit. tslp 1.12's lock-free staticget_languagefast path also removes a global-mutex hop on the per-thread parser pool's hot path (tree-sitter is ~30% of scan time). Validated against the full harden harness: all 8 OSS repos pass with 0 extraction failures and scan times at or under baseline.
Patch release: blob and index formats are unchanged (RELEASE_MINOR stays 12), so no
.basemind/ rebuild.
Fixed
- Multiple concurrent sessions on one repo no longer break code-map navigation. fjall takes
an exclusive directory lock, so only one
basemind serveprocess can open a repo's index at a time; additional editor/agent sessions fall back to read-only and previously got an error (read_only_index_unavailable) from every fjall-backed tool — the reported "blocked / not responsive".find_references,find_callers,find_implementations, andcall_graphnow answer from in-RAM indexes built from the content-addressed blobs (which are concurrently readable), so unlimited sessions work against one repo: one writer on fjall, N readers on the shared blobs.outline/search_symbols/dependents/ git tools already worked read-only.
[0.12.0] — 2026-06-29
Minor release: the document/RAG and web-crawl engines move to their renamed,
MIT-relicensed crates. RELEASE_MINOR bumps to 12, so the blob + index formats
are considered incompatible — .basemind/ is wiped and rebuilt from source on
the next basemind scan.
Changed
- Document tier now builds on
xberg1.0.0-rc.1(waskreuzberg5.0.0-rc.35) and the web-crawl tier oncrawlberg1.0.1(waskreuzcrawl0.3.0-rc.55). These are the renamed, MIT-licensed successors of the same engines, so thedocuments,memory, andcrawlfeature surfaces are unchanged. TheElastic-2.0license exceptions for the old crates are dropped fromdeny.toml. - Document extraction migrated to xberg's async
extractAPI. xberg 1.0 removed the synchronousextract_file_syncwrapper; the scanner's doc path now drives the asyncextract(ExtractInput, &ExtractionConfig)on a shared multi-thread runtime and reads the per-document result out of the new batch-shapedExtractionResult.results. arc-swap1.9.1→1.9.2viacargo upgrade.arrow-array/arrow-schemastay pinned to58to match lancedb 0.30's transitive arrow.- Web crawling now blocks private/loopback/link-local targets by default (SSRF
protection inherited from crawlberg 1.0).
web_scrape/web_crawl/web_mapagainst127.0.0.1,10.0.0.0/8,169.254.0.0/16, etc. are rejected with an SSRF-policy violation. A newcrawl.allow_private_networkconfig flag (defaultfalse) re-enables them for internal-docs hosts you control; theCRAWLBERG_ALLOW_PRIVATE_NETWORKenv var is also honoured as a process-wide override.
Fixed
- Status line no longer renders blank on Linux —
build_basemind_lineread file mtimes with BSDstat -f %mfirst. On GNU coreutils-fmeans "display filesystem status" and succeeds, printing a multi-line blob instead of failing, so the|| stat -c %Yfallback never ran; underset -euo pipefailthe blob's bareFileword aborted the command substitution as an unbound variable, emptying the whole line. Anepoch_mtimehelper now tries GNUstat -c %Yfirst and falls back to BSDstat -f %monly when-cgenuinely fails (macOS), used at both mtime call sites (#32).
Patch release: blob and index formats are unchanged (RELEASE_MINOR stays 11), so no
.basemind/ rebuild. A tar security bump on the npm installer, a Homebrew-tap fix, and
raised runtime floors.
Security
- npm installer bumps
tar6 → 7.5.16 (GHSA-vmf3-w455-68vh) — the bundled extractor was on atarline whose PAX size-override parsing differential allows file smuggling. The fix only exists intar7.5.16+, which is ESM-only and requires Node ≥ 18;install.jsnow pullstarin via a dynamicimport()at the extract site, and the npm package's engine floor moves to Node ≥ 22.
Fixed
- Homebrew tap no longer breaks on Intel hosts — the generated formula's
on_intelblock calledodieat formula-load time, which aborted everybrewcommand that read the tap on an Intel Mac (poisoning bottle builds and installs for all formulae in the tap, not just basemind). The Apple-Silicon-only constraint is now expressed viadepends_on arch: :arm64, evaluated at install time instead of load time.
Changed
- Runtime floors raised — the npm package (Node ≥ 22, was ≥ 14) and the opencode plugin (Node ≥ 22, was ≥ 18); pip package requires Python ≥ 3.10 (was ≥ 3.8). Older runtimes are end-of-life.
[0.11.0] — 2026-06-26
Minor release: RELEASE_MINOR bumps to 11, so the blob and Fjall index formats are considered
stale and every .basemind/ is wiped and rebuilt from source on the next basemind scan (one-time,
intentional). The headline is a precomputed git-history index that turns the history MCP tools from
full-walk-per-query into sub-millisecond lookups, plus an install fix that unblocks Apple Silicon
Macs running an x86_64 shell, Node, or Python under Rosetta.
Added
- Precomputed git-history Fjall index —
commits_touching,find_commits_by_path, andsymbol_historypreviously walked the commit graph on every call, paying an O(history) cost per query. They are now backed by a per-repo Fjall index built once at scan time and refreshed incrementally for new commits, so queries are O(result) sub-millisecond lookups. Posting lists are stored newest-first, keepingcommits_touchingO(n) in the returned commits rather than in total history depth.
Fixed
- Apple Silicon installs under Rosetta no longer abort —
uname -m,os.arch(), andplatform.machine()all report the process architecture, so an x86_64 shell, Node, or Python running under Rosetta reportsx86_64on Apple Silicon hardware. All three installers (the shell launcher, npminstall.js, pipdownloader.py) matched the Darwin/x86_64 branch and aborted with "Intel macOS not supported", even though the native arm64 binary runs fine. They now probe a hardware-level signal the translation layer cannot spoof (sysctl -n hw.optional.arm64) and resolveaarch64-apple-darwinwhenever that probe — or the reported arch — indicates arm64. Fixes #28. - Windows
hardentest compiles again — the new on-disk index-size measurement usedstd::os::unix::fs::MetadataExt::blocks()unconditionally, breaking thewindows-latesttest leg's compile. The block-count path is nowcfg(unix)-gated with alen()fallback on other platforms.
Performance
- Path-scoped history walks —
commit_touches_pathcomputed the full recursive tree diff of every walked commit just to test membership of one path, so a single history query over a deep monorepo could run for minutes (measured >5 min on a 242k-commit repo). It now looks up the entry at the exact path in the commit tree and each parent viagixlookup_entry(O(path depth) object reads, no sibling recursion), comparing(blob oid, mode). Semantics are identical togit log --full-history -- <path>; the same query drops to ~11 s worst case and ~2.5 s typical.
Changed
- Dependencies — bumped the embedded
rmuxshells trio (rmux-client/rmux-sdk/rmux-server) from 0.6 to 0.7 and refreshed the lockfile for compatible updates.arrowstays pinned at 58 to match whatlancedbresolves transitively.
[0.10.3] — 2026-06-25
Patch release: blob and index formats are unchanged (RELEASE_MINOR stays 10), so no
.basemind/ rebuild. Three Windows-only correctness fixes surfaced once the comms suite and the
full tool sweep run on the windows-latest CI leg, plus install/release hardening so a partial
publish can no longer leave the plugin launcher unable to install.
Fixed
- Windows full scan now produces
/-separated index keys — the full-scan walker was optimized to feedPath::to_str()straight into the index without the\→/normalization the incrementalscan_pathspath still did, so on Windows every nested file was keyed with backslashes (vendored\inner.rs) and forward-slash lookups missed — breaking all subdirectory queries (outline / search / references for any file not at the repo root). Normalize at the walker source; the extra allocation is Windows-only and never touches the Unix hot path. - Windows comms named pipe now isolates by
comms_dir—comms_socket_pathderived the Windows pipe name from the username only (\\.\pipe\basemind-comms-<user>), ignoringBASEMIND_COMMS_DIR, so every comms dir on a host collapsed onto one per-user singleton pipe. Parallel comms suites (each isolated to its own tempdir) collided — daemons cross-contaminated, one test's teardown killed another's daemon, and concurrentcomms startraces hung to the CI timeout. The pipe name now mixes in a hash ofcomms_dir, mirroring the per-dir Unix socket; production (which leavesBASEMIND_COMMS_DIRunset) keeps a single stable per-user broker. Atimeout-minutes: 30backstop on the CI test job prevents any future hang from blocking the queue-not-cancel main concurrency. - Windows
comms startno longer hangs — the detached broker daemon inherited the launcher's stdout/stderr: on WindowsCreateProcessruns withbInheritHandles = TRUEwhenever stdio is redirected, leaking every inheritable handle into the child — including the pipe a parent captured viaCommand::output()(orserve's MCP stdio). The long-lived daemon then held the write end open, so the capturing parent never saw EOF and blocked until the daemon died. Unix is immune (Stdio::nulldup2's/dev/nullover the child fds and Rust setsCLOEXECon its own pipes).spawn_detached_daemonnow clearsHANDLE_FLAG_INHERITon its std handles before the detached spawn, so the daemon inherits none of them; the real-daemon comms E2E suite runs on Windows again. - Plugin launcher names an incomplete release instead of failing opaquely — when a pinned
release is missing a platform binary or its checksums file (a partial publish, as 0.10.0 was),
mcp-launch.shdied with a bare "download failed" / "could not fetch checksums" that the MCP client surfaced only as "failed to connect". It now reports the release as incomplete and tells the user to update the plugin (Claude Code:/plugin update) to a complete release. - Releases publish atomically —
create_releasemade the GitHub release live before its binaries finished uploading, so a failed platform build (as 0.10.0's Linux legs) left it half-populated with no checksums, and the launcher's checksum-verified download then fails closed for every user pinned to it. The release is now created as a draft; afinalize_releasejob promotes it only once all four platform archives and the checksums file are present (and the npm / PyPI / Homebrew publishes gate on that finalize, since they download from the release). A failed platform build now leaves a hidden draft, never a live, broken release.
[0.10.2] — 2026-06-25
Patch release: blob and index formats are unchanged (RELEASE_MINOR stays 10), so no
.basemind/ rebuild. Fixes the Linux release archive, which 0.10.1 shipped broken on a clean host.
Fixed
- Linux release binaries: bundled libraries could not find their siblings — the archive
bundles native
.sos (libheif, libaom, …) intolib/, but only the main binary carried the$ORIGIN/librpath. A bundled lib with a sibling dependency (libheif.so.1→libaom.so.3, both inlib/) had no rpath of its own, so on a clean host the loader failed withlibaom.so.3: cannot open shared object file— even onbasemind --version. The in-container packaging smoke missed it because the build container has those codecs system-installed.package-release.shnow sets$ORIGINon every bundled lib so sibling-to-sibling deps resolve, verified by running the real release artifact on a clean glibc-2.28 host.
[0.10.1] — 2026-06-25
Patch release: blob and index formats are unchanged (RELEASE_MINOR stays 10), so no
.basemind/ rebuild. This release completes the 0.10.0 distribution — it ships the Linux
binaries and the npm / PyPI / Homebrew packages that 0.10.0 could not produce.
Fixed
- Linux release binaries (x86_64 + aarch64) — the 0.10.0 Linux build linked ort's prebuilt
ONNX Runtime with
cargo-zigbuild, which fails because zig links LLVMlibc++while the prebuilt is built against GNUlibstdc++. The Linux binaries now build inside the officialmanylinux_2_28containers with native gcc. The prebuilt ort also references glibc 2.38 symbols (__isoc23_strtol/ll/ull,__libc_single_threaded); a small compatibility shim (scripts/glibc228_compat.c) backfills them so the binaries keep a GLIBC_2.28 floor (RHEL 8 / Debian 11 / Ubuntu 20.04+ / Amazon Linux 2023). Because 0.10.0's binary legs failed, its checksums and the npm / PyPI / Homebrew publishes were skipped; 0.10.1 carries the same source and completes every distribution channel.
[0.10.0] — 2026-06-24
Minor release: RELEASE_MINOR bumps 9 → 10, so the on-disk index version changes and every
.basemind/ cache rebuilds from source on the next scan. This rebuild is one-time and
harmless — the blob and index formats are unchanged.
Added
- Agent shells: embedded rmux daemon + six MCP tools — spawn detached headless shell sessions
via
shell_spawnwith optional cwd, env overrides, and title. Drive sessions withshell_send(write stdin),shell_capture(visible screen),shell_broadcast(multicast input),shell_list(enumerate with liveness), andshell_kill(terminate). basemind embeds the daemon (re-execs itself with--__internal-daemon— no externalrmuxbinary). Sessions are long-lived across tool calls and driven headless via MCP. - Visual attach (Unix + Windows) — when
[shells].visualis notheadless,shell_spawnopens the session in a terminal tab/window attached to it via a hiddenbasemind --__internal-attachre-exec (macOS / Linux emulators or Windows Terminalwt.exe; basemind ships no externalrmuxbinary). Presentation is best-effort — a spawn never fails just because no terminal could be driven — and the response'sattach_commandis returned for manual re-attach. - Comms-coupled session rooms (Unix + Windows,
commsfeature) — spawned children auto-join a session-scoped comms room via inheritedBASEMIND_SESSION_ID/BASEMIND_PARENT_AGENT_ID/BASEMIND_AGENT_ID, enabling bidirectional parent↔child messaging and forming parent→child inheritance chains across agents. [shells]configuration sub-tree — controls visual presentation mode (current/window/web/headless; defaultcurrent= new tab in open terminal), terminal emulator choice (auto/iterm2/terminal_app/windows_terminal/gnome_terminal/konsole/wezterm/alacritty/kitty/xterm), and session pty dimensions (default_cols/default_rows) and lifecycle (keep_on_exit). Requires--features shells.- Multi-agent comms orchestration — one
servenow drives many named sub-identities over a single connection via a per-callas_agentparameter, backed by a per-identity broker-client registry parented to the orchestrator. Newdm_senddelivers a direct message into another agent's inbox over a private pairwise room;agent_register/agent_listadvertise and discover the live roster. The whole surface is mirrored on the CLI (basemind comms … --as-agent,dm,room-for-path), and amulti-agent-roomskill + a code-review-panel demo show the pattern. - Per-repo comms rooms + recency — agents auto-join a default room keyed by the repo, created on
demand via
get_or_create_chat_room_for_path;Globalis repurposed for machine-wide ops coordination. Reads are recency-aware:room_history/inbox_readdefault to the last 24h (since_hourswidens,0reads the full append-only log), each message carriesage_secs, androom_listflags a roomstaleafter 7 days of silence. - Windows support for agent comms + shells — the comms broker (named-pipe transport, singleton,
signal handling) and the embedded rmux shell runtime now build and run on Windows, and the comms
MCP tools are exposed there.
--features fulltherefore ships agent comms and agent shells on every published platform — macOS, Linux, and Windows — not Unix only. A dedicated Windows CI job builds and tests thecomms+shellssurface.
Changed
- Dependency refresh — kreuzberg
5.0.0-rc.30→rc.35(document-tier extraction stack) andrmcp1.7→1.8.arrowstays at 58 (lock-step with lancedb 0.30). rmcp 1.8 deprecates MCP logging (SEP-2577); basemind keeps the capability for now since the status line andrescanprogress depend on it. - Linux release binaries target glibc 2.28 (RHEL 8 / Debian 11 / Ubuntu 20.04+ / Amazon Linux
2023) — the two
*-unknown-linux-gnuartifacts are built withcargo-zigbuild(zig as the linker), pinning the required glibc symbol floor without changing runtime behaviour (still dynamically linked). A CIobjdumpguard fails the build if a dependency pulls a newer symbol. macOS / Windows artifacts are unchanged.
Removed
- Experimental A2A server (
--features a2a) — the gRPC + JSON-RPC/SSE Agent-to-Agent server, its protobuf codegen +proto/, thebasemind a2a servecommand, the buf CI job, and the a2a-only dependency stack (tonic, prost, axum, axum-server, tower, tower-http, rustls, reqwest, subtle, rcgen, …) are removed. It was opt-in and never part of the shipped release surface; the maintenance cost (pinned codegen, held-back deps, a dedicated buf CI job) outweighed its experimental value. The comms agent-card shapes stay A2A-schema-aligned, so an HTTP front-end can still be added behind theCommsFrontendtrait later without it.
Fixed
- Comms daemons no longer pile up — a busy broker that missed a single liveness ping could be wrongly judged dead and have its socket unlinked-and-rebound by a new daemon, orphaning the original on a dangling socket that nothing reaped promptly; across many sessions dozens accumulated. Two guards: the liveness probe now retries before declaring a daemon dead (so a live-but-busy broker is not reclaimed), and a Unix socket-ownership watchdog makes any daemon whose socket was unlinked or replaced self-terminate within seconds instead of lingering as an orphan.
- Comms daemon takes over a previous build on load — when a serve or CLI brings up the broker and finds the socket held by an older or protocol-incompatible daemon, it now asks that daemon to stop and spawns a current one in its place, converging the singleton on the newest binary; if the predecessor will not yield, it errors out clearly (naming the version + pid) instead of silently talking to an incompatible daemon — which is how the pre-0.10 skew surfaced as an opaque "connection closed". A same-version (or newer) daemon is reused, so concurrent sessions still share one broker.
- Comms messages now expire — the broker prunes messages past a 7-day TTL on startup and hourly thereafter, so the user-global comms store cannot grow without bound. Recency-aware reads already hid old messages; this reclaims their storage. Room records and per-room sequence counters are left intact so read cursors stay monotonic.
- Concurrent serve sessions no longer collide (#26, #27) — the editor plugin spawns one
basemind serveper session, but the store write lock is single-holder. A contending serve now starts in a read-only mode (instead of exiting and handing the MCP client an opaque-32000), so its tools register and the session is usable:outline,search_symbols,list_files,dependents,workspace_grep, and the git tools answer from the in-RAM map and git. Fjall takes an exclusive lock on its index, so the call/reference tools (find_references,find_callers,find_implementations,call_graph) cannot read it from a second process — they now return a clear "held by another basemind serve" error rather than a misleading empty result. The lock-holding serve stays the sole writer; a read-only serve'srescanreturns the same clear error. Lock contention fails fast with the clean lock message rather than the multi-GB busy-spin reported on 0.9.0 (#26). - Agent-shells hardening —
shell_spawnnow honours the[shells].enabledmaster switch, confinescwdto the repository root (rejecting../ absolute escapes), threads the configureddefault_cols/default_rowsinto the spawned pty, validatesBASEMIND_SHELLS_SOCKETon the client path, rejects carriage returns in env keys/values, and widens the loader-injection warning list (LD_AUDIT,DYLD_FALLBACK_LIBRARY_PATH). The shells and comms modules build cleanly on both Unix and Windows (see Added); a build without those features still excludes them entirely. - Status line recognizes pre-0.9 indexes — the basemind status line keyed its "scanned yet?"
check on the fused
.fmblob introduced in 0.9, so an index written by an earlier binary (split.l1/.l2blobs) showed "scanning…" indefinitely though it was healthy. It now accepts both layouts and never double-counts the secondary blob layer.
Security
quinn-protobumped to 0.11.15 (RUSTSEC-2026-0185) — fixes a remote memory-exhaustion vector via unbounded out-of-order QUIC stream reassembly, pulled transitively under--features full.- Known/tracked advisories carried this release (no fix available yet):
bincode1.3.3 unmaintained (viarmux-proto),memmap20.9.10 unsound pointer offset (vialancedb, documents/memory).
[0.9.0] — 2026-06-23
Minor release: RELEASE_MINOR bumps 8 → 9, so the on-disk index version changes and every
.basemind/ cache rebuilds from source on the next scan. This rebuild is structural — the
per-file extraction blob format changed from two files (<hash>.l1.msgpack + <hash>.l2.msgpack)
to one combined frame (<hash>.fm.msgpack); any old split blobs left on disk are reclaimed by
cache gc. A scanner hot-path pass cuts cold-scan wall time ~22%. Bumps kreuzberg to 5.0.0-rc.30.
Changed
- Scanner: ~22% faster cold scans — two flamegraph-ranked wins. (1) Per-file Fjall index commits
are now batched per rayon worker (256 files per commit), removing the ~14% of scan time worker
threads spent serializing on Fjall's single write lock. (2) The L1 outline and L2 calls are fused
into one content-addressed blob (
<hash>.fm.msgpack), so the default eager-L2 scan does oneopen+ atomicrenameper file instead of two. Measured min-of-7 cold scans on a 6.7k-file repo: 2.68 s → 2.07 s; the per-file read-before-write index atomicity is preserved. - Bump kreuzberg to
5.0.0-rc.30— picks up the upstream Tesseracttessdata-path fix, so the released binary resolves trained data at runtime instead of baking in the build runner's path. Resolves #12. - Dependency refresh to latest compatible (gix 0.85, criterion 0.8, …). arrow stays at 58 to match
lancedb 0.30; the experimental
a2afeature's gRPC deps are held pending a tonic-0.14 migration.
Fixed
- Comms daemons no longer leak across sessions. A daemon orphaned by a dead session (reparented to pid 1) used to keep its socket + flock and never exit, so they accumulated indefinitely. The broker now tracks connected links + last activity and self-terminates after 30 min with no clients, via the normal drain path. A live client (even a quiet subscriber) keeps it alive. Unix-only.
- Removed the document-tier troubleshooting workarounds from the README now that both upstream
kreuzberg bugs are fixed (TLS-MITM in rc.29,
tessdatain rc.30). Resolves #14.
Removed
- The pre-0.9 split-tier blob format (
.l1.msgpack/.l2.msgpack), superseded by the combined.fm.msgpackframe.cache gcreclaims any left behind by the schema-bump refresh.
[0.8.0] — 2026-06-22
Minor release: RELEASE_MINOR bumps 7 → 8, so the on-disk index version changes and every
.basemind/ cache rebuilds from source on the next scan (one-time; the blob/index formats are
unchanged, so the rebuild is harmless). Deepens the MCP surface — prompts, argument completions,
logging, and progress notifications — and hardens the consumer experience: cleaner document-tier
scans, a cold-start scan path agents can invoke, serve diagnostics, and a recovery runbook. Bumps
kreuzberg to 5.0.0-rc.29.
Added
- MCP prompts (
prompts/list+prompts/get) — four reusable, parameterized workflows that teach a client to drive basemind structure-first:onboard-repo,trace-symbol,explain-file,review-working-tree. - MCP argument completion (
completion/complete) — autocompletes prompt arguments from the in-RAM code map:trace-symbol'ssymbolagainst indexed symbol names,explain-file'spathagainst indexed file paths (pure prefix scan, no store lock). - MCP logging + progress —
logging/setLevelplus arescan_completelog with scan counts, and start/done progress notifications onrescanwhen the client supplies a progress token. - Tool annotations on every MCP tool (
read_only_hint/destructive_hint/idempotent_hint/open_world_hint) so clients (Claude Code et al.) can auto-approve read-only tools and only prompt for mutating ones. - Cold-start indexing for agents — a
bm-scancommand +basemind-scanskill that runbasemind scanvia the CLI (no MCP server required), so an agent can build the index when basemind reports "no index". - Recovery runbook — a
bm-doctorcommand +basemind-doctorskill: diagnose the index, detect a stale lock via the.lock.metaholder pid, rebuild via scan, and reconnect the MCP server (client-specific). Plusbasemind servenow logs its lifecycle (startup pid/version/view and the exact exit reason) to its stderr / the client's MCP server logs.
Changed
- Bump kreuzberg to
5.0.0-rc.29— picks up the TLS-certificate fix for model downloads behind corporate TLS-MITM proxies (hf-hubnow uses the platformnative-tlsprovider, honoring the OS cert store +SSL_CERT_FILE/SSL_CERT_DIR). Resolves #14.
Fixed
- The document tier no longer counts non-extractable files as failures. A file tree-sitter doesn't
recognize as code (e.g. an Erlang
.app.src, whichmime_guessmaps toapplication/x-wais-source) is now skipped — and logged at debug — instead of inflating the failed count; genuine extraction errors on real documents still count as failures.
[0.7.0] — 2026-06-22
Minor release: RELEASE_MINOR bumps 6 → 7, so the on-disk index version changes and every
.basemind/ cache rebuilds from source on the next scan (intentional, one-time). The headline is a
first-class code-aware token-reduction surface (the compress/expand tools, per-call budgets,
TOON encoding, behavioral output compression) plus code-grounded memory/skill governance
(memory_audit + git-mined skill proposals). Also ships MCP tool annotations so clients can
auto-approve read-only tools, and a sweep of CLI/MCP bug fixes.
Added
compressMCP tool — code-aware compression (tree-sitter structural elision + cheap lexical passes) with an honest before/after token report;expandpulls a single symbol's body back.- Per-call
max_tokensbudgets on high-volume tools (outline,search_*,find_references,workspace_grep,search_documents, …): rank-to-fit with an explicit truncation marker + cursor. - Opt-in TOON encoding (
format:"toon") for high-volume list responses, and an opt-in lean tool surface (BASEMIND_MCP_LEAN) that advertises three wrapper tools instead of the full set. - Behavioral output compression (
basemind compress-output/delta/checkpoint/detect-waste) with credential-safe, fail-open semantics, plus opt-in plugin hooks (Bash-output compression, read-cache deltas). - Code-grounded governance —
memory_auditverifies stored memories against the live code map (structural-hash drift → stale, decay + 90-day auto-archive) and git-mined skill proposals (proposals_mine/proposals_list/proposal_accept/proposal_reject); additive provenance/verification fields onMemoryRecordand two new lazy Fjall keyspaces. - MCP tool annotations on all tools (
read_only_hint/destructive_hint/idempotent_hint/open_world_hint) so consumers (Claude Code et al.) can auto-approve read-only tools. - Real tokenizer-backed token counts (o200k under
documents) in the compress report and telemetry, and instructive-32602parameter errors via aLenient<T>wrapper + serde aliases (acceptquery/pattern/needle/name/regex/… across the search tools). - Plugin integrations for Kimi Code and pi, and a Codex-readable marketplace manifest.
Changed
- Bump kreuzberg to
5.0.0-rc.28. - Honest telemetry:
est_tokens_savednow credits document/list/web tools instead of reporting 0, andsearch_symbols.total(total_is_partial) /list_files.limit_clamped/status.blob_countexpose what was previously silent. scan/rescanexit0when per-file read failures occur but the index was still updated (previously exit 2 masked a successful scan);scan --revskips submodule gitlinks instead of failing;-q/--quietnow suppresses subsystem INFO/WARN logs.cache cleargains aviews:<name>selector to clear a single view without nuking all of them.- Slimmer tool descriptions to cut per-session schema tokens.
Fixed
- Lock-contention errors now name the actual holder (
serve/watch/scan/rescan) via a.locksidecar instead of always guessingwatch(#11). querycommands resolve absolute and./-prefixed paths to the indexed key (#19); serving or querying a never-scanned named view now errors instead of silently returning empty (#18).comms statusgives an actionable "daemon not running — start withbasemind comms start" message (#21); comms verbs no longer emit a false "--jsonhas no effect" warning (#20).git_cache_bytesdocumented as the disk layer only (#23); CLI lock / LanceStore-shutdown crashes and legacy comms front-matter decoding fixed.- Known upstream issues in the document tier (OCR
tessdatapath #12, model download behind a TLS-MITM proxy #14) are documented with workarounds; fixes tracked in kreuzberg and picked up on a later bump.
[0.6.3] — 2026-06-21
Patch release: schema unchanged (RELEASE_MINOR stays 6). Reworks the plugin MCP launcher so it
no longer depends on npx/uvx at runtime — fixing intermittent start-up failures when several
agent sessions (or the comms-monitor poll loop) launch basemind concurrently.
Fixed
- Plugin launcher npx/uvx race —
scripts/mcp-launch.shpreviously exec'dnpx basemind@<ver>(thenuvx) as the runtime. npx stages into a shared, spec-hashed~/.npm/_npx/<hash>directory, so two concurrent launches raced on it and failed withENOENT … package.json; the binary was also never cached, so every launch re-resolved over the network and inherited node/python start-up cost plus lavamoat postinstall blocks. The launcher now has a single install method: download the checksum-verified prebuilt release binary once into a stable per-user cache (${XDG_CACHE_HOME:-~/.cache}/basemind/bin/<version>/), serialized with an atomic lock, and exec it directly on every subsequent launch.npx/uvxand theBASEMIND_LAUNCHERoverride are removed; setBASEMIND_BIN=/path/to/basemindto point at a local dev build. The npm/PyPI/Homebrew/cargo install channels are unchanged.
[0.6.2] — 2026-06-21
Patch release: schema unchanged (RELEASE_MINOR stays 6). Fixes the macOS binary so the
npm/PyPI/Homebrew/cargo wrappers actually run — 0.6.0 and 0.6.1 shipped an unrunnable macOS binary.
Fixed
- macOS binary SIGKILL on launch — the release packaging bundles non-system dylibs into
lib/and rewrites their@loader_pathinstall names withinstall_name_tool, which edits the Mach-O in place and invalidates the linker's ad-hoc code signature. macOS then killed the process on first page-in (SIGKILL, "Code Signature Invalid") with no output, so the binary downloaded by every wrapper was unrunnable. The packaging now re-signs each dylib and the binary ad-hoc after the rewrites and verifies--strictbefore packaging. - pip/uvx downloader — a stale or partial
~/.cache/basemind/<ver>/madeos.replace()fail withErrno 66(cannot overwrite a non-empty directory), wedging installs until a manual cache wipe. The downloader now clears a stale cache dir under its lock and retries the atomic move.
[0.6.1] — 2026-06-21
Patch release: blob/index/comms schema unchanged (RELEASE_MINOR stays 6), so no
.basemind/ rebuild. Fixes the Windows release build that blocked the 0.6.0 wrapper publish.
Fixed
- Windows build — the agent-comms substrate is built on Unix domain sockets
(
UnixStream/UnixListener, peer-cred auth) with no Windows analogue, butcommsimportedtokio::net::UnixStreamungated, so the--features fullx86_64-pc-windows-msvcbuild failed to compile (since comms landed in 0.5.0). This failed the Windows binary job and held back the npm/PyPI/Homebrew publishes. The comms surface is now gated onall(feature = "comms", unix)— on Windows the binary compiles with comms absent; unix is unchanged. 0.6.0 published to crates.io only; 0.6.1 is the first complete cross-channel release of the 0.6 line.
[0.6.0] — 2026-06-20
Minor release: RELEASE_MINOR bumps 5 → 6, so the blob, Fjall-index, and LanceDB schema versions
advance — the first basemind scan / serve after upgrading wipes and rebuilds the .basemind/
cache and the LanceDB store in place. Headline: an experimental, opt-in Agent-to-Agent (A2A)
task server, plus scanner/query hot-path perf wins and release-pipeline hardening.
Added
- Experimental A2A server —
basemind a2a serve, gated behind--features a2aand intentionally excluded from the default andfullbuilds and from the shipped release binary. One axum listener serves the A2A task protocol three ways — gRPCA2AService, JSON-RPC 2.0, and SSE streaming — with the agent card at/.well-known/agent-card.json. Hardened surfaces: bearer-token auth (constant- time compare, non-loopback bind refused without a token), TLS termination (--tls-cert/--tls-key, ALPN h2 + http/1.1), and push-notification webhook delivery behind a real SSRF guard (rejects loopback, RFC-1918, link-local, and the cloud-metadata IP; re-checks the resolved address and pins it on connect). rescanlevers (CLI + MCP) plus robust auto-rescan on an empty working-view index.- comms
inbox_ack— advances the per-agent read cursor (idempotent, non-destructive; the shared append-only room log is untouched) — and richer message front-matter (ts_micros,tags,reply_to,seq, and an optionalscopepath/glob set for relevance filtering).
Changed
- Scanner/query perf — cache the parse-timeout env lookup, pre-classify L1 captures, and defer the
walk allocation in the scanner inner loop; serve
outlinefrom the in-RAM cache to cut query-path allocations; bounded-concurrent crawl indexing aligned with the kreuzberg parallelism pool.
Fixed
- Atomic release publishing + resilient wrappers — a version-consistency gate across all shipped
surfaces, an unconditional checksums job asserting every archive,
needs/result == successguards on each publish job, and fail-closed retrying npm/pip installers with an atomic cache. Fixes the v0.5.0 broken-install regression (missing checksums file → every wrapper install failed). - comms daemon reconnects and respawns on a broken pipe instead of failing the request.
0.5.0 — 2026-06-20
Minor release: RELEASE_MINOR bumps 4 → 5, so the blob, Fjall-index, and LanceDB schema versions
advance — the first basemind scan / serve after upgrading wipes and rebuilds the .basemind/
cache and the LanceDB store in place. Headline: basemind becomes a multi-agent communication
substrate (scoped rooms + per-agent inbox) on top of its context layer, with split memory and
cross-harness delivery.
Added
- Agent-to-agent communication. A singleton, user-global broker daemon (its own Fjall
store over a Unix socket, independent of any repo's exclusive index lock) hosts scope-joined
rooms — an agent auto-joins every room covering its git remote, a path prefix, or global.
Messages are two-tier: a front-matter envelope (subject · from · id) that
room_history/inbox_readscan cheaply, plus a body fetched on demand bymessage_get. An agent's own posts are excluded from its inbox. New MCP toolsagent_register,agent_list,room_create,room_list,room_join,room_leave,room_post,room_history,message_get,inbox_read, with fullbasemind comms …CLI parity. - Split memory. Memory gains an individual (per-agent) tier alongside the existing
group (shared) tier, selected by a
visibilityparameter; agent identity resolves fromBASEMIND_AGENT_ID→ config → persisted.basemind/agent-id→anon. - Cross-harness delivery. The comms mandate and a "prefer basemind over grep/git/manual"
directive ship to every harness via the MCP server
instructions, a newbasemind-commsskill, and the generated per-harness instruction files. SessionStart + UserPromptSubmit hooks inject unread front-matter on boot and per turn; a background monitor (~15 s) surfaces new messages while an agent works or idles (Claude Code). - Statusline comms segment. The basemind statusline now shows the agent's unread message count (bright when non-zero) and identity, gated on a running broker and TTL-cached so it stays cheap.
Changed
- Schema bump:
RELEASE_MINOR4 → 5. The blob, Fjall-index, and LanceDBmemory/documentsschema versions advance, so the firstbasemind scan/serveafter upgrading wipes and rebuilds the.basemind/cache and the LanceDB store in place (the latter now guarded by amemory_schema_verinmeta.jsonso the memory-table column add never faults at query time). Stored memory is rebuildable scratch, not a source of truth — re-memory_putanything you want to keep, or expect it to be re-derived.
0.4.0 — 2026-06-19
Minor release: RELEASE_MINOR bumps 3 → 4, so the blob + index schema versions advance.
The first basemind scan / serve after upgrading rebuilds the cache in place. Prebuilt binaries
now ship --features full (96 document formats, OCR, embeddings, reranker, semantic search, web
crawl, shared memory). Windows asset triple changed: x86_64-pc-windows-gnu → x86_64-pc-windows-msvc
(ONNX Runtime ships no MinGW prebuilts); anyone hard-coding the old asset name must update.
Intel macOS (x86_64-apple-darwin) is no longer shipped — macOS prebuilts are Apple Silicon
(aarch64-apple-darwin) only; Intel Mac users build from source (cargo install basemind --features full).
Added
- Full-feature prebuilt binaries via native per-platform runner. All prebuilt binaries
(Homebrew, npm, pip, GitHub Releases) now ship
--features full: 96 document formats (PDF, Office, email, HTML, archives, structured, source, markup), OCR (Tesseract), HEIC/AVIF (libheif), embeddings + reranker + NER, shared memory, and web crawl. First use downloads ML models over the network; binaries are larger. Replaced goreleaser with a new native per-platform CI runner pipeline. - Enforced binary checksum verification. All install paths (npm, pip/uvx, direct download via
mcp-launch.sh) now verify the binary's sha256 againstbasemind_<version>_checksums.txtand fail closed on mismatch or missing checksum. (This was previously claimed in the README but not implemented.)
Changed
find_referencesandfind_implementationsnow do real substring matching. Previously documented as substring matching but actually exact-only. Now genuinely substring (case-sensitive), soFoo::bar()andbar()both matchname="bar".- Dropped Intel macOS (
x86_64-apple-darwin) prebuilt binaries. macOS releases are Apple Silicon only. npm/pip/Homebrew install on an Intel Mac now fails fast with a clear message; build from source instead.
Fixed
- SSRF denylist + redirect revalidation for web crawler. Added safeguards to prevent server-side
request forgery on the
web_scrapeandweb_crawltools. - Configured embedder preset. Eliminates cross-process vector state wipe, improving performance and cache isolation.
- Atomic memory writes. Prevents torn writes on concurrent
memory_putacross sessions. - Index panic on corrupted entries. Fixed a panic when the Fjall index contained malformed keys (now degrades gracefully).
- Git blame robustness. Improved handling of edge cases (missing commits, detached HEAD, shallow repos).
- Config layer precedence fixes. MCP > CLI > env > TOML > defaults now strictly enforced.
- Hot-path allocation cuts. Reduced allocations in scanner, extract, and query paths; removed
unnecessary
String::from_utf8round-trips and clones in the Fjall secondary-index scans.
0.3.0 — 2026-06-18
Minor release: RELEASE_MINOR bumps 2 → 3, so the blob + index schema versions advance.
Unlike previous minor bumps, the cache is no longer hard-wiped — see "Durable cache
refresh" below. The first basemind scan / serve after upgrading re-extracts in place.
Added
- Full MCP↔CLI parity. Every MCP tool now has a
basemindCLI subcommand, running the identical in-process tool code against the same on-disk index (read-only, no lock — safe to run alongside a livebasemind serve). Human-readable output by default,--jsonfor the raw structured response. New groups:basemind query(outline, symbol, search, references, callers, implementations, call-graph, grep, list-files, status, repo-info, dependents),basemind git(status, history, blame, diffs, churn, symbol-history),basemind memory,basemind web, andbasemind telemetry. - Cache garbage collection + cleanup. New
cache_gc(reclaim orphaned blobs no view references),cache_stats(on-disk size + orphan accounting), andcache_clearMCP tools, plusbasemind cache gc|stats|clearCLI commands. GC runs automatically in the background onservestartup. Blobs are shared across views; GC only sweeps blobs no view's index references, under the store lock so it never races a scan. - Continuous background watch.
basemind servenow watches the working tree and incrementally refreshes its index in the background by default (debounced), keeping queries fresh without manualrescan. Opt out withbasemind serve --no-watchfor very large repos / CI. basemind-cliskill documenting the CLI surface, and a restructured README with two equal install paths (MCP plugin vs CLI + skill).
Changed
- Durable cache refresh on schema bump. A schema/version bump now refreshes the
content-addressed blob store in place (re-extract overwrites blobs at their stable
content-hash path; background GC reclaims the remainder) instead of deleting
.basemind/blobs/. No window where the expensive extraction cache is gone. The Fjall secondary index (derived, cheap) still rebuilds from blobs on its own schema bump. - Git-cache schema is now tied to
RELEASE_MINORso a release refreshes it consistently (it auto-rebuilds from git).
Fixed
- Tightened token-savings estimate. The
telemetry_summary"tokens saved" figure no longer scales with total corpus size (it previously credited ~5% of the whole repo per grep-style call). Baselines are now derived from the actual response payload, decoupled from corpus size. - Read-only store degrades gracefully on a schema bump. A CLI query run before the first
post-upgrade scan now reads an empty index ("run
basemind scan") instead of erroring, and never opens the stale Fjall index.
0.2.6 — 2026-06-18
Changed
- Responsive two-line statusline with per-capability metrics. The status line now
renders a context row (model · output-style · vim · dir · branch · context%) above the
basemind row, since a custom statusLine replaces Claude Code's default and cannot sit
below it. The basemind row breaks telemetry down per capability — searches, git, docs,
memory, web — showing only buckets with activity today, alongside calls and estimated
tokens saved. Width comes from
$COLUMNSwith full/compact/minimal tiers; override withBASEMIND_STATUSLINEand hide the context row withBASEMIND_STATUSLINE_CONTEXT=0.
0.2.5 — 2026-06-18
Added
- PreToolUse guard hook drives basemind usage. A configurable hook
(
hooks/pre-tool-guard) reaches the agent when it reaches forGrep/Globand points it at the matching basemind tool — the only Claude Code lever that can also cover subagents.BASEMIND_GUARDselects the mode:nudge(default, advisory once per session),redirect(deny with a pointer to the basemind tool), oroff. - Full-capability awareness in the agent surfaces. The always-injected MCP
get_infoinstructions and thebasemindskill now describe the whole surface — code map across 300+ languages, full-text + semantic search, git intelligence, document RAG over 90+ file formats, shared memory, and web crawl — instead of a code-map-only framing. The README leads with the same capability set.
Fixed
Store::openno longer races on a just-released lock.acquire_lockretries the exclusiveflockwith a short backoff, fixing a macOS-only flake where a sequential open → close → open hitLocked.
0.2.4 — 2026-06-18
Added
- Context-economy operating discipline shipped across every agent surface. The
MCP
get_infoinstructions, thebasemindskill, the SessionStart hook, and the README now state one default workflow: these tools return paths, line numbers, and signatures rather than file bodies, so agentsoutlinebefore reading,search_symbols/find_references/workspace_grepbefore grep, andrescanafter edits. The SessionStart hook injects this every session instead of only nudging when the statusline is unset.
0.2.3 — 2026-06-18
Added
serveauto-scans a fresh repo on startup. When the working-view index is empty, the server kicks off an initial scan in the background (reusing the in-processrescanpath, so it never contends for the Fjall lock). The agent no longer has to runbasemind scanby hand — the statusline flips from "scanning…" to live counts on its own..basemind/is now self-ignoring. The first time the store is created it writes.basemind/.gitignorecontaining*, so a user's repository never accidentally commits the machine-local index. An existing.gitignoreis left untouched.
Changed
- README redesign. Centered hero with a uniform badge row, a fenced statusline preview (replacing a broken image reference), and collapsible architecture / harness-setup sections. Clarified that registering the marketplace and installing the plugin are two distinct steps.
Fixed
- Nightly hardening CI is green again. The
hardeningjob built--features full(kreuzberg's libheif + tesseract stack) but only installed protoc, so thelibheif-sysbuild failed. The job now installs the codec dev libraries and builds libheif 1.23.0 from source (cached across runs).
0.2.2 — 2026-06-17
Added
- Claude Code plugin now auto-installs the basemind binary. The MCP launcher
(
scripts/mcp-launch.sh) tries version-matched cached binaries, then npx (npm package), uvx (PyPI package), and finally downloads the prebuilt binary from the GitHub release with checksum verification. Override the launch strategy withBASEMIND_LAUNCHER=auto|npx|uvx|download. - SessionStart hook pre-warms the binary and nudges statusline setup. The
hook runs in the background on session start to ensure the first tool call isn't
a cold install, and suggests
/bm-statuslineif the statusline isn't yet configured. /bm-statuslineslash command wires the statusline into~/.claude/settings.json. Plugins cannot set the main statusline automatically;/bm-statuslineis the one-step opt-in.
0.2.1 — 2026-06-17
Fixed
- Claude Code slash commands now load. The
/bmand/bm-statscommands were under.claude-plugin/commands/, which Claude Code ignores — plugin component directories must sit at the plugin root, and basemind's marketplacesourceis"./", so the plugin root is the repo root. Movedcommands/(and confirmedskills/) to the repo root;.claude-plugin/now holds only the manifests + statusline. The Codex / Cursor / OpenCode trees keep their own in-tree copies (their plugin root is their own directory), kept in sync byscripts/sync-plugin-skills.sh.
Changed
- Publish workflow no longer hard-fails on a missing Homebrew token. The
GitHub-release-assets job skips the Homebrew tap publisher when
HOMEBREW_TOKENis absent (and runs it when present), so an optional tap update can't fail the binary release.
0.2.0 — 2026-06-17
Minor release — schema wipe (RELEASE_MINOR=2). The blob and inverted-index
formats are versioned to the release minor, so the next basemind scan rebuilds
.basemind/ from source. This is the intentional migration path; no user action
beyond a rescan is needed.
Changed
- Dependencies on crates.io.
kreuzbergmoved from thev5.0.0-rc.15git pin to the published crates.io release=5.0.0-rc.18; the temporaryallow-gitexemptions indeny.tomlare dropped. With no remaining git dependencies,cargo publishto crates.io is unblocked — basemind now ships on all four registries (crates.io, npm, PyPI, Homebrew) from a single tag. - README is now a positioning document. Rewritten from a code-map feature inventory into a full AI-context-layer overview: four pillars (Code, Documents, Memory, Web), a feature table mapping every MCP tool to its backend, a three-flavour quickstart, comparisons against grep / vector-only RAG / repo-map indexers / GitHub search, and a differentiators section. Sub-package READMEs (npm / PyPI / OpenCode) collapse to install stubs linking the root.
- Aligned package metadata across all surfaces. One canonical description and
a positioning-led 5-keyword set (
mcp,agent-context,rag,code-map,tree-sitter) now ship identically acrossCargo.toml, the npm / PyPI manifests, and every harness plugin manifest. GitHub repo description + topics updated to match.
Added
- Redesigned Claude Code statusline. Bright 256-color palette (no more
low-contrast dim text), a
◆brand mark, a dedicated serve/scan freshness dot, and width-aware wide/narrow layouts. The file count now reads the L1 blob set directly (exact, deduped) instead of estimating from index bytes; scan recency reads the view index mtime; an empty repo renders an actionablerun: basemind scanhint instead of a blank line. The intelligence row (documents / memory / web) appears when a LanceDB store is present. - Agent-facing skills + slash commands bundled into every plugin tree. The
basemindrouting skill andbasemind-statsdashboard, plus/bmand/bm-statsslash commands, now ship inside.claude-plugin/,.codex-plugin/,.cursor-plugin/, and thebasemind-opencodenpm tarball — so an installed plugin teaches the agent how to use the tools without manual onboarding. A newscripts/sync-plugin-skills.sh(wired as a poly hook) keeps the canonical source and the per-harness copies in lock-step.
0.1.1 — 2026-06-15
Fixed
npm-package—npx basemind …(and equivalentnpm install -g basemindinvocations) used to silently fall through to whateverbasemindalready lived on$PATHbecause npm skipped creating thenode_modules/.bin/basemindsymlink when thebin:target was missing at install time (the native binary is downloaded bypostinstall). The wrapper now ships a Node.js shim atbin/basemind.jsthatspawnSync's the downloaded native binary, so the symlink target exists at install time and npx routes correctly through the wrapper.publish.yaml— re-enabled the Homebrew tap step now thatHOMEBREW_TOKENis on the path to being provisioned. Set the secret on the basemind repo before pushing the next tag; goreleaser will then auto-updateGoldziher/homebrew-tap'sFormula/basemind.rb.
0.1.0 — 2026-06-15
Initial public release. First minor wipes the schema (RELEASE_MINOR=1)
so any pre-tag .basemind/ cache rebuilds on next scan — intentional.
Feature-complete code-map server with the kreuzberg document tier surface (reranker, keywords, NER, summarization, language detection, TOON output) and schema-driven config across TOML / CLI / MCP / env vars, distributed across all major coding-agent harnesses (Claude Code, Codex, Cursor, Gemini, Factory Droid, OpenCode, Copilot CLI, and the generic MCP path) plus npm / PyPI / crates.io.
Added
basemind scan— rayon-parallel scanner that indexes a workspace into content-addressed msgpack blobs (.basemind/blobs/) plus a Fjall-backed inverted index (.basemind/views/<view>/index.fjall/). Two extraction tiers ship: L1 outlines (symbols, signatures, imports, docs) and L2 call sites; L3 structural hash available for symbol-history diffing.basemind serve— stdio MCP server (rmcp) exposing the full code-map and git-history tool surface (outline,search_symbols,find_references,find_callers,list_files,dependents,repo_info,status,symbol_history,working_tree_status,recent_changes,commits_touching,find_commits_by_path,diff_file,diff_outline,hot_files,blame_file,blame_symbol).- Dynamic 300+ language coverage via
tree-sitter-language-pack.
Hand-written
.scmoverrides ship for Rust, Python, TypeScript, TSX, JavaScript, Go. Other languages for which TSLP ships a vendoredtags.scm(kotlin, csharp, swift, cpp, scala, solidity, lua, …) get best-effort symbol + call extraction via the fallback adapter that rewrites GitHub-standard@definition.*/@reference.callcaptures into basemind's@symbol.*/@call.*shape. - Schema-driven config across TOML / CLI / MCP / env vars. Rust types in
src/config/deriveschemars::JsonSchema; the snapshot atschema/basemind-config-v1.schema.jsonis regenerated from those types and asserted bytests/config_schema.rs. Adding a config field lights up all four surfaces via#[command(flatten)](clap) and#[serde(flatten)](MCP params). Precedence: MCP > CLI > env > TOML > defaults, with per-field provenance tracking viasrc/config/source.rs. - Document tier —
search_documentsMCP tool over Lance-backed embedding index with kreuzberg ingestion. Per-query overrides on everydocuments.*andllm.*setting, plusentity_categoryandkeywords_containspost-filters. - TOON wire format for MCP responses via
documents.output.format = "toon"(or--documents-output-format toon). Round-trip parity with JSON asserted intests/mcp_smoke.rs. - Language-aware ingestion.
documents.language.{auto_detect, min_confidence, detect_multiple}flows into kreuzberg'sLanguageDetectionConfigand the chunking tokenizer. ISO 639-3 codes (e.g."fra") surface onFileMapDoc.detected_languages. - Cross-encoder reranker as a post-step on
search_documents. Off by default;documents.reranker.{enabled,preset,top_k}opts in. Preset is validated upfront againstkreuzberg::get_reranker_preset; reranked index is bounds-checked before reorder. - Keyword extraction (YAKE / RAKE) and named entity recognition at
extract time. New tail fields on
FileMapDoc(keywords,entities) with#[serde(default)]— blob-compatible with prior blob shapes (asserted bytests/schema_bump.rs). - Extractive + abstractive summarization via
documents.summarization. Abstractive routes through liter-llm with the new top-level[llm]section (model inprovider/modelform, api_key, base_url, temperature, timeout, retries, max_tokens). NER backendllmnow wires the resolvedLlmConfig. SecretStringnewtype +ApiKeyenum (Literal | Env | Unset). Secrets mask to"<redacted>"inDebug/Displayand acrossSerialize(including the toml→serde_json validation round-trip).- Real-OSS hardening harness (
tests/harden.rs,./scripts/harden.sh) — clones 8 upstream repos (ripgrep, tokio, typescript, react, django, requests, gin, ripgrep-shallow), exercises every MCP tool against each, and pins canary lower bounds (tokio:find_references("spawn") >= 200, django:find_references("get") >= 200, react:search_symbols("useState") >= 20, ripgrep-shallow truncation surfaces). - Schema sync to release version —
RELEASE_MINORinsrc/version.rsdrives bothINDEX_SCHEMA_VERand the blobSCHEMA_VER. Minor-release bumps wipe.basemind/on next scan; patch releases stay compatible. - Distribution:
brew install Goldziher/tap/basemind,npm install -g basemind,pip install basemind,cargo install basemind --locked. Precompiled binaries on GitHub Releases for{x86_64,aarch64}-{linux-gnu,apple-darwin}andx86_64-pc-windows-gnu. - Per-harness install matrix — manifests + skill bundle for every major
coding-agent harness, all bumped in lock-step by
task release:sync-version VERSION=…:- Claude Code —
.claude-plugin/plugin.json+marketplace.json+statusline.sh(live one-line summary of the indexed map with true-color brand mark, freshness dot, and call/token-savings telemetry from.basemind/telemetry.jsonl). Install via/plugin marketplace add Goldziher/basemindthen/plugin install basemind@basemind. - Codex CLI / App —
.codex-plugin/plugin.jsonwith fullinterfaceblock (displayName, categoryDeveloper Tools,capabilities,defaultPrompt,brandColor).scripts/sync-to-codex-plugin.shmirrors into a fork of theopenai/pluginsmarketplace. - Gemini CLI — root-level
gemini-extension.jsonwithcontextFileNameandmcpServers. Install viagemini extensions install https://github.com/Goldziher/basemind. - Cursor —
.cursor-plugin/plugin.json. - OpenCode — published as the
basemind-opencodenpm package. Install via{ "plugin": ["basemind-opencode@latest"] }inopencode.json. Skills are bundled into the tarball; the plugin shim does dual-mode resolution (bundled-path-first, repo-root-fallback) so monorepo dev and npm install both work without duplication. - Factory Droid / GitHub Copilot CLI — reuse the existing
.claude-plugin/marketplace.jsonper their published patterns. - Every manifest carries the same canonical user-facing description and
crates.io-capped 5-keyword set (
mcp,tree-sitter,code-map,scanner,indexer).
- Claude Code —
- Release pipeline —
.github/workflows/publish.yamlpublishes on everyv*tag: GitHub release assets (cross-compiled binaries via goreleaser + zig), crates.io, npm × 2 (basemindbinary wrapper +basemind-opencodeOpenCode plugin), and PyPI. Per-registry idempotent via existing-version detection; OIDC-driven trusted publishers on all four registries.
Changed
tree-sitter-language-pack:=1.9.0-rc.27→=1.9.0-rc.45. Hierarchicaldata_extraction+ 17 data formats; rc.40–45 are CI/codegen-only.kreuzbergbumped to the reranker / LLM API surface (=5.0.0-rc.7baseline → published rc covering reranker + LLM at publish time).alloc-stdlib = "=0.2.2"pin lifted (no longer binding after lock re-resolve; singlealloc-no-stdlib 2.0.4in the tree).src/mcp/types.rs+src/mcp/helpers.rssplit into_documents.rssiblings to stay under the 1000-line cap.
Performance
- Harden 8/8 green across ripgrep / tokio / typescript / react / django / requests / gin / ripgrep-shallow. All canaries pass. Per-repo scan times within baseline: typescript 21.7 s (81 324 files), tokio 0.2 s (859 files), django 2.5 s (7 061 files), react 2.2 s, requests 0.7 s, gin 1.0 s, ripgrep 4.0 s, ripgrep-shallow 0.16 s. All 25 MCP tools clean across all repos.
search_documentspost-processing releases the store read-lock before blob I/O;ahash::AHashMap/AHashSeton the post-filter path.