A local coding engine that cannot fix the bug has not necessarily failed the same way as one that crashed outright, but the harness dispatching it doesn’t fully know which happened either. Treating both as exit 1, the way most wrappers do, buries a recoverable outcome inside a generic failure. Phase 15 gave the harness a third state: decline, exit 2, distinct from done (exit 0) and from a hard error (exit 1) that stops the run cold. What decline does not distinguish is a genuinely rejected patch from the tool crashing mid-run: both still land in that same exit-2 bucket. Phase 15’s own notes named that as an open question rather than something this phase resolved, and it stayed open, later handed off as consolidated debt to the tool it wraps. The interesting part isn’t the third exit code. It’s what that code was allowed to trigger automatically, and the one thing it was never allowed to trigger at all.

A vocabulary with a verb in it

Once decline exists as its own outcome, it can mean something specific: not “this attempt failed,” but “this engine, correctly, would not proceed, try the next one.” The routing rule for code-implement tasks gained an ordered escalation_chain, and the decline branch walks it, skipping any candidate blocked by privacy policy or simply unavailable, taking the first that resolves. A single fallback: field still works as a one-element chain for any rule that doesn’t define the richer form, so nothing else in the router had to change to add this. The chain is a routing decision. It is not, by itself, permission to spend anything.

The one thing escalation cannot do on its own

The chain can walk from a local model to a subscription CLI to another subscription CLI, entirely on its own, because none of those steps change who is paying attention to cost or exposure until the moment a step actually requires sending code off the machine. That moment is a hard stop. In --json mode, unattended by design, the chain still walks candidates, still finds the next one available, and when the next one requires cloud egress, it refuses there: exit 1, nothing sent, no ledger entry claiming otherwise. The live proof of this ran four times against a real, deliberately broken one-line bug. Autonomous mode declined locally, walked the chain to a cloud engine, and refused correctly rather than guessing what a human would have wanted. The interactive run, later, asked and got an actual “y,” and only then did a cloud call happen.

What the boundary actually protects

The rule generalizes past this one adapter: escalation may change which engine runs; it must never change whether cloud egress gets consented to. Those are two separate axes, decided by two separate mechanisms, and collapsing them into one “just try until something works” loop would be the easy version of this feature and the wrong one. An engine that declines gracefully and an orchestrator that escalates automatically are both good things. Neither one should be able to spend money or expose code on a human’s behalf just because the alternative is a human walking away for a minute.

What next

The same distinction between a mechanism working and a claim being justified shows up again, at a larger scale, in a companion story: a live smoke test where every one of these mechanisms behaved exactly as designed and the actual bug still didn’t get fixed.

What Next

The Smoke Test That Missed the Task

Every layer of this escalation contract worked, correctly, in both autonomous and interactive mode. The bug still didn't get fixed. Here's why that's a mixed verdict, not a failure.