Summary
On a default install, the Brain Activity console's "Lessons" metric is structurally always 0, and there is no automatic way to make it non-zero. The panel ships the visualization of a captured → distill → lessons pipeline whose automatic feeder is (per the plugin's own ADRs) not yet built. The only thing that writes to namespace='lessons' is a manual, unhooked script.
This is the plugin behaving contrary to its own stated design intent ("works out of the box"), not a crash. (Distinct from #15, which is the console failing to launch; this is about the console launching and then showing a metric that can never fill on its own.)
The drift (ADR vs code)
- ADR-0018 "Brain Activity panel" — Status: Implemented (ships v3.1.0). It builds the console card with a "compact animated distillation flow strip (captured → distill → lessons)" and a headline Lessons stat, reading
namespace='lessons' via GET /api/activity (scripts/onboarding-console.mjs → gatherActivity() → SELECT COUNT(*) … WHERE namespace='lessons').
- ADR-0017 "Recursive per-user learning loop" — Status: Accepted (foundation shipped v3.0.5). Its "Known limits / next layers" section explicitly says the automatic path that would populate lessons — "best-practice → skill promotion (codifying a repeated workflow … as a global skill)" and "retrieval-in-project" — are "designed, not yet built."
- The code confirms the feeder is absent. The only writer to
namespace='lessons' is scripts/record-lesson.mjs, and it is wired to zero hooks (grep -c record-lesson plugin/hooks/hooks.json → 0). It is a manual command a user must run by hand.
So a shipped, "Implemented" console (ADR-0018) presents an automatic lessons pipeline whose automatic feeder an "Accepted-but-next-layer" ADR (ADR-0017) says isn't built. The panel outran the wiring.
Reproduce
Fresh install → /ruvnet-brain:configure → Brain Activity card. On any project store that has only auto-captured telemetry:
totals: { memories: 669, lessons: 0 }
namespace breakdown: commands 379 · feedback 215 · session 67 · patterns 5 · cost-tracking 3
Nothing lands in lessons because nothing auto-distills into it; the SONA "Learnings" loop (ADR-0017, working — 4361 patterns) feeds ~/.claude-flow, a different store, not this namespace.
Why it matters
The console's own framing is "the model is the engine — this is the harness driving it" and the loop is described as "shipped as a plugin default … automatically." A headline metric that is structurally 0 for every user until they discover and run a manual script undercuts exactly that "it just works, and you can see it working" story.
The "distillation flow" visualization compounds this: roughly half its width is a real, populated particle-field for ~1,300 captured memories (with a working "see the memories by kind" interaction) and the other half is 5 generic decorative sparkle icons under "0 lessons — the keepers" — not real data standing in for something, but a fallback rendered because there is nothing to show. A deliberately animated, symmetric-looking two-sided visualization where one whole side is structurally guaranteed to render only decoration, for virtually every install, indefinitely.
Additional bug found in the same strip: a self-contradicting "receipt" line
console/activity.js:339:
'<div class="ab-receipt">last compact: <span class="mono">47 msgs → 6 lessons</span> · nothing lost<sup>illustrative — every other number is live</sup></div>'
Two separate problems in this one line:
- Markup bug: there's no space in the source between
lost and <sup>, and no CSS rule for .ab-receipt or sup anywhere in style.css adds one. It renders literally as "nothing lostillustrative — every other number is live" — run together, no separator.
- Content self-contradiction: this line asserts a specific, concrete-sounding claim — "47 msgs → 6 lessons" — styled with the
ab-receipt class, a term this app uses everywhere else specifically to mean real, verified data (mirrors ADR-013's own stated design law: "every number traces to a receipt... no estimated savings"). But this particular "receipt" is fake, disclosed only via a small, easily-missed superscript. It sits directly below the real stat tile reading "0 lessons — the keepers" — so the same card shows "0 lessons" in one place and "6 lessons" (fabricated) in the next, with only that inline caveat resolving the contradiction. A user who doesn't notice the tiny superscript reads two different lesson counts on the same screen.
Suggested fix (either is fine)
- Wire the automatic path ADR-0017 already scoped: capture →
ruflo memory distill → namespace='lessons' on a hook (e.g. SessionEnd, alongside learn-flush.mjs), or surface AgentDB Reflexion critiques (a failed episode + critique is already a retrievable lesson) into the lessons view. Then Lessons fills out of the box.
- Or make the empty state honest: the Lessons card should say "0 recorded — run
record-lesson to capture one; auto-distill is a planned next layer (ADR-0017)" instead of presenting a distillation flow that has no automatic input.
Environment
- ruvnet-brain v3.1.1 (also v3.1.0, where ADR-0018 shipped)
- ADRs:
docs/adr/0018-brain-activity-panel.md (Implemented), docs/adr/0017-recursive-per-user-learning-loop.md (Accepted, next-layers unbuilt)
- Node 24, macOS; rUv stack via npx
Summary
On a default install, the Brain Activity console's "Lessons" metric is structurally always 0, and there is no automatic way to make it non-zero. The panel ships the visualization of a
captured → distill → lessonspipeline whose automatic feeder is (per the plugin's own ADRs) not yet built. The only thing that writes tonamespace='lessons'is a manual, unhooked script.This is the plugin behaving contrary to its own stated design intent ("works out of the box"), not a crash. (Distinct from #15, which is the console failing to launch; this is about the console launching and then showing a metric that can never fill on its own.)
The drift (ADR vs code)
namespace='lessons'viaGET /api/activity(scripts/onboarding-console.mjs→gatherActivity()→SELECT COUNT(*) … WHERE namespace='lessons').namespace='lessons'isscripts/record-lesson.mjs, and it is wired to zero hooks (grep -c record-lesson plugin/hooks/hooks.json→ 0). It is a manual command a user must run by hand.So a shipped, "Implemented" console (ADR-0018) presents an automatic lessons pipeline whose automatic feeder an "Accepted-but-next-layer" ADR (ADR-0017) says isn't built. The panel outran the wiring.
Reproduce
Fresh install →
/ruvnet-brain:configure→ Brain Activity card. On any project store that has only auto-captured telemetry:Nothing lands in
lessonsbecause nothing auto-distills into it; the SONA "Learnings" loop (ADR-0017, working — 4361 patterns) feeds~/.claude-flow, a different store, not this namespace.Why it matters
The console's own framing is "the model is the engine — this is the harness driving it" and the loop is described as "shipped as a plugin default … automatically." A headline metric that is structurally 0 for every user until they discover and run a manual script undercuts exactly that "it just works, and you can see it working" story.
The "distillation flow" visualization compounds this: roughly half its width is a real, populated particle-field for ~1,300 captured memories (with a working "see the memories by kind" interaction) and the other half is 5 generic decorative sparkle icons under "0 lessons — the keepers" — not real data standing in for something, but a fallback rendered because there is nothing to show. A deliberately animated, symmetric-looking two-sided visualization where one whole side is structurally guaranteed to render only decoration, for virtually every install, indefinitely.
Additional bug found in the same strip: a self-contradicting "receipt" line
console/activity.js:339:'<div class="ab-receipt">last compact: <span class="mono">47 msgs → 6 lessons</span> · nothing lost<sup>illustrative — every other number is live</sup></div>'Two separate problems in this one line:
lostand<sup>, and no CSS rule for.ab-receiptorsupanywhere instyle.cssadds one. It renders literally as "nothing lostillustrative — every other number is live" — run together, no separator.ab-receiptclass, a term this app uses everywhere else specifically to mean real, verified data (mirrors ADR-013's own stated design law: "every number traces to a receipt... no estimated savings"). But this particular "receipt" is fake, disclosed only via a small, easily-missed superscript. It sits directly below the real stat tile reading "0 lessons — the keepers" — so the same card shows "0 lessons" in one place and "6 lessons" (fabricated) in the next, with only that inline caveat resolving the contradiction. A user who doesn't notice the tiny superscript reads two different lesson counts on the same screen.Suggested fix (either is fine)
ruflo memory distill→namespace='lessons'on a hook (e.g. SessionEnd, alongsidelearn-flush.mjs), or surface AgentDB Reflexion critiques (a failed episode + critique is already a retrievable lesson) into the lessons view. Then Lessons fills out of the box.record-lessonto capture one; auto-distill is a planned next layer (ADR-0017)" instead of presenting a distillation flow that has no automatic input.Environment
docs/adr/0018-brain-activity-panel.md(Implemented),docs/adr/0017-recursive-per-user-learning-loop.md(Accepted, next-layers unbuilt)