Learn
Skills that improve from their own runs.
Skills are static; agents repeat the same mistakes run after run. Plenty of tooling improves a skill before it ships, and agents increasingly carry runtime memory, but nothing feeds what happens during real runs back into the skill itself.
steer learn closes that gap. Its design follows the pattern shared by the
memory systems that hold up in practice: automatic capture, deterministic
curation, gated application.
When to use it
- The skill runs repeatedly and reality corrects it: endpoints differ by account, a preference surfaces, a workaround becomes the rule.
- The repo-health example ships the generated Learning section: capture at the moment of correction, digest at run start, outcome recorded before finishing.
- Skip it for one-shot utilities. Lessons pay for themselves through repetition; a skill nobody reruns has nothing to learn from.
The loop
capture ──▶ curate ──▶ apply ──▶ promote
(agent) (steer, (agent (human,
no LLM) reads) into the skill)Commands below use the installed CLI's spelling. A generated SKILL.md
spells the same commands python3 scripts/steer.py learn ... through the
skill's bundled runtime, and the digest's own hints then use that
spelling too.
-
Capture: the SKILL.md tells the agent to record lessons the moment they happen (a user correction, a failure that then worked another way):
steer learn note "Skip vendored and generated files in the diff" \ --kind correction --context "large diffs" --evidence "run 2026-06-11" -
Apply: at the start of every run the agent reads the bounded digest:
steer learn show # ranked digest, ~2000 chars (--budget N to change)## Lessons from previous runs (pr-review) - [3] ★ Skip vendored and generated files in the diff (when: large diffs) - [1] Cite file and line in every finding [from v0.1.0] - …and 4 more: steer learn review (helped? steer learn confirm <id> · wrong? steer learn dispute <id>) -
Curate: deterministic, no LLM inside the framework (the agent is the reflector; steer is the non-LLM curator):
- duplicate notes confirm the existing lesson instead of duplicating
confirm/disputeadjust a helpful/harmful score; a lesson disputed more than confirmed auto-archives (reversible by a later confirm)- a hard cap (200 active) evicts the weakest first;
pinexempts a lesson;forget <id>archives one on the spot - lessons from an older skill version are marked
[from vX]in the digest
-
Promote: the human-gated path into the shipped skill:
steer learn review # scores, kinds, status (--all includes archived) steer learn promote 3 # → appends to <skill>/learnings.mdlearnings.mdis plain markdown with provenance comments (<!-- steer:lesson 3 2026-06-11 -->), so the diff is reviewable and it ships with the skill like any other file.steer validatewarns when it grows past 150 lines and notes when SKILL.md never references it.
Where lessons live
~/.steer/skills/<name>/ # outside the skill dir, survives reinstalls
├── lessons.db # source of truth (SQLite)
│ lessons: id, text, normalized, kind, context, evidence,
│ skill_version, workspace,
│ status (active|pinned|archived|promoted),
│ confirmations, contradictions, created_at, last_seen_at
│ runs: id, status (ok|failed), note, skill_version, workspace,
│ created_at
└── LEARNINGS.md # auto-maintained readable mirror
<skill-dir>/learnings.md # promoted lessons only; ships with the skillLEARNINGS.md is regenerated after every mutation, so humans and agents
can read or grep the full picture without the CLI:
# Learnings: pr-review
Updated: 2026-06-11 · runs: 14 (86% ok) · 1 pinned · 6 active · 2 promoted · 3 archived
## Active
- [3] Skip vendored and generated files in the diff (correction, +4, when: large diffs, last: 2026-06-11)The DB stays the source of truth (counters need transactions); the mirror is
the place you look. Lessons live outside the skill directory because
skill dirs get zipped, uploaded, and overwritten by upstream updates;
learned data must survive reinstalls and must never ship by accident. This
also resolves the conflict between self-updating skills and local learning:
updates replace the skill, lessons persist, and promote is the deliberate
channel from one to the other.
Auto-learning
The loop above still asks the agent to remember to capture. Auto-learning removes that dependency using a Claude Code skill-scoped Stop hook (active only while the skill is active):
steer new my-skill --auto-learn # implies --with learngenerates:
hooks:
Stop:
- hooks:
- type: command
command: steer learn reflect --skill my-skill
timeout: 15When the agent tries to finish, steer learn reflect deterministically
scans the session transcript (steer stays LLM-free; the agent is the
reflector) for:
- user corrections ("no, …", "that's wrong", "use X instead", "I meant …")
- failed tool calls (
is_error: true) - whether a
learn note/learn runcommand already ran (it counts only commands actually executed, not the skill's own instructions quoted in the transcript)
If there are signals and nothing was captured, it blocks the stop once,
with exact instructions: distill each durable lesson, record the run
outcome, then finish. It never loops (honors stop_hook_active), never
fires when capture already happened, and never breaks the host on an
internal error (always exits 0). Debug it with
steer learn reflect --scan-only --transcript <file>; tune the trigger
threshold with --min-signals N.
Portability: the hook is a Claude Code extension, so steer validate flags
it with a PORTABILITY warning by design. On other agents the skill degrades
gracefully to the instruction-driven loop in its Learning section.
The hook command runs the installed CLI, not the bundle: auto-learn is
the one steer feature that needs steer on the consumer's PATH, and the
generated SKILL.md says so next to it.
Guardrails
- No secrets in lessons: credential-shaped notes (
sk-…,key=…,ghp_…) are refused with a pointer tosteer secrets, since lessons can end up in shipped files. - Atomic lessons: notes over 500 chars are refused ("one rule per note").
- Bounded injection:
showtruncates at a char budget; everything else is behindreview(progressive disclosure, like a memory index). - Bad lessons die: dispute-beyond-confirmation auto-archives, which bounds the blast radius without an LLM judgment call.
Run outcomes
steer learn run ok # or: failed --note "timeout on step 3"
steer learn stats
# pr-review: 14 runs recorded (12 ok, 2 failed, success 86%)
# lessons: 6 active, 1 pinned, 2 promoted, 3 archived
# last run: 2026-07-03T18:20:11+00:00(stats prints only the statuses that exist; add --json, as on every
learn subcommand, for the machine-readable version.)
This answers the two questions a skill author actually has: is it working, and what has it learned.
Python
from steer import Learnings
with Learnings("pr-review") as learn:
learn.note("Use bulk endpoints over per-item calls",
kind="preference", context="more than 50 items")
print(learn.digest(budget=1500))
learn.confirm(3)
learn.promote(3, "./pr-review")
learn.stats()