Skip to content

v3.4.12 — Self-healing: run smooth under a running daemon (#258, #257)#259

Merged
kengio merged 8 commits into
mainfrom
fix/258-token-gain-daemon-lock
Jul 12, 2026
Merged

v3.4.12 — Self-healing: run smooth under a running daemon (#258, #257)#259
kengio merged 8 commits into
mainfrom
fix/258-token-gain-daemon-lock

Conversation

@kengio

@kengio kengio commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

v3.4.12 — Self-healing: run smooth under a running 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. Started as a token gain hotfix (#258) and grew to cover the whole daemon-contention seam.

What's fixed

  1. token gain under a running daemon — the reported bug. Every mode hard-errored Database already open. Cannot acquire lock. because the rollup DB was opened eagerly. Now the default summary / --by / --history / --reset read the lock-free JSONL source of truth, and --all-time / --since route through the daemon's /api/token/gain.
  2. Upgrade-without-restart (Gap 4) — routing adopts a same-vault daemon even across a version skew, so token gain works right after this release without manually restarting the daemon. New discover_same_vault_any_version.
  3. Exit-code consistency — a genuinely contended rollup open (or a too-old daemon route) now maps to the shared E_ENGINE_BUSY / exit 77, like every sibling search surface.
  4. serve reuse-or-start (Gap 2, v3.4.12: Token Gain dashboard is dark under onebrain serve (no token cache) — open token.redb read-only #257)serve now 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. The explicit --port/--dir standalone escape hatch also opens its token cache now.
  5. vsearch daemon-routable (Gap 3) — new vector-only mode=vec on /api/vault/search; search vsearch routes through it instead of failing E_ENGINE_BUSY during an onebrain mcp session.
  6. 4 Copilot autoreview findingsdoctor scoped-key panic guard, dead test code, a chrono deprecation, and a fake-daemon read-loop.

Verification

Closes #258
Closes #257

kengio added 8 commits July 12, 2026 17:42
token gain opened token.redb eagerly at the top of run(), before dispatching
any mode. redb is single-process, so a running onebrain daemon (the token.redb
owner) made every mode hard-error with 'Database already open. Cannot acquire
lock.' — even --history, which only reads the JSONL raw log.

Fix (lazy open + daemon routing):
- The default summary, --by, --history and --reset read the lock-free JSONL
  source of truth and never open redb → they work while a daemon holds the lock.
- --all-time / --since route through the daemon's existing GET /api/token/gain
  when a warm vault-bound daemon holds the DB, else open Direct.
- --rebuild (a redb write) and a genuinely contended Direct open now surface an
  actionable message (retry after `onebrain daemon stop`) instead of the raw
  redb error.

Tests: lock-held default/history succeed; Direct-under-lock reports the
actionable message; --all-time routes through a fake daemon (local redb held
locked so it can ONLY pass by routing); a 404 gain route is actionable.

Closes #258
Review + self-healing hardening on top of the #258 lazy-open fix:

- Gap 4 (upgrade-without-restart): route --all-time/--since to a same-vault
  daemon REGARDLESS of version via new discover_same_vault_any_version — the
  gain route returns a version-stable PivotResult, so a still-running old daemon
  holding token.redb no longer forces a Direct open into its lock. Read verbs
  never restart another session's daemon (that stays ensure_running/mcp's job).
- Gap 8 (daemon dies mid-call): a transport error from the gain route now falls
  through to a now-unlocked Direct open instead of erroring.
- R2#1: a genuinely contended rollup open (and a too-old daemon route) now map
  to CoreError::EngineBusy -> E_ENGINE_BUSY / exit 77, the same transient-lock
  code search query/vsearch/get use (was a plain exit-1 error).
- R2#3: fix the stale 'no live traffic writes GainEvents yet' module comment.

Tests: version-skew routing, daemon-gone->Direct, by/since forwarding,
rebuild-under-lock exit-77, and discover_same_vault_any_version adoption.

Copilot autoreview findings (bundled):
- doctor_integration find_scoped_key_line: segments[0]/last().unwrap() ->
  first()?/last()? (no panic on empty segments)
- onebrain-fs init test: remove dead Rc<RefCell> count var + unused imports
- token rollup: DateTime::<Utc>::from_timestamp -> DateTime::from_timestamp
  (chrono 0.4.23 deprecation)
Copilot: the token_check + token_gain test fake daemons read at most one 8192-byte
chunk, so a request split across TCP segments could be misidentified. Accumulate
until `\r\n\r\n` (end of HTTP headers) before routing.
…one (#258)

serve was passive: it reused a version+vault-matched daemon, else ran a
FOREGROUND standalone server with hold_engine=false — which (a) has no token
cache, so the Token-Gain dashboard is dark (#257), and (b) collides on the redb
lock / port with a different-version daemon.

Now serve uses ensure_running (the confirmed reuse-or-start policy): reuse a
running daemon for this vault, else start one — restarting a stale/mismatched
one — then hand the user that daemon's webui URL. The started daemon holds the
engine + token cache, so the dashboard is populated. A genuine start failure
falls back to standalone rather than erroring. Explicit --port/--dir/$ONEBRAIN_BIND
and $ONEBRAIN_NO_DAEMON still force standalone.
…te (#257, #258)

Gap 1 (#257): standalone serve (the explicit --port/--dir escape hatch) opens
its token.redb cache via a new ServeConfig.open_token_cache flag, so /api/token/*
answers 200 and the Token-Gain dashboard lights up instead of 503-ing. The
daemon (hold_engine) already opened it; the unit-test router leaves it off.

Gap 3 (#258): add a vector-only 'vec' mode to /api/vault/search (run_vec_held +
run_vec, mirroring the hybrid pair via Engine::vector_search), and route the CLI
'search vsearch' through it (route_to_daemon -> run_query_via_daemon 'vec'). So
vsearch works while an mcp session holds the engine instead of failing
E_ENGINE_BUSY. Semantic-build-gated; lex-only has no vec route (opens direct).

CHANGELOG: v3.4.12 reframed to the self-healing theme.

2627 tests pass; default + lex-only builds clean; fmt + clippy clean.
Adds a download-free empty-index test for run_native's mode=vec path (run_vec),
and a vec assertion to run_native_no_index_returns_empty — closing the coverage
gap on the new per-request vector path.
…t only reused)

Review follow-up: serve now starts the daemon via ensure_running, so the old
'Daemon already serving this vault' banner was inaccurate on the just-started
path.
…258, #257)

Review #2 (consistency) caught doc/comment drift from the mechanism changes:
- serve.md / daemon.md / serve.rs module doc: 'serve never starts a daemon' is
  now false — serve reuses-or-starts (ensure_running).
- daemon.md / mcp.rs / server/search.rs docs: vsearch is now daemon-routed via a
  new mode=vec; the route speaks lex|hybrid|vec.
- ADR 0032 records the self-healing decisions and supersedes ADR 0023's
  'vsearch not routed' + the old serve 'never starts a daemon' stance.
@kengio kengio merged commit 231da42 into main Jul 12, 2026
15 checks passed
@kengio kengio deleted the fix/258-token-gain-daemon-lock branch July 12, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant