Watch it fail safely

An agent wrote code that compiled clean and passed all 722 tests. It was still wrong.

This is captured output from two real runs, and it is worth being precise about which is which. The verdict, the dry run and the branch below come from a run against FluentRoslyn, a published .NET library, fixing two open findings from its own code-review log. The interactive replay and the ledger come from the acceptance run that proved the engine, against a scratch repository. Nothing here is reconstructed, but nothing here is one continuous run either.

The claim worth testing is narrow and falsifiable: gates are not enough, and a reviewer from a different model family catches things no gate can. What follows is one case where that happened, with the receipt.

Before spending anything

A dry run costs nothing and answers the two questions worth asking first — what will it do, and what could it cost.

$ upstroke run plan.md --config upstroke.toml --budget 12 --dry-run

id           kind  deps  chain
-----------  ----  ----  -----
literals     fix   -     small=claude-haiku-4-5 -> mid=claude-sonnet-5 -> frontier=claude-opus-5
array-ranks  fix   -     small=claude-haiku-4-5 -> mid=claude-sonnet-5 -> frontier=claude-opus-5

gates: build, test [from config]
review: claude-code/claude-opus-5
capacity: 2 pool(s) connected
  claude-code: unknown [unknown]
  - nothing has measured this pool, so what is left is unknown — not full
ok: 2 tasks, no cycles
budget: $12.00 would cap this run; nothing is spent in a dry run
dry run: no agents executed, nothing spent

Note what it declines to claim. No pool reports "full" — it reports unknown, because nothing has measured it yet. A capacity estimate that flatters is worse than none.

The run, as the log recorded it

The engine holds its own terminal, so this is the view from the second one. Scrub through a real 45-minute run — five tasks, one dependency chain, one independent task. This one is the acceptance run, against a scratch repository rather than FluentRoslyn: it is the run with a dependency graph worth showing, and with the parked question below. Every state is derived from its append-only event log, which is exactly how the dashboard would work.

replaying .upstroke/runs/01KZNG12FJA9DSJEYSK46NVBSC/events.jsonl — 45 events, 45 minutes

00:00

Task graph of a upstroke run, coloured by state
committed running parked — waiting on you queued or blocked
tasktrailworkerreview

The moment worth pausing on is around 17 minutes, when format-policy turns amber. It hit a decision the repository could not settle and stopped to ask. Watch what changelog does next: it keeps going, because nothing depends on the parked task. A question parks exactly the task that raised it and the runnable frontier keeps draining — which is a property of the graph, and the reason a list view cannot show it.

The catch

Back to FluentRoslyn, and to the moment that makes the case. Its first attempt produced a fix that built cleanly and passed all 722 tests. The reviewer — a different model from the one that wrote it — rejected it anyway:

Negative zero is emitted as System.BitConverter.Int64BitsToDouble(...), which is not a compile-time constant. FieldBuilder.WithInitializer is reachable with Const(), so a const float/double field initialized from -0.0 now fails in the consumer with CS0133. That case compiled before this change.

claude-opus-5, reviewing work by claude-haiku-4-5

No gate could have caught that. The code compiles — in this repository. It breaks in a consumer's build, in a case the test suite never exercises, and it silently removes a capability that previously worked. The reviewer also knew that Int32BitsToSingle does not exist in the library's netstandard2.0 target, pointed at the two files where the codebase already solves this correctly, and cited the duplication cluster closed three commits earlier. It had read the git history.

The retry resumed the same session with that verdict fed back, and the second attempt shipped the correct fix: a prefix-minus over an explicitly floating-point token, which stays a compile-time constant.

What it cost, and on what

This is the acceptance run's ledger, matching the replay above — seventeen lines. Per task: how many attempts, which rungs it climbed, what the implementer spent, and what the reviewer spent, kept in separate columns because folding them together makes a cheap rung look expensive. FluentRoslyn's own bill is in the closing section.

$ upstroke status 01KZNG12FJA9DSJEYSK46NVBSC

  task           attempts  trail                  worker   review   total
  readme         1         small ok               $0.0980  $0.4313  $0.5292
  parse-basic    2         small×2 ok             $0.1502  $1.0475  $1.1977
  parse-edges    2         small failed → mid ok  $0.6046  $1.2133  $1.8179
  format-policy  4         small×4 ok             $0.4776  $1.6852  $2.1629
  changelog      1         small ok               $0.0311  $0.2975  $0.3287
  total $6.0363 (api-equivalent; subscription spend is notional)
  per-pool drain:
    claude-code: 19 attempt(s), $6.0363

An unreported cost prints as —, never $0.0000: a ledger that cannot tell free from unreported is worse than no ledger.

Where the work landed

The engine commits to its own branch. Your working branch is never touched, and every commit arrives already reviewed by a model from a different family than the one that wrote it.

$ git log --oneline -n 2 upstroke/run-01KZNMR59E5ATC9MBYY29WZB6E
54a5909 [upstroke] literals: Emit float and double special values as source that compiles
9580f55 correct ROADMAP's coverage claim, which Review 3 closed

$ git log --oneline -n 1 main
9580f55 correct ROADMAP's coverage claim, which Review 3 closed

What this does not show

It cost $12.44 to fix one finding, and the run stopped at its ceiling before reaching the second. Review was roughly half of that, and on this evidence it is the half that earns its keep — but it is not cheap, and anyone deciding whether to adopt this should price it at $3–12 per real task rather than from a toy example.

Nor is any of this deterministic. The same task, on the same model, from the same commit, failed review in one run and stopped to ask a question in another. That is worth stating plainly, because it means a single run proves less than its clean numbers suggest — including the runs on this page.

Every figure and quotation here is taken from run logs on disk. The graph above is driven by the actual event log of run 01KZNG12FJA9DSJEYSK46NVBSC.