Switch Models During a Fable 5.2 Agent Task Without Losing Progress
Use a verified checkpoint and tool-result ledger to resume a task on another model. Includes a real Ofox handoff, synthetic stock data, and a validator.
When an agent switches away from Fable 5.2, preserve its verified task state separately from its conversation history. The new model needs the inputs, completed tool results, remaining work, and acceptance conditions. A readable summary of what the previous model “was thinking” is not a reliable substitute.
We tested this pattern through Ofox on November 10, 2026: Fable 5.2 requested a read-only stock lookup, received three synthetic rows, and completed its native tool round trip. We then started a new Opus 5.5 conversation with an application-generated checkpoint. Its final JSON passed independent checks for every SKU and quantity. This demonstrates one small state-transfer workflow, not an hours-long agent benchmark or preserved hidden reasoning.
Why a successful fallback can still lose useful state
The official preserved-thinking documentation distinguishes model compatibility from application progress. On the documented Claude API and Google-managed paths, older models cannot read Fable 5.2 thinking blocks; unsupported blocks can be dropped while the request succeeds. Account binding adds another boundary when a different account handles a continuation.
Those rules do not make every model switch an error. They mean that HTTP success is insufficient evidence of reasoning continuity. Ofox’s catalog lists routing capabilities, but it does not prove the behavior of every signed-history transfer across providers or accounts. We did not test cross-account replay, and our handoff deliberately avoids depending on it.
There are two distinct strategies. Continue an unchanged conversation on a compatible, verified route when preserving that history is appropriate. Or start a new conversation with application-owned state when deliberately changing models, tools, or task configuration. This tutorial implements the second strategy; it does not modify signatures or reconstruct private thinking.
Download the small reproducible example
Download checkpoint_demo.py. It uses Python 3.9 or later and the standard library. Its default checks make no API calls. Put it in an empty folder so that generated files do not overwrite an existing task.
For the complete Fable tool request → tool-result continuation → new Opus session, also download run_handoff.py into the same folder. Run python3 run_handoff.py for local checks only. After setting OFOX_API_KEY, python3 run_handoff.py --live explicitly makes up to three paid requests, saves the request/response files in a new handoff-run folder, and validates the final result. It makes no automatic retries and refuses to reuse that output folder. Inspect a failed or uncertain attempt before creating another run.
Each request is bounded to 4,096 output tokens and a small synthetic body. Those bounds limit this demonstration; they are not a universal dollar cap or a guarantee that a timed-out request was free. The published runner follows the request sequence we tested; its local checks and request serialization were verified separately.
python3 checkpoint_demo.py self-test
python3 checkpoint_demo.py prepare
The first command runs positive and negative checks. The second creates checkpoint.json and handoff.txt, refusing to replace files that already exist. The data is a synthetic inventory fixture, not customer stock or a business recommendation:
| SKU | Available | Target | Expected shortage |
|---|---|---|---|
| DEMO-001 | 7 | 10 | 3 |
| DEMO-002 | 15 | 12 | 0 |
| DEMO-003 | 0 | 4 | 4 |
The task is to report max(target − available, 0) for every row. It explicitly forbids placing orders. Keeping the zero-shortage row is part of the task: a response listing only products that need stock is incomplete here.
Save evidence before asking another model to continue
The checkpoint records the objective, a hash of the source rows, one completed read operation, its actual result and hash, pending work, and the prohibition on external writes. The application creates this record from the tool’s result. It does not ask the model to certify its own actions.
{
"schema_version": 1,
"task_id": "synthetic-stock-20261110",
"objective": "Report shortage=max(target-available,0) for every SKU. Do not place orders.",
"completed": [{
"operation_id": "read-stock-1",
"operation": "read_stock",
"status": "verified"
}],
"pending": ["Produce all three SKU shortage rows", "Pass independent validator"],
"side_effects": []
}
This excerpt omits source hashes and result rows for readability; the generated file includes them. Keep the full file for execution. A hash detects a mismatch against the source you trust; it does not establish who authored the source, whether inventory is current, or whether an untrusted checkpoint is safe. In production, retain the authoritative input version and access controls as well.
For a real agent, store the checkpoint durably at a completed tool boundary. Record the tool invocation ID for conversation pairing and a separate stable operation ID for your application ledger. They serve different purposes. A new model may create a different tool-call ID for the same logical operation, so a provider-generated ID alone is not a general duplicate-action guard.
The tested Fable tool round trip
Our first request used anthropic/claude-fable-5.2, the native Ofox Messages endpoint, output_config.effort: "low", max_tokens: 4096, and a single read_stock tool with an empty-object input schema. Tool choice was auto.
The response ended with tool_use and requested read_stock with {}. Our runner verified the name and arguments, returned the three fixture rows under the matching tool_use_id, and preserved the whole assistant content. The second Fable request ended with end_turn and reported shortages 3, 0, and 4.
That native continuation matters: a checkpoint does not excuse a broken tool loop. If the same-model request fails with an unmatched ID, repair the tool-result pairing first. If the failure names signed history, use the thinking-signature diagnostic and 5.2 upgrade checks.
We intentionally used low effort for this tiny arithmetic fixture. It says nothing about the effort needed for a difficult long-running task. Also, a catalog’s aws_bedrock provider listing is not proof of a particular upstream account on a particular response; our recorded route is the Ofox native endpoint and returned model ID.
Start the receiving model with an explicit handoff
The generated handoff.txt says:
Resume this read-only task from verified application state.
Do not claim access to prior reasoning.
Do not call tools or place orders.
Return only a JSON object with key rows, containing every SKU
and integer shortage. No Markdown.
Checkpoint: [the complete generated checkpoint JSON]
Create the receiving request from that file. This example consumes API credits when run; the local preparation commands above do not.
import json
from pathlib import Path
request = {
"model": "anthropic/claude-opus-5.5",
"max_tokens": 4096,
"output_config": {"effort": "low"},
"messages": [{"role": "user", "content": Path("handoff.txt").read_text()}],
}
Path("handoff-request.json").write_text(json.dumps(request))
curl --fail-with-body --max-time 180 \
https://api.ofox.ai/anthropic/v1/messages \
-H "x-api-key: $OFOX_API_KEY" \
-H 'anthropic-version: 2023-06-01' \
-H 'content-type: application/json' \
--data-binary @handoff-request.json > handoff-response.json
There are no previous assistant messages or signed thinking blocks in this request. It resumes the task, not the same provider conversation. That is why changing models here does not require editing the old prefix. A larger task needs a correspondingly richer evidence bundle, with source locations and unresolved questions rather than an oversized unverified narrative.
Validate the delivered result independently
Extract only completed text output, parse it as JSON, and run the provided validator:
import json
from pathlib import Path
r = json.loads(Path("handoff-response.json").read_text())
if r.get("stop_reason") != "end_turn":
raise SystemExit("Incomplete response; inspect before accepting.")
text = "\n".join(b["text"] for b in r.get("content", []) if b.get("type") == "text")
result = json.loads(text) # Reject prose and Markdown instead of guessing.
Path("result.json").write_text(json.dumps(result, indent=2))
python3 checkpoint_demo.py validate result.json
Our actual Opus response parsed to:
{"rows":[{"sku":"DEMO-001","shortage":3},{"sku":"DEMO-002","shortage":0},{"sku":"DEMO-003","shortage":4}]}
The validator accepted three rows with total shortage 7. It checks exact SKU coverage, duplicate IDs, integer types, and each recomputed quantity. It rejects a string such as "3", a boolean, missing or extra SKUs, and incorrect amounts. The checkpoint verifier separately rejects changed source/result hashes, an unverified read operation, or unexpected side effects.
These deterministic checks are stronger evidence for this task than asking either model, “Are you sure the answer is complete?” They are still deliberately narrow: the script does not validate an arbitrary business workflow, fetch current inventory, or provide a production transaction system.
What the run established—and what it did not
| Stage | Observed result | Input / output tokens |
|---|---|---|
| Fable 5.2 requests read tool | HTTP 200, tool_use, correct allowed tool | 467 / 27 |
| Fable 5.2 receives tool result | HTTP 200, end_turn, correct three shortages | 578 / 235 |
| New Opus 5.5 session receives checkpoint | HTTP 200, end_turn, final JSON passes | 457 / 67 |
All three reported zero cache-read and cache-creation tokens. Applying the checked standard list rates gives $0.022008 for these three requests: $0.004816, $0.014024, and $0.003168 respectively. This is a usage-based calculation, not a separately reconciled invoice. The comparison article explains the rates and why a tiny test does not rank the models.
We did not trigger a real service outage, test cross-account thinking replay, compare many tasks, or prove automatic fallback reliability. The source data was synthetic, the read tool ran locally, and no orders were submitted. The model responses themselves were real API responses.

Screenshot of our locally rendered test record, populated from actual API responses. This is not an Ofox product-interface screenshot.
Handle interruption around a write differently
For a read-only operation, repeating a lookup may be acceptable if freshness and cost are understood. For a write, a timeout leaves a more dangerous question: did the external system complete the action before the client lost the response?
A production ledger should distinguish planned, in-flight, confirmed, failed, and uncertain operations. After a timeout, query the destination by your stable operation key before retrying. If the destination supports an idempotency key, use it and preserve the response. If it does not, you cannot manufacture exactly-once delivery merely by storing a local JSON file.
Do not mark an uncertain write as completed because a model’s summary says it happened. Do not let a receiving model repeat it to “be safe.” Resume from verified results, and send unresolved operations to reconciliation. The same pattern applies to submitted forms, created tickets, outgoing emails, and payments, although each destination requires its own controls.
Make the checkpoint fit the deliverable
For a research report, carry the source URL, retrieved evidence, claim status, and unanswered questions. For a spreadsheet, carry input versions, row identifiers, transformation rules, and output checks. For a code change, carry the commit or diff, tests actually run, failures, and remaining work. Preserve evidence of completed work rather than only a progress percentage.
Use a small initial migration: one read-only task, one model change, and an independent acceptance condition. Expand only after failure cases are understood. The API guide covers initial connectivity, while Ofox native Messages and provider routing describe the integration surface. State continuity remains an application responsibility even when the next request succeeds.


