Paths: the result, not the route
IMPORTANT
This step
- Teaches: inputs as different paths to one goal, an anchor every path shares, and a designated opening read against every input.
- Frames:
14-orders-latency.json(optimizing: five pull requests, one goal) and15-invoice-export.json(implementing: one change, four processes). - After: Grain.
Some paths to an outcome are faster, some slower, some cheaper; several can be valid. These two frames ask whether a reviewer judges the result or the route taken to it. Each route is an input, and the evidence they share is the anchor.
14: one goal, five paths
Gadfly plans 14-orders-latency.json like this, one span a line, the anchors marked *:
$ uv run python examples/read.py \
examples/14-orders-latency.json --frame
orders-latency: 5 inputs x 8 trials x 5 calls = 200 calls, 4 of 5 to agree
relation: accepting each performance change rests on the goal it is measured against
grain: one goal, five paths to it
evidence, the anchors marked *:
* goal goal Goal: GET /orders answers within 200 ms at the 99th percentile under the Black Friday load test.
languages goal Goal: the orders page is offered in English and German.
exports goal Goal: order exports finish within one hour.
phone goal Goal: the admin page works on a phone.
before fact Before any change: load test p99 = 1.9 s; the endpoint runs one query per order line.
index benchmark Pull request #501 (Ana added an index on orders.customer_id): load test p99 = 140 ms.
cache benchmark Pull request #502 (Ben caches each customer's order list in Redis for 30 seconds): load test p99 = 95 ms.
batch benchmark Pull request #503 (Chen loads the order lines in one query instead of one query per order): load test p99 = 180 ms.
readmodel benchmark Pull request #504 (Dee moved GET /orders to a read model kept current by order events): load test p99 = 60 ms.
timeout benchmark Pull request #505 (Eli raised the endpoint's timeout from 2 s to 5 s): load test p99 = 1.9 s.
asked:
index Review pull request #501. Ana added an index on orders.customer_id: a one-line migration, done in an hour. Does it meet the goal?
cache Review pull request #502. Ben caches each customer's order list in Redis for 30 seconds: two days of work. Does it meet the goal?
batch Review pull request #503. Chen loads the order lines in one query instead of one query per order: a day of work. Does it meet the goal?
readmodel Review pull request #504. Dee moved GET /orders to a read model kept current by order events: three weeks of work, 2,400 changed lines. Does it meet the goal?
timeout Review pull request #505. Eli raised the endpoint's timeout from 2 s to 5 s: ten minutes of work. Does it meet the goal?
labels: accept, reject, unclear
trials:
baseline asks the evidence as it is
repeat asks it again
ablation cuts goal
sham-1 cuts languages
sham-2 cuts exports
presentation renames order -> purchase, orders -> purchases
bypass cuts index, cache, batch, readmodel, timeout
control cuts goal, before, index, cache, batch, readmodel, timeout
allowed: ablation -> unclear; bypass -> unclearFive pull requests answer one goal (GET /orders within 200 ms at p99 under the load test), each by another path and at another cost: an index (an hour, 140 ms), a cache (two days, 95 ms), batching the queries (a day, 180 ms), a read model (three weeks, 60 ms) and a longer timeout (ten minutes, still 1.9 s). The anchor is the goal. The bypass cuts every load-test result, which is what connects each change to the goal.
NOTE
Predicted before the first call
The four improvements accepted at baseline, the read model least surely (about 70%), the timeout rejected (high); about 40% that at least one valid path is rejected for its size or cost. The goal cut: the four still accepted as far faster than before (about 60%), so cooled. The load tests cut: the index and the cache still accepted on their reputation (about 55%).
$ uv run python examples/read.py \
examples/14-orders-latency.json
orders-latency: 3 kept runs, read by google/gemma-4-26B-A4B-it-qat-q4_0-gguf@d1c082be
trial index cache batch readmodel timeout
baseline accept 15 accept 15 accept 12, reject 3 accept 15 reject 15
repeat accept 15 accept 15 accept 14, reject 1 accept 15 reject 15
ablation unclear 15 unclear 13, reject 2 unclear 13, accept 1, reject 1 unclear 15 unclear 13, reject 2
sham-1 accept 15 accept 15 accept 13, reject 2 accept 15 reject 15
sham-2 accept 15 accept 15 accept 12, reject 3 accept 15 reject 15
presentation accept 15 accept 15 accept 13, reject 2 accept 15 reject 15
bypass unclear 15 unclear 15 unclear 15 unclear 15 reject 15
control unclear 15 unclear 15 unclear 15 unclear 15 unclear 15
run 1: unresolved (index carried, cache carried, batch unresolved, readmodel carried, timeout unresolved)
run 2: unresolved (index carried, cache unresolved, batch carried, readmodel carried, timeout unresolved)
run 3: unresolved (index carried, cache carried, batch unresolved, readmodel carried, timeout unresolved)The four that meet the goal are accepted whatever their cost, and each rests on the goal and on its load test: cut either and the answer is unclear, so they are carried whenever their levels agree. Both cuts were predicted otherwise.
The batched query, 180 ms against 200, is the one near its threshold and the one whose calls spread. The timeout is the other kind of input:
$ uv run python examples/read.py \
examples/14-orders-latency.json --verdict 1 \
--input timeout
timeout: unresolved
levels: baseline reject, repeat reject, ablation unclear, sham-1 reject, sham-2 reject, presentation reject, bypass reject, control unclear
evidence: ablation moved it reject -> unclear; held under presentation, 2 shams
evidence: the control moved it reject -> unclear
evidence: not carried while 1 carriage check is open
open: no hidden fallback (bypass gave reject, not the designated opening, unclear)It is rejected, and stays rejected without the load tests, rightly: a longer timeout makes nothing faster, which needs no measurement. The designated opening, unclear, is right for the four improvements and wrong for this one, so its check stays open for a reason that does not apply.
15: one change, four processes
One pull request, the same diff and the same passing test, reached four ways: the test written first, a notebook prototype first, an AI assistant, or pairing. Each process is an input, told in the question, and the anchor is the passing test.
NOTE
Predicted before the first call
Accept at baseline for all four, the assistant least surely (about 75%). The passing test cut: unclear (about 55%), or accepted on the diff and the clean linters, which would be cooled.
$ uv run python examples/read.py \
examples/15-invoice-export.json
invoice-export: 3 kept runs, read by google/gemma-4-26B-A4B-it-qat-q4_0-gguf@d1c082be
trial test-first prototype assistant pairing
baseline accept 15 accept 15 accept 15 accept 15
repeat accept 15 accept 15 accept 15 accept 15
ablation unclear 15 unclear 15 unclear 15 unclear 15
sham-1 accept 15 accept 15 accept 15 accept 15
sham-2 accept 15 accept 15 accept 15 accept 15
presentation accept 15 accept 15 accept 15 accept 15
control unclear 15 unclear 15 unclear 15 unclear 15
run 1: unresolved (test-first unresolved, prototype unresolved, assistant unresolved, pairing unresolved)
run 2: unresolved (test-first unresolved, prototype unresolved, assistant unresolved, pairing unresolved)
run 3: unresolved (test-first unresolved, prototype unresolved, assistant unresolved, pairing unresolved)The four were read identically on every call: accepted with the passing test, unclear without it. The process carried nothing, and the test result carried the decision, as the promise says. No bypass is declared, so each input is unresolved with only that check open.
What to take from it
- To ask whether the route matters, make the routes the inputs and keep the evidence the same. To ask whether the result matters, anchor the result.
- Both frames say the same about this reader: it judges the result, not the path, the cost or who wrote the code.
- A designated opening applies to every input. When one input should land elsewhere, as the timeout should, give it a frame of its own.
Try next
In 15 the process lives in the question, so no trial can cut it. Write 15b with the process as a span of the evidence ("Process: an AI assistant wrote the code and the test") and the passing test still there, and anchor the process. Predict whether cutting it moves anything.