AGENTIC ENGINEERING

The Model Is an Operational Variable: Provenance and Budgets for Agent Loops

N
Nick
Agentspan
July 7, 2026 12 min read
Updated July 9, 2026
The Model Is an Operational Variable: Provenance and Budgets for Agent Loops

The model layer has been unusually loud lately. Claude Fable 5 launched on June 9 as the first Mythos-class model available to the public, priced at double Opus rates. Within days, a U.S. export directive forced it temporarily offline; it returned on July 1. And Fable 5 is leaving subscription limits: from July 13 it bills through metered usage credits instead, a cutoff Anthropic has already moved once.

This article argues the following: an agent loop is a sequence of model calls, and it has to keep its own receipts, because neither the model that serves each pass nor the billing behind it is stable. This month, both changed. And when a loop cannot say which model actually served a given pass, you cannot debug a mid-run quality shift, you cannot audit what produced an output, and you cannot bound what a long-running loop spends.

Fable 5 is the sharpest current example. It screens every request with a safety classifier, and when a request is flagged, Claude switches the conversation to Opus 4.8 on Anthropic’s own surfaces. On the API, it returns a refusal with no content. Which requests get flagged is not deterministic. We measured it.

We have written before about why an agent loop that lives in your process loses work when it crashes and why an agent is not just an LLM in a while loop. This post extends that argument to two more things the runtime should own: the loop’s model provenance and its budget. Everything below ran live against a local Agentspan server and the Anthropic API on July 6–7, 2026.

TL;DR
9 min readIntermediate

Claude Fable 5's safety classifier intermittently refuses benign requests (4 of 10 identical calls in our test) while claude.ai switches those conversations to Opus 4.8, and Fable moves to metered usage credits after July 12, a deadline already extended once. A production agent loop should record which model served every pass, fail over explicitly, and enforce a spend ceiling — built here as a durable adaptive loop.

  • 1The API reports what served each call: read model and stop_reason off every response
  • 2Fable 5 refusals are probabilistic: an identical benign prompt was refused 4 times out of 10
  • 3Record requested vs served model, tokens, and cost in each pass's task output — the execution history becomes the audit trail
  • 4Fail over to a pinned fallback in your own code, and write down why
  • 5Put the budget in the loop condition, next to the iteration ceiling and the done flag

Why does Claude Fable 5 refuse or switch to Opus 4.8?

Fable 5 is the public, guardrailed sibling of Claude Mythos 5. Because its raw capabilities exceed what Anthropic will ship unguarded, every request passes through automated safety checks targeting four areas: offensive cyber techniques, most biology, chemistry, and life-sciences queries, distillation attacks, and a narrow set of frontier-model development tasks. On Anthropic’s surfaces, a flagged request continues on a non-Mythos model such as Opus 4.8 rather than failing outright.

The catch is precision. Users report routine systems programming and authorized security work being downgraded, even inside Anthropic’s Cyber Verification Program, with similar reports for basic code reviews. On a chat surface, a downgrade costs you some capability. Inside an automated loop, it changes the behavior of one iteration out of many, and nothing tells you which one.

On the raw API there is no automatic switch by default. There is only the response, and two fields in it are load-bearing: model, which reports the model that actually served the call, and stop_reason, which reports how generation ended.

We measured it: 4 of 10 identical requests refused

The test prompt is deliberately benign, the kind of task an internal-platform agent does all day: review an Express middleware for a timing-attack vulnerability and suggest a constant-time fix. Defensive, textbook, the sort of thing linters nag about.

We sent the identical request to claude-fable-5 ten times in a row on July 6. Six returned a substantive answer, cut off only by our 350-token cap. Four returned a response like this one, with empty content and output tokens varying from 4 to 7:

{
  "model": "claude-fable-5",
  "content": [],
  "stop_reason": "refusal",
  "usage": { "input_tokens": 115, "output_tokens": 4 }
}

HTTP 200, no error object. The same prompt sent to claude-opus-4-8 was never refused in any of our four control attempts.

Two observations matter downstream. First, the refusal is intermittent: same prompt, same model, same account, minutes apart, sometimes answered and sometimes not. A classifier near its decision boundary behaves probabilistically, so you cannot allowlist your way around it. Second, a refusal produces nothing but still reports usage: input_tokens: 115 on every refused response. Our loop counts that toward pass cost as a conservative ceiling, though Anthropic’s documentation says a refusal with no output is not billed.

For a loop, the deeper failure mode is state. Pass 3 of your refinement loop returns empty; a naive loop carries the empty draft forward, and every pass after it refines nothing.

What changes on July 12: Fable 5 moves to usage credits

Through July 12, Fable 5 is included in Pro, Max, and Team plans for up to half of weekly usage limits, extended from the original July 7 cutoff hours before it took effect. After that it bills through usage credits at API rates ($10 per million input tokens, $50 per million output), enabled and capped from account settings. Anthropic calls the arrangement temporary, returning to subscriptions once capacity allows.

Good to Note

Update, July 9: the first version of this post, published July 7, said Fable 5 leaves subscription limits “tomorrow.” Hours later, Anthropic extended the deadline to July 12. That is this article’s thesis in miniature: the billing under your agent is set by someone else’s announcement, and even the announcement moves. The loop below needed no changes, because its budget guard reads the same either way. This paragraph did.

The billing regime under a running agent changed on a calendar date, with no deploy on your side. A loop written as “iterate until converged” will either converge or keep spending, and the allowance it was written against no longer exists. A production loop needs a spend ceiling alongside its iteration ceiling.

The pattern: record provenance, fail over explicitly, cap spend

The fix is architectural, and it is the same move that makes a loop survive a crash: take the loop out of your process and give it to a runtime that records every pass. Agentspan calls this shape an adaptive loop — each iteration is durable and observable, and the loop’s continuation condition is enforced server-side. Three additions complete it:

  1. Record provenance per pass. Every pass’s task output carries requested_model, served_model, the API response_id, stop_reason, token counts, and cost. The execution history becomes the audit trail: which model ran pass 3 of run 8842 is a lookup, not an archaeology project.
  2. Fail over explicitly. On stop_reason: "refusal", the tool calls a pinned fallback, claude-opus-4-8, the same tier Anthropic’s surfaces fall back to, and records why. Anthropic also ships an opt-in server-side fallback on its API; we keep the policy in our own code so it works across providers and lands in the execution history.
  3. Cap spend in the loop condition. Cost accumulates across passes in the task output, and the loop’s continuation condition checks it alongside the iteration ceiling and the done flag.

Building it as an adaptive loop

The whole demo is one script against a local server (pip install conductor-agent-sdk, agentspan server start). The model call lives in a @tool, which the runtime executes as a durable worker task, one instance per pass:

from conductor.ai.agents import AgentRuntime, plan_execute, tool

PRIMARY, FALLBACK = "claude-fable-5", "claude-opus-4-8"
PRICE = {"claude-fable-5": (10.0, 50.0), "claude-opus-4-8": (5.0, 25.0)}

@tool
def agent_pass(goal: str = "", draft: str = "", iteration: int = 1,
               budget_usd: float = 1e9, cost_total_usd: float = 0.0) -> dict:
    """One refinement pass: ask Fable 5 first, fail over to Opus 4.8 on
    refusal, and return full model provenance for the pass."""
    iteration = int(iteration or 1)
    draft = draft or ""
    cost_total = float(cost_total_usd or 0.0)
    budget = float(budget_usd or 1e9)
    prompt = prompt_for(iteration, goal, draft)

    resp_id, served, stop, text, usage = call_model(PRIMARY, prompt)
    pass_cost = cost_usd(PRIMARY, usage)
    tokens_in, tokens_out = usage["input_tokens"], usage["output_tokens"]
    fallback_reason = None

    if stop == "refusal" or not text:
        fallback_reason = f"{PRIMARY} returned stop_reason={stop!r} with no content"
        resp_id, served, stop, text, usage = call_model(FALLBACK, prompt)
        pass_cost += cost_usd(FALLBACK, usage)
        tokens_in += usage["input_tokens"]
        tokens_out += usage["output_tokens"]

    if iteration == 3 and text:
        new_draft = f"{draft}\n\n### Example: hardening the token check\n\n{text}"
    else:
        new_draft = text or draft

    cost_total += pass_cost
    return {
        "iteration": iteration,
        "requested_model": PRIMARY,
        "served_model": served,
        "stop_reason": stop,
        "fallback_reason": fallback_reason,
        "response_id": resp_id,
        "tokens_in": tokens_in,
        "tokens_out": tokens_out,
        "pass_cost_usd": round(pass_cost, 6),
        "cost_total_usd": round(cost_total, 6),
        "budget_exceeded": cost_total >= budget,
        "done": iteration >= 4,
        "draft": new_draft,
    }

call_model is a plain requests.post to /v1/messages; the served model and stop reason come straight off the response body, and cost_usd applies the PRICE table. Nothing here depends on the provider: the same pattern pins and records any model behind any API.

The loop itself is a DO_WHILE with three guards in its server-enforced condition. The SIMPLE task’s name is the tool’s name, and each pass reads the previous pass’s output:

{
  "name": "agent_loop",
  "taskReferenceName": "agent_loop",
  "type": "DO_WHILE",
  "loopCondition": "if ($.agent_loop['iteration'] < 5 && $.think['done'] != true && $.think['budget_exceeded'] != true) { true; } else { false; }",
  "loopOver": [{
    "name": "agent_pass",
    "taskReferenceName": "think",
    "type": "SIMPLE",
    "inputParameters": {
      "goal": "${workflow.input.goal}",
      "budget_usd": "${workflow.input.budget_usd}",
      "draft": "${think.output.draft}",
      "iteration": "${agent_loop.output.iteration}",
      "cost_total_usd": "${think.output.cost_total_usd}"
    }
  }]
}

Serving the tool and starting a run is the remaining glue:

harness = plan_execute(name="provenance_harness", tools=[agent_pass],
                       planner_instructions="(unused)", model="anthropic/claude-opus-4-8")

with AgentRuntime() as runtime:
    runtime.serve(harness, blocking=False)      # registers agent_pass as a worker
    register_workflow(WORKFLOW)                  # POST /api/metadata/workflow
    execution_id = start(goal, budget_usd=1.0)   # POST /api/workflow/provenance_agent_loop

One operational note: the SDK registers the tool’s task definition with a 10-second response timeout by default. Model calls run longer, so we override the task definition to 120 seconds before starting; otherwise the server re-queues a pass mid-call and you pay for the same tokens twice.

What the execution history shows

We gave the loop a four-pass documentation task, run repeatedly with identical input: draft and refine a security-hardening checklist for an internal Express API, with pass 3 producing the timing-attack code review.

Some runs are uneventful. Every pass reports served_model: claude-fable-5 and stop_reason: end_turn; the run costs about $0.22, and the four provenance records all say the same thing: you got what you asked for.

Execution view of the fallback run

The fallback run in the execution view: four agent_pass tool passes under one execution ID, each a durable step with its own recorded output. From the outside, nothing looks unusual.

Then there is the run where pass 3 drew the short straw. Fable 5’s classifier refused the code-review prompt it had answered in full on the previous run, and the tool’s policy took over: it called claude-opus-4-8, completed the pass, and wrote down why.

Pass 3's task output: requested claude-fable-5, served claude-opus-4-8, with the fallback reason

Pass 3’s recorded output: requested claude-fable-5, served claude-opus-4-8, and the reason. As our loop counts it, the pass cost $0.029: the refused call contributed only reported input tokens, and Opus runs at half Fable’s rates, so the fallback pass came out cheaper than a Fable-served one.

From the outside the two runs are indistinguishable: the same completed tasks, the same COMPLETED status. The difference lives only in the recorded output of pass 3, which is the entire point. Without provenance in the history, “why is this run’s code example structured differently from yesterday’s?” has no answer.

The budget guard closes the loop on the usage-credits problem. We started the same workflow with budget_usd: 0.08. Pass 2 pushed cumulative cost to $0.087, its output set budget_exceeded: true, the loop condition failed, and the workflow completed after two passes instead of four: terminated by the spend ceiling rather than the done flag, with the partial draft preserved in the history.

The budget run's final pass output showing budget_exceeded true

The budget run’s final pass: cost_total_usd crossed the $0.08 ceiling, so budget_exceeded flipped and the loop stopped. An unattended loop cannot run up a bill it was never authorized to.

Beyond Fable 5

None of this is specific to Fable 5’s classifier. GPT-5.5 Instant replaced GPT-5.3 as ChatGPT’s default in May, and Fable 5 itself spent almost three weeks of June offline under a U.S. export directive before returning on July 1. The serving model is an operational variable, so treat it like one: pin what you can, record what actually happened, and keep the policy where you can see it. A durable loop gives you the substrate for all three, because the runtime that owns the iteration state can own the receipts too.

Further reading

Related Posts