feat(self-healing): close the auto-heal loop, wire into brain.correct() (Phase 1)#77
feat(self-healing): close the auto-heal loop, wire into brain.correct() (Phase 1)#77
Conversation
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
|
Caution Review failedPull request was closed or merged during review No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📜 Recent 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). (7)
🧰 Additional context used📓 Path-based instructions (1)src/gradata/**/*.py⚙️ CodeRabbit configuration file
Files:
🔇 Additional comments (4)
📝 Walkthrough
WalkthroughAdds optional automatic self-healing for detected rule failures. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Brain
participant Core as brain_correct()
participant AutoHeal as auto_heal_failures()
participant Events as brain.query_events()
participant Reviewer as review_rule_failures()
participant Patcher as brain.patch_rule()
Client->>Brain: Brain.correct(auto_heal=True)
Brain->>Core: invoke with auto_heal
Core->>Core: detect RULE_FAILURE(s)
alt auto_heal enabled and conditions permit
Core->>AutoHeal: auto_heal_failures(failure_events?)
AutoHeal->>Events: fetch RULE_FAILURE events (if needed)
Events-->>AutoHeal: events
AutoHeal->>Reviewer: review_rule_failures(events)
Reviewer-->>AutoHeal: candidates
loop candidates (up to max_patches)
alt passes retroactive test & not duplicate
AutoHeal->>Patcher: patch_rule(category, old, new, reason="auto_heal: ...")
Patcher-->>AutoHeal: result (patched / skipped)
alt patched
AutoHeal-->>Core: patch receipt appended
else
AutoHeal-->>Core: record skip reason
end
else
AutoHeal-->>Core: record skip reason
end
end
AutoHeal-->>Core: aggregated summary
Core->>Core: annotate event with auto_healed summary
end
Core-->>Brain: correction result (may include auto_healed)
Brain-->>Client: return
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/gradata/brain.py (1)
377-388: 🧹 Nitpick | 🔵 TrivialDocument the
auto_healparameter in the docstring.The new
auto_healparameter isn't mentioned in the method's docstring. Users may not discover this opt-out mechanism.📝 Suggested docstring addition
``applies_to`` is an optional free-form scope token (e.g. ``"client:acme"``, ``"task:emails"``, ``"global"``) that binds the correction to a specific context. When set, it is persisted on the event and propagated to any lesson that graduates from this correction's lineage. Injection-time filtering by ``applies_to`` is a follow-up — persistence only for now. A ``None`` value preserves the existing global behaviour. + + ``auto_heal`` controls whether detected RULE_FAILURE events trigger + automatic patching via the self-healing loop. Defaults to ``True``. + Set to ``False`` to detect failures without applying patches. Auto-heal + is also skipped when ``dry_run=True``, ``approval_required=True``, or + when the brain is in renter mode. """🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/gradata/brain.py` around lines 377 - 388, Add a docstring entry describing the auto_heal parameter for the "Record a correction" method (the function whose signature includes auto_heal: bool = True) explaining that auto_heal is a boolean opt-out for automatic healing of related lessons/events, its default value (True), what setting it to False does (disable automatic lineage-based healing/propagation), and any side-effects or persistence behavior (e.g., no change to applies_to persistence). Update the parameter list in the docstring to include ":param auto_heal: bool — default True; when False, disable automatic healing of related lessons/records; when True, allow automatic propagation/healing as part of correction processing."
🤖 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/enhancements/self_healing.py`:
- Around line 492-497: Add a type annotation for the brain parameter in
auto_heal_failures to improve IDE/static analysis: use a forward reference
string (e.g., brain: "Brain") or guard an import with a TYPE_CHECKING block and
annotate brain with Brain; also ensure the file has from __future__ import
annotations at the top to allow postponed evaluation of annotations and avoid
circular import issues.
In `@tests/test_self_healing.py`:
- Around line 438-453: Consolidate the duplicated brain_with_rule fixture by
moving its definition to module scope as a single
`@pytest.fixture`(scope="function") named brain_with_rule and import the same
symbols used in the duplicated versions (Brain.init, write_lessons_safe,
format_lessons, Lesson, LessonState, tmp_path fixture). Keep the logic that
creates the brain, constructs the RULE lesson (date "2026-04-01", state
LessonState.RULE, confidence 0.92, category "TONE", description "Never use
exclamation marks", fire_count 8), writes lessons via
brain._find_lessons_path(create=True) and
write_lessons_safe(format_lessons([...])) and return the brain; then remove the
identical class-level fixture definitions so tests simply reference the single
module-level brain_with_rule fixture.
- Around line 585-602: The test test_auto_heal_triggers_on_rule_failure weakly
asserts patching by gating assertions with "if patched:"; remove the conditional
and make the patching deterministic or split into two tests: one that only
asserts rule_failure_detected from brain_with_rule.correct(draft=..., final=...,
category="TONE") and a second test that uses a known-good input which guarantees
a RULE_PATCHED event (call
brain_with_rule.query_events(event_type="RULE_PATCHED", limit=10) and assert
patched non-empty, patched[0]['data']['reason'].startswith("auto_heal:"), and
"auto_healed" in the correct() result); update test names accordingly (e.g.,
test_detects_rule_failure and test_auto_heal_emits_rule_patched) and remove the
conditional branch so assertions always run against concrete expected outputs.
---
Outside diff comments:
In `@src/gradata/brain.py`:
- Around line 377-388: Add a docstring entry describing the auto_heal parameter
for the "Record a correction" method (the function whose signature includes
auto_heal: bool = True) explaining that auto_heal is a boolean opt-out for
automatic healing of related lessons/events, its default value (True), what
setting it to False does (disable automatic lineage-based healing/propagation),
and any side-effects or persistence behavior (e.g., no change to applies_to
persistence). Update the parameter list in the docstring to include ":param
auto_heal: bool — default True; when False, disable automatic healing of related
lessons/records; when True, allow automatic propagation/healing as part of
correction processing."
🪄 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: 53cc7d8a-a9d9-4575-af2c-1c2027171194
📒 Files selected for processing (4)
src/gradata/_core.pysrc/gradata/brain.pysrc/gradata/enhancements/self_healing.pytests/test_self_healing.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). (6)
- GitHub Check: Test (Python 3.12)
- GitHub Check: Test (Python 3.13)
- GitHub Check: test (3.11)
- GitHub Check: test (3.12)
- GitHub Check: test (3.13)
- GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
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/brain.pysrc/gradata/_core.pysrc/gradata/enhancements/self_healing.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_self_healing.py
🪛 GitHub Actions: SDK CI
src/gradata/brain.py
[warning] 1378-1378: pyright: Import "gradata.enhancements.memory_extraction" could not be resolved (reportMissingImports)
🔇 Additional comments (7)
src/gradata/enhancements/self_healing.py (2)
523-530: LGTM: Resilient exception handling for orchestration.The broad exception handler ensures the auto-heal loop doesn't crash on transient DB issues. This is appropriate for a best-effort healing orchestrator.
547-596: LGTM: Well-structured orchestration loop.The logic correctly sequences: max_patches cap → retroactive test gate → deduplication → patch application. The dedup key normalization (
.upper(),.strip()) ensures consistent matching.src/gradata/_core.py (2)
429-450: LGTM: Auto-heal integration is correctly guarded.The skip conditions (
dry_run,approval_required,_renter_mode) appropriately prevent automatic patching in sensitive contexts. The failure event is correctly wrapped in the format expected byauto_heal_failures.
442-448: LGTM: Inline healing correctly capped.Using
max_patches=1for inline healing duringcorrect()prevents runaway patching while still closing the loop for the immediate failure. The conditional addition ofauto_healedto the event only when a patch succeeds is correct.src/gradata/brain.py (1)
509-537: LGTM: Clean public API for auto-healing.The
auto_heal()method correctly delegates toauto_heal_failureswith proper parameter forwarding. The docstring accurately describes the behavior and return value.tests/test_self_healing.py (2)
455-463: LGTM: Precise assertion on empty input behavior.The test correctly asserts the exact expected return structure rather than just checking truthiness. This follows the coding guidelines for tests.
604-626: LGTM: Good coverage of opt-out and dry_run behavior.These tests verify the critical guardrails:
auto_heal=Falseprevents patching while still detecting failures, anddry_run=Trueprevents patching entirely. Assertions are specific and check exact counts.
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
… ann + test fixture) - Document auto_heal parameter on Brain.correct (defaults to False post-council) - Add forward-ref Brain type annotation on auto_heal_failures via TYPE_CHECKING - Hoist brain_with_rule to module-scope fixture and remove 5 duplicated class-level copies - Split weak test_auto_heal_triggers_on_rule_failure into two deterministic tests: one asserts rule_failure_detected only, the second drives auto_heal_failures with known-good delta words and asserts patched==1 plus auto_heal: reason prefix Co-Authored-By: Gradata <noreply@gradata.ai>
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
… ann + test fixture) - Document auto_heal parameter on Brain.correct (defaults to False post-council) - Add forward-ref Brain type annotation on auto_heal_failures via TYPE_CHECKING - Hoist brain_with_rule to module-scope fixture and remove 5 duplicated class-level copies - Split weak test_auto_heal_triggers_on_rule_failure into two deterministic tests: one asserts rule_failure_detected only, the second drives auto_heal_failures with known-good delta words and asserts patched==1 plus auto_heal: reason prefix Co-Authored-By: Gradata <noreply@gradata.ai>
6dee3e9 to
1feae5c
Compare
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
tests/test_self_healing.py (1)
651-677:⚠️ Potential issue | 🟡 MinorThis E2E test can mask a broken inline auto-heal path.
After opting into
brain.correct(..., auto_heal=True), the test still runsreview_rule_failures()andbrain.patch_rule()manually. That fallback can createRULE_PATCHEDeven if the new inline orchestration regresses, so this no longer proves the behavior introduced by the PR.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/test_self_healing.py` around lines 651 - 677, The E2E test currently masks inline auto-heal by calling review_rule_failures() and brain.patch_rule() after brain.correct(..., auto_heal=True); update the test to verify the inline path directly by checking for a RULE_PATCHED event emitted by the brain after brain.correct(...) (e.g., query_events(event_type="RULE_PATCHED") and assert at least one), and remove or conditionally skip the manual review_rule_failures() + brain.patch_rule() loop so the test only passes when the inline auto_heal orchestration itself produces RULE_PATCHED; alternatively, if keeping manual fallback, add an explicit assertion that a RULE_PATCHED event already existed before invoking review_rule_failures()/brain.patch_rule() to ensure the inline path is validated.
🤖 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/_core.py`:
- Around line 449-468: Replace the print-based stderr writes in the auto-heal
loop with the module logger `_log`: remove the `import sys as _sys` and the
`print(..., file=_sys.stderr)` call and instead call `_log.warning(...)` (or
`_log.info(...)` if preferred) with the exact same formatted message built from
`heal_summary` patches (`_patch.get("rule_id")`, `_patch.get("old_confidence")`,
`_patch.get("new_confidence")`, `_patch.get("revert_command")`) so the message
flows through the SDK logging pipeline; keep the surrounding try/except
defensive guard and otherwise preserve the same message format and variables.
- Line 85: The cloud branch currently calls _cloud.correct(...) and ignores the
auto_heal flag; update the cloud call in the method that defines
applies_to/auto_heal (the code path around _cloud.correct at lines referenced)
to forward the auto_heal boolean (and applies_to) into _cloud.correct so the
cloud can honor the request, and add a fallback: if the cloud client either
lacks support or returns a sentinel/raises NotImplementedError, invoke the local
correction path (e.g., self._correct or brain._local_correct) with
auto_heal=True so the self-healing loop runs locally; ensure you reference and
pass the same applies_to and auto_heal parameters through when calling
_cloud.correct and the local fallback.
In `@src/gradata/brain.py`:
- Around line 516-544: The auto_heal wrapper should clear the brain's in-memory
rule cache after applying standalone patches so callers don't keep a stale
prompt; update the auto_heal method (which calls auto_heal_failures) to inspect
the returned summary dict (e.g., check "patched" or length of "patches") and, if
any patches were applied, invalidate or clear the instance cache `_rule_cache`
(or set it to None) so subsequent calls to `apply_brain_rules` or prompt
generation use the updated lessons written via `patch_rule`.
In `@src/gradata/enhancements/self_healing.py`:
- Around line 581-598: The rule_id generation uses Python's process-randomized
hash(candidate['original_description']), producing unstable IDs; replace that
with the stable digest used elsewhere by calling the existing _make_rule_id
helper from src/gradata/rules/rule_engine.py (or compute a SHA-256 hex prefix
deterministically from f"{category}:{original_description}"), then set rule_id =
f"{candidate['category'].upper()}:{digest}" and keep the rest of the receipt
construction the same; also search for the same incorrect hash() pattern in
rule_engine.py (~line 1063) and replace it with the same stable digest approach
so revert commands are deterministic across processes.
---
Outside diff comments:
In `@tests/test_self_healing.py`:
- Around line 651-677: The E2E test currently masks inline auto-heal by calling
review_rule_failures() and brain.patch_rule() after brain.correct(...,
auto_heal=True); update the test to verify the inline path directly by checking
for a RULE_PATCHED event emitted by the brain after brain.correct(...) (e.g.,
query_events(event_type="RULE_PATCHED") and assert at least one), and remove or
conditionally skip the manual review_rule_failures() + brain.patch_rule() loop
so the test only passes when the inline auto_heal orchestration itself produces
RULE_PATCHED; alternatively, if keeping manual fallback, add an explicit
assertion that a RULE_PATCHED event already existed before invoking
review_rule_failures()/brain.patch_rule() to ensure the inline path is
validated.
🪄 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: 6fac4f42-ff0f-4066-900e-ae0c66852e14
📒 Files selected for processing (4)
src/gradata/_core.pysrc/gradata/brain.pysrc/gradata/enhancements/self_healing.pytests/test_self_healing.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). (13)
- GitHub Check: Test (Python 3.13)
- GitHub Check: test (3.13)
- GitHub Check: Test (Python 3.12)
- GitHub Check: test (3.12)
- GitHub Check: test (3.11)
- GitHub Check: Python 3.11
- GitHub Check: Python 3.12
- GitHub Check: Test (Python 3.11)
- GitHub Check: Test (Python 3.12)
- GitHub Check: Test (Python 3.13)
- GitHub Check: Python 3.12
- GitHub Check: Python 3.11
- GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
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/_core.pysrc/gradata/brain.pysrc/gradata/enhancements/self_healing.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_self_healing.py
| approval_required: bool = False, dry_run: bool = False, | ||
| min_severity: str = "as-is", scope: str | None = None, | ||
| applies_to: str | None = None, | ||
| applies_to: str | None = None, auto_heal: bool = False, |
There was a problem hiding this comment.
auto_heal is silently ignored for connected cloud brains.
Lines 115-119 still short-circuit into _cloud.correct(...) without forwarding the new flag or forcing a local fallback. A caller can opt into brain.correct(..., auto_heal=True) and never execute the self-healing loop when cloud mode is active.
Also applies to: 115-119
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/gradata/_core.py` at line 85, The cloud branch currently calls
_cloud.correct(...) and ignores the auto_heal flag; update the cloud call in the
method that defines applies_to/auto_heal (the code path around _cloud.correct at
lines referenced) to forward the auto_heal boolean (and applies_to) into
_cloud.correct so the cloud can honor the request, and add a fallback: if the
cloud client either lacks support or returns a sentinel/raises
NotImplementedError, invoke the local correction path (e.g., self._correct or
brain._local_correct) with auto_heal=True so the self-healing loop runs locally;
ensure you reference and pass the same applies_to and auto_heal parameters
through when calling _cloud.correct and the local fallback.
| # Make auto-heal visible: one stderr line per patch so | ||
| # silent rule edits can't sneak through. Guarded so a | ||
| # printing bug can never break the learning loop. | ||
| try: | ||
| import sys as _sys | ||
| for _patch in heal_summary.get("patches", []) or []: | ||
| _rid = _patch.get("rule_id", "?") | ||
| _old = _patch.get("old_confidence") | ||
| _new = _patch.get("new_confidence") | ||
| _revert = _patch.get( | ||
| "revert_command", f"gradata rule revert {_rid}" | ||
| ) | ||
| print( | ||
| f"[gradata] auto-healed R-{_rid}: " | ||
| f"confidence {_old} -> {_new}, " | ||
| f"revert with `{_revert}`", | ||
| file=_sys.stderr, | ||
| ) | ||
| except Exception: # pragma: no cover — defensive | ||
| pass |
There was a problem hiding this comment.
Use logging for the auto-heal notice instead of print().
This puts a raw stderr write on the core correction path and bypasses the SDK’s logging pipeline. Emit the same message through _log so handlers can still route it to stderr without sidestepping logging controls. As per coding guidelines, src/gradata/**/*.py: no print() statements (use logging).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/gradata/_core.py` around lines 449 - 468, Replace the print-based stderr
writes in the auto-heal loop with the module logger `_log`: remove the `import
sys as _sys` and the `print(..., file=_sys.stderr)` call and instead call
`_log.warning(...)` (or `_log.info(...)` if preferred) with the exact same
formatted message built from `heal_summary` patches (`_patch.get("rule_id")`,
`_patch.get("old_confidence")`, `_patch.get("new_confidence")`,
`_patch.get("revert_command")`) so the message flows through the SDK logging
pipeline; keep the surrounding try/except defensive guard and otherwise preserve
the same message format and variables.
| if result.get("patched"): | ||
| # Stable, deterministic rule id derived from category + original | ||
| # description. Mirrors the convention used elsewhere in the | ||
| # graph layer so `gradata rule revert {id}` can round-trip. | ||
| rule_id = ( | ||
| f"{candidate['category'].upper()}:" | ||
| f"{hash(candidate['original_description']) % 10000:04d}" | ||
| ) | ||
| preserved = result.get("confidence_preserved") | ||
| old_desc = candidate["original_description"] | ||
| new_desc = candidate["proposed_description"] | ||
| patch_diff = f"- {old_desc}\n+ {new_desc}" | ||
| receipt = { | ||
| "rule_id": rule_id, | ||
| "old_confidence": preserved, | ||
| "new_confidence": preserved, | ||
| "patch_diff": patch_diff, | ||
| "revert_command": f"gradata rule revert {rule_id}", |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
python - <<'PY'
import subprocess
import sys
expr = "print(hash('Never use exclamation marks') % 10000)"
first = subprocess.check_output([sys.executable, "-c", expr], text=True).strip()
second = subprocess.check_output([sys.executable, "-c", expr], text=True).strip()
print({"first_process": first, "second_process": second, "stable": first == second})
PYRepository: Gradata/gradata
Length of output: 127
🏁 Script executed:
# First, check the exact content at the specified lines
head -598 src/gradata/enhancements/self_healing.py | tail -18Repository: Gradata/gradata
Length of output: 964
🏁 Script executed:
# Search for any existing rule-id generation utilities or helpers
rg -i "rule.id|stable.*hash|digest" src/gradata/ -A 2 -B 2Repository: Gradata/gradata
Length of output: 50497
🏁 Script executed:
# Check if there are any hash utilities or stable ID generation functions
fd -e py . src/gradata | xargs grep -l "stable\|deterministic" | head -10Repository: Gradata/gradata
Length of output: 389
Use stable digest instead of hash() for rule_id generation.
The current code derives rule_id from hash(candidate['original_description']) % 10000, but Python salts hash(str) per interpreter process. This means the receipt's rule_id and revert_command will differ between the process that patched the rule and a later gradata rule revert ... invocation, breaking the round-trip contract claimed in the comment.
Use the existing _make_rule_id() helper from src/gradata/rules/rule_engine.py (which applies SHA-256), or apply the same stable digest pattern directly:
digest = hashlib.sha256(f"{candidate['category']}:{candidate['original_description']}".encode()).hexdigest()[:8]
rule_id = f"{candidate['category'].upper()}:{digest}"Also check src/gradata/rules/rule_engine.py line ~1063 for the same incorrect pattern.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/gradata/enhancements/self_healing.py` around lines 581 - 598, The rule_id
generation uses Python's process-randomized
hash(candidate['original_description']), producing unstable IDs; replace that
with the stable digest used elsewhere by calling the existing _make_rule_id
helper from src/gradata/rules/rule_engine.py (or compute a SHA-256 hex prefix
deterministically from f"{category}:{original_description}"), then set rule_id =
f"{candidate['category'].upper()}:{digest}" and keep the rest of the receipt
construction the same; also search for the same incorrect hash() pattern in
rule_engine.py (~line 1063) and replace it with the same stable digest approach
so revert commands are deterministic across processes.
brain.auto_heal() reads recent RULE_FAILURE events, runs review_rule_failures, gates each candidate through retroactive_test, and applies the survivors via brain.patch_rule. Batches are deduped by (category, original_description) and hard-capped at max_patches (default 5) so a single session can't rewrite a rule multiple times. Before this commit, PR #21 emitted RULE_FAILURE but nothing consumed it. The full flow required the caller to manually call review_rule_failures + brain.patch_rule as the E2E test does. Adds auto_heal_failures(brain, ...) orchestrator in self_healing.py and Brain.auto_heal public method that delegates to it. 6 new tests covering empty input, passing candidate, retro-test failure, in-batch dedup, max_patches cap, and event-log read when no events are passed.
When brain.correct() detects a RULE_FAILURE it now invokes auto_heal_failures inline, capped at one patch per call so a single correction can rewrite at most the rule it just failed. Skipped in dry_run, approval_required, and renter modes; opt-out via correct(auto_heal=False). Adds auto_heal kwarg to brain.correct + brain_correct in _core.py and surfaces the heal summary on event["auto_healed"] when a patch lands. 3 new tests covering the default-on path, the opt-out path, and the dry_run skip. Closes the diagnose-then-do-nothing gap that PR #21 left open.
…ipt for visibility Polyclaude council verdict (4/4 FALSE) on Phase 1: default auto_heal=True is too hot. Ship False default plus a visible PatchReceipt so any auto-heal that does fire is loud, not silent. Flip to True deferred until (a) a pending_patches review queue and (b) ablation showing rule-quality lift on a held-out broken-rule corpus. - brain.correct() and _core.brain_correct() default auto_heal to False - auto_heal_failures() returns PatchReceipt dicts: rule_id, old/new confidence, patch_diff, revert_command (legacy fields kept for BC) - _core emits one stderr line per successful auto-heal patch so silent rule edits cannot sneak through - tests: opt-in explicit auto_heal=True on existing integration tests, plus new test_default_off_no_patch and test_patch_receipt_shape Co-Authored-By: Gradata <noreply@gradata.ai>
… ann + test fixture) - Document auto_heal parameter on Brain.correct (defaults to False post-council) - Add forward-ref Brain type annotation on auto_heal_failures via TYPE_CHECKING - Hoist brain_with_rule to module-scope fixture and remove 5 duplicated class-level copies - Split weak test_auto_heal_triggers_on_rule_failure into two deterministic tests: one asserts rule_failure_detected only, the second drives auto_heal_failures with known-good delta words and asserts patched==1 plus auto_heal: reason prefix Co-Authored-By: Gradata <noreply@gradata.ai>
…ebase onto main Rewrite TestSelfHealingE2E::test_inline_auto_heal_emits_rule_patched and TestCorrectAutoHealIntegration::test_auto_heal_emits_patch_when_retroactive_test_passes to exercise ONLY the inline `brain.correct(auto_heal=True)` path. Remove the manual `review_rule_failures()` + `brain.patch_rule()` fallback so a regression in the inline orchestration fails the test instead of being masked by the fallback emitting RULE_PATCHED on its own. Also address three smaller CR items: - `_core.py`: forward `auto_heal` through the cloud branch by falling back to the local pipeline when the flag is set (cloud client does not yet carry the flag), so `auto_heal=True` is no longer silently dropped in cloud-connected brains. - `_core.py`: route the auto-heal notice through `_log.warning` instead of `print(..., file=sys.stderr)` to keep the SDK logging pipeline consistent. - `brain.py`: invalidate `_rule_cache` after `Brain.auto_heal()` applies patches, so later `apply_brain_rules()` calls see the patched prompt. Rebased onto origin/main (includes #76 cloud split, #80 distribution, #81 rule-to-hook auto-promotion). Rebase was conflict-free -- zero file overlap between this branch and the three landed PRs. Co-Authored-By: Gradata <noreply@gradata.ai>
1feae5c to
2b75fea
Compare
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 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/brain.py`:
- Around line 548-552: The try/except/pass around self._rule_cache.invalidate()
should be replaced with contextlib.suppress(Exception) to satisfy Ruff SIM105;
import contextlib if not already imported, then wrap the call to
self._rule_cache.invalidate() inside a contextlib.suppress(Exception) block
(condition remains result.get("patched")) so exceptions are suppressed without
an empty except.
🪄 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: fc0e6ee0-d216-418f-abba-6802f1caf080
📒 Files selected for processing (4)
src/gradata/_core.pysrc/gradata/brain.pysrc/gradata/enhancements/self_healing.pytests/test_self_healing.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). (6)
- GitHub Check: test (3.13)
- GitHub Check: Test (Python 3.11)
- GitHub Check: test (3.12)
- GitHub Check: test (3.11)
- GitHub Check: Test (Python 3.11)
- GitHub Check: Cloudflare Pages
🧰 Additional context used
📓 Path-based instructions (2)
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/brain.pysrc/gradata/_core.pysrc/gradata/enhancements/self_healing.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_self_healing.py
🪛 GitHub Actions: SDK CI
src/gradata/brain.py
[error] 549-552: Ruff (SIM105) reported: Use contextlib.suppress(Exception) instead of try-except-pass for cache invalidation. Replace the try/except/pass block as suggested.
🔇 Additional comments (10)
src/gradata/enhancements/self_healing.py (2)
585-588: Unstablerule_iddue to Python's randomizedhash().Python salts
hash(str)per interpreter process, so therule_idandrevert_commandwill differ between the process that patched the rule and a latergradata rule revert ...invocation. This breaks the round-trip contract.Use a stable digest (SHA-256) instead:
🔒 Proposed fix
+import hashlib + def auto_heal_failures( ... if result.get("patched"): - rule_id = ( - f"{candidate['category'].upper()}:" - f"{hash(candidate['original_description']) % 10000:04d}" - ) + digest = hashlib.sha256( + f"{candidate['category']}:{candidate['original_description']}".encode() + ).hexdigest()[:8] + rule_id = f"{candidate['category'].upper()}:{digest}"[raise_major_issue, duplicate_comment]
493-621: LGTM on the orchestration logic.The
auto_heal_failuresfunction correctly implements the closed-loop flow: fetching events, generating candidates, gating with retroactive test, deduplicating, and applying patches. The defensive exception handling and cap enforcement are appropriate.src/gradata/_core.py (2)
114-118: LGTM on the cloud routing fix.The condition correctly skips cloud routing when
auto_heal=True, ensuring the self-healing flag isn't silently dropped. The comment explains the rationale clearly.
436-471: LGTM on the inline auto-heal integration.The gating conditions (
auto_heal and not dry_run and not approval_required and not renter_mode) are correct. The defensive logging ensures visibility while never breaking the learning loop.src/gradata/brain.py (2)
516-547: LGTM on theauto_heal()method.The method correctly delegates to
auto_heal_failures, and the cache invalidation when patches are applied addresses the stale rule cache concern from the prior review.
377-412: LGTM on thecorrect()signature update.The
auto_healparameter is properly documented and forwarded tobrain_correct. The docstring clearly explains the behavior and gating conditions.tests/test_self_healing.py (4)
9-28: LGTM on the consolidated fixture.The module-level
brain_with_rulefixture eliminates the duplication flagged in the prior review while maintaining test isolation viascope="function".
422-532: LGTM onTestAutoHealcoverage.The tests comprehensively cover the orchestrator: empty input, successful patching, retroactive test failures, deduplication, max_patches cap, and implicit event reading. Assertions are specific and check concrete values.
535-651: LGTM onTestCorrectAutoHealIntegrationcoverage.The split between detection (
test_auto_heal_detects_rule_failure) and patching (test_auto_heal_emits_patch_when_retroactive_test_passes) addresses the prior review concern about conditional assertions. The opt-out and dry_run tests ensure the gating logic is exercised.
654-710: LGTM on the E2E test.The test explicitly verifies the inline path without fallback masking, ensuring regressions in the orchestration are caught. The on-disk rule rewrite assertion at line 708 confirms persistence.
…ff SIM105) CodeRabbit's 3rd review on PR #77 + CI Ruff job both flagged the try/except/pass block at brain.py:548-552 as SIM105 violation. Replaced with contextlib.suppress(Exception) (contextlib already imported at line 51). Behavior unchanged. Co-Authored-By: Gradata <noreply@gradata.ai>
There was a problem hiding this comment.
Gradata has reached the 50-review limit for trial accounts. To continue receiving code reviews, upgrade your plan.
…test LOC) Deletes dead code flagged in the autoresearch leanness audit after grep-verifying that no runtime import path exists. All 2453 tests pass. Source files removed (2101 LOC): - src/gradata/contrib/enhancements/outcome_feedback.py (1 LOC, docstring stub) - src/gradata/enhancements/super_meta_rules.py (197 LOC, no importers; SuperMetaRule dataclass + SQL table live in meta_rules.py / meta_rules_storage.py and remain wired) - src/gradata/enhancements/pubsub_pipeline.py (49 LOC, test-only) - src/gradata/rules/budget.py (43 LOC, test-only) - src/gradata/rules/rw_lock.py (54 LOC, test-only) - src/gradata/cloud/wiki_store.py (451 LOC, only cloud/__init__.py re-export + test) - src/gradata/enhancements/rule_verifier.py (243 LOC, only manifest string + test reference) - src/gradata/enhancements/rule_evolution.py (434 LOC, only manifest string + test references; contradiction_detector.py covers the live path via self_improvement.py:545) - src/gradata/security/privacy_model.py (113 LOC, test + docs only; _core.py / brain.py / _export_brain.py grep-clean) - src/gradata/benchmarks/swe_bench.py (516 LOC, docstring example + test only, no CLI/docs runtime reference) Test files removed (1042 LOC): matching tests for each module plus targeted pruning of rule_evolution test classes (TestRuleConflicts, TestRuleRelationEnum, rule_evolution imports in TestIntegration) from tests/test_steals.py and the TestRuleABTesting block in tests/test_adaptations.py. Registry + docstring updates: - contrib/enhancements/install_manifest.py: drop rule_verifier from rule-integrity module components - _manifest_helpers.py: drop rule_evolution from _core_modules - enhancements/__init__.py: drop rule_verifier docstring line - cloud/__init__.py: drop WikiStore lazy re-export - enhancements/meta_rules_storage.py: docstring no longer points at the deleted super_meta_rules.py NOT DELETED (verified live via PRs #77/#81/#86): - enhancements/rule_ranker.py, self_healing.py, rule_canary.py, rule_to_hook.py (all have runtime callers) - middleware/ (flagged empty in the audit but actually contains _core.py + 4 adapters — kept) - src/gradata/graphify-out/ (did not exist in this tree) Tests: 2453 passed, 24 skipped (test_integration_full.py ignored per task spec). Co-Authored-By: Gradata <noreply@gradata.ai>
…test LOC) (#90) Deletes dead code flagged in the autoresearch leanness audit after grep-verifying that no runtime import path exists. All 2453 tests pass. Source files removed (2101 LOC): - src/gradata/contrib/enhancements/outcome_feedback.py (1 LOC, docstring stub) - src/gradata/enhancements/super_meta_rules.py (197 LOC, no importers; SuperMetaRule dataclass + SQL table live in meta_rules.py / meta_rules_storage.py and remain wired) - src/gradata/enhancements/pubsub_pipeline.py (49 LOC, test-only) - src/gradata/rules/budget.py (43 LOC, test-only) - src/gradata/rules/rw_lock.py (54 LOC, test-only) - src/gradata/cloud/wiki_store.py (451 LOC, only cloud/__init__.py re-export + test) - src/gradata/enhancements/rule_verifier.py (243 LOC, only manifest string + test reference) - src/gradata/enhancements/rule_evolution.py (434 LOC, only manifest string + test references; contradiction_detector.py covers the live path via self_improvement.py:545) - src/gradata/security/privacy_model.py (113 LOC, test + docs only; _core.py / brain.py / _export_brain.py grep-clean) - src/gradata/benchmarks/swe_bench.py (516 LOC, docstring example + test only, no CLI/docs runtime reference) Test files removed (1042 LOC): matching tests for each module plus targeted pruning of rule_evolution test classes (TestRuleConflicts, TestRuleRelationEnum, rule_evolution imports in TestIntegration) from tests/test_steals.py and the TestRuleABTesting block in tests/test_adaptations.py. Registry + docstring updates: - contrib/enhancements/install_manifest.py: drop rule_verifier from rule-integrity module components - _manifest_helpers.py: drop rule_evolution from _core_modules - enhancements/__init__.py: drop rule_verifier docstring line - cloud/__init__.py: drop WikiStore lazy re-export - enhancements/meta_rules_storage.py: docstring no longer points at the deleted super_meta_rules.py NOT DELETED (verified live via PRs #77/#81/#86): - enhancements/rule_ranker.py, self_healing.py, rule_canary.py, rule_to_hook.py (all have runtime callers) - middleware/ (flagged empty in the audit but actually contains _core.py + 4 adapters — kept) - src/gradata/graphify-out/ (did not exist in this tree) Tests: 2453 passed, 24 skipped (test_integration_full.py ignored per task spec). Co-authored-by: Gradata <noreply@gradata.ai>
Summary
PR #21 shipped 8 self-healing helpers; only
detect_rule_failurewas wired.brain.correct()emittedRULE_FAILUREevents into a void. This PR closes the loop, then applies the polyclaude council verdict: default off, loud when it fires.Audit finding
PR #21 helpers (
detect_rule_failure,apply_patch,retroactive_test,review_rule_failures,check_nudge_threshold,suggest_scope_narrowing,narrow_rule_scope,_generate_deterministic_patch) all existed but onlydetect_rule_failureran. PR #74'sdemote_stale_rulesis wired. The headline gap: no code path joinedRULE_FAILUREemission toreview_rule_failures+brain.patch_rule. Even the E2E test called them by hand.What this PR ships
auto_heal_failures(brain, ...)orchestrator: reads RULE_FAILURE events, gates each candidate throughretroactive_test, applies survivors viabrain.patch_rule(which preserves confidence + emitsRULE_PATCHED). Caps atmax_patches=5, dedups by (category, original). ReturnsPatchReceiptdicts (rule_id, old_confidence, new_confidence, patch_diff, revert_command) so auto-edits are inspectable.Brain.auto_heal()public API.brain.correct(auto_heal=False)(default, per council). When opted in (auto_heal=True), inline-calls the orchestrator withmax_patches=1when a freshRULE_FAILUREis detected. Skipped indry_run,approval_required, renter modes. Every successful patch prints one stderr line:[gradata] auto-healed R-{id}: confidence X -> Y, revert withgradata rule revert {id}`` so silent rewrites cannot sneak through.Polyclaude council (4/4 FALSE)
The council vetoed
auto_heal=Trueas the default. This PR applies the verdict:auto_heal=False. The orchestrator + inline wiring ship, but are opt-in per call.PatchReceiptand a stderr line, so the edit is never silent.Trueis deferred until both (a) apending_patchesreview queue exists and (b) ablation shows rule-quality lift on a held-out broken-rule corpus.Deferred (not in this PR)
auto_heal=True(pending queue + ablation)pending_patchesreview queuecheck_nudge_threshold/narrow_rule_scope(failure-context dict in_core.pydoes not carrydomain/active_memoriesyet)Tests
2412 pass (+2 new:
test_default_off_no_patch,test_patch_receipt_shape). Ruff clean on touched src files (3 pre-existing F841 in PR #21 test code untouched).Commits
caea3c2feat(self-healing): close the auto-heal loop5ce5f80feat(self-healing): wire auto_heal into brain.correct() by defaulte268f14fix(self-healing): default auto_heal=False per council; add PatchReceipt for visibilityCo-Authored-By: Gradata noreply@gradata.ai