Claude Code agents that improve an MCP server by arguing through its GitHub Issues.
Two agents alternate:
- Implementer: has the source, SSH to
lem(the box the server runs on), and the authority to change things. - Tester: has none of that. It talks to the server only as an MCP consumer, the way a real user would, and reports what it finds.
The only channel between them is GitHub Issues. Because the tester can't read the code, a fix can only convince it through the interface. A fix that reads correctly but doesn't behave correctly gets sent back.
Other agents work alongside them, each through the same issues:
- Feature lead: designs features and ideas with Don in issue comments, then builds
approved ones in stages (see below). Its builds run inside the loop, and its design
sessions run from
run-features.sh, which the loop starts whenever one is waiting. - Docs reviewer: closes a fix to text the tester can't see (the README, a
docs/page that isn't a guide) by reading the merged tree. It is read-only. - Curator: rules on requests to change a regression case, filed on this repo.
- Regression agent: runs on its own (
run-regression.sh), not in the loop. It runs the growingregression-suite-*.mdchecks against the live server and files issues for failures and slowdowns. - Release sessions:
run-release.shadds a read-only code review and a notes draft when a release is cut (see "Releasing").
The loop normally runs against lem. A second copy can run against a dw server on the Mac
(DW_TARGET=local), and labels decide which loop works which issue (see "Another
server"). This repo holds no application code, only the drivers, role prompts and
regression suites. HARNESS-ROADMAP.md is the plan for where the
harness goes next.
┌─────────────┐ GitHub Issues ┌─────────────┐
│ implementer │ ──────────────────▶ │ tester │
│ lead builds │ ◀────────────────── │ docs review │
│ source repo │ │ MCP only │
│ ssh lem │ │ qa-* spaces │
└──────┬──────┘ └──────┬──────┘
│ deploy develop │ tools/skills
▼ ▼
┌───────────────────── dw MCP server on lem ─────────────────────┐
└────────────────────────────────────────────────────────────────┘
run-loop.sh repeats the following. Every session is a fresh claude -p, one per issue,
never one per role.
-
Park outside filings. An issue filed by any login other than
TICKET_OWNERis parked for Don (owner:don+status:needs-approval). The repo is public, and nothing a stranger files is worked unattended. -
Claim, then triage. The implementer's queue is claimed for this loop with a
target:label first, so two loops never work the same issue. Triage runs when two or more issues are waiting. For each one it asks: is it a duplicate, already fixed ondevelop, or a restatedwontfix? Should it be batched with a related issue? Would the fix add new engine or validation surface that a narrow verify can't cover? In that last case it escalates the issue to Don. Every outcome is recorded as atriage:comment. -
One implementer session per remaining issue. The session reproduces the bug, fixes it on a branch, and merges to
develop. It then deploysdevelop(never the branch) with one call:ssh lem '~/diffusers-workflow/scripts/deploy.sh develop'.lemcan only be on one commit, and a cycle hands off several fixes, which is why onlydevelopis deployed. The session ends by handing the issue to the tester with a comment saying what changed. -
The feature lead. A waiting design or decompose starts
run-features.sh. Then the lead builds at mostLEAD_STAGES_PER_CYCLEapproved stages (default 1), each handed to the tester like a fix, and closes out finished features. -
Re-check
lem. The driver makes surelemis onorigin/developand resets the tester's plugin tree to the same commit. -
One tester session per issue it holds. The session kind depends on the issue:
- verify reruns a handed-off fix or stage's repro and one or two nearby cases over
MCP, then closes the issue as
verifiedor sends it back with what's still wrong. - spec writes a stage's cases from its approved plan before any code exists.
- answer replies to an implementer's
status:needs-infoquestion. - handoff applies an edit to this repo the implementer asked for.
Every
TESTER_TASK_EVERYcycles, a task session also moves forward a throwaway series inqa-workspaces (agents/tester/standing-task.md) and files anything it hits. On the cycles in between, a pendingwontfix/duplicateclosure gets its own short session, so the tester can accept it or reopen it once. - verify reruns a handed-off fix or stage's repro and one or two nearby cases over
MCP, then closes the issue as
-
Docs review. The docs reviewer closes or bounces each
docs-reviewfix, including one the tester rerouted this cycle. -
Suite upkeep. The driver commits any cases the tester added, and the curator rules on pending requests to change a case.
-
Print a status board queried live from GitHub, with a line for a release freeze and for any private security drafts, then start the next cycle. The driver sleeps only when a cycle left the board unchanged.
touch logs/stop-after-cyclemakes it exit here instead.
A few things the driver does so the sessions don't have to:
- Prompt contents. It puts the issue's title, labels, body and latest comments in each
prompt, along with the commit
lemis running. Comments by any login other thanTICKET_OWNERare withheld. - Label check. It checks an issue's labels just before starting its session, so an issue already handed off as part of a batch is skipped.
- Keeping
lemondevelop. It redeploysdevelopiflemis on anything else before the tester runs (DEPLOY_ON_MISMATCH=0only warns). - Bounce escalation. After
IMPLEMENTER_ESCALATE_AFTERbounces (default 2), an issue's next implementer session runs on the tester's model. AfterIMPLEMENTER_PARK_AFTERbounces (default 4), the driver parks the issue with Don instead, because the two roles aren't agreeing on what "fixed" means.
Nothing survives between sessions except what's written down: the issues, the suite files,
and the tester's qa-bible.md. qa-bible.md is a snapshot capped at about 12 KB (cast,
assets, episode ledger, house rules, next step), not a journal. The role prompts tell each
agent to leave a trail another session can resume from: a pushed branch plus a progress
comment. A session cut off by its spend cap is then picked up where it stopped.
Tickets are GitHub Issues on dkackman/diffusers-workflow, filed with its "MCP agent-loop
ticket" template. Agents work on them only through gh issue.
owner:*is a baton. An open issue has exactly one ofowner:implementer,owner:tester,owner:leadorowner:don, naming whoever acts next. An agent touches only issues with its own owner label, and only to act on them or hand them off.- Only the tester closes an issue as
completed(withstatus:verified), and only after a real MCP call in that session. The implementer never verifies its own fixes. One exception: a fix that changed only text neither the server nor the plugin serves (the README, adocs/page that isn't a guide) is labeleddocs-review, and a read-only docs reviewer closes it withstatus:reviewed. The tester can't see those files, and must not read source to try. wontfixis the implementer's call. It gives a reason and closes the issue asnot planned. The tester may reopen once with new evidence, and a secondwontfixis final. "Can't reproduce" goes throughstatus:needs-infofirst.duplicatecloses an issue asnot planned, withduplicate of #NNin a comment. Closed issues still count when checking for duplicates. A reproducingverifiedissue is a regression to reopen, not a duplicate.status:needs-approval+owner:donparks an issue with the human. The implementer must use it for engine or syntax changes, new concepts consumers would have to learn, and breaking changes bigger than a rename, after writing a proposal in the source repo'sdocs/proposals/. Triage can escalate earlier, without a proposal. No agent touches a parked issue.field-reportmarks an issue filed from Don's own dw sessions (real use) rather than by the harness. The loop works it like any other issue. The feature lead (roadmap R11) reads it as evidence of demand.breaking-changetells the tester to adjust its calls rather than file the change as a bug.- Commits reference the issue (
fix(mcp): #42 - ...), go on branches merged todevelop, and never go tomaster. - No session polls or sleeps. "Nothing to do" means the session exits.
open ──▶ status:fixed-pending-verify ──▶ status:verified (closed completed)
▲ │
└────────────────────┘ (tester bounces it back to owner:implementer)
open ──▶ status:fixed-pending-verify + docs-review ──▶ status:reviewed (closed completed)
or back to owner:implementer
open ──▶ status:needs-info ──▶ open
open ──▶ wontfix (closed not planned) ──▶ (tester accepts, or reopens once)
open ──▶ duplicate (closed not planned)
open ──▶ status:needs-approval + owner:don ──▶ open | wontfix (human decides)
idea ──▶ owner:lead ──▶ feature plan | owner:implementer (one fix) | closed
lib/classify.jq is the protocol as code: it maps every open issue to
the session that runs next, or to where it waits. Every driver queue, the digest and the
post-session audit read it. An issue no queue will ever pick up is "stranded": the audit
warns on it, and the digest lists it.
Only an owner change moves an issue. Status labels are context for whoever holds it.
A status addressed to you (status:needs-approval, status:plan-review, or
status:needs-info on an issue you were asked about) goes stale once you hand the issue
back, and the agent that receives it clears it. So you never need to tidy statuses: the
owner swap is the whole move. lib/classify.jq encodes this, and
tests/classify-cases.json holds a fixture for each hand-back below.
| To | Do this |
|---|---|
| Hand back an issue you were asked about (a park, a question, a failed deploy) | Comment your answer, then swap owner:don for owner:implementer (a bug) or owner:lead (a feature, idea or stage). |
| Start a feature or an idea | Swap owner:don for owner:lead. A comment on scope or priority is optional. |
| Approve a plan | Add status:plan-approved and swap to owner:lead. Only you can add that label. |
| Ask for changes to a plan | Comment what you want, then swap to owner:lead. |
| Decline a feature or idea | Comment "don't build", then swap to owner:lead. The lead records it, and a close-out closes it. |
| Defer one | Comment, and keep owner:don. |
| Verify a stage the plan gave you (no MCP surface) | Check it, then gh issue close <n> --reason completed. |
| Reject a bug | Add wontfix, then close it not planned. |
| Answer a curator escalation (harnest) | Comment approve, deny or your amendment, then remove owner:don. |
| Reverse a curator ruling (harnest) | Reopen it with a comment saying what you want instead. |
| Let an outside filer's issue into the loop | Hand it back as in the first row. It is never re-parked after that. |
| Handle a private security finding | ./scripts/file-advisory.sh --list (also on the board line and in the digest). Fix it out of band, or hand it to the implementer yourself without the details going public; publish the advisory once the fix ships, or close it. |
| Freeze for a release | Open an issue titled with the version (Release 0.5.0), labeled release + owner:don. Until you close it, only issues labeled release-blocker move, and the tester's standing task is held. Add release-blocker to what must land first. |
| Send a Mac issue to lem (or fix a wrong claim) | gh issue edit <n> --remove-label target:local --add-label target:lem. lem's loop deploys develop, which has any Mac fix. |
| Correct an issue's backend | Swap its backend: label. cuda keeps it off the Mac loop, mps off lem's. |
| Check a Mac-only verification on CUDA | Issues with verified-on:mps were verified on the Mac only. Reopen one as owner:tester + status:fixed-pending-verify + target:lem and lem's tester re-verifies it. |
What doesn't move anything:
- A comment on its own, while you hold the issue. Agents read your comments when you hand the issue back, not before.
- Adding or removing a status without the owner swap.
priority:*,field-report, or other plain labels.- Any change to an issue an agent holds. It's mid-flight, so a change can race the
session working on it. To step in, swap its owner to
owner:donfirst.
run-digest.sh prints the exact command for each parked issue's approve and reject, from
its state.
GitHub is the only ticket history. Issues from before the 2026-09-12 migration that were still active then were carried forward and cite "migrated from T0xx" in their body.
run-regression.sh runs the regression agent. It uses the same consumer-only fence as the
tester, but it never hands work to anyone: it runs cases and reports failures on GitHub.
Each level has its own file and its own workspace, so levels never share fixtures or
affect each other's timings:
| level | file | workspace | what belongs there |
|---|---|---|---|
smoke |
regression-suite-smoke.md |
regression-smoke |
fast, fundamental, general-purpose; the default |
complete |
regression-suite-complete.md |
regression-complete |
general-purpose but slower or more edge-case; runs after smoke |
model-specific |
regression-suite-model-specific.md |
regression-model-specific |
tied to one video/image model or pipeline; opt-in |
security |
regression-suite-security.md |
regression-security |
hostile-input probes against the default security posture; opt-in, and its issues carry security |
./run-regression.sh # smoke (default)
./run-regression.sh complete # smoke, then complete
./run-regression.sh security # one opt-in level
./run-regression.sh all # all four, once each
./run-regression.sh smoke my-suite.md # override one level's file; workspace comes from the filename- Case IDs carry a per-level prefix (
S-/C-/M-/SE-). A case states its intent and expected result, and the agent checks the exact call shape against the live tool schema on every run. - Chunking: a level runs as sessions of
CASES_PER_SESSIONconsecutive cases, plus a final sweep that removes leftover artifacts. The default is 3 cases on a provider that declares a context window under 120k tokens, and 8 otherwise. Set it to0to run each level in one session. - Cleanup: each case deletes what it generated once it and anything depending on it are done. What stays is the durable fixtures listed in the suite's "Fixtures" section and artifacts an open issue needs for a repro.
- Performance:
regression-perf/holds one append-only JSONL file per case, recording each timing or declared metric, pass or fail. When no human has set abaseline:, a reading more than about 50% over the recent or all-time median is flagged. The format is inregression-perf/README.md. - Who adds cases: all three consumer-side paths grow the suites. The regression agent adds cases next to ones it already covers. The tester adds a case after verifying it over MCP. The implementer only proposes a case in its hand-off comment, because it doesn't have this repo and a case isn't confirmed until the tester runs it.
- What's in a suite file: only the durable test. A pass leaves no trace in the file, and a failure becomes an issue.
- Removing a case: no agent deletes or weakens one. The only route is a proposal filed
on this repo, labeled
suite+status:needs-approval. - Commits: the driver commits suite and
regression-perf/changes before and after every level. It flags any commit that removed lines.
Run it by hand, from cron, or with the loop skill. run-loop.sh and run-regression.sh
share a lock (logs/.driver.lock) and wait for each other, because an implementer deploy
would restart the server partway through a regression run.
A second dw server on this machine (the Mac: Apple silicon, MPS) runs the loop and the
regression suites while lem is busy, and covers MPS. Everything for it is keyed by
DW_TARGET=local, and nothing it does reaches lem. The design is in
docs/superpowers/specs/2026-09-26-mac-loop-design.md.
One-time setup.
scripts/setup-mac-loop.sh --dry-run # what it would make
scripts/setup-mac-loop.sh # dw-agent-mps (implementer clone), dw-mps-serve (serving clone), venvs, ~/dw-mps-workspaceThen stop any dw server you started by hand. When the loop starts and nothing answers,
it deploys the serving clone (scripts/deploy-local.sh, which runs
DW_LOCAL_DIR/scripts/deploy.sh develop and records what it deployed in
logs/.deployed.local; that record, not the clone's checkout, is what the prompts call
"running"). The server binds to
127.0.0.1 in a screen session named dw-serve (screen -r dw-serve) and logs to
~/dw-serve.log. The deploy needs the dw deploy.sh that finds the server by its port
(branch fix/deploy-find-server-by-port).
Running it.
DW_TARGET=local SHARED_PASSES=1 ./run-loop.sh # SHARED_PASSES=1 while lem's loop is off
DW_TARGET=local ./run-regression.sh smoke
tail -f logs/loop.local.log
scripts/sync-fixtures.sh # lem's qa-cast media, when lem isn't busy (below)Which loop works which issue. Two label families decide it:
| label | question | set by |
|---|---|---|
backend:shared / backend:cuda / backend:mps |
what is the bug about? | the filer, or triage when it's missing |
target:lem / target:local |
which server's loop holds it? | the driver, when a loop takes it; kept until it closes |
| issue | lem loop | Mac loop |
|---|---|---|
claimed target:lem / target:local |
its own claims only | its own claims only |
unclaimed backend:cuda |
yes | no |
unclaimed backend:mps |
no | yes |
unclaimed backend:shared, or no backend |
yes | yes (triage labels it) |
| unclaimed, past the implementer (handed off before claims existed) | yes | no: it was deployed to lem |
- Claiming. A loop claims every fix in its queue before triage, and never over another loop's claim. When both claim the same issue in the same moment, the one that reads back both labels removes its own. If both do, neither holds it, and it is claimed next cycle.
- Handing over to lem. A session that finds an issue belongs on lem hands the claim
over with
--remove-label target:local --add-label target:lem. That's the Mac implementer for a cuda bug, and the Mac tester for a repro it can't run here: a lem-only fixture, CUDA, or a model too big for 64 GB. Both loops deploydevelop, so lem's tester gets the fix. - A contradiction. A
backend:cudaissue claimedtarget:localisstranded, and the audit flags it.
What the Mac loop does differently.
- Its own state:
- its lock is
logs/.driver.lock.local. A Mac regression run uses the same lock, so the two take turns; neither ever waits on lem's lock. - logs:
loop.local.logand<role>.local.log; - state files:
progress.local.tsv,closures-seen.local,stop-after-cycle.local; - clones:
dw-agent-mps,dw-agent-plugin-mps,dw-agent-lead-mps,dw-mps-serve.
- its lock is
- Deploy. Only the driver deploys, between sessions:
deploy_cmd(providers.sh) runs the serving clone'sdeploy.shthroughscripts/deploy-local.sh. The Mac implementer merges, pushes and hands off, and the guard refuses it the deploy scripts. A session's own MCP connection keeps the old server from exiting on thescreenrestart path (#446, 2026-09-26). Its target section (agents/implementer/target.md) andagent-settings/implementer.local.jsonsay so. - Verify. The tester follows its target section
(
agents/tester/target.md). A verification here addsverified-on:mps, meaning CUDA hasn't re-verified the fix. - Suite cases. The Mac tester adds a case only for a verified
backend:sharedfix, since every suite runs on lem too. For abackend:mpsfix it proposes the case in its verify comment, until the MPS level exists (harnest#16). - Not on the Mac: the tester's standing task (its
qa-bible.mdand series media are lem's), feature specs, lead builds and close-outs. Those issues wait for lem. - Server-free passes. Feature design, docs review and curator review run in one loop
only: lem's while it's running, the Mac's otherwise.
SHARED_PASSES=1or0overrides that. - Budget.
TESTER_BUDGET_USDdefaults to 8 here and 5 on lem, because MPS jobs run 2-3x slower. - Guard (via
HARNEST_TARGETandHARNEST_ROLE):- the Mac implementer can't run
ssh,scp,rsyncor a deploy script; - a Mac regression run can't edit a suite file (the Mac tester may);
- nothing on the Mac writes to lem's perf history;
- a new issue carries one
backend:mpsorbackend:sharedlabel, one owner, and notarget:label; - no comment on a
target:lemissue; - a Mac verification must add
verified-on:mps; - the only
target:label an agent may add is the hand-over to lem, and only a Mac tester may addverified-on:.
- the Mac implementer can't run
- Release gate. Every regression filed during the gate blocks it,
backend:mpsincluded.
A regression run here can't touch lem either.
- Identity.
DW_URLmust name this machine, so a leftover lem URL is refused. Then/api/healthmust answer with this machine's hostname, so an ssh tunnel to lem is refused too. The Mac loop runs the same check before it starts. - Plugin and commits. It loads the plugin from
DW_LOCAL_DIR/plugins/dw, so lem'sPLUGIN_TREEis never reset. It commits onlyregression-perf/local/, never the suite files or lem's readings, and makes no startup commit.
The agent follows
agents/regression/target.md, which is added to its
system prompt:
- Issues: an open issue for the same case counts as already filed unless it is
backend:cudaor claimedtarget:lem. A failure never becomes a comment on atarget:lemissue. A new one isowner:implementer+backend:mps(backend:sharedwhen the case also fails on lem), so the Mac loop can work it. - Timing: figures in case text were measured on lem's CUDA GPU. A reading is judged
only against
regression-perf/local/history, and one that includes a first-time download is marked as such. - Skipped: a case whose fixture is missing here, whose expectation is about CUDA
hardware, or that would load H3 or full-size LTX is listed as
REGRESSION-SKIP: <case> <reason> <what>, not filed. - Differs: an expectation about lem's history or hardware (a quote's
basis,measured_on) is listed asREGRESSION-DIFFERS:. The driver logs both counts per level. - Security probes: Linux-only paths get macOS stand-ins for reads, and leak checks
also catch
/Users/and/private/.
The driver never hands out the cases in TARGET_SKIP_CASES on a local target. The
default is S-F079, C-F005, C-F007, C-F023 and C-F047, each of which can run a 64 GB
machine out of memory. It logs them as skipped. all on a local target is smoke, complete
and security. model-specific runs only when you
name it, since H3 and LTX at full size are a memory risk on 64 GB of unified memory.
Fixtures. Most cases that need fixtures use media the tester made on lem
(asset:qa-cast/...), and those cases are skipped until the media is here. To copy it,
run this when lem isn't busy:
scripts/sync-fixtures.sh --dry-run # what would come across
scripts/sync-fixtures.sh # copy lem's qa-cast/, uploads/qa-cast/, cast/, reference_sheet.jpgIt reads lem at idle priority with a bandwidth cap. It writes into the local server's
common/assets, never overwriting a file already there. Knobs: FIXTURE_SOURCE,
DW_LOCAL_WORKSPACE, FIXTURE_BWLIMIT_KBPS.
run-release.sh refuses any target but lem.
Work bigger than one fix goes through the feature lead (roadmap R11). A feature is one
issue labeled feature, and you start one by swapping its owner:don for owner:lead.
An idea goes the same way: its first design session decides whether it is a feature, a
single fix (handed to owner:implementer), a duplicate, or not worth doing.
- Design (
run-features.sh, whichrun-loop.shruns each cycle when a design or decompose waits). The lead first checks the proposal against the code with one read-only sweep, and measures demand (field-reportissues, real use in your own workspaces). It then posts a plan that opens with a verdict: build, build smaller, defer or don't build. After the verdict come the design, the stages, and questions marked with defaults. The issue goes toowner:don+status:plan-review. You answer in comments and swap the owner back. The plan is one comment, edited in place. - Approve. You add
status:plan-approved. No agent can: the guard refuses it. - Decompose (
run-features.sh). Each stage becomes a sub-issue, ordered by "blocked by" links. A stage that needs another feature is blocked by that issue. - Spec (
run-loop.sh, tester). The tester writes each stage's acceptance cases from the plan, before any code, aspending: #<stage>suite cases. The regression agent skips them. - Build (
run-loop.sh, lead pass). One stage per cycle, once it has no open blocker. The lead fans code-only work out toclaude-sonnet-5-5subagents, then integrates, merges todevelop, deploys once, and hands off. - Verify (
run-loop.sh, tester). The tester checks the stage's repro plus its pending cases. A pass removes thepending:lines; a bounce goes back to the lead. - Close out. Once every stage is closed, the lead writes the design record, and the tester closes the parent after running every case the feature added.
The lead can also stop mid-build and re-plan. That removes status:plan-approved and
halts the feature until you approve again. If you decline a feature, a close-out moves
its doc to docs/proposals/declined/ and closes any open stages.
Each phase ends with a marker comment carrying the plan version it belongs to
(<!-- harnest:decomposed v2 -->, specced v2, spec-questions v2). A stage builds only
when its parent's current plan has both decomposed and specced. A new plan version
makes the old markers stop counting, so after a re-plan decompose reconciles the stages
and the tester specs what changed, without anyone arranging it. If the tester finds the
plan too vague to test, it posts spec-questions instead, and the lead answers with a
new plan version for you to approve.
./run-features.sh # design/decompose whatever is the lead's turn, now (the loop also does)
ONLY_ISSUES=378 ./run-features.shrun-bench.sh measures the implementer against issues whose right answer is already known.
It re-runs the implementer on an issue from bench/cases/, starting at the develop commit
just before the real fix. The clone holds no later history and has no remote, and GitHub,
lem, MCP and the web are all denied. The script then scores the result four ways:
- new pytest failures;
- the real fix's tests run against the candidate;
- overlap with the files the real fix touched;
- a verdict (
pass/partial/fail) from a separate Opus judge.
./run-bench.sh # all cases: claude-sonnet-5-5, the working-tree prompt
IMPLEMENTER_MODEL=opus ./run-bench.sh # a different model
BENCH_PROMPT_REV=<commit> ./run-bench.sh # the implementer prompt as of a commit
./run-bench.sh --summary # pass rate, cost and turns per configurationIt never touches lem, so it takes no driver lock. See bench/README.md.
These are standalone, read-only drivers. Each one proposes changes and never applies
them. Harness proposals go to a human. Suite proposals get a ruling from the curator's
review session in run-loop.sh (below), which escalates judgment calls to a human.
-
run-curate.sh [level]runs one Opus session per regression level. It reads the suite, its perf history and what recent runs cost per chunk, then files one issue on this repo (suite+status:needs-approval) of proposed moves, merges, contradictions, stale references and retirements against a per-level budget. A level is skipped for 7 days after a run, or while its last curation issue is still open. The first run (harnest#2, applied 2026-09-23) moved 40 cases from smoke to complete, leaving smoke at 56 cases against a 30 min / $13 budget. The first measured run after it (61 cases, 2026-09-23) took 61 min and $14.53, so smoke's budget is now 65 min / $16. -
Curator review (
curator_passinrun-loop.sh, on the tester's model). One session per opensuiterequest on this repo, from any agent or from an audit.- It applies what the record settles: a stale reference, or an expectation that a
verified or
breaking-changeissue changed on purpose. - It denies an edit that would make a failing case pass with no such record.
- It escalates judgment calls to you with
owner:don: moves, merges, audit retirements, the security suite beyond a stale reference, and anything touching more than 3 cases.
Its rulings appear in
run-digest.sh's output; reverse one by reopening it with a comment. - It applies what the record settles: a stale reference, or an expectation that a
verified or
-
run-retro.shreads the logs since the last retro and files up to three evidenced proposals on this repo, labeledharness+status:needs-approval. The logs cover cost per role, denials, guard refusals, audit warnings, bounces and bench results.RETRO_EVIDENCE_ONLY=1prints the evidence without running a session. The first (harnest#3, applied) added a "Your shell" section to the tester and regression prompts, naming the tool to use for each pattern the fence denies. -
run-digest.shwrites one line perowner:donissue: the ask, a recommendation and the command that carries it out, plus the median days parked. It writeslogs/digest.md;DIGEST_ISSUE=Nalso posts it on issue N. -
contract/run.pyruns mechanical cases as a plain MCP client, with no LLM.run-regression.shuses it for any suite case markedrunner: script. Seecontract/README.md.
run-release.sh <version> <stage> cuts a diffusers-workflow release (roadmap R14). You
run each stage. The script records each result on the release issue as a marker keyed to
the exact origin/develop commit, so a stage can be rerun or resumed. cut refuses until
every gate has passed, been accepted, or been carried forward on the commit it merges.
./run-release.sh 0.5.0 freeze # opens "Release 0.5.0": the loop now moves only release-blockers
./run-release.sh 0.5.0 check # nothing in flight, lem on develop, master merges cleanly
./run-release.sh 0.5.0 review # four read-only area reviews; blockers become release-blocker issues
./run-release.sh 0.5.0 notes # drafts the notes onto release/0.5.0-notes for you to edit and merge
touch logs/stop-after-cycle # then, once the loop has exited:
./run-release.sh 0.5.0 gates # CI + CodeQL, preflight in a worktree, regression (security complete)
./run-release.sh 0.5.0 status # what passed on which commit
./run-release.sh 0.5.0 accept regression "filed #470-#472, all suite drift"
./run-release.sh 0.5.0 accept check --waive 474,497 "stage 1 ships alone; v2 in 0.6"
./run-release.sh 0.5.0 cut --next 0.6.0-alpha.1 # PR, CI, merge, tag, notes, reopen develop, close the freeze-
Gates are per commit, and some carry. A fix after the gates moves
develop, andcutthen wants the gates again on the new commit.status,gatesandcutfirst carry a gate forward when every commit since its last pass is one it may cross:- the release notes (a commit touching only
docs/RELEASING.md), for review, security and regression; - a closed, verified
release-blocker's fix (type(scope): #N), for review and security only. Regression never carries across code.
The carried marker lists the commits. ci and preflight always rerun, and so does check, since the board moves without develop moving. Anything else takes
accept <gate> <why>: you took an earlier run as good enough. - the release notes (a commit touching only
-
Waive a known problem once.
accept check --waive <n>,... <why>records a waiver for each issue, and every latercheckdrops those issues' problems, so a known, accepted problem (a feature shipping one stage early) doesn't fail check on each new commit.statuslists the waivers. -
lem follows develop. When lem is behind
origin/develop(merging the notes moves it),checkand the regression gate deploy develop themselves if no loop holds the driver lock; a running loop redeploys it each cycle. -
The gates hold lem.
gatestakes the driver lock for its whole run, so a loop started mid-gate waits instead of redeploying lem.run-regression.shruns under the lock (HARNEST_HELD_LOCKnames the holder it may run under). During a freeze the curator holds suite-change requests, andcutremoves anystop-after-cycle*flag left behind. -
The freeze doesn't hide merged work.
checkfails on any open issue develop carries commits for (type(scope): #Nsubjects inmaster..develop), whatever its labels say, and on a held fix awaiting verification. Let it finish, label itrelease-blockerso the loop moves it, or withdraw it in a commit whose subject says "withdraw" or "revert" and names#N.notesgets the same list, plus merged PRs. -
Stale suite text doesn't block. A regression agent that finds a case failing only because a verified or
breaking-changeissue changed the behaviour on purpose files a harnesssuiterequest, not a ticket. The gate counts tickets, and lists those requests separately for the curator. A consumer on the Mac may file that one kind of request too (HARNEST_HARNESS_REPO, defaultdkackman/harnest, names where). On 0.5.0, 9 of the 12 tickets the gate filed were suite drift. -
Real models run on this machine, twice. dw's
scripts/preflight.sh(the preflight gate) andscripts/release.sh(insidecut) both runpytest -m integration: SD 1.5 and mms-tts on the Mac's MPS, from the HF cache. release.sh refuses without an accelerator. Neither checks for the Mac loop, whosedw-mps-serveshares that memory, so stop it (DW_TARGET=local) beforegatesandcut. The release PR's checks, whichcutwaits on, include a Playwright e2e job that runs only on PRs intomaster. -
Security findings stay off the public tracker. The review files each one as a private draft security advisory, and the release issue gets only their count.
cutrefuses while one is a blocker, unless youaccept security. -
Tested offline:
freeze,check,acceptand waivers, carrying,status, the gates' lock andcut's refusal, plus the marker, findings and notes logic. -
Run for real: 0.5.0 (2026-09-27) went from freeze to published tag with this script. Waivers and carrying came after it, from what it cost (roadmap R14).
| Variable | Default | Controls |
|---|---|---|
RELEASE_TREE |
~/src/dkackman/dw-agent-release |
the detached worktree the review and preflight run in |
RELEASE_MODEL / RELEASE_PROVIDER |
the tester's | the review and notes sessions |
RELEASE_EFFORT |
EFFORT |
their --effort |
RELEASE_REVIEW_BUDGET_USD |
8 |
per review area (four areas) |
RELEASE_NOTES_BUDGET_USD |
3 |
the notes session |
RELEASE_REGRESSION_LEVELS |
security complete |
the levels the regression gate runs (complete runs smoke first) |
RELEASE_FORCE |
0 |
1 reruns a gate or review area that already passed on this commit |
./run-loop.sh # forever
MAX_CYCLES=1 ./run-loop.sh # one cycle, then stop
ONLY_ISSUES=227 ./run-loop.sh # work only #227 (comma-separate for more)
IMPLEMENTER_MODEL=haiku SLEEP_SECS=60 ./run-loop.sh
touch logs/stop-after-cycle # finish the current cycle, then exit (a release freeze)
tail -f logs/loop.log # watch from another terminalPreconditions:
claudeandghonPATH, withghauthenticated asTICKET_OWNER.- Passwordless
ssh don@lem. - The agents' own clone at
SOURCE_DIR: rungit clone -b develop https://github.com/dkackman/diffusers-workflow.git ~/src/dkackman/dw-agent, thenbash ./install.shinside it. This is not your working checkout. - On
lem: the dw repo'sscripts/deploy.sh, with the server running under thedw-servesystemd user unit. The script falls back to ascreensession if the unit isn't installed.
| var | default | what |
|---|---|---|
SOURCE_DIR |
~/src/dkackman/dw-agent (-mps on DW_TARGET=local) |
agents' clone of the dw repo, with its own venv; the implementer's and the lead's builds' cwd |
PLUGIN_TREE |
~/src/dkackman/dw-agent-plugin (-mps on local) |
detached worktree reset to origin/develop; where the tester and regression agent load the dw plugin from |
TICKET_REPO / TICKET_OWNER |
dkackman/diffusers-workflow / dkackman |
where the tickets live; the only login whose issues and comments are trusted |
DW_URL / DW_TOKEN |
the target's (http://lem:8765/mcp) / xyz |
the MCP endpoint (dev token, LAN only) |
DW_TARGET / DW_LOCAL_DIR |
lem / ~/src/dkackman/dw-mps-serve |
which server run-loop.sh, run-features.sh and run-regression.sh run against: lem, or local, a server this machine runs from the serving clone DW_LOCAL_DIR (see "Another server") |
SHARED_PASSES |
unset | 1/0 forces whether this loop runs the server-free passes (feature design, docs review, curator review). Unset: lem's loop always does, and another target's loop only while lem's isn't running |
DW_LOCAL_WORKSPACE / DW_ORIGIN_URL |
~/dw-mps-workspace / the dw repo on GitHub |
the local server's --workspace, and what scripts/setup-mac-loop.sh clones |
PROVIDER |
anthropic |
anthropic, ollama or gateway; see below |
IMPLEMENTER_MODEL / TESTER_MODEL |
claude-sonnet-5-5 / claude-opus-5-5 |
per-role models (tester pinned to the exact id, not the opus alias); each has a *_PROVIDER defaulting to $PROVIDER |
TRIAGE_MODEL / TRIAGE_PROVIDER |
the tester's | triage is strong by default: a wrong wontfix/duplicate never bounces back |
REGRESSION_MODEL |
claude-sonnet-5-5 |
run-regression.sh; same *_PROVIDER pattern |
LEAD_MODEL / LEAD_PROVIDER |
the tester's | the feature lead, in both drivers; LEAD_WORKER_MODEL (claude-sonnet-5-5) runs its code subagents |
LEAD_STAGE_BUDGET_USD / LEAD_CLOSEOUT_BUDGET_USD / TESTER_SPEC_BUDGET_USD |
15 / 3 / 8 |
per-session caps in run-loop.sh; run-features.sh has LEAD_DESIGN_BUDGET_USD (6) and LEAD_DECOMPOSE_BUDGET_USD (2) |
EFFORT |
medium |
--effort for every role; override per role with IMPLEMENTER_/TESTER_/TRIAGE_/REGRESSION_/LEAD_/CURATOR_EFFORT and REVIEWER_EFFORT (triage and the docs reviewer follow the tester's) |
IMPLEMENTER_BUDGET_USD / TESTER_BUDGET_USD / TRIAGE_BUDGET_USD |
8 / 5 (8 on local) / 3 |
--max-budget-usd per session; 0 = uncapped |
REGRESSION_BUDGET_USD |
6 |
per regression chunk |
NO_PROGRESS_PARK_AFTER |
2 |
sessions in a row that leave an issue's labels unchanged before the driver parks it with Don; 0 = never |
AUTOCOMPACT_TOKENS |
120000 |
--autocompact for every session |
TESTER_TASK_EVERY |
4 |
run the tester's standing-task session every Nth cycle |
IMPLEMENTER_ESCALATE_AFTER / IMPLEMENTER_PARK_AFTER |
2 / 4 |
bounces before escalating to the tester's model / parking with Don; 0 = never |
DEPLOY_ON_MISMATCH |
1 |
redeploy develop if lem is on anything else before the tester pass; 0 = warn only |
ONLY_ISSUES |
unset | restrict a cycle to these issue numbers |
CASES_PER_SESSION |
3 or 8 | run-regression.sh only; see above |
FALLBACK_MODEL |
unset | --fallback-model; must be servable by the role's provider |
SESSION_RETRY_PAUSE_SECS |
30 |
a session that ends without a result is retried once; a rejected rate limit instead sleeps the driver until the reset |
CO_AUTHOR / CO_AUTHOR_EMAIL |
derived | override the commit trailer on suite edits |
SLEEP_SECS / MAX_CYCLES |
120 / 0 |
pause after an idle cycle; 0 = run forever |
REGRESSION_NIGHTLY_AT |
empty (off) | an hour, 0-23: once a day, at the first cycle boundary at or after it, the loop runs run-regression.sh under its own lock (log logs/nightly-regression.log), so drift reaches the loop before a freeze. Held during a freeze. About $68 and 5.5 h at 0.5.0's suite size |
REGRESSION_NIGHTLY_LEVEL |
all |
what the nightly run passes run-regression.sh: a level, optionally a suite file |
IMPLEMENTER_PROVIDER / TESTER_PROVIDER / REGRESSION_PROVIDER |
$PROVIDER |
where each role's model is served |
IMPLEMENTER_EFFORT / TESTER_EFFORT / TRIAGE_EFFORT / REGRESSION_EFFORT / LEAD_EFFORT |
$EFFORT |
per-role --effort; triage follows the tester's |
HARNESS_REPO |
dkackman/harnest |
this repo: where suite requests and harness proposals are filed |
CURATOR_MODEL / CURATOR_PROVIDER |
the tester's | the curator's review sessions in run-loop.sh |
CURATOR_REVIEW_BUDGET_USD |
3 |
per curator review session |
REVIEWER_MODEL / REVIEWER_PROVIDER |
the tester's | the docs reviewer: verifies docs-review fixes (README, non-guide docs) the tester can't observe |
REVIEWER_BUDGET_USD |
2 |
per docs review session |
LEAD_STAGES_PER_CYCLE |
1 |
stage builds per cycle; one feature in build at a time |
LEAD_DESIGN_IN_LOOP |
1 |
run-loop.sh: run run-features.sh in a cycle when a design or decompose waits; 0 leaves them to a hand run |
LEAD_TREE |
~/src/dkackman/dw-agent-lead (-mps on local) |
run-features.sh: the lead's detached worktree at origin/develop |
CLAUDE_CODE_PRINT_BG_WAIT_CEILING_MS |
1800000 |
how long a headless session waits for a background subagent before it is killed (Claude Code's default is 600 s) |
The standalone drivers have their own knobs:
| var | default | what |
|---|---|---|
CURATE_MODEL / CURATE_PROVIDER / CURATE_EFFORT |
claude-opus-5-5 / $PROVIDER / $EFFORT |
run-curate.sh audit sessions |
CURATE_BUDGET_USD |
6 |
per audit session |
CURATE_EVERY_DAYS / CURATE_FORCE |
7 / 0 |
how often a level is due; 1 audits even if it was curated recently |
CURATE_RUNS |
3 |
recent runs of a level in the audit's cost table |
CURATE_BUDGET_SMOKE / _COMPLETE / _MODEL_SPECIFIC / _SECURITY |
65 16 / 60 20 / 60 12 / 15 6 |
a level's target for one full run: minutes, then USD |
RETRO_MODEL / RETRO_PROVIDER / RETRO_EFFORT / RETRO_BUDGET_USD |
claude-opus-5-5 / $PROVIDER / $EFFORT / 5 |
run-retro.sh |
DIGEST_MODEL / DIGEST_PROVIDER / DIGEST_BUDGET_USD |
claude-sonnet-5-5 / $PROVIDER / 1 |
run-digest.sh |
DIGEST_CURATOR_DAYS |
7 |
how far back the digest lists the curator's rulings |
BENCH_BUDGET_USD / BENCH_JOBS |
4 / 1 |
run-bench.sh: per replayed session, and cases run in parallel |
BENCH_LABEL |
<provider>/<model>@<prompt commit> |
the label a run's results are grouped under in --summary |
BENCH_WORK / BENCH_RESCORE |
$TMPDIR/harnest-bench / 0 |
where replays run; 1 re-scores a kept session without a new one, judge re-judges only |
JUDGE_MODEL / JUDGE_PROVIDER / JUDGE_EFFORT / JUDGE_BUDGET_USD |
claude-opus-5-5 / anthropic / high / 1 |
the bench's judge |
JUDGE_TIMEOUT_SECS / JUDGE_TRIES |
150 / 3 |
bound on each judge try, and how many tries |
Each role has its own model setting, because which roles may run a weak model is a design decision:
- Keep the tester on a strong model. Its verdict gates everything else, and a weak verifier fails silently by approving fixes it can't really judge.
- The implementer is safe to vary. Its mistakes show up in its patches and get bounced. It does hold SSH and auto-mode shell access, so an unvetted model there is a different kind of risk.
- The regression agent is the measured exception. It executes written cases rather than judging fixes. On 2026-09-21, the same smoke run cost $17.85 on Sonnet against $32.64 on Opus, with no loss in what was filed.
providers.sh is the one table that maps a provider to the environment its claude
process needs:
| provider | what it is |
|---|---|
anthropic |
Native Claude models, as an alias (opus, sonnet, haiku, fable, opus[1m]) or a full id. Clears any ambient ANTHROPIC_BASE_URL/ANTHROPIC_AUTH_TOKEN/ANTHROPIC_DEFAULT_*_MODEL, so the label is true. Rejects non-Claude names. |
ollama |
A local or Ollama-cloud tag. Ollama serves the Anthropic Messages API at /v1/messages, so no proxy is needed. Requires OLLAMA_CONTEXT_TOKENS. Rejects Claude names. |
gateway |
Anything else speaking the Messages API (LiteLLM, claude-code-router, …). Requires GW_BASE_URL, and never falls back to an ambient one. |
Under a provider other than anthropic, name every role that runs, because a Claude alias
can't be served there:
PROVIDER=ollama OLLAMA_CONTEXT_TOKENS=65536 \
IMPLEMENTER_MODEL=gemma4:31b-it-q4_K_M TESTER_MODEL=gemma4:31b-it-q4_K_M ./run-loop.shOllama settings. For ollama, providers.sh sets what ollama launch claude would
set, without writing that command's global ~/.ollama/config.json. On top of that it sets
two more:
CLAUDE_CODE_MAX_CONTEXT_TOKENS, fromOLLAMA_CONTEXT_TOKENS. Without it, Claude Code assumes a 200k window. The server'sOLLAMA_CONTEXT_LENGTH(ornum_ctx) must be at least this value, or the server silently truncates. 64k is Ollama's own recommendation.ENABLE_TOOL_SEARCH=true, because MCP tool search is off by default for base URLs that aren't Anthropic's, and this harness is almost entirely MCP calls.
The other provider settings are OLLAMA_BASE_URL (default http://127.0.0.1:11434),
OLLAMA_TOKEN (a dummy token, so no real key is sent), and OLLAMA_STRICT_SUPPRESS (for
models that reject thinking, caching or beta fields). For gateway they are GW_TOKEN
(without it, the ambient ANTHROPIC_API_KEY is sent) and GW_CONTEXT_TOKENS.
Provenance. Each prompt states the model and provider the session runs as. Agents must name the exact model id in every comment they write, because a verification is only worth what the model behind it was. Suite-edit commits carry a trailer with the resolved id:
Claude (<id>) <noreply@anthropic.com>for a Claude model;<model> (via <provider>) <noreply@localhost>for anything else.
"Model-specific" in the regression suite means the video model, not the LLM.
No agent runs with --dangerously-skip-permissions. A headless session never prompts: a
call that would have prompted is denied, and the denial comes back to the agent. What
differs by role is what gets approved automatically.
-
Tester and regression agent:
--permission-mode dontAskplus an explicit allowlist (CONSUMER_PERMISSION_FLAGS). It allows:mcp__dw__*, exceptdelete_modelandupdate_diffusers, which are denied outright;- the
dwskills; gh issueand nothing else fromgh, sincegh apiorgh repo clonewould expose the source;- file tools for the suite files and
qa-bible.md; - a few read-only shell helpers.
There's no
ssh,curl,python, orgitwrite; the drivers commit suite edits themselves. The one gap is thatRead/Edit/Writecan't be limited by path, so the prompts cover it. If a legitimate need is denied, widen the list on purpose. -
Implementer:
--permission-mode auto. Its shell use (git,gh,ssh lem,pytest,uv, …) can't be listed up front, so the classifier approves routine work and denies destructive or exfiltrating actions. The classifier's description of the environment isagent-settings/implementer.json, passed with--settings. -
Feature lead, design and decompose:
dontAskplusLEAD_DESIGN_PERMISSION_FLAGS: read-only source, read-only git, read-onlydw,gh issue,WebFetch, and writes only to /tmp and to its own plan comment. Its builds and close-outs use the implementer's flags.
Guard hooks. A PreToolUse hook,
agent-settings/hooks/guard.py, refuses the protocol
violations that are cheap to spot on a command line, at the moment of the call. The agent
gets the reason back and can correct itself, rather than the audit finding it later.
- The implementer can't close an issue as
completed, addstatus:verified, lift anowner:don/status:needs-approvalpark, push tomaster, or force-push. - The implementer can't hand off (
status:fixed-pending-verify) with uncommitted changes, withrufffailing on files it changed, with the UI'scheck/lint/testfailing when it changedui/, or with a test failing that passed ondevelopwhen the session began. The driver exports that commit asHARNEST_BASE_COMMIT. - The tester and regression agent can't close as
completedor addstatus:verifiedin a session that has made nomcp__dw__*call. - No role can add an
owner:*label without removing one in the same command.
The hook is wired through agent-settings/implementer.json and
guard_settings consumer (providers.sh). It matches command text, so it catches mistakes, not a
determined workaround; the audit is still the backstop.
What sessions don't get. Every session runs with --setting-sources project,local and
CLAUDE_CODE_DISABLE_AUTO_MEMORY=1, so no user-level plugins, hooks, memory or MCP servers
reach an unattended agent. --tools limits each role to the built-in tools it uses.
--mcp-config together with --strict-mcp-config gives each role only the dw server,
with no account-level connectors. The tester and regression agent load the dw plugin
with --plugin-dir from PLUGIN_TREE, which the driver resets to origin/develop (the
commit lem runs) before every tester pass. A skill fix is therefore testable in the cycle
it merges.
That leaves two deploy paths, and the implementer says which one a fix used:
- Server code: merge to
develop, then deploydeveloponlem. - Plugin or skill changes: merge to
developand push, with no restart needed.
Everything streams to the terminal and to logs/loop.log, prefixed by session:
[implementer:triage], [implementer:#145], [tester:#145], [tester:task],
[tester:closures], [regression:smoke.2], [regression:smoke.sweep],
[lead:#N], [curator:harnest#N]. Each role also has its own .log and a .jsonl of the
raw events.
-
grep usage: logs/loop.log: one line per session, with turns, duration, cost, peak context and token totals. Each session also logs amodel:line with the resolved id. -
grep '\[audit\]' logs/loop.log: drift from the protocol, checked after each session. It flags an open issue without exactly oneowner:*label, acompletedclose by anyone but the tester, an issue a session left stranded, and a suite commit that removed lines. The audit only logs; it repairs nothing. -
logs/progress.tsv: the no-progress ledger. An issue that sessions leave unchangedNO_PROGRESS_PARK_AFTERtimes in a row is parked with Don instead of re-run. -
After every cycle, the driver prints a status board with one line per open issue: number, status, owner and title.
-
python3 dashboard/serve.py(port 8780, or--port/DASHBOARD_PORT), then open http://127.0.0.1:8780. It's a local, read-only page, served from this machine only, that refreshes every few seconds.--host 0.0.0.0shares it on the LAN (http://<this-mac>.local:8780). Whatever it binds to, it answers only loopback, private-range and link-local clients, and only an IP,localhostor.localin theHostheader, so a port-forward can't publish it and a web page can't reach it through DNS rebinding. It shows:- the drivers running, with the lock each target holds;
- what needs you, each linked to GitHub:
owner:donissues on both repos,harnessproposals awaiting approval, and draft security advisories. It re-checks once the loops have been quiet 20 s after a session starts or ends (at most once a minute, at least every 5 min), backs off 15 min if GitHub answers with a rate limit, and a new item shows in Events; - what the current session on lem (
loop.log) and on the Mac (loop.local.log) is doing: turns, context, last tool call; - the recent sessions, with their cost;
- a release's gate log, while one is running;
- a live tail of any file in
logs/, with a filter.
It reads files and
ps, plus read-onlyghfor that list: no ssh, no lock.
run-loop.sh the cycle: implementer, feature lead builds, tester, curator review
run-regression.sh regression driver
run-features.sh feature lead: design and decompose sessions (lock-free)
run-bench.sh replay benchmark of the implementer (offline)
run-curate.sh suite curation audit: one proposal issue per level
run-retro.sh retro: evidenced harness proposals, filed on this repo
run-digest.sh one-line-per-issue digest of the owner:don queue
dashboard/ local read-only dashboard of running drivers and logs (serve.py)
contract/ script-run regression cases and their MCP client
bench/ benchmark cases, replay note, results (see bench/README.md)
providers.sh provider table and shared helpers (isolation, permissions, logging, audits, queues)
lib/classify.jq the ticket protocol's state machine: issue -> queue
measure-base-ctx.sh measures turn-1 context for a flag set
agent-settings/implementer.json auto-mode classifier's picture of the implementer's environment, plus its guard hook
agent-settings/hooks/guard.py the guard (protocol invariants and the hand-off gate)
agents/ role prompts; the drivers build each session's from parts (role_prompt)
implementer/ core.md + fix.md or triage.md
tester/ core.md + verify/handoff/answer/closures/task/spec.md, cases.md, standing-task.md
lead/ core.md + design/decompose/build/closeout.md
regression/ core.md + run-cases.md + chunk.md or sweep.md
curator/ suite curator: audit (run-curate.sh), review (run-loop.sh)
RETRO.agent.md retro role
regression-suite-{smoke,complete,model-specific,security}.md
regression-perf/ append-only per-case metric history (JSONL)
HARNESS-ROADMAP.md planned evolution of the harness
CLAUDE.md notes for Claude Code sessions working on this repo
qa-bible.md tester's memory across cycles (gitignored)
logs/ session output (gitignored)