Skip to content

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.

Read the issue :material-arrow-right:

◯ 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 fullpip-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