|
|
@@ -1,138 +1,118 @@
|
|
|
-# Pattern Catalog — seven production loop shapes
|
|
|
+# Pattern Catalog — a morphology of loop shapes
|
|
|
|
|
|
-Each pattern is a proven outer-loop shape with a cadence, a starting risk tier, a gate,
|
|
|
-and an escalation rule. **Start every pattern at L1** (read-only) and graduate it only
|
|
|
-after its reports prove its judgment. The `loop-scaffold` script seeds a `loop.config.yaml`
|
|
|
-keyed by `--pattern <name>`; the names below are the canonical keys.
|
|
|
+Loops aren't a fixed list of recipes — they're **compositions of three orthogonal axes**.
|
|
|
+Name the axes and the patterns fall out; you can also compose ones not named here. The
|
|
|
+named patterns below are the well-trodden *points* in this space. `loop-scaffold` seeds a
|
|
|
+`loop.config.yaml` keyed by `--pattern <name>` (the canonical keys); the rest of the space
|
|
|
+you compose by hand. **Start every pattern at L1** and graduate only once its reports prove
|
|
|
+its judgment.
|
|
|
|
|
|
-The columns that matter for every pattern: **cadence** (how often), **tier** (starting
|
|
|
-autonomy), **gate** (what must pass before landing), **escalate** (what it must hand to a
|
|
|
-human instead of doing).
|
|
|
+## The three axes
|
|
|
|
|
|
----
|
|
|
-
|
|
|
-## `daily-scan` — discover + prioritize
|
|
|
-
|
|
|
-| | |
|
|
|
-|---|---|
|
|
|
-| Cadence | 1–2 h (weekday business hours) |
|
|
|
-| Tier | L1 (report only) |
|
|
|
-| Job | sweep the backlog/inbox/alerts; rank by priority; write the day's `STATE.md` |
|
|
|
-| Gate | none — it writes nothing but the report |
|
|
|
-| Escalate | everything; a human decides what to action |
|
|
|
-
|
|
|
-The off-peak, lowest-priority loop. It produces the work-list the other loops and the
|
|
|
-human draw from. Output is a `STATE.md` priority/watch/noise snapshot, nothing else.
|
|
|
-
|
|
|
----
|
|
|
-
|
|
|
-## `pr-watch` — watch review state
|
|
|
-
|
|
|
-| | |
|
|
|
-|---|---|
|
|
|
-| Cadence | 5–15 min |
|
|
|
-| Tier | L1 |
|
|
|
-| Job | list open PRs; flag stuck (no review N hours), failing checks, merge conflicts |
|
|
|
-| Gate | none — surfaces state, posts a summary comment at most |
|
|
|
-| Escalate | a human reviews/merges; the loop never merges |
|
|
|
-
|
|
|
-Skeleton: `gh pr list --json …` → classify each (waiting-on-review / failing / conflict /
|
|
|
-ready) → update `STATE.md` watch list → optionally one summary comment per PR.
|
|
|
-Public-comment text follows the repo's preview-before-send discipline.
|
|
|
+**1. Trigger — what starts a tick.**
|
|
|
|
|
|
----
|
|
|
-
|
|
|
-## `ci-watch` — triage build failures
|
|
|
+| Trigger | Fires when | Mechanism | Best for |
|
|
|
+|---|---|---|---|
|
|
|
+| `cadence` | a clock interval elapses | `/loop` (supervised), Desktop task, cloud routine, or a daemon | steady polling — backlog, PRs, deps |
|
|
|
+| `event` | an external thing happens (CI fail, error, deploy, message) | a **Channel** (MCP webhook receiver) pushes it into a live session | responsiveness + low cost — no idle polling |
|
|
|
+| `goal` | runs continuously **until a condition holds**, then stops | `/goal` (+ auto mode) | run-to-completion — migrations, metric targets |
|
|
|
|
|
|
-| | |
|
|
|
-|---|---|
|
|
|
-| Cadence | 5–15 min |
|
|
|
-| Tier | L2 (after L1 proves triage quality) |
|
|
|
-| Job | detect red CI; classify the failure; at L2, propose a fix in a worktree |
|
|
|
-| Gate | `verify` (the failing test passes) **and** `guard` (full suite + typecheck) |
|
|
|
-| Escalate | flaky/infra failures, anything touching deploy/secrets, ambiguous root cause |
|
|
|
+> **Event beats poll when you can get it.** A CI webhook firing the tick is cheaper and
|
|
|
+> faster than a 10-min poll. The cost: a Channel needs a **persistent session**
|
|
|
+> (`claude --channels …` in a background process), and it's a research-preview,
|
|
|
+> Anthropic-auth-only feature. Cadence stays fully detached; event trades that for
|
|
|
+> responsiveness. See [claude-code-loops.md](claude-code-loops.md).
|
|
|
|
|
|
-The highest-priority loop in the multi-loop order — a red build blocks everyone. At L1 it
|
|
|
-classifies and reports; at L2 it opens a fix PR in an isolated worktree and hands the
|
|
|
-branch to `fleet-ops` for the gated merge. Never auto-merges to `main`.
|
|
|
+**2. Posture — how much autonomy** (the [risk tier](risk-tiers.md)): `L1` report · `L2`
|
|
|
+propose-and-human-gates · `L3` autonomous-in-a-denylist.
|
|
|
|
|
|
----
|
|
|
+**3. Locus — where it runs / what it can touch.**
|
|
|
|
|
|
-## `dep-bump` — patch-only bumps
|
|
|
+| Locus | Mechanism | Can touch | Use when |
|
|
|
+|---|---|---|---|
|
|
|
+| `connector` | **cloud routine** (`/schedule`) | your claude.ai connectors (email, Asana, Slack, issues) — **no local files** | the work lives in services, not your repo |
|
|
|
+| `local` | Desktop task / daemon / `/loop` | the repo, build, models, local tools | the work touches local state |
|
|
|
|
|
|
-| | |
|
|
|
-|---|---|
|
|
|
-| Cadence | 6 h – 1 d |
|
|
|
-| Tier | L2 |
|
|
|
-| Job | find outdated deps; bump **patch-only**, behind a release-age cooldown |
|
|
|
-| Gate | `guard` (full suite + build) passes; supply-chain cooldown satisfied |
|
|
|
-| Escalate | minor/major bumps, anything failing the guard, any flagged advisory |
|
|
|
-
|
|
|
-Pair with [`supply-chain-defense`](../../supply-chain-defense/SKILL.md): respect the
|
|
|
-7-day cooldown (`preinstall-check.sh`) and behavioural score before a bump lands. Patch
|
|
|
-bumps that pass both the cooldown and the guard are the *only* class safe to auto-land,
|
|
|
-and only on a feature branch, never `main`.
|
|
|
+The recipe-selector in [claude-code-loops.md](claude-code-loops.md) is just these axes
|
|
|
+resolved to a mechanism. A loop = **(trigger × posture × locus) + the [state spine](state-spine.md)**.
|
|
|
|
|
|
---
|
|
|
|
|
|
-## `changelog-gen` — release-note drafts
|
|
|
-
|
|
|
-| | |
|
|
|
-|---|---|
|
|
|
-| Cadence | 1 d, or on tag |
|
|
|
-| Tier | L1 (draft only) |
|
|
|
-| Job | summarize merged PRs since the last tag into a `RELEASE_NOTES_DRAFT.md` |
|
|
|
-| Gate | none — produces a draft for human approval |
|
|
|
-| Escalate | the human edits + publishes; the loop never runs `gh release create` |
|
|
|
-
|
|
|
-Drafts to a file, never publishes. Publishing a release is a one-way visibility
|
|
|
-commitment — it stays a human step (the repo's release-review discipline). Pair with
|
|
|
-[`github-ops`](../../github-ops/SKILL.md) for the human-driven publish.
|
|
|
+## The catalog
|
|
|
+
|
|
|
+Each row: the axes, the recommended native mechanism, the job (gate → what it escalates),
|
|
|
+and the **failure mode to watch** ([failure-modes.md](failure-modes.md)).
|
|
|
+
|
|
|
+| Pattern | Trigger · Locus | Start tier | Mechanism | Job → escalates | Watch |
|
|
|
+|---|---|---|---|---|---|
|
|
|
+| `daily-scan` | cadence · local | L1 | Desktop task (off-peak) | sweep backlog/alerts, write `STATE.md` → all to a human | silent-stop |
|
|
|
+| `pr-watch` | event\|cadence · connector | L1 | Channel (PR webhook) or cloud routine | flag stuck/failing/conflicted PRs → never merges | runaway tokens if polled tight |
|
|
|
+| `ci-watch` | **event** · local | L2 | Channel (CI webhook) → fix in a worktree | failing test passes + full guard → flaky/deploy/secrets | gate reward-hacking |
|
|
|
+| `dep-bump` | cadence · local | L2 | Desktop task/daemon | patch-only behind cooldown + guard → minor/major, advisories | supply-chain |
|
|
|
+| `changelog-gen` | event(on tag)\|cadence · local | L1 | tag-event or Desktop task | draft `RELEASE_NOTES_DRAFT.md` → human publishes | — |
|
|
|
+| `merge-hygiene` | cadence · local | L1 | Desktop task (off-peak) | dead branches / stale flags → ambiguous deletes | worktree-boundaries |
|
|
|
+| `issue-sort` | cadence\|event · connector | L1 | cloud routine | classify + suggest labels → priority/dupe-close | — |
|
|
|
+| **`metric-chase`** | **goal** · local | L2 | `/goal` driving [`iterate`](../../iterate/SKILL.md) | drive coverage/latency/bundle/**eval-score** to target → unreachable / guard fails | gate reward-hacking · **high cost** |
|
|
|
+| **`regression-watch`** | cadence\|event(on release) · local | L1→L2 | Desktop task/daemon | run a benchmark/eval, diff vs baseline → a real regression | flaky bench = false alarm · **high/run** |
|
|
|
+| **`digest`** | cadence · **connector** | L1 | **cloud routine** | summarize email/Asana/calendar/news → nothing (read-only) | over-scoped connector |
|
|
|
+| **`backfill`** | **goal** · local | L2/L3 | `/goal` (+ worktree/container) | drain a migration/queue **to completion** → an item needing judgment | runaway budget · **long** |
|
|
|
+| **`monitor`** | **event** · local | L1 | **Channel** (error/log/deploy webhook) | triage the event → page a human on anomaly | alert fatigue · needs a live session |
|
|
|
+| **`freshness`** | cadence · local | L1 | Desktop task (daily/weekly) | re-check docs/data/deps vs reality → confirmed drift | transient failure ≠ drift |
|
|
|
|
|
|
---
|
|
|
|
|
|
-## `merge-hygiene` — hygiene
|
|
|
-
|
|
|
-| | |
|
|
|
-|---|---|
|
|
|
-| Cadence | 1–6 h (off-peak) |
|
|
|
-| Tier | L1 |
|
|
|
-| Job | find merged-and-deletable branches, stale feature flags, orphaned artifacts |
|
|
|
-| Gate | none at L1; at L2, branch deletion behind a "merged + N days old" rule |
|
|
|
-| Escalate | anything ambiguous; never deletes a branch with unmerged commits |
|
|
|
-
|
|
|
-Honors [worktree boundaries](../../../rules/worktree-boundaries.md): never touches another
|
|
|
-session's `.claude/worktrees/`, never sweeps with `git add -A`.
|
|
|
+## Notes on the patterns that need them
|
|
|
+
|
|
|
+- **`ci-watch` / `pr-watch` — prefer event over poll.** Wire a CI/PR webhook through a
|
|
|
+ Channel so the tick fires on the event, not a timer. A polled `pr-watch` at 5 min costs
|
|
|
+ ~3× a 15-min one for marginal freshness; the event-driven version costs ~nothing while
|
|
|
+ quiet. At L2, `ci-watch` opens a fix in a worktree and hands the branch to `fleet-ops`;
|
|
|
+ never auto-merges `main`.
|
|
|
+- **`metric-chase` is the bridge to [`iterate`](../../iterate/SKILL.md).** The loop's
|
|
|
+ *trigger* is a `/goal` ("coverage ≥ 90, or stop after N turns"); the *work* each turn is
|
|
|
+ an `iterate` step (modify → measure → keep/discard). Use it for any measurable target —
|
|
|
+ including an **eval score** (this is the GLM/Opus-bench shape). Highest cost class; bound it.
|
|
|
+- **`digest` is the canonical cloud-routine pattern.** It needs *connectors, not code*, so
|
|
|
+ it's the one archetype where the fresh-clone cloud routine is exactly right — it keeps
|
|
|
+ your claude.ai connectors and runs with the machine off. Read-only: no write scopes.
|
|
|
+- **`backfill` is run-to-completion, not recurring.** A `/goal` drains the queue/migration;
|
|
|
+ when the condition holds it stops and clears. Bound it (`or stop after N`, a token budget)
|
|
|
+ — it's the runaway-budget risk made flesh. For arbitrary execution, run it in a container.
|
|
|
+- **`monitor` is the purest event loop.** An error-tracker/deploy webhook → a Channel → a
|
|
|
+ persistent background session that triages and pages on anomaly. No polling at all. The
|
|
|
+ trade-off is keeping that session alive.
|
|
|
+- **`regression-watch`** runs a real suite each tick (expensive), so cadence it slowly or
|
|
|
+ trigger it on a release event. Treat a transient/flaky failure as advisory (don't page on
|
|
|
+ one red run) — the same exit-7-vs-exit-10 discipline our staleness verifiers use.
|
|
|
|
|
|
---
|
|
|
|
|
|
-## `issue-sort` — classify + label
|
|
|
+## Composing a pattern not in the catalog
|
|
|
|
|
|
-| | |
|
|
|
-|---|---|
|
|
|
-| Cadence | 2 h – 1 d |
|
|
|
-| Tier | L1 (propose-only) |
|
|
|
-| Job | classify new issues (bug/feature/question/dupe), suggest labels + priority |
|
|
|
-| Gate | none — proposes labels, applies only the mechanical ones at L2 |
|
|
|
-| Escalate | priority calls, dupe-closing, anything needing product judgment |
|
|
|
+Pick a point in the space the named patterns don't cover. Examples:
|
|
|
|
|
|
-At L1 it proposes; at L2 it may apply purely-mechanical labels (`needs-triage` →
|
|
|
-`bug`/`docs`) but never closes an issue or sets priority unattended.
|
|
|
+- *event · connector · L1* — a Slack message (Channel) triggers a read-only lookup against
|
|
|
+ a connector. (A "support-triage" loop.)
|
|
|
+- *goal · connector · L2* — drain an Asana backlog to empty via `/goal`, updating tasks
|
|
|
+ through the connector.
|
|
|
+- *cadence · local · L3* — a nightly autonomous refactor in an isolated container.
|
|
|
|
|
|
----
|
|
|
+The discipline is identical regardless of the point: bounded scope, a gate, an escalation
|
|
|
+rule, a kill switch, a budget — and **start at L1**.
|
|
|
|
|
|
-## Choosing a pattern → tier → cadence
|
|
|
+## Choosing — the short version
|
|
|
|
|
|
-1. **Match the job** to the closest pattern; use `custom` only if none fit.
|
|
|
-2. **Start at the pattern's L1 tier.** Run it read-only until you trust its reports.
|
|
|
-3. **Set the cadence** to the slowest that still catches the work in time — cadence is
|
|
|
- the biggest cost lever (see [loop-estimate](../scripts/loop-estimate.py)). A 5-min PR
|
|
|
- pr-watch loop costs 3× a 15-min one for marginal freshness gain.
|
|
|
-4. **Graduate** to L2 only with a `guard`, a `worktree`, an `escalation` rule, and a
|
|
|
- `land_via` — then re-run `loop-check` at the new tier.
|
|
|
+1. **Locus first:** does it touch local code? → `local` (Desktop task/daemon). Pure
|
|
|
+ connector work? → `connector` (cloud routine).
|
|
|
+2. **Trigger next:** is there an event to react to? → `event` (Channel) — cheaper + faster.
|
|
|
+ A clear finish line? → `goal`. Otherwise → `cadence`, slowest that still catches the work.
|
|
|
+3. **Posture:** start **L1**. Graduate to L2 (with a guard, worktree, escalation, `land_via`)
|
|
|
+ only once the reports earn it; re-run `loop-check` + `loop-doctor --live` at the new tier.
|
|
|
|
|
|
## See also
|
|
|
|
|
|
-- [risk-tiers.md](risk-tiers.md) — what each tier may do and the permission-mode mapping.
|
|
|
-- [state-spine.md](state-spine.md) — the multi-loop priority order these patterns share.
|
|
|
+- [risk-tiers.md](risk-tiers.md) — the posture axis (permission-mode mapping).
|
|
|
+- [claude-code-loops.md](claude-code-loops.md) — the trigger/locus axes resolved to mechanisms + the recipe selector.
|
|
|
+- [failure-modes.md](failure-modes.md) — the "watch" column, in depth.
|
|
|
+- [state-spine.md](state-spine.md) — the multi-loop priority order these share.
|
|
|
- [../assets/loop.config.template.yaml](../assets/loop.config.template.yaml) — the config every pattern fills in.
|