0025 — The flow runs without a human: green merges itself, the fixer merges, and the driver assigns
- Status: Accepted (the posture and the names). The code half is in flight in three lanes as this is written — the
pr_drivemerge, theredmain.tsline, and the workflow renames — and this ADR does not wait on any of them, because ADR-0004 puts the record at decision time and this decision changes how six systems relate. - Date: 2026-09-12
- Deciders: Gera, 2026-09-12, across one working session, recorded verbatim in
~/.claude/jobs/fleet-reset-2026-09-12/DESIGN.mdandPLAN.md. The session that wrote this was auditing the operating docs against that design and found the contradictions listed under What this supersedes; the audit is what forced the ADR, which is ADR-0004 working as intended. Specialist housing (decision 7) rests on Astra's written test, receiptf71df4d01in~/.claude/jobs/fable/.
The headline, in his words
"I'm expected to do nothing throughout the driver flow."
That sentence is the acceptance test for everything below. Any design whose
normal path stops and asks him has failed it. The exception is about 5% — a
genuinely important decision — and the explicit instruction is that we DO NOT
BUILD AROUND THAT 5%. A queue of parked questions is now a design failure, not
a healthy backlog; two were parked when this was written
(smith-decision-b89238852, b89235012), and under this posture that is the
defect, not the process.
The decisions
1. Auto-merge. Every PR, every repo. Nothing holds for a human.
When CI is green and the cloud reviewer says 🟢, it merges. No Slack ask, no days-long wait, no human ✅ path.
- This includes human-authored PRs and includes
propflowai. Chosen with the blast radius stated out loud: a half-finished human PR that happens to be green will merge itself. SmithApprovalWorkflow's human wait is deleted. Its machine gate — PROPEN+ mergeable + CI all-green + claude-bot 🟢 — moves into the PR driver, which is the one place a PR is already driven from open to merged.- The safety check moves rather than disappearing. Merge on green; and if main breaks, red-main appoints a fixer who fixes AND merges without asking (decision 2). The counterfactual to a bad merge is not a frozen main, it is a red main that somebody eventually notices.
- Residual risk, accepted knowingly: a PR that is green but WRONG and does not break main is caught by nobody. The reviewer's 🟢 is now the only remaining quality gate anywhere in the system, and it is load-bearing in a way it was not yesterday. Recorded as a known hole, not as an oversight.
2. The fixer has full authority, and its scope is the RED
"It could merge a PR and everything. It could do anything it needs to do just to make sure that everything is clean."
- A fixer may merge.
supervisor/src/redmain.ts:1263— "Open a PR. Never merge — that is a human decision." — is deleted. It was one line of generated prose;redmain.ts:1363already passes--allow-push, so the permission existed and only the sentence blocked it. - Scope is the RED, not the work that caused it. The non-goals in
fixer-SKILL.md§8 stand unchanged. Full authority over the red is not authority over the codebase. - Why the risk is smaller than it reads: main is ALREADY red when a fixer
starts, so the counterfactual to merging is not a safe green main — it is red
until somebody notices, which is never in 52 of 172 recorded appointments
(~30% never spawned a lane at all). And the finish line is self-verifying: a
push:mainrun is green or it is not. - The fixer announces, so no other lane tackles the same red — ONE message
when it takes a red, ONE when main is green. ⚠️ As a WRITE TO A SHARED
SURFACE, never N messages. ADR-0003
is signals, never calls: a fixer that messages every agent IS a caller, and it
cannot know who "everybody" is anyway. The write half already exists and is
already read —
claim.json+status.jsonunder~/.claude/jobs/red-main/, consumed byagent_smith/red_main_owner.py:91-93viaslack_socket.py:2909-2935. What was missing is that AGENT SESSIONS do not read it, so worker and driver doctrine now says check the claim before you worry about a red — which is a read, not a call. - This does not loosen ADR-0015; it completes it. ADR-0015 rule 4 already said the assigned fix is the only merge to main any lane queues while red. What is new is that the assigned fix does not then stop and wait for a person.
3. The driver is a COORDINATOR. It orchestrates; it does not act.
"I would give it a dock. The dock is gonna have items… the driver is just gonna say, okay, these action items, I'm gonna go ahead and give them to sub-agents… four sub-agents can tackle four things if they don't overlap. But if they do overlap, maybe the driver is gonna say, okay, we're gonna go one at a time… The driver is really driving the orchestration. They're not driving the actions. They're driving the setup with the sub-agents. They're really like a coordinator."
- Setup hands the driver a DOCK, not a list of items. The driver assigns the dock's action items to sub-agents — parallel where they do not overlap, serial where they do.
- ⚠️ The overlap decision is NOT new machinery. It is the
optimizerspecialist's exact existing job — "says which rows a Driver may work AT ONCE, only where independence is PROVABLE" — which is built, on PATH, and today has zero callers. Wire it; do not rebuild it. The same is true ofcleaner: close and assignment signals must TRIGGER both, and today both run only when typed. - One driver. The two-drivers collision stands ruled against.
- If no dock exists the driver creates one, from the current one as a template. "We should always have one."
- The driver opens NEW action items and they COUNT — "Always add and the new items do count but we will eventually close." Only items pertaining to the nature of the work — strong wins — never tangential ones. There must be no rule that says stop opening relevant work because we are tired. Consequence accepted: the finish line MOVES, so 100% is not monotonic. The real risk — opening faster than closing — is observable: track items opened vs closed per hour and surface it as a SIGNAL. Do not turn it into a rule that stops the driver finding work.
4. Work is ASSIGNED, never self-claimed — .claims becomes .assigned
This is a MODEL change, not a rename, and it is why the word moves at all.
claimed = the driver helps itself off the board. assigned = it works what it
is given. Today's driver drifted its own goal to "whatever 14 merged PRs
prove" while the board sat at 48%; a driver that cannot pick its own work is
the fix. The word carries the model, so the word changes.
There are two assignment levels and one word, deliberately:
- Gera assigns the DOCK, at setup, and nothing after. He is not in the per-item loop.
- The driver assigns each ACTION ITEM to a sub-agent (decision 3).
⚠️ Collision, accepted with eyes open. assign is 983 hits in propflowai's
domain layer — assignedVendorCompanyId (534), assignment (301), assignee
(20) — vendors assigned to work orders. This is weaker than the lease
collision ADR-0023
refused: that one collided THREE ways inside the harness itself, in the same
prose. This one is cross-repo and cross-language (agent-smith Python vs
propflowai TypeScript), no shared symbol table. THE RULE TO CARRY: assigned
in the harness always means ACTION ITEMS, never vendors. If harness prose ever
says "assigned" about a vendor, it is wrong.
Words tested and rejected, so nobody re-derives them: lease (a product entity
- the renewals saga + a page — three senses in the harness),
owner(ownerId, property owners),hold2846,take1185,reserve237,adopt162,docket48,lock(rejected by Gera: "confusing"),flight("confusing"), andassignfor anything but action items (assignedVendorCompanyId, 534). Free but unused:undertake(0),allocate(10).
5. /driver mints a FRESH workflow, and it starts from a SETUP TABLE
/drivermints a fresh workflow each time. Setup, thendriver start, then it runs to completion. Nothing persists between runs, so nothing goes stale.- The setup step prints a TABLE, columns
# | specialist | enabled | parameter-ref | validation. His word is "setup" — not onboarding, not a wizard. - All specialists ON by default. Opting out is the exception, by number.
finisheris NEVER off — it is the only thing permitted to say the work is done (decision 6), so "on by default" is not enough for it. - Each row names the ONE parameter that specialist needs:
updater— which DOCK does it write the closingprsref onto;slacker— where does it POST;finisher— what CLAIM counts as complete;fixer— what is its RANGE, which repo(s) if main goes red;nudger/cleaner/optimizer/decider— the same shape, their own parameter. - Every parameter VALIDATES AT SETUP against a real referent — a dock slug that resolves to a published tracker, a Slack channel that exists, a repo that exists — and is RE-CHECKED ON USE. An invalid ref on an enabled specialist blocks start.
- Why validation and re-check, and not a typed table: a typed table is a REMEMBERED FACT, and his own standing rule kills those — "if a fact has to be REMEMBERED to stay true, it will be false. Derive it or hook-check it." A wrong dock slug must fail AT SETUP, not leave the updater silently writing nothing for three weeks, which is the failure already on this record.
- It has nowhere to live today:
task.jsonhas exactly 10 keys, none about specialists, and "specialist" appears 0 times instart-operator,operator-here,drive-gate.py,task-eventorregister-work.
6. DONE is a PR that addresses the point, and the SUB-AGENT self-certifies
"If there's a PR then the sub-agent can mark complete… if the sub-agent wants to validate on prod they're encouraged to but not required to because in some cases we probably can't."
- An action item is done when the sub-agent that did the work says so and a PR addresses the point. Production validation is ENCOURAGED, NOT REQUIRED.
- ⚠️ This retires "merged is not shipped" AS A GATE. See What this supersedes for exactly which clause in which document.
- ⚠️ KNOWN FAILURE MODE, MITIGATED NOT IGNORED. Self-declared completion is
exactly how the last driver drifted — the recorded lesson
attested-checks-let-a-goal-driftsaysexit:/file:acceptance items are self-declared, no command is run, and the board-pinned goal migrated to whateverpr:refs could prove. The cheap mitigation that preserves self-certification: a sub-agent's "done" MUST NAME ITS PR. Then the claim is checkable by anyone later, without anyone having to grade it now. - The limit of that mitigation, recorded on the day it was adopted. On
2026-09-12
scripts/preflight-address-collisions.tsread FIVE address collisions inpropflow-prodwhere PR #7713's body had recorded THREE the day before. A PR body is a snapshot of a moment, and the moment had passed — "names its PR" would not have caught that. This is the argument prod evidence was drawn for, and it is why prod evidence survives as the stronger grade a sub-agent is encouraged to reach, rather than being deleted. - Only
finishermay declare done. It recounts the published dock and exits 0 ONLY at 100%. Nothing else may declare done. This is already howfinisheris built and is already wired to the Stop hook viadrive-gate.py:726— it ran 64 times on 2026-09-12, every one exit 1, correctly refusing to let a driver stop at 48%.
7. The decider answers ~95%, and the test is IN PLAN + REVERSIBLE
Target: ~95% answered without a human. Measured 2026-09-12 across 337 receipts: 61% (207 RESOLVED, 72 NEEDS HUMAN, 53 ERROR, 5 WALLED).
The escalation test, replacing the soft word "questionable":
- Is this IN THE PLAN?
- Is it REVERSIBLE?
In-plan AND reversible → the decider rules, including deleting or adjusting data when the plan calls for it. Out of plan, OR irreversible → human. Two hard tests an agent can apply at the moment it asks beat one adjective it will argue with.
The 16% error rate is PLUMBING, not judgment, which is what makes the target reachable: 45 of the 53 errors are the underlying
claudeinvocation failing (38 "both runs failed… claude exited 1" with EMPTY stdout, 5 "no stdin data received", 2 session limit, 1 not on PATH).The 21% NEEDS HUMAN is mostly LEGITIMATE — the order-reversal check catching its own instability ("THE TWO PASSES DISAGREED, AND ONLY THE OPTION ORDER DIFFERED") and genuine holds. Do NOT tune this number down by loosening the check. ADR-0019 is the mechanism that has to carry the remainder.
EVERY invocation writes a receipt, including failures. Exit 0 with no receipt must be impossible. It happened in this very session:
fable-decideran at ~14:57 with a full--ctx-file, printed "asking Fable twice, in parallel…", exited 0, and wrote no verdict and no receipt. It is not in the 337 and not in the 53. A failure that leaves no record cannot be counted, alarmed on, or fixed — and under "Gera does nothing" it is the worst mode: the driver waits on an answer that will never come.The dock's decision object gains
owner, with THREE values, not two:value means derived from deciderthe ladder ruled receipt outcome RESOLVEDhumangenuinely his receipt outcome NEEDS HUMANFAILEDthe run errored, walled, or wrote no receipt at all ERROR/WALLED/ absent⚠️ Two values are not enough. A decision that errored belongs to NOBODY; with only two values it renders as neither and disappears from the dock while nothing waits on anyone. That is the silent stall, and under "Gera does nothing" it is the failure that costs a whole run. The FAILED state must be loud.
DERIVED, never typed. The receipt already records the outcome and
answered_by;owneris read from it. Gera never marks a row, so the field cannot go stale — his standing rule, satisfied by construction.
8. Specialist housing: SEVEN activities, ONE agent
Astra's written test (receipt f71df4d01): bounded contract → Temporal
ACTIVITY; investigative ownership → AGENT. Cross-rung integration by SIGNALS,
never activity calls (ADR-0003).
| housing | specialists |
|---|---|
| activity | decider · nudger · cleaner · optimizer · finisher · slacker · updater |
| agent | fixer |
⚠️ updater moved, and Astra's own rule is what moved it. Astra made
updater an AGENT because "updater requires judgment" — full vs partial
closure. Gera has now removed that judgment: "the updater should be just focused
on passing information into the docs… it should be self-determined." The
sub-agent decides; the updater only writes it down. With the judgment gone the
updater has a bounded contract, so under Astra's own test it is an activity.
The record is kept this way round on purpose: the test survived, the answer
changed.
Liveness must not lie. It must never report a working specialist as never-run
— specialists.json says everRun:false for nudge and fable-decide while
fable-decide has 336 receipts — and must never silently drop an unknown name: a
well-formed optimizer2 line is dropped at specialists-snapshot:50. Both are
ADR-0009 failures: a signal
that reports healthy-but-blind is worse than one that fails.
9. Names — final, and cheap because everything was stopped first
| mechanism | old | new |
|---|---|---|
| a driver session driving one task to a criterion | OperatorTaskWorkflow + DriverTaskWorkflow |
DriverWorkflow |
| the action items a driver has been given | .claims |
.assigned |
| one execution per job, however many doors it arrives through | SmithWorkClaimWorkflow |
SingleRunnerWorkflow |
| drives one PR to merge | SmithPrDriveWorkflow |
PrDriveWorkflow (now merges) |
| a PR approval held for a human | SmithApprovalWorkflow |
DELETED — the human wait is removed and the machine gate folds into PrDriveWorkflow |
| a parked human decision | SmithDecisionBlockWorkflow |
AnswerWaitWorkflow |
| a pane stuck on a dialog nobody is answering | SmithPromptResponderWorkflow |
StuckPaneWorkflow |
| per-item generation authority | SmithRowLeaseWorkflow |
PENDING Fable — Astra: keep it, a RETRY is a second writer even with one driver |
Drop the Smith prefix throughout. It named the HOST, not the user, and was
already inconsistent — 10 prefixed types, 4 unprefixed, one namespace.
Why this is a rename in place and not an additive cutover.
ADR-0021
§5 requires an additive cutover for any workflow rename: register the new type
alongside the old, keep the old registered until a live query returns zero
running executions. That rule exists only to protect running executions —
worker.py says it plainly: the legacy type must stay registered for as long as
one execution still carries it in its history, because a worker missing that name
fails the workflow task RETRYABLY, so nothing surfaces to anyone. Stop everything first and the constraint disappears, which is why the reset
is sequenced stop-then-rename rather than the other way round: zero running
executions means rename in place and delete the old names outright — no dual
registration, no drain period, no migration to maintain.
⚠️ THE CONDITION, NOT A DATE — and this ADR does not claim it has been met.
ADR-0021 decision 5 says the old name retires "only when a live query returns
zero running executions of that type — not on a guess, and not on a date", and
that rule survives here unchanged. PLAN.md records 72 running executions at
~14:40 on 2026-09-12 and the intent to end them; the rename lands when the
query below reads 0, and not before.
temporal workflow count --query "ExecutionStatus = 'Running'"
Written as a command because a retirement condition nobody can execute is a fact somebody has to remember.
What this supersedes — precisely, and nothing more
Per DESIGN.md §8 and ADR-0023
decision 6: ADRs are not rewritten. Each entry below is marked superseded on
its own Status or Date line, appending no line, so every line-number citation
into those files stays true — PR #141's precedent.
| record | clause superseded | what stands |
|---|---|---|
| ADR-0012 | "Can count Rung 5" — shipped derived only from a merged PR AND a prod_evidence permalink; a merged-but-untested fix does not count. Decision 6 above replaces the gate. |
Everything else: the scheduled pipeline, the review merging its own PRs, and the merge_guard. See the open question below — the guard is a restart interlock, not an approval gate, and is NOT deleted here. |
| ADR-0021 | §5's additive cutover target DriverTaskWorkflow, and its retirement query. Decision 9 renames in place to DriverWorkflow because zero executions are running. |
Decisions 1, 2, 3, 4, 6, 7 and the 2026-09-11 amendment. The role is still the Driver; /operator is still a live door; the product's operator still does not move. |
| ADR-0023 | Decision 4's "the harness does not say lease for a work claim — it says CLAIM", for the DRIVER'S work items only. Decision 4 above makes those .assigned. |
Decisions 1, 2, 3, 5, 6, 7 and 8. The yielding rule, "action item", the row_lease split, and the interface lags are untouched — and SmithRowLeaseWorkflow is still PENDING, now on Fable. |
propflow-docs CLAUDE.md §A tracker is GENERATED, ARTIFACT-STYLE.md §Trackers, and bin/refresh-tracker (propflow-docs #47, p6-merged-is-not-shipped, 2026-09-10) |
"Merged is not shipped" as a GATE. That rule is why the board reads 27 shipped against 114 merged-but-never-exercised; under decision 6 those 114 are done. | The standard is superseded, NOT deleted. It stays as the record of why the distinction was drawn, and prod_evidence stays as the STRONGER grade a sub-agent is encouraged to reach. The prod_evidence key itself is an interface (ADR-0023 §6) and does not move. |
Not superseded, and reinforced: ADR-0003 (the fixer announces by a shared-surface write, never N messages), ADR-0004 (this ADR exists because of it), ADR-0009 (the liveness lies above are failures under it), ADR-0015 (one owner per red — decision 2 lets that owner finish, it does not add a second), ADR-0019 (the mechanism that has to carry the 95%).
Open, and named rather than papered over
ADR-0012'smerge_guardversus decision 1. The guard refuses a merge that would restart the worker while a person is mid-turn with Smith. It is not an approval gate and decision 1 does not name it, so it is left standing here — but "every PR merges on green" and "some merges wait on a live human turn" are two sentences that will be read together by someone. This needs an explicit ruling, not an inference.scripts/backfill-vendor-identity-claims.tscarries a written prohibition in its own header — "A HUMAN RUNS--mode=apply, NEVER AN AGENT." It predates this posture and has not been reconciled with it. Under decision 7's test it plausibly survives (customer data, irreversible → human), but do not treat the new posture as having silently overridden a rule written into code about customer data. Decide it explicitly.- Two ADRs are numbered 0014 and two are numbered 0024. Both 0024s are cited
by
SYSTEMS.mdunder the same label. Renumbering would break every existing citation, so this is recorded, not fixed.
Consequences
- The operating docs move today, in place, because they are instructions and
a wrong instruction is a live defect: the five
~/.local/bin/canon/*-SKILL.mdmanuals,SYSTEMS.md,docs/the-four-roles.md,docs/the-specialists.mdandCLAUDE.md. The ADRs above get aStatusbanner and nothing else. docs/the-four-roles.mdremains the single canonical vocabulary table (ADR-0023 decision 8)..assignedlands there, not in a second glossary.- The reviewer's 🟢 becomes the system's only quality gate. Anything that degrades it — a reviewer that times out, a reviewer whose token expired (ADR-0005) — is now a first-order outage, not a nuisance.
- A parked decision is now an incident. Under the headline,
blocked raisequeue depth is a defect metric.