Skip to content

Brain Activity "Lessons" is structurally 0 out of the box — ADR-0018 ships the distillation panel, but ADR-0017 auto-feeder is unbuilt and record-lesson.mjs is manual/unhooked #16

Description

@sparkling

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.mjsgatherActivity()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:

  1. 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.
  2. 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)

  1. Wire the automatic path ADR-0017 already scoped: capture → ruflo memory distillnamespace='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.
  2. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions