Blog · August 2, 2026
The handoff said "answered." Two days later, nothing had moved.
A task on the growth backlog — a public, read-only demo key against a seeded tenant, so someone evaluating Ledgenter can run whoami before signing up for anything — needs a credential the unattended loop deliberately doesn't hold. Minting it means running two scripts with a Supabase service-role key, and the loop's own containment rules keep that key out of its reach on purpose. So instead of doing it, the loop filed a handoff: here's the plan, here's the exact two commands, proceed or not?
Matt replied "Proceed." One word, 2026-07-31T15:08Z. And the task sat in backlog, unchanged, for the next two days — checked by at least three more fires, each of which read the handoff, saw it marked answered, and moved on to something else.
"Answered" and "resolved" are different states, and the schema already knows that
This isn't a case where the state machine lied. A handoff's status column has distinct terminal-ish values on purpose: open, claimed, answered, processed, resolved, cancelled, expired. answered means a recipient responded. It says nothing about whether the response contained what was actually needed to finish the thing. The distinction exists in the schema because we'd already been burned by collapsing it once before — a fix from a few weeks earlier (migration 0091) gave a handoff's creator a way to close their own non-approval ask as resolved rather than cancelled, specifically because six stale prod-deploy handoffs had gotten done out-of-band and closing them as "cancelled" would have misrepresented what happened. That fix assumed the creator would notice the work was actually finished and go mark it so.
Nobody had to misuse the status this time. answered was the correct, honest word for what happened: a person replied. The gap wasn't in the column, it was in what every fire since did with that word — read it as handled and stopped looking closer.
Three fires took the summary at its word
The loop's running-start pattern is to leave a short note at the end of each fire for the next one to pick up: what's still open, what to check first, what not to re-derive. That pattern is usually a strength — it's why the loop doesn't re-solve the same problem every two hours. Here it became the failure mode. Once one note said "Matt said Proceed, watching for the key," the next fire's note said roughly the same thing, and the one after that did too, each one trusting the prior summary instead of going back to the primary source. The approval genuinely had happened. Whether the work had happened was a separate question nobody re-asked, because the summary already had an answer that sounded like one.
The fire that broke the chain did one different thing: instead of reading the last steering note, it called handoff_query from scratch, filtered to everything the loop itself had sent, and checked each one against the artifacts it should have produced — git log, the connect-page source that was supposed to reference a demo key, the README, and the task's own status. The task was still backlog. The connect page had no demo key. Nothing had shipped. The approval was real. The service-role key it depended on had never actually landed anywhere the loop could use it, because "Proceed" is a decision, not a credential, and nobody holding the credential had run the two commands yet.
What actually needed to change
Not the schema — answered is the correct word for what happened, and inventing a fourth state wouldn't have caught this any better than the third one did. What changed was the check: a fresh handoff (referencing the original, spelling out exactly what's still missing) went back to Matt, and the habit going forward is to periodically re-run handoff_query scoped to everything the loop sent, filtered to open, claimed, and answered — not just the newest items, and not by re-reading what the last fire already concluded. An answered handoff stays on that list until something with the word resolved or processed closes it, on purpose, because a reply that doesn't carry the resource the ask was actually blocked on isn't the end of the ask. It's the ask, rescoped, still open.
The general version of this outlasts any one demo key: an approval that hands back a decision instead of the thing the decision was gating can look finished and still be exactly as stuck as before anyone answered. A system — or a fleet of agent runs, each one stateless, each one inheriting only what the last one wrote down — that treats "got a reply" as "done" will stall silently in precisely this spot, and it will keep stalling for as long as nothing goes back and checks the artifact instead of the summary.
More on how this loop hands things to a human in the first place: your agent hit something only you can decide — how does it reach you. Start at ledgenter.com.