●BILLING — Day two after the Jun 15 change: Agent SDK, headless runs, GitHub Actions, and third-party agents now bill against separate monthly credits ($20/$100/$200) at full API rates with no rollover, making first-day cost measurements the basis for any rework●REGULATED — TCS partnered with Anthropic to bring Claude to banks, airlines, and other regulated industries, while DXC integrates Claude into the core systems those sectors rely on●RETIRED — Sonnet 4 and Opus 4 left the API on Jun 15; confirm via your logs that scripts referencing them have moved to the latest generation such as Opus 4.8●EXPORT — Claude Fable 5 and Mythos 5 remain suspended under a US export-control directive (since Jun 12); Anthropic says it is working to restore access●SAFE — Only the two new Mythos-class models are affected; every other model including Opus 4.8 keeps running normally●SUBAGENTS — Claude Code sub-agents can spawn their own sub-agents up to five levels deep, widening the design space for multi-stage delegation●BILLING — Day two after the Jun 15 change: Agent SDK, headless runs, GitHub Actions, and third-party agents now bill against separate monthly credits ($20/$100/$200) at full API rates with no rollover, making first-day cost measurements the basis for any rework●REGULATED — TCS partnered with Anthropic to bring Claude to banks, airlines, and other regulated industries, while DXC integrates Claude into the core systems those sectors rely on●RETIRED — Sonnet 4 and Opus 4 left the API on Jun 15; confirm via your logs that scripts referencing them have moved to the latest generation such as Opus 4.8●EXPORT — Claude Fable 5 and Mythos 5 remain suspended under a US export-control directive (since Jun 12); Anthropic says it is working to restore access●SAFE — Only the two new Mythos-class models are affected; every other model including Opus 4.8 keeps running normally●SUBAGENTS — Claude Code sub-agents can spawn their own sub-agents up to five levels deep, widening the design space for multi-stage delegation
Keep a Decision Rationale Ledger for Autonomous Agents — So You Can Explain 'Why' Later
When an autonomous agent takes hard-to-reverse actions like a production deploy or a bulk delete, capture the chosen option, rejected alternatives, and assumptions in a structured ledger. Includes structured output, an append-only log, and tiering by impact.
Last month I had an automation pipeline I run as an indie developer rewrite a config file and push the change to production. When I looked back at one of those changes the following week, I could no longer reconstruct why that value had been chosen. The log only held the fact that something "was changed" — nowhere was the reasoning: which options were compared, on what grounds, and what was rejected.
No errors. The action succeeded. And yet a pile of automated decisions I could no longer explain felt quietly dangerous. Here I want to share the design and implementation of a small mechanism — a decision rationale record — that makes that "why" recoverable after the fact.
Borrowing 'Explainability' from Regulated Industries, in a Small Way
Around the same time, news broke that TCS and Anthropic had partnered to bring Claude into regulated industries like banking and aviation, with DXC also pushing integration into existing systems. When AI goes into production in those domains, one question is non-negotiable: can a human explain the decision afterward? In an audit or an incident review, if only the outcome survives and not the rationale, automation simply isn't allowed in the first place.
The pipeline I run has nothing to do with regulation. Even so, I think this demand for explainability is worth borrowing — in a small form — precisely for solo autonomous operations. When you operate alone, there's no third party to verify your decisions later. The you of six months ago is effectively a stranger, and you inherit and keep running the automated decisions that stranger made.
What I'm borrowing isn't a heavyweight audit platform. It's a single discipline: before taking a hard-to-reverse action, capture the rationale in a structured form, one line.
The Data Model — Chosen, Rejected, Assumptions, Reversibility
The first thing to decide is what to keep. The fact that "something changed" is already in the log. What was missing was the structure of the thinking that preceded it. I settled on five minimal fields.
chosen: the action actually taken (summary)
considered: the list of options that were weighed
rejected: the options that were turned down, with reasons
assumptions: the premises behind the decision (revisit if they break)
reversibility: whether the action can be undone (reversible / hard_to_reverse / irreversible)
I added that last reversibility field to grade the density of rationale after the fact. The harder an action is to reverse, the more dangerous it is to have thin rejected-options and assumptions.
In TypeScript, a record looks like this:
interface DecisionRecord { action_id: string; // the action this is tied to chosen: string; // summary of the action taken considered: string[]; // options compared rejected: { option: string; reason: string }[]; assumptions: string[]; // premises (re-evaluate if broken) reversibility: "reversible" | "hard_to_reverse" | "irreversible"; confidence: number; // 0.0–1.0}
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦You'll have a working pattern that makes the model itself write 'why I did this' as structured output and append it to a tamper-evident ledger
✦You'll stop wasting 15%+ of output tokens demanding rationale on every action, and scope it to the few cases that actually need it
✦You'll avoid the 'rationale theater' trap where records become hollow post-hoc justifications, and capture rejected options and assumptions instead
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Make the Model Write Its Own Rationale via Structured Output
Having a human write the rationale after the fact won't last. The realistic approach is to make the model that decided on the action write the rationale, in structured form, within the same call. Using the Anthropic SDK's tool definitions, you make the rationale schema the tool's input schema, forcing "always return this shape."
The code below shows getting the action and the rationale out together and pulling it out as JSON.
import jsonfrom anthropic import Anthropicclient = Anthropic()# The rationale record schema doubles as the tool input schemaDECISION_TOOL = { "name": "record_decision", "description": "Record the rationale before taking a hard-to-reverse action", "input_schema": { "type": "object", "properties": { "chosen": {"type": "string"}, "considered": {"type": "array", "items": {"type": "string"}}, "rejected": { "type": "array", "items": { "type": "object", "properties": { "option": {"type": "string"}, "reason": {"type": "string"}, }, "required": ["option", "reason"], }, }, "assumptions": {"type": "array", "items": {"type": "string"}}, "reversibility": { "type": "string", "enum": ["reversible", "hard_to_reverse", "irreversible"], }, "confidence": {"type": "number"}, }, "required": ["chosen", "considered", "rejected", "assumptions", "reversibility", "confidence"], },}def decide_with_rationale(task: str) -> dict: """Get the action decision and rationale record in one call.""" resp = client.messages.create( model="claude-opus-4-8", max_tokens=1024, tools=[DECISION_TOOL], tool_choice={"type": "tool", "name": "record_decision"}, messages=[{ "role": "user", "content": ( "For the following task, produce the action to take and your " "rationale in the record_decision format. Every rejected option " "must include a concrete reason.\n\n" f"Task: {task}" ), }], ) for block in resp.content: if block.type == "tool_use" and block.name == "record_decision": return block.input raise RuntimeError("No rationale record was returned")record = decide_with_rationale("Raise the retry ceiling in the prod config from 3 to 5")print(json.dumps(record, ensure_ascii=False, indent=2))# => a dict with chosen / considered / rejected / assumptions / reversibility / confidence
The crucial part is forcing tool use with tool_choice. Skip it and the model may decide "the rationale is obvious, I'll omit it" and return plain text. Force it, and the absence of a rationale record itself becomes an anomaly signal.
Append the Record to a Tamper-Evident Ledger — with a Content Hash and Commit
Once you have the record, store it so it can't be quietly rewritten. I use an append-only JSONL: one action per line, never overwriting or deleting a line. The key is to attach a content hash and the git commit at that moment.
import hashlibimport jsonimport subprocessimport timefrom pathlib import PathLEDGER = Path("decision_ledger.jsonl")def _git_commit() -> str: try: out = subprocess.run( ["git", "rev-parse", "--short", "HEAD"], capture_output=True, text=True, check=True, ) return out.stdout.strip() except Exception: return "unknown"def append_record(action_id: str, record: dict, served_model: str) -> str: """Append the rationale record to the append-only ledger; return its content hash.""" entry = { "ts": time.strftime("%Y-%m-%dT%H:%M:%S%z"), "action_id": action_id, "served_model": served_model, # the model that actually responded "commit": _git_commit(), "record": record, } line = json.dumps(entry, ensure_ascii=False, sort_keys=True) digest = hashlib.sha256(line.encode("utf-8")).hexdigest()[:16] with LEDGER.open("a", encoding="utf-8") as f: f.write(line + "\n") return digest
I include served_model because the model you request and the model that actually responds can diverge. When a fallback kicks in, you don't want to later mistake a decision made by a different model for "the latest model decided this." For recording the responding model, see recording which model served the response. For keeping the ledger itself along an audit-and-replay axis, archiving Claude API responses is a closely related design.
Which Actions Should Require a Rationale — Draw the Line by Impact
At first I demanded a rationale for every action. That was a mistake. Forcing rationale even on harmless actions like reads and log writes bumped output tokens by a perceptible 15%+, and the ledger filled with "no particular reason" noise that buried the decisions that mattered.
So I split actions into three impact tiers and narrowed where rationale is required.
read-only (no rationale): fetch, search, view. Nothing to reverse.
reversible-write (confidence only): writes you can undo — draft saves, regenerable cache updates.
hard-to-reverse and above (full rationale enforced): production deploys, bulk file deletes, external API writes, anything touching billing.
A name-based mapping was enough for classification. When I let the model self-report impact, it tended to underrate impact exactly when it was in a hurry, so impact is hard-coded on the code side.
The one thing I wouldn't budge on is defaulting unknown action names to hard_to_reverse. If a classification is missing, erring toward capturing too much rationale only wastes a little, whereas erring the other way lets an irreversible operation through with no record.
This is the one to watch most. When you make the model emit the action and the rationale together, the rationale tends to become prose that justifies a conclusion already reached. It shows up as the rejected-options field listing bland alternatives that were never actually considered.
As a countermeasure, I made a concrete reason mandatory for each rejected option, and my quality gate rejects records whose reason is under 30 characters. The bar is whether the reason is specific to the situation — not a generality like "because it's costly," but something like "because at this value retries run serially and tail latency nearly doubles."
Pitfall 2: Rationale slows the decision
Demanding full rationale naturally raises latency and output tokens. Spreading it to read-only and reversible-write makes the whole operation heavy. The impact-based line above is less about quality and more about concentrating that weight only where it's needed.
Pitfall 3: Nobody re-reads the ledger
Keeping records is meaningless if no one reads them back. Once a week I pull only the records where reversibility is hard_to_reverse or above andconfidence is below 0.7, and read those. I can't read everything, but "low-confidence, hard-to-reverse decisions" come to just a handful — and that's where the problems almost always hid.
# Pull only low-confidence, hard-to-reverse decisions, weeklyjq -c 'select(.record.reversibility != "reversible" and .record.confidence < 0.7)' decision_ledger.jsonl
What the Sudden Fable 5 Suspension Taught Me
While I was working on this design, the just-released Claude Fable 5 and Mythos 5 were suspended following a U.S. government export-control directive. Not on a schedule — one day, suddenly. It drove home again that dependence on the newest model is a risk not only of capability but of availability.
What tied this to the rationale ledger was realizing that a fallback is itself a decision that needs a rationale. When a model becomes unavailable and you switch to another, don't just switch — record "which capability (say, long context) you gave up" and "which actions you should therefore hold." Then, after recovery, what to restore is clear. Availability decisions are exactly the kind worth keeping in an explainable form. The design of stopping or switching the agent itself becomes more robust when combined with running Human-in-the-loop in production.
Your Next Step
Start by listing the actions in the pipeline you currently run autonomously and counting how many fall into hard_to_reverse or above. In most cases it's fewer than you'd think. Wire the record_decision tool above into just those few, and you've taken a step toward "automated decisions you can explain later" without weighing the whole operation down.
If you're also running autonomous operations on your own, I hope this helps.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.