| name | author-learnings |
| description | Read a delivered unit's real outcomes — the measure lens (baseline/target/realized), the validate verdicts and fix reports, the run lens (production actuals), and the delivered epic/slice status — against the current product model, and author the MEANING updates the delivery taught — a refined capability/functionality one_line, a raised nfr_needs level (monotonic-up), an earned status promotion, the grounding-doc sections the learning changed, and a new append-only decision per material learning. Each proposed change carries the outcome that justifies it and a confidence tier. Writes the rewritten grounding docs STRAIGHT to the live model (per-node docs only) and emits the spine meaning-field deltas and the decision records as structured data in an STM learn-manifest.yaml — never the shared model files (_spine.yaml, profile, decisions), never the tree skeleton, never a slice/epic entry. Generative artifact production for the /learn play. |
| version | 0.2.0 |
| user-invocable | false |
| model | opus |
| allowed-tools | Read, Write, Bash, Glob |
author-learnings
Turns a delivered unit's real outcomes into the updates that keep the living product
model honest. After a unit ships, the pipeline has already produced the evidence — the measure lens
(each metric's baseline → target → realized + proof), the validate verdicts and fix reports (which
gates truly cleared; a fix_required is a model-was-wrong signal), the run lens (production actuals
vs planned), and the delivered epic/slice status. This skill reads that evidence against what the
model claimed, and authors the precise meaning updates the delivery taught — never the tree
skeleton, never a slice or epic entry. It is the close of the loop the old enrich gate used to be.
Write discipline (ADR 026, direct-model-write)
This skill writes ONLY the per-node grounding docs (capability.md, functionality.md, and the
slice lens/{measure|run|quality}.md) — each its own file — straight to the live model under
<product_base>/product-os/…. It NEVER writes any shared model file: not _spine.yaml, not the
profile block, not a decisions/ record. Every shared-file mutation is emitted as structured
data in the STM learn-manifest.yaml — the spine meaning-field deltas in changes: and the full
decision records in decisions: — for /learn's deterministic keyed persist (persist_learn.py) to
apply in place after the human checkpoint. /learn runs the shape linter and the content-quality eval
over the live docs, then the scoped-write guard over the full delta, before the checkpoint; nothing
is committed until the gate resolves.
What it produces
The rewritten grounding docs at their live paths, plus a human-reviewable learn-manifest.yaml
listing every proposed change with its outcome citation and a confidence tier. Each rewritten
grounding doc conforms to its locked template (standards/schemas/product-os/grounding/…) and must
clear the linter (shape) and the content-quality eval (meaning). The manifest is the machine-checkable
contract validate_learn.py (pre-checkpoint) and persist_learn.py (the keyed persist) read — every
change carries an outcome, every nfr move is monotonic-up, every status promotion is earned, every
decision is a new accepted record carrying all its own fields.
Inputs
| Field | Required | Description |
|---|
issue | yes | The delivered unit's issue (display + decision provenance). |
unit | yes | The resolved unit from check_ready_unit — {issue, epics[], slices[]}. |
outcomes | yes | The outcome-evidence paths from check_ready_unit — {measure_lenses[], run_lenses[], validate[], delivered_epics[], delivered_slices[]}. Read these; they are what the delivery actually showed. |
spine | yes | Path to the live spine (product-os/_spine.yaml) — read-only, for the current claims and the node ids. |
product_base | yes | Product model root — to READ the affected nodes' grounding docs and resolve a prior decision a learning may supersede, and to WRITE the rewritten per-node grounding docs in place under product-os/…. |
manifest_path | yes | Output path under STM for the learn-manifest.yaml (the proposed-change contract — spine deltas + decision records as structured data). |
Procedure
Reasoning (what the outcome actually taught, and which claim it confirms or refutes) is yours.
Outcome-grounding, the allowlist, and template/eval conformance are non-negotiable.
- Read the outcomes. Load each
measure.md (did the metric move from baseline to target, with
real proof?), each run.md (what production actually needed vs planned), every validate verdict +
fix report (which gates cleared; read a fix_required as the model being wrong about a boundary or
rule), and the delivered epic/slice status. Note, per signal, what it teaches.
- Read the claims. Load the spine and the affected nodes' grounding docs —
capability.md
(benefit hypothesis, boundaries), functionality.md (acceptance, rules), and the slice's lens docs
— to see what the model claimed before delivery.
- Diff reality against the claim. For each gap the evidence proves, decide the smallest honest
update on the allowed surface only:
- spine
one_line — a capability/functionality descriptor delivery showed too thin or wrong;
- spine
nfr_needs level — raise (monotonic-up only) when production proved a higher level needed;
a raised profile nfr is a box-move and MUST carry a decision;
- spine
status — an EARNED promotion only (proposed → active when proven; to validated/delivered
where the outcome earns it). NEVER advance a fix_required — that becomes a grounding refinement;
- grounding sections — refresh only what the learning changed: a capability's benefit hypothesis
(confirmed/refuted) and boundaries, a functionality's acceptance and rules &
behavior, and the measure / run / quality lens docs with the real results;
- a decision — every material learning becomes a NEW append-only decision record.
- Ground every change. Each proposed change, doc rewrite, and decision carries a non-empty
outcome citation naming the signal that justifies it (e.g. "measure: render p95 1.9s vs target
<2s — confirmed"; "validate fix_required: privacy gate caught raw-log leak — boundary missed").
A change with no outcome is dropped, not guessed.
- Tier by confidence.
high for a change a single clear outcome proves; low for an inferred or
partial signal — /learn surfaces low-confidence changes one by one at the checkpoint.
- Write the live docs; emit the shared-file deltas as manifest data. Write each rewritten
grounding doc straight to its live path under
<product_base>/product-os/… (per template) —
these are the only files this skill writes. Emit every spine meaning-field change in the manifest's
block and every new decision as a full record in the manifest's block
(carrying , , and — builds the
record from these). Write to . NEVER write , the
block, or a record — those are the keyed persist's job.
Output
The rewritten grounding docs, at their live paths:
<product_base>/product-os/{domain}/…
capability.md | functionality.md # a rewritten grounding doc (only changed ones)
slices/{slice}/lens/{measure|run|quality}.md # a rewritten lens doc (only changed ones)
and the STM manifest at manifest_path:
learn-manifest.yaml — field-for-field what validate_learn.py and persist_learn.py read:
changes:
- node_ref: cap-source-coverage
node_kind: capability
field: nfr_needs
dimension: performance
from: medium
to: high
outcome: "run.md: production p95 480ms vs target 150ms — performance underscoped"
confidence: high
docs:
- rel: product-os/token-dash/slices/slice-trusted-coverage/lens/measure.md
outcome: "measure: coverage 100% realized; render p95 1.9s vs <2s — both confirmed"
decisions:
- id: dec-learn-source-coverage-perf-high
node_ref: cap-source-coverage
level:
persist_learn.py builds each decisions/<id>.yaml record from the manifest decisions: entry
(stamping decided_by/date/status: accepted), so title, reason, alternatives, and level
MUST be present in the manifest — there is no draft decision file to fall back on.
Return the contract with the live doc paths and the learn-manifest.yaml path — paths, never
inline content.
Rules
- Outcome-grounded. Every proposed change, doc rewrite, and decision carries an
outcome citation
that traces to a real signal (measure / validate / run / delivered status). No outcome → drop it.
- Meaning only, never the skeleton. Propose changes ONLY to
one_line, nfr_needs level,
earned status, the listed grounding sections, and new decisions. NEVER rename or re-parent a
domain/capability/functionality, NEVER rewrite a slice or epic entry, NEVER edit an accepted
decision in place — those are rejected by validate_learn.py / the keyed persist.
- Monotonic-up. An
nfr_needs level may only rise; a raised profile nfr (a box-move) carries a
decision.
- Earned status. Promote a status only to an earned state the outcome proves; never advance a
fix_required — carry it as a grounding refinement instead.
- Append-only decisions. Every material learning is a NEW decision (status
accepted); a learning
that overturns a prior decision names what it supersedes, never edits the old record.
- Template-true. Each rewritten grounding doc conforms to its locked template and must clear the
linter + the content eval — every section self-explaining, never thinned.
- Docs to live, shared-file deltas as manifest data. Write ONLY the per-node grounding docs to
their live paths; emit the spine meaning-field changes and the full decision records as structured
data in
learn-manifest.yaml. Never write _spine.yaml, the profile block, or a decisions/
record — the keyed persist owns those.