Lightweight Retrospective
Turn what a run learned into something the next run reads. Observations are written during the work and gathered here; the durable ones graduate to the library and the spent ones stop being offered.
Everything this skill records is a typed tool call. It composes no markdown, allocates no numbers, names no files, and never reads back what it or another skill wrote.
Follow the shared conventions in dpm/shared/skill-conventions.md — read that file at startup.
This skill uses Gate Presentation, Conversational Output, Written Deliverable Length,
Cross-References and Artifact Publishing from it.
Input
Four modes, and they never run together. $ARGUMENTS selects one:
learn— promote a durable lesson into the library and retire it at the source.retire— retire a spent lesson, which has no library entry to go to.triage— waive the completed epics that finished clean, so nothing goes on asking them for a retro they do not need.- anything else, or nothing — synthesise. This is the default and everything under Process.
The four are separate because their terminus differs, not their subject. learn retires a
lesson because it graduated; retire retires one because it is spent; triage records that there
was never anything to retire; synthesis creates the grouping the other three act on. A run does one.
For synthesis, resolve the subject:
- If
$ARGUMENTSnames a document — a ULID, or a human reference as another skill printed it —mcp__plugin_dpm_dpm__read_epicormcp__plugin_dpm_dpm__read_quickon it. A reference goes throughmcp__plugin_dpm_dpm__resolve_referencefirst, which returns the row it names or refuses; a ULID is already the id and needs no resolving. - Otherwise
mcp__plugin_dpm_dpm__list_epicandmcp__plugin_dpm_dpm__list_quick, and offer them withAskUserQuestion, showing each title and status. Ask which one; never take the most recent. - If there are none, say so and stop.
An epic and a quick record are the same input here. Both hold observations written against
their work, and an observation carries where it came from in story_id rather than in the shape of
the text around it — so there is no source kind to detect and no second parse for the other one.
Startup
Session
Follow the shared Session Startup procedure with skill: 'dpm:retro', putting the mode and the
step about to start in phase.
The mode belongs in state, because a run resumed into the wrong one would retire what it was
asked to synthesise.
Library
Follow the shared Library Check procedure with scope keyword retro. This is also the corpus
learn writes into, so knowing what is already there is what stops a second entry saying the same
thing.
Process
Synthesis. The three other modes are below.
Step 1: Gather the observations
mcp__plugin_dpm_dpm__list_story on the epic, then mcp__plugin_dpm_dpm__list_observation with each story_id and
include_body. On a quick record, mcp__plugin_dpm_dpm__list_observation scoped to its quick_id, again with
include_body. Step 2 writes what a group of observations says that no one of them says; over rows
whose text was withheld it is a synthesis of their categories.
The two scopes are the two places work happens, and neither is the retro. A do run writes an
observation against the story it was working; a quick run writes one against the quick record. Both
arrive here ungathered, which is the state this step exists to end.
A retired observation is not returned, and that is the tool's doing rather than this skill's.
The list omits them unless a caller passes include_retired, which synthesis never does — so there
is no marker to look for in the text and no rule here to remember. Pass it only when the question is
the audit trail rather than the work.
Then each one's categories: mcp__plugin_dpm_dpm__list_observation_category per observation, resolved against
mcp__plugin_dpm_dpm__list_taxonomy in the observation domain, called with a limit above the seeded count
so a project that added terms of its own does not lose them to the default page — a category that
falls off the end reads here as an observation with no category at all. An observation may carry
more than one, and
that is deliberate — a finding that is both a testing gap and a pattern worth reusing is two
categories on one row rather than an invented compound.
Summarise what was found — how many observations, across which categories, from how many stories — and proceed.
Step 2: Synthesise
Group the observations by category and write, per category, what the group says that no single
observation in it says. That paragraph goes on the observations as synthesis, which is why the
grouping is worth doing rather than listing.
Signal over noise. Two sharp observations beat ten vague ones. Where a category holds one
observation and nothing to draw from it, leave synthesis unset rather than restating the text.
Where there are no observations at all, the retro is still worth writing: the story outcomes are the
content, read from mcp__plugin_dpm_dpm__list_story and its status. Say what completed, what did not, and
what that implies.
Step 3: Write the retro
Gate first: "Record this retro?" with Approve / Request changes / Stop. On approval:
mcp__plugin_dpm_dpm__create_retrowith the epic or quick record asparent_id, a short kebab-caseslugand atitle. That call assigns the number, which nothing here works out.mcp__plugin_dpm_dpm__update_observationper observation, settingretro_idto the new retro,positionfor the order it reads in, andsynthesiswhere Step 2 wrote one.
Setting retro_id is the gathering, and nothing else changes. story_id and quick_id are
where the observation came from; neither is cleared, re-supplied, or moved. An observation written
against Story 3 and gathered into a retro is still queryable as Story 3's, and one written against a
quick record is still that record's — which is what makes "where did this lesson come from?"
answerable after the promotion that follows.
Do not create a new observation on the retro. A run that wrote fresh rows instead of pointing the existing ones at the retro would leave every count the same and every origin gone.
Step 4: Library write-back
Where an observation bears on a library document already read at startup — a codebase discovery
against an architecture entry, a criteria gap against a standards one — offer to amend it.
mcp__plugin_dpm_dpm__create_document_section on that library document, with the observation as the body and a
heading naming the date. Present every proposed amendment before writing any, and write only what
was approved.
Skip in silence where nothing matches. An amendment nobody needed is a document nobody trusts.
Step 5: Handoff
/dpm:pivotwhere a criteria gap or scope surprise means the spec or epic missed something/dpm:retro learnwhere an observation has proved durable across runs/dpm:specor/dpm:epicswhere the retro is the starting context for the next cycle
Lesson promotion (learn)
A lesson that keeps proving true belongs in the library, where Library Check reads it every run, rather than in the retro layer where it is re-judged every time.
Step L1: Select
mcp__plugin_dpm_dpm__list_observation with no scope and include_body for the whole corpus, then
mcp__plugin_dpm_dpm__list_observation_category on each. Where $ARGUMENTS carried text after learn, narrow
to it. The candidates are presented for a user to choose a lesson from, and a candidate is its text.
The candidates are what the list returns, and a promoted lesson is not among them. Promotion retires at the source, and the list omits retired rows — so a lesson cannot be offered twice, and nothing here has to check whether it was promoted before. That is the same predicate synthesis relies on, doing the work a scan for a marker used to do.
Present the candidates grouped by category, each naming its retro and its story so the user sees where it came from. Support selecting more than one.
Step L2: Preview, then promote
Preview both halves before writing either. For each selection show the library entry that will
be created — its title, its doc_type, its scopes, its body — and the retirement that will follow
it. Confirm. Nothing is written until this is confirmed, because a promotion the user did not expect
is a lesson that has left the retro layer and cannot be found by looking there.
On confirmation, per lesson, in this order:
mcp__plugin_dpm_dpm__create_librarywithslug,titleanddoc_type— the kind of document it is, as Library Check groups them.mcp__plugin_dpm_dpm__create_library_scopeper scope, one row each. A lesson about testing scopes todo; one about terminology may scope toall.mcp__plugin_dpm_dpm__create_document_sectionfor the lesson, written as standalone guidance rather than as a quotation of the observation.mcp__plugin_dpm_dpm__update_observationsettinglibrary_doc_idto the new document, withretired_atandretired_reasonin the same call.
The retirement travels with the link, which is what makes the pair atomic. A lesson retired but not promoted is one that has disappeared from both places, and splitting step 4 into two calls is what would create it. The order matters too: the library entry exists before anything points at it.
Provenance is the foreign key and there is no source line to write. library_doc_id says which
entry this observation became, its origin column still says which story or quick record raised it,
and "what was promoted from where" reads from either end. Do not write the trail into the entry's prose as well — a second
copy is one that will disagree.
Lesson retirement (retire)
Same selection as L1, and the same reason it needs no marker scan.
Ask for a one-line reason each lesson is spent — what changed so that it no longer holds — and
preview the retirement before writing it. Then mcp__plugin_dpm_dpm__update_observation with retired_at and
retired_reason, and nothing else.
Retirement means the lesson is no longer true anywhere. It is not the answer to "this does not
apply to the work in front of me" — that is do's per-run disposition, which writes a
retro_application row and leaves the observation alone.
Retiring is durable and this skill does not undo it. The row is never deleted, so the
observation and its reason stay readable under include_retired and the audit trail survives the
judgement — but there is no call here that puts a lesson back on offer. Ask before retiring, not
after.
Triage (triage)
A retro is not mandatory. An epic that finished clean has nothing to synthesise, and something has to record that so it stops being asked.
Step T1: Classify
mcp__plugin_dpm_dpm__list_epic, then for each whose status is complete: mcp__plugin_dpm_dpm__list_retro scoped by
parent_id to that epic for the retros already written, and mcp__plugin_dpm_dpm__list_story with
mcp__plugin_dpm_dpm__list_observation and include_body per story for what it holds — the waivable outcome is
no observations worth synthesising, which is a judgement about what they say. Archived epics do not
come back and are not classified — an epic that was swept is not one waiting on a decision.
Three outcomes, and only one of them is actionable:
- Settled — it already has a retro, or already carries
retro_waived_at. Skip in silence. - Waivable — no retro, and no observations worth synthesising.
- Has observations — no retro, but real ones are sitting there. Report these; never waive
them. Recommend
/dpm:retroon the epic instead.
Step T2: Confirm and waive
Present the waivable epics with the one-line reason each reads clean, and support waiving some of
them rather than all. Then gate with AskUserQuestion — which epics to waive — and write only
what it returns. Then mcp__plugin_dpm_dpm__update_epic with retro_waived_at and
retro_waived_reason together.
Both or neither — the database refuses one without the other. A waiver with a date and no reason is a decision with no record of why it was made, so the pairing is enforced rather than encouraged.
It is never a marker in prose: nothing greps for it, because it is a column. And as with retirement, this skill writes a waiver and does not lift one — so confirm before waiving rather than waiving broadly and correcting after.
Output
There is no file to save. The retro is the rows; the document is a projection of them, and a pre-commit check keeps the two from diverging.
Do not tell the user a path. Building one from a number and a slug is the filename construction this skill does without.
An artifact can be published from this output on request — follow the shared Artifact Publishing procedure. It is separately confirmed and never the default.
For retro the artifact is a trend view across retros: this retro's observations set against the
ones before it, category by category, so a lesson recurring across several epics reads as a run
rather than as one bullet in one file. That judgement no single retro carries — the pattern exists
only in the sequence. If you cannot write the one-line justification for what the visual carries
that the prose cannot, it has not earned its place.
Record it only once published, with mcp__plugin_dpm_dpm__create_artifact carrying its address, title and
publication time, then mcp__plugin_dpm_dpm__create_artifact_document binding it to this retro.
Guidelines
- Signal over noise. Synthesise into patterns, not reformatted lists.
- Actionable. A recommendation that changes nothing about how the next cycle is planned is an observation wearing a different hat.
- Works without observations. Story outcomes alone make a useful retro.
- Promotion is graduation, not duplication. A lesson lives in one place — the retro layer until it proves durable, then the library.
- Retirement is deliberate and reversible, and never a substitute for "not relevant here".
- Correct yourself sparingly, per the shared convention.