The loop¶
A working model of the autonomous development loop that builds Adepthood — drawn in the conventions of systems thinking, and drawn as it actually runs rather than as it was designed.
Every node is clickable. Follow one into the workflow file, the skill, or the live run history that produced it.
The legend¶
▭ Stock — an accumulation. Something that fills and drains.
⬡ Constraint — a valve on a flow. Caps, floors, cooldowns.
◯ B — a balancing loop. Pushes the stock back toward a target.
◯ R — a reinforcing loop. Amplifies. Compounds over time.
⌁ Dashed red — wired but never fires. Present in design, absent in reality.
◇ Amber — requires a human. The loop stops here until a person acts.
The whole system¶
flowchart TB
classDef stock fill:#f3ecdf,stroke:#a5572f,stroke-width:3px,color:#2b2620
classDef flow fill:#ffffff,stroke:#5a5046,stroke-width:1.5px,color:#2b2620
classDef dead fill:#fff5f2,stroke:#c0392b,stroke-width:2px,stroke-dasharray:6 4,color:#8f2d1e
classDef human fill:#fdf4e3,stroke:#b8860b,stroke-width:2px,color:#5a4210
classDef valve fill:#e7dcc8,stroke:#8f4a28,stroke-width:2px,color:#2b2620
classDef loopb fill:#eef4ee,stroke:#3f7d4f,stroke-width:2px,color:#1e3f28
classDef gate fill:#ffffff,stroke:#a5572f,stroke-width:2px,color:#2b2620
subgraph SOURCES ["① Inflows — what creates issues"]
direction TB
FLARE["/flare<br/>human-invoked only"]:::human
SCANS["12 producer scans<br/>startup_failure · never run"]:::dead
DEPBOT["Dependabot → Ralph bridge<br/>on PR open"]:::flow
GRAPHST["graph-staleness<br/>nightly 04:40 UTC"]:::flow
PLAYIN["weekly-playbook<br/>Sundays · WIP-limit 1"]:::flow
end
subgraph GOV ["② Governors — constraints on inflow"]
direction TB
BMAX{{"BACKLOG_MAX = 50<br/>hard-coded in 3 files"}}:::valve
HOP{{"hopper.yml<br/>floor 12 · ceiling 80 · 6h cooldown"}}:::valve
end
STOCK[("GitHub Issues<br/>the backlog<br/>89 open")]:::stock
subgraph BAL ["③ Balancing loops — what drains and reshapes"]
direction TB
GROOM(("B1<br/>grooming")):::loopb
DESLOP(("B2<br/>de-slopify")):::loopb
end
subgraph INNER ["④ The inner system — start green / stay green"]
direction TB
PICK["pick-next.sh<br/>priority-ordered claim"]:::flow
G1["Gate 1 · TDD<br/>red → green → refactor · seconds"]:::gate
G2["Gate 2 · check-all.sh<br/>lint·types·security·complexity·suite"]:::gate
G25["Gate 2.5 · self-review<br/>code-review-orchestrator over the diff"]:::gate
G3["Gate 3 · CI<br/>3.11/3.12/3.13 · branch cov · audit"]:::gate
G4["Gate 4 · Claude review<br/>LGTM / COMMENTS / CHANGES_REQUESTED"]:::gate
MERGE{"pr-ready.sh<br/>13 tokens"}:::valve
end
PRS["Merged PRs<br/>the outflow"]:::flow
subgraph META ["⑤ Metrics and self-optimization"]
direction TB
DISCORD["Discord recap<br/>on PR close · event-driven"]:::flow
RETRO(("R1<br/>retrospective")):::loopb
PLAYOUT(("R2<br/>playbook")):::loopb
end
FLARE --> BMAX
SCANS -.-> BMAX
DEPBOT --> STOCK
GRAPHST --> BMAX
PLAYIN --> BMAX
BMAX --> STOCK
HOP -->|"refill when runway below 12"| SCANS
STOCK -->|"measures runway"| HOP
STOCK --> GROOM
GROOM -->|"reprioritise · close superseded · correct premises"| STOCK
STOCK --> DESLOP
DESLOP -->|"every 30 merges"| STOCK
STOCK --> PICK
PICK --> G1
G1 --> G2
G2 -->|"fail"| G1
G2 --> G25
G25 -->|"blocking finding"| G1
G25 --> G3
G3 -->|"fail"| G1
G3 --> G4
G4 -->|"CHANGES_REQUESTED"| G1
G4 -->|"LGTM"| MERGE
MERGE --> PRS
PRS --> DISCORD
PRS --> RETRO
PRS --> PLAYOUT
RETRO -.->|"memory files · local only"| PICK
PLAYOUT -.->|"rules · anchor still empty"| G1
PRS -->|"closes"| STOCK
click FLARE "https://github.com/Geoffe-Ga/adepthood/blob/main/.claude/skills/flare/SKILL.md" "The /flare skill — human-invoked issue filing"
click SCANS "https://github.com/Geoffe-Ga/adepthood/actions/workflows/scan-todo.yml" "Run history — every run is startup_failure"
click DEPBOT "https://github.com/Geoffe-Ga/adepthood/blob/main/.github/workflows/dependabot-to-ralph-issue.yml" "The Dependabot to Ralph bridge"
click GRAPHST "https://github.com/Geoffe-Ga/adepthood/blob/main/.github/workflows/graph-build.yml" "Nightly graph build and staleness issue"
click PLAYIN "https://github.com/Geoffe-Ga/adepthood/blob/main/.github/workflows/weekly-playbook.yml" "The weekly playbook workflow"
click BMAX "https://github.com/Geoffe-Ga/adepthood/blob/main/.github/workflows/_claude-scan.yml" "The drain gate — BACKLOG_MAX = 50"
click HOP "https://github.com/Geoffe-Ga/adepthood/blob/main/.github/workflows/hopper.yml" "The hopper — refills the queue when runway runs low"
click STOCK "https://github.com/Geoffe-Ga/adepthood/issues" "The live backlog"
click GROOM "https://github.com/Geoffe-Ga/adepthood/blob/main/.github/workflows/scan-groom.yml" "Grooming — daily 04:00 UTC and every 10 merges"
click DESLOP "https://github.com/Geoffe-Ga/adepthood/blob/main/.github/workflows/deslop.yml" "De-slopify — every 30 merges"
click PICK "https://github.com/Geoffe-Ga/adepthood/blob/main/scripts/ralph/pick-next.sh" "The picker — priority-ordered issue claim"
click G1 "https://github.com/Geoffe-Ga/adepthood/blob/main/scripts/backend/test.sh" "Gate 1 — targeted tests, the TDD inner loop"
click G2 "https://github.com/Geoffe-Ga/adepthood/blob/main/scripts/backend/check-all.sh" "Gate 2 — the full local quality ladder"
click G25 "https://github.com/Geoffe-Ga/adepthood/blob/main/.claude/agents/code-review-orchestrator.md" "Gate 2.5 — specialist reviewers over the diff"
click G3 "https://github.com/Geoffe-Ga/adepthood/actions/workflows/backend-ci.yml" "Gate 3 — CI run history"
click G4 "https://github.com/Geoffe-Ga/adepthood/blob/main/.github/workflows/claude-code-review.yml" "Gate 4 — the automated reviewer"
click MERGE "https://github.com/Geoffe-Ga/adepthood/blob/main/scripts/ralph/pr-ready.sh" "pr-ready.sh — the 13-token readiness vocabulary"
click PRS "https://github.com/Geoffe-Ga/adepthood/pulls?q=is%3Apr+is%3Amerged" "Every merged PR"
click DISCORD "https://github.com/Geoffe-Ga/adepthood/blob/main/scripts/ralph/RECAP.md" "The Discord recap — what it reports and why"
click RETRO "https://github.com/Geoffe-Ga/adepthood/blob/main/.claude/commands/ralph-tick.md" "The tick that schedules retrospection"
click PLAYOUT "https://github.com/Geoffe-Ga/adepthood/blob/main/CLAUDE.md" "The playbook anchor in CLAUDE.md"
Every node as a plain link
The diagram's nodes are clickable. This index is the same set of destinations in text — useful on a phone, with a keyboard, or if the mermaid runtime fails to load.
Inflows · /flare · producer scans (run history) · Dependabot bridge · graph-build · weekly-playbook
Governors · drain gate · hopper
Stock · the live backlog
Balancing · grooming · de-slopify
Inner system · pick-next.sh · Gate 1 · Gate 2 · Gate 2.5 · Gate 3 · Gate 4 · pr-ready.sh
Outflow · every merged PR
Metrics · Discord recap · the tick · playbook anchor
What is actually running¶
The diagram above is drawn from run history, not from documentation. Three of its branches are dark.
Why this model is honest about being broken
A model that shows only the intended design is a brochure. A loop diagram that hides its dark branches also teaches the wrong lesson about autonomous systems: the interesting failures are the quiet ones.
⌁ All 12 producer scans — never executed · issue #2259
Every scheduled scan reports startup_failure with zero jobs created, on
every run in recorded history.
$ gh run list --workflow=scan-todo.yml --limit 3
scan-todo startup_failure startup_failure startup_failure
scan-security startup_failure startup_failure startup_failure
startup_failure produces no job, so there is no red step to open and no log
to read. The run list shows a neutral entry rather than a failure anybody
would chase.
The consequence for the model: the entire producer half of the inflow is
absent. The 89 open issues arrived through human /flare and the Dependabot
bridge alone, while the governors kept measuring a supply that was never
arriving.
◯ R2 · The playbook — one completed turn in four attempts
The self-improvement loop is designed to distil durable rules from real
failures each week. Its anchor in CLAUDE.md currently reads:
<!-- playbook rules are inserted below this line -->
Nothing below it. The first three scheduled runs failed outright; the fourth
succeeded and produced exactly one delta — which is still unimplemented, and
because the workflow stands down whenever any playbook-labelled issue is
open, that one unmerged issue is now blocking the loop that produced it.
A WIP limit of one is a defensible design. It also means a single stalled delta halts learning entirely.
◯ R1 · The retrospective — real, but outside the repo
The every-10-PRs retrospective genuinely exists and asks what you remember it asking: it reviews the session for token burn and for moments the operator had to intervene, then writes durable memory files.
But it lives at ~/.claude/skills/session-retrospective/, outside the
repository, and its memory files live in a local project directory. No
GitHub Actions agent can see any of it. The loop learns locally and forgets
in CI — which is why it is drawn as a dashed return edge.
① Inflows¶
Five things create issues. Only three of them currently work.
| Source | Cadence | Status |
|---|---|---|
/flare |
Human-invoked | Working |
| Dependabot bridge | On Dependabot PR | Working |
graph-build staleness |
Nightly 04:40 UTC | Working |
weekly-playbook |
Sundays | Stood down |
| 12 producer scans | Daily → biweekly | Never run |
/flare is not a slash-command file
There is no .claude/commands/flare.md. /flare resolves because flare is
a skill name and the Skill tool accepts /<name> invocation. Worth knowing
if you go looking for it.
The twelve scans and their intended cadences
| Scan | Cron | Priority | Cap |
|---|---|---|---|
| security | Daily 05:00 UTC | P0 | 5 |
| deps | Daily 06:00 UTC | P2 | 5 |
| bugs | Daily 07:00 UTC | P1 | 4 |
| dead-code | Mon 08:00 UTC | P3 | 6 |
| complexity | Tue 08:00 UTC | P2 | 6 |
| coverage | Wed 08:00 UTC | P2 | 6 |
| perf | Thu 08:00 UTC | P2 | 5 |
| todo | Fri 08:00 UTC | P3 | 5 |
| types | 1st and 15th, 08:00 | P3 | 6 |
| docs | 1st and 15th, 09:00 | P3 | 4 |
| mutation | 8th and 22nd | P2 | — |
| a11y | Biweekly | P2 | — |
All twelve share one engine —
_claude-scan.yml
— which reads prompts/scans/<name>.md, runs read-only analysis, and files
deduplicated six-component issues. Scans never push code. Issues are
their only durable output, which is what makes them a pure inflow.
② Governors¶
Four numbers restrain the inflow, and they do not agree with each other.
BACKLOG_MAX = 50¶
Hard stand-down. At 50 total open issues every producer scan files nothing.
Hard-coded independently in three files —
_claude-scan.yml,
deslop.yml, hopper.yml. Changing the governor means editing three places.
MIN_QUEUE = 12¶
The refill floor. When agent-ready runway drops below 12, the hopper dispatches a producer scan to top it up.
MAX_QUEUE = 80¶
The drain ceiling. Above 80 agent-ready issues the hopper stands down and lets the fleet work the queue down.
6-hour cooldown¶
Per-workflow. Prevents the hopper from re-dispatching the same scan in a tight loop when the queue stays low.
The failure mode this shape hides
When the drain gate trips, the workflow run succeeds. The gate job sets
proceed=false and the scan job is skipped — so the run shows green.
Someone scanning run history for red would conclude the scans are healthy and
filing issues. They are doing neither. This is the same shape as the
startup_failure problem above: a mechanism that reports success while
proving nothing.
③ Balancing loops¶
The stock does not only drain by being built. Two loops actively reshape it.
B1 · Grooming runs daily at 04:00 UTC
and every 10 merged completions in the local loop. A pass re-prioritises,
closes issues superseded by shipped work, merges duplicates, and — the one that
matters most — corrects false premises. Issues here go stale routinely:
several agent-ready tickets have described architecture that had already moved,
and building them faithfully would have produced correct code for a system that
no longer exists.
B2 · De-slopify runs every 30 merged completions, pruning accumulated low-value work.
A number worth correcting
Local memory recorded de-slop as running "every 2nd groom, about 20 merges."
The actual config is groom_interval: 10, deslop_interval: 30 — every
third groom. The diagram uses the config, not the memory. Stale
documentation about the loop is itself a loop defect.
④ The inner system¶
Five gates, each cheaper than the next, each running once.
| Gate | Command | Cost | On failure | |
|---|---|---|---|---|
| 1 | Targeted tests (TDD) | test.sh <paths> |
seconds | is the fix loop |
| 2 | Full local ladder | check-all.sh |
~4m23s cold · ~8s on receipt | fix in place, re-run |
| 2.5 | Self-review | code-review-orchestrator |
minutes | back to Gate 1 |
| 3 | CI | GitHub Actions | ~5 min | back to Gate 1 |
| 4 | Claude review | claude-code-review.yml |
minutes | back to Gate 1 |
Gate 2.5 exists and is not in CLAUDE.md
scripts/ralph/PROMPT.md heads a step literally titled "Gate 2 → Gate
2.5" and requires dispatching the code-review-orchestrator agent over the
working-tree diff, fixing every blocking finding before anything reaches
CI. It is a real rung that the published four-gate table omits.
Two different fours, and they are not the same four
- The gate model (
adepthood-constraints.md): TDD → check-all → CI → Claude review - The ladder (
CLAUDE.md): targeted tests → check-all → git hooks → CI
One has a review rung and no hooks; the other has hooks and no review. Both are called "the four gates." The diagram draws the union, which is five.
Failure returns to Gate 1 is doctrine, not mechanism
The house rules say it plainly — fix the root cause with a failing-test-first cycle, re-clear Gate 2 locally, then climb again. But no script enforces it. Nothing routes a failed gate back into TDD; a formatting-only failure is honestly fixed in place. The return arrows in the diagram are a norm the agents follow, not a rail the system provides.
The receipt that makes Gate 2 nearly free — for the backend only
Gate 2 fingerprints the tree (scripts/, .pre-commit-config.yaml, the
interpreter, pip freeze) and skips work already proven green: ~4m23s cold
becomes ~8s on a hit.
Two caveats the number hides. Security checks always run in full —
pip-audit consults an advisory database that changes without the tree
changing, so a receipt must never suppress it. And the frontend has no
receipt at all; every frontend run is a cold run.
The merge decision¶
pr-ready.sh
collapses the whole state of a PR into one of thirteen tokens — not three:
ready · ready-unreviewed · behind · unknown · draft · blocked ·
conflicted · pending · ci-failed · changes-requested ·
awaiting-review · review-self-skipped · optout
Each names a different remedy. behind wants a sync; conflicted wants a real
resolution; blocked cannot be fixed by pushing at all. Collapsing them would
send an agent to do the wrong thing confidently — the recurring theme of this
whole system.
Gate 4 is not a GitHub approval
No state == "APPROVED" is ever set, and the base branch enforces no
required checks. The verdict is a comment that tooling parses — which
means a review posted to the wrong PR is indistinguishable from a real one.
That happened, and is now guarded by requiring the reviewer to report which
PR it actually read.
⑤ Metrics¶
The Discord recap is event-driven, not scheduled. It fires on
pull_request: closed with merged == true — deliberately, because a
push: main trigger would also fire on hotfixes and reverts.
What the recap reports
- The merged PR: number, title, author, and the issue it closed
- Cycle time — issue open to PR merged
- Gate outcomes and where the run spent its time
- Cumulative completions (currently 716) and the streak
- Knowledge-graph freshness: node and edge counts, and the age of the semantic layer
Detail lives in
RECAP.md.
The retrospective asks, every ~10 PRs: where were tokens misspent? Where did the operator have to intervene, and how could that intervention be designed away? Its output is durable memory — the reason a mistake made on Tuesday is not repeated on Thursday.
What the model teaches¶
Three properties of this system are worth more than its throughput.
Governors are invisible when they work. Both the drain gate and the dead scans present as green. A supervisory loop that cannot signal its own inaction is indistinguishable from one that has nothing to do — and the fix is not a better threshold, it is making stand-down visible.
The learning loops are the fragile ones. The build loop has run 716 times. The playbook has completed one turn in four attempts, and the retrospective's memory cannot reach CI at all. Reinforcing loops compound only if they close; these mostly do not, yet.
The expensive failures are quiet. Every serious defect this audit surfaced — scans that never ran, a review posted to the wrong PR, a test fixture writing to the real repository, guards that pass while proving nothing — shares one shape: a mechanism reporting success it has not earned. Systems thinking has a name for watching the wrong variable. This is what it looks like in a delivery pipeline.
Drawn from run history, workflow files and skill definitions on 2026-08-14 ·
sources linked inline · corrections welcome via
/flare