Skip to content

feat(hooks): inject meta-rules into LLM context at session start#45

Merged
Gradata merged 7 commits intomainfrom
feat/inject-meta-rules
Apr 15, 2026
Merged

feat(hooks): inject meta-rules into LLM context at session start#45
Gradata merged 7 commits intomainfrom
feat/inject-meta-rules

Conversation

@Gradata
Copy link
Copy Markdown
Owner

@Gradata Gradata commented Apr 14, 2026

Summary

Closes a structural gap: meta-rules were being created, stored, and loadable — but never injected into the LLM prompt. The inject_brain_rules hook only emitted ``, so the entire compounding layer sat dormant at the inference surface.

Changes

  • `src/gradata/hooks/inject_brain_rules.py`: load meta-rules from system.db via `meta_rules_storage.load_meta_rules`, cap at `MAX_META_RULES=5`, context-rank via `format_meta_rules_for_prompt`, append a `` block to the existing `` block.
  • Both the import and the DB load are wrapped in try/except so missing meta-rules support (minimal install) or a corrupt/missing system.db degrade gracefully to rules-only instead of failing session start.
  • `tests/test_hooks_learning.py`: 2 new tests — meta-rules appear in injection block when DB has them, and injection tolerates missing system.db.

Why it matters

Without this, the whole meta-rule subsystem was observable (via dashboard) but not operational (the LLM never saw the principles). Any session-to-session improvement from meta-rule emergence was silent. This PR makes the compounding layer actually influence the model.

Test plan

  • `python -m pytest tests/test_hooks_learning.py` — 11/11 (9 existing + 2 new)
  • `python -m ruff check src/gradata/hooks/inject_brain_rules.py` — clean
  • Smoke: run a session with a brain that has seeded meta-rules, verify `` block appears in the injected context

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

Warning

Rate limit exceeded

@Gradata has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 41 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 41 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4086a623-c57b-4241-aa75-8029ec223482

📥 Commits

Reviewing files that changed from the base of the PR and between 13d9231 and 56db167.

📒 Files selected for processing (4)
  • src/gradata/enhancements/meta_rules.py
  • src/gradata/enhancements/meta_rules_storage.py
  • src/gradata/hooks/inject_brain_rules.py
  • tests/test_hooks_learning.py
📝 Walkthrough

Walkthrough

Adds optional meta-rule injection to the inject_brain_rules hook: if brain_dir/system.db exists and meta-rule helpers are importable, load meta-rules, filter by trusted sources, rank/limit to 5, format them, and append a <brain-meta-rules> block to the returned prompt when applicable.

Changes

Cohort / File(s) Summary
Hook: meta-rule injection
src/gradata/hooks/inject_brain_rules.py
Guarded imports of meta-rule helpers; added MAX_META_RULES = 5; check for brain_dir/system.db; load meta-rules when available; filter by INJECTABLE_META_SOURCES; format with format_meta_rules_for_prompt(..., limit=MAX_META_RULES); append <brain-meta-rules> only if non-empty; safe-fail and debug logging on errors.
Meta-rule model & policy
src/gradata/enhancements/meta_rules.py
Added MetaRule.source: str = "deterministic"; added INJECTABLE_META_SOURCES = frozenset({"llm_synth","human_curated"}); `format_meta_rules_for_prompt(..., limit: int
Meta-rule storage & migration
src/gradata/enhancements/meta_rules_storage.py
Added source TEXT column to meta_rules table with default 'deterministic'; migrations/backfill updated; save_meta_rules persists source; load_meta_rules reads source and populates MetaRule.source with fallback.
Tests
tests/test_hooks_learning.py
Added tests covering meta-rule injection behavior: inclusion for injectable sources, exclusion for deterministic source or missing/corrupt DB, enforcement of MAX_META_RULES, and graceful degradation to rules-only on errors.

Sequence Diagram

sequenceDiagram
    participant Hook as inject_brain_rules.main()
    participant FS as FileSystem
    participant Loader as load_meta_rules()
    participant Formatter as format_meta_rules_for_prompt()
    participant Builder as Prompt Builder

    Hook->>FS: check existence of brain_dir/system.db
    alt system.db exists and helpers importable
        Hook->>Loader: load_meta_rules(db_path)
        Loader-->>Hook: meta_rules or error
        Hook->>Hook: filter metas by INJECTABLE_META_SOURCES
        Hook->>Hook: rank/sort and truncate to MAX_META_RULES
        Hook->>Formatter: format_meta_rules_for_prompt(top_metas, context, limit=MAX_META_RULES)
        Formatter-->>Hook: formatted_meta_block
        alt formatted_meta_block non-empty
            Hook->>Builder: append <brain-rules> + <brain-meta-rules>
        else
            Hook->>Builder: append <brain-rules> only
        end
    else
        Hook->>Builder: append <brain-rules> only
    end
    Builder-->>Hook: return prompt
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

feature

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: injecting meta-rules into the LLM context at session start, which directly addresses the core gap described in the PR.
Description check ✅ Passed The description provides relevant context about the changes, explains why they matter, and aligns with the file modifications across multiple modules.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/inject-meta-rules

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 14, 2026

Deploying gradata-dashboard with  Cloudflare Pages  Cloudflare Pages

Latest commit: 56db167
Status: ✅  Deploy successful!
Preview URL: https://b5dbeeb1.gradata-dashboard.pages.dev
Branch Preview URL: https://feat-inject-meta-rules.gradata-dashboard.pages.dev

View logs

@coderabbitai coderabbitai Bot added the feature label Apr 14, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/gradata/hooks/inject_brain_rules.py`:
- Around line 154-158: The current code slices metas by raw confidence into
metas_sorted before calling format_meta_rules_for_prompt, preventing
context-aware re-ranking from promoting lower-confidence but more relevant
rules; fix by removing the [:MAX_META_RULES] cap here (pass the full
sorted-by-confidence list or the original metas into
format_meta_rules_for_prompt) and apply the MAX_META_RULES limit only after
format_meta_rules_for_prompt's context-aware ranking, or alternatively add a
limit parameter to format_meta_rules_for_prompt (e.g., limit=MAX_META_RULES) and
have that function perform the context ranking first then truncate; update calls
accordingly (symbols: metas, metas_sorted, format_meta_rules_for_prompt,
MAX_META_RULES, context).

In `@tests/test_hooks_learning.py`:
- Around line 76-124: Add a parametrized test that verifies the MAX_META_RULES
cap and context-aware ordering by creating more than MAX_META_RULES MetaRule
instances via MetaRule and save_meta_rules, then calling inject_main with a
context that should boost a lower-confidence rule into the top-N result; assert
that the output contains exactly MAX_META_RULES meta-rules, includes the
promoted rule text, and preserves ordering by relevance. Locate test usage
around test_inject_also_emits_meta_rules_block_when_db_has_them /
test_inject_tolerates_missing_meta_rules_db and reuse tmp_path,
patch.dict(os.environ, {"GRADATA_BRAIN_DIR": str(tmp_path)}), ensuring the new
test references MAX_META_RULES constant and inject_main to exercise the
context-aware ranking path. Ensure the test is parametrized (e.g., varying
number of meta rules and context strings) to cover the boundary condition.
🪄 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: 1505dc64-f478-4140-9e18-4c4090e75c6c

📥 Commits

Reviewing files that changed from the base of the PR and between 342ac09 and 0b46097.

📒 Files selected for processing (2)
  • src/gradata/hooks/inject_brain_rules.py
  • tests/test_hooks_learning.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). (9)
  • GitHub Check: Test (Python 3.12)
  • GitHub Check: Test (Python 3.13)
  • GitHub Check: Test (Python 3.11)
  • GitHub Check: test (3.13)
  • GitHub Check: Python 3.12
  • GitHub Check: Python 3.11
  • GitHub Check: test (3.11)
  • GitHub Check: test (3.12)
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (3)
tests/**

⚙️ CodeRabbit configuration file

tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.

Files:

  • tests/test_hooks_learning.py
src/gradata/**/*.py

⚙️ CodeRabbit configuration file

src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].

Files:

  • src/gradata/hooks/inject_brain_rules.py
src/gradata/hooks/**

⚙️ CodeRabbit configuration file

src/gradata/hooks/**: JavaScript hooks for Claude Code integration. Check for: no shell injection (no execSync with user
input), temp files must use per-user subdirectory, HTTP calls must have timeouts, errors must be silent (never block
the tool chain).

Files:

  • src/gradata/hooks/inject_brain_rules.py

Comment thread src/gradata/hooks/inject_brain_rules.py Outdated
Comment thread tests/test_hooks_learning.py Outdated
Gradata and others added 2 commits April 14, 2026 08:10
Structural gap: meta-rules (tier-1 compound principles from 3+ graduated
rules) were being created, stored, and loaded by the SDK — but never
injected into the LLM prompt. The LLM saw <brain-rules> but never
<brain-meta-rules>, which meant the entire compounding layer was
dormant at the inference surface.

Fix: inject_brain_rules.py now ALSO loads meta-rules from system.db
via meta_rules_storage.load_meta_rules, caps them at MAX_META_RULES=5,
context-ranks via format_meta_rules_for_prompt, and appends a
<brain-meta-rules> block to the existing <brain-rules> block.

Defensive: import is try/except so sites without the meta_rules module
don't break. DB load is try/except so a corrupt/missing system.db
degrades to "rules only" rather than failing the session start hook.

Tests: 2 new — meta-rules appear in injection block when DB has them,
and injection tolerates missing system.db without raising.

Co-Authored-By: Gradata <noreply@gradata.ai>
…c principles

2026-04-14 ablation (432 trials, 3 LLMs, judged blind) showed deterministic
auto-generated meta-rule principles regress correctness:
  Sonnet 4.6:  -1.1% (full vs base)
  DeepSeek V3: -1.4% (full vs base)
  qwen2.5-coder:14b: drops correctness gain from +8.1% to +2.9%

The OSS clusterer produces principles like "Code: Avoid: foo. Prefer: bar"
or self-contradictory token frequencies. These are not safe to inject.

Changes:
- MetaRule gains a `source: str = "deterministic"` field. Default is the
  safe option — pre-existing rows are auto-generated and stay excluded.
- INJECTABLE_META_SOURCES = {"llm_synth", "human_curated"}. Anything else
  is excluded from prompt injection.
- meta_rules_storage: new ALTER TABLE migration adds the `source` column
  with default 'deterministic'. save/load read+write the field.
- inject_brain_rules hook filters by source before formatting. If metas
  exist but none are injectable, logs a debug line and skips silently.
- Tests: positive case uses source='llm_synth'; new negative case asserts
  high-confidence deterministic metas are NOT injected.

When LLM synthesis ships (cloud-side discover_meta_rules), it must set
source='llm_synth' to flow through. Hand-curated brains can use
source='human_curated'.

Co-Authored-By: Gradata <noreply@gradata.ai>
@Gradata Gradata force-pushed the feat/inject-meta-rules branch from 0b46097 to 9dab67e Compare April 14, 2026 15:15
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

Gradata added a commit that referenced this pull request Apr 14, 2026
Replaces placeholder with output of export_ab_proof.py against
.tmp/rule-ablation-v2 (Sonnet/DeepSeek/qwen14b × base/rules/full × 16
tasks × 3 iters, judged blind by Haiku 4.5).

Headline numbers (with_full_mean — rules + meta-rules):
- correctness: 0.833 → 0.832 (-0.1 pp)
- preference_adherence: 0.732 → 0.755 (+2.3 pp)
- quality: 0.793 → 0.799 (+0.6 pp)

Note: with_rules_mean is higher than with_full_mean across all dimensions.
The deterministic meta-rule overlay regresses results; this is the
empirical evidence that drove the source-aware injection filter (PR #45).

Co-Authored-By: Gradata <noreply@gradata.ai>
@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 14, 2026

Amended with empirically-grounded source filter (9dab67e + force-push for clean rebase on main):

  • MetaRule gains source: str = 'deterministic' field. Default = safe.
  • INJECTABLE_META_SOURCES = {'llm_synth', 'human_curated'} — anything else excluded from injection.
  • Schema migration: ALTER TABLE meta_rules ADD COLUMN source TEXT DEFAULT 'deterministic'. Pre-existing rows are auto-treated as deterministic and NOT injected (this is the safety property — Oliver's brain has 3 garbage deterministic metas that would have polluted every session).
  • Tests: positive case uses source='llm_synth'; new negative case asserts a high-confidence (1.00) deterministic meta is NOT injected.

Empirical justification: 2026-04-14 ablation (432 trials, 3 LLMs, blind judge) showed deterministic meta-rules regress correctness on Sonnet (-1.1%), DeepSeek (-1.4%), and halve qwen14b's correctness gain from +8.1% to +2.9%.

Cloud LLM synthesis (when shipped) sets source='llm_synth' to flow through. Hand-curated brains use source='human_curated'.

Gradata added a commit that referenced this pull request Apr 14, 2026
…ort (#44)

* feat(cloud): honest A/B proof — /public/proof endpoint backed by ablation

Replaces the ABProofPanel's fabricated marketing copy ("200 blind expert
evaluators", "3,000 comparisons", "70% win rate") with real ablation-backed
numbers served by a new public endpoint.

New pieces:
- cloud/app/routes/proof.py: GET /public/proof, reads cloud/data/proof_results.json,
  returns {available, source, subjects, judge, trials, dimensions, per_model}.
  Graceful empty state if file missing or corrupt — never fabricates.
- cloud/scripts/export_ab_proof.py: aggregates an ablation run's JSONL judgments
  into proof_results.json. Computes per-dimension means, 95% CIs, deltas, and
  per-model breakdown. Run: python cloud/scripts/export_ab_proof.py
- cloud/data/proof_results.json: placeholder (overwritten by export script).
- cloud/dashboard/src/components/brain/ABProofPanel.tsx: fetches /public/proof,
  shows real trials/subjects/judge when live, falls back to demo fixture with
  a visible "demo data" label when empty. Delta color flips on regressions
  (honest: we show -pp in red rather than only shipping positive results).
- cloud/tests/test_proof.py: 5 new tests (missing file, present file,
  corrupt file, unauthenticated public access, export helper loadable).

Pipeline: run ablation → run export script → redeploy cloud → dashboard lights
up with honest data automatically. No marketing claims the data doesn't
support.

Co-Authored-By: Gradata <noreply@gradata.ai>

* fix(cloud): address CR on #44 — wrong-shape JSON, with_full_mean, regression test

- proof.py: validate parsed JSON is a dict before calling setdefault;
  degrade gracefully when someone drops a list/string into the results
  file (test coverage added).
- ABProofPanel: use with_full_mean (rules + meta) for the 'rules+meta vs
  baseline' comparison cell, with fallback to best_mean, then to 0.
  Matches panel copy.
- test_proof: regression test for valid-but-wrong-shape JSON (`[]`).

Co-Authored-By: Gradata <noreply@gradata.ai>

* data: commit real ablation results to proof_results.json (427 trials)

Replaces placeholder with output of export_ab_proof.py against
.tmp/rule-ablation-v2 (Sonnet/DeepSeek/qwen14b × base/rules/full × 16
tasks × 3 iters, judged blind by Haiku 4.5).

Headline numbers (with_full_mean — rules + meta-rules):
- correctness: 0.833 → 0.832 (-0.1 pp)
- preference_adherence: 0.732 → 0.755 (+2.3 pp)
- quality: 0.793 → 0.799 (+0.6 pp)

Note: with_rules_mean is higher than with_full_mean across all dimensions.
The deterministic meta-rule overlay regresses results; this is the
empirical evidence that drove the source-aware injection filter (PR #45).

Co-Authored-By: Gradata <noreply@gradata.ai>

---------

Co-authored-by: Gradata <noreply@gradata.ai>
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 14, 2026

Addressed CR feedback (commit d1ebc51):

  • Major (cap before context ranking): Added optional limit parameter to format_meta_rules_for_prompt; cap is now applied after context-aware ranking via rank_meta_rules_by_context(max_rules=limit). Hook passes the full injectable set with limit=MAX_META_RULES instead of pre-slicing by raw confidence. A lower-confidence rule with a strong context weight can now be promoted into the prompt.
  • Nitpick (boundary test): Added test_inject_caps_meta_rules_and_context_promotes_lower_confidence — seeds MAX_META_RULES + 2 high-confidence neutral metas plus one low-confidence (0.60) meta with a 3.0x context weight in drafting, then asserts the output contains exactly MAX_META_RULES numbered lines and includes the context-promoted rule. Serves as a regression guard for the pre-slice bug.

Full suite: 2074 passed, 23 skipped.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 14, 2026

@coderabbitai review — fixes pushed, please re-verify.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 14, 2026

@Gradata: Kicking off a fresh full review to re-verify both fix batches:

  1. Source-filter safety (9dab67e) — MetaRule.source default, INJECTABLE_META_SOURCES allow-list, schema migration safety (pre-existing rows treated as deterministic), positive/negative test coverage.
  2. Cap-after-ranking fix (d1ebc51) — limit param added to format_meta_rules_for_prompt, hook passes the full injectable set with limit=MAX_META_RULES, boundary regression test promoting a lower-confidence context-weighted rule.

Re-verify both fix batches from commits 9dab67e and d1ebc51; confirm source-filter logic, schema migration safety, cap-after-context-ranking ordering, and boundary test coverage.

✅ Actions performed

Full review triggered.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/gradata/enhancements/meta_rules.py (1)

377-395: ⚠️ Potential issue | 🟡 Minor

Honor the "" contract when limit removes every rule.

limit=0 currently leaves metas empty after ranking/slicing, but this function still returns the section header instead of "". That would leak an empty <brain-meta-rules> block and breaks the documented return contract. The same branch also behaves oddly for negative limits.

Suggested fix
-    # Re-rank by context weight when a context is provided. Pass `limit`
-    # through as `max_rules` so ranking + capping happens atomically;
-    # otherwise apply the cap after the fact (no ranking case).
-    if context:
+    if limit is not None and limit <= 0:
+        return ""
+
+    # Re-rank by context weight when a context is provided. Pass `limit`
+    # through as `max_rules` so ranking + capping happens atomically;
+    # otherwise apply the cap after the fact (no ranking case).
+    if context:
         metas = rank_meta_rules_by_context(
             metas, context, max_rules=limit if limit is not None else len(metas),
         )
     elif limit is not None:
         metas = metas[:limit]
+
+    if not metas:
+        return ""
 
     lines = ["## Brain Meta-Rules (compound principles)"]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/gradata/enhancements/meta_rules.py` around lines 377 - 395, After
ranking/slicing, if the resulting metas list is empty the function must return
an empty string to honor the "" contract; update the logic in the block using
metas/rank_meta_rules_by_context so that after applying
rank_meta_rules_by_context or metas = metas[:limit] you check if not metas and
return "" immediately. Also normalize negative limit values (e.g., treat limit
<= 0 as empty result or clamp negative values to None) so negative limits don't
produce the header; adjust the use of limit when calling
rank_meta_rules_by_context (pass max_rules=max(0, limit) or handle limit<=0
early) and then proceed to build the lines only when metas is non-empty.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/gradata/hooks/inject_brain_rules.py`:
- Around line 155-180: Wrap the entire meta-rule processing pipeline (not just
load_meta_rules) in a try/except so any exception during loading, filtering, or
formatting is caught and the code falls back to silent degradation (i.e., treat
metas as empty and skip injection). Specifically, surround the block that calls
load_meta_rules(db_path), filters into injectable, and calls
format_meta_rules_for_prompt(injectable, context=context, limit=MAX_META_RULES)
with a single try/except; on exception log at debug level and set
metas/injectable/formatted to empty values so meta_block is never set and
SessionStart continues normally.

In `@tests/test_hooks_learning.py`:
- Around line 224-237: Add a sibling test to
test_inject_tolerates_missing_meta_rules_db that writes a malformed-but-readable
system.db file into tmp_path (while still creating lessons.md) and then calls
inject_main({}) under the same with patch.dict(os.environ, {"GRADATA_BRAIN_DIR":
str(tmp_path)}): context; the malformed system.db should contain a deliberately
corrupted payload (e.g. truncated/invalid JSON or non-deserializable bytes) but
be present so inject_main sees a file, and the test should assert inject_main()
returns non-None, the result contains "<brain-rules>" and does NOT contain
"<brain-meta-rules>", and that no exception is raised; reuse the same test
scaffolding and names (e.g. test_inject_tolerates_missing_meta_rules_db -> new
test_corrupt_meta_rules_db_degrades_to_rules_only) and reference inject_main and
GRADATA_BRAIN_DIR to locate behavior to validate.

---

Outside diff comments:
In `@src/gradata/enhancements/meta_rules.py`:
- Around line 377-395: After ranking/slicing, if the resulting metas list is
empty the function must return an empty string to honor the "" contract; update
the logic in the block using metas/rank_meta_rules_by_context so that after
applying rank_meta_rules_by_context or metas = metas[:limit] you check if not
metas and return "" immediately. Also normalize negative limit values (e.g.,
treat limit <= 0 as empty result or clamp negative values to None) so negative
limits don't produce the header; adjust the use of limit when calling
rank_meta_rules_by_context (pass max_rules=max(0, limit) or handle limit<=0
early) and then proceed to build the lines only when metas is non-empty.
🪄 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: acec069c-ff40-4300-a0d2-9fab209dc0d2

📥 Commits

Reviewing files that changed from the base of the PR and between 9dab67e and d1ebc51.

📒 Files selected for processing (3)
  • src/gradata/enhancements/meta_rules.py
  • src/gradata/hooks/inject_brain_rules.py
  • tests/test_hooks_learning.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). (1)
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (3)
tests/**

⚙️ CodeRabbit configuration file

tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.

Files:

  • tests/test_hooks_learning.py
src/gradata/**/*.py

⚙️ CodeRabbit configuration file

src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].

Files:

  • src/gradata/hooks/inject_brain_rules.py
  • src/gradata/enhancements/meta_rules.py
src/gradata/hooks/**

⚙️ CodeRabbit configuration file

src/gradata/hooks/**: JavaScript hooks for Claude Code integration. Check for: no shell injection (no execSync with user
input), temp files must use per-user subdirectory, HTTP calls must have timeouts, errors must be silent (never block
the tool chain).

Files:

  • src/gradata/hooks/inject_brain_rules.py

Comment thread src/gradata/hooks/inject_brain_rules.py
Comment thread tests/test_hooks_learning.py
Round-2 CR: a partially corrupt system.db can deserialize successfully and
then blow up inside format_meta_rules_for_prompt (e.g. JSON null in
source_lesson_ids becomes None and len() raises). The previous try/except
only guarded load_meta_rules, so failures in the filter/format pipeline
still aborted SessionStart.

- Wrap entire load -> filter -> format pipeline in one try/except so any
  downstream failure degrades to rules-only injection.
- Preserve context-aware cap: full injectable set still passed to formatter
  with limit=MAX_META_RULES so context ranking happens before slicing.
- Add test_corrupt_meta_rules_db_degrades_to_rules_only as sibling to the
  missing-DB case; writes garbage bytes to system.db and asserts the hook
  returns only <brain-rules> without raising.

Co-Authored-By: Gradata <noreply@gradata.ai>
@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

Addressed round-2 CR (commit 58fa161):

  1. Major (inject_brain_rules.py): Widened the try/except to cover the entire load -> filter -> format pipeline, not just load_meta_rules(). A corrupt system.db that deserializes (e.g. None for source_lesson_ids) but then explodes inside the formatter now degrades to rules-only instead of aborting SessionStart. Context-aware cap preserved — full injectable set still passed to formatter with limit=MAX_META_RULES so ranking happens before slicing.
  2. Nitpick (test_hooks_learning.py): Added test_corrupt_meta_rules_db_degrades_to_rules_only as sibling to the missing-DB case. Writes garbage bytes to system.db, asserts hook returns only <brain-rules> without raising.

Tests: 2074 pass, 23 skipped (one pre-existing flake in test_hooks_intelligence unrelated to this PR — passes in isolation, untouched by diff).

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

@coderabbitai review — CR Pro active, please re-verify.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

@Gradata: Kicking off a full re-verification pass covering all three fix batches:

  1. Source-filter safety (9dab67e) — MetaRule.source default, INJECTABLE_META_SOURCES allow-list, schema migration safety (pre-existing rows → deterministic, not injected), positive/negative test coverage.
  2. Cap-after-ranking fix (d1ebc51) — limit param on format_meta_rules_for_prompt, hook passes full injectable set with limit=MAX_META_RULES, boundary regression test confirming context-weight promotion before slicing.
  3. Widened try/except + corrupt DB degradation (58fa161) — entire load → filter → format pipeline wrapped, test_corrupt_meta_rules_db_degrades_to_rules_only added as a sibling to the missing-DB case.

Re-verify all three fix batches from commits 9dab67e, d1ebc51, and 58fa161; confirm source-filter logic and migration safety, cap-after-context-ranking ordering and boundary test coverage, and widened try/except with corrupt-DB degradation test.

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/gradata/enhancements/meta_rules_storage.py (1)

160-167: ⚠️ Potential issue | 🟠 Major

Legacy meta_rules tables won't load until something writes the migration.

load_meta_rules() now selects source unconditionally, but read-only callers such as src/gradata/hooks/inject_brain_rules.py never invoke ensure_table(). On an upgraded brain with an older meta_rules schema, this raises no such column: source, so meta-rules silently disappear until a later save happens. Either migrate before reading or probe the table schema and synthesize "deterministic" when the column is absent.

Suggested fix
         rows = conn.execute(
-            """SELECT id, principle, source_categories, source_lesson_ids,
-                      confidence, created_session, last_validated_session,
-                      scope, examples, context_weights, applies_when, never_when,
-                      transfer_scope, source
-               FROM meta_rules
-               ORDER BY confidence DESC"""
+            f"""SELECT id, principle, source_categories, source_lesson_ids,
+                      confidence, created_session, last_validated_session,
+                      scope, examples, context_weights, applies_when, never_when,
+                      transfer_scope,
+                      {"source" if "source" in {r[1] for r in conn.execute("PRAGMA table_info(meta_rules)")} else "'deterministic'"} AS source
+               FROM meta_rules
+               ORDER BY confidence DESC"""
         ).fetchall()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/gradata/enhancements/meta_rules_storage.py` around lines 160 - 167,
load_meta_rules() unconditionally selects the new "source" column causing "no
such column: source" on older read-only DBs; change load_meta_rules() to first
probe the meta_rules table schema (e.g., PRAGMA table_info or SELECT with zero
rows) or catch the specific sqlite error and, if "source" is missing, synthesize
source="deterministic" for each row instead of failing; alternatively,
call/trigger ensure_table() migration defensively before reading, but prefer
detecting missing column in load_meta_rules() and populating the returned dicts
with source="deterministic" so callers like inject_brain_rules.py work without
forcing a write migration.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/gradata/hooks/inject_brain_rules.py`:
- Around line 163-175: The current call to
format_meta_rules_for_prompt(injectable, context=context, limit=...) in
inject_brain_rules.py ignores rule gating based on session payload, causing
applies_when/never_when to be bypassed; update the call inside the injectable
handling block to pass the hook payload (or a normalized subset) via the
condition_context parameter (e.g., condition_context=hook_payload or a sanitized
payload dict) so format_meta_rules_for_prompt can evaluate
applies_when/never_when correctly during SessionStart; ensure you normalize/trim
sensitive fields before passing the payload.

---

Outside diff comments:
In `@src/gradata/enhancements/meta_rules_storage.py`:
- Around line 160-167: load_meta_rules() unconditionally selects the new
"source" column causing "no such column: source" on older read-only DBs; change
load_meta_rules() to first probe the meta_rules table schema (e.g., PRAGMA
table_info or SELECT with zero rows) or catch the specific sqlite error and, if
"source" is missing, synthesize source="deterministic" for each row instead of
failing; alternatively, call/trigger ensure_table() migration defensively before
reading, but prefer detecting missing column in load_meta_rules() and populating
the returned dicts with source="deterministic" so callers like
inject_brain_rules.py work without forcing a write migration.
🪄 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: 2aa04d93-d3c4-445f-885e-1a34f61649ad

📥 Commits

Reviewing files that changed from the base of the PR and between c8f64ce and 58fa161.

📒 Files selected for processing (4)
  • src/gradata/enhancements/meta_rules.py
  • src/gradata/enhancements/meta_rules_storage.py
  • src/gradata/hooks/inject_brain_rules.py
  • tests/test_hooks_learning.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). (1)
  • GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (3)
src/gradata/**/*.py

⚙️ CodeRabbit configuration file

src/gradata/**/*.py: This is the core SDK. Check for: type safety (from future import annotations required), no print()
statements (use logging), all functions accepting BrainContext where DB access occurs, no hardcoded paths. Severity
scoring must clamp to [0,1]. Confidence values must be in [0.0, 1.0].

Files:

  • src/gradata/enhancements/meta_rules_storage.py
  • src/gradata/enhancements/meta_rules.py
  • src/gradata/hooks/inject_brain_rules.py
src/gradata/hooks/**

⚙️ CodeRabbit configuration file

src/gradata/hooks/**: JavaScript hooks for Claude Code integration. Check for: no shell injection (no execSync with user
input), temp files must use per-user subdirectory, HTTP calls must have timeouts, errors must be silent (never block
the tool chain).

Files:

  • src/gradata/hooks/inject_brain_rules.py
tests/**

⚙️ CodeRabbit configuration file

tests/**: Test files. Verify: no hardcoded paths, assertions check specific values not just truthiness,
parametrized tests preferred for boundary conditions, floating point comparisons use pytest.approx.

Files:

  • tests/test_hooks_learning.py

Comment thread src/gradata/hooks/inject_brain_rules.py
Gradata added 2 commits April 14, 2026 21:55
CodeRabbit round-3 feedback:
1. load_meta_rules() probes pragma for `source` column and synthesizes
   'deterministic' when absent, so brains upgraded from a pre-source
   schema no longer raise `no such column: source` on read-only paths
   (inject_brain_rules never calls ensure_table()).
2. inject_brain_rules now forwards a sanitized hook payload as
   `condition_context`, so applies_when / never_when are honored during
   SessionStart instead of being silently bypassed. Only string/int/
   float/bool fields from session_type/task_type/source/cwd are
   forwarded — no transcripts, file contents, or secrets.

Regression tests:
- test_load_meta_rules_legacy_schema_without_source_column: creates a
  legacy meta_rules table, confirms load returns rows with
  source='deterministic' instead of crashing.
- test_inject_respects_applies_when_never_when: an llm_synth rule with
  never_when=session_type=code is excluded on code sessions and
  injected on prose sessions.

All 2077 tests pass, 23 skipped.
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

Rebased on origin/main + round-3 CR addressed:

  • load_meta_rules() now probes PRAGMA table_info(meta_rules) and synthesizes source='deterministic' when the column is missing, so brains on the legacy schema no longer crash read-only callers (inject_brain_rules) with no such column: source.
  • inject_brain_rules forwards a sanitized hook payload (session_type, task_type, source, cwd — scalars only) as condition_context, so applies_when / never_when gating is evaluated at SessionStart instead of silently bypassed.
  • Added two regression tests: test_load_meta_rules_legacy_schema_without_source_column and test_inject_respects_applies_when_never_when.
  • Preserved: context-aware MAX_META_RULES cap (applied after ranking), test_inject_caps_meta_rules_and_context_promotes_lower_confidence boundary test, test_corrupt_meta_rules_db_degrades_to_rules_only degrade-to-rules-only behavior.

Full suite: 2077 passed, 23 skipped.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@Gradata
Copy link
Copy Markdown
Owner Author

Gradata commented Apr 15, 2026

@coderabbitai review — rebased on latest main, all prior fixes applied

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

@Gradata: Kicking off a fresh review pass covering the rebase on main and the latest round of fixes:

  1. Legacy schema resilienceload_meta_rules() probing PRAGMA table_info(meta_rules) and synthesizing source='deterministic' when the column is absent, preventing no such column: source crashes on read-only callers.
  2. Condition context forwardinginject_brain_rules sanitizing the hook payload to scalars (session_type, task_type, source, cwd) and forwarding as condition_context so applies_when/never_when gating is evaluated at SessionStart.
  3. Regression test coveragetest_load_meta_rules_legacy_schema_without_source_column and test_inject_respects_applies_when_never_when alongside all previously verified tests.
  4. Preserved invariants — context-aware MAX_META_RULES cap after ranking, boundary promotion test, and corrupt-DB degradation behavior.

Re-verify the full patch after rebase on main: legacy schema PRAGMA probe in load_meta_rules, sanitized scalar condition_context forwarding in inject_brain_rules, the two new regression tests, and all previously verified fix batches (source-filter, cap-after-ranking, widened try/except).

✅ Actions performed

Full review triggered.

@Gradata Gradata merged commit 4fdddcf into main Apr 15, 2026
11 checks passed
@Gradata Gradata deleted the feat/inject-meta-rules branch April 15, 2026 07:55
Gradata added a commit that referenced this pull request Apr 15, 2026
SessionStart injection picks top-N rules once for the whole session.
JIT flips the axis: on every user prompt, score each graduated rule
against the draft with a cheap Jaccard on word-unigrams and inject only
the top-k that match THIS draft. Saves context budget, tightens
per-prompt relevance.

Gated by env var GRADATA_JIT_ENABLED (default false) so existing
SessionStart behavior is unchanged for anyone who hasn't opted in.
When both are active they complement: SessionStart = broad priors,
JIT = tight per-prompt overlay.

Emits JIT_INJECTION events to brain/events.jsonl for telemetry. Rule
must clear both confidence floor (0.60 default) and similarity floor
(0.05 default); rather inject zero rules than noise, same philosophy
as the PR #45 source-filter gate.

Hook wired as an additional UserPromptSubmit entry alongside
context_inject so the two complement rather than collide.

Co-Authored-By: Gradata <noreply@gradata.ai>
Gradata added a commit that referenced this pull request Apr 15, 2026
…68)

* feat(jit): per-call rule injection hook with draft-relevance ranking

SessionStart injection picks top-N rules once for the whole session.
JIT flips the axis: on every user prompt, score each graduated rule
against the draft with a cheap Jaccard on word-unigrams and inject only
the top-k that match THIS draft. Saves context budget, tightens
per-prompt relevance.

Gated by env var GRADATA_JIT_ENABLED (default false) so existing
SessionStart behavior is unchanged for anyone who hasn't opted in.
When both are active they complement: SessionStart = broad priors,
JIT = tight per-prompt overlay.

Emits JIT_INJECTION events to brain/events.jsonl for telemetry. Rule
must clear both confidence floor (0.60 default) and similarity floor
(0.05 default); rather inject zero rules than noise, same philosophy
as the PR #45 source-filter gate.

Hook wired as an additional UserPromptSubmit entry alongside
context_inject so the two complement rather than collide.

Co-Authored-By: Gradata <noreply@gradata.ai>

* refactor(jit): drop redundant zero-intersection branch in _jaccard

Division by len(a|b) already yields 0.0 when intersection is empty. The
early return was dead code.

Co-Authored-By: Gradata <noreply@gradata.ai>

---------

Co-authored-by: Gradata <noreply@gradata.ai>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant