| name | ops-triage-loop |
| description | The weekly sweep that turns captured proto-learnings into reviewed work. Reads the open `ops/proto-learning` issues on the learnings repo, clusters them by lesson, applies a promotion threshold, and routes each cluster to its home โ an issue on the repo that owns it, a drafted PR against shared skills, a `ops/loop-improvement` issue against the engine, or closed with a reason. Files issues rather than editing product repos, and nothing auto-merges. Scheduled, not event-triggered: `ops/triaged` is a marker it writes, never a trigger. Trigger on "triage the learnings", "run the triage sweep". |
ops-triage-loop
The triage half of the self-learning system. The capture half โ read-only SubagentStop /
SessionEnd hooks in this plugin โ files proto-learnings as issues off the critical path.
This loop turns that raw backlog into reviewed work by routing each lesson to whoever owns it.
It is scheduled, not routed. There is no triage label and no route row: it wakes weekly and
sweeps. ops/triaged is a marker this loop writes, so the next run skips what it has already
handled โ it is an output, never a trigger.
Why weekly and not per-issue: the threshold needs a population. A lesson earns a shared-skills
PR by recurring, and you cannot see recurrence one issue at a time. Capture is continuous;
triage is periodic; a weekly cadence keeps the inbox from growing without flooding anyone.
It never hand-edits a product repo. For anything specific to one repo it files an issue
there and lets that repo's own process implement it. It drafts an actual PR only for shared
tooling. Everything is gated; nothing auto-merges.
What it calls
| Capability ยท action | Why | Visibility |
|---|
ops-repo-meta ยท topology | the learnings inbox, and the code repo a lesson belongs to | cross-cutting (read) |
ops-repo-meta ยท identity | labels.proto_learning, labels.triaged, labels.loop_improvement, by purpose | cross-cutting (read) |
ops-notify ยท send | one line per run, only when something was routed | cross-cutting (infra) |
All GitHub work goes through github-ops by operation name. This loop commands no
services: it reads, files and closes, which is forge work, not product work.
Config
| Thing | Value |
|---|
| Inbox | the learnings repo from topology (defaults to code) |
| Inbox filter | open issues, labelled labels.proto_learning, not labelled labels.triaged |
| Routed items per run | โค 10 total, of which โค 5 may be PRs |
| Cadence | weekly, as a scheduled routine |
The numbers are inherited defaults, not measurements. The threshold of 2 and the caps of
10/5 come from the prototype, which ran over a family of many small repos where the same lesson
recurred quickly. Over two large repos a cluster fills more slowly. Adopt them, watch real inbox
volume, and change them deliberately โ do not re-derive them from nothing.
The routing table
guessedHome on the record is a hint; you decide. The deciding question is
"would a different repo benefit from this?"
| Home | Where it goes | Mechanism | When |
|---|
code | the repo named by the cluster's sourceRepo | Issue, titled [from-learnings] โฆ | The lesson is specific to that repo โ a quirk of its own code, content or config. Only here when it affects that repo and no other. |
shared-skills | the shared consumer repo of a repo family | PR, drafted | Generalizable: it recurs across repos, or would help any repo. Requires the threshold. |
loop-self | this engine | Issue labelled labels.loop_improvement | About how a loop or the orchestration itself behaves. The loop must not rewrite its own definition unreviewed. |
| discard | โ | Close with a one-line reason | Not actionable, stale, or wrong. |
That fourth outcome is normal, not a failure. "Not actionable, stale or wrong" is a legitimate
result, and a triage run that never discards anything is not being honest with the inbox.
Domain-specific versus generalizable is the core judgement. A lesson goes to one repo only
when it affects that repo and no other. The moment it would help a second repo it belongs in
shared-skills, not duplicated into one repo's skills. When genuinely unsure: prefer
shared-skills if it is a tooling or pattern lesson, otherwise hold it (leave the
proto-learning open, uncommented) rather than mis-filing.
shared-skills may not be reachable in your topology. It only means anything for a repo
family with a shared consumer repo. For a single-product consumer there are only two live
destinations, code and loop-self โ and since a shared-skills PR is the only thing the
threshold gates, the threshold gates nothing there. Say so in the run summary rather than
pretending to apply it.
Step 1 โ gather the inbox
List the open issues on the inbox repo carrying labels.proto_learning and not
labels.triaged. For each, parse the fenced json record from the body (see
references/proto-learning-schema.md).
- Malformed record โ comment asking for a reformat and skip it. Never guess at fields; a
guessed
category clusters wrongly and is worse than a skip.
- Empty inbox โ report "nothing to triage" and stop. A normal outcome.
Step 2 โ cluster and dedupe
Group issues expressing the same lesson: same sourceRepo + same category + a
semantically equivalent lesson. Each cluster becomes one routed item and carries the
full list of source issue numbers as provenance.
Dedupe across the whole open set, here, in reasoning โ not per issue. That is the entire point
of a batch sweep, and it is the one part of this mechanism that is genuine judgement rather than
arithmetic. Two records describing the same gap in different words are one cluster.
Step 3 โ apply the threshold
Compounding means a pattern, not a one-off:
- Recurred โ a cluster with โฅ 2 distinct source issues, or the same lesson seen across
โฅ 2
sourceRepos โ eligible for shared-skills.
- Single occurrence, repo-specific โ a
code issue, or hold it (leave open,
uncommented) if it is too thin to act on yet. Never promote a single incident into a shared
skill.
loop-self clusters are not threshold-gated โ route them whenever they are actionable. A
loop that wastes a wake-up wastes it every week; waiting for a second sighting buys nothing.
Step 4 โ route each cluster
code โ an issue on the owning repo:
- Confirm the lesson truly affects only that repo. If it would help another, re-route to
shared-skills.
- Create an issue there: a clear title prefixed
[from-learnings] , what should change and
why, and โ from the record's notes โ a hint whether it belongs in that repo's CLAUDE.md or in
one of its skills. Let that repo's process decide the final placement.
- Do not apply the ready label. A human decides whether to feed it to the issue loop. That is
the compounding gate, and a loop that self-feeds has removed it.
shared-skills โ a drafted PR:
- Create a branch and push the smallest edit to the shared skill that should have surfaced
the lesson.
- Open the PR. Threshold and provenance are required, no exceptions.
loop-self โ an issue on the engine:
- Create an issue labelled
labels.loop_improvement: what the loop does today, what should
change, and why.
- Do not draft a PR editing a loop skill. A human frames a change to the loop's own
definition.
Every routed item carries provenance: the source issue numbers linked, the
sourceRepo#issue / PR each came from, and the occurrence count as threshold evidence.
Reviewers approve facts, not vibes โ a routed item without its evidence is unreviewable.
Step 5 โ mark processed
The rule differs by destination, and the difference matters:
shared-skills (a PR) โ comment the PR link on each source issue and add
labels.triaged so the next run skips it, but leave it open until the PR merges. Never
close a proto-learning just because you opened a PR for it โ a rejected PR would silently lose
the lesson.
code and loop-self (issues) โ close each source proto-learning with a comment
linking the new issue. Safe, because the lesson now lives in that issue.
- Discarded โ close with a one-line reason.
Caps and guardrails
- โค 10 routed items per run, of which โค 5 are PRs. If more clusters are ready, route the
highest-value ones,
log how many were deferred, and leave the rest for next week โ
never silently drop them.
- Never hand-edit a product repo's content. File an issue there instead.
- A shared-skills PR requires the threshold and provenance. No exceptions.
- Never auto-merge, never force-push, never edit a protected branch directly.
- One cluster โ one routed item โ one home. Never bundle unrelated lessons.
- Never apply a ready label to anything you file. The human gate is what makes this compound
safely.
- Never use
fable.
Running as a routine
Schedule this weekly as a cloud routine. It wakes, runs Steps 1โ5 against the current inbox,
routes up to 10 clusters, and stops โ the issues sit in their owning repos and any shared PR sits
for review. ops/triaged keeps the next run from re-doing the same work.
It needs this skill, github-ops, and ops-capabilities. The three labels
(ops/proto-learning, ops/triaged, ops/loop-improvement) must exist on the inbox repo before
the first sweep โ the filter is label-based, and ops/triaged is written by this loop.