Hightower's AI Harness Engineering

Hightower's AI Harness Engineering

Anthropic’s Claude Certified Architect Exam (CCA-F) -- Claude API Patterns: A Schema Guarantees Shape, Not Truth: Structured Output and Memory on the Raw Claude API - IV

Part 4: CCA-F Claude API Patterns -- The model returns flawless JSON with a fabricated field, and the exact dollar amount it authorized early scrolled off into the middle and disappeared.

Rick Hightower's avatar
Rick Hightower
Jul 14, 2026
∙ Paid

The model hands you flawless JSON with a field it invented, while the exact value you authorized scrolls out of context and the agent acts on a number it can no longer see. On the raw Messages API you fix both by hand, with every moving part finally in view.

In this article: You will learn why Claude structured output is really a forced tool call, why a valid schema still lets the model lie, and how a validation-retry loop separates shape from truth. Then we turn to memory: the API is stateless, the message array is the only memory the model has, and a rolling window with pinned facts is what keeps a precise value from being summarized into a vague one. By the end you can build honest extraction and durable conversational state with code you can read end to end.


CCA-F Part 4: The model returns flawless JSON with a fabricated field, and the exact dollar amount it authorized early scrolled off into the middle and disappeared. On the raw Messages API, you fix both by hand, and the moving parts are finally visible.

Here is a failure that survives every shape check you can throw at it. You ask the model to extract a refund decision, and it hands back JSON that parses cleanly, validates against your schema, and is still wrong. The refund_reason field is populated with a plausible sentence about a defective product, except the ticket never mentioned a reason at all. The model invented one because your schema demanded a string and a string is what it produced.

Here is a second failure, quieter and more expensive. Early in a long support case, the agent authorized a refund of exactly $247.83 due by Friday. Forty turns later it is asked to process the refund, and the figure is gone. It scrolled into the middle of a growing message array and the model can no longer see it, or a summarization step compressed it to “a refund soon.” The precise number, the one thing that mattered, is the first casualty.

These are the two failures the CCA-F study guide weights most heavily, Module 4 and Module 5. The Agent SDK hides them behind structured-output helpers and a managed session. On the raw Claude Messages API there is no hiding: the schema you design, the validation you run, and the array you assemble on every call are lines you write. This article builds both. The correct posture throughout is simple to state and easy to forget: a JSON schema guarantees shape, not truth, and the API is stateless, so the message array is your memory.

Everything here is verified against anthropic 0.109.x.

Structured output is a forced tool call

The canonical mechanism for Claude structured output is not a special response mode. It is tool_use with a JSON schema, made deterministic by forcing the tool with tool_choice. You define a tool whose input_schema is the shape you want, force it with {"type": "tool", "name": ...}, and the model’s tool_use block arrives with your structured data in .input, schema-valid by construction.

The schema discipline from Module 4 lives entirely in that schema. The single most important decision is how you model absence. Make refund_reason nullable so absence is representable, give request_type an "unclear" escape member, and mark only the always-present fields required.

extract_tool = {
    “name”: “record_refund_decision”,
    “description”: “Record the structured outcome of a refund case.”,
    “input_schema”: {
        “type”: “object”,
        “properties”: {
            “order_id”: {“type”: “string”},
            “request_type”: {“type”: “string”, “enum”: [“refund”, “exchange”, “unclear”]},  # ①
            # Nullable: the model returns null when the ticket states no reason,
            # instead of inventing one to satisfy the shape.
            “refund_reason”: {“type”: [“string”, “null”]},  # ②
            “amount_usd”: {“type”: “number”},
        },
        # refund_reason is intentionally NOT required, so absence is legal.
        “required”: [“order_id”, “request_type”, “amount_usd”],  # ③
    },
}

① The "unclear" enum member gives request_type a legal escape hatch, so the model can decline to classify rather than guess between refund and exchange. ② The nullable type lets refund_reason be null when the ticket states no reason, which makes absence representable instead of forcing a fabricated value. ③ The required list names only the always-present fields, so omitting refund_reason here is what makes a missing reason legal rather than a schema violation.


If you are a paid subscriber, thank you. Your support makes this work possible.

If you are a free subscriber and find these articles useful, please consider upgrading. A paid subscription is $80 per year or $8 per month.

Free subscribers typically receive access to the full versions of paid articles after one to two months.

Share Hightower's AI Harness Engineering

User's avatar

Continue reading this post for free, courtesy of Rick Hightower.

Or purchase a paid subscription.
© 2026 Rick Hightower · Privacy ∙ Terms ∙ Collection notice
Start your SubstackGet the app
Substack is the home for great culture