Summary
brain.prove() / brain health reports show 0 rule applications even on brains with rich correction history (e.g. oliver-admin: 50 corrections in 30d, 13 graduated rules, 35 active lessons, 0 rule applications).
Root cause: the RULE_APPLICATION event emission plumbing is fully implemented (gradata.rules.rule_tracker.log_application, Brain.track_rule) but no production code path actually calls it. Every site that surfaces a rule (Brain.apply_brain_rules, the SessionStart inject_brain_rules hook, the daemon's /apply endpoint) injects rules into the prompt and returns — none of them log an application event.
The downstream consumers of RULE_APPLICATION (enhancements/scoring/success_conditions.py, enhancements/scoring/reports.py, the prove/manifest path) therefore see an empty event stream and report zero. This isn't a metric-display bug; it's a missing emission contract.
Reproduction
$ python3 -c "import sqlite3; c=sqlite3.connect('/home/olive/.gradata/brain/system.db'); \
print(c.execute(\"SELECT type, COUNT(*) FROM events GROUP BY type ORDER BY 2 DESC\").fetchall())"
[('CORRECTION', 141), ('LESSON_CHANGE', 133), ('RULE_GRADUATED', 40),
('RULE_FAILURE', 20), ('HOOK_DEMOTED', 3), ('LESSON_ADDED', 3),
('RULE_PATCH_REVERTED', 3), ('RULE_TO_HOOK_INSTALLED', 2),
('RULE_TO_HOOK_FAILED', 1)]
# Note: no RULE_APPLICATION row at all.
Brain has 13 graduated rules and SessionStart hooks have been firing — but zero applications recorded.
Direct manual emission works, confirming the persistence layer is fine:
$ python -c "
from gradata.rules.rule_tracker import log_application
ev = log_application(rule_id='diag', session=99999, accepted=True, source='diag')
print(ev['type']) # -> RULE_APPLICATION, row appears in events table
"
Investigation — code paths reviewed
$ grep -rn 'log_application\|track_rule' src/gradata/ --include='*.py'
src/gradata/_events.py:683: """Public alias for session detection. Used by brain.track_rule()."""
src/gradata/brain.py:2119: def track_rule(
src/gradata/brain.py:2128: from gradata.rules.rule_tracker import log_application
src/gradata/brain.py:2138: return log_application(...)
src/gradata/rules/rule_tracker.py:29:def log_application(
src/gradata/rules/__init__.py:14:from gradata.rules.rule_tracker import RuleApplication, log_application
src/gradata/rules/__init__.py:26: "log_application",
Definition sites only — zero call sites outside Brain.track_rule, and Brain.track_rule itself has zero callers anywhere in src/. Callers reviewed and ruled out:
| Code path |
What it does |
Logs RULE_APPLICATION? |
Brain.apply_brain_rules (src/gradata/brain.py:1091) |
Ranks lessons, returns formatted prompt text. Emits rules.injected on the in-memory bus only. |
No |
gradata.hooks.inject_brain_rules (src/gradata/hooks/inject_brain_rules.py) |
SessionStart hook: writes ranked rules into Claude Code system prompt and injection_log (sqlite, separate table). |
No |
gradata.hooks.context_inject |
UserPromptSubmit hook: re-ranks rules per message. |
No |
gradata.hooks.jit_inject |
Opt-in JIT injection scored against the draft. |
No |
gradata.daemon /apply (src/gradata/daemon.py:376) |
HTTP apply_brain_rules endpoint for JS/TS clients. |
No |
gradata.enhancements.rule_pipeline (src/gradata/enhancements/rule_pipeline.py:578) |
Calls verify_rules(...) for post-application checks. |
No |
The injection_log sqlite table in inject_brain_rules._ensure_injection_log is a side-table for delta-injection deduping — it is not queryable by the prove/reports pipeline, which reads only the events table.
Root cause
Missing emission contract. The intended invariant — every rule surfaced through apply_brain_rules / SessionStart injection produces exactly one RULE_APPLICATION event with accepted=True (provisionally), to be downgraded/marked misfired=True if a subsequent CORRECTION event in the same session contradicts it — is unimplemented.
Suggested fix
Two-line emission at the bottom of Brain.apply_brain_rules, plus the same call at the bottom of inject_brain_rules.run_hook. Sketch in src/gradata/brain.py:
# in apply_brain_rules(), right after the `rules.injected` bus emit,
# before format_rules_for_prompt(applied):
if applied:
from gradata.rules.rule_tracker import log_application
from gradata._events import get_current_session
sess = get_current_session() or 0
for a in applied:
try:
log_application(
rule_id=a.rule_id,
session=sess,
accepted=True, # provisional; flipped by misfire detector
source="apply_brain_rules",
scope=scope,
)
except Exception as e:
logger.debug("RULE_APPLICATION emit failed for %s: %s", a.rule_id, e)
And a symmetric call in src/gradata/hooks/inject_brain_rules.py after the injected rule IDs are finalised (use _session_id(data) for the session, fall back to 0).
The misfired=True retro-flip already has a home in enhancements/scoring/failure_detectors.py — once emissions exist, that detector will start producing meaningful misfire rates.
Regression test sketch
tests/test_rule_application_emission.py:
Brain.init(tmp_path) + seed two RULE-state lessons via direct lessons.md write.
- Call
brain.apply_brain_rules("write an email about budget").
- Assert
len(query(event_type="RULE_APPLICATION")) >= 1.
- Repeat for the SessionStart hook by invoking
gradata.hooks.inject_brain_rules.run_hook with a fake stdin payload.
Both assertions fail on main today.
Affected versions
- SDK version:
gradata 0.7.5 (from python -c "import gradata; print(gradata.__version__)")
- Affected brain:
/home/olive/.gradata/brain/system.db (oliver-admin), 141 CORRECTION events, 40 RULE_GRADUATED events, 0 RULE_APPLICATION events.
- Likely all versions ≥ the introduction of
rule_tracker.py — git log src/gradata/rules/rule_tracker.py will give the exact regression-window start.
Kanban / tracking
GRA-1240 (hermes kanban task t_2830c5b4).
Summary
brain.prove()/ brain health reports show 0 rule applications even on brains with rich correction history (e.g. oliver-admin: 50 corrections in 30d, 13 graduated rules, 35 active lessons, 0 rule applications).Root cause: the
RULE_APPLICATIONevent emission plumbing is fully implemented (gradata.rules.rule_tracker.log_application,Brain.track_rule) but no production code path actually calls it. Every site that surfaces a rule (Brain.apply_brain_rules, theSessionStartinject_brain_ruleshook, the daemon's/applyendpoint) injects rules into the prompt and returns — none of them log an application event.The downstream consumers of
RULE_APPLICATION(enhancements/scoring/success_conditions.py,enhancements/scoring/reports.py, the prove/manifest path) therefore see an empty event stream and report zero. This isn't a metric-display bug; it's a missing emission contract.Reproduction
Brain has 13 graduated rules and SessionStart hooks have been firing — but zero applications recorded.
Direct manual emission works, confirming the persistence layer is fine:
Investigation — code paths reviewed
Definition sites only — zero call sites outside
Brain.track_rule, andBrain.track_ruleitself has zero callers anywhere insrc/. Callers reviewed and ruled out:Brain.apply_brain_rules(src/gradata/brain.py:1091)rules.injectedon the in-memory bus only.gradata.hooks.inject_brain_rules(src/gradata/hooks/inject_brain_rules.py)injection_log(sqlite, separate table).gradata.hooks.context_injectgradata.hooks.jit_injectgradata.daemon/apply(src/gradata/daemon.py:376)apply_brain_rulesendpoint for JS/TS clients.gradata.enhancements.rule_pipeline(src/gradata/enhancements/rule_pipeline.py:578)verify_rules(...)for post-application checks.The
injection_logsqlite table ininject_brain_rules._ensure_injection_logis a side-table for delta-injection deduping — it is not queryable by the prove/reports pipeline, which reads only theeventstable.Root cause
Missing emission contract. The intended invariant — every rule surfaced through
apply_brain_rules/ SessionStart injection produces exactly oneRULE_APPLICATIONevent withaccepted=True(provisionally), to be downgraded/markedmisfired=Trueif a subsequentCORRECTIONevent in the same session contradicts it — is unimplemented.Suggested fix
Two-line emission at the bottom of
Brain.apply_brain_rules, plus the same call at the bottom ofinject_brain_rules.run_hook. Sketch insrc/gradata/brain.py:And a symmetric call in
src/gradata/hooks/inject_brain_rules.pyafter the injected rule IDs are finalised (use_session_id(data)for the session, fall back to0).The
misfired=Trueretro-flip already has a home inenhancements/scoring/failure_detectors.py— once emissions exist, that detector will start producing meaningful misfire rates.Regression test sketch
tests/test_rule_application_emission.py:Brain.init(tmp_path)+ seed two RULE-state lessons via directlessons.mdwrite.brain.apply_brain_rules("write an email about budget").len(query(event_type="RULE_APPLICATION")) >= 1.gradata.hooks.inject_brain_rules.run_hookwith a fake stdin payload.Both assertions fail on
maintoday.Affected versions
gradata 0.7.5(frompython -c "import gradata; print(gradata.__version__)")/home/olive/.gradata/brain/system.db(oliver-admin), 141 CORRECTION events, 40 RULE_GRADUATED events, 0 RULE_APPLICATION events.rule_tracker.py—git log src/gradata/rules/rule_tracker.pywill give the exact regression-window start.Kanban / tracking
GRA-1240 (hermes kanban task
t_2830c5b4).