Learning loop: consolidate, absorb, refine
The engine's session-side pipeline (spec: docs/superpowers/specs/2026-08-01-learning-engine-design.md
§5–§10). Capture happens automatically (plugin hooks); this skill turns what
was captured into observations, and observations into reviewable skill-edit
PRs. The human gate is git merge.
When to use this skill
- Promoting queue flags and completing dated learnings entries ("consolidate",
a Stop-hook nudge, end of a work block).
- Drafting skill edits from ready observations ("absorb", "update my skills",
"run the loop"): output is always a PR branch, never a direct edit.
- Catalog hygiene passes ("refine the skills"): prune/dedup/staleness through
the same delta pipeline, report-first.
- Contested or structural edits ("experiment", "A/B this edit", "try
competing fixes"): description rewrites, restructures, competing fixes
where the right answer isn't obvious enough for a single draft.
- Scheduled example verification ("deep verify", "verify the examples"):
promoting pinned examples/references content from status: unverified
once its evals.yaml fixture passes.
- Loop health ("learning loop status"): queue depth, unabsorbed backlog,
eval-suite size, ledger totals.
- For distilling external repos, use the mining skill; for authoring a new
skill from scratch, skill-author.
Key directives
- Delegation posture: embed; the workflows live here; the deterministic
tools live at scripts/engine/ (apply_deltas.py, run_trigger_evals.py,
ledger.py, mine_transcripts.py, skill_metrics.py, observations.py,
placement.py, run_variants.py, deep_verify.py, run_task_checks.py) and
in the plugin hooks (recall).
- Scripts hold the pen. LLM roles draft
deltas and diagnose; apply_deltas.py applies them (snapshot, bump,
changelog, sidecars). Never hand-edit a skill during absorb; never bypass
the script's refusals; a refusal is a design signal, not an obstacle.
- Consolidation never touches skills/ content.
Its write surface is learnings/, learnings/observations/, and the
evidence/evals sidecars; that boundary is what makes it autonomous-safe.
- Absorb consumes status: ready only. The
ready bar (proof ≥ 2 | user-correction | three-part evidence | external
official) is enforced by the observations lint; do not absorb around it.
- Merge is the gate. Every absorb/refine
run ends in a PR with the evidence table; no agent merges to main
skills/**. Mid-build sessions capture; they never edit skills directly.
- Dedup against everything seen ,
including absorbed and rejected observations, or judged-rejected findings
reappear forever.
- One self-check round on consolidator
and absorber output: re-read the draft against the source transcript
window for misattribution, missed dead-ends, wrong anchors, before writing.
- Variants are deltas, never rewrites.
Full-file candidate rewrites are forbidden (context collapse); a variant
that apply_deltas refuses is a broken candidate, not a contender.
Quick start
Consolidate (autonomous-safe), then absorb to a PR:
# status: what's pending?
wc -l .robium/queue.jsonl # flags
grep -rc "status: ready" learnings/observations/*.md
uv run scripts/engine/skill_metrics.py # catalog health
# consolidate: promote flags + complete entries + merge into observations
# (LLM workflow; see Decision guidance; writes learnings/ + sidecars only)
# absorb: draft deltas from ready observations, then:
uv run scripts/engine/apply_deltas.py deltas.yaml --dry-run # review the report
git checkout -b loop/absorb-$(date +%F)
uv run scripts/engine/apply_deltas.py deltas.yaml
uv run skills/skill-author/scripts/validate_skills.py
uv run scripts/engine/run_trigger_evals.py --skills <touched...> \
--flip-gate-baseline archive/<skill>/<old-version> --flip-skill <skill>
gh pr create --title "loop: absorb <topic>" --body-file report.md
Decision guidance
Consolidate (spec §6). Inputs: queue flags, miner output
(scripts/engine/mine_transcripts.py over .robium/transcripts/), unconsolidated
learnings entries; each resolved to its archived-transcript window and read in
full context. Steps: promote flags that clear the noise bar into schema-v2
entries (verbatim text preserved); complete hand-written entries (evidence,
skill tags, recurrence); merge into observations per the schema README's
merge-on-same-finding and evolve-don't-overwrite rules; increment ledgers
(scripts/engine/ledger.py) with sources; harvest eval cases (no-skill-fired →
triggers.positive of the right skill, misfires → triggers.negative); draft
the end-of-block retro for human sign-off; attribute successes (green blocks
credit helpful to the anchors whose guidance shaped the actions; best-effort,
neutral by default). Then the self-check round.
Absorb (spec §7.1). On ready observations: branch loop/absorb-YYYY-MM-DD;
draft deltas feedback-conditioned (current SKILL.md + observation's symptom/
fix/dead-ends + smallest-edit directive + placement rule; run
scripts/engine/placement.py per finding); apply via apply_deltas.py; verify
(validator → trigger evals → flip gate); scoped dup check over touched skills;
PR with the evidence table (per edit: skill, anchor, op, observation link,
sources, eval results). See the delta-format reference for op semantics.
Refine: the five passes (see the refine-passes reference) re-armed on
ledgers: prune harmful>0 ∧ helpful=0 first; dedup seeds from
skill_metrics.py --dupes; staleness (90-day windows) unchanged; usage reads
retro lines; growth review reads the archive. Output: retire/move/annotate
deltas through the same pipeline → PR. Scoped refine after every absorb; full
refine ~monthly.
Experiment (spec §9). Trigger: contested or structural edits where the
right answer isn't obvious enough for a single draft: description
rewrites, restructures, competing fixes. Flow: draft 2–3 feedback-
conditioned DELTA variants (never full-file rewrites) against the
observation, each its own deltas file, alongside the implicit baseline;
scripts/engine/run_variants.py builds a whole-catalog scratch copy per
candidate, applies its deltas through apply_deltas, and scores every
variant deterministically: trigger pass-rate, flip-gate count, task
pass-rate (only with --with-tasks, since task checks are real subprocess
runs and never run silently), and token count; unless --no-llm, a blind
judge (shelling to claude -p with shuffled candidate labels) offers a
content pick that degrades to "skipped" on any failure rather than
fabricate one. A candidate apply_deltas refuses never reaches scoring;
it is reported as a build failure, a broken candidate, not a contender
(the variants-are-deltas directive). Fitness ordering: trigger pass-rate
desc, then task pass-rate desc (an unrun None never discriminates either
side), then token count asc; leanness is the tiebreaker. Post the
printed score table straight into the PR body; the "recommendation:"
line is the engine's rank only; the human picks by merging the winning
variant's deltas through the normal absorb path, never by the tool
selecting unattended. --archive-losers then copies every non-winning
variant (skill dir, deltas file, scores) to
archive//variants///: branch points kept for later,
not garbage. Worked walkthrough and score-table reading: the
experiment-recipes reference.
Deep-verify (spec §8 layer 4). A scheduled lane, not a per-PR gate: pinned
status: unverified examples/references get promoted mechanically when the
skill's evals.yaml carries a tasks: fixture whose example: field names the
file (skill-relative path). scripts/engine/deep_verify.py --inventory
lists every unverified file across the catalog and whether it has a
matching fixture (an unfixtured file is a coverage gap to close, not a
failure); --run --skills <names...> actually runs each matched fixture
(via the same run_task_checks primitive the task-check runner uses) and,
on PASS, emits an annotate op flipping the marker to
"status: verified (deep-verify: )"; nothing is written to
skills/** directly, the emitted learnings/deltas/-deep-verify.yaml
file goes through the normal apply_deltas review like any other delta. A
FAIL is a finding for the printed report, not a fatal error: the CLI
always exits 0, because a stale example is something to fix later, not a
gate to block on. Run it on a schedule (per-build or monthly, alongside
refine), not as part of every absorb.
Recall runs without invocation (UserPromptSubmit hook): ready
observations matching the prompt inject as [robium-recall] context, citing
ids. A wrong recall is capture signal: name the id and correct it; the
consolidator counts it harmful.
Platform gotchas
- apply_deltas refuses an op whose archive dir already exists: that means a
prior run bumped without merging. Rebase/merge the pending PR first.
- The trigger-eval judge shells to the claude CLI; offline or in CI without
a key, pass --no-llm for the deterministic fallback (results are then
keyword-based: good for gating, weaker for judging close calls).
- evidence.yaml and evals.yaml are engine-written; hand-edits will be
overwritten and break increment audit trails.
Customization
- User tier (Phase 4): same modes with observations under .robium/ and the
absorb destination an overlay under .claude/skills/; the workflows are
path-parameterized, nothing else changes.
- Eval-case harvest thresholds and the recall budget are constants in the
hook scripts; tune per install, not per session.
References
references/delta-format.md: op semantics, deltas.yaml schema, refusal rules.
references/promotion-bar.md: queue→facts→observations promotion criteria.
references/refine-passes.md: the five hygiene passes, evidence-armed.
references/learnings-loop.md: the pre-engine hardening process (history + the manual fallback).
references/experiment-recipes.md: the variant A/B walkthrough, the
contrastive-rollouts recipe, and when not to experiment.
- Engine tools (repo root): scripts/engine/ (apply_deltas.py,
run_trigger_evals.py, ledger.py, mine_transcripts.py, skill_metrics.py,
observations.py, placement.py, run_variants.py, deep_verify.py,
run_task_checks.py).
- Spec: docs/superpowers/specs/2026-08-01-learning-engine-design.md §5–§10.
Changelog
- 0.2.1 (2026-08-03): style pass; removed em dashes throughout (no content changes).
- 0.2.0 (2026-08-02): experiment + deep-verify modes land (run_variants, deep_verify, task checks); experiment-recipes reference incl. contrastive-rollouts recipe (Phase 3, spec §9).
- 0.1.1 (2026-08-02): delta-format documents the learnings/deltas/ location convention (#81)
- 0.1.0 (2026-08-02): initial skill, consolidate/absorb/refine/status modes
over the Phase 2b delta pipeline; absorbs skill-updater's promotion bar and
skill-refiner's five passes as references (learning-engine Phase 2b, §13).
1---2name: learning-loop-53description: The session-side surface of robium's learning engine: consolidate captured flags and learnings into evidence-counted observations, absorb ready observations into anchor-targeted skill-edit PRs via the deterministic delta pipeline, refine (prune/dedup/staleness) through the same pipeline, run blind variant A/B on contested edits ('experiment', 'A/B this edit', 'try competing fixes'), deep-verify unverified examples in pinned fixtures ('deep verify', 'verify the examples'), and report loop health. Use when: 'consolidate', 'absorb', 'run the loop', 'update my skills', 'absorb these learnings', 'refine the skills', 'learning loop status', end-of-block retros, promoting .robium/queue.jsonl flags, or drafting an absorb/refine PR. Everything before git merge may run autonomously; nothing lands on main skills/** without a human merge. Not for: mining external repos (mining), fresh skill authoring or the quality bar (skill-author), building robot applications (architect).4---56# Learning loop: consolidate, absorb, refine78The engine's session-side pipeline (spec: docs/superpowers/specs/2026-08-01-learning-engine-design.md9§5–§10). Capture happens automatically (plugin hooks); this skill turns what10was captured into observations, and observations into reviewable skill-edit11PRs. The human gate is git merge.1213## When to use this skill1415- Promoting queue flags and completing dated learnings entries ("consolidate",16 a Stop-hook nudge, end of a work block).17- Drafting skill edits from ready observations ("absorb", "update my skills",18 "run the loop"): output is always a PR branch, never a direct edit.19- Catalog hygiene passes ("refine the skills"): prune/dedup/staleness through20 the same delta pipeline, report-first.21- Contested or structural edits ("experiment", "A/B this edit", "try22 competing fixes"): description rewrites, restructures, competing fixes23 where the right answer isn't obvious enough for a single draft.24- Scheduled example verification ("deep verify", "verify the examples"):25 promoting pinned examples/references content from status: unverified26 once its evals.yaml fixture passes.27- Loop health ("learning loop status"): queue depth, unabsorbed backlog,28 eval-suite size, ledger totals.29- For distilling external repos, use the mining skill; for authoring a new30 skill from scratch, skill-author.3132## Key directives3334- Delegation posture: **embed**; the workflows live here; the deterministic35 tools live at scripts/engine/ (apply_deltas.py, run_trigger_evals.py,36 ledger.py, mine_transcripts.py, skill_metrics.py, observations.py,37 placement.py, run_variants.py, deep_verify.py, run_task_checks.py) and38 in the plugin hooks (recall).39- **Scripts hold the pen.** <!-- id: scripts-hold-the-pen --> LLM roles draft40 deltas and diagnose; apply_deltas.py applies them (snapshot, bump,41 changelog, sidecars). Never hand-edit a skill during absorb; never bypass42 the script's refusals; a refusal is a design signal, not an obstacle.43- **Consolidation never touches skills/ content.** <!-- id: consolidate-write-surface -->44 Its write surface is learnings/, learnings/observations/, and the45 evidence/evals sidecars; that boundary is what makes it autonomous-safe.46- **Absorb consumes status: ready only.** <!-- id: absorb-ready-only --> The47 ready bar (proof ≥ 2 | user-correction | three-part evidence | external48 official) is enforced by the observations lint; do not absorb around it.49- **Merge is the gate.** <!-- id: merge-is-the-gate --> Every absorb/refine50 run ends in a PR with the evidence table; no agent merges to main51 skills/**. Mid-build sessions capture; they never edit skills directly.52- **Dedup against everything seen** <!-- id: dedup-against-rejected -->,53 including absorbed and rejected observations, or judged-rejected findings54 reappear forever.55- **One self-check round** <!-- id: one-self-check-round --> on consolidator56 and absorber output: re-read the draft against the source transcript57 window for misattribution, missed dead-ends, wrong anchors, before writing.58- **Variants are deltas, never rewrites.** <!-- id: variants-are-deltas -->59 Full-file candidate rewrites are forbidden (context collapse); a variant60 that apply_deltas refuses is a broken candidate, not a contender.6162## Quick start6364Consolidate (autonomous-safe), then absorb to a PR:6566```bash67# status: what's pending?68wc -l .robium/queue.jsonl # flags69grep -rc "status: ready" learnings/observations/*.md70uv run scripts/engine/skill_metrics.py # catalog health7172# consolidate: promote flags + complete entries + merge into observations73# (LLM workflow; see Decision guidance; writes learnings/ + sidecars only)7475# absorb: draft deltas from ready observations, then:76uv run scripts/engine/apply_deltas.py deltas.yaml --dry-run # review the report77git checkout -b loop/absorb-$(date +%F)78uv run scripts/engine/apply_deltas.py deltas.yaml79uv run skills/skill-author/scripts/validate_skills.py80uv run scripts/engine/run_trigger_evals.py --skills <touched...> \81 --flip-gate-baseline archive/<skill>/<old-version> --flip-skill <skill>82gh pr create --title "loop: absorb <topic>" --body-file report.md83```8485## Decision guidance8687**Consolidate** (spec §6). Inputs: queue flags, miner output88(scripts/engine/mine_transcripts.py over .robium/transcripts/), unconsolidated89learnings entries; each resolved to its archived-transcript window and read in90full context. Steps: promote flags that clear the noise bar into schema-v291entries (verbatim text preserved); complete hand-written entries (evidence,92skill tags, recurrence); merge into observations per the schema README's93merge-on-same-finding and evolve-don't-overwrite rules; increment ledgers94(scripts/engine/ledger.py) with sources; harvest eval cases (no-skill-fired →95triggers.positive of the right skill, misfires → triggers.negative); draft96the end-of-block retro for human sign-off; attribute successes (green blocks97credit helpful to the anchors whose guidance shaped the actions; best-effort,98neutral by default). Then the self-check round.99100**Absorb** (spec §7.1). On ready observations: branch loop/absorb-YYYY-MM-DD;101draft deltas feedback-conditioned (current SKILL.md + observation's symptom/102fix/dead-ends + smallest-edit directive + placement rule; run103scripts/engine/placement.py per finding); apply via apply_deltas.py; verify104(validator → trigger evals → flip gate); scoped dup check over touched skills;105PR with the evidence table (per edit: skill, anchor, op, observation link,106sources, eval results). See the delta-format reference for op semantics.107108**Refine**: the five passes (see the refine-passes reference) re-armed on109ledgers: prune harmful>0 ∧ helpful=0 first; dedup seeds from110skill_metrics.py --dupes; staleness (90-day windows) unchanged; usage reads111retro lines; growth review reads the archive. Output: retire/move/annotate112deltas through the same pipeline → PR. Scoped refine after every absorb; full113refine ~monthly.114115**Experiment** (spec §9). Trigger: contested or structural edits where the116right answer isn't obvious enough for a single draft: description117rewrites, restructures, competing fixes. Flow: draft 2–3 feedback-118conditioned DELTA variants (never full-file rewrites) against the119observation, each its own deltas file, alongside the implicit baseline;120scripts/engine/run_variants.py builds a whole-catalog scratch copy per121candidate, applies its deltas through apply_deltas, and scores every122variant deterministically: trigger pass-rate, flip-gate count, task123pass-rate (only with --with-tasks, since task checks are real subprocess124runs and never run silently), and token count; unless --no-llm, a blind125judge (shelling to claude -p with shuffled candidate labels) offers a126content pick that degrades to "skipped" on any failure rather than127fabricate one. A candidate apply_deltas refuses never reaches scoring;128it is reported as a build failure, a broken candidate, not a contender129(the variants-are-deltas directive). Fitness ordering: trigger pass-rate130desc, then task pass-rate desc (an unrun None never discriminates either131side), then token count asc; leanness is the tiebreaker. Post the132printed score table straight into the PR body; the "recommendation:"133line is the engine's rank only; the human picks by merging the winning134variant's deltas through the normal absorb path, never by the tool135selecting unattended. --archive-losers then copies every non-winning136variant (skill dir, deltas file, scores) to137archive/<skill>/variants/<date>/<name>/: branch points kept for later,138not garbage. Worked walkthrough and score-table reading: the139experiment-recipes reference.140141**Deep-verify** (spec §8 layer 4). A scheduled lane, not a per-PR gate: pinned142status: unverified examples/references get promoted mechanically when the143skill's evals.yaml carries a tasks: fixture whose example: field names the144file (skill-relative path). scripts/engine/deep_verify.py --inventory145lists every unverified file across the catalog and whether it has a146matching fixture (an unfixtured file is a coverage gap to close, not a147failure); --run --skills <names...> actually runs each matched fixture148(via the same run_task_checks primitive the task-check runner uses) and,149on PASS, emits an annotate op flipping the marker to150"status: verified <date> (deep-verify: <task>)"; nothing is written to151skills/** directly, the emitted learnings/deltas/<date>-deep-verify.yaml152file goes through the normal apply_deltas review like any other delta. A153FAIL is a finding for the printed report, not a fatal error: the CLI154always exits 0, because a stale example is something to fix later, not a155gate to block on. Run it on a schedule (per-build or monthly, alongside156refine), not as part of every absorb.157158**Recall** runs without invocation (UserPromptSubmit hook): ready159observations matching the prompt inject as [robium-recall] context, citing160ids. A wrong recall is capture signal: name the id and correct it; the161consolidator counts it harmful.162163## Platform gotchas164165- apply_deltas refuses an op whose archive dir already exists: that means a166 prior run bumped without merging. Rebase/merge the pending PR first.167- The trigger-eval judge shells to the claude CLI; offline or in CI without168 a key, pass --no-llm for the deterministic fallback (results are then169 keyword-based: good for gating, weaker for judging close calls).170- evidence.yaml and evals.yaml are engine-written; hand-edits will be171 overwritten and break increment audit trails.172173## Customization174175- User tier (Phase 4): same modes with observations under .robium/ and the176 absorb destination an overlay under .claude/skills/; the workflows are177 path-parameterized, nothing else changes.178- Eval-case harvest thresholds and the recall budget are constants in the179 hook scripts; tune per install, not per session.180181## References182183- `references/delta-format.md`: op semantics, deltas.yaml schema, refusal rules.184- `references/promotion-bar.md`: queue→facts→observations promotion criteria.185- `references/refine-passes.md`: the five hygiene passes, evidence-armed.186- `references/learnings-loop.md`: the pre-engine hardening process (history + the manual fallback).187- `references/experiment-recipes.md`: the variant A/B walkthrough, the188 contrastive-rollouts recipe, and when not to experiment.189- Engine tools (repo root): scripts/engine/ (apply_deltas.py,190 run_trigger_evals.py, ledger.py, mine_transcripts.py, skill_metrics.py,191 observations.py, placement.py, run_variants.py, deep_verify.py,192 run_task_checks.py).193- Spec: docs/superpowers/specs/2026-08-01-learning-engine-design.md §5–§10.194195## Changelog196197- 0.2.1 (2026-08-03): style pass; removed em dashes throughout (no content changes).198- 0.2.0 (2026-08-02): experiment + deep-verify modes land (run_variants, deep_verify, task checks); experiment-recipes reference incl. contrastive-rollouts recipe (Phase 3, spec §9).199- 0.1.1 (2026-08-02): delta-format documents the learnings/deltas/ location convention (#81)200- 0.1.0 (2026-08-02): initial skill, consolidate/absorb/refine/status modes201 over the Phase 2b delta pipeline; absorbs skill-updater's promotion bar and202 skill-refiner's five passes as references (learning-engine Phase 2b, §13).