Chapter 28 of 30

What Should Happen Next?

Concepts

CHAPTER 28 โ€” What Should Happen Next?

STATUS

Editorial enhancement pass 2026-09-14 (after the same-day drafting pass). Restructured around three separately evidenced decisions; ladder mechanism and real ledger traces added; stale commit-state statements, one overclaim, a mislabelled table column, and prose artifacts corrected. No evidence altered; no experiment rerun; no CodeAI change in this pass (pricing-prefix fix considered and declined, reasons below).

CENTRAL QUESTION

Which operation is needed before choosing a model?

THESIS

Operation != escalation != selector. Each has a different evidence state: the operation seam is source-inspected policy (epistemic-v2; historical v1 matrix demo), escalation is measured (Stage 29B), and whether the selector should contain a model call is designed with a falsifier and unrun. The ladder was cheaper per accepted outcome and produced one more correct acceptance, yet failed its frozen adoption rule on one accepted-but-wrong outcome; top-first remains default for this workload under this rule, with its clean column on A05 coming from a truncated, unusable output.

DISTINCTIONS

  • operation choice != model choice != escalation (table maps decision -> mechanism -> evidence state)
  • model budget != process budget
  • verified by one checker != true (A05 passes wrong; T07/T10 reject right)
  • truncated mid-deliberation != judgment delivered
  • verdict label != whole verdict (three clauses)

SECTION OUTLINE

  • Constructed scene; bridge from Ch27 (Part 5 spent calls freely).
  • Three decisions table; MRKL + RouteLLM as bounded anchors.
  • Operation seam: full current decide_next_step source; split budgets; executed precedence fragments; ACTION unreachable; flags caller-supplied (docstring); decisions unrecorded; v1 matrix historical, 7a0d43b->a1b562a scheduler change is dict-literal reformat; DSL dropped.
  • Stage 29B: workload/arms/check/frozen rule; ladder event vocabulary (five decline kinds); real A05 and T07 traces from preserved ledger; result table; verdict unfolded (ladder_justified false; label “top-rung-first wins on this workload”); $5 arithmetic; $0/$25 sensitivity.
  • A05 case + preserved reasoning (heading toward null, cut off at length); T07/T10 mirror.
  • Rungs that earned nothing; decline-reason routing as hypothesis; S10 rule vs strong model (one item, scoped); latency/escalation axes; scope fence.
  • Spend defect: cause, repair now in a1b562a (reduced excerpt), three regression tests; pricing-prefix shadow named with why not fixed.
  • Unrun challenger: GPT-5 router; falsifier thresholds verbatim-in-substance incl. REFUSE <=5% and power caveat; status table; naming rules.
  • Checking without trusting; controls; What this is not; weak; Do this now; failure modes; established; Next -> Ch29 (joints question).

LOAD-BEARING CLAIMS

  1. Ladder 33 accepted (rule 10, free 23), 32 correct, 1 wrong (A05); 7 people asked of which 5 were no-single-answer items; 44 calls; $0.0055963 known; 416,746 ms; 51 escalation transitions; $1.0608/accepted at $5. Top-first 31/31/0; 9 asked (6 no-single-answer); 40 calls; $0.03168 + 1 unknown; 314,768 ms; 9 escalations; $1.4526. Rule: cheaper AND correct >= top-first-1 AND wrong <= top-first; third failed; ladder_justified false; verdict label "top-rung-first wins on this workload". Same at $0/$25 and with hidden spend priced. [measured]
  2. Ladder traces (preserved events): A05 rule_declined:0 ttl lines -> zen-free resolved; T07 rule_declined + 3x check_failed:quote_has_0_durations -> asked_human; T10 3x quote_has_2_durations. [measured]
  3. A05 top-first: finish length, 2047/2048 completion tokens reasoning, empty content, empty_output -> person; reasoning preserved, weighs null and is cut off drafting a null answer. Mechanism of one avoidance, not judgment. [measured]
  4. Rung decomposition 10/10, 23/30, 0/7, 0/7, 7 persons; arithmetic closes. Decline reasons input/checker-bound -> decline-reason routing hypothesis. [measured]
  5. S10: rule 10/10 structured at 0 calls; top-first strong model 9/10, S10 bare “86400” -> quote_has_0_durations -> person. Scoped to one item. [measured]
  6. Scheduler epistemic-v2 source; ACTION unreachable; flags caller-supplied; decide_next passthrough; v1 matrix 16/16 historical. [source + demo]
  7. Usage repair in a1b562a: usage parsed before text; failure path keeps tokens, estimates cost when priced; 3 tests (A05 shape measured/unknown cost; priced estimate; usage-less unavailable). Bundle untouched. Sibling adapters unrepaired. [source]
  8. Pricing prefix shadow: gpt-4o-mini billed at gpt-4o (2.50 vs 0.15 input, ~16.7x). Not fixed: test_budget_safety asserts 0.00045 under shadow; PRICING_VERSION “2026-09-01” pinned in replay tests; router_experiment records ordered_rates as pricing identity -> needs versioned migration. [source]
  9. Router: implementation + tests; router_cases_v1 DRAFT 68 cases, no oracle; router-prereg.md “NOT FROZEN โ€” NOT RUN”; all committed in a1b562a; no live comparison. Falsifier: >=15pp overall, no stratum worse >5pp; M<=D catastrophes, zero novel classes; median flip <=10%, p95 <=20%; REFUSE <=5%; prompt rephrase <=10% no new catastrophes; auditability >=90% non-vacuous, not materially worse; cost descriptive. Power limited (24 + 24โ€“32 cases). [reported + source]
  10. GPT-5 real-time router continuously trained on switching/preference/ correctness signals: credible which-model architecture; does not settle operation selection. [OpenAI 2025]

EDITORIAL CORRECTIONS (this pass)

  • “the rule rung beat every model on ten fully specified tasks” -> only the strong model ran on structured items in top-first (9/10); scoped to S10.
  • “stochastic generation adds failure modes without adding intelligence” generalization scoped to the observed item.
  • Table column “Person asked (correctly)” -> correct_person means person asked on a no-single-answer item; relabelled.
  • “Two regression tests” -> three.
  • Stale: “other session’s uncommitted scheduler change”, “untracked draft” corpus/prereg, “repairs it in place” -> committed in a1b562a.
  • Removed typo “design,_binding”, duplicated citation, and process note about an unopened Anthropic source.
  • Verdict label “top-rung-first wins on this workload” now quoted and unfolded instead of denied (“Not ’top-first won’”).
  • Added that a larger token budget could have produced either A05 outcome.

EVIDENCE

  • execution-ladder/2026-09-14-7a0d43b: analysis.json rows/totals/decision recomputed this pass; preregistration.json decision_rule read; chapter-evidence-report.md; ladder/events.json A05 trace; top-first artifact 5f/bc6a55… body (usage, finish, reasoning).
  • scheduler/ README: v1 16/16 parity, ACTION dead, DSL verdict.
  • CodeAI a1b562a: scheduler.py, ladder.py event/decline names, providers.py send + PRICING_TABLE, diff 7a0d43b..a1b562a for scheduler/providers/tests.
  • docs/applied-ai/ch28-router-experiment-design.md ยง0, ยง7; router-prereg.md header; router_cases_v1.DRAFT.json (68, DRAFT).

PAPERS

  • Karpas et al. MRKL (2022): component choice apart from generation.
  • Ong et al. RouteLLM (2024): which-model routing after the call.
  • OpenAI GPT-5 System Card (2025): deployed trained router.

CONTROLS / LIMITATIONS

One run per arm, 40 synthetic items, no variance; ambiguity-blind checker; one ladder config / model set / pricing period; person cost a scenario; unknown-spend call flagged; latency summed sequential wall time; decline- reason routing untested; router unrun; v2 matrix rows uncovered; decisions unrecorded; flags precomputed; pricing shadow + sibling adapters open.

DEPENDENCIES

Ch27 (frozen promotion rules), Ch21 (adequacy), Ch20/22 (authority downstream of CALL/CHECK; controls), Ch18 (reported vs measured), Ch24โ€“27 (arm tables, oracle != selector), Ch10 (chambers gain rungs), Ch13 (usage semantics: missing never zero).

FORWARD BRIDGE

Pieces tested separately; whether they hold together on one task, and which joints the runtime enforces, is Chapter 29’s question.

OPEN ITEMS

  • Decline-reason ladder v2 as a preregistered stage.
  • Ledger scheduler decisions; derive flags from ledger state.
  • v2 split-budget matrix.
  • Pricing: longest-prefix match under a new PRICING_VERSION with migration.
  • Sibling adapter usage-first repair.
  • Router R1/R2: freeze corpus + oracle + run + verify.

DIAGRAM (2026-09-14)

Added the five-flag operation-selection decision tree in code precedence order (STOP dominates; CHECK survives spent model budget; CALL; ASK_HUMAN; two STOP leaves), with ACTION dashed as in-enum-but-unselected and RETRIEVE absent per the chapter constraint. Model selection happens elsewhere by design.

Explain this chapter with AI

Copy this prompt into ChatGPT, Claude, Gemini, a local model, or another AI.

Apply this chapter with AI

Copy this prompt into ChatGPT, Claude, Gemini, a local model, or another AI.

Part 6 โ€” Put Intelligence Into the Process

A claim awaiting a check

Constructed scene. A patch sits open with one review comment unresolved: a retry test that may or may not cover the new branch. Three things could happen next. A model could propose a fix. A deterministic check could run the test suite. A person could be asked whether the branch matters at all. Each costs something different, fails differently, and answers a different question. “Try a stronger model” is not on that list until something has established that generation is the missing operation.

Chapter 27 ended with a policy that had to earn promotion and did not. Every experiment in Part 5, though, spent model calls freely in order to ask its question. This chapter turns the same discipline on the spending itself: the next model call should have a reason, and the model stops being the implicit controller of the loop.

Which operation is needed before choosing a model?

Three decisions that usually get merged

“Route to the right model” hides three separate decisions:

Decision The question CodeAI mechanism Evidence in this chapter
Operation Call, check, ask a person, or stop? decide_next_step, a pure function Source inspection; a historical matrix demo of an older version
Escalation This rung declined โ€” climb, or go to a person? run_ladder, with recorded decline reasons Stage 29B, measured
Selector Should the operation decision itself contain a model call? Router contract and model-based router Designed with a falsifier; unrun

Keeping them apart changes what failure means. Choosing a cheaper model cannot repair choosing the wrong operation. A failed model call is not automatically a reason to buy a stronger one; it may be a reason to run a check, or to stop climbing and ask a person. And the evidence for one decision does not transfer to another. Stage 29B, the chapter’s measured center, tests escalation. It says nothing directly about whether an operation selector should be deterministic โ€” which is the question the unrun experiment exists to ask.

The literature has names for two of the halves. Modular architectures that route among language models, knowledge sources, and discrete reasoners treat which component answers as a decision separate from generation (Karpas et al., 2022). That supports treating operation choice as its own layer, though a modular diagram does not demonstrate a fixed-priority scheduler, and the mapping onto CodeAI is the book’s. Learned routers attack the neighboring decision: given that a model will be called, which one, trading quality against cost with routers trained on preference data (Ong et al., 2024). Neither paper validates CodeAI’s ladder.

The operation seam

Here is the whole current policy, from CodeAI source:

def decide_next_step(query: SchedulerInput) -> SchedulerDecision:
    if query.process_budget_exhausted:
        return SchedulerDecision(Operation.STOP, "process budget exhausted", POLICY_VERSION)
    if query.has_required_verification:
        return SchedulerDecision(Operation.CHECK, "required deterministic verification exists", POLICY_VERSION)
    if query.requests_independent_proposals and not query.model_budget_exhausted:
        return SchedulerDecision(Operation.CALL, "task requests independent proposals", POLICY_VERSION)
    if query.requires_human_authority_for_next_effect:
        reason = "next effect requires human authority"
        if query.model_budget_exhausted and query.requests_independent_proposals:
            reason += "; model budget blocks proposals"
        return SchedulerDecision(Operation.ASK_HUMAN, reason, POLICY_VERSION)
    if query.model_budget_exhausted and query.requests_independent_proposals:
        return SchedulerDecision(Operation.STOP, "model budget blocks proposals", POLICY_VERSION)
    return SchedulerDecision(Operation.STOP, "no epistemic operation required", POLICY_VERSION)

1

No model, no I/O, no ledger write. Policy epistemic-v2 orders five caller-supplied flags, and its most important design choice is the one that is easiest to miss: two different budgets. Model-budget exhaustion blocks CALL and nothing else. Process-budget exhaustion stops everything. Collapse them into one “budget exhausted” flag and a system that has spent its cognition allowance can no longer run the check it still owes.

The policy as a decision tree, in precedence order โ€” operation first, model choice later and elsewhere:

    flowchart TD
    IN["scheduler input<br/><i>five caller-supplied flags</i>"] --> P{"process budget<br/>exhausted?"}
    P -->|"yes"| S1["STOP<br/><i>dominates everything</i>"]
    P -->|"no"| C{"required verification<br/>exists?"}
    C -->|"yes"| CK["CHECK<br/><i>survives a spent model budget</i>"]
    C -->|"no"| R{"proposals requested<br/>+ model budget left?"}
    R -->|"yes"| CL["CALL"]
    R -->|"no"| H{"next effect needs<br/>human authority?"}
    H -->|"yes"| AH["ASK_HUMAN"]
    H -->|"no"| M{"model budget<br/>blocks proposals?"}
    M -->|"yes"| S2["STOP<br/><i>model budget blocks proposals</i>"]
    M -->|"no"| S3["STOP<br/><i>no epistemic operation required</i>"]
    AC["ACTION<br/><i>in the enum; no input selects it</i>"]
    style AC stroke-dasharray: 4 4
  

Constructed calls against that function show the precedence, executed as teaching code rather than as a stage: 2

decide_next_step(SchedulerInput(
    has_required_verification=True,
    requests_independent_proposals=True,
    requires_human_authority_for_next_effect=True,
    process_budget_exhausted=True,
    model_budget_exhausted=True,
)).operation  # Operation.STOP: process stop dominates the other four flags

decide_next_step(SchedulerInput(
    has_required_verification=True, model_budget_exhausted=True,
)).operation  # Operation.CHECK: the check survives the spent model budget

Three boundaries are part of the design, not omissions to smooth over.

  • ACTION is unreachable. The enum contains it; no input selects it. Effects are authorized downstream under explicit grants (Chapters 20 and 22). A seam path to ACTION would complete the enum cosmetically while bypassing that authority story. CALL and CHECK schedule work; neither authorizes an effect. 3
  • The flags are not facts. The docstring says it directly: flags are caller-supplied state, not inferred facts or resource accounting. Whatever computes has_required_verification is outside this function and outside its guarantees. 2
  • Decisions are not recorded. Runtime.decide_next is a thin passthrough; a decision reaches the ledger only if a caller persists it. 4

The historical scheduler matrix needs its version stamp attached. It ran all sixteen combinations of the older four-flag vocabulary against policy epistemic-v1: 16/16 parity between the code and a data-table interpreter, CALL/CHECK/ASK_HUMAN/STOP reachable, ACTION dead, and the same simultaneity precedence. The committed policy has since become the five-flag epistemic-v2 with split budgets. Legacy spellings still migrate, but no matrix covers the split-budget rows. The later change to scheduler.py between 7a0d43b and a1b562a is a dict-literal reformat with no behavioral change. 5 6

That demo also settled a smaller design question. A five-row declarative table reproduced the if-chain exactly, but it duplicated the chain without fixing the real gaps โ€” unpersisted decisions and precomputed flags. At this size, the book keeps the small function and drops the policy DSL. 5

The measured center: Stage 29B

The scheduler answers which operation. Stage 29B asks the question one level down: once the operation is “get an answer,” how should the process climb from cheap to expensive, and when should it stop climbing?

The workload. Forty synthetic cache-TTL extraction items with frozen gold: ten structured (CDN_TTL_SECONDS=86400), twelve prose, twelve with distractor durations, six with no single correct TTL.

The arms. The ladder climbs rule โ†’ free model โ†’ cheap model โ†’ strong model โ†’ person. Top-first sends every item to the strong model, then to a person.

The acceptance check. Every candidate passes through the same deterministic checker, blind to gold: valid JSON, a verbatim quote from the input, exactly one duration expression in that quote, and seconds equal to that duration.

The adoption rule, frozen before the run. The ladder is adopted only if all three hold at a $5-per-person-asked scenario: cheaper per accepted outcome; correct acceptances at least top-first’s minus one; accepted-but-wrong outcomes no more than top-first’s.

Calls went to named providers at declared prices, each arm-item pair in its own process, with every response body, receipt, and event preserved. 7

How the ladder climbs

run_ladder records every transition. A rung is entered with a reason, declines with one of five reason kinds, and the run ends resolved, with a person, or exhausted:

ladder.rung_entered   rung, reason
ladder.rung_declined  rung, reason โˆˆ { rule_declined:โ€ฆ, call_not_succeeded:โ€ฆ,
                                       output_unavailable:โ€ฆ, check_failed:โ€ฆ,
                                       check_error:โ€ฆ }
ladder.resolved | ladder.asked_human | ladder.exhausted

8

Two real traces from the preserved ledger show what that buys. A05 climbed one rung:

ladder.rung_entered   rule      "first rung"
ladder.rung_declined  rule      "rule_declined:0 ttl lines"
ladder.rung_entered   zen-free  "escalated from rule: rule_declined:0 ttl lines"
ladder.resolved       zen-free

T07 climbed all the way, and its decline reasons never changed:

rule_declined:0 ttl lines
check_failed:quote_has_0_durations     (free)
check_failed:quote_has_0_durations     (cheap)
check_failed:quote_has_0_durations     (strong)
โ†’ ladder.asked_human

7

The router’s decision path contains no model call: run_ladder is 61 lines, _check_decline eight. 7

The result

Recomputed from the frozen analysis rows, matching the bundle’s tables: 7

At $5 per person asked Ladder Top-first
Accepted outcomes 33 (rule 10, free 23) 31 (strong 31)
Correct acceptances 32 31
Accepted but wrong 1 (A05) 0
People asked (of which: item had no single answer) 7 (5) 9 (6)
Model calls 44 40
Known model spend $0.0056 $0.0317, plus one call of unknown spend
Cost per accepted outcome $1.061 $1.453
Summed model latency 417 s 315 s

The preregistered output is recorded as ladder_justified: false, with the verdict label “top-rung-first wins on this workload.” That label needs unfolding, because either half of it read alone is wrong. The ladder met two conditions: it was cheaper per accepted outcome, and it produced one more correct acceptance. It failed the third: it produced one accepted-but-wrong outcome where top-first produced none. The rule required no increase in wrong acceptances, so the ladder was not adopted and top-first remains the default for this workload under this rule. Not “the strong model is better.” Not “cheap-first lost.” One clause fired, and that clause was written to protect exactly what it protected. 7

The cost column rests on a scenario, not a wage. Executed with the frozen figures:

cost = model_spend + people_asked * PERSON_COST   # PERSON_COST = $5, a scenario
cost_per_accepted = cost / accepted
# ladder:    (0.0056 + 7 * 5) / 33 = 1.061
# top-first: (0.0317 + 9 * 5) / 31 = 1.453

With five dollars per person, human escalations dominate both totals. The preserved sensitivity runs at $0 (per-accepted costs of fractions of a cent) and $25 ($5.30 against $7.26); the decision is the same at both, and it is also unchanged when top-first’s unknown-spend call is priced in. The ordering is stable across those scenarios; the absolute numbers are scenario artifacts. 7

A05, and the accident that avoided it

The input reads: in staging the cache TTL is one minute; in production it is ten minutes. There is no single correct answer, and the gold says so. The free rung answered 600 seconds, quoting “10 minutes.” Valid JSON, verbatim quote, exactly one duration, seconds matching the quote: the checker passed it, and the acceptance was wrong.

This is Chapter 21’s adequacy lesson in a new setting. The checker verifies grounding. It was never built to verify that the question has one answer, and it did exactly what it was built to do. The checker is not tuned afterward. A05 is evidence of the checker’s limit, and repairing it post hoc would erase the measurement. 7

Top-first did not make the same mistake, and the preserved bytes show why. Its strong-model call finished for length with 2,047 of its 2,048 completion tokens spent on reasoning and empty content. The reasoning is preserved: the model notices the two durations, asks itself “Maybe answer should be null because not a single?”, circles the schema, and is cut off partway through drafting a null answer. The adapter classified the call as empty_output, the call failed, and the item went to a person.

So top-first’s zero came from a token limit reached mid-deliberation, not from a judgment it delivered. The reasoning suggests the model was heading toward null. It never produced an answer the checker could see, and a slightly larger token budget could have produced either outcome. What the run shows is the mechanism of this one avoidance, not better judgment by the strong model. 7

The checker has a mirror-image limit. T07 (gold 7,200 seconds) and T10 (gold 5,400) had correct answers the quote checker could not verify: zero duration expressions found in one quote, two in the other, on every model rung in both arms. Both items went to people after the paid rungs failed them too. A wrong answer passed; correct answers were rejected. What one deterministic checker can verify is not the same as what is true. 7

The rungs that earned nothing

Resolution in the ladder arm: rule 10 of 10 attempts, free model 23 of 30, cheap model 0 of 7, strong model 0 of 7, a person for the remaining 7. The arithmetic closes on its own: 10 + 23 + 7 = 40 items, and 30 + 7 + 7 = 44 model calls, which corroborates the per-item rows independently of the report’s prose. 7

The measured statement stops there. It does not show that stronger models cannot solve such cases, only that on this seven-item remainder the two paid rungs produced no accepted resolution. The recorded reasons suggest why: quote_has_0_durations, quote_has_2_durations, and items with no value to verify โ€” reasons rooted in the input and the checker, which a stronger model cannot change. That motivates a decline-reason-aware policy, routing by why a rung declined rather than escalating every decline through more capacity. It is a hypothesis with a clear next experiment, not a result. 7

The rule rung carries the chapter’s plainest systems point. On the ten structured items, the rule resolved all ten with no model call. Top-first’s strong model got nine right; on S10 it answered with the bare digits 86400 without their key, so the quote contained no duration expression and the item went to a person. That is one item against one model, and the claim stays that size: when the operation is already mechanically specified, a model call adds a way to fail that the rule does not have, without adding anything the rule was missing. 7

Cost is not the only axis. The ladder spent less on models across more calls, took more summed model time (417 s against 315 s, dominated by the free rung’s reasoning), escalated far more often between rungs (51 transitions against 9), and asked people less often (7 against 9). Reducing model spend did not reduce process time. And the scope fence holds on every sentence above: one synthetic workload, one checker, one ladder configuration, one model set, one pricing period, one run per arm, no variance estimate. 7

The spend the ledger could not see

The A05 top-first call returned HTTP 200 with 92 input and 2,048 output tokens reported, and no text. CodeAI’s adapter raised on the missing text, and its failure path then recorded usage as unavailable and spend as unknown, discarding the usage it had been handed. The projection honestly carried one unknown-spend attempt. The independent verifier, recomputing from the preserved bytes at declared prices, found $0.0083 โ€” the most expensive single call in the run โ€” and failed the spend claim. The bundle keeps both: decision tables use known spend with the unknown call flagged beside them. 7

The cause was an ordering bug inside one function: usage was parsed after text extraction, so an extraction failure skipped it. The repair, made while this chapter was drafted and now in CodeAI a1b562a, parses usage first and carries already-parsed tokens into the failure result. Reduced from the source, with the failure path’s result fields shown as assignments:

raw_usage = parsed.get("usage") if isinstance(parsed.get("usage"), dict) else None
in_tokens, in_reported = _extract_usage(raw_usage, "prompt_tokens")
out_tokens, out_reported = _extract_usage(raw_usage, "completion_tokens")
text = _chat_text(parsed)        # may raise; usage is already in hand
...
# failure path
usage_source = "measured" if (in_reported or out_reported) else "unavailable"
cost_usd = estimate_cost_usd(self.model, in_tokens, out_tokens)
cost_source = "estimated" if cost_usd is not None else "unknown"

9

Three regression tests pin the boundary. The preserved A05 shape (empty content, 92/2,048 usage, a model absent from the pricing table) keeps measured tokens with unknown cost โ€” the $0.0083 belongs to the experiment’s declared prices, not to the adapter’s table. A priced-model variant estimates cost on the failure path. A failure with no usage stays unavailable. The historical bundle is untouched and the experiment was not rerun, so the tables above still show what the run recorded. The sibling adapters share the old ordering and are named, not silently repaired. 10

One adjacent defect stays named. estimate_cost_usd matches model IDs by prefix in table order, so gpt-4o-mini is billed at gpt-4o rates, about seventeen times the intended input price. The one-line fix changes billing semantics under a pricing version that existing tests and the router design pin, so it needs its own versioned migration rather than a quiet edit here. 11 12

The challenger that has not run

The book’s second standing bet is that the process’s routing decision should contain no model call. That bet now has a serious opponent. A deployed system routes in real time between fast and reasoning models using conversation type, complexity, tool needs, and explicit intent, and its router is continuously trained on signals such as users switching models, preference rates, and measured correctness (OpenAI, 2025). That establishes learned routing as a credible production architecture for choosing which model. It does not settle whether operation selection should contain a model call, which is the narrower bet here.

CodeAI’s answer is an experiment design, not a result. It asks whether a model-based router chooses the next operation better than the deterministic policy when both see the same explicit state. Its falsifier says the deterministic bet must be revised if the model-based router is at least 15 percentage points better at operation selection overall, with no stratum worse by more than 5 points; causes no more catastrophic misroutes and no new catastrophe classes; keeps median decision flip rate at or below 10% (p95 at or below 20%); refuses at most 5% of decisions; and produces reasons at least 90% non-vacuous, with auditability not materially worse. The binding runs the other way too: a deterministic router that is systematically worse is a real failure, because reproducibility alone wins nothing. A second, exploratory track separates where intelligence might pay โ€” interpreting state versus choosing policy โ€” and does not feed the falsifier. The design even states its own power limit: with 24 structured and 24โ€“32 narrative cases, a small correctness difference would be suggestive at best. 13

Its status needs to be classified precisely, because several different things are true at once:

Artifact Status
Router contract, model-based router, runner, analysis, verifier Implemented, with tests
68-case corpus Committed as DRAFT; not frozen; no oracle file
Preregistration Committed; declares itself “NOT FROZEN โ€” NOT RUN”
Live comparison None, under any protocol

14 15 13

So there is no router accuracy, no catastrophe count, no cost, no flip rate โ€” nothing to report except the design and its falsifier. That is a stronger position than it sounds: the book’s architectural preference is on the table with the exact conditions under which it falls, and nothing in this chapter or the next depends on how the experiment would come out.

Two naming rules hold throughout. The model arm is model-based, never “learned”: nothing was trained on routing data. And in the exploratory track, a deterministic state compiler must answer UNKNOWN where it cannot extract a fact, never guess. Deterministic is not omniscient, and the experiment compares honest boundaries. 13

Checking it without trusting it

The ladder’s verifier imports neither CodeAI nor the producer. It restates the rule, the quote parser, and the check, then re-derives rung order, verdicts, decline reasons, spend from usage and declared prices, receipts, both controls, scoring, and the decision from preserved bytes. Result: 13 of 14 semantic claims pass. The one failure is the spend under-derivation described above. Five seeded corruptions โ€” a flipped verdict, understated spend, a skipped rung, inflated acceptances, a dropped receipt โ€” each produced problems beyond that baseline failure. A fresh process reprojected all four ledgers identically. The verifier was re-run during drafting with the same 13/14 result. 7

The controls also behaved. A scripted wrong answer on the free rung (45,000 seconds for “45000 ms”) failed the check and escalated, and the cheap rung resolved it correctly. Under READ authority, the resolved answer’s write was denied with the apply adapter called zero times; under WRITE, it applied once. 7

The scheduler matrix verifier asserts 16/16 code-versus-table parity at the v1 surface. The router track has a verifier waiting for a run that does not exist, which makes every router sentence above checkable in a simple way: each is design or status, never outcome. 5 13

What this is not

  • Not a ladder endorsement. The favored architecture failed its own adoption rule. Cheaper did not mean adopted.
  • Not a top-first endorsement. Its clean wrong-acceptance column came, on the one decisive item, from a truncated call.
  • Not a model ranking. Rungs are mechanisms with costs. The rule beat the strong model on one structured item because the operation was already specified.
  • Not economics. Person costs are scenarios; model prices are one period’s declarations.

Where it is still weak

  1. One run, one workload, one checker. Forty synthetic items with no repetition; nothing travels without a new run. 7
  2. The adequacy gap is structural. A05 passes and T07/T10 fail by the checker’s design. 7
  3. Decline-reason routing is untested. The zero paid-rung resolutions motivate it; nothing measures it.
  4. Scheduler flags are precomputed and decisions unrecorded. The seam returns values; deriving and persisting them is the caller’s job. 4
  5. No matrix covers epistemic-v2. The split-budget rows are source-inspected only. 16
  6. The router track is unrun. 13
  7. Pricing prefix shadow and sibling adapters. Named, not repaired. 17

Do this now

Thirty minutes. Put one model call on trial before spending it.

  1. Write the next step of a real task as five flags: process budget, verification pending, proposals wanted, human authority needed, model budget. Run them through a function like the one above and write down the operation with its reason. Then change one flag and watch the priority reorder.
  2. Take an escalation path you already run (retry with a bigger model, then ask a person) and record a decline reason on every transition for one day. Count how many escalations carried a reason no stronger model could fix.
  3. Find one call your accounting could not see: an empty response, a timeout after billing, dropped usage. Decide what its row should have said, and whether your adapter parses usage before or after the step that can fail.
  4. Write the falsifier for one architectural preference you hold. If you cannot name the thresholds that would make you revise it, you hold a slogan, not a bet.

If you are building with an assistant:

Decide the operation before the model: process stop first, pending
verification next, proposals as calls only while model budget remains, a
human gate before effects, otherwise stop. Keep model budget and process
budget as separate flags. Climb escalation ladders with a recorded decline
reason at every transition, and adopt a ladder policy only under a rule
frozen in advance that covers cost per accepted outcome, correct acceptances,
and wrong acceptances together. Parse usage before anything that can fail.
Present competing architectures with their falsifiers, and leave unrun
experiments unrun.

Failure modes

  • Calling the next model by default. A declined rung is evidence about what to try, not an instruction to spend more.
  • Reading one clause of the verdict. “Top-first won” and “the ladder was cheaper” are both true and both incomplete.
  • Crediting the cutoff. A call truncated mid-deliberation delivered no judgment, whatever its reasoning was leaning toward.
  • Tuning the checker after A05. Repairing adequacy after the fact destroys the evidence that measured it.
  • Merging the budgets. One generic “budget exhausted” flag cannot express checks surviving spent cognition.
  • Narrating the unrun. Design, implementation, and preregistration are not a result, however complete they look.

What this chapter established

  • Operation, escalation, and selector are three decisions with three different evidence states: source-inspected policy, a measured stage, and an unrun design. 2
  • The deterministic seam orders process stop, verification, budget-permitted calls, the human gate, and stop, with model and process budgets split, flags caller-supplied, and decisions unrecorded. 2
  • The measured ladder was cheaper per accepted outcome ($1.061 against $1.453 at the $5 scenario) and produced one more correct acceptance, and it failed its frozen adoption rule because it added one accepted-but-wrong outcome. 7
  • A05 shows a grounding checker accepting a grounded wrong answer; top-first avoided it through a truncated, unusable output, not demonstrated judgment. T07/T10 bound the checker from the other side. 7
  • The failure-path usage defect is repaired in current source with regression tests; the bundle is untouched. The pricing-prefix shadow remains named. 9
  • The model-router challenge is specified with a falsifier and remains unrun. 13

Next

The pieces are now all on the table, and this chapter deliberately does not assemble them: decision records, authority, observed effects, bound verification, replay, blind proposal collection, measured promotion, and an execution policy with recorded reasons. Each was tested against its own fixture, and several survived by being told no. Whether they hold together when one task has to pass through all of them โ€” and which joints the runtime actually enforces โ€” is the last construction question in the book. Chapter 30 then asks what the construction means once the process, and the policy deciding what happens next, is yours.

Continue with Applied AI.

References

  • Ehud Karpas, Omri Abend, Yonatan Belinkov, Barak Lenz, Opher Lieber, Nir Ratner, Yoav Shoham, Hofit Bata, Yoav Levine, Kevin Leyton-Brown, Dor Muhlgay, Noam Rozen, Erez Schwartz, Gal Shachaf, Shai Shalev-Shwartz, Amnon Shashua, and Moshe Tenenholtz. MRKL Systems: A Modular, Neuro-Symbolic Architecture That Combines Large Language Models, External Knowledge Sources and Discrete Reasoning. arXiv:2205.00445, 2022. Paper.
  • Isaac Ong, Amjad Almahairi, Vincent Wu, Wei-Lin Chiang, Tianhao Wu, Joseph E. Gonzalez, M. Waleed Kadous, and Ion Stoica. RouteLLM: Learning to Route LLMs with Preference Data. arXiv:2406.18665, 2024. Paper.
  • OpenAI. GPT-5 System Card. 2025. System card.

Implementation sources: Stage 29B ran at CodeAI 7a0d43b. Source inspection for this chapter used a1b562a, which contains the failure-path usage repair, its regression tests, the router track files, and a behavior-neutral reformat of scheduler.py. src/codeai/scheduler.py: Operation, SchedulerInput, SchedulerDecision, decide_next_step, POLICY_VERSION; src/codeai/runtime.py: Runtime.decide_next; src/codeai/ladder.py: run_ladder, _candidate, _check_decline, project_ladder_run, render_receipt; src/codeai/providers.py: OpenCodeCognitionAdapter.send, PRICING_TABLE, estimate_cost_usd; src/codeai/router_model.py, router_contract.py, router_experiment.py, router_analysis.py; experiments/router_compare.py, experiments/verify_router.py, experiments/router-prereg.md, experiments/router_cases_v1.DRAFT.json. Tests: tests/test_opencode_gateway.py (usage-preservation regressions), tests/test_budget_safety.py (pricing shadow). The scheduler excerpt is current source; the precedence fragments were executed as teaching code, not as a stage. The ladder traces are read from the preserved ledger events and analysis rows. Evidence: experiments/applied-ai/evidence/execution-ladder/2026-09-14-7a0d43b/ (preregistration, evidence report, analysis rows, preserved bodies and ledgers, independent verifier), experiments/applied-ai/evidence/scheduler/ (v1 matrix demo), and docs/applied-ai/ch28-router-experiment-design.md (unfrozen, unrun design). No evidence was modified. Footnotes mark provenance: measurement notes refer to pinned evidence, demo notes to preserved unpinned execution, source notes to inspected code, report notes to frozen designs and reports.


  1. Source inspection: src/codeai/scheduler.py (decide_next_step, POLICY_VERSION). ↩︎

  2. Source inspection: src/codeai/scheduler.py (decide_next_step). ↩︎ ↩︎ ↩︎ ↩︎

  3. Source inspection: src/codeai/scheduler.py (Operation). ↩︎

  4. Source inspection: src/codeai/runtime.py (Runtime.decide_next). ↩︎ ↩︎

  5. Unpinned demonstration: experiments/applied-ai/evidence/scheduler↩︎ ↩︎ ↩︎

  6. Source inspection: src/codeai/scheduler.py (SchedulerInput). ↩︎

  7. Measured run: experiments/applied-ai/evidence/execution-ladder/2026-09-14-7a0d43b↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  8. Source inspection: src/codeai/ladder.py (run_ladder, _candidate, _check_decline). ↩︎

  9. Source inspection: src/codeai/providers.py (OpenCodeCognitionAdapter.send). ↩︎ ↩︎

  10. Source inspection: tests/test_opencode_gateway.py↩︎

  11. Source inspection: src/codeai/providers.py (PRICING_TABLE, estimate_cost_usd). ↩︎

  12. Source inspection: tests/test_budget_safety.py↩︎

  13. Report: docs/applied-ai/ch28-router-experiment-design.md↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  14. Source inspection: src/codeai/router_model.py, (router_contract.py). ↩︎

  15. Source inspection: experiments/router-prereg.md↩︎

  16. Source inspection: src/codeai/scheduler.py (POLICY_VERSION). ↩︎

  17. Source inspection: src/codeai/providers.py (PRICING_TABLE). ↩︎