feat(search): end-to-end timing instrumentation behind BRAINLAYER_SEARCH_PROFILE flag#325
Conversation
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
📝 WalkthroughWalkthroughThis PR adds comprehensive opt-in search profiling instrumentation across BrainBar (Swift) and BrainLayer (Python). When ChangesSearch Profile Instrumentation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@codex review |
|
@greptileai review |
|
@coderabbitai review |
|
You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a324baeb52
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@brain-bar/Sources/BrainBar/SearchProfileLogger.swift`:
- Around line 40-42: The loop merging caller-provided `fields` into `event`
currently allows overwriting reserved profiling keys (ts, scope, step, query_id,
dur_ms); change the merge to skip any keys in a reserved set (e.g. let reserved:
Set = ["ts","scope","step","query_id","dur_ms"]) and only assign `event[key] =
value` when `!reserved.contains(key)` (or alternatively namespace/rename
conflicting keys), keeping the rest of the merging logic intact so canonical
keys cannot be overridden.
In `@brain-bar/Sources/BrainBarDaemon/SearchProfileLogger.swift`:
- Around line 40-42: In SearchProfileLogger, the loop that merges `fields` into
`event` allows callers to overwrite core event keys; change the merge to protect
reserved event keys by defining a set of reserved keys (e.g., the canonical
event schema keys used by this logger) and only copying entries from `fields`
whose keys are not in that reserved set; reference the existing `fields` and
`event` variables in the merging logic, skip (or rename/prefix) any conflicting
key, and optionally emit a warning via the logger when a field is dropped to aid
debugging.
In `@src/brainlayer/mcp/search_handler.py`:
- Around line 492-495: The dispatcher currently only emits the brain_search
timing in the final fallback and drops profile_query_id/profile_scope on
recursive calls; fix this by ensuring a single profile_query_id/profile_scope is
created once (use search_profile.new_query_id() only when profile_query_id is
None) and passed through every recursive call to _brain_search (propagate
profile_query_id and profile_scope as arguments), and emit the brain_search
timing event on every dispatcher return path (or wrap the top-level dispatch in
a try/finally that emits brain_search using the preserved
profile_query_id/profile_scope and search_profile.now()) so every code path
records timing and all recursive layers share the same query id for correlation.
In `@src/brainlayer/search_profile.py`:
- Around line 45-46: The emit logic must avoid mutating reserved schema keys and
must not let JSON serialization errors bubble up; in the emit function replace
the direct event.update(fields) with a shallow merge that ignores reserved keys
("ts","scope","step","query_id","dur_ms") from fields (e.g., create merged =
{**event, **{k:v for k,v in fields.items() if k not in RESERVED}}) and then wrap
the logger call in a try/except: attempt json.dumps(merged, sort_keys=True,
separators=(",",":")), and on TypeError (or other serialization errors) fall
back to a safe serialization (e.g., convert non-serializable values to their
repr/str) or log a minimal JSON-safe payload (timestamp, scope, step, query_id,
dur_ms plus a "meta" stringified fields) so profiling never raises; reference
event.update(fields), json.dumps, and the emit function/reserved key list when
making the changes.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 235ed41c-05bd-4eae-b802-99ec8305da4f
📒 Files selected for processing (11)
brain-bar/Sources/BrainBar/HybridSearchHelperClient.swiftbrain-bar/Sources/BrainBar/MCPRouter.swiftbrain-bar/Sources/BrainBar/QuickCapturePanel.swiftbrain-bar/Sources/BrainBar/SearchProfileLogger.swiftbrain-bar/Sources/BrainBarDaemon/SearchProfileLogger.swiftdocs.local/audits/2026-05-25-search-profile-instrumentation-mandate.mdsrc/brainlayer/brainbar_hybrid_helper.pysrc/brainlayer/mcp/search_handler.pysrc/brainlayer/search_profile.pysrc/brainlayer/search_repo.pytests/test_search_profile.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: Macroscope - Correctness Check
- GitHub Check: test (3.11)
- GitHub Check: test (3.13)
- GitHub Check: test (3.12)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py
📄 CodeRabbit inference engine (AGENTS.md)
**/*.py: Flag risky DB or concurrency changes explicitly and do not hand-wave lock behavior
Enforce one-write-at-a-time concurrency constraint; reads are safe but brain_digest is write-heavy and must not run in parallel with other MCP work
Run pytest before claiming behavior changed safely; current test suite has 929 tests
**/*.py: Usepaths.py:get_db_path()for all database path resolution; all scripts and CLI must use this function rather than hardcoding paths
When performing bulk database operations: stop enrichment workers first, checkpoint WAL before and after, drop FTS triggers before bulk deletes, batch deletes in 5-10K chunks, and checkpoint every 3 batches
Files:
tests/test_search_profile.pysrc/brainlayer/search_repo.pysrc/brainlayer/brainbar_hybrid_helper.pysrc/brainlayer/search_profile.pysrc/brainlayer/mcp/search_handler.py
src/brainlayer/**/*.py
📄 CodeRabbit inference engine (CLAUDE.md)
src/brainlayer/**/*.py: Use retry logic onSQLITE_BUSYerrors; each worker must use its own database connection to handle concurrency safely
Classification must preserveai_code,stack_trace, anduser_messageverbatim; skipnoiseentries entirely and summarizebuild_loganddir_listingentries (structure only)
Use AST-aware chunking via tree-sitter; never split stack traces; mask large tool output
For enrichment backend selection: use Groq as primary backend (cloud, configured in launchd plist), Gemini as fallback viaenrichment_controller.py, and Ollama as offline last-resort; allow override viaBRAINLAYER_ENRICH_BACKENDenv var
Configure enrichment rate viaBRAINLAYER_ENRICH_RATEenvironment variable (default 0.2 = 12 RPM)
Implement chunk lifecycle columns:superseded_by,aggregated_into,archived_aton chunks table; exclude lifecycle-managed chunks from default search; allowinclude_archived=Trueto show history
Implementbrain_supersedewith safety gate for personal data (journals, notes, health/finance); use soft-delete forbrain_archivewith timestamp
Addsupersedesparameter tobrain_storefor atomic store-and-replace operations
Run linting and formatting with:ruff check src/ && ruff format src/
Run tests withpytest
UsePRAGMA wal_checkpoint(FULL)before and after bulk database operations to prevent WAL bloat
Files:
src/brainlayer/search_repo.pysrc/brainlayer/brainbar_hybrid_helper.pysrc/brainlayer/search_profile.pysrc/brainlayer/mcp/search_handler.py
🧠 Learnings (2)
📚 Learning: 2026-03-18T00:12:08.774Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 87
File: brain-bar/Sources/BrainBar/BrainBarServer.swift:118-129
Timestamp: 2026-03-18T00:12:08.774Z
Learning: In Swift files under brain-bar/Sources/BrainBar, enforce that when a critical dependency like the database is nil due to startup ordering (socket before DB), any tool handler that accesses the database must throw an explicit error (e.g., ToolError.noDatabase) instead of returning a default/empty value. Do not allow silent defaults (e.g., guard let db else { return ... }). Flag patterns that silently return defaults when db is nil, as this masks startup timing issues. This guidance applies broadly to similar Swift files in the BrainBar module, not just this one location.
Applied to files:
brain-bar/Sources/BrainBar/SearchProfileLogger.swiftbrain-bar/Sources/BrainBar/HybridSearchHelperClient.swiftbrain-bar/Sources/BrainBar/MCPRouter.swiftbrain-bar/Sources/BrainBar/QuickCapturePanel.swift
📚 Learning: 2026-03-29T18:45:40.988Z
Learnt from: EtanHey
Repo: EtanHey/brainlayer PR: 133
File: brain-bar/Sources/BrainBar/BrainDatabase.swift:0-0
Timestamp: 2026-03-29T18:45:40.988Z
Learning: In the BrainBar module’s Swift database layer (notably BrainDatabase.swift), ensure that the `search()` function’s `unreadOnly=true` path orders results by the delivery frontier cursor so the watermark `maxRowID` stays contiguous. Specifically, when `unreadOnly` is enabled, the query must include `ORDER BY c.rowid ASC` (e.g., via `let orderByClause = unreadOnly ? "c.rowid ASC" : "f.rank"`). Do not replace the unread-only ordering with relevance-based sorting (e.g., `f.rank`) unconditionally or for the unread-only path, as it can introduce gaps in the watermark and incorrectly mark unseen rows as delivered. Flag any future change to the `ORDER BY` clause in this function that makes relevance sorting apply to the unread-only case.
Applied to files:
brain-bar/Sources/BrainBar/SearchProfileLogger.swiftbrain-bar/Sources/BrainBar/HybridSearchHelperClient.swiftbrain-bar/Sources/BrainBar/MCPRouter.swiftbrain-bar/Sources/BrainBar/QuickCapturePanel.swift
🪛 LanguageTool
docs.local/audits/2026-05-25-search-profile-instrumentation-mandate.md
[style] ~23-~23: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...rainSearch: step=router_dispatch. 3. BrainBar helper RPC start and completion: step=...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
🔇 Additional comments (8)
tests/test_search_profile.py (1)
30-54: LGTM!docs.local/audits/2026-05-25-search-profile-instrumentation-mandate.md (1)
1-35: LGTM!brain-bar/Sources/BrainBar/HybridSearchHelperClient.swift (1)
137-169: LGTM!brain-bar/Sources/BrainBar/MCPRouter.swift (1)
239-242: LGTM!Also applies to: 268-273, 321-323, 363-365, 381-383
brain-bar/Sources/BrainBar/QuickCapturePanel.swift (1)
113-114: LGTM!Also applies to: 277-280, 381-383, 388-402, 406-411, 415-417, 424-430, 436-442
src/brainlayer/mcp/search_handler.py (1)
14-14: LGTM!Also applies to: 1155-1156, 1181-1183, 1200-1200, 1222-1237
src/brainlayer/search_repo.py (1)
18-18: LGTM!Also applies to: 1243-1245, 1296-1335, 1336-1366, 1381-1475
src/brainlayer/brainbar_hybrid_helper.py (1)
23-24: LGTM!Also applies to: 48-62, 175-195, 224-237
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
@coderabbitai review |
|
@codex review |
|
You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 8c81107. Configure here.
| let divisor = pow(10.0, Double(places)) | ||
| return (self * divisor).rounded() / divisor | ||
| } | ||
| } |
There was a problem hiding this comment.
Unused daemon profile logger
Low Severity
SearchProfileLogger was added under BrainBarDaemon, but nothing in that target references it; only the BrainBar copy is used for hybrid search instrumentation.
Reviewed by Cursor Bugbot for commit 8c81107. Configure here.
There was a problem hiding this comment.
💡 Codex Review
brainlayer/src/brainlayer/mcp/search_handler.py
Lines 598 to 602 in 8c81107
When _brain_search reroutes file-path-like queries through a recursive _brain_search(...) call, it does not forward profile_query_id or profile_scope. With BRAINLAYER_SEARCH_PROFILE=1, this causes the nested call to mint a new query_id and fall back to the default scope, so a single user query produces split/mislabeled timing traces (notably for helper-scoped requests). Forwarding both fields in this recursive call keeps end-to-end timing correlated for extracted-file queries.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
|
@coderabbitai review |
|
@codex review |
|
You need to increase your spend limit or enable usage-based billing to run background agents. Go to Cursor |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cd7ab40147
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let response = try queue.sync { | ||
| try startLocked() | ||
| do { | ||
| return try send(arguments: arguments) |
There was a problem hiding this comment.
Propagate generated query_id to helper payload
When BRAINLAYER_SEARCH_PROFILE=1 and the caller omits _profile_query_id, this method generates a profileQueryID for helper_rpc_* logs but sends the original arguments unchanged. The Python helper then generates a different ID (HybridSearchHelper._search), so a single request gets split across multiple query_ids and cannot be reliably correlated end-to-end in profiling logs. This affects any direct HybridSearchHelperClient.search(arguments:) caller that doesn’t pre-populate _profile_query_id.
Useful? React with 👍 / 👎.


Summary
BRAINLAYER_SEARCH_PROFILE=1for Etan's 2026-05-25 report of ~15s search latency after Phase 2.4-F/PR feat(search): hybrid pre-warm + command-bar debounce + preview_text overlay #320 was expected to make warm hybrid search fast.ts,scope,step,query_id, and optionaldur_ms.docs.local/audits/2026-05-25-search-profile-instrumentation-mandate.md.Instrumentation points
keystroke_submit.router_dispatch.helper_rpc_start,helper_rpc_done.embed.hybrid_search.binary_knn,float_rerank,fts_merge.startup_warm_statewith warm/binary/mmap fields.render_done.Test plan
.venv/bin/pytest tests/test_search_profile.py -qfailed with 0 profile events before implementation..venv/bin/pytest tests/test_search_profile.py tests/test_brainbar_hybrid_helper.py -qswift testinbrain-bar/(449 tests).venv/bin/ruff check src/ tests/.venv/bin/ruff format --check src/ tests/.venv/bin/pytest(2216 passed, 47 skipped, 5 xfailed)Scope guard
Instrumentation only. No performance fix in this PR.
Note
Low Risk
Instrumentation is gated by an env flag and does not alter search results; overhead is limited to extra timing and logging when profiling is enabled.
Overview
Adds instrumentation-only search latency profiling behind
BRAINLAYER_SEARCH_PROFILE=1(silent when unset), aimed at diagnosing ~15s search latency without changing retrieval behavior.Introduces
SearchProfileLogger(Swift) andsearch_profile(Python) that emit single-line JSON events withscope,step,query_id, and optionaldur_ms. Aquery_idis created at entry (Quick Capture keystroke, MCPhandleBrainSearch, or helper RPC) and forwarded via_profile_query_idthrough the hybrid helper into_brain_searchandhybrid_search.Instrumented steps span BrainBar UI (
keystroke_submit,render_done), router (router_dispatch), helper RPC (helper_rpc_start/helper_rpc_done), MCP (embed,hybrid_search,brain_search), repo internals (binary_knn,float_rerank,fts_merge/float_search), and helper warmup (startup_warm_statewith binary index / mmap hints). A local audit doc describes how to read logs byquery_id.Tests in
tests/test_search_profile.pyassert JSON events, error fields on failure, and stablequery_idacross dispatch paths.Reviewed by Cursor Bugbot for commit cd7ab40. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Add end-to-end search timing instrumentation behind
BRAINLAYER_SEARCH_PROFILEflagSearchProfileLogger(Swift) andsearch_profile(Python) module that emit structured JSON timing events viaNSLog/logger.infowhenBRAINLAYER_SEARCH_PROFILE=1is set.keystroke_submit), router dispatch (router_dispatch), helper RPC (helper_rpc_start/helper_rpc_done), embedding (embed), hybrid search (hybrid_search,fts_merge), and final render (render_done)._profile_query_idcorrelation ID end-to-end acrossQuickCaptureViewModel,MCPRouter,HybridSearchHelperClient, and the Python MCP search handler so all events for a single query can be joined.tests/test_search_profile.pycovering event emission, error propagation, query ID preservation, and non-JSON-safe field serialization.Macroscope summarized cd7ab40.
Summary by CodeRabbit
New Features
Tests