Derived from 22 verified reports and 10 acceptance probes
on the same work, 2026-08-01 → 02

Twenty-two reports.
Zero overclaims.
Seven not done.

Both numbers are true. That gap is the whole problem,
and it has a fix that fits on one slide.
DEFINE DONE BEFORE YOU ASK IF IT IS
docs.propflowai.coDEFINITION OF DONE
02 / THE GAP
The same work, graded twice

Nobody lied. The question was too small.

PR LAYER
22/22
merged, closed — all true
OUTCOME LAYER
4
NOT MET
PARTIAL
3
true, and incomplete
CLEAN
2
of ten probed
Every PR-layer claim survived checking. "Ten merged, five closed" was accurate every time. Then the dashboard still capped All-time at five months, a resident at their SMS ceiling could still be cold-called, and 59 tenants holding $70k stayed invisible. Merged and works are different objects, and only one was ever being measured.
the honest reports were answering the question they were given02
03 / THE GAP
There is already a skill for this, and it has the same hole

The agent picks the claim. So the agent picks what gets verified.

The verify-claim skill names this failure exactly — "agent lied about being done is the single most-cited frustration; the remedy is structural, not attitudinal." It is good, and it would have passed every one of these.
What it checks

The claim

"Ten PRs merged, two closed as duplicates." Run gh pr view — every word confirmed. Green.

The dashboard is still broken.
What nobody checked

The goal

"Click All time on any card and see all stored history."

Nobody wrote that down, so nothing could grade it — and the worker had no reason to volunteer a bar it was never set.
The fix is one sentence: the acceptance criterion is written by the coordinator, at the outcome layer, BEFORE the work is graded. If the worker writes it, it writes down what it did.
a self-chosen exam is always passed03
04 / THE LADDER
Say which level you mean, every time
L0 · CLAIMED

The session says it is done

Worth nothing on its own — not because sessions lie, but because they answer the question they were given. Never a stopping condition.
L1 · MERGED

PRs merged, issues closed, confirmed by gh

Necessary, never sufficient. This is the level that scored 22/22 while seven outcomes were unmet. ⚠️ squash-merge defeats --is-ancestor and git diff main — use the squash SHA plus gh state.
L2 · OBSERVABLE

The outcome exists where a user or operator would meet it

The query returns rows. The message is in the channel. The page shows the data. Read the thing itselfdeployment passed by reading Slack and spot-checking its own merge, and explicitly refused green CI as proof because the workflow is fail-soft.
L3 · DURABLE

Something re-checks it without being asked

A drift test, a vacuity fence, a positive control. Without this L2 decays: two documents printed verified counts that drifted within three days because nothing re-ran them.
Done = L3 for anything that can rot. L2 for genuinely one-shot work. Anything below L2 is not done, it is claimed.
name the level or the word means nothing04
05 / THE PROBE
Four questions, in this order — the order matters
0 · THE ASK

Quote your original ask verbatim, from your first turn

This question earned its place. Three of ten workers used it to correct the end state I had written — one had been asked to "remove the arm", not to cover every send door. It stops the coordinator inventing scope and the worker drifting from it, in one move.
1 · VERDICT

MET / PARTIAL / NOT MET, at every layer it touches

Per-layer, not overall. "Enforcement wired" was true of five doors and false of seven.
2 · EVIDENCE

The observable — explicitly not a PR number

The code path that runs, the rows returned, the message that posted. If it cannot be observed: "cannot verify because <reason>", and that is NOT MET.
3 · THE GAP

file:line, and what would close it

A gap without a location is a feeling.
Two refusals, stated in the probe itself: do not re-derive scope — if the end state is wider than the ask, that is the finding. And "we would have to rebuild X" is not grounds for met or impossible; only a blocker specific enough to check.
3 of 10 corrected the coordinator — that is the probe working05
06 / STOPPING
Every item lands in exactly one — and quitting has a price of entry
DONE
L3 (or L2 where nothing can decay), evidence recorded. The only state that closes an item.
PARKED
Needs a human decision, or the act itself is dangerous — money, a customer send, anything irreversible. Named, decision stated in one line. Does not block the loop: holding a session on a call it cannot make is forcing patience, not work.
BLOCKED
Waiting on something external — CI, a deploy, another PR. Also does not block. ⚠️ a probe that has gone idle without replying is NOT blocked; it finished and its answer was lost. Read its transcript.
UNVERIFIABLE
Cannot be observed, and why. Logged as unverifiable, never quietly promoted to done. One session's entire report ended here rather than be counted.
STUCK — the expensive one
Only reachable after a Fable subagent has been asked and also failed. "I am stuck" is not available to a session that has only tried its own framing. Requires: what was attempted, what Fable recommended, why it did not work, and a blocker specific enough that a human could check it. "We would have to refactor X" is not stuck — it is unstarted.
Fully complete, or land in one of the five — and nothing sits in DONE without evidence. Not "the queue is empty". Not "the session feels finished". Both were tried on the same day and both were wrong.
five states · quitting requires a failed Fable attempt06
07 / STOPPING
The failure that produced three hours of silence, then did it again

A loop is exactly as good as its work list.

The gate reads a queue, a PR list, an issue list and an inbox. Four verified findings lived only in a chat message, so it evaluated its inputs correctly, said "a real finish", and stopped with the work undone. Fixed — then it happened again, because the user's own stated plan was never registered either.
Must be registered
· GitHub issues and PRs
· Findings from probes
· The human's stated plan
· Outstanding replies
· Anything you would be annoyed to discover was dropped
Converting is the work
Turning a finding into a tracked object is not admin — it is the step that makes the loop able to hold it. A finding in prose is a finding that dies with the context.
Write the criterion at file time
Put the acceptance criterion into the issue when you open it. Then "done" is defined before anyone can be tempted to define it as what they did.
an empty work list is not evidence of finished work07
08 / TRAPS
Every one of these produced a confident wrong answer in two days
01

Zero results from a broken search

A grep returning 0 is evidence about the grep. Three parties hit this; one logged four in a row. Print the matches.
02

An empty collection satisfying every predicate

A CI wait returned instantly because no checks existed yet. all() over an empty set is vacuously true — require the collection to be populated as a separate condition.
03

A missing instrument reading as a passing one

The Stop hook was deleted by a stray git reset; the tool that reports on it printed "a real finish" because absent output fell through to the happy branch.
04

Approximating the instrument instead of running it

Re-measuring a document's counts with similar greps gave 3/7/65; its own commands gave 0/4/53. Run it as specified.
05

A file-level scan standing in for a behavioural one

"20 files call the carrier with no conversation write" — two of two sampled were fine. The scan answers does this file contain that symbol.
06

A test racing the thing it measures

A status-bar check showed nothing because a live watcher overwrote the fixture between write and read. The branch was correct; the test was wrong.
ask: what would have to be true for this to pass while the claim is false?08
09 / THE LOOP
Three mechanisms, because one of them cannot do the other's job
drive-gate

Refuses to stop

A Stop hook, blocking while registered work is actionable. Five independent releases so it can never become a hang. Fails open — a blocking hook that throws wedges every session on the machine.
nudge

Brings you back

A Stop hook cannot restart a session that already stopped. That asymmetry cost three hours. nudge --idle counts stillness and wakes the session — resolving the pane by live PID, never by name.
the status bar

Shows the wait

⏳ propflowai#5262 · 2:14 — what is held and how long. And ⚠ nudge STALLED when the watcher dies, because a loop that dies quietly has no symptom at all.
Fail toward waking up. The gate fails open — a broken gate must not wedge the machine. The nudge fails the other way: if it cannot evaluate the gate it fires anyway. A spurious nudge costs one turn; a silent stall costs the session.
refuse to stop · come back · make the wait visible09
10 / PROVE IT FAILS
Three caught in one day — none of them by reading the test

A test that passes on the broken code proves nothing, and reading it will not tell you.

01 · SWALLOWED

The safety test threw from the wrong layer

A regression test for a “this must never block a call” fence threw from storage — but the function swallows its own storage errors, so the test passed with the fence removed. Rewritten to throw from the path that actually broke CI.
02 · UNREACHED

The positive control that tested nothing

It injected a pattern the matcher ignored. Green, and measuring nothing — the control needs to exercise the real predicate, not a plausible-looking neighbour.
03 · COUPLED?

“It passes” is not “it is connected to the change”

Two rewritten tests passed. Removing the one-line change they were meant to pin reddened exactly those two and nothing else — that is the evidence, not the green run before it.
Break it on purpose, watch it go red, restore. Ten seconds, and it is the only way to know a guard can fail. Do it in batches when you have several: revert two fixes, confirm two guards fire and the other two stay green, then revert the others. A guard that reddens when an unrelated thing breaks is as useless as one that never reddens at all.
The CI corollary. A required check can be green having executed none of the diff’s tests — an affected selection of 695 files against a cap of 200 skips the run entirely. Green and tested are different claims, and only one of them is on the check.
the break that stays green is the one that teaches you10
11 / THE CARD
If you remember nothing else

Define done before you ask whether it is.

Before the work
✓ The coordinator writes the acceptance criterion
✓ At the outcome layer — what a user would see
✓ Into the issue, at file time
✓ Name the level: L2 observable or L3 durable
✓ Register it, or the loop cannot hold it
When grading
✓ Ask for the original ask, verbatim, first
✓ Demand the observable, never a PR number
✓ Verify with a different instrument than the claim
Print the matches — never trust a count
✓ Land it in one of the five terminal states
Unverifiable is not done
Fully complete — or land every registered item in DONE, PARKED, BLOCKED, UNVERIFIABLE or STUCK, with nothing in DONE without evidence. Never stop because the queue emptied or the session felt finished; both were tried on the same day and both were wrong. And never reach STUCK without having asked a Fable subagent first — a fresh framing is cheaper than a wrong quit.
see also: /a/coordinator-pattern.html · /a/agent-operating-manual.html11

All slides

PropFlow Docs