0013 — Answered is not executed, and the sweep chases both
- Status: Proposed (flips to Accepted the first time a swept unexecuted row ends with its task moving off
awaiting-human) - Date: 2026-09-07
- Deciders: Gera, asked directly on 2026-09-07 — "When you answer a question
on the decisions page, nobody actually does the work. Where should I fix it?"
→ "Teach the robot to pick up answered ones" — and the session that found
it (
ccswitch, while closing out the fleet-capacity item). Written at decision time because ADR-0004 says a decision that changes how systems relate is not done until it is, and this one changes how the decisions page, the block driver and the task ledger relate on the path every answer travels.
The failure
Measured 2026-09-07: six decisions Gera had answered sat between one and six
days with nobody acting on them. Each row carried his verbatim choice —
"Merge all three in order: #6915, then #370, then #371", "Turn it on — I set
the switch, restart Smith". Every raiser's task was still awaiting-human.
Two writers, each correct alone:
- The decisions page closes the row when he clicks. Five of the six carry
via: agents-web. That behaviour answers his 2026-08-05 report — "I click answers and they are NOT BEING RECORDED" — because the Decisions tab renders the ledger, so a row left open reads as still waiting on him. - The driver's whole design is that the row stays open until the raiser has EXECUTED. Its own words: "this driver does NOT close the row the instant an answer appears — that would write 'executed' over work nobody did." It wakes, it nags, and the happy path is the raiser closing its own row.
Together the first defeats the second. SmithDecisionBlockWorkflow._apply reads
RESOLVED on its next poll and returns _closed_by_someone, which reports
closure.DONE — "the happy path" — and terminates. The raiser is never woken.
The sweep will not re-arm it, because open_block_records returns state == "open" only. The answer is recorded, durable, correct, and unexecuted.
This is the 2026-08-10 failure the driver was built to delete ("40 answers landed, an hour later 28 still read as unresolved, because nothing was polling"), reappearing one layer up: now something polls, and the poll is switched off by the click.
The decision
answered is not a terminal state, and the sweep drives it.
open_block_recordsalso returns rows that areresolved, whose resolutionreasonisanswered, that carry no execution record, and whose raiser owns a non-terminal task. They are stamped so the driver knows what it is driving. Rows are still skipped forwaiting_on, unreadable, id-less.- The driver, for such a row, does not treat
RESOLVEDas somebody else's close. It goes to the wake-and-nag path it already has, quoting the recorded choice, and ends onclosure.PARTIALwith the remainder named — recorded, not executed — neverDONE, which it cannot know. - The bound is liveness, not a counter. An unexecuted row is driven only
while
livenesssays its raiser is ALIVE. Waking a dead session does nothing, and a lane whose raiser is gone needs a person or a new operator, not a nag — so it stays onblocked outstanding's worklist instead. This reuses the fleet's one three-valued primitive and refuses on UNKNOWN, the same gate_close_it_ourselvesalready carries.
Why not the alternatives
Stop the page closing the row. Restores the original design exactly, and reintroduces 2026-08-05 unless the Decisions tab first grows a third rendered state. It changes the surface Gera uses daily to fix a defect behind it; the sweep change does not.
Full separation (open → answered → executed → closed) with the page
rendering all three. The cleanest end state, and the right eventual shape. It
touches the page, the driver and the ledger contract at once, and none of that
is needed to stop answers being stranded today. Left open deliberately; this ADR
does not foreclose it.
What this does not fix, stated so nobody assumes it
- A row whose raiser is dead is still not executed by anything. It is named
by
blocked outstanding(local-bin#86, which cross-references the task ledger so "no execution record" becomes "provably not executed") and it waits for a human or a new operator. Reviving a lane on an answer that arrived after it stopped listening is a separate decision. - The driver still never interprets prose. A note-only answer is woken and nagged like any other and left for a person.
- Some of the six answers are days old and may be stale on their own terms — "Merge #6915 then #370" is not obviously still right. Driving a row delivers the answer to the raiser; it does not vouch for it.
Consequences
decision-blocksis in the catalog as of #118, so the sweep's liveness is now watched. This ADR is what its entry's "what this system does NOT do" paragraph will be amended to describe once the change lands.- The sweep gains a task-ledger read. That is a second ledger in a module whose docstring is precise about which functions touch the filesystem, and that claim gets re-derived in the same PR rather than left to rot.
- A row can now be driven twice in its life: once open, once answered. The manifest already names what is being driven, so this stays visible.
Amended by ADR-0022 (2026-09-10)
Two things this ADR named and did not do are now done, and one thing it said about the catalog turned out to be the reverse of what happened.
- "Full separation... the cleanest end state, and the right eventual shape... left open deliberately" is ADR-0022. The forcing case is one this ADR could not express at all: decided; application blocked, because a human identity is required.
- The narrowing here is keyed on the raiser's own sid, and that is too
narrow. A sid dies with its pane, so a respawned Operator's inherited work
read as a dead lane. Measured 2026-09-10: of the four newest stranded rows,
zero joined the task ledger by raiser sid, and
blocked outstanding's "provably not executed" section - the section this ADR exists to fill - was empty while 176 answers sat unapplied. ADR-0022 keys it on the task. - The
decision-blocksentry's "does NOT do" paragraph was supposed to be amended "once the change lands". It was not; it kept saying the sweep "reads OPEN blocks only, correctly" for the eleven days after this shipped, and nothing failed. Fixed with ADR-0022, and recorded there as its own defect class rather than tidied away.