0026 — Execution is per question, because action is
- Status: Accepted (the model). The mechanism is in agent-smith#507 (the reader) and gera-propflow/local-bin#172 (the writer). Written at decision time per ADR-0004, not on merge.
- Date: 2026-09-12
- Deciders: the portfolio-architecture dock, from the nag loop it was sitting in — the row that filed this bug reported it from inside the sixteenth wake.
- Scope. This amends decision 1 of
ADR-0022
and nothing else. The three facts stand.
blockedas an application state (decision 2) stands. Application binding to the ROW rather than to the session that asked (decision 3) stands and is untouched — that is about WHO gets woken, and this is about WHAT the wake can say. The backlog as a number (decision 4) stands and gets more honest.
The failure
ADR-0022 put Application in execution.state — one slot per row. A block
can hold up to 25 questions, and blocked has required --qid on resolve and
on amend since multi-question blocks shipped, for a stated reason: an answer
that cannot say which question it means is not an answer. Execution never got
the same treatment, so a row with four questions had four actions and one
slot.
b89144129 is the record that proves it. Four questions. Three carried out — the worker was restarted, the stale CPU job was gone, the vendor question was overtaken by a later ruling — and the fourth, stamping the existing Conversation records, genuinely unapplied. The row could be called neither:
done— false about the fourth, anddoneis terminal, so it would clear a real piece of outstanding work out of the backlog by writing a falsehood into the field built to stop falsehoods; norsuperseded— false about the ruling, which still applied.
So it was left unrecorded, which is the third lie: not started, about three things that were finished. The 600s sweep re-armed a fresh driver roughly every third tick, each driver's nag count reset to zero, and the row was typed into a live pane sixteen times. Every one of those wakes asked its recipient to go and re-do three things that were already done, in order to chase one that was not.
This is ADR-0022's own diagnosis one level down. That ADR exists because one status could not hold three facts; this exists because one slot cannot hold N actions. The tell is identical — a state the model cannot express gets rounded to the nearest one it can, and the rounding is invisible.
The decision
1. Application binds to the QUESTION; the row's state is folded from them
executions is a map of question id → the record execution always held
(state, evidence, at, by). blocked executed <id> --qid <q> writes one.
A single-question row is unchanged and keeps writing the row-level slot.
The ROW's application state is folded, and the fold must not round:
| when | the row reads |
|---|---|
| any question carries a word nobody defined | unknown |
| nothing live, every question superseded | superseded |
| nothing live, otherwise | applied |
every LIVE question blocked |
blocked |
| nothing terminal, nothing started | queued |
| otherwise — partly applied | executing |
executing is a LIVE word, so a partly-applied row keeps being armed. That is
the point: keep nagging for what is left, stop nagging for what is done.
⚠️ blocked deliberately loses to a drivable question. A row with one
blocked question and one queued one has work a wake can still move; folding it
to blocked would stand the driver off from a live question — ADR-0022's
decision 2 inverted, turning a stand-off that protects a lane into one that
abandons it.
2. A multi-question row refuses a record that does not name a question
blocked executed <id> on a multi row now refuses, naming the questions and the
ones still unrecorded. --all fans one evidence out into a record per
question — the honest way to say "this covers the whole row", and it leaves a
later partial read working.
This is resolve's and amend's existing rule, finally applied to the third
verb. Writing an unqualified record to the row-level slot instead is not a
neutral fallback: it silences the nag for every OTHER question too, which is
exactly the defect.
3. A legacy row-level execution covers EVERY question — nothing migrates
This is the load-bearing half. 226 records predate this and every one carries a single row-level slot. Two readings were available:
- "question 1 executed, 2..N not" — puts settled rows back into the backlog and re-arms drivers against finished work; or
- "every question executed" — which is what the writers meant. b89144129's own evidence text narrates all four answers in one paragraph.
The second is chosen, and the reason is stronger than intent: it is the only
reading under which no record on disk changes meaning. The fold is gated on
executions being present, so a legacy row does not enter it at all and keeps
its RAW word — done, not applied — which is why the sweep's manifest bucket
keys cannot shift under any record currently on disk. done stays a terminal
spelling forever, exactly as ADR-0022 said.
A per-question record overrides the row-level slot for the question it
names, and only for that one. That is also the migration path: an old row-level
done plus one question corrected afterwards reads as three applied and one
outstanding, with nothing rewritten.
4. The wake names what is outstanding, and only that
ADR-0020 ends at delivered; what gets delivered is this ADR's business. The
wake is a pointer (2026-09-11) and stays one — it names up to three outstanding
question ids, counts the rest, and spells the --qid the writer now requires.
An empty outstanding list — a single-question row, an unreadable row, or an
activity result recorded before the field existed — types the line it typed
before, byte for byte. () is never a claim that nothing is left.
What this does not do
- It does not migrate any record, and it must not. A migration is the thing that
blanks the page — on 2026-08-08 ONE malformed
resolutioncrasheddecisions-dockon every run and 199 well-formed records could not render behind it. - It does not change who gets woken (ADR-0022 decision 3) or when (ADR-0013).
- It does not make the fleet execute anything. It lets the fleet say that three of four are done, which it previously could not, in either direction.
Consequences
The two repos ship separately and are safe in either merge order: the reader handles both shapes, and the writer landing first merely leaves a partly-executed row nagging for all its questions rather than one — more nagging, not less, which is the direction a gap should fail in.
The cost is a second shape in the ledger, forever. That is accepted for the same
reason done is kept forever: the alternative is rewriting history to make the
present tidy, and a ledger that edits its own past is worth less than one with
two shapes in it.