feat(core): suite-2 uplift read (+40pp) + scope-filter blindness, capture gap, Redis drift - #175
Conversation
…g drift
Three findings from building the second generation-uplift suite. That
suite takes its treatment-arm injection from the LIVE KRA path rather
than a hand-written block; the first probe returned a null, and chasing
it surfaced more than the benchmark would have.
1. Capture gap (partially closed). Two of three probed repo rules were
absent from the Brain entirely — force-dynamic and the package
boundary — despite each having a class-of-bug entry and real
debugging cost. Proven to be capture, not retrieval: teaching
force-dynamic and re-running the identical prompt ranked it FIRST.
Nine rules backfilled; the rest of GUIDELINES is unswept.
2. scope-filter blindness (open). buildKnowledgeWhere and
buildRawProjectFilter resolve visibility purely on ownerProjectId
and never consult the scope column, so scope:"user" rows carrying a
non-null ownerProjectId are invisible outside their capture project
— 117 rows. Measured: the best-matching item in the corpus (0.9009
similarity, 11 successful uses, exact trigger match) is returned
first by unscoped retrieval and not at all by the project-scoped
session path. ~Half the corpus (101 in Default vs 100 in Brain
Platform) is invisible to a BrainPlatform session. Left open
deliberately: the fix changes what every user sees.
3. Redis drift (doc fixed). .env.example claimed "Production
docker-compose provides redis://redis:6379". It does not — compose
only passes ${REDIS_URL:-} through. So a healthy redis container has
run for weeks with nothing connected, rate-limit state lives in an
in-process Map that resets on every deploy, and the get-then-set
race fixed in #173 was a live production bypass rather than a
dev-only concern.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Rqj5y9fT3XPUqnqKyustp9
📝 WalkthroughWalkthroughUpdated Redis deployment guidance and added a Known Issues entry documenting corpus capture gaps, user-scope visibility behavior, measurements, backfill notes, and corrected Compose configuration details. ChangesDocumentation updates
Estimated code review effort: 1 (Trivial) | ~5 minutes Possibly related issues
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ 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 |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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 `@docs/KNOWN_ISSUES.md`:
- Line 403: Update the issue heading and wording in the documented entry to
state that the visibility blind spot affects project-scoped retrieval or
project-scoped sessions, not user-scope retrieval generally. Preserve the
existing evidence and distinction that unscoped retrieval can return these rows,
while keeping the open design-call status unchanged.
🪄 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: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: d73c8c27-316d-409b-b311-27e11d63cd08
📒 Files selected for processing (2)
.env.exampledocs/KNOWN_ISSUES.md
| | Issue | Where | Status | | ||
| |---|---|---| | ||
| | ~~**Hard-won repo rules were never taught to the Brain.**~~ **Partially closed (2026-07-28).** A probe of three documented rules found two absent from the corpus entirely: the `force-dynamic` rule (§10) and the package-boundary rule — each with a class-of-bug entry here and real debugging cost behind it. The Oracle said so outright: *"isn't captured in your current knowledge base."* Only the `#418` mount-gate rule was present. **This is a capture gap, not a retrieval gap** — proven by teaching `force-dynamic` and re-running the *identical* prompt, which then ranked it **first**. KRA ranked it correctly the moment it existed. Nine rules were backfilled from GUIDELINES §9/§10 and AGENTS.md. **The rest of GUIDELINES/KNOWN_ISSUES has not been swept** — the docs remain the de facto memory for pre-Stage-1 lessons. | `docs/GUIDELINES.md`, the Brain corpus | partially done | | ||
| | **`scope: "user"` knowledge is invisible outside the project it was captured in — 117 rows affected.** Both `buildKnowledgeWhere` and `buildRawProjectFilter` (the pgvector path that `kra.ts` and `oracle.ts` actually use) resolve visibility as `ownerProjectId = $activeProject OR (ownerProjectId IS NULL AND ownerUserId = $user)`. **The `scope` column is never consulted.** A row with `scope: "user"` and a non-null `ownerProjectId` therefore matches neither arm and is filtered out — even though `scope: "user"` is `brain_teach_knowledge`'s *default* and reads as "follows the user". (`Knowledge.visibility` is a separate Phase-4 field and does not govern this path.) **Measured impact, same prompt both arms:** unscoped retrieval ranked `cmqpqemoh…` — trigger *"Adding a user-facing concept or glossary page to the External Brain webapp"*, 100% success over 11 uses — **first, at 0.9009 similarity**. The project-scoped session path did not return it at all, injecting a tangential i18n rule instead. **Scale:** 101 active items in `Default` vs 100 in `Brain Platform`; 117 rows repo-wide are `scope='user'` with a non-null project. Roughly half the corpus is invisible to a `BrainPlatform` session. **Not fixed here** — the fix is a design call with real blast radius (honour `scope` in the filter, vs. null out `ownerProjectId` on user-scope rows as a data repair) and it changes what every user sees. Note the duplicate-project detector cannot surface this: it looks for *normalized name collisions*, and `Brain Platform` vs `Default` will never collide. | `packages/core/src/scope-filter.ts:64-75,155-168`; `kra.ts:172` | open — needs a design call | |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Scope the heading to project-scoped retrieval.
The current wording implies that scope: "user" knowledge is generally invisible outside its capture project, but this entry also says unscoped retrieval returns the row, and the later resolved note documents scope-aware behavior when no project is supplied. Please state explicitly that the blind spot affects project-scoped sessions, so readers do not infer that all user-scope retrieval is broken.
Proposed wording
-| **`scope: "user"` knowledge is invisible outside the project it was captured in — 117 rows affected.**
+| **Project-scoped sessions can hide `scope: "user"` knowledge captured in another project — 117 rows affected.**📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| | **`scope: "user"` knowledge is invisible outside the project it was captured in — 117 rows affected.** Both `buildKnowledgeWhere` and `buildRawProjectFilter` (the pgvector path that `kra.ts` and `oracle.ts` actually use) resolve visibility as `ownerProjectId = $activeProject OR (ownerProjectId IS NULL AND ownerUserId = $user)`. **The `scope` column is never consulted.** A row with `scope: "user"` and a non-null `ownerProjectId` therefore matches neither arm and is filtered out — even though `scope: "user"` is `brain_teach_knowledge`'s *default* and reads as "follows the user". (`Knowledge.visibility` is a separate Phase-4 field and does not govern this path.) **Measured impact, same prompt both arms:** unscoped retrieval ranked `cmqpqemoh…` — trigger *"Adding a user-facing concept or glossary page to the External Brain webapp"*, 100% success over 11 uses — **first, at 0.9009 similarity**. The project-scoped session path did not return it at all, injecting a tangential i18n rule instead. **Scale:** 101 active items in `Default` vs 100 in `Brain Platform`; 117 rows repo-wide are `scope='user'` with a non-null project. Roughly half the corpus is invisible to a `BrainPlatform` session. **Not fixed here** — the fix is a design call with real blast radius (honour `scope` in the filter, vs. null out `ownerProjectId` on user-scope rows as a data repair) and it changes what every user sees. Note the duplicate-project detector cannot surface this: it looks for *normalized name collisions*, and `Brain Platform` vs `Default` will never collide. | `packages/core/src/scope-filter.ts:64-75,155-168`; `kra.ts:172` | open — needs a design call | | |
| | **Project-scoped sessions can hide `scope: "user"` knowledge captured in another project — 117 rows affected.** Both `buildKnowledgeWhere` and `buildRawProjectFilter` (the pgvector path that `kra.ts` and `oracle.ts` actually use) resolve visibility as `ownerProjectId = $activeProject OR (ownerProjectId IS NULL AND ownerUserId = $user)`. **The `scope` column is never consulted.** A row with `scope: "user"` and a non-null `ownerProjectId` therefore matches neither arm and is filtered out — even though `scope: "user"` is `brain_teach_knowledge`'s *default* and reads as "follows the user". (`Knowledge.visibility` is a separate Phase-4 field and does not govern this path.) **Measured impact, same prompt both arms:** unscoped retrieval ranked `cmqpqemoh…` — trigger *"Adding a user-facing concept or glossary page to the External Brain webapp"*, 100% success over 11 uses — **first, at 0.9009 similarity**. The project-scoped session path did not return it at all, injecting a tangential i18n rule instead. **Scale:** 101 active items in `Default` vs 100 in `Brain Platform`; 117 rows repo-wide are `scope='user'` with a non-null project. Roughly half the corpus is invisible to a `BrainPlatform` session. **Not fixed here** — the fix is a design call with real blast radius (honour `scope` in the filter, vs. null out `ownerProjectId` on user-scope rows as a data repair) and it changes what every user sees. Note the duplicate-project detector cannot surface this: it looks for *normalized name collisions*, and `Brain Platform` vs `Default` will never collide. | `packages/core/src/scope-filter.ts:64-75,155-168`; `kra.ts:172` | open — needs a design call | |
🤖 Prompt for 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.
In `@docs/KNOWN_ISSUES.md` at line 403, Update the issue heading and wording in
the documented entry to state that the visibility blind spot affects
project-scoped retrieval or project-scoped sessions, not user-scope retrieval
generally. Preserve the existing evidence and distinction that unscoped
retrieval can return these rows, while keeping the open design-call status
unchanged.
… live-KRA) Committed BEFORE any run, per the honesty guardrail suite 1 set: this file's first commit is the pre-registration timestamp. Task list, injection procedure and metric are locked as of this commit. Suite 1 measured the injection MECHANISM: generic utility tasks with a hand-written injected block. Four of its six tasks tied because the base model already knew the edge case. Suite 2 changes both variables — tasks whose correct answer is only derivable from BrainPlatform convention, and a treatment block taken verbatim from the LIVE KRA path rather than authored. That makes it a test of retrieval + generation together, i.e. the product. Confound control: KNOWN_ISSUES §0p / #174 established that scope:"user" knowledge is invisible outside its capture project. All five rules under test were taught into the canonical Brain Platform project and verified retrievable from a projectName:"BrainPlatform" session before this file was written — three of them ranking #1 for their own task query. Without that control a null could not distinguish "knowledge didn't help" from "knowledge wasn't visible". Records two caveats up front rather than discovering them later: grading is static assertion over emitted source (weaker than suite 1's executable tests, since these conventions govern code shape), and the control arm's repo isolation is instruction-enforced rather than sandboxed — which would inflate control and understate uplift, so any control pass is checked against its transcript and a contaminated task is reported void rather than as a tie. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rqj5y9fT3XPUqnqKyustp9
…on pays Runs the suite pre-registered in f62895b: five corpus-dependent tasks, treatment arm injected verbatim from the LIVE KRA path. Control 3/5, treatment 5/5 — +40pp, 0 regressions. The aggregate is the less interesting half. What separates a flip from a tie is whether the convention is locally arbitrary: - FLIPS. Task 1 (force-dynamic) — control emitted no static-rendering opt-out of any kind, reproducing the v0.14.0 bug verbatim; the rule only exists because deploy/Dockerfile builds with dummy env. Task 4 (@brain/core/format-relative) — control hand-rolled its own formatter, exactly the divergence v0.15.0 consolidated away. Neither is derivable from general expertise. - TIES. Tasks 2, 3 and 5 are conventions that coincide with good practice: the control arm reached for x-forwarded-host unprompted, mount-gated its window read, and used {count} placeholders without being told. So injection pays where the convention is arbitrary and ties where it is craft — which predicts where capture effort returns most. Suite 1's ties had the same cause; two independent suites now agree. Caveats recorded rather than buried: n=5; grading is static assertion over emitted source; isolation was instruction-enforced and every control pass was checked for contamination (none found). Most importantly, the five rules were verified retrievable from a project-scoped session first, to control for #174 — roughly half the corpus is invisible to such a session today, so an uncurated run would score lower. This measures the Brain with its known retrieval defect controlled for, not as a user experiences it. Both arms' raw outputs committed under suite-2/tasks/ for audit. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Rqj5y9fT3XPUqnqKyustp9
0. Suite-2 generation-uplift read — +40pp, and where injection pays
Pre-registration committed at
f62895bbefore any arm ran. Fivecorpus-dependent tasks; treatment arm injected verbatim from the live KRA path
rather than hand-written — so this measures retrieval and generation together.
The aggregate is the less interesting half. What separates a flip from a tie is
whether the convention is locally arbitrary:
force-dynamicon env-reading server componentx-forwarded-hostinstead ofreq.urlwindow.location@brain/core/format-relativesubpathkind (task 1) — the v0.14.0 bug verbatim — and hand-rolled its own
formatRelative(task 4), exactly the divergence v0.15.0 consolidated away.x-forwarded-hostunprompted,mount-gated its
windowread, and used{count}placeholders without being told.So injection pays where the convention is arbitrary and ties where it is craft —
which predicts where capture effort returns most. Suite 1's four ties had the same
cause; two independent suites now agree on the mechanism.
Caveat that matters most: the five rules were verified retrievable from a
project-scoped session first, to control for #174 below. Roughly half the corpus
is invisible to such a session today, so an uncurated run would score lower.
This measures the Brain with its known retrieval defect controlled for.
What
Three findings from building the second generation-uplift suite. That suite takes
its treatment-arm injection from the live KRA path rather than a hand-written
block — the change you make when you want to measure the product instead of the
mechanism. The first probe returned a null, and chasing it surfaced more than the
benchmark itself would have.
1. Capture gap — partially closed
Probed three documented repo rules. Two were absent from the Brain entirely —
force-dynamic(GUIDELINES §10) and the package-boundary rule — despite eachcarrying a class-of-bug entry here and real debugging cost. The Oracle said so
outright: "isn't captured in your current knowledge base."
Proven to be capture, not retrieval: taught
force-dynamic, re-ran theidentical prompt, and it came back ranked first. KRA ranked it correctly the
moment it existed. Nine rules backfilled from GUIDELINES §9/§10 and AGENTS.md.
This inverts the diagnosis I opened with. I'd recommended work on retrieval
quality (NDCG 0.3075) — but retrieval can only rank what capture put there.
2.
scope: "user"is invisible outside its capture project — open, #174buildKnowledgeWhereandbuildRawProjectFilter(the pgvector pathkra.tsandoracle.tsactually use) resolve visibility asownerProjectId = $activeProject OR (ownerProjectId IS NULL AND ownerUserId = $user). Thescopecolumn isnever consulted. A row with
scope: "user"and a non-nullownerProjectIdmatches neither arm — and that is exactly what
brain_teach_knowledgeproduces bydefault in a project-bound session.
Controlled reproduction, identical prompt:
brain_retrieve_knowledgebrain_start_session(projectName:"BrainPlatform")Scale: 101 active rows in
Defaultvs 100 inBrain Platform; 117 rowsrepo-wide are
scope='user'with a non-null project.Deliberately not fixed here. Both candidate fixes change what every user sees;
that's a design call, written up in #174 rather than taken unilaterally.
3. Redis config drift — doc fixed
.env.exampleclaimed "Production docker-compose providesredis://redis:6379".It does not — compose only passes
${REDIS_URL:-}through. Consequences on thereference instance: a healthy
deploy-redis-1has run for weeks with nothingconnected; rate-limit state lives in an in-process Map that resets on every
deploy, silently clearing daily caps like
RATE_LIMIT_MEETING_EXTRACT_PER_DAY;and the get-then-set race fixed in #173 was therefore a live production
bypass, not a dev-only concern.
Test plan
scripts/check-doc-refs.shpasses locally (DOC_REF_CEILING=172).and the before/after on an identical prompt moved it from absent to rank 1.
kra.ts:172uses the raw one, and reproduced the miss with a controlledscoped-vs-unscoped comparison.
psqlagainst prod (SELECTonly, permittedautonomously).
.env.exampleclaim checked againstdeploy/docker-compose.yml:147-149.(docs +
.env.exampleonly), but CI is still the gate.REDIS_URL="redis://redis:6379"in the live.envand redeploy.
.envis gitignored — no PR can do this.scopein the filter vs.null out
ownerProjectIdon user-scope rows).🤖 Generated with Claude Code
https://claude.ai/code/session_01Rqj5y9fT3XPUqnqKyustp9