| latest_version | 3.4.25 |
|---|---|
| released | 2026-08-28 |
All notable changes to the OneBrain CLI binary (onebrain) in the v3.x Rust rewrite.
Format follows Keep a Changelog.
Versioning: CLI version is tracked in workspace
Cargo.toml. v3.x is the Rust port of v2.x (TypeScript/Bun).v3.0.0-alpha.1is the first user-facing alpha (binary artifacts published to GitHub Releases for 7 platforms).
onebrain gateway run— loopback streamable-HTTP MCP endpoint (/mcp, protocol2026-07-28pinned) serving the read-only Brain pack (capabilities,brain_search,brain_get,brain_tasks) across vaults from~/.onebrain/gateway.yml. Loopback only; a remote tunnel lands later in v3.5.- OAuth 2.1 authorization + resource server for the gateway:
/mcpnow requires a Bearer access token. RFC 9728/8414 discovery, RFC 7591 dynamic client registration (public clients only — no client secrets ever minted), an/authorizeconsent page gated by a device-pairing code (5-wrong-attempt/60s lockout), and/tokenauthorization-code exchange (PKCE S256, single-use codes) plus rotating refresh tokens with reuse detection (a replayed refresh token revokes its whole token family).onebrain gateway pair [--rotate]prints or rotates the pairing code independent of a running gateway. Seedocs/gateway.md#authentication. gateway.ymlgains apublic_urlkey: the gateway's OAuth issuer base URL for the still-unshipped remote tunnel. Validated atgateway runstartup (must be a barehttps://, or loopback-onlyhttp://, origin with no path/query/fragment) — an invalid value fails startup naming the key instead of silently resolving to a wrong or insecure issuer. Seedocs/gateway.md#gatewayyml-schema.- Gateway policy engine + human approvals + audit trail (Gateway PR 4): every
tool call is classified
read_only/mutating/destructiveand checked against a per-class mode (auto/ask_once/ask_always/deny) under a newgateway.ymlpolicy:block — read-only tools default toauto(unchanged behavior), writes default toask_once, and a future destructive tool would default toask_always. A call that needs approval blocks until a human answers it, through either a native macOSdisplay dialogprompt (osascript) or the new operatorGET/POST /approvalsHTTP surface — gated by the gateway's pairing code, deliberately OUTSIDE the connector Bearer layer, so a connector's own access token can never self-approve its own pending call. An approvedask_oncecall records a TTL-bounded grant (grant_ttl_minutes, default 30) so later calls from the same client, for the same vault and risk class, don't need to ask again — the vault is part of the consent scope, so approving a write into one vault never authorizes writes into another, and anask_alwaysapproval records no grant at all. The pending-approval registry is bounded (16 overall, 4 per client): past either limit a gated call is refused with a policy error instead of queueing another human prompt. Every tool call — allowed, denied, approved, or timed out — is appended as one JSON line to~/.onebrain/gateway/audit/YYYY-MM.jsonl(redacted args summary only, never a raw note body or credential). Seedocs/gateway.md#policy--approvals. brain_capture— the gateway's first WRITE tool: creates a new inbox note from atitle/text, confined to the vault by three independent guards (a syntactic plain-relative-path check, post-canonicalization confinement that catches a symlinked-out folder, and an equality check that the confined path is exactly the path the write will open), gated by the policy engine above (RiskClass::Mutating). An emptytextis rejected asinvalid_params; a same-day title collision returns a clean tool error naming the vault-relative path; the follow-up search reindex is detached, so the call returns as soon as the note is on disk rather than waiting on a daemon cold start. Seedocs/gateway.md#brain_capture.capabilitiesnow reports each tool's risk class and the policy mode currently in force for it, plus anapproval_channelsobject (native/http/telegram) naming which approval channels can actually deliver a prompt on this machine right now — a caller is never told a write can be approved through a channel that cannot carry the prompt to a human. Seedocs/gateway.md#capabilities-truthfulness.- Telegram approval channel (Gateway PR 5).
onebrain gateway telegram setupwalks through wiring up a dedicated bot — paste a@BotFathertoken, send a one-time code back to the bot to prove identity (never arrival order or a plain "press START"), and the wizard writesgateway.yml's newtelegram.bot_token/telegram.chat_idkeys and sends a confirmation message. A blocked call now also fires an inline-keyboard prompt (✅ Approve / ⛔ Deny) to that chat, carrying the same Client/Tool/bounded-summary framing the native dialog shows — never the raw tool-call body — and edits the message to show the outcome (clearing the keyboard) once ANY channel resolves it. A demand-driven background poller (one thread per process, spawned on first need) watches for the button press viagetUpdates, persists its cursor to a bot-token-keyed offset file after every batch — so a clean restart resumes from where it left off, a crash between a batch and its persisted cursor at worst re-fetches and re-handles a few already-seen updates on the next start (harmless: approvals are in-memory and per-process, so a stale resolve is just a no-op), and rotating the bot token starts that new bot with a clean cursor instead of inheriting a stale one — and exits again once nothing is left pending (bounded by one ≤25s long-poll cycle past the last pending approval). Authorization has no pairing code at all — a button press is accepted iff its sender's Telegram id matches the configuredchat_id, which must be a private (positive-id) chat; a dedicated bot is required, since Telegram allows only onegetUpdatesconsumer per token and the OneBrain Claude Code plugin's own bot already holds that slot for its own, unrelated, vault-levelnotifications.telegram_chat_idintegration.capabilities.approval_channels.telegramreports whether a bot is configured, not whether it's currently reachable, and an audit-log line'schannelfield can now name"telegram"alongside its existing"native"/"http"values whenever a human resolved the call through it — stillnullforauto, a policydeniedwith no human involved, or atimedoutcall, exactly as before. Seedocs/gateway.md#telegram-approval-channel.
onebrain gateway runnow installs atracingsubscriber (stderr, honouringRUST_LOG, defaultinfo). It previously installed none, so every operator diagnostic the gateway emits — a failed audit write, a degenerateapproval_wait_seconds: 0, a call refused at the pending-approval cap, the full error behind a deliberately-sanitized client message — went nowhere. The pairing code andgateway listening on …lines remain plain stdout, unchanged.- Gateway note filenames now keep Unicode alphanumerics, so a Thai,
Japanese, Korean, or Cyrillic
brain_capturetitle produces a filename in that script. Previously any title without ASCII letters sanitized to nothing and fell through to a fixedcaptureslug, so only ONE such capture could succeed per day and every later one failed as a same-day collision. The length cap is now byte-aware as well as character-aware, and a capture that genuinely has no usable text to name it (an emoji-only title and body) gets a short random suffix instead of colliding with every other one that day. - MCP: rmcp 2.1.0 → 3.0.1 — protocol
2026-07-28baseline for the remote MCP gateway; stdio server and all 4 tools unchanged; legacyinitializenegotiation (2025-03-26 / 2025-11-25) now guarded by an integration test. - README roadmap re-synced to the 2026-08-28 renumber: Gateway v3.5 · Council v3.6 · Studio/Surfaces v3.7 · Terminal v3.8 · Bootstrap v3.9 · cleanup v3.10 · bundles v3.11+.
- The operator
/approvalssurface is now rate-limited by the SAME five-failures/60-second lockoutPOST /authorizeapplies to the pairing code, on one shared counter. It previously verified that code directly, so the one credential standing between a caller and self-approval accepted unlimited, unthrottled, unlogged guesses — reachable with no OAuth token at all. - The native macOS approval dialog is now time-bounded (
giving up after, sized to the call's own remainingapproval_wait_seconds), so it dismisses itself when the call it belongs to gives up. Previously it stayed up indefinitely: a human could click Approve on a prompt that silently did nothing, and each abandoned dialog pinned a blocking-pool thread until the process exited. args_summaryis bounded before it is written to the audit log or shown as an approval prompt. Tool handlers interpolate raw caller-supplied parameters into it and the audit log has no size cap or rotation, so a client could previously grow that file by a megabyte per call through a read-only tool needing neither approval nor a grant.
- Codex hooks now run through the installed CLI instead of a Python file inside a replaceable versioned plugin cache, so refreshing the plugin cannot break an active task.
checkpoint resetaccepts--session-token <TOKEN>, so/wrapupclears the counter the hook actually incremented instead of a state file resolved from the agent shell's own environment.
task list --limit Nreturns a deterministic bounded result while preserving the full filtered count indata.total, keeping startup task payloads small.onebrain session token(hidden), a resolve-only counterpart tosession initfor mid-session token recovery — it resolves the same token through the same chain withoutsession init'sclean_stale_state_fileside effect, which otherwise silently wipes the Stop-hook cadence counter when re-run mid-session.
- Claude lifecycle registration and
plugin updatemigration now converge Stop and configured PostToolUse hooks on one event-dispatchingonebrain hookrunner. Historical direct checkpoint/pending and qmd/search entries are deduplicated without touching foreign hooks; direct checkpoint/search commands remain available to users and child processes.
Restart active agent sessions after upgrading. Their already-loaded hook registrations cannot use the old codex-hook compatibility name, because that alias is intentionally not shipped; new registrations invoke only onebrain hook.
A backlog-clearing release taken deliberately before v3.5, so the gates v3.5 leans on are trustworthy first. No new features by design.
- 63 verbs that only ever answered "not implemented" no longer parse. They now fail as unknown commands (exit 2), indistinguishable from a typo, instead of advertising a surface that does not exist.
hide = truekept them out of--helpbut the parser still accepted them, so anything that discovers verbs by trying them — a script, a doc, a user — reached exit 72 for a command that was never built (#334)
schedule register --testnow runs the same validatorsregisterdoes. A configregisterrefuses outright — a control character in an argument, say — was previously accepted and executed by the one command you reach for to check a config (#375)schedule register --dry-runlists every bad entry instead of stopping at the first, so a config with three mistakes reports three (#376)--testagainst a command-mode entry says so, rather than reportingno schedule: entry matching skill …and sending you hunting a typo that is not there (#375)- 15 clippy errors in
cfg(windows)- andcfg(unix)-gated code that no Linux or macOS run type-checks (#383)
- CI runs
clippy -D warningsonwindows-latestas a separateclippy-windowsjob. It had never passed there: CI linted onubuntu-latestonly, and--all-targetsaborts at the first failing target, hiding 11 of the 15 errors behind the first 4. It is a separate job, not a matrix leg onclippy— addingstrategy.matrixrenames that job's status context, so theclippycontext branch protection requires would stop reporting and every PR would block.clippy-windowsis a required check onmain(#383) - A scheduler unit test no longer depends on the host's shell layout. It used a bare
sh, which resolves on CI's Windows runner because Git Bash is on PATH and does not on a real Windows machine — so it was green in CI and red on the platform it was meant to protect (#382) docs/platform-support.mddocuments the onnxruntimecpuid_infoline accurately: it affects virtualized ARM Linux whose hypervisor reports an invalid MIDR part number, not aarch64 Linux generally (#332)
If a script calls one of the removed verbs, it now gets exit 2 instead of 72. That is the
point of the change — the verbs were never implemented — but a script that treated 72 as
"expected, skip" will now see an unknown-command failure. The full list is committed at
crates/onebrain-cli/tests/fixtures/removed-verbs.txt.
plugin uninstall is not among them. It is a hybrid: a real, shipped implementation for
--harness codex, falling through to not-implemented only for other harnesses.
This release supersedes half of ADR 0006, which
required unbuilt verbs to be stubbed so the grammar could not drift. The <noun> <verb> grammar
and the hidden v3.0 aliases remain in force; only the stub-the-unbuilt-verbs half is reversed.
- A vanished log directory can no longer kill a scheduled skill — launchd opens no redirect for it, so there is no path left to fail (#372)
- Every scheduled skill run appends a record to the vault — readable in Obsidian, found by vault search, whether the run succeeded or failed (#377)
- The CLI opens its own job log after it starts, so it can recreate a missing directory instead of dying before it exists (#372)
Run onebrain schedule register after upgrading. The fix lives in the plist, not the binary —
existing plists keep their old redirect until they are re-emitted, so a vault that skips this step
still carries the #372 failure mode.
Command-mode entries (command: rather than skill:) are deliberately unchanged: launchd execs
their binary directly, so no OneBrain process exists to own a log or write a record for them.
doctor's scheduler checks are unchanged in this release. Teaching them to tell a scheduled run
from a manual one needs a signal that works on systemd and Task Scheduler too, not only launchd —
that is a design question and it is deferred rather than half-shipped.
- Control characters in schedule args are refused by every backend, not only the XML ones — one config now gets one verdict on all three platforms (#355)
schedule register --resumecan no longer delete a file outside the marker directory; a drive-relative name escaped it on Windows (#354)- A bad entry no longer half-registers — control characters are refused before anything is written or activated (#355)
doctorreports a missing scheduler log directory, the fault that kills every launchd job with no output at all (#362)doctorreports a scheduled skill that has produced nothing, and states how much of your schedule it can actually speak for (#363)
- An escaper regression now fails CI: the corpus carries an escaped-argument case, and a non-ignored test pins the committed fixture to what the renderer emits today (#353)
- A cited corpus fixture must exist — the evidence contract is enforced rather than stated (#359)
- Docs-only PRs skip the test matrix without stranding its required checks (#360)
Two new doctor checks may warn on an existing vault. scheduler logs is auto-fixable with doctor --fix.
scheduled output is informational — it names any scheduled skill that has produced no log, and always states
how many entries it could not check (command-mode entries and skills that write no log).
- Schedule args may contain
",$, backtick and\again — each renderer escapes its own sink (#344) - systemd: newlines refused,
;and'quoted — a bare;split one command into two (#344) - Windows: args quoted per
CommandLineToArgvW; acmd.exe /cpayload passes through verbatim (#344) - Command labels no longer truncate at 40 characters and collide — longer ones carry a hash suffix (#345)
- Registering no longer deletes a job that is still in your config (#345)
- Stale-label cleanup works on Linux and Windows, and runs after the replacement is installed (#345)
notifications.telegram_chat_idis a known config key, placed and self-documented bydoctor --fix(#348)
A vault that hand-added a notifications: block reports config layout drift once; doctor --fix moves it into
the Automation section, preserving the value and its comment. Fresh vaults are unaffected.
Known issue — editing a schedule entry's args strands the old job (#352). It stays installed and
firing, and no CLI command can reach it: --remove derives labels from the current config. v3.4.20 and earlier
behave identically. Remove a leftover by hand:
- macOS —
ls ~/Library/LaunchAgents/com.onebrain.*, thenlaunchctl bootout gui/$(id -u)/<label>and delete the plist - Linux —
ls ~/.config/systemd/user/onebrain-*, thensystemctl --user disable --now <unit>and delete it - Windows —
schtasks /Delete /TN "\OneBrain\<label>" /F
A fix was built for this release and withdrawn: schedule labels are global while its bookkeeping was per-vault, so it could delete another vault's live job. #352 redesigns it around verifying artifact ownership.
[3.4.20] — 2026-07-29 — Cross-platform scheduling parity: onebrain schedule actually schedules on macOS, Windows, and Linux
Theme: the scheduler stops being macOS-only and stops lying. Every claim below is backed by a real fire or a measured corpus, not a rendered file.
- Windows: real Task Scheduler backend —
schedule registercompiles cron/at entries into Scheduled Tasks under\OneBrain\(UTF-16 XML viaschtasks /Create; the measured 48-trigger cap, month semantics, and multi-Repetitionbehavior are pinned by a schema corpus CI re-validates against realschtaskson every PR), fire-proven end-to-end onwindows-latest— register → fire → one-shot self-delete (#310) - Linux: real systemd user-timer backend — units written to
~/.config/systemd/user, activateddaemon-reload → enable → restart; one-shots fully self-delete (units +timers.target.wantssymlink) viaExecStopPost; fire-proven on a real systemd 255 user session, corpus checked bysystemd-analyze verifyon every PR (#313, #314) - Scheduler logs move out of the vault into per-OS state dirs (
~/Library/Logs/onebrain· journald · Task Scheduler history) — a cloud-synced vault can no longer make launchd fail every run with a silent exit 78, which is the incident this release exists to end (#315) schedule list/--statusreport what the OS scheduler says, not what's on disk:launchctl print/schtasks /Query/systemctl --user is-active, with⚠for present-but-inactive — the state the old file-existence check could not see (#312)- macOS activation is imperative: register boots the job out and back in immediately (no more plists waiting for next login), and
--removeboots out before deleting so a removed job actually stops firing (#312) --dry-runprints the platform's real artifact(s) headed by the entry label; remove/preview messages no longer show launchd paths on non-macOS hosts- Help text and docs are platform-neutral, with a new
docs/platform-support.mdsection on backend semantics (logged-in-only across all three; missed runs skip on Windows/Linux by design, coalesce on macOS wake) - Test-created search collections now stamp
.onebrain-test-collectionat creation, so any future cache-root cleanup enumerates a closed set instead of guessing from names (#305)
Known: plugin update re-registers schedules while it runs, so an entry firing in that exact window sees a brief bootout/re-register gap (MN-6 — accepted, logged in the epic's decisions).
onebrain daemonnow works on Windows: process probes viaOpenProcess/GetExitCodeProcess, detached spawn viaDETACHED_PROCESS, and adaemon-<hash>.stopmarker standing in for SIGTERM — same ask/wait/escalate shape as Unix, no new dependency (#307)- Windows sessions therefore reach
Backend::Daemoninstead of falling back to the engine-owning path, so the MCP server holds no collection lock andsearch reindexsucceeds while an editor is open — previously it wasexit 77every time, and the Stop hook's--pending-onlycatch-up reporteddetached: truewhile doing nothing (#307) - A daemon whose slot files are removed while it runs now shuts itself down rather than holding the collection lock unreachably —
daemon stop --allenumerates registrations, not processes, so it could not see one on any platform (#308) Engine::remove_doccommits the keyword index before redb, so a crash mid-remove leaves a repairable state instead of permanently orphaning lex docs that then surface as hits on the lex-only fast paths (#297)onebrain search statusreportsunknown (locked)instead of the factually wrongneverwhen the index cannot be read, anddoctor's lex-index hint now names the process actually holding the lock rather than a daemon that may not exist (#307)
- Add
codexto harness detection, skill execution, ad-hoc harness runs, and per-entry scheduling. - Invoke Codex through
codex execwith workspace-write, ephemeral sessions, vault cwd, model, and JSON forwarding. - Add explicit managed Codex plugin installation with an atomic vault marker and additive feature configuration.
[3.4.17] — 2026-07-22 — Search-index health: stop writing on read paths, flag a stuck-rebuild shortfall
Theme: read paths that quietly wrote to disk or the user's config, and a doctor blind spot where a half-built keyword index reported healthy.
- Pure read paths no longer persist a generated collection name into
onebrain.yml:onebrain search model list, the MCP token/status routes, and the daemon's token-cache open all resolve the collection read-only now, so a listing or status command can't rewrite (and strip the comments from) your config (#300) - The daemon no longer creates an empty collection cache dir at boot for a never-indexed vault — it reports "no token cache" (503) instead of materializing one, which is also what had been leaking stray dirs into the cache root during the test suite (#300)
onebrain doctornow flags a keyword index that holds fewer chunks than its metadata when a rebuild is also stuck pending — the genuinely-incomplete case — and always prints ashortfall: N chunk(s)detail line when the counts differ, so a partial index no longer reads as healthy (#298)- Test-suite isolation is enforced at the source: a static guard now fails CI if any test that spawns the binary doesn't pin its cache root, closing the class of leak behind #300 rather than the one instance
[3.4.16] — 2026-07-20 — Search recall: headings become searchable, the rerank gate stops deleting hits
Theme: two defects that were removing correct answers from every search, and the schema migration the first one forces.
heading_pathis now searched, not just stored — and it shares the script-aware tokenizer withbody, so Thai and CJK headings can match at all for the first time. Heading-shaped recall doubles (hit@10 0.300 → 0.600 on a real 782-doc vault) with no loss on body-term queries (#294)- Breaking (index): that tokenizer change alters the tantivy schema, so an index built by ≤3.4.15 is migrated on first use — rebuilt from stored chunk metadata, no files re-read and nothing re-embedded (1.2 s for 6271 chunks, then 8 ms). Crash-safe: an interrupted migration is detected and finished on the next open, and a rebuild that would erase the only surviving copy refuses instead. Downgrading afterwards requires
onebrain search reindex --force(ADR 0034) search.reranker.min_scoredefaults to0.0: the reranker now reorders instead of deleting rows, restoring hits it was silently dropping (heading-shaped hit@10 0.233 → 0.500, body-term 0.500 → 0.733). The confidence bands are unchanged at 0.30/0.60 — the gate asks whether to delete a row, the band asks how much to trust it (#295)- Vaults initialized on v3.4.7–v3.4.15 have
min_score: 0.30written into their ownonebrain.ymland keep the old behaviour;onebrain doctornow flags it as a superseded default and--fixresets it onebrain doctorgains alex-indexcheck for a keyword index that is empty, duplicated or orphaned relative to its stored metadata — states that previously reported as healthy while search returned nothing;--fixrepairs the first two from metadatadoctor --fix --jsonno longer runs a repair for checks that have none, so a warm daemon no longer produces a spurious failure and exit 1
Theme: close the v3.4 line — after a binary upgrade, a warm daemon of the OLD version no longer serves stale routes (the v3.4.14 gain-dashboard-dark class), and the fake-daemon tests are deterministic for real.
onebrain updateretires every warm daemon after a successful upgrade (they respawn at the new version on next use), andonebrain plugin updateretires a version-skewed daemon for its vault — so the WebUI/mcp never keep serving an old binary's routes (#291)onebrain doctornow warns when a running daemon's version differs from the CLI, with theonebrain daemon stop --allhint — the safety net for an in-placebrew upgrade(#291)- Fake-daemon token-check tests are deterministic: verdict→exit-code mapping runs in-process (no socket timing), while the daemon-adoption/#264 regression + HTTP-branch coverage runs on a generous fixed budget — no more slow-CI-runner flakes (#289)
- CI guard asserts the README Quickstart
onebrain --versionexample matches the workspace version, so it can never ship stale again
Theme: complete the v3.4 line — every failure message says what happened, why, and what to do next; the Token-Gain dashboard tells the truth. Companion plugin release 3.4.0 ships the search cascade + grep-gate hook.
- Bind before banner:
serveprints its banner only after the listener actually binds — a failed bind shows only the error, never a URL+token; the URL now carries the actual bound port (so--port 0prints a clickable address).daemon startdetects a child that died before binding (waitpid, not a zombie-fooled probe) and says so fast instead of claiming success (#278) - Output style contract across every command: failures read
✗ what — why+💡 next step(docs/output-style.md); JSON envelopes stay single-line and glyph-free via a typed hint mechanism, error codes and exit codes unchanged (PermissionDenied binds still exit 66) (#279) - Token-Gain dashboard reads the lock-free gain JSONL — the daemon
/api/token/gain(and--all-time/--since) no longer serve the never-auto-populated rollup DB, so the WebUI shows real numbers without a manual rebuild; a bare pre-3.4.14 client request still gets the legacy all-time view (#281) - A gain read that races a
--resetarchive rename no longer errors (vanished file = zero events, not a 500) - Corrupt gain timestamps bucket under the self-flagging
1970-01.jsonl(epoch fallback), never the current month's live log - Breaking:
--since/?since=now validate strictYYYY-MM-DD— a malformed date errors (exit 70 /E_INVALID_DATE, route 400) instead of silently matching nothing; scripts passing non-zero-padded dates like2026-1-1must use2026-01-01(previously these silently returned zero results) (#287) - The remaining bare
Error:dead ends get the ✗/💡 dressing — the vault-not-found walk-up and not-a-vault-root failures (every verb's most common errors) andtoken gain --rebuild's EngineBusy remedy (now split into what/why + hint); exit codes 64/77 unchanged (#288)
Theme: make the token-optimization read-hook ledger actually gate in production (it shipped enabled-but-inert), and let one machine run a warm daemon per vault instead of one that thrashes across vaults.
- Unify the already-sent ledger key on
doc_hash+ the vault-relative, canonicalized path acrosssearch get/ MCPget/ the read-hook — so cross-surface dedup fires andtoken checkgates on the absolute paths the read-hook receives, including vaults under a symlinked path (/tmp→/private/tmp) (#255, #268) token checkroutes to a warm same-vault daemon even across a version skew — ending the cold-open lock collision that made the gate fail open 100% of the time in the field; the round-trip budget is configurable (token_optimization.check_timeout_ms, default 200) for iCloud/networked vaults, a successful deny is metered (ledger_deny) intoken gain, anddoctorflags a read-hook that fails open ~always (#264)- Per-vault daemon slots — each vault gets its own warm daemon (
daemon-<hash>.*on an ephemeral port) instead of one machine-wide daemon that thrashes when two vaults are active;daemon statusenumerates all,daemon stopgains--vault/--all, anddoctorsurfaces running daemons plus a lingering pre-upgrade one (#230) onebrain daemon startwalks up from cwd to bind the vault like every sibling verb, instead of spawning a vault-less daemon (#262)- Scheduler command-mode launchd plists embed
--vaultfor onebrain jobs (generic binaries untouched), fixing scheduled jobs that exited 78; the legacyrun-skillalias now walk-up-resolves (#263) doctor --fixbackfills a documentedtoken_optimizationsub-key missing from an existing block — parse-guarded, handles spaced/quoted keys (#270)- Hardening (Copilot findings): the
register-hooksmigration notice points at the correct command (plugin install); one-shot launchd/bin/sh -cwrappers shell-escape every interpolated value and reject shell-special chars inargskeys — closing a demonstrated command-injection
Breaking: onebrain daemon status --json now returns {"daemons": [...]} (a list) instead of a single object, reflecting the multi-daemon model. Upgrading: run onebrain daemon stop --all once after upgrading to retire a lingering pre-v3.4.13 machine-wide daemon.
Theme: no command, serve, or MCP call should error or fail to run because a daemon is (or isn't) holding the single-process redb lock.
token gainworks under a running daemon: default/--by/--history/--resetread the lock-free JSONL raw log;--all-time/--sinceroute through the daemon's/api/token/gain— even across a version skew, so it works right after an upgrade without restarting the daemon (#258)- A genuinely contended rollup open (or a daemon too old to serve the route) now reports the shared
E_ENGINE_BUSY(exit 77) with an actionable hint, instead of a raw redbDatabase already openerror at exit 1 (#258) servenow reuses or starts a daemon (restarting a stale/version-mismatched one) instead of an engine-less foreground standalone — so the Token-Gain dashboard is populated rather than dark; the explicit--port/--dirstandalone escape hatch now also opens its token cache (#257, #258)search vsearchis daemon-routable: vector-only search routes through the daemon's new/api/vault/search?mode=vecinstead of failingE_ENGINE_BUSYwhile anonebrain mcpsession holds the engine (#258)- Fixes:
doctor's scoped-key lookup no longer panics on empty segments; remove dead test code; migrate achronoDateTime::from_timestampdeprecation (Copilot autoreview)
Closes the three real seams that post-ship verification of v3.4.10 surfaced — none was caught by the epic's gates because no test exercised those exact cross-component paths — plus polish.
- Fix
search/query --output jsonerroring (E_INTERNAL) whenever duplicate chunks collapse:Signal::ChunksCollapsedis now a struct variant, serializable under the internally-tagged enum (#249) - Embed the Token Gain WebUI dashboard in the binary — the pinned web UI was a version behind, so v3.4.10 shipped without it; bumped to v0.1.8 (#250)
token checknow gates a repeat read via an in-process Direct-mode ledger check when no daemon is running, so the read-hook actually gates without a warm daemon (#248)doctor --fixbackfills a missingtoken_optimizationblock into an existingonebrain.yml, byte-identical to a freshinit(#247)- Docs:
ChunksCollapsedis now correctly documented as exact-duplicate (not near-duplicate) chunk collapse (#246)
- Token-optimization layer — a 4-rung level ladder (off/conservative/balanced/aggressive; lossless by default) shapes agent-facing
search/getoutput through a transform funnel, with an honesty-signal contract: any truncation or omission is always disclosed to the agent (with a--forcere-fetch cursor), never silent (#237, #241) - Two-tier cache — query-result memoization + an already-sent ledger that turns a repeat read of an unchanged doc into a small reference receipt instead of resending the full body;
onebrain search get <path> --forcere-materializes on purpose (#239, #241) onebrain token gain— measures exactly what was saved: a raw per-call log plus precomputed daily/monthly/yearly rollups,--bypivots, and--resetepochs for baseline comparisons (#240)onebrain token check+token discover— a fail-open PreToolUse read-hook gate (200 ms budget, off by default) plus a field-test tool that scans Claude Code transcripts for repeat reads the ledger could have saved (#242)- Dedicated token-optimization guide + a Token Gain dashboard in the embedded WebUI (#243)
- Hardening: collection-lock acquired before migration, doctor legacy-stub detection, artifact-dedup path guard, test-support parity,
session --vault(#238) - Config wiring: unset
get_max_tokens/snippet_max_charsnow follow the per-level cap ladder (6000/4000/4000, 200/150/120) — a set value pins a fixed cap;strip_frontmatter: auto|always|neveris fully honored (#241)
- Breaking: pinned
ONEBRAIN_TOKENmust be ≥32 chars in[A-Za-z0-9_-]; violations abort serve/daemon startup (was warn-and-generate) — closes a Windows command-injection path (#218) serve --opennow works on Windows via quotedcmd /C start(#218)- Collection cache split into
models/+index/with eager auto-migration (never re-downloads models);search statusreports split sizes + layout state (#225) - Fresh
onebrain.ymlemitssearch.exclude(attachments + archive);doctor --fixbackfills existing vaults (#220) session init --jsongainsrecap_pending(unrecapped session-log count) (#219)- doctor detects legacy qmd installs with guided cleanup — destructive actions require interactive confirmation, never run headless (#221)
- Compile-time guard for
SYSTEM_SECTION/SECTIONSdrift (#213)
- Breaking:
serve --hostremoved — localhost-only; containers useONEBRAIN_BIND(#205) - Self-documenting
onebrain.ymltemplate + doctor value validation/reset (#196) - Section-banner layout +
doctor --fixrestructures existing vaults (#203) - All config writers comment-preserving via shared
yaml_edit(#200) - Doctor output redesign: boxed Summary, no inline hints, daemon-routed search check (#200)
daemon statusfull dashboard + daemon-awareserve --open(#197)search model list/statusdisplay parity + Ready row (#195)
- Added Tier-2 cross-encoder reranker (
onebrain-rerank-v1, self-hosted bge-reranker-v2-m3 int8), default-on, replacing the ADR 0024 cosine gate with a calibrated 0–1 score. (ADR 0025) (#190, #191) - Added
search.rerankerconfig (enabled,model,min_candidatesdefault 10,min_scoredefault 0.30); model downloads + sha256-verifies duringreindex. top_k/min_candidatesare now settable on every surface (CLI flags, config,/api/vault/searchquery params).--min-scorenow filters the calibratedrerank_scorewhen reranking is active (legacy raw-score meaning when off).- MCP
querytool now reranks and surfacesrerank_scorelike every other surface. - Fixed: reindex previously couldn't download the reranker model (wrong accessor path), leaving it inert for every user.
- Added: warm daemon (
daemon __run) owns the native-search engine as sole redb owner; token-gated internal reindex/status/get endpoints + daemon discovery + idle-shutdown TTL. (ADR 0023 · docs/daemon.md) (#164) - Added:
onebrain mcpand CLI search now route through the daemon so multiple concurrent sessions coexist; passive per-vault discovery never disrupts another vault's session. (#168, #169) - Fixed: search-engine lock contention now surfaces honestly (
E_ENGINE_BUSYexit 77 for user verbs, silent skip for hooks) instead of misreporting. - Fixed:
search search(lex) now populatesheading_pathfrom the stored tantivy field in a single pass. - Fixed: auto-started daemon receives its vault via an explicit argument (no env-var mutation); reindex-path confinement now also runs in the engine, not just the HTTP layer. (#175)
- Fixed: honest
E_ENGINE_BUSY/503 errors during the pre-daemon-to-daemon upgrade transition window instead of opaqueE_INTERNAL/503 strings. (#179) - Fixed: semantic search no longer silently returns nothing — per-model
vec_floorcutoff replaced with a recall-firstkeep_top_clustercutoff + confidence label. (ADR 0024) (#183)
[3.4.5] — 2026-07-05 — native search · no dependency · auto reindex/embed · model reindex ux/ui (the qmd epic)
- Breaking: removed the
onebrain qmd …command group and the external@tobilu/qmddependency — nativeonebrain-searchnow powers webui search + the reindex hook; useonebrain search …instead (hooks/schedules auto-rewritten on nextplugin update/schedule register; the reindex hook now runs synchronously, tracked in #133). - Breaking: native-search state (model + index +
engine.redb) now lives in the OS data dir instead of the purgeable cache dir, after macOS cleanup wiped a ~536 MB index (#114); existing state auto-migrates on next command. (ADR 0021) - Fixed:
doctornow flags a missing index on a configured collection as a possible OS-purge instead of "no index yet";search status/MCPquerydegrade honestly with no index. - Added: auto reindex/embed hook —
search reindex --lex-onlyon PostToolUse,--pending-onlyon Stop; both auto-migrate from the old qmd hook entries. (#133) - Added (transition):
session init --jsonemits the canonicalsearch_unembeddedkey alongside the deprecatedqmd_unembeddedalias. - Internal: renamed
HookSpec::QMD→REINDEX, removed the dead.obsidian/seeding path. Closes #142. - Fixed:
plugin updateon a vault with noupdate_channelno longer 404s — absent/unknown channel now defaults tomaininstead of the nonexistentnextbranch.
- Fixed: scheduled cron skills no longer exit 78 (EX_CONFIG) —
skill runnow prepends its own binary dir to the headlessclaudechild's PATH. (#124) - Fixed: generated plists use the current
skill runsubcommand instead of the deprecatedrun-skillalias, so scheduled runs stop logging a deprecation notice. (#125)
- Scheduler cron now accepts step (
*/N), list (a,b,c), and range (a-b) syntax per field, emitted as launchdStartCalendarIntervalarrays. (#116) - Scheduler command-mode plists are now disambiguated by their args so two entries for the same binary no longer collide;
schedule registerauto-migrates stale pre-#116 plists. (#116) - Scheduler cron
weekdaynow accepts the standard0-7range (both mean Sunday), normalizing7→0. - Scheduler cron now rejects strings that restrict both day-of-month AND day-of-week (cron ORs them, launchd ANDs them) — use two
schedule:entries instead. - Scheduler cron combination cap raised from 366 to 1000, accepting the "every day of every month" idiom while still rejecting
*/1 */1 * * *. onebrain schedule listis now implemented (was a stub), reusing the existing status view. (#116)- CI now runs the lex-only (
--no-default-features) test suite alongside clippy. (#119) - Polish (#120):
SearchMcpServerrenamed toMcpServer;gettool documents line clamping;QueryParamsdead-code allowance tightened.
- Security fix:
serve/daemon auth token now comes from the OS CSPRNG (getrandom) on every platform instead of a time-seeded fallback that made every Windows token (and any failed-read Unix run) guessable; no fallback remains, an unavailable OS RNG now panics rather than emit a predictable token. getrandompromoted from a transitive to a direct dependency (already in the graph — no new crate).query's camelCase wire test now covers all threelex/vec/hydesub-query variants (would have caught arename_alltypo before it shipped).search statusnow opens the engine at the already-resolved cache dir instead of re-resolving the vault + collection.- Test fixtures write the canonical
onebrain.ymlinstead of the legacyvault.yml, avoiding a spurious deprecation warning.
- Added
onebrain mcp— MCP stdio server (rmcp) over the native engine:query(lex/vec/hyde, RRF-fused),get,multi_get,status. session initnow probes the native index forqmd_unembeddeddirectly (no qmd subprocess), same JSON contract.- Model picker: pressing Enter on an active model with missing files (e.g. OS-purged cache) now re-downloads without re-embedding.
search statusreports the active model's on-disk size only (was summing everymodels--*dir).dot_scalargains a debug-build equal-length assertion; simsimd fallback logs before returningNEG_INFINITY.- ADR 0018 polish: sysroot typo fixed, win-arm64 decision restructured into sub-bullets.
- Added native Rust search engine: tantivy BM25 + fastembed embeddings + flat mmap vector store + RRF hybrid ranking — no Node/Python runtime.
- Added
onebrain search query/search/vsearch/get/status/reindex(--json) plussearch model list/setand an interactive TTY model picker. - Multilingual: ~100-language semantic search (default
multilingual-e5-small, swappable) + no-space-script keyword bigrams for Thai/CJK/Lao/Khmer/Myanmar. - Swappable embedding model via
search model set(rebuilds vector store, re-embeds);bge-m3is the best-accuracy upgrade path. - Platform-tiered semantic search (rustls): targets with no ONNX Runtime prebuilt ship a lex-only binary, gated by the
semanticcargo feature. (ADR 0017) - Runs alongside qmd (engine milestone only) — MCP swap and qmd removal land in follow-up milestones.
- Release cross-toolchains fixed so all 9 targets build (aarch64-linux-gnu g++, arm64 Windows MSVC toolset), plus a main-branch review sweep (webview redirect off-by-one, translate error logging, gzip robustness/hardening).
- Added
POST /api/translate— server-side bridge to Google's free gtx endpoint, powering the WebUI select-to-lookup Translate action (5,000-char cap, 8s timeout, fixed host). - Fixed: webview preflight now resolves scheme-relative and absolute-path redirect
Locations (RFC 3986) — th.wikipedia'sSpecial:Searchredirect was wrongly reported unframeable.
- Release workflow now downloads the prebuilt webui dist (from onebrain-webui's own GH Release tarball, sha256-verified) instead of rebuilding it — releases are minutes faster and reproducible.
- Fail-closed: missing/malformed pin metadata, missing asset, or hash mismatch aborts the release loudly.
- Added
GET /api/webview/preflight?url=— inspectsX-Frame-Options/CSPframe-ancestorsso the web UI can decide iframe-embed vs new-tab. - Fail-safe: any probe failure (bad scheme, network error, timeout) degrades to
frameable:false, never an HTTP error.
- Added
GET /robots.txtserved without a token (private-instanceDisallow: /) — the one exemption to the whole-surface token gate; fixes Lighthouse SEO 91 → 100. - Verb-restricted to GET/HEAD only so the exemption never widens the CSRF surface.
- Precompressed web UI assets (gzip at build time);
servedetects the gzip magic and serves withContent-Encoding: gzip— release binary ~16.2 MB → ~9.3 MB (−43%). - Zero new dependencies — pure-Rust
flate2fallback only for clients withoutAccept-Encoding: gzip. - No effect on non-
servecommands or non-assets/files; detection is by gzip magic bytes.
onebrain servenow reports the bundled web UI version + release date fromversion.json/changelog.json.- Prettier startup banner — framed, emoji-prefixed layout mirroring the session-greeting look.
server::{webui_version, webui_released}+ pureparse_*helpers added, unit-tested; dist'sversion.json/changelog.jsonserved as static assets too.- No behavior change to routing/auth — startup output only.
- test(cli): +9 assert_cmd tests cover
dispatch()process::exitarms —v31/dispatch.rs91.08% → 95.64%. - Core line coverage 95.03% → 95.21%.
- Residual
dispatch()arms (real network/subprocess/TTY paths) documented indocs/coverage.md. - No behavior change — tests + docs only.
- test(server): +28 oneshot/unit tests cover the JSON API handlers —
server/api.rs69.56% → 87.06%. - test(cli/fs): +47 tests close residual command-layer branches —
dispatch.rs88.69%→91.08%,onebrain-fs/update89.62%→92.62%,register_schedule.rs91.30%→93.09%,doctor.rs→94.21%. - Core line coverage 94.28% → 95.03%.
- Documented the realistic coverage ceiling in
docs/coverage.md(100% unreachable on stable; genuinely-unreachable lines listed as residuals). - No behavior change — tests + docs only.
- test(fs): +94 tests close coverage gaps across the onebrain-fs cluster (
note/archive.rs,init/mod.rs,vault_sync/pin.rs,register_hooks/*,doctor/vault_yml_keys.rs,v31/hook_rewriter.rs, and more). - Tests target real error/edge paths with meaningful assertions; permission-denial tests are
#[cfg(unix)]-gated. - Core line coverage 93.62% → 94.28%; residuals tracked in
docs/coverage.md. - No behavior change — tests only.
- test(cli): closes coverage gaps in the command-module layer —
doctor.rs87.55%→94.20%,register_schedule.rs72.08%→91.30%,vault_ctx.rs51.35%→100%,run_skill.rs+110 tests. - Core line coverage 92.59% → 93.62%; residuals documented in
docs/coverage.md. - Test isolation hardening: plugin-cache/qmd-embeddings fix-path tests now run via subprocess with a tempdir
$HOME/PATH. - No behavior change — tests only.
- Fixed:
onebrain updateno longer hangs on Homebrew — Homebrew 4.4+'s "proceed? [y/n]" prompt was corrupted by the install spinner redrawing the TTY;HOMEBREW_NO_ASK=1fixes it. - style(cli): tighter
--helplayout — category headings flush left, commands indent 2 spaces.
- test(cli): adds
scripts/coverage.sh+docs/coverage.md(excluded-files list + rationale + baselines); targets 100% line coverage on testable core code. - test(cli): covers
v31/dispatch.rsstub + verb arms — 76.94% → 86.70% line. - Measured baselines: whole-workspace 89.58% line; core (exclusions applied) 92.59% line. No behavior change.
- feat(cli): groups root
--helpcommands into 4 named category sections (⚙️ System Management, 🧠 Vault Management, 🔄 Session Management, 🚀 Launch Management). - Category headings show emoji on a terminal, render plain when piped, so
onebrain --help | catstays clean. - Descriptions pulled live from clap
aboutannotations — can't drift from source of truth. - Subcommand help (
onebrain note --help, etc.) is unchanged. - Drift-guard test: CI fails if any visible root subcommand is missing from CATEGORIES or a category entry is stale.
- Options section keeps its compact format, unaffected by the categorized block injection.
- Fixed
is_root_help_requestto not intercept--version/-V.
- feat(cli): surfaces the
noteandtaskcommand groups inonebrain --help— all 14noteverbs +task listwere implemented but hidden. - Stub verbs
task add/task donestay hidden until implemented; all-stub groups and v3.0 legacy aliases remain hidden. - Added tests asserting
note/taskvisibility and stub-group hiding.
- fix(fs):
scan_tasksnow skips checkbox lines inside fenced code blocks — demo/fixture tasks no longer pollute task scans (also fixes/api/vault/tasks). - feat(cli): implements
onebrain task list— fence-aware dated-task listing with--due-by, repeatable--folder,--all.
- docs(serve):
--dirhelp text updated from stale "API-only" wording to "serve the embedded UI" (matching the v3.3.10 embed).
- fix(serve): startup banner now correctly reports
dist: (embedded web UI)for a no---dirrun. - fix(serve): OWASP A03 —
GET /api/vault/file//rawnow refuse vault tooling dirs (.git/.obsidian/.claude/.trash/node_modules), matching the write paths. - fix(serve): OWASP A03 — the
claudechat subprocess argv ends options with--so a message starting with-/--can't be smuggled as a flag.
- feat(serve): new
GET /api/vault/search?q=&mode=lex|hybridshells out to theqmdindex for the web UI's search panel. - fix(serve): the endpoint returns 503 when
qmd_collection/qmdbinary is missing, falling back to filename/path search.
- fix(serve): security headers relaxed to
SAMEORIGIN/frame-ancestors 'self'so the web UI can frame its own/api/vault/rawto preview PDFs. - fix(serve): CSP
img-srcnow allowsblob:so pptx-preview embedded media can load. - feat(serve):
/api/vault/rawsends audio/video content-types and honorsRangerequests for native<audio>/<video>streaming. - fix(serve): hardened
/api/vault/rawagainst stored XSS now that same-origin framing is allowed — script-carrying types served asapplication/octet-stream+ attachment disposition. - fix(serve): OWASP hardening — pinned
ONEBRAIN_TOKENnow requires ≥32 chars; theclaudesubprocess no longer inherits it.
- fix(serve):
GET /api/vault/raw?download=1now sends the file's real name via RFC 5987filename*, preserving spaces/non-ASCII names on download.
- fix(serve): CSP now allows
data:fonts so the Office-document preview can render embedded slide/text fonts.
- feat(serve): the whole router is now token-gated (every route/method) via header, bearer, query param (GET/HEAD only), or cookie.
- feat(serve): a security-headers middleware sets CSP,
X-Frame-Options,X-Content-Type-Options,Referrer-Policy, COOP, and HSTS on https. - feat(serve):
resolve_tokenhonors$ONEBRAIN_TOKEN(≥16 chars) so the token can stay stable across restarts. - fix(serve): chat request bodies are capped;
servewarns when binding a non-loopback address over plain HTTP.
- fix(tasks):
GET /api/vault/tasksnow scans only the configured project + area folders instead of the whole vault.
- fix(doctor): the qmd-embeddings check now reports "qmd status unavailable" on incomplete/corrupted probe output instead of inventing "0 unembedded".
- fix(qmd): session-init's unembedded count and
qmd statusno longer report a false0whenqmd statusis slow — shared probe timeout bumped 2s → 15s. - perf(session-init): startup probe keeps a tighter 5s cap so a hung qmd can't freeze the greeting; degrades to
nullon timeout. - feat(session-init):
qmd_unembeddedis nownull(not0) when the probe can't determine the count, distinguishing unknown from a genuine zero. - fix(qmd): robust
qmdresolution — probe now looks in the bun-global dir so a restricted-PATH launcher (hook/launchd/Obsidian terminal) can find it. - refactor(qmd): unified the duplicated qmd-status probes into one shared
onebrain-cache::qmdsource of truth. serve/daemondefault port changed from4317to6789(collided with OpenTelemetry OTLP); override with--portas before.- chore(license): relicensed from
AGPL-3.0-onlytoMIT OR Apache-2.0.
- feat(note):
onebrain note edit <path> <content>— verbatim overwrite/create via sharedwrite_noteprimitive. - feat(note):
onebrain note delete <path>— move a note to.trash. - feat(note):
onebrain note mkdir <path>— create a folder. - These are the CLI counterparts to the v3.3.1 daemon write endpoints — both surfaces now share one implementation.
- feat(daemon): note write surface —
POST/PUT/DELETE /api/vault/file,POST /api/vault/move(rewrites incoming wikilinks),POST+DELETE /api/vault/folder. - feat(daemon):
GET /api/vault/raw(image/PDF preview) andPOST /api/vault/upload(binary attachments), behind a body-size limit. - feat(daemon):
GET /api/vault/tasks— vault-wide dated Obsidian-Tasks scan. - feat(daemon):
POST /api/chat— SSE stream over aclaude -pagent turn (concurrency-capped, process-group kill on disconnect). - feat(auth): per-session token accepted via
?token=query on GET/HEAD only; writes stay header-only. - refactor(core): handlers are thin veneers over shared
onebrain_fsprimitives — CLI and daemon share one implementation per vault operation.
- feat(daemon):
onebrain daemon start|stop|status— self-respawning detached process tracked bydaemon.pid. - feat(serve):
onebrain serve [--dir] [--port] [--host] [--open]brings up one local HTTP surface (static SPA + read-only vault JSON API); per-session token gates/api/*. - deps: net-new compiled crates are
axum 0.8+tower+tower-http,tracing,nix.
- fix(cache-clean): orphan cache dirs under an unregistered marketplace are now swept even when a registered marketplace exists.
- fix(cache-clean):
remove_dir_allfailures are now surfaced (counted + stderr warning) instead of silently dropped. - Verified the Step 9 sweep runs unconditionally on every real Claude update.
[3.2.20] — 2026-05-29 — completions: exclude hidden commands
- fix(cli): shell completions no longer list hidden/internal/legacy subcommands — generated from a recursively hidden-filtered command tree.
- feat(cli):
onebrain completions <SHELL>— hidden subcommand emitting a shell completion script (bash/zsh/fish/powershell/elvish). - feat(cli): optional shell-aware hint after interactive
onebrain init; enables Homebrew formula completion auto-install.
- perf/size:
reqwest→ureq(blocking sync HTTP) removes the entire async stack from the release binary — −342 KB, −54 crates, ~12% faster clean build. - Internal: removed the dead
tokio_helperruntime shim (zero callers); the daemon (v3.3) re-introducestokiodeliberately. - Dep:
serde_yaml(archived upstream) →serde_yaml_ng, an actively-maintained drop-in. - Internal: dropped the unused
clapenvfeature. - Internal: unified the two
plugin updatetext renderers into one, removing a trait that existed only for test doubles (PR #57). - Internal: renamed
vault_sync::run_silent+register_schedule::run_quiet→ bothrun_embeddedfor naming consistency (PR #57).
[3.2.17] — 2026-05-29 — onebrain update: refresh Homebrew tap before upgrade + dedicated npm channel
- Fix:
onebrain updateon a Homebrew install now refreshes theonebrain-ai/onebraintap beforebrew upgrade, so a fresh formula is visible immediately after a release. - Feat:
onebrain updatenow has a dedicated npm channel — an npm-installed binary updates vianpm install -g @onebrain-ai/cli@<version>instead of the Direct swap path.
- Fix: stale plugin-cache orphans no longer silently shadow the vault-local plugin —
doctornow detects orphans created outside an update. - Feat: new
doctorplugin-cachecheck reports stale cached plugin versions;--fixprunes them. - Feat:
plugin updateprints a post-update reload next-step (↻ /reload-plugins …) whenever a real version change lands.
[3.2.15] — 2026-05-28 — --help compact-with-wrap · plugin update polish · per-command emoji · version tracking · --json minified
- Breaking:
--helpreverts to compact layout (command + description on one line) —next_line_helpno longer forces every arg into long format; args with[default]+[possible values]still wrap the value block to an indented line. - Polish: per-command framed-header emoji differentiated —
doctor→ 🔬,update→ 🚀,plugin update→ 🔄 (was all 🧠, competing with the brand glyph). - Polish:
plugin updateno longer leaks the orchestrator's per-step▸ <label>lines above its framed report — routes throughvault_sync::run_silent. - Feat:
plugin updatenow reports current + latest version explicitly (vX → vY/vX · up-to-date/installed vY); JSON envelope gainsversion_before/version_after. - Breaking:
--json(and--output json) now emits minified single-line JSON by default — pass--json --prettyfor indented output. - Breaking:
--output tableand--output tsvremoved (both silently fell through to the JSON encoder unchanged); remaining set istext/json/yaml. - Polish:
skill run --help/harness run --helpreverted to the compact one-line style — Options section stays compact,[default]+[possible values]still wrap onto an indented line. - Polish: positional
<NAME>args onskill info/show/bootstrap(and hiddenbundleverbs) now carry a description in the Arguments section.
- Polish:
plugin updatenow animates its three step rows with the same braille spinner + random 800–2000ms pacing thatdoctor/updateuse. - Internal: new
render_plugin_update_animated/_topair with an injectableWrite+ step-delay override for deterministic spinner tests.
- Polish:
plugin updatenow renders a framed doctor-style report instead of a key:value summary. - Polish: removed "OneBrain Vault Sync" intro/outro frame leakage via a new
vault_sync::run_embeddedhelper. - Polish: silenced
register_schedule's per-plist✓ Wrote …chatter when invoked fromplugin update. - Polish: non-TTY (CI/scheduler/piped) sub-output is now silenced too via
PlainProgress::with_embedded. - Fix: partial-failure path no longer paints the failing step with
✓— now renders✗ … failedmatching the footer glyph.
- Polish: every
--helpscreen now uses the long format (description below the option name,[default]/[possible values]on their own lines) vianext_line_help = true. - Restored:
HarnessMode::WithContext/AdHocvariant docs (stripped in v3.2.11 to keep help compact — no longer needed with the long format).
[3.2.11] — 2026-05-28 — help cleanup: --help only · skill help → skill show · harness run --help compact · banner consistency
- Breaking:
<group> helpsubcommand removed across the tree — useonebrain <group> --helpeverywhere. - Rename:
skill help <NAME>→skill show <NAME>(distinguishes SKILL.md body from clap--help); same rename for the hiddenbundle help→bundle show. - Fix: bare
onebrain harnessnow emits the brand banner before showing help (missedarg_required_else_helpgroup hops). - Fix:
onebrain skill show <NAME>no longer prints the banner twice. - Polish:
harness run --helprewritten to the compact one-line style used byskill run --help. - Polish: no more banner above "unrecognized subcommand 'help'" errors;
MissingSubcommandwired into the banner-gate interception path.
- Feat:
onebrain skill info <NAME>— prints a skill's frontmatter (name/description/schedulable/required_args); JSON/YAML supported. - Feat:
onebrain skill help <NAME>— prints the SKILL.md body; text dumps markdown verbatim,--jsonwraps as{name, body}. - Feat:
--jsononskill run/harness runnow passes through to the harness (--output-format json) so captured stdout is the harness's native structured response. - Polish: bare
onebrain harnessnow prints help instead of silently runningdetect. - Polish:
harness run/skill rundescriptions rewritten to surface--harness/--model/--modeinline at the group-help level.
- Fix:
--mode ad-hocnow actually skips vault context — forcescwd = $TMPDIRsoclaude/geminican't auto-walk-up and silently reload OneBrain'sCLAUDE.md. - Polish:
harness run's watched spinner now says "on the prompt" instead of "on the skill" (copy-paste leak fromskill run).
- Feat:
onebrain harness run [PROMPT]— send an ad-hoc prompt to the chosen agent harness (--harness {claude,gemini},--model); reads stdin if[PROMPT]is omitted. - Two modes via
--mode {with-context,ad-hoc}: with-context loads the vault's CLAUDE.md/INSTRUCTIONS.md (vault required); ad-hoc skips the vault flag entirely (cwd = $PWD). - Internal: refactored the shared spawn path (
harness_argv) so bothskill runandharness runreusespawn_harness, the in-place spinner, and output capture.
- UX:
skill runshows an in-placeindicatifspinner on a watched run, replacing the per-10s heartbeat that flooded scrollback during long runs. - Internal: pipes the harness's stdout/stderr into in-process buffers via two reader threads while
child.wait()blocks, so await()error can still kill the harness instead of leaking an orphan.
- Feat:
skill run --harness {claude,gemini}(defaultclaude) — run a OneBrain skill through either agent runtime; gemini uses--approval-mode yoloto matchclaude -p's trust model. - Feat:
skill run --model <m>— passed through to the harness; the biggest raw-speed lever for headless runs. - Perf: headless runs skip the interactive startup ceremony —
skill runsetsONEBRAIN_HEADLESS=1,session initreportsheadless: true. - Internal: generalized claude-only binary resolution to
resolve_claude_bin/resolve_gemini_binover a sharedresolve_bin.
- Fix: the auto-checkpoint safety net never fired — two compounding root causes left
07-logs/checkpoint/empty across every session. - Root cause 1: session token churned (terminal env vars unset in Obsidian/Desktop) so the message counter never accumulated across restarts.
- Fix:
CLAUDE_CODE_SESSION_IDis now the top-priority token source — stable across PID churn and distinct sessions sharing one terminal. - Root cause 2: the 30-minute time threshold was dead for a session's first checkpoint (
last_tsstayed 0). - Fix: anchor
last_tson the first stop so the minutes threshold starts ticking immediately.
doctor --fixis now one pass with a confirmation step: report shown once, planned fixes previewed, then a[y/N]prompt confirms before anything changes.- Feat:
doctor --fixcreates missing vault folders via a newfoldersrecipe, named fromonebrain.yml. - Fix:
doctorqmd check timeout raised 3s → 15s — a real index could take ~10s forqmd status, causing spurious timeouts. - Polish:
doctorframe rules now span the longest line instead of stopping short. - Feat:
skill runshows progress on an interactive TTY (start line + elapsed heartbeat) whileclaude -pruns. - Feat:
skill runaccepts--skill <name>as an alias for the positional name. - Polish:
--vaultis the single documented vault flag;--vault-dirbecomes a hidden back-compat alias everywhere. - Chore: removed the dead
.ci-triggerscaffold file.
- Fix:
onebrain skill runnow resolves the vault through the canonical chain (--vault→ONEBRAIN_VAULT→ walk-up from cwd) instead of demanding an explicit path. - Hardening:
skill rungives the spawnedclaude -pa null stdin so it can't block reading an inherited interactive TTY. - Fix: global
--vaultaccepted on every command —skill run/schedule register/plugin migraterenamed their local field tovault_dirto stop colliding with the global arg id. - Feat:
onebrain doctorstampsstats.last_doctor_run/last_doctor_fixinonebrain.ymlon every run.
- Feat:
onebrain updategets an animated TTY — framed header + braille spinner on thefetch/installphases (matchingdoctor). - Polish: banner vertical gradient — a top-lit shade layered on the horizontal cyan→purple→pink hue; non-truecolor fallback is now a vertical-only gray ramp.
- Polish:
doctorspinner now visibly rotates and paces 800–2000ms per check; summary-footer rule widened to span the verdict line. - Internal: the framed header, braille spinner frames, and pacing band extracted into
output::sodoctor/updateshare one look.
- Feat:
onebrain doctorredesign — 9 checks grouped into 4 sections under a🧠 OneBrain Doctor · <vault>header, via a new reusable braille-spinner progress primitive. - Fix:
doctorqmd-hook false "missing" — the detector now recognizes both the canonicalqmd reindexform and the legacyqmd-reindexalias;--fixmigrates + dedups. - Feat: banner wordmark gradient — continuous horizontal cyan→purple→pink gradient across
ONEBRAINin truecolor. - Polish:
onebrain update's post-update hint now names the directonebrain plugin updatepath alongside/update.
- Feat:
onebrain note <verb>— 11 native vault note operations (search/list/find/read/stat/backlinks/orphans/append/new/archive/move) replacing ad-hocgrep/ls/find/cat. - All verbs emit the canonical
Envelope<T>(text/json/yaml), vault-required, backed by 100+ fs-layer + CLI unit tests plus a 22-case fixture-vault integration suite.
- Fix:
onebrain updateno longer reports "Binary validation failed" after a successful upgrade — the post-install validator expected Bun'sv-prefixed version shape, not the Rust/claponebrain 3.1.4output. - Hardening: the post-install gate now confirms the PATH-resolved
onebrainactually reports the just-installed version (>= expected), surfacing the specific failure cause.
- Feat:
onebrain updateverifies the downloaded binary's SHA-256 against the published<archive>.sha256before the swap — an unverifiable asset is now a hard failure. - Feat: Homebrew-aware
onebrain update— a brew-managed install now delegates tobrew upgrade onebraininstead of swapping the Cellar binary in place.
- Fix:
onebrain schedule registernow dual-reads the config (canonicalonebrain.ymlpreferred, legacyvault.ymlfallback) — it hardcodedvault.ymland silently found zero schedule entries on a v3.1 vault.
- Feat:
onebrain qmd embedimplemented (was a stub) — runsqmd embedin the foreground with inherited stdio, surfacing a non-zero exit as an error.
- Fix (data loss):
onebrain init --forceno longer clobbers an existing config — re-init now preservesonebrain.ymlverbatim; missing keys are repaired bydoctor --fixinstead. - Feat: timestamped config backups — every config-overwriting operation first copies to
.onebrain-backups/<file>.<timestamp>.bak, refusing the write if the backup fails. - Fix: doctor check labels renamed
vault.yml→onebrain.yml/onebrain.yml-keysto match the canonical filename. - Fix: stale
vault.ymlreferences in user-facing output (help text, error messages) updated toonebrain.yml. - Feat:
onebrain qmd status— reports index + embedding health (collection/indexed/embedded/pending/size/updated) in text/json/yaml. - Fix:
session initunembedded count now works and is vault-aware — parses the text form instead of--json(which qmd ignores). - Feat: animated
doctoron an interactive TTY — checks reveal one at a time with a short per-step delay.
- Feat: R1 branded banner — 5-line FIGlet "Slant"
OneBrainwordmark + tagline on interactive sessions and every--helpscreen, gated on a 6-rule TTY chain. - Feat: locked 27-entry command tree — 3 root verbs + 24 resource groups, singular-noun 2-level
onebrain <noun> <verb>; other 200+ verbs stubbed withE_NOT_IMPLEMENTED(exit 72). - Feat:
--vaultglobal flag + walk-up resolver +ONEBRAIN_VAULTenv, documented priority order, surfaced by newonebrain vault current. - Feat:
plugin updatesemantic swap — now self-updates the CLI binary (wasonebrain update); the legacy plugin-overlay behavior moves underplugin update's vault-side step. - Fix:
onebrain initnow registers the plugin with Claude Code AND prompts before initializing in a non-empty directory. - Feat: canonical
Envelope<T>JSON shape + partial-failure contract (E_PLUGIN_UPDATE_PARTIAL);BrokenPipeon stdout now exits 0 instead of panicking. - Feat: output-format compliance — interactive commands default to text and honor
--json/--json --pretty/--yaml/--outputconsistently via one canonical dispatcher. - Breaking: config file renamed
vault.yml→onebrain.yml— CLI v3.1+ dual-reads for back-compat (one-time deprecation warning on legacy);doctor --fixmigrates via atomic rename; v4.0.0 dropsvault.ymlsupport entirely.
These shipped under the v3.0.x patch line after the 2026-05-22 GA and are not part of v3.1.0 itself.
- npm wrapper source recovered and landed in-repo at
npm-wrapper/after the original tarball-only source was lost;engines.noderaised to>=20. - CI auto-publishes the npm wrapper on each stable tag via npm Trusted Publishers (OIDC +
--provenance, no long-lived token). - postinstall verifies SHA256 against the published
.sha256before extracting, closing the gap between attested publish and binary integrity. - bin shim re-raises signal terminations (
128 + signum) so Ctrl-C/SIGTERM is distinguishable from a real error in CI. - README + CONTRIBUTING signpost the new
npm-wrapper/layout; install table promotes npm + Homebrew out of "planned" (both live since v3.0.0 GA). - postinstall hardening: retry-with-backoff on HTTP 404, Alpine/musl detection, Windows tar fallback to PowerShell
Expand-Archive, post-install smoke test, escape-hatch env overrides. (PR #29) - Raspberry Pi + 32-bit ARM Linux support — release matrix adds
armv7/arm-unknown-linux-gnueabihf; every Pi from 1 to 5 now has a published binary.
- Complete Rust rewrite of OneBrain CLI replacing v2.x TypeScript/Bun — 4-crate workspace, ~10× less memory, 92% smaller binary, startup within 10ms of Bun on warm cache.
- 7-platform release pipeline (macOS Apple Silicon + Intel, Linux ARM64 + x86_64 glibc/musl, Windows ARM64 + x86_64),
cargo-binstall-ready. onebrain updatefetches binaries directly from GitHub Releases over HTTPS and atomically swaps the running binary — no npm/bun shell-out anywhere.- Stable JSON output contracts for v3.x (
doctor --json,update --check --json,update --plan) — frozen schemas, stability covers v3.x, v4 may break. - Trust model: downloaded binaries authenticated solely by GitHub's TLS chain — no SHA-256/cosign verification at GA (matches rustup/deno/bun baseline).
- Skill + scheduler ecosystem wired end-to-end —
register-hooks/register-schedule/run-skillround-trip with the plugin's hooks; plist generation verified byte-identical to Bun v2.3.3. doctorships 8 read-only checks and 5--fixrecipes; remaining recipes + Windows zip extraction deferred to v3.0.1.- Distribution at GA: GitHub Releases +
onebrain updateis the primary path; npm-wrapper and Homebrew tap are planned for the v3.0.x window, not published at GA.
[3.0.0-alpha.9] — 2026-05-20 — GA candidate: fix onebrain update install path · TTY spinner · direct harness · real --test · Windows pin
onebrain updateinstall path rewritten to fetch directly from GitHub Releases (alpha.1–alpha.8 shelled out tobun/npm install -g, which never had the Rust binary published — every real update failed). Downloads over HTTPS (rustls TLS, no checksum verification yet — trust model matches rustup/deno/bun), atomically swaps via tmp + rename. Windows zip extraction intentionally stubbed for v3.0.0.- TTY spinner + colorized output for
onebrain update; non-TTY output stays plain-text byte-for-byte;--jsonsuppresses all log output. directharness lands inregister-hooksas a first-class no-op — vaults without.claude/print "direct mode · no hooks to register" instead of a gemini-only error message.register-schedule --test <skill>is now a real implementation — builds the same argv launchd would emit, spawns it synchronously, and propagates the exit code.update --planJSON now includesbinary_targets[]enumerating the six published(triple, ext)pairs.- New
UpdateError::Install(String)variant replacesUpdateError::Networkfor filesystem/OS errors during install, so failures no longer misleadingly blame the network. --vault-dirflag pattern audited across all subcommands (Reviewer C-I4) — user-visible flag name is consistent everywhere; no code change.- Defense-in-depth:
extract_tar_gznow guards onentry_type().is_file()so a malicious tar can't promote a symlink/dir to "the binary"; deleted the dead bun/npm install-command code path.
- feat:
doctor --jsonemits a single JSON document ({ok, summary, checks[]}); combines with--fixfor a post-fixfix[]array; schema stable for v3.x. doctor --jsonoutside a vault now emits a JSON failure envelope on stdout with exit code 1, instead of an anyhow plain-text error.update --check --jsonemits{ok, current, latest, update_available, released_at?};update_availableisnull(not a guessed false) when the remote fetch failed.update --planis--check --jsonplusrelease_url/binary_url_template, designed for the/updateplugin skill; implies dry-run.vault-sync --vault-dir <path>flag-form alternative to the positional argument.register-scheduleresolvesfolders.logsfromvault.ymlinstead of hardcoding07-logs/scheduler/..., with path-traversal guards.- 3-round review consensus fix-pass:
version_at_leastpromoted topub;progress_writeroption added toVaultSyncOptions; +5 unit tests.
[3.0.0-alpha.7] — 2026-05-20 — feat(doctor): four new --fix recipes (settings-hooks · plugin-files · vault.yml-keys · claude-settings)
doctor --fixnow repairs four more check types:settings-hooks,plugin-files,vault.yml-keys(backfills keys, strips deprecated ones, repairs non-positive checkpoint values),claude-settings.- Dispatch widened to Warn AND Error so previously-bypassed failure modes are now repaired by
--fix. - Atomic writes everywhere —
vault.yml/settings.jsonmutations go through.tmp + rename. fix_plugin_filesnow respects the samerefuse_dangerous_vault_pathguard asonebrain vault-sync.orphan-checkpointsroutes to Manual with a clearer hint pointing at/wrapup— auto-deletion intentionally off the table.- Five recipes total ship with the auto-fix flow; the
vault.yml-keysmessage notes YAML comments aren't preserved yet.
[3.0.0-alpha.6] — 2026-05-20 — fix(update): target CLI repo + prerelease-safe · ci: GHA Node 24 · docs: README hero + badges
onebrain updatenow targets the CLI repo (onebrain-ai/onebrain-cli) instead of the plugin repo, fixing a bug where the non---checkform could downgrade users to the plugin repo's last Bun release.- Semver-aware version comparison via the
semvercrate replaces the string-equality check, preventing silent downgrades. - GitHub Actions Node 24 bump across
ci.yml/release.yml, clearing deprecation warnings ahead of the forced cutover. - README hero/banner + CLI-only badges aligned with the plugin repo's presentation; license badge updated to AGPL-3.0.
doctor --fixnow actually attempts repair instead of a stub — first recipe isqmd-embeddings, re-running all checks after the fix pass.- Removed
(Slice N)internal porting markers from every subcommand description shown in--help. - New
FixOutcome { Fixed, Failed, Manual }enum + summary block so the user can quickly read what changed.
update --checkwarm-path 480ms → 10ms (~48× faster) via an on-disk JSON cache with a 1-hour TTL;--freshbypasses it.doctorwall time ~980ms → ~890ms by running theqmd-embeddingsprobe on a background thread while the other 7 checks run serially.qmd-embeddingsprobe jitter eliminated by replacing a 100ms poll loop withwait-timeout's blockingwait_timeout.onebrain updateno longer spawns a subprocess for the current version, usingenv!("CARGO_PKG_VERSION")instead.- New unit/integration tests cover the cache hit/miss/staleness paths and the in-process version constant.
[3.0.0-alpha.3] — 2026-05-20 — fix(parity): close all 6 Bun-CLI argv gaps + init becomes one-step + safety + friendlier release notes
initnow runsvault-syncautomatically, collapsing the previous 2-step bootstrap into one;--no-syncskips it for offline/CI use.- Closes 6 Bun-CLI argv gaps the Rust port had dropped (
vault-sync --branch, positional args onsession-init/checkpoint/register-schedule/init,migratepositional). - Unifies the flag surface — every
--vaultflag now also accepts--vault-diras a visible clap alias. vault-syncrefuses to write at filesystem root or the literal$HOME— a defensive guard against foot-cannons.migrate <name>rejects supplying both the positional[cutoff_date]and--cutoff <date>together.- GitHub Release body now renders a friendly platform table so non-Rust users can pick the right download.
- README rewritten with the platform table + one-step quickstart; CONTRIBUTING.md added.
- Adds 9 new integration tests; suite now at 634 passing.
- fix(release): adds
shell: bashto Build/Strip steps so$TARGETexpands correctly on Windows runners; unblocks 7/7 platform builds. (PR #20)
- Ports the full Bun CLI parity surface (slices 7–13):
init(vault bootstrap + schedule presets +register-hooks),vault-sync(9-step release-overlay flow),register-hooks,register-schedule(launchd plists, skill/command mode, one-shotat:),update(GitHub releases fetch + atomic swap),run-skill,migrate,doctor(8 read-only checks), andorphan-scan(Active-Session Guard). (PR #2, #3, #9–#16) - Fixes 2 parity regressions found during the port:
initreportinghooks: okwhile.claude/settings.jsonwas never written (slice 10);vault-syncsilently exiting 0 on a caught error with no message (slice 13). - New core modules:
onebrain-core::scheduler(cron/launchd, ports Bun 1:1),onebrain-fs::init/orphan(injectable IO closures for offline/TTY-free tests),load_vault_config_atfor direct-path config loading. VaultFoldersextended from 1 key (logs) to all 8 standard PARA keys, matching Bun'sDEFAULT_FOLDERS.doctor --fixauto-repair deferred to v3.0.1 per spec §7.10 — flag is parsed but emits a stub message; doctor itself is parity-green.- New workspace deps:
regex,dirs,libc,indexmap,inquire(interactive prompts). .github/workflows/release.yml7-platform release pipeline (tar.gz/zip + sha256);CHANGELOG.mdreformatted to the repo's compact style (PR #5).- Post-merge hardening on PR #3 (ENOENT vs EACCES differentiation,
frontmattervisibility fix, boundary tests) plus repo metadata (description, homepage, topics, branch ruleset).
- 4-crate Cargo workspace (
onebrain-core/onebrain-fs/onebrain-cache/onebrain-cli) scaffolding all 13 subcommands (12 stilltodo!()). session-initsubcommand with 8-layer session token resolution (Bun v2.3.3 parity): env vars → process ancestor walk-up → day-scoped cache → PID fallback.qmd_unembeddedcount sourced from spawningqmd status --json(2s timeout, returns 0 on any failure) — matches Bun.- Block path: vault-not-found OR config-load-error both emit
{"decision":"block","reason":"onebrain-init-required"};session-initnever exits non-zero. - 4-layer test pyramid: inline unit +
assert_cmdintegration +instasnapshots + golden-master parity vs Bun v2.3.3. - Error model split:
thiserrortyped errors per library crate +anyhowpropagation in the binary, mapped to sysexits.h-aligned exit codes. - CI workflow: fmt + clippy + 3-platform test matrix (ubuntu/macos/windows).
- AGPL-3.0-only license; Windows ARM64 added as the 7th release-matrix platform; 46 tests passing.