Last Step/Prompt Engineering and Structured Output20%
Domain 46 task statements20% of the exam

Prompt Engineering and Structured Output

How to make Claude produce consistent, schema-valid output on the first try through explicit criteria, few-shot patterns, constrained decoding, validation loops, and batch discipline.

Prompt engineering for production is not about clever phrasing but about removing ambiguity from every input that shapes Claude's next token. The system prompt, the few-shot examples, the JSON schema, and the validation loop each remove a different class of variance: vague criteria produce inconsistent classification, missing examples produce format drift, a missing schema produces malformed JSON, and a missing validator lets wrong values pass as correct structure. When all four are in place the model still makes mistakes, but those mistakes become detectable and correctable rather than silent and random.

This domain's six tasks map directly to the six places a production pipeline drifts. Task 4.1 makes the system prompt explicit by replacing subjective instructions with categorical criteria and the RACCE diagnostic. Task 4.2 adds few-shot examples with reasoning so the model matches a demonstrated pattern instead of interpreting an adjective. Task 4.3 moves from probabilistic JSON in text to constrained decoding through tool schemas and structured outputs. Task 4.4 closes the loop with semantic validation and retry with error feedback that distinguishes fixable from unfixable failures. Task 4.5 chooses batch versus synchronous for cost without breaking latency guarantees. Task 4.6 replaces single-pass self review with multi-pass independent review plus calibrated confidence routing.

Two principles cut across every task. First, structure removes variance more reliably than additional prose. An explicit category list beats a paragraph defining conservatism, a concrete input-output pair beats a page defining warmth, and a JSON schema with constrained decoding beats an instruction to respond only in JSON. Whenever the exam offers more instructions versus an example or a schema, the example or schema is the correct answer. Second, schema guarantees structure but never guarantees correctness. Line items can sum to the wrong total inside perfectly valid JSON, and a field can be populated with a plausible invention that passes type checks. Production therefore needs both structural enforcement and semantic validation, with retry that names the specific error rather than repeating the same prompt.

On this page
  1. 4.1 System Prompts Replace vague guidance with explicit categorical criteria, RACCE structure, and primacy-aware placement so every response is judged by the same testable boundary.
  2. 4.2 Few-Shot Prompting Show the pattern with 2 to 4 reasoning-annotated examples so Claude matches a demonstrated judgement instead of interpreting an adjective.
  3. 4.3 Structured Output Guarantee schema-valid JSON through tool schemas and constrained decoding, then design nullable enums and redundancy fields that handle real document variation.
  4. 4.4 Validation and Retry Loops Add semantic validation and retry with document plus failed output plus specific error so fixable mistakes self-correct and unfixable ones route to human review.
  5. 4.5 Batch Processing Use the Message Batches API for latency-tolerant work at 50 percent savings and keep blocking workflows synchronous, correlating every batch result through custom_id.
  6. 4.6 Multi-Pass Review Split large audits into per-file passes plus an integration pass from an independent instance, with calibrated confidence routing to focus human review where it matters.
The essentials
Everything below explains why each of these is true.
  1. System prompt persists across every turn and carries persona, constraints, output conventions, and quality criteria. The Messages API system parameter accepts a plain string or an array of blocks each with type and cache_control for prompt caching with ephemeral type.
  2. RACCE is the diagnostic for system prompts: Role, Audience, Criteria, Constraints, Examples. When a prompt underperforms the fault maps to a missing or underspecified RACCE component.
  3. Explicit categorical criteria define what to flag and what to skip with concrete code patterns per category. Confidence thresholds are poorly calibrated and are a routing mechanism, not a substitute for criteria.
  4. Primacy and recency both receive strong attention. Put critical behavioural constraints at the very top of the system prompt, supporting detail in the middle, and examples last where recency helps.
  5. Negative constraints such as do not include a preamble or do not wrap output in markdown fences directly suppress a pattern. Lowering temperature reduces randomness but does not guarantee pattern absence.
  6. Few-shot examples with reasoning teach the decision principle and generalise to novel patterns. Use 2 to 4 targeted examples, mix classes in classification, keep formatting identical down to punctuation, and place examples close to the query.
  7. Structured outputs hierarchy: tool_use with JSON schemas or output_config.format with json_schema eliminates JSON syntax errors via constrained decoding. Prompt-based JSON in text is probabilistic and will produce malformed output.
  8. tool_choice modes: auto lets the model return text instead of a tool, any forces a tool call but lets the model choose which, forced tool selection with type tool and name guarantees exactly that tool. Strict mode on a tool requires additionalProperties false and applies constrained decoding to arguments.
  9. Optional and nullable fields prevent fabrication. Required fields pressure the model to invent values when source data is absent. Add unclear and other to enums, with a freeform detail string for other, to handle ambiguity and long tail categories.
  10. Retry with error feedback sends the original document, the failed extraction, and the specific validation error. It fixes format mismatches, structural errors, misplaced values, and arithmetic errors, but cannot fix genuinely absent information which routes to human review.
  11. Batch API gives 50 percent cost savings with results available when all requests finish or at 24 hours whichever comes first, most batches finishing inside an hour but with no per-item latency promise, and custom_id correlation. Use batch for latency-tolerant work and synchronous for blocking workflows. Work backwards from the deadline to schedule submissions.
  12. Independent review instances beat self review in the same session because the reviewing instance does not carry the generation reasoning. Large audits need per-file local passes plus a cross-file integration pass, with calibrated confidence thresholds for routing via labelled validation sets.
Task 4.118 min

System Prompts

Replace vague guidance with explicit categorical criteria, RACCE structure, and primacy-aware placement so every response is judged by the same testable boundary.

What you need to know

The system prompt is the single most influential input you control when deploying Claude because it persists unchanged across every turn of the conversation. It is the right place for anything that should shape every response rather than just the current one: persona, behavioural constraints, output conventions, and the criteria by which quality is judged. Anything time bound or query specific belongs in a user message, while any standard that must apply without an invocation step belongs in the system prompt.

The most common production failure is a vague instruction that sounds engineering grade but gives the model no actionable decision boundary. Phrases such as be conservative, only report high-confidence findings, use your best judgement, be ethical, and be careful cannot be verified from the output and mean different things in different contexts. The exam uses exactly this language as distractors because it feels responsible while being untestable. The correct replacement is explicit categorical criteria that name what to flag and what to skip with concrete patterns per category, for example flag comments only when claimed behaviour contradicts actual code behaviour and otherwise skip minor style preferences and local patterns.

RACCE is the framework that makes a system prompt diagnosable when output quality drops. Role activates domain knowledge and perspective, without it Claude defaults to generic helpful assistant behaviour. Audience calibrates vocabulary, tone, and assumed knowledge, without it tone mismatches the consumer. Criteria define how quality is evaluated and which tradeoffs are favoured, without them Claude decides whether correctness or brevity matters. Constraints are hard boundaries not to cross, without them Claude improvises scope. Examples demonstrate the target format and quality through demonstration rather than description. When a system prompt underperforms the fault almost always maps to one missing RACCE component.

System prompts exhibit a primacy effect where Claude attends more strongly to content at the very beginning and end of context than to content buried in the middle. A critical behavioural constraint placed in the middle of an 8000 token system prompt receives reliably weaker attention than the same constraint placed at the top. The fix is structural: put the most critical behavioural constraints first, supporting detail in the middle, and examples last where they also benefit from recency. A practical budget is to keep the system prompt under 10 percent of total token budget, otherwise it competes with the task for attention.

Confidence-based filtering is not a substitute for explicit criteria. Instructions such as only report high-confidence findings fail because LLM self-reported confidence is poorly calibrated, the model is often sure about wrong findings and hesitant about right ones. Confidence scoring earns its place as a routing mechanism for uncertain outputs to human review, which is covered in the multi-pass review task, but it never replaces explicit criteria that define what counts as a valid finding in the first place. The hierarchy is explicit criteria first, confidence routing second, and skipping the first step cannot be compensated by tuning the second.

RACCE as a diagnostic and the explicit criteria replacement

Vague guidance fails the verifiability test. A constraint such as never provide a specific diagnosis or always escalate when the user describes an emergency can be checked directly in the output, while be ethical cannot. Explicit categorical criteria pass the same test: report bugs and security vulnerabilities and flag comment mismatches only when claimed behaviour contradicts code, skip style preferences and local patterns, each paired with a concrete code snippet for the critical severity and a snippet for the minor severity so severity is recognised by pattern not by prose interpretation.

RACCE makes the gap explicit during debugging. Walk each component when a prompt underperforms and ask whether role, audience, criteria, constraints, or examples is underspecified. Underperforming documentation mismatch detection almost always maps to criteria, missing the definition of what counts as a mismatch, not to a model capability limit. Adding high confidence language after the fault is still criteria missing, so it still fails.

System parameter shape and prompt caching

The Messages API system parameter accepts either a plain string or an array of structured blocks where each block carries type and cache_control. The array form is what enables prompt caching: wrapping the large stable system prompt with cache_control type ephemeral lets the platform cache the block at full price once per cache lifetime then charge a fraction on subsequent calls that share the same prefix.

Three structural rules matter. The system prompt persists unchanged across every turn, so time bound or query specific material does not belong there. System and user messages carry different authority, with system framing the session and user capable of requesting a temporary per-turn shift. The assistant role in the messages array can carry prewritten content, which is the standard mechanism for few-shot examples placed close to the query for recency influence.

Primacy, negative constraints, and size discipline

When a prompt grows by appending detail, critical constraints drift toward the middle where primacy is weakest. The presentation that a long plausible prompt asks which instruction is reliably followed keys to the instruction at the top, not the middle, and the fix is to reorder rather than to add emphasis words. Supporting detail and long examples belong in the middle and at the end where recency still helps.

Negative constraints are more reliable than sampling adjustments for suppressing unwanted behaviour. Lowering temperature reduces randomness but does not guarantee the absence of a preamble, while a specific negative constraint such as do not include text outside the JSON object or do not wrap output in markdown fences directly suppresses the pattern. Keep negative constraints specific and verifiable, the same rule as positive constraints.

Caching reduces per-token price but not attention cost. A 10000 token system prompt still consumes 10000 tokens of attention budget per call, the cache only discounts the price, so size discipline still applies even when caching is enabled.

Authoritative mechanism reference

The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.

Mechanism reference: The system parameter: what it is and what it is not

The Messages API exposes system as a top-level parameter alongside model, max_tokens, and messages. It is not a message role. The valid role values inside messages are user and assistant and those messages must strictly alternate, while system sits outside that array and persists across every turn of the conversation. This structural placement matters. Content placed in system is present before the first user message and is reapplied unchanged on every subsequent turn, so it frames how every user message is interpreted. Content placed inside a user message competes for attention as one instruction among many and is more vulnerable to being overridden by later user turns.

system can be supplied as a single string or as an array of content blocks. The array form is the form that enables prompt caching and structured organisation of complex system prompts. When system is an array, each entry has type: "text", a text string, and optionally cache_control: { type: "ephemeral" } on stable sections. A typical construction keeps the role, task framing, and guidelines in a cached block and leaves per-request context outside it. Systematic prompt caching of the system prompt plus tool definitions typically represents a large share of token usage, and caching that block can reduce cost at scale, but the mechanism itself is API-level persistence and caching, not model-level authority.

The distinction between steering and enforcement is central. The system prompt is higher-authority guidance that the model is trained to weigh more heavily than an individual user turn, but it is not a hard enforcement boundary. Documentation and lessons explicitly state that a determined or adversarial user message can still degrade adherence to a system instruction, especially for nuanced or unusual requests. The lesson draw is that system is the correct and primary place for standing behavioural constraints, and that repeating critical constraints in every user message is not the recommended repair. Better prompt design and, for hard requirements, application-layer validation are the documented repairs. Where the reference page emphasises putting role and constraints in the system prompt, it is describing this steering-without-guarantee property accurately.

Ownership in this mechanism sits with the developer at authoring time and with the API at delivery time. The SDK constructs the request, the API delivers system with higher-authority framing, and application code owns enforcement where guarantees are needed, such as schema validation after the response or a structured outputs configuration that constrains the sampling grammar. The model owns interpretation, but interpretation is bounded by what the system prompt actually defines as a decidable predicate.

Mechanism reference: RACCE decomposition: where role, audience, criteria, constraints, and examples belong

System prompt design lessons decompose an effective system prompt into five components: Role, Audience, Criteria, Constraints, and Examples, abbreviated RACCE. Each component maps to a failure mode when omitted.

Role activates domain knowledge and perspective. A concrete role such as You are a senior security engineer specializing in web application security. Review code for security vulnerabilities with attention to OWASP classifications. reliably surfaces relevant decision patterns, while You are a helpful assistant does not change behaviour in a measurable way. Lesson guidance on role specificity describes an additive narrowing effect: each qualifier such as seniority, specialization, and context reduces the chance of generic output. Role also interacts with placement. A role established in system is persistent across turns, while a role introduced in a user message provides temporary or supplementary framing for a single turn, such as asking the architect to review from a QA perspective for one exchange. When multiple roles appear, they compete, and specificity, recency, and system-level persistence determine which influence dominates. Conflicting roles produce averaging rather than selection.

Audience calibrates vocabulary, technical depth, and tone. The reference task for system prompts treats audience as a first-class decision rather than a stylistic afterthought. Without an audience, the model defaults to a generic register that may be too technical or too shallow. An audience definition such as explaining to a junior developer who knows Python but not distributed systems removes ambiguity about how much background can be assumed. Audience matters for code review prompts because the consumer is often a developer reading bot comments under time pressure, which strengthens the need for crisp severity labels and concise remediation rather than expansive explanation. The audience component is therefore not decorative. It determines how findings are framed for the developer who must act on them.

Criteria define how quality is evaluated and which tradeoffs to favour when objectives compete. Prioritized criteria outperform unordered lists of desirable properties. A line such as Prioritize correctness over brevity. If you must choose between a complete answer and an accurate one, choose accuracy and note what was omitted. gives the model a clear tiebreaker, while Be helpful, accurate, and concise forces the model to resolve the tradeoff silently and inconsistently. For code review, criteria are the REPORT and SKIP sets, the conjunctive conditions that must all hold before a finding is emitted, and the per-severity examples that anchor classification. Without criteria, the model makes its own quality assumptions, and those assumptions are the precise source of false positives that the reference page targets.

Constraints are hard boundaries the model must not cross, regardless of what the user asks. Good constraints are specific, testable, and few enough to be followable. Lesson material notes research on instruction-following degrading significantly beyond roughly ten distinct behavioural rules, and recommends keeping constraints to five to ten prioritized items. The test for a constraint is whether compliance can be verified from output alone. Never provide a specific diagnosis, you can describe symptoms and suggest consulting a doctor passes the test, while Be ethical does not. For code review pipelines, constraints include negative constraints such as Output ONLY the JSON object. Do not generate any text before or after it which are more reliable than sampling adjustments such as lowering temperature for suppressing preambles. Negative constraints matter most where format drift would break parsing.

Examples embedded in the system prompt demonstrate the exact interaction pattern, format, and edge-case handling that the application requires. Placement at the end of the system prompt maximises influence because examples closest to the user message carry the most weight. Two to four input/output pairs are the documented range for the examples component, and the lesson example inventory shows patterns such as clear request, escalation, and out-of-scope handling to establish both format and judgement boundaries. In the context of task 4.1, examples serve a calibration function for severity and a boundary function for category membership. Concrete code examples per severity tier create stable, checkable anchors that produce consistent classification across invocations, while contrastive pairs teach where the boundary sits rather than merely reinforcing a surface feature.

The recommended ordering of these components matches how the model processes the prompt: role first to establish context for everything that follows, then audience, then criteria, then constraints, then examples last and closest to the user content. This ordering also supports caching, because role, audience, criteria, and constraints are stable across requests while examples and the user task vary more. Structured with this ordering and tagged with delimiters, the system prompt becomes auditable: a reviewer can see which component handles which instruction and can change one component at a time during iteration, which is the documented safe update practice of shadowing new output against old before broad rollout.

Mechanism reference: Why instructions must be verifiable predicates, not adverbs

An instruction is only useful if the model can decide whether a candidate finding satisfies it without appeal to an ungrounded judgement. Lesson material formalises this as the testability requirement: a constraint passes if two reasonable people would agree on whether output followed it, and fails if that judgement remains debatable. Vague instructions fail the test because they are adverbs without operands. Be conservative does not name which construct to flag or which to skip. Only report high-confidence findings names a threshold but attaches it to confidence, a self-reported score that tracks reported certainty rather than membership in a defined category. Use your best judgement and prioritize precision over recall behave identically: they gesture at a tradeoff without defining the categories whose precision should improve. Each of these phrases can be appended to any prompt without changing the set of observable properties the model checks, so the classification function does not gain a new predicate and the false positive distribution does not move.

Verifiable predicates have a different logical form. They compare observables: flag a comment only when its claimed behaviour contradicts the code's actual behaviour, report only when a query uses string concatenation, the concatenated value is unsanitized, and the value originates from an untrusted entry point, report only when an amount exceeds the threshold and the merchant is first-seen and the shipping country mismatches the billing country. Each clause names a property that can be checked in the artifact alone, so a reviewer can determine which clause failed for a dismissed finding. Forensics records that replacing a vague line with a conjunctive predicate dominates precision more than any surrounding prose, because membership becomes decidable and auditable. The predicate also explains why repeating or intensifying the vague line never repairs precision: extremely conservative and be careful and avoid false positives map to the same ambiguous prior under harsher pressure, trimming volume without changing which families misfire. The dial that moves precision is not pressure but the presence of a new checkable clause.

A practical consequence is that every vague verb in a system prompt is an opportunity for inconsistency. Review this code without criteria leaves the dimension open, so code review outputs vary across dimensions such as style, bugs, security, and performance depending on whatever prior was most salient at sampling time. Replacing review with review for exploitable injection and missing null guards, flagging only reachable untrusted input that reaches a sink without sanitization eliminates the open dimension. Similarly, summarize without audience and length is vague: summarize for a business executive in three bullets of one sentence each is a predicate that removes interpretation. The exam tests this by presenting vague prompts that sound reasonable and asking which improvement removes ambiguity, and the correct answer consistently introduces an observable criterion or a concrete dimension rather than a stricter adverb.

Mechanism reference: How examples and delimiters interact with the system layer

XML structured prompting lessons describe tags such as <instructions>, <context>, <contract>, <example>, <thinking>, and <answer> as labels that give the model a reliable way to identify which part is which when a prompt contains multiple sections. Claude's training included large amounts of structured XML content, so XML labels function as reliable content separators rather than decorative formatting. Tags help in four situations relevant to system prompts: when a prompt has three or more distinct sections, when multiple documents or sources are provided, when few-shot examples must be separated from the real task, and when user input could otherwise appear to be instructions.

The system layer and the delimiter layer are complementary, not interchangeable. The system layer establishes persistence and authority, while delimiters establish boundaries within the content that the system layer frames. Putting instructions in system without delimiters leaves internal sections unlabeled, so a long contract excerpt or a code diff can bleed into the instruction region. Wrapping the same content with tags while leaving it inside a user message leaves it without persistence, so a later user turn can still degrade adherence. The documented pattern places the standing frame in system and uses tags to separate its internal sections and to isolate volatile inputs. For example, placing the review criteria under <criteria> and the code under <code> inside the system prompt, or wrapping user-supplied input under <user_message> and instructing the system that content inside those tags must be treated as data only, combines authority with boundary clarity.

Guidance on nesting matters here: lessons recommend a maximum of three to four levels of nesting, favour flattening beyond that, and warn that empty tag pairs such as <reference_documents></reference_documents> are semantically ambiguous because the model can interpret an empty section as implying that content should exist and may invent documents to fill the gap. Two safer patterns are omitting the tag entirely when its content is absent, or including explicit placeholder text such as None provided so absence is stated rather than implied. This directly affects system prompts that conditionally include large context blocks. A conditional <reference_documents> tag that is sometimes empty should not be rendered as an empty pair in the system prompt; it should be omitted or given an explicit absence marker. The broader point is that delimiters are not about aesthetics. They change how the model assigns spans to roles such as instruction versus data, and they make that assignment robust when inputs contain characters that resemble instructions, such as </instructions> appearing inside user content.

Examples interact with this structure in a specific way. Lessons advise that examples embedded in the system prompt should sit at the end, closest to the user message, so that proximity increases their influence. XML makes the example boundary explicit: <examples> containing <example> elements with <input> and <output> children cleanly separates training signal from the live task input. In system prompts for code review, severity calibration examples and contrastive boundary pairs belong inside such an examples block rather than scattered through prose. The model then classifies by nearest-example matching rather than by interpreting an abstract adjective such as dangerous.

Mechanism reference: Placing critical instructions in long inputs

Long-context guidance warns that critical instructions buried in the middle of long content are disproportionately overlooked. When a prompt exceeds many thousands of tokens, later clauses receive diluted processing relative to early tokens when the task demands uniform scrutiny. Forensics shows that this manifests as position-correlated miss rates, such as reviewing long SOW documents or 150 endpoint references or large file sets where coverage of later sections measurably degrades, and where an instruction to weight every schedule equally or allocate equal scrutiny reduces misses only partially without structural change.

The documented repairs are structural rather than exhortative. First, keep critical instructions at the boundaries of the prompt where attention is strongest rather than in the middle of a long data block. Second, shard inputs that demand uniform coverage into per-unit independent calls, such as per-clause calls for a long contract where each call receives one clause plus the relevant playbook rule, followed by a separate cross-clause integration pass to catch interactions that span units. Third, use delimiters so that instructions and data occupy distinct tagged sections rather than running together as an indistinguishable stream. Adding more adverbs inside the same long prompt does not change the underlying attention distribution. The exam tests this by contrasting add an instruction to weight every section equally while keeping the entire document in one prompt with split the document into per-clause calls plus an integration pass, and only the latter is treated as structural.

For system prompts specifically, this means the system prompt itself should remain compact where possible, rely on caching for large stable guidelines, and avoid stuffing all per-request data into the system content. Large reference documents belong in the user message under labeled tags or in a cached block with explicit structure, not interleaved with the core instructions where they bury them. Where an instruction must survive in a long input, the combination of early placement, shallow XML structure, and per-unit sharding is the documented path, not intensifying the same instruction.

Mechanism reference: Guidance in a prompt versus enforcement in code or schema

Two distinct reliability strategies appear in this domain. Prompt-level guidance steers the model toward the right judgement by defining what to check, what to report, and what to skip. Enforcement at the API or application layer guarantees a structural property regardless of whether the model followed the guidance. The reference page covers the first strategy thoroughly but the full domain distinguishes them explicitly, and exam scenarios require knowing which layer owns which guarantee.

Prompt guidance includes the categorical criteria, the severity anchors, the negative constraints, and the placement of role and audience. It is the right tool for judgements that require interpretation of code and intent, such as deciding whether a comment contradicts actual behaviour or whether a pattern described on a page matches a listed vulnerability shape. Its failure mode is non-determinism and sensitivity to phrasing. Documented mitigation is careful design, examples, and verification, not stronger sampling settings.

Enforcement includes schema-constrained decoding and application-layer validation. Documentation describes output_config.format with {"type": "json_schema", "schema": ...} as a mechanism that enforces schema compliance by grammar-constrained sampling, and strict: true on a tool definition as the separate feature that enforces compliance on tool names and inputs. The older top-level output_format field and the associated beta header are accepted only for a transition period, while output_config.format is the current form. Structured outputs guarantee valid JSON matching a schema but do not guarantee semantic correctness: a field such as quantity: "30 minutes" can be syntactically valid while semantically wrong, and checks for semantic correctness still require validation logic outside the model. Two other enforcement realities matter: a refusal returns stop_reason: "refusal" with a 200 status and billed tokens, with refusal text taking precedence over the schema, and hitting the token limit truncates before the schema can be satisfied, so schema compliance is not absolute.

The split between guidance and enforcement determines how pipeline code is written. For a code review pipeline that must produce machine-readable findings, guidance defines what counts as a finding and what severity to assign, while enforcement validates that the response parses as valid JSON with the expected fields such as file, line, detected_pattern, and severity before findings are surfaced. Lesson material warns that asking for XML output without validation is fragile because the model may produce malformed XML such as missing closing tags or extra text outside tags, and recommends regex-based fallback extraction when XML is used as output. The same logic applies to severity labels: prompting Classify each finding as CRITICAL, HIGH, MEDIUM, or LOW is guidance that benefits from code examples per tier, but the downstream validator should still reject an unknown label or a missing field rather than assume the model always obeyed. Reserve prompts for steering judgement and schemas for guaranteeing shape, and add application checks for semantic plausibility where schema alone cannot catch the error.

Mechanism reference: Severity calibration and why prose fails

Prose severity definitions such as Critical: Issues that could cause system failures or data loss or Minor: Issues that affect code readability but not functionality share a structural defect: they map an abstract label to context-dependent estimates such as could cause or dangerous or slightly suboptimal without naming an observable property of the artifact. The model must infer what counts as dangerous from surrounding tokens, so identical null-pointer shapes in two files receive different labels without any artifact change. Widening the prose into longer paragraphs adds more interpretable words rather than new tests, so variance persists. Forensics records that even seemingly precise variants such as poses an immediate danger still produced substantial misclassification, and that a review of many critical flags found a large share belonging in a lower tier, while identical patterns were labeled critical in one file and low in another.

Concrete code examples per severity tier repair this by replacing interpretation with nearest-example matching. A labeled fragment such as a query that concatenates unsanitized external input directly into a sink is observable in the code before any judgement about danger. A naming inconsistency such as userName versus user_name within the same module is observable as a textual mismatch without functional risk. Once each tier is bound to such examples, classification asks which example the candidate resembles most closely, which is stable across runs. Evidence favours one worked example per level as the minimum viable anchor, with two to three for the most contested boundary or five to seven spanning the full range drawn from consensus or borderline cases where disagreement was historically highest. Providing many examples for one tier while omitting anchors for neighbouring tiers leaves novel findings without comparative context, so distribution across tiers matters as much as existence.

Mechanism reference: Confidence: why self-reported scores are not calibrated probabilities and how routing uses them correctly

Self-reported confidence is a score the model emits alongside a finding, such as confidence: 0.94 or confidence: 8 on a 1 to 10 scale, expressing how sure the model reports itself to be. That report comes from the same reasoning that produced the finding. It is not an independent measurement of correctness and it is not a calibrated probability that tracks empirical accuracy. Lesson and forensics material is explicit on this point: the model is often sure about wrong findings and hesitant about right ones, so raising the bar from 0.85 to 0.90 or 0.99 still lets confident false positives through while hiding correct but cautiously scored findings. The reference page phrases this compactly as LLM self-reported confidence is poorly calibrated and states the hierarchy that explicit criteria come first and confidence-based routing comes second. The documented path to reliability is criteria plus verification, with routing on top.

Three confusions make confidence attractive as a filter and explain why each fails. First, a quantitative form such as only report above 0.85 appears more precise than be conservative but preserves the same defect: the threshold is applied to an uncalibrated signal, so the gate filters by reported certainty, not correctness. Second, lowering temperature to zero is sometimes proposed as an alternative to a confidence gate, but determinism without a decidable criterion reproduces the same vague judgement deterministically rather than correcting it. Third, confidence can be scoped per category rather than globally, such as separate thresholds for input handling versus authentication versus data exposure, but per-category thresholds tuned to historical dismissal rates still tune around the symptom while leaving the misclassification logic intact. Each bar gates an underspecified category, so surface-feature errors such as parameterized queries flagged as injection or env-var secret loads flagged as hardcoded credentials continue to clear the gate, because the reported score for those findings was already high for the wrong reason.

Confidence becomes useful after criteria exist and after it has been calibrated against labeled truth. Calibration is a post hoc mapping built from a validation set per field and per segment, such as invoice_total versus indemnification clause or comparison table versus appendix, where the set is labeled by human adjudication or a known-correct reference and is held out from prompt iteration. The calibration step records what a reported 0.95 actually meant empirically for each segment, for example 0.94 measured on party name at reported 0.95 versus 0.71 measured on indemnification at the same reported score. Once fitted, the calibrated score can drive thresholds that reflect measured precision: a threshold where the fitted curve crosses the target precision for auto-release, with a lower band routed to review and contradictory material routed regardless of score. Without this per-segment fit there is no principled way to choose a threshold, because the same reported number means different things in different segments.

Two additional practices make a confidence router trustworthy. First, stratified random sampling of a fixed fraction of high-confidence outputs, weekly and across document type, field type, or confidence band, surfaces confident-but-wrong errors that no filter would show and yields a pipeline-wide error estimate that can be tracked over time. Lowering the threshold or adding heuristic rules for known error sources such as comparison tables covers only previously seen shapes and cannot estimate the overall high-confidence error rate, while re-extracting and comparing variance fails for structural errors that have zero variance and return the same wrong value on every run. Stratified sampling is the mechanism that measures whether explicit criteria actually reduce the high-confidence error rate from cycle to cycle.

Second, granularity matters. Field-level or category-level scores enable targeted routing while a single document-level score masks the weak segment by averaging. A document whose unit field is extracted at 0.94 correctness while quantity is at 0.31 can look healthy under a document score, so reviewers miss the concentrated weakness. Lessons on confidence scoring describe this as achieving efficiency by routing the 20 percent that contains 60 percent of errors once granularity and calibration are in place. The aggregate score is adequate only when disposition is whole-document and per-field accuracy is genuinely uniform.

The production distinction is therefore between filtering and routing. Filtering discards low-confidence material silently, which loses information needed for safety and hides the right population in the wrong bucket. Routing preserves every finding but sends low-confidence or contradictory or not-yet-validated material to a human or an independent fresh-context verifier, while high-confidence material in validated segments may proceed. Nothing is suppressed for scoring low; low simply means more scrutiny. That is why the reference hierarchy exists and why the exam penalises any answer that proposes confidence as the primary gate before explicit criteria define what counts.

Mechanism reference: Trust, false positive spillover, and the role of criteria

A pipeline rarely emits one undifferentiated stream. It tags findings by category such as security, correctness, performance, style, naming, or documentation, or by family such as injection versus hardcoded-secret, or by rule such as color contrast versus alt text versus focus order. Per-category precision often diverges, with accurate categories operating at low false positive rates while one or two noisy tags operate at very high rates. Developers consume the stream as a single trust object. When one tag repeatedly delivers dismissed items, consumers develop a global heuristic such as ignore the bot or skim every high ticket rather than a per-tag reliability judgement. The noisy tag then bleeds credibility from the accurate tags, and the accurate findings lose effective recall even though their precision never changed.

Spillover explains why aggregate statistics mislead. A pipeline whose overall rate looks moderate can still suffer cross-category distrust when most false positives concentrate in one tag. Lowering the total count by trimming about a fifth through be conservative does not restore engagement when the remaining mix still contains frequent bad tags, because the prior that the bot is noisy was built from frequency of bad experiences rather than from the exact ratio. Showing confidence per finding or carrying a uniform strictness reduction across all categories fails for the same reason: the consumer applies the global ignore before evaluating any score, and the accurate categories are suppressed alongside the noisy ones. The harm model here is category-weighted, not aggregate, so per-category measurement is required to reason about trust.

Criteria are the lever that breaks the spillover, because they let the noisy category be identified and repaired without touching the accurate categories. Report-versus-skip sets that enumerate both sides, conjunctive conditions that require every clause such as reachable AND impactful before a finding is emitted, and per-severity code examples that anchor classification each reduce the contribution of a specific tag without changing the accurate stream. Trust restoration is therefore not an aggregate problem. It is a per-category precision problem solved by explicit definitions that make each noisy tag decidable.

Mechanism reference: The detected_pattern diagnostic and per-category analysis

When trust has already collapsed or when a team is deciding which repair to prioritize, coarse signals do not help a diagnosis. A finding stream that records only file, line, and description, or only a broad category such as security flagged 500 times, hides which trigger shape drove dismissals. Grouping by a self-rated confidence or a circular is_false_positive boolean asks the same classifier to judge itself. The forensics record identifies detected_pattern as the finest-grained diagnostic signal: a field that records the construct that triggered the flag, such as string concatenation in database query, user input passed to eval(), parameterized query as the safe counterpart, or list comprehension versus generator expression.

With detected_pattern present, dismissals can be aggregated per pattern, such as 73 percent dismissed on concatenation versus 8 percent on eval, and the dominant pattern becomes the target for explicit criteria and for examples. Without it, a team cannot distinguish two situations that demand different repairs: one where a single pattern accounts for most dismissals and needs a focused predicate, and one where dismissals are diffuse and point to a broader criteria gap. The field is diagnostic rather than curative: adding detected_pattern while leaving a noisy tag in the delivered stream does not restore trust, but adding it alongside a quarantine gives the team the signal needed to rewrite the right clause and to measure whether that rewrite moved the per-pattern dismissal rate. The rule for diagnostics is therefore to pair measurement with isolation: log the finest-grained available signal, act on delivery immediately, and verify the repair off-line.

Mechanism reference: Why contrastive few-shot pairs and single-polarity examples behave differently

Two ways of adding examples inside a system prompt produce opposite outcomes on the exam, and the distinction maps directly to whether the task is format imitation or boundary learning.

Format imitation is served by positive-only examples. A prompt that demonstrates the interaction pattern User: I need to return my order to Assistant: Could you share your order number teaches the structure to imitate. For code review, this would be examples that show how a finding should be formatted as JSON, which severity label to attach, and which fields to populate. Where the problem is format variance rather than judgement variance, a few positive demonstrations are the correct fix, consistent with the lesson note that examples calibrate tone and format when placed closest to the user input.

Boundary learning requires contrastive pairs that hold the surface feature constant across two sides that differ only on the precise property that flips the label. For example, two findings that both mention a string concatenation in a query but differ on whether the concatenated value traces to an untrusted entry point, or two messages that both contain a sensitive term but differ on whether the term is a directed attack versus self-reference or quotation for condemnation versus endorsement. Each pair carries a short because line naming the decision feature. The model given only the bad side learns feature implies flag as a rule and applies it to the benign side. Pairs that hold the surface token constant force the model to locate the actual decision feature such as directed versus self-referential or reachable versus isolated via parameterization. Once exposed, that feature transfers to phrasings never shown. Lesson framing that combines role with few-shot examples makes the same point: roles prime the reasoning approach while examples demonstrate the expected quality level and boundary, and together they outperform either alone.

Single-polarity positive-only sets such as eight few-shot, two per high-dismissal category, each pairing a flagged snippet with an instruction to down-weight fail because they increase the prior for feature implies flag without conditioning on the surrounding intent that actually determines correctness. The exam consistently treats four contrastive pairs as correct while treating six to eight genuine-violation-only examples or twelve weighted toward the positive side as incorrect even though the latter set is larger, because size does not substitute for the missing negative side. The negative side is not about balance for aesthetics. It is the only way to teach the model that the same surface feature can be benign and that a neighbouring predicate such as sanitization or parameterization or quoting determines the label.

The boundary for choosing which example strategy to use is therefore functional: if novel phrasings will continue to appear and the current rules are already written but the model still treats a surface token as sufficient evidence, contrastive pairs are the repair. If the prompt never defined its positive and negative sets in the first place, pairs without defined sets are premature: writing explicit report-versus-skip criteria comes first, then pairs that refine the boundary those criteria established. The reference page omits this nuance but the adjacent lesson on anti-patterns anticipates it: specificity is a dial, and review for O(n squared) time complexity and memory usage is better than review this code, which is better than no dimension at all, and refinement toward enumerated criteria precedes refinement of the boundary between its members.

Mechanism reference: System prompt role placement and the system versus user distinction revisited

Placement is worth a deeper treatment because the system prompt concept is frequently tested as a multiple-choice distinction: where to put role definition, hard constraints, output format, and the immediate task. Lesson structure treats the answer as a table rather than a rule of thumb: role, hard constraints, and output format that should hold for every turn belong in system, while the actual task for this turn and any one-off format change belong in the user message. The user versus system choice is not about emphasis. It is about persistence and overridability.

A role that belongs in system but is placed in a user message loses persistence. It applies for a turn and then decays, so a later user turn can drift the persona without any explicit override. A role that belongs in a user message, such as a temporary Now act as a QA engineer and review my test plan shift on top of a senior software architect system baseline, gains temporary framing without losing the primary persona. Conversely, a constraint that belongs in system but is left as a user instruction is not framed as higher-authority guidance and is more easily degraded by a subsequent user turn that pushes against it. The documented repair for a constraint that drifted mid-conversation is not to repeat the constraint in every user message, but to strengthen the system wording where it was underspecified or to add application-layer validation for the hard requirement.

Nested roles illustrate why persistence, recency, and specificity together determine outcome. A system prompt that says You are concise competing with a user message that says provide exhaustive detail does not deterministically choose one side. It averages them. A specific user role can then override a vague system role, but a specific system role dominates a vague user role. These hierarchy effects are predictable: system over user when both are comparably specified, recency adding weight to the most recent turn even when that instruction is less authoritative, specificity beating generality, and conflicts producing averaging. Choosing where a role lives is therefore a control over which hierarchy effect should dominate for the task.

Three implementation properties follow. First, the system prompt should take the most critical instruction first, because ordering inside the system prompt itself carries a primacy effect, and the first instruction is weighted more. Second, stable sections of the system prompt should be stored and delivered as cacheable blocks, because the system prompt plus tool definitions is the share of tokens most amenable to caching across requests. Third, changes to the system prompt should touch one component at a time, with shadow testing that compares old and new behaviour on a sample of real inputs and monitoring of regression signals such as tool call patterns, escalation rate, and refusal rate. Updating role and constraints in the same deployment is a characteristic anti-pattern because attribution of any behavioural shift becomes ambiguous.

Mechanism reference: Positive versus negative instruction framing

A separate framing choice within the system prompt is whether to describe what not to do or what to do instead. The reference page does not foreground this distinction, but forensics shows it is consistently tested: negative prohibitions such as NEVER include the original content in your explanation or You MUST NEVER, under ANY CIRCUMSTANCES, include original content risk reinforcing the undesired pattern, while positive instructions such as Describe flagged content by violation category, pattern type, and severity level only give the model an explicit allowed set to produce instead. Negative definitions leave the model to choose a replacement behaviour that often preserves the undesired pattern under different wording, while positive definitions constrain the output space to a specific shape. The same contrast appears in severity filtering. Only report HIGH and MEDIUM. Omit LOW unless asked with a positive classification ladder such as Classify each finding as CRITICAL, HIGH, MEDIUM, or LOW outperforms a vague negative such as Do not report minor findings, because the positive ladder names the exact alternative.

Lesson guidance on negative constraints adds precision to this picture: negative constraints are most valuable for output formatting where a positive rule alone still leaves a gap, such as respond only in JSON still allowing a Here is the JSON preamble that breaks parsing, so the reliable fix is an explicit negative such as Do not generate any text before or after the JSON object, no preamble, no explanation, no markdown code fences. That is a narrow exception to a general preference for positive framing. Where the task is to avoid a verbatim repetition, such as reproducing flagged content, positive framing dominates because negatives such as add examples showing what not to include demonstrate the bad shape the model is being asked to avoid and post-processing filters that remove quoted content are brittle on edge cases. The documented pattern is to prefer positive instruction that names the desired action, using negative constraints only for the specific gap an affirmative alone cannot close, and always pairing any negative with the positive replacement so the model has a well-defined alternative to emit.

Mechanism reference: Long inputs, attention dilution, and the verification gap

Two additional mechanisms explain why verification strategies succeed or fail, and both interact directly with system prompt design. The first is attention dilution across long inputs. The second is anchoring in self-review.

Long-contract and large-batch examples in forensics show that a single prompt ingesting a long artifact, such as an entire redlined SOW plus the playbook, a reference list of many endpoints, or a batch of many files, causes coverage of later sections to degrade even after the prompt explicitly asks the model to weight every schedule or section equally. Miss rates remain elevated because the instruction does not change the underlying attention distribution. The structural repair is to replace uniform weighting in a single call with per-unit independent calls. In its SOW form, that is per-clause calls where each call receives one clause plus the relevant playbook rule and aggregate findings, followed by a separate cross-clause integration pass that catches exposure emerging from interactions between schedules. In code review form, that is per-file processing where each file is evaluated against the same checkable rules, avoiding the burial of later files in a single session. The integration pass is not optional where interactions matter, because per-unit sharding alone would miss cross-unit effects.

Anchoring explains why same-thread verification fails. A pipeline that feeds the first pass finding and its reasoning back into the same conversation and asks the model to re-examine it inherits the prior framing, so reaffirmation remains high even though a material share of findings are non-issues. Even a second instance that is seeded with the original reasoning is instructed to uphold or overturn that reasoning, which frames the task as defence rather than fresh judgement. The repair is a second instance in a fresh conversation that receives only the clause or diff, the relevant playbook rule, and the asserted severity, without the first pass reasoning, and that independently judges whether the item is genuine. Any shared reasoning channel reintroduces the anchoring, so verification is trustworthy only when it is independent and grounded in the same explicit criteria that the first pass should have used.

Mechanism reference: Structured outputs and the boundary between prompting and enforcement

The DOC-URLS companion for this domain clarifies a terminology shift that matters for grounding. Structured outputs are now requested with output_config.format containing {"type": "json_schema", "schema": ...}. The older output_format top-level field and its associated beta header remain accepted for a transition period only. Strict tool use is the separate feature with strict: true on a tool, enforcing schema compliance on tool names and inputs by grammar-constrained sampling. The two features are independent and composable in one request. Schema compliance is therefore not a property of prompt wording at all. It is a sampling constraint enforced by the API.

That enforcement has limits that pipeline code must account for. A refusal returns stop_reason: "refusal" with a 200 status and billed tokens, and the refusal text takes precedence over the schema. Reaching the token limit also truncates output before the schema can be satisfied, so a large or unconstrained prompt that exhausts the budget will still produce incomplete output even though a schema was requested. For system prompt design, the consequence is that no amount of system wording replaces the structured outputs configuration when the consumer parses results programmatically. The system prompt defines what counts as a finding, structured outputs and application validation ensure that definition is delivered in a parseable shape, and confidence routing under calibration handles the residual uncertainty that neither enforcement can resolve. Each layer owns a different guarantee, and the pipeline fails when it asks one layer to do another layer's job, such as asking confidence to enforce a schema or asking a system sentence to guarantee format.

Ownership map

GuaranteePrimary ownerMechanismFailure mode if owned elsewhere
Persistent role, audience, and criteria across turnssystem in the Messages API, authored by the developerTop-level system parameter, optionally as cached blocks with cache_controlPlacing standing role or constraints in a user message loses persistence and lets later user turns override standing behaviour
Message ordering and alternationAPI and SDKmessages array must strictly alternate user and assistant, SDK construction enforces thisTwo consecutive same-role messages produce an API error, a 400 rather than a model judgement
Content boundary between instructions, examples, and dataDeveloper prompt design, aided by XML tags<instructions>, <criteria>, <examples>, <user_message> wrappers with shallow nestingUnlabeled sections let data appear to be instructions, especially when user input contains strings resembling closing tags
Format and schema complianceAPI through structured outputs, plus application codeoutput_config.format with JSON Schema and strict: true on tools, plus post-response parsing and validationPrompt-only format requests drift under unusual inputs, long conversations, or competing instructions; XML output without validation is malformed in edge cases
Semantic plausibility of fieldsApplication codePost-parse validation that checks cross-field consistency, such as quantity units or reachability of a sink, before deliverySchema-valid but semantically wrong values such as a duration in a quantity field pass schema checks
Severity stability and per-category precisionDeveloper prompt designConcrete code examples per severity tier, explicit REPORT versus SKIP sets, conjunctive conditions that must all holdProse labels such as dangerous or could cause data loss produce run-to-run and cross-file inconsistency
System-wide trust after a noisy category degradesDeveloper pipeline configurationTemporary disable or quarantine of the high false-positive categories from delivered output, with offline iteration on a validation setGlobal confidence gates or uniform strictness reductions suppress accurate categories alongside noisy ones and do not restore engagement
Re-enablement of a quarantined categoryDeveloper release processMeasurement on a held-out set of known findings such as past pull requests, with a per-category false positive gate before broad rolloutRe-enabling after reading the new criteria or after a single-reviewer pilot passes only seen complaint shapes
Confidence usability for routingDeveloper process plus application codePer-field and per-segment calibration against a labeled validation set, then routing of low-confidence or contradictory material to human or fresh-context verifier rather than silent suppressionUncalibrated self-rated thresholds tune around the symptom while leaving the misclassification logic intact
Input-scale coverage in long documentsDeveloper pipeline architecturePer-clause or per-file sharding plus a separate integration pass for cross-unit interactions, with fresh-context verification for critical findingsSingle long prompt with added equal-weighting instructions and same-thread self-review reaffirms the original errors

Version and terminology currency

Three currency notes affect how this material should be read by a candidate who may encounter older exam guide phrasing.

First, structured outputs terminology has shifted. Older material references output_format as the top-level mechanism for schema-constrained output. The current mechanism is output_config.format with {"type": "json_schema", "schema": ...}. Strict tool use with strict: true on a tool definition is the separate feature that constrains tool names and inputs. Community material that presents prompt-requested JSON versus tool use as the complete hierarchy is therefore incomplete. The full hierarchy is prompt-requested JSON, structured outputs through output_config.format, and strict tool use, with the last two composable and independent. A candidate should answer with output_config.format when asked which field enforces schema, and distinguish it from strict tool use.

Second, lesson variants that call system a message role use older phrasing. Messages carry role: "user" and role: "assistant", while system is a top-level parameter outside the alternating messages array. Behaviour is unchanged: system is the intended channel for standing, persistent instructions, carried as a string or as an array of text blocks with optional cache controls. Forensics distinguishes a role in system versus a role in a user message as persistence versus per-turn override.

Third, prompt caching is now a stable API feature rather than preview. The cache_control: { type: "ephemeral" } marker on stable system blocks is documented, and the note that a large system prompt plus tool definitions is worth caching because it is identical across requests is current. This affects what counts as a production-ready construction when shown a cached guidelines block, not which severity answer is correct.

No other version shifts affect task 4.1. Cost, throughput, and batch-window numbers belong to adjacent task statements and are treated there, not here. Where the reference page uses the legacy name for the trust bleed, the mitigation naming of temporary disable in policies is the stable one, and the additional variants quarantine, suppress from posted output, downgrade to a non-blocking channel, and stop surfacing while those rules are validated describe the same delivery quarantine rather than distinct mechanisms.

Official versus community divergence

Community material that appears in research feeds diverges from documentation on two points that directly affect this task. A candidate should answer with the documented position in each case and be prepared to recognise the community phrasing as a distractor.

Divergence 1: Whether confidence is a precise filter that tightens as the numeric gate rises. Community write-ups frequently present confidence as though raising 0.85 to 0.90 or 9 of 10 or 0.99 mechanically raises precision, and recommend per-category self-rated bars tuned to dismissal rates as though the score were measuring correctness. Forensics records that even 0.94 to 0.97 fields are later corrected while 0.80 items are correct, and that tightening the gate preserves the confident-but-wrong tail while hiding correct findings that were reported with lower numbers. The documented position is that self-reported confidence is not a calibrated probability and that raising the threshold before criteria exist and before calibration on labeled data does not change which families misfire. Community material that recommends global or per-category self-rated filtering as the first fix therefore contradicts documentation. A candidate should prefer explicit criteria first, calibrated routing second and should treat any answer that filters by self-rated confidence before defining what counts as incorrect.

Divergence 2: Whether vague replacement phrasing such as extremely conservative, be careful, use your best judgement, and avoid false positives constitutes an incremental improvement. Community posts often suggest that intensifying a vague line or scattering careful throughout the prompt will gradually improve precision, sometimes paired with show confidence or lower temperature so that the change appears technical. Lesson material formalises the opposite view as the testability criterion: if two reasonable people would disagree about whether a constraint was followed, the wording is not specific enough to be a boundary, no matter how strongly it is stated. Forensics records that these intensified variants produce the same single-category failure with only volume trimmed, and that they compete with explicit rules when both are present and can suppress genuine findings that feel uncertain. The documented repair is to replace the adverb with a predicate over observables or to replace a prose severity paragraph with a code example per tier, rather than to restate the adverb more forcefully. A candidate should treat any answer that strengthens a vague instruction without adding a new checkable clause as incorrect.

A narrower divergence appears in how severity consistency is presented. Community suggestions sometimes propose a mapping table of issue types to default severities or a requirement to explain reasoning for manual recalibration as the cure for inconsistency. Both are documented as insufficient. Type alone does not determine risk magnitude without a concrete code shape, and tracing does not fix the underlying prose mapping. Only per-tier code examples move consistency. The exam uses this incomplete community suggestion as a distractor precisely because it looks systematic without addressing the missing observable anchor.

Beyond the task statement

The reference page for 4.1 covers four of the mechanisms it assigns, but the adjacent lesson material contains topics that enrich a system prompt design answer and that the reference omits entirely.

Meta-prompting as a system prompt authoring aid. Meta-prompting treats prompt authoring as a generative task where a model helps write or improve a prompt, then the improved prompt is tested and iterated. For task 4.1, this matters because improving noisy review categories is an iterative rewrite where a meta prompt can propose explicit criteria and code examples, but only a held-out measurement can validate that precision moved. A candidate should therefore distinguish use a meta prompt to draft the rewrite as a workflow aid from re-enabling because the explicit rules themselves demonstrate improved precision as a validation claim. The first is legitimate, the second is not.

Extended thinking and its interaction with prompt wording. Extended thinking shifts reasoning into a dedicated thinking content block at the API level rather than a tag the application must parse from text. It helps when step-by-step reasoning genuinely benefits the task and can hurt where it does not, and the reference build exercise notes that reasoning does not fix vague criteria. For system prompts, enabling thinking or requesting output inside <thinking> does not correct an undefined boundary. Longer reasoning against an unclear standard yields longer justifications for the same ambiguous decisions. Thinking is complementary to explicit criteria and examples but never a substitute for them.

Role hierarchy and persona drift in long conversations. Lessons on role prompting detail how persona consistency can decay across many turns and recommend reinforcement patterns such as reminders to stay in role, explicit behavioural constraints such as never breaking character, and behavioural anchors such as always providing production examples. This is adjacent material that the reference does not connect to trust. A review bot that drifts from its code-review persona mid-conversation will amplify spillover, because a drifted persona is more likely to emit a finding that crosses a category boundary. The system prompt's role definition is therefore part of trust design: the persona frames which categories the assistant should treat as in-scope in the first place, and examples at the end reinforce that frame each turn.

Prompt caching and its placement implication. Lessons on system prompt design and on context management both note that system prompts are ideal candidates for prompt caching because they are identical across requests, and that large stable guidelines should be placed in a cached block where subsequent calls pay a fraction of the cost. Beyond cost, caching affects reliability. A long system prompt rewritten as two cached blocks, one for role and constraints and one for the large guidelines, is easier to audit than a monolithic string, and the cached boundary discourages the antipattern of interleaving volatile per-request data inside the system content. Candidates who recognise that a code example shows system: [{ type: "text", text: LARGE_GUIDELINES, cache_control: { type: "ephemeral" } }] are seeing a production-ready construction, not a debugging choice.

Negative constraints and the positive-over-negative preference. Updated lesson content separates negative constraints that are layered on top of positive instructions from standalone prohibitions, and notes that negatives are most reliable for format rather than judgement. That distinction refines the exam's positive-over-negative guidance: where the goal is to suppress verbatim reproduction of flagged content or to enforce a severity ladder, the correct answer names the desired action rather than restating the prohibition. Where the goal is to guarantee machine-parseable JSON, a precise negative added to the positive request, such as Do not generate any text before or after the JSON object, is the expected pattern.

Few-shot taxonomy beyond formatting. The few-shot lesson presents examples as the strongest lever for showing output shape and for handling edge cases, placed closest to the task. Lesson and forensics clarify the taxonomy: positive-only examples calibrate format, while contrastive pairs that hold a surface feature constant across flag versus skip calibrate the boundary. A system prompt for code review therefore needs both, in exam order: explicit criteria first, then the right example shape for the residual failure. Adding two per category positive-only demonstrations to a prompt whose criteria remain underspecified is therefore marked incorrect. Examples do not rescue criteria, they refine what criteria already define.

Instruction hierarchy and recoverability. Lesson material treats hierarchy explicitly: system instructions are the standing frame, user turns are per-request input, later instructions can override earlier ones when they compete, and system is intended to dominate without achieving a hard guarantee. For a review pipeline, hierarchy determines recoverability: a noisy category in the system prompt is recoverable by editing the persistent frame, while a per-turn user override is recoverable by removing the override and relying on the cleaner baseline. This is why trust-recovery designs disable delivery from the system configuration rather than adding a user turn that says ignore the noisy category this time. The former changes standing behaviour, the latter creates a turn-local exception that must be renewed.

The build exercise link. The reference page includes a multi-step build exercise that starts from a vague prompt, measures false positive behaviour on a small test set, rewrites to explicit criteria and code examples per severity tier, and then applies the trust strategy of disabling problematic categories. That exercise is the bridge between the system prompt task and production practice. It makes the transition from consistent classification as an impression to stable across repeated runs on the same snippets as an observable behaviour. Adjacent lessons treat build exercises as the way to prove that a prompt change survived shadowing and that the system layer still dominates after the change.

Worked production examples: Example A: Replacing a vague review prompt with explicit categorical criteria

A team runs a CI bot on every pull request. The original system prompt is a single sentence appended during a complaint-driven hotfix:

> Review this code. Be conservative. Only report high-confidence findings.

Measured behaviour matches the anti-pattern described in the reference page and in lessons on prompt anti-patterns: classification is inconsistent across runs, style nits are flagged as critical in one file and skipped in another, and genuine injection patterns are missed or marked minor depending on surrounding context. Intensifying the line to extremely conservative or use your best judgement or avoid false positives keeps the same prior under harsher pressure and preserves the single-category failure.

The rewrite replaces the adverb with checkable predicates, enumerated report and skip sets, and a conjunctive condition for vulnerability findings. It lives in system as a cached block, separates criteria from code with delimiters, and puts format constraints at the end with their negative companion. The observable change is that membership becomes decidable: a reviewer can verify whether userName versus user_name belongs in SKIP for locally consistent style, or whether a query with string concatenation that reaches a sink from an untrusted entry point without sanitization belongs in REPORT.

example.ts
typescript
// Example 1: Vague prompt rewritten into explicit categorical criteria
// System prompt construction with RACCE ordering and XML delimiters

const SYSTEM_PROMPT_EXPLICIT_CRITERIA = [
  "You are a senior application security reviewer specializing in TypeScript.",
  "Audience: developers reading CI comments under time pressure. Be concise and actionable.",
  "Criteria for findings:",
  "<criteria>",
  "  REPORT: bugs where claimed comment behaviour contradicts actual code behaviour,",
  "          security findings where untrusted input reaches a sink without sanitization",
  "          with both reachability from user input AND impact of data exposure or unauthorized action.",
  "  SKIP: style, naming, formatting, linter-enforced checks, intentionally broad",
  "        exception handlers in batch jobs, and house logging style.",
  "  SEVERITY: defined by code examples per tier in <examples> below, not by prose labels.",
  "</criteria>",
  "<constraints>",
  "  1. Output valid JSON matching the response schema. Output ONLY the JSON object, no preamble, no markdown fences.",
  "  2. Never include the original flagged code verbatim. Describe category, pattern type, and severity only.",
  "  3. If evidence is insufficient to decide a clause, mark the finding as insufficient evidence and skip.",
  "</constraints>",
  "<examples>",
  "  <!-- severity and boundary examples live here, closest to the user input -->",
  "</examples>",
].join("\n");

import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();

async function reviewDiff(diffText: string) {
  const response = await client.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 2048,
    system: [
      { type: "text", text: SYSTEM_PROMPT_EXPLICIT_CRITERIA, cache_control: { type: "ephemeral" } as const },
    ],
    messages: [
      { role: "user", content: `<code_diff>${diffText}</code_diff>\n<task>Apply <criteria> to the diff. Produce JSON findings.</task>` },
    ],
  });
  // Application-layer enforcement: parsing, field validation, severity enum check
  return JSON.parse(response.content[0].type === "text" ? response.content[0].text : "{}");
}

What this block proves: explicit criteria replace interpretation with a membership test, conjunctive conditions prevent single-property over-flagging such as flagging string concatenation that is config-internal, and delimiter separation prevents the diff from appearing to be instructions. The failure boundary is that explicit criteria do not help when a trust boundary is scoped too narrowly, such as restricting reachability to external HTTP requests while message queues are legitimate entry points. Widen that conjunct to the team's actual boundary rather than dropping the conjunction.

Worked production examples: Example B: Severity calibrated by concrete code, not prose

The second failure is severity instability. A system prompt that defines Critical: system failure or data loss. Minor: readability only. still maps an abstract adjective to a continuous estimate of could cause, so the same missing null guard is labelled critical in one file and low in another without artifact change. Expanding the paragraph adds more adjectives without adding a checkable property. Even adding numeric risk 0 to 100 preserves the defect because the number inherits the same prose calibration problem.

The repair binds each tier to labeled code fragments with a one-line rationale, so classification becomes nearest-example matching rather than estimation. The documented minimum is one code snippet per severity value, with stronger coverage at two to three for the contested boundary or five to seven spanning the full range drawn from consensus or borderline cases. Strings of the form userName versus user_name within the same module sit at the minor anchor. Unsanitized external input concatenated into a query sits at the critical anchor. Missing null guards on a may-be-null service sit at the major anchor. The start of the ladder must not be assumed to be format imitation: without anchors, numbers and prose together remain insufficient.

example.ts
typescript
// Example 2: Severity anchored with concrete code per tier
// Each tier carries a snippet observable in the artifact, not a prose definition

const SEVERITY_ANCHORS_XML = `
<examples>
  <example tier="CRITICAL" rationale="external input concatenated without sanitization, reachable sink">
    <code>query = f"SELECT * FROM users WHERE id = {user_input}"</code>
    <fields><severity>CRITICAL</severity><category>security</category><detected_pattern>string concatenation in database query</detected_pattern></fields>
  </example>
  <example tier="CRITICAL" rationale="unsanitized value passed to eval-like sink from external origin">
    <code>eval(user_input)</code>
    <fields><severity>CRITICAL</severity><category>security</category><detected_pattern>user input passed to eval()</detected_pattern></fields>
  </example>
  <example tier="MAJOR" rationale="crash path, observable missing guard on may-be-null">
    <code>const user = cache.get(id); return user.name;</code>
    <fields><severity>MAJOR</severity><category>bug</category><detected_pattern>missing null check on cache lookup</detected_pattern></fields>
  </example>
  <example tier="MINOR" rationale="readability only, no functional risk">
    <code>let userName = "alice"; let user_name = "bob";</code>
    <fields><severity>MINOR</severity><category>style</category><detected_pattern>naming inconsistency within module</detected_pattern></fields>
  </example>
  <example tier="MINOR" rationale="style idiom already enforced by tooling, no novel risk">
    <code>if (x == null) throw new Error("missing"); // defensive guard per team lint</code>
    <fields><severity>LOW</severity><category>style</category><detected_pattern>defensive null check per linter</detected_pattern></fields>
  </example>
</examples>
`.trim();

const SYSTEM_WITH_SEVERITY_ANCHORS = [
  "You are a senior TypeScript reviewer reporting severity by example match.",
  "<criteria>",
  "  Classify severity by which anchored example the candidate most closely resembles.",
  "  Do not estimate danger from surrounding tokens. Compare to the nearest snippet and its rationale.",
  "</criteria>",
  SEVERITY_ANCHORS_XML,
  "<constraints>Output valid JSON. Only use severity values that appear in <examples>.</constraints>",
].join("\n");

What this block proves: severity labels become stable across invocations and files because the standard is observable. The failure boundary is per-tier coverage: anchoring only CRITICAL with multiple examples while omitting MAJOR or MINOR counterparts leaves novel findings without a comparative context, and a single idiosyncratic example per tier overfits that tier to a narrow shape. Choosing anchors from a consensus set such as consensus cases or borderline disagreement cases where human reviewers previously diverged strengthens transfer beyond hand-crafted snippets.

Worked production examples: Example C: system parameter used correctly with structured sections and delimiters

The third repair concerns where things live. A common construction puts everything in the user message, so standing instructions compete as one paragraph among many. The correct construction places the standing frame in system with stable blocks cached and volatile inputs in the user message wrapped with tags that prevent injection.

example.ts
typescript
// Example 3: Correct system parameter construction with delimiters and caching

import Anthropic from "@anthropic-ai/sdk";

const SYSTEM_ROLE = "You are a senior code review assistant specializing in TypeScript security and correctness.";
const SYSTEM_GUIDELINES = `
<criteria>
  <report_when>
    <rule>claimed comment behaviour contradicts actual code behaviour</rule>
    <rule>query uses string concatenation AND value is unsanitized AND value originates from untrusted entry point</rule>
  </report_when>
  <skip_when>style preferences, local naming patterns, linter-enforced checks, sanctioned exception handlers</skip_when>
</criteria>
<response_format>
  JSON array of objects with fields: file, line, category, severity, detected_pattern, remediation.
</response_format>
`.trim();

const SYSTEM_NEGATIVE = "Output ONLY the JSON array. Do not generate any text before or after it. No preamble, no code fences.";

const client3 = new Anthropic();

async function reviewWithSystemAndDelimiters(code: string, fileName: string) {
  const response = await client3.messages.create({
    model: "claude-sonnet-4-6",
    max_tokens: 4096,
    system: [
      { type: "text", text: SYSTEM_ROLE },
      { type: "text", text: SYSTEM_GUIDELINES, cache_control: { type: "ephemeral" } as const },
      { type: "text", text: SYSTEM_NEGATIVE },
    ],
    messages: [
      {
        role: "user",
        content: [
          { type: "text", text: "Review the file below against <criteria>. Respect <response_format>." },
          { type: "text", text: `<file name="${fileName}"><code>${code}</code></file>` },
        ],
      },
    ],
  });
  const text = response.content.find((b) => b.type === "text")?.text ?? "[]";
  const parsed = JSON.parse(text);
  // Enforcement owns shape: reject unknown severity, missing file, or verbatim code echo before surfacing
  return Array.isArray(parsed) ? parsed.filter((f) => ["CRITICAL","HIGH","MEDIUM","LOW","MAJOR","MINOR"].includes(f.severity)) : [];
}

What this block proves: system carries persistence and authority, the cached block SYSTEM_GUIDELINES amortizes token cost, tags <criteria> and <file> keep instruction and data in distinct spans, and the negative constraint handles the preamble gap that temperature adjustment cannot close. The failure boundary is empty-tag ambiguity and over-nesting. Conditional sections such as <reference_documents> should be omitted or given None provided rather than rendered as <reference_documents></reference_documents> where the model may invent missing content, and nesting should be flattened beyond three to four levels. The observable output is parseable JSON with no surrounding prose, ready for downstream enforcement such as severity enum rejection.

Worked production examples: Example D: Category-disable and re-enable for trust recovery

Noisy categories that dominate dismissals destroy system-wide trust because developers apply a global ignore before evaluating any per-finding signal. The correct first bleed stop is to remove the noisy categories from delivered output. They continue offline against a validation set, are iterated with explicit criteria and code examples, and are re-enabled only once per-category precision is validated on held-out data. Lowering a threshold or showing confidence does not restore engagement when the remaining mix still contains frequent bad tags, and keeping all categories active while improving each gradually leaves the attention cost in place.

Delivery quarantine has many surface names that describe the same intervention: temporarily disable the documentation mismatch category, suppress the style category from posted output for now, downgrade to a non-blocking review channel, and stop surfacing the high false-positive categories while those rules are validated. The essential property is isolation of the noisy stream from the primary attention channel.

example.ts
typescript
// Example 4: Category quarantine with held-out validation before re-enablement

type ReviewCategory = "bug" | "security" | "performance" | "style" | "naming" | "documentation";

interface ReviewConfig {
  enabledCategories: ReviewCategory[];
  quarantined: ReviewCategory[];
  thresholds: { perCategoryFalsePositiveMax: number; heldOutSetSize: number };
}

const CANDIDATE_CONFIG: ReviewConfig = {
  enabledCategories: ["bug", "security"],
  quarantined: ["style", "naming", "documentation"],
  thresholds: { perCategoryFalsePositiveMax: 0.15, heldOutSetSize: 80 },
};

// Offline iteration: rewrite quarantined prompts with explicit criteria + code examples
// then validate before broad rollout; reporter-only pilot or single-reviewer check is not sufficient

type ValidationResult = { category: ReviewCategory; falsePositiveRate: number; sample: string };

function shouldReEnable(result: ValidationResult, cfg: ReviewConfig): boolean {
  // Per-category gate on a held-out set of past pull requests with known findings
  // such as an 80-case consensus set, not the complaint sample
  if (result.falsePositiveRate > cfg.thresholds.perCategoryFalsePositiveMax) return false;
  // Also require per-pattern inspection via detected_pattern before re-enabling
  // e.g. concatenation shape versus eval shape must both clear the gate
  return true;
}

// Delivery path posts only enabledCategories. Quarantined categories run offline
// against the held-out set and are considered for re-enablement one at a time.

function filterFindingsForDelivery(findings: Array<{ category: ReviewCategory }>, cfg: ReviewConfig) {
  return findings.filter((f) => cfg.enabledCategories.includes(f.category));
}

function promoteAfterValidation(cfg: ReviewConfig, result: ValidationResult): ReviewConfig {
  if (!shouldReEnable(result, cfg)) return cfg;
  return {
    enabledCategories: [...cfg.enabledCategories, result.category],
    quarantined: cfg.quarantined.filter((c) => c !== result.category),
    thresholds: cfg.thresholds,
  };
}

What this block proves: quarantine attacks the consumer prior directly, so trust in accurate categories returns without change to those categories and without suppressing genuine findings in the accurate set. Offline iteration preserves coverage for later refinement, and per-category validation on a held-out set supplies evidence that the rewritten criteria improved measured precision on the failure shapes that drove dismissals. The failure boundaries are uniform disablement and premature re-enablement. Disabling is incorrect when noise is uniformly distributed and no clean high-precision remainder exists to preserve, where global criteria work scoped to blast radius is the expected path instead. Re-enabling immediately after new criteria are added, or after a reporter-only pilot or a single senior reviewer's small manual check, is consistently marked incorrect because the check does not cover the production distribution.

Worked production examples: Example E: Confidence used for routing, not for suppressing findings

Confidence belongs after criteria and after calibration. Before those steps, any gate over confidence is a filter over self-assessment from the same reasoning that produced the finding, which is why raising 0.85 to 0.90 or switching 1 to 10 versus 0 to 100 preserves the defect, and why confidence exceeds 0.7 suppresses correct uncertain material alongside confident errors. After explicit criteria and calibration, the same signal becomes a router that preserves every finding while directing low-certainty, contradictory, or not-yet-validated material to a human or an independent fresh-context verifier. High-confidence material in validated segments may proceed, and nothing is silently dropped for scoring low.

Granularity strengthens the router. Field-level scores such as quantity: 0.31 versus unit: 0.94 enable targeting of the weak segment, while a single document-level score hides it. Calibration method strengthens it further. Reported confidence must be mapped per field and per segment against a labeled set where human adjudication is truth, with stratified random sampling of a fixed fraction of high-confidence outputs each week across document type, field type, or confidence band to surface confident-but-wrong errors and to measure cycle-to-cycle improvement. The following production skeleton implements routing rather than suppression.

example.ts
typescript
// Example 5: Confidence as a calibrated router, not a silent filter

type ExtractionField = "quantity" | "unit" | "party_name" | "indemnification_clause";

interface FieldConfidence {
  field: ExtractionField;
  reported: number;   // self-reported score from the model
  calibrated?: number; // mapped from a labeled validation set per field and segment
  contradictory?: boolean;
}

interface Calibration {
  field: ExtractionField;
  segment: string; // e.g. comparison_table, appendix, scanned_handwritten
  mapping: Record<string, number>; // reported bucket to measured accuracy, e.g. "0.95": 0.71
  threshold: { autoRelease: number; review: number };
  source: string; // label for the validation set that produced the mapping
}

const CALIBRATIONS: Calibration[] = [
  {
    field: "party_name",
    segment: "typed_contract",
    mapping: { "0.95": 0.99, "0.85": 0.93, "0.75": 0.82 },
    threshold: { autoRelease: 0.95, review: 0.80 },
    source: "labeled_validation_set_1000_docs_typed_contract",
  },
  {
    field: "indemnification_clause",
    segment: "appendix_heavy",
    mapping: { "0.95": 0.71, "0.85": 0.64, "0.75": 0.51 },
    threshold: { autoRelease: 0.97, review: 0.85 },
    source: "labeled_validation_set_600_docs_appendix_segment",
  },
];

type Route = "auto_release" | "human_review" | "fresh_context_verifier";

function routeField(f: FieldConfidence, calib: Calibration | undefined): Route {
  if (f.contradictory) return "human_review";
  if (!calib) return "human_review"; // uncalibrated segments do not auto-release
  const measured = calib.mapping[String(f.reported)] ?? 0;
  if (measured >= calib.threshold.autoRelease) return "auto_release";
  return "human_review";
}

// Never filter silently. Low or uncalibrated simply means more scrutiny.
function emitWithRouting(fields: FieldConfidence[]): Array<FieldConfidence & { route: Route }> {
  return fields.map((f) => {
    const calib = CALIBRATIONS.find((c) => c.field === f.field);
    return { ...f, route: routeField(f, calib) };
  });
}

// Stratified sampling of high-confidence material, weekly, to catch confident-but-wrong
// and to measure whether explicit criteria moved the high-confidence error rate

interface SamplingPlan {
  fraction: number; // fixed fraction of high-confidence extractions
  cadence: "weekly";
  strata: Array<{ dimension: "document_type" | "field_type" | "confidence_band"; values: string[] }>;
}

const SAMPLING_PLAN: SamplingPlan = {
  fraction: 0.05,
  cadence: "weekly",
  strata: [
    { dimension: "document_type", values: ["product_pages", "comparison_tables", "appendix_heavy"] },
    { dimension: "field_type", values: ["quantity", "indemnification_clause"] },
    { dimension: "confidence_band", values: ["85-90", "90-95", "95-100"] },
  ],
};

What this block proves: routing preserves information under scrutiny where filtering would discard it, per-field granularity prevents a strong field from masking a weak one, and per-segment calibration gives a principled basis for choosing thresholds that reflect measured precision rather than intuition. The sampling plan closes the loop: a fixed weekly fraction stratified across sensitive dimensions guarantees coverage of rare but error-prone types that pure random would miss and allows the team to track whether fixes such as few-shot examples for comparison tables actually moved the pipeline-wide estimate. The failure boundary is global thresholds, document-level scores, and lowering a global bar to match review capacity. A threshold is a quality signal, not a volume dial. The global document score is adequate only when disposition is whole-document and per-field accuracy is uniform, which the 99 versus 71 pattern shows is frequently not the case.

Build exercise material

The reference page prescribes a hands-on exercise that makes the abstract claim concrete by showing that vague instructions produce inconsistent classification while explicit criteria with code examples produce stable output. The following steps translate that prescription into verifiable build steps with observable outcomes. Each step states what to do, why it tests a task claim, and what success looks like. Steps assume access to the Messages API. All keywords use inline backticks as required for identifiers such as system, messages, content, cache_control, and detected_pattern.

Step 1: Establish a vague baseline. Create a system prompt with a vague line such as Review this code. Be conservative. Only report high-confidence findings. and a generic role such as You are a helpful assistant. Drive it with five code snippets that span known categories: one reachable injection with user_input reaching a concatenation sink, one parameterized query that is safe, one missing null guard, one naming inconsistency userName versus user_name, and one intentionally broad exception handler that the team lint permits. Send each snippet as <code> inside the user message with temperature at its default. Record the findings. Observable outcome that proves the step worked: classification is inconsistent. The same file run twice produces different severity labels, a style nit is labeled CRITICAL on one run, and a genuine injection is missed or marked MINOR on another. Lesson mapping predicts this variance because no dimension or criterion was defined, so the model guesses the dimension each time.

Step 2: Rewrite with explicit categorical criteria and positive framing. Replace the vague line with a RACCE-structured system prompt in system with three concrete components: a REPORT set bugs, security vulnerabilities, claimed versus actual comment contradictions and a SKIP set style preferences, local patterns, linter-enforced checks, house logging style, a conjunctive security predicate uses string concatenation AND concatenated value is unsanitized AND value originates from untrusted entry point, and a positive output directive Describe flagged content by violation category, pattern type, and severity level only with its negative companion Output ONLY the JSON object, no preamble. Wrap the code under <code> and the task under <task>. Observable outcome: the same five snippets now produce a stable binary decision per snippet. The injection that satisfies all three conjuncts is reported, the parameterized safe counterpart is skipped, and a reviewer can point to which clause failed for any skipped item. The change is attributable to the new predicate because no other parameter changed.

Step 3: Anchor severity with code examples per tier and place them last. Add an <examples> block at the end of the system prompt with one anchored example per severity value: CRITICAL with query = f"SELECT * FROM users WHERE id = {user_input}", MAJOR with const user = cache.get(id); return user.name; where the guard is missing, and MINOR with userName versus user_name within the same module, each with a one-line rationale. Keep tier prose minimal because prose alone preserves inconsistency even when it reads as precise. Observable outcome: severity labels converge across runs. Identical null-pointer shapes in two files receive the same label, and the model justifies labels by nearest-example resemblance rather than by estimating danger. The failure case to watch for is anchoring only one tier with multiple examples while omitting anchors for neighbouring tiers, which leaves novel findings without comparative context.

Step 4: Measure false positive rate on a held-out set and apply the trust strategy. Assemble a held-out set of past pull requests whose ground truth is known, such as an 80-case consensus set not used during rewrite. Compute per-category false positive rate before and after the rewrite. If any category remains above the team's gate, for example 0.15, apply delivery quarantine: remove that category from posted output, keep accurate categories at existing precision, and iterate offline with explicit criteria, code examples, and contrastive pairs that hold the surface feature constant across flag versus skip. Observable outcome: the delivered stream reflects the high-precision remainder and the quarantined category can be remeasured each cycle without polluting trust. Re-enablement requires the held-out rate to drop below the gate, not a reading that prose looks improved or a single-reviewer check on seen examples.

Step 5: Replace confidence filtering with calibrated routing. Add a confidence field alongside each finding, but do not gate delivery on it. Build a per-field calibration mapping from a labeled validation set per field and per segment, such as invoice_total versus indemnification clause or comparison table versus appendix, where reported 0.95 maps to different measured accuracy in different segments. Route fields whose calibrated score clears the segment-specific auto-release threshold, route low-confidence or contradictory material to human review, and route not-yet-validated material to a fresh-context verifier that sees only the artifact and rule. Supplement the router with stratified sampling of a fixed weekly fraction of high-confidence outputs across document type, field type, or confidence band. Observable outcome: low-confidence material is no longer silently suppressed and high-confidence blind spots become measurable. The step fails if the threshold is chosen globally or at the document level, where a strong field masks the weak one.

Step 6: Verify placement and enforcement layers end to end. Confirm that system carries role, criteria, constraints, and severity examples with caching on stable blocks, and that the user message carries only the labeled artifact such as <code> and <task>. Confirm that structured outputs with output_config.format are configured where JSON is parsed and that application code validates enums such as severity values and detected_pattern before surfacing. Confirm that long inputs use per-file or per-clause sharding plus integration where interactions span units, and that CRITICAL verification uses a fresh-context verifier rather than same-thread review. Observable outcome: one-component changes can be shadow-tested and parse failures or refusals with stop_reason: "refusal" do not surface as malformed findings because enforcement sits outside the prompt.

Mechanism and API surface

System parameter with string or blocks
Messages API system accepts a plain string or an array of blocks with type and cache_control, the array form enables prompt caching for large stable prompts.
Prompt caching with ephemeral
Wrap the stable system prompt with cache_control type ephemeral, first call pays full price to populate the cache, subsequent calls with the same prefix pay a fraction per token while attention cost remains.
RACCE framework
Role, Audience, Criteria, Constraints, Examples. Each maps to a failure mode and the fix for an underperforming prompt almost always maps to one component.
Explicit categorical criteria with examples per severity
Define what to flag and what to skip per category with concrete code patterns, for example critical as a SQL injection snippet and minor as inconsistent variable naming, not prose descriptions.
Primacy and recency placement
Critical behavioural constraints at the very top, supporting detail in the middle, examples last. Content in the middle of a long prompt receives weaker attention than the same content at either end.
Negative constraints over temperature
Specific suppressions such as Output ONLY the JSON object, no preamble, no explanation, no markdown fences suppress a pattern directly. Temperature controls randomness, not pattern absence.
Size budget and update discipline
Keep system prompt under 10 percent of token budget, version in source control, shadow test a sample of real inputs on old versus new prompt, change one RACCE component at a time, and monitor satisfaction, tool call patterns, and refusal rate.

The decision rules in play

Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.

R1

Vague conservatism directives fail because they do not define a decidable boundary

The prompt contains be conservative, only report high-confidence findings, use your best judgement, or be careful without a testable predicate. The model must map the adverb to a yes or no decision with no deterministic clause to evaluate, so it falls back to sampling against a generic caution prior. The line is often appended at the end of a longer prompt during a complaint-driven hotfix without reworking category definitions.

Models have no stable cross-domain meaning for conservative. For code review it may mean fewer style nits in one run and more hedging on security in the next. Without an externally defined threshold or category list the instruction adds no information to the decision function. Evidence shows volume trimming near a fifth with false-positive rate essentially flat on the bad category, because the shapes were never named. Raising to extremely conservative or 100 percent certain intensifies the same undefined prior without giving a new test, so the classification function does not change.

Boundary. The rule flips when the qualitative line is replaced by a predicate over observables such as Flag a comment only when its claimed behavior contradicts the code's actual behavior. That predicate is a logical condition comparing what the comment claims to what the code does. Once present, the vague modifier is unnecessary and, if retained, competes with the explicit rule and may suppress genuine findings that feel uncertain.

Recurring specifics. - Phrases: be conservative, only report high-confidence findings, use your best judgement, be careful, flag carefully, prioritize precision over recall, err on the side of caution, avoid false positives - Numbers after the patch: 35 to 41 percent false positives persist, about a fifth volume trimmed, dismissed rate flat - Co-occurring fields that do not fix it: confidence, temperature, max_tokens, generic reasoning that justifies against an unclear standard

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Appears quantitative.

Why it fails. Self-reported confidence is poorly calibrated, so the gate filters by reported certainty not correctness.

When it would be right. After explicit criteria define the category and confidence is calibrated, as a routing signal for human review.

Proposal. Proposal.

Why it attracts. Determinism promises consistency.

Why it fails. Determinism without a decidable criterion reproduces the same vague judgment deterministically.

When it would be right. When criteria and examples are already explicit and residual variance is pure sampling noise.

Proposal. Proposal.

Why it attracts. Longer reasoning feels thorough.

Why it fails. More reasoning against an undefined standard yields longer justifications for the same ambiguous decisions.

When it would be right. When explicit examples define the boundary and extra reasoning compares the artifact to those examples.

How the same rule gets re-asked
  • - Adverb variant carefully flag vulnerabilities keeps the same failure and still requires explicit criteria. - Stacking be conservative plus prioritize precision over recall preserves the same single-category failure. - Scale change to above 80 percent preserves failure because any threshold over an uncalibrated self-report behaves identically.
R2

Explicit categorical report-versus-skip criteria turn noise into a binary test

Replace the qualitative instruction with enumerated report and skip sets bound to observables. The model evaluates membership rather than estimating caution. A finding is reported only if it lands in the report set and not in the skip set.

The block REPORT: bugs, security vulnerabilities and SKIP: style, naming, formatting with one-sentence observable predicates dominates precision more than surrounding prose.

example.ts
typescript
type FindingCategory = "bug" | "security" | "data-loss" | "style" | "naming" | "formatting";

interface ReviewCriteria {
  report: FindingCategory[];
  skip: FindingCategory[];
  commentRule: "flag only when claimed behavior contradicts actual code behavior";
}

Membership is decidable and auditable. A reviewer can verify whether userName versus user_name belongs in SKIP for locally consistent style, or whether string concatenation reaching a sink belongs in REPORT for injection. Both sides must be enumerated; defining only REPORT leaves a large middle ground of local patterns that the model fills with prior-driven over-flagging.

Boundary. Fails when category definitions are themselves vague prose such as REPORT: comments that are inaccurate which reintroduces ambiguity. Must name an observable contradiction, not a synonym for accuracy. Also fails when the skip set omits a large locally-permitted idiom class such as defensive null checks the linter already enforces, intentionally broad exception handlers in batch jobs, and house logging style.

Recurring specifics. - Positive: bug, security, logic bug, vulnerability, data-loss - Negative: style, naming, formatting, minor style, local pattern, linter-enforced check, sanctioned exception handler, permitted logging - Comment predicate: flag only when claimed behavior contradicts actual code behavior - Schema: category, report, skip, detected_pattern, confidence when misused as a gate

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Self-correction.

Why it fails. Confidence tracks verbosity rather than membership.

When it would be right. As secondary routing after explicit criteria.

Proposal. Proposal.

Why it attracts. Mechanically reduces volume.

Why it fails. Suppresses genuine defects once filled.

When it would be right. As presentation pagination after precision is high.

Proposal. Proposal.

Why it attracts. Auditable.

Why it fails. Justification against vague categories rationalizes over-flagged items.

When it would be right. When criteria are explicit and reasoning walks the membership test.

How the same rule gets re-asked
  • - Switching domain to invoice extraction preserves the same split: reportable patterns that cause payment errors versus formatting-only skips. - Expanding skip from generic minor style to enumerated linter-enforced null checks, batch broad handlers, house logging strengthens the test. - Comment predicate contradicts actual behavior versus general accuracy instruction is the smallest mutation that flips correctness.
R3

Conjunctive boolean criteria require every clause to be satisfied before a finding is emitted

A flagging condition is a conjunction of checkable properties. Each conjunct must be true or the finding is skipped. Examples include attack vector is reachable from user input AND impact is data exposure or unauthorized action and untrusted external input reaches a sink without sanitization.

Examples that satisfy all three versus those that fail one make the conjunction operational.

result.json
json
{
  "finding": "sql_injection",
  "criteria": {
    "all_of": [
      "query uses string concatenation, not parameterization",
      "concatenated value is unsanitized",
      "value originates from external HTTP request"
    ]
  },
  "action": "report only if every condition is true"
}

Single-property triggers are overinclusive. string concatenation in a query alone flags internal config concatenations, test fixtures, and admin tools with no external input. Requiring reachability together with concrete impact eliminates the large benign subset that shares a surface feature but lacks a complete exploit chain. The conjunction also makes the skip legible: a reviewer can see which clause failed.

Boundary. Too strict when a conjunct excludes a genuine risk class in scope, such as restricting origin to external HTTP requests while file imports or message queues are legitimate entry points. Fix by widening that conjunct to the team's actual trust boundary rather than dropping the conjunction. Disjunction reachable OR impactful reintroduces over-flagging and is consistently incorrect.

Recurring specifics. - Injection: query = f"SELECT * FROM users WHERE id = {user_input}" reportable only when user_input traces to an external entry point without sanitization. Safe: query = "SELECT * FROM users WHERE role = '" + internal_config + "'" where internal_config is config constant or admin session. - Phrasing tested: Flag only vulnerabilities where the attack vector is reachable from user input AND the impact is data exposure or unauthorised action - Other shapes: external attachment plus a request for credentials, amount above threshold AND first-time merchant AND mismatched shipping country

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Suggests reduced sensitivity.

Why it fails. No new predicate for reachability or impact.

When it would be right. Never as a substitute for the conjunction.

Proposal. Proposal.

Why it attracts. Keeps important findings.

Why it fails. Severity labels are vague until anchored, so ambiguity migrates.

When it would be right. After per-tier concrete criteria exist, as presentation.

Proposal. Proposal.

Why it attracts. Raises bar.

Why it fails. Confident false positives that share a surface pattern but fail a conjunction clause survive.

When it would be right. As a calibrated routing threshold after criteria are defined.

How the same rule gets re-asked
  • - Sink change from SQL concatenation to user input passed to eval() preserves conjunction structure. - AND versus OR flip is tested as the discriminating mutation. - Origin scope external HTTP versus any untrusted entry point moves correctness based on matching the stated threat model.
R4

Severity prose labels produce run-to-run inconsistency even when wording seems precise

Prompt defines tiers with phrases critical means the code is dangerous, critical means the code could cause system failures or data loss, minor means slightly suboptimal or readability only. At flag time the model maps a pattern to an abstract label by interpreting surrounding context, so identical null-pointer shapes in two files receive different labels without any artifact change. The prose lives in a severity block or CLAUDE.md table and inherits its ambiguity.

Abstract labels are not anchored to observable properties. The model estimates danger using contextual priors such as file length and co-occurring findings, so surrounding tokens bias the estimate. Expansion into longer paragraphs adds more interpretable words rather than checkable tests, so variance persists. Evidence includes a review of 200 CRITICAL flags where 40 percent belong in HIGH, identical missing null check labeled critical in one file and low in another, and 20 percentage points disagreement between analysts on identical batches.

Boundary. Adding prose detail does not cross the boundary. One line and five characteristics per tier behave identically as insufficient. The boundary is crossed only when each tier is bound to concrete code patterns observable in the artifact. A CLAUDE.md table mapping issue types to default severities without code examples remains incorrect because type alone does not determine risk magnitude.

Recurring specifics. - Tiers: critical, high, major, medium, low, minor - Prose fragments: could cause system failures, the code is dangerous, slightly suboptimal, affects readability but not functionality, immediate danger, use your best judgement with impact and urgency - Shapes: missing null check, unsanitised user input in SQL query versus userName versus user_name, database transaction error handling flagged as a bug - Ineffective companions: temperature 0, longer max_tokens, generic reasoning

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Determinism promises identical labels.

Why it fails. Deterministic reproduction of the same ambiguous mapping.

When it would be right. After examples anchor each tier and residual variance is sampling noise.

Proposal. Proposal.

Why it attracts. Quality control.

Why it fails. Same prose definitions yield same indeterminate mapping.

When it would be right. When second pass uses independently anchored criteria or a fresh verifier with examples.

Proposal. Proposal.

Why it attracts. Less surface.

Why it fails. Ambiguity concentrates on the remaining boundary.

When it would be right. Only as product simplification after per-tier criteria are explicit.

How the same rule gets re-asked
  • - Domain change to content moderation, disaster impact, or extraction unclear preserves the rule. - Short to long prose keeps the same failure. - Shape change null pointer to SQL injection keeps the fix unchanged.
R5

Concrete code examples per severity tier create stable, checkable calibration anchors

Replace each prose sentence with labeled code examples pairing a fragment with its correct tier and a one-line rationale. The model classifies by pattern matching against the observable shape of the nearest example.

The code carries the definition; surrounding prose explains the mapping rationale.

example.ts
typescript
// CRITICAL - Unsanitised external input reaches a query sink
const query = `SELECT * FROM users WHERE id = ${user_input}`;
// Rationale: external input concatenated without sanitization, reachable sink

// MAJOR - Missing null check on a service that may return null
function getUser(id: string) {
  const user = cache.get(id); // may be null
  return user.name; // no null guard
}
// Rationale: crash path, observable missing guard

// MINOR - Inconsistent naming within the same module
let userName = "alice";
let user_name = "bob";
// Rationale: readability only, no functional risk

Examples supply an observable standard retrievable in context. Instead of inferring what dangerous means, the model asks whether the candidate looks more like the CRITICAL snippet or the MINOR snippet. Classification becomes nearest-example match, which is stable across runs. Identical patterns converge to the same label across invocations and files without parameter changes.

Boundary. One example per tier is the minimum viable anchor. A single idiosyncratic example per tier may overfit that tier to a narrow shape and still misclassify borderline forms. Evidence favors one worked example per level to five to seven spanning the range, or two to three for the most contested boundary drawn from 80 consensus cases or fifty borderline cases. Providing many CRITICAL examples but no MAJOR or MINOR counterparts leaves novel findings without comparative context.

Recurring specifics. - Shapes: f"SELECT * FROM users WHERE id = {user_input}", userName versus user_name, missing null guards, parameterized versus concatenated queries - Counts: two to three for a failing category, five to seven spanning full range, 200 CRITICAL audit sample, 80 consensus, fifty borderline - Tier identifiers: critical, high, major, medium, low with one example per value; CVSS ranges, exploitability, blast radius for measurable tiers

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Numbers appear precise.

Why it fails. Numbers inherit the same prose calibration problem without anchored examples.

When it would be right. After examples anchor integer boundaries, as secondary ordering.

Proposal. Proposal.

Why it attracts. Withholds low-confidence findings.

Why it fails. Hides misclassifications rather than correcting tier definitions.

When it would be right. After tier definitions are explicit, as routing for borderline tiers.

Proposal. Proposal.

Why it attracts. Emphasis feels diligent.

Why it fails. Names the outcome without a new test.

When it would be right. Never as a replacement for examples.

How the same rule gets re-asked
  • - Domain to regulatory filing anomaly null-rate spike above 5 percent on revenue-lineage is critical versus non-breaking additive schema change is minor with one anomaly payload per tier. - Source data from handcrafted snippets to gold cases from 80 consensus cases or fifty borderline cases treated as strictly stronger. - Count one per tier to five to seven does not change mechanism, only robustness for borderline.
R6

Contrastive few-shot pairs generalize the true boundary to phrasings never explicitly enumerated

When novel phrasings will continue to appear, the prompt adds contrastive pairs holding the surface feature constant across two sides. Each pair contrasts near-identical artifacts differing on the precise feature that flips the label, with a short because naming that feature.

Both sides plus the because line teach generalization beyond any enumerated list.

result.json
json
{
  "pairs": [
    {
      "text": "reclaimed slur used self-referentially by the target",
      "label": "allowed",
      "because": "self-reference, not a directed slur against a protected attribute"
    },
    {
      "text": "same slur token used as a directed attack on the protected group",
      "label": "toxic",
      "because": "directed slur against a protected attribute"
    }
  ],
  "count": "3 to 4 pairs",
  "includes_reasoning": true
}

Enumerated allowlists and prohibited lists are closed sets while language drifts weekly. A model given only the bad side learns slur token present as the trigger and applies it to benign phrasings. Pairs holding the surface token constant force the model to locate the actual decision feature such as directed versus self-referential, quotation for condemnation versus endorsement, or satirical exaggeration versus credible threat. Once exposed, the feature transfers to phrasings never shown. This is the regime where few-shot outperforms additional criteria text: when the rules are already written but the model still treats a surface token as sufficient evidence and must handle never-shown edge cases.

Boundary. Pairs are correct only when the boundary is the blocker. When the prompt never named the positive and negative sets, pairs without defined sets are insufficient. Evidence where the prompt said only be conservative and only report high-confidence vulnerabilities treats the fix as writing explicit criteria defining reportable versus accepted-safe patterns before any pair refinement.

Recurring specifics. - Counts: 3 to 4 contrastive pairs, 4 pairs each with opposite labels and brief reasoning, 4 worked genuine-violation-only examples as the failing control - Shapes: reclaimed slur self-referential versus directed slur, hate speech quoted inside a condemnation versus endorsed, satirical exaggeration versus credible threat, threatening template versus rhetorical hyperbole twin - Signal: policy already enumerates every prohibited category in detail yet flags borderline-but-acceptable at roughly the same rate as genuine violations, suppressed real violations when tightening flag only when highly confident, novel phrasings not spelled out verbatim

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Covers acceptable uses by name.

Why it fails. Still enumerated and cannot anticipate weekly novel rewordings.

When it would be right. When the domain is closed and acceptable uses are enumerable.

Proposal. Proposal.

Why it attracts. Threshold promises containment.

Why it fails. Confidence is uncorrelated with boundary correctness for surface-feature decisions.

When it would be right. After pairs correct the boundary, as a calibrated routing layer for low certainty borderline.

Proposal. Proposal.

Why it attracts. Positive demonstrations.

Why it fails. Reinforces the surface token as trigger.

When it would be right. Only for format imitation.

How the same rule gets re-asked
  • - Domain from toxicity to infrastructure severity, comment contradiction, or invoice unclear preserves contrastive structure. - Count 3 to 4 versus 6 to 8 while restricting to one polarity flips correctness: fewer but contrastive beats many but one-sided. - Allowlist of reclaimed-term and victim-quoted plus regional slang maintains incorrect status as enumerative in a drifting domain.
R7

Single-polarity positive examples do not teach the boundary and reproduce surface-feature errors

Prompt adds few-shot where all examples demonstrate the desired finding shape, each noting which prohibited term or violation category is present, but never shows the near-identical benign counterpart that should not be flagged. The model receives strong reinforcement for feature implies flag without counterevidence that the same feature can be benign.

Typical block is two per high-dismissal category or 6 to 8 examples spanning slur, threat, harassment that are all toxic or violation labeled.

Positive-only examples increase the prior for feature implies flag without modifying feature given context. The surrounding intent that actually determines correctness such as self-reference, quotation, satire, parameterized query, or env-var secret load never appears as a negative example, so the decision surface does not learn to condition on it. Two configurations with similar counts produce opposite outcomes: two per high-dismissal category pairing a flagged snippet with an instruction to down-weight without contrast is treated as mere caution, while two per category contrasting true-positive versus compliant look-alike with reasoning is treated as teaching the boundary.

Boundary. Positive-only is correct when the task is format imitation rather than boundary learning. Evidence on vague feedback such as complex ticket-allocation logic endorses 3 to 4 few-shot examples showing the exact format as the cure for vague feedback, because the problem is format. Distinguish format teaching where one polarity suffices from boundary teaching where it fails.

Recurring specifics. - Counts: eight few-shot, two per high-dismissal category, each pairing a flagged snippet with an instruction to down-weight - Categories: injection and hardcoded-secret accounting for 70 percent of dismissals, slur, threat, harassment, color contrast versus alt text, focus order, ARIA - Surface features that false-positive one-sided: parameterized queries flagged as injection, env-var secret loads flagged as hardcoded credentials, intentionally permissive CORS on public read-only endpoints, text over images and gradients, missing tags on S3 bucket

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Teaches caution.

Why it fails. Without paired benign versus violation demos, cannot learn which patterns look intentional.

When it would be right. Only when restructured into contrastive pairs with per-side reasoning.

Proposal. Proposal.

Why it attracts. Weighting toward genuine feels safer for recall.

Why it fails. Reinforces surface association.

When it would be right. When balanced across the boundary with every acceptable pattern matched.

Proposal. Proposal.

Why it attracts. Gate plus targeted check.

Why it fails. Gate uncorrelated with boundary correctness and double-check lacks concrete contrast.

When it would be right. Never as a replacement for contrastive examples when generalizing to never-shown phrasings.

How the same rule gets re-asked
  • - Single-polarity set two per category to several weighted toward color contrast preserves incorrect status. - Count four to twelve does not rescue a contrast-deficient set. - Instruction down-weight to re-derive the contrast judgment keeps incorrect status when no benign counterpart is present.
R8

Trust collapses system-wide when one noisy category bleeds credibility from accurate categories

A pipeline emits findings tagged by category such as security, correctness, performance, style, naming, documentation or by family such as injection and hardcoded-secret or by rule such as color contrast, alt text, focus order. Per-category precision differs widely, for example accurate near 8 percent false positives while noisy reach 48, 52, or 70 percent. Developers consume the stream as a single trust object and apply a global heuristic ignore the bot rather than per-tag reliability. Once the noisy tag delivers a large fraction of dismissed items, consumers skip or mute the entire stream and accurate findings have zero effective recall.

Review bandwidth is finite and attention is categorical only when the label predicts utility. When style alone produces roughly 70 percent of all findings dismissed as acceptable local patterns, the prior this bot comment is noise overwhelms per-finding signal. Even a global volume trim of about a fifth does not restore engagement when the remaining mix still contains frequent bad tags, because the prior was built from frequency of bad experiences rather than exact ratio. The spillover is treated as the primary harm of false positives.

Boundary. Spillover holds even when aggregate false positives look moderate. Evidence expects category-weighted harm, not aggregate: 33 percent overall with three quarters from a single rule still produces cross-category distrust, and lowering aggregate to 26 percent via be conservative and only report high-confidence violations does not restore trust. The rule flips only when per-category precision is genuinely uniform or when the noisy tag is isolated from delivery.

Recurring specifics. - Aggregates: style and naming 52 percent, documentation 48 percent, performance 18 percent, security and correctness 8 percent, 33 and 40 percent overall with one category majority, style violation dismissed unread 95 percent while security risk read 85 percent before collapse, null pointer 60 percent alongside SQL injection 95 percent accurate, unused variable 70 percent - Noisy tags: unused variable, style violation, naming conventions, documentation mismatch, potential null pointer, color contrast, dual-licensed packages flagged as conflicts, vendored test fixtures flagged as unlicensed, parameterized queries under injection - Consumer signals: ignore all bot comments, skim every high ticket, mute the bot, dismiss without reading, trust erosion

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Transparency for selective attention.

Why it fails. Confidence is poorly calibrated and reviewers apply global ignore before evaluating any score.

When it would be right. After debiasing and calibration, per-finding scores can help with ordering within a trusted stream.

Proposal. Proposal.

Why it attracts. Eventual correctness without disabling.

Why it fails. Developers continue to pay the attention cost of the noisy stream and keep the global ignore heuristic.

When it would be right. After the noisy category is isolated from delivery, offline improvement can proceed.

Proposal. Proposal.

Why it attracts. Reduces total comment volume.

Why it fails. Suppresses accurate categories along with noisy.

When it would be right. Never when noise is concentrated, only when precision is uniformly poor.

How the same rule gets re-asked
  • - Domain to moderation, license scanning, or WCAG preserves same spillover because the consumer's global heuristic is artifact-agnostic. - Noisy tag unused variable to style and documentation to color contrast to injection keeps the same repair. - Share of false positives from two thirds to three quarters or roughly 70 percent does not change answer; dropping concentration below threshold removes spillover condition.
R9

Temporary disable and quarantine of the high false-positive category is the correct first bleed-stop

When triage confirms one or two categories dominate dismissals and spillover is underway, the first intervention removes those categories from delivered output. They continue offline against a validation set, are iterated with explicit criteria and examples, and re-enabled only once per-category precision is validated. Remaining categories continue at existing precision and the consumer immediately experiences a stream dominated by accurate findings.

Naming variants all describe delivery quarantine: temporarily disable the documentation mismatch category, temporarily disable the naming convention category, temporarily disable high false-positive categories style, naming, and documentation and run only high-precision categories, suppress the style category from posted output for now, stop surfacing the three high-false-positive categories while those rules are validated, downgrade to a non-blocking review channel.

Quarantine attacks the consumer prior directly. Once noisy findings are no longer delivered, the every other finding is noise experience ends on the next pull request or report. Trust in accurate categories returns without change to those categories, without added infrastructure, and without suppressing genuine findings in the accurate set. Offline channel retains coverage for later refinement, so the intervention is proportionate: stop the bleeding where it bleeds while keeping useful signal.

Boundary. Incorrect when false positives are uniformly distributed rather than concentrated. If every category is noisy at similar rates, there is no clean high-precision remainder to preserve, so global criteria work with priority on highest blast radius is expected. Also incorrect when the noisy findings are a mandatory regulatory gate that cannot be paused. In that narrow compliance case the correct approach is add explicit criteria and concrete examples for that category while routing low-confidence findings to mandatory human review.

Recurring specifics. - Phrasings: temporarily disable, temporarily quarantine, temporarily downgrade to a non-blocking review channel, suppress from posted output for now, stop surfacing while those rules are validated, keep only high-precision categories while improving prompts offline - Success framing: without touching the performance category or adding infrastructure, without suppressing genuine bugs and security findings, while it works on making the noisy category usable, so trust is restored without silencing the categories - Anti-phrasings: increase conservatism globally, add second model pass without better criteria, show only high-confidence findings, apply uniform strictness reduction

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Simple single knob.

Why it fails. Category-blind, filters accurate findings along with noisy.

When it would be right. After quarantine and explicit criteria restore a trusted stream, as presentation.

Proposal. Proposal.

Why it attracts. Filters noisy hardest.

Why it fails. Thresholds tune around the symptom without defining what counts, self-rated thresholds are uncalibrated.

When it would be right. Never as primary fix when root cause is missing explicit criteria.

Proposal. Proposal.

Why it attracts. Capability improvement.

Why it fails. Precision failures are prompt-design failures, not capability.

When it would be right. When criteria are explicit and failures are genuine reasoning limits.

How the same rule gets re-asked
  • - Quarantined category documentation mismatch to unused-variable warnings to performance suggestions to color contrast to dual-licensed conflicts preserves correct action and rationale. - Presentation temporarily disable to downgrade to non-blocking review channel preserves correctness because delivery is isolated from primary attention. - Paired diagnostic add detected_pattern per dismissal alongside quarantine keeps correct status, while field alone without quarantine is insufficient.
R10

Post-fix re-enablement requires held-out validation against known findings before broad rollout

After rewriting a noisy category with explicit criteria and examples, run the rewritten prompt against a held-out set of past pull requests, filings, or documents whose ground truth is known, often past pull requests with known findings or a labeled reference set. Measure false-positive rate and per-field or per-segment accuracy on that set. Only when the rate drops to an acceptable level re-enable for everyone. The set is not used during iteration, so it serves as an unbiased check.

Reading the rewritten criteria cannot prove that false positives will fall on real inputs. Criteria can read sound while misfiring on shapes not considered during rewrite such as string concatenation with no external input, reclaimed slur usage, or vendored test fixtures. Empirical validation on a known set provides evidence that the criteria improve measured precision on the failure shapes that drove dismissals.

Boundary. When validation on a representative held-out set has already passed, immediate re-enablement is correct. Re-enable immediately after the new criteria are added because the explicit rules themselves demonstrate improved precision is incorrect because no empirical check was performed. A reporter-only pilot or single senior engineer's small manual check is insufficient due to limited coverage.

Recurring specifics. - Artifacts: held-out set of past pull requests with known findings, labeled validation set, labeled reference set per filing type, known-correct reference set, 80 consensus cases - Metric: false positive rate has dropped to an acceptable level before re-enabling for everyone, segment accuracy meets target before auto-release - Anti-patterns: re-enable only for the reporter pilot, single senior engineer small-set check, re-enable immediately after new criteria

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Limited blast radius.

Why it fails. Reporter shapes may not represent full production distribution.

When it would be right. As brief smoke test after held-out validation has passed.

Proposal. Proposal.

Why it attracts. Expert eye.

Why it fails. Small seen set overfits to complaint examples.

When it would be right. As qualitative read after quantitative held-out validation, never as sole gate.

Proposal. Proposal.

Why it attracts. Concrete criteria feel obviously better.

Why it fails. Plausibility is not measurement.

When it would be right. Non-production demo, not delivery pipeline.

How the same rule gets re-asked
  • - Artifact to regulatory filings, dependency manifests, or SOW clauses preserves held-out validation shape. - Metric to per-segment accuracy preserves rule as validation must be per segment. - Anti-pattern reporter-only pilot to single senior engineer small-set check preserves distractor status because both are non-representative checks.
R11

Self-reported confidence fails as a filtering gate before criteria exist

Model emits confidence and the pipeline suppresses below 0.85, 0.90, or 8. The score is self-assessment from the same reasoning that produced the finding, not an independent measurement.

Models score many false positives high, such as parameterized queries flagged as injection, and some genuine findings low, so raising the bar to 0.99 still leaks confident false positives while hiding true findings. Evidence shows high closed as not a vulnerability with high scores, 0.94 to 0.97 fields later corrected while 0.80 items are correct, and 47 to 41 percent persistence after adding avoid false positives.

Boundary. Fails as a pre-criteria filter. Becomes useful only after explicit criteria define the category and scores are calibrated against a labeled set, where the calibrated score routes low-certainty items rather than suppressing them.

Recurring specifics. - Thresholds: only above 0.90, filter below 8, confidence exceeds 0.7, only when highly confident - Numbers: 40 percent of CRITICAL belonging in HIGH, 12 percent high-confidence semantic errors, almost nothing reaches the editor - Fields: confidence 0.0 to 1.0, confidence 45, category too coarse, is_false_positive circular

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Stricter.

Why it fails. Confident false positives clear even the higher bar.

When it would be right. After per-segment calibration where threshold reflects measured precision.

Proposal. Proposal.

Why it attracts. Scale aid.

Why it fails. Miscalibration is scale-invariant.

When it would be right. Presentation of an already calibrated signal.

Proposal. Proposal.

Why it attracts. Structured.

Why it fails. Same uncalibrated self-report machine-readable.

When it would be right. As routing after calibration.

How the same rule gets re-asked
  • - Scale 1 to 10 to 0 to 100 preserves incorrect status. - Global versus per-category self-rated cutoff both fail due to uncalibrated signal. - Threshold before second pass versus before delivery both fail when signal remains uncalibrated.
R12

Confidence belongs in routing after criteria are defined, not in filtering

After explicit criteria and calibration, the pipeline routes rather than suppresses. High-confidence in validated segments may proceed, low-confidence or contradictory goes to human or independent verifier, and nothing is silently dropped for scoring low.

config.yaml
yaml
routing:
  high_confidence:
    requires: "calibrated score >= threshold AND segment validated"
    action: "auto-release"
  low_confidence:
    action: "route to human review"
  contradictory_evidence:
    action: "route to human regardless of score"

Filtering discards information needed for safety, while routing preserves it under scrutiny. With calibration, 20 percent routed can contain 60 percent of errors, and sampling surfaces confident-but-wrong items a filter would publish.

Boundary. Fails when score is uncalibrated or global accuracy masks segment weakness such as item not as described 71 percent under 95 percent aggregate. Boundary is calibrated per field and per type before auto-release.

Recurring specifics. - Intents: escalate to human, route contradictory or ambiguous, second independent instance - Success: field-level per return-reason calibrated, auto-approve only validated, route low-confidence and contradictory to humans - Failure: only auto-approve when highly confident on uncalibrated model changes neither weak category nor contradictory handling

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Reduces load.

Why it fails. Confident errors remain and correct uncertain items are lost.

When it would be right. After calibration as routing not suppression.

Proposal. Proposal.

Why it attracts. Second pass verification.

Why it fails. Hand-chosen threshold uncalibrated.

When it would be right. When second instance uses calibrated scores.

Proposal. Proposal.

Why it attracts. Simple.

Why it fails. Document averaging masks weak field.

When it would be right. Only when per-segment validation passed.

How the same rule gets re-asked
  • - Target human versus second independent instance correct only when second lacks first reasoning and uses calibrated scores. - Trigger low confidence versus contradictory evidence both route. - Granularity field-level calibrated versus single whole-request flips correctness due to masking.
R13

Calibration against a labeled validation set per field and per segment makes confidence usable

Build a labeled validation set per document, filing, or return reason such as invoice_total versus indemnification clause or comparison table versus appendix. Map reported confidence to measured accuracy per field and choose thresholds where the fitted curve crosses the target.

example.ts
typescript
interface CalibratedSegment {
  segment: "invoice_total" | "indemnification_clause" | "comparison_table";
  thresholds: { autoRelease: 0.87, review: 0.65 };
  measured: { at_0_95: 0.94, at_0_85: 0.88, at_0_75: 0.71 };
  source: "labeled_validation_set_1000_docs";
}

Raw 0.95 may be 71 accuracy in one segment and 94 in another. Without measuring, threshold choice is guesswork. Evidence shows 99 percent on party name versus 71 on indemnification under same report, so per-segment fit is required.

Boundary. Global calibration insufficient when errors concentrate. 96.8 percent overall with pharmaceutical filings errors hidden masks weakness. Lowering threshold to match 5 percent capacity when 35 percent score below 0.7 is backwards: threshold is a quality signal, not a volume dial.

Recurring specifics. - Sets: labeled validation set, labeled reference set per filing type, known-correct reference set, 80 consensus cases, fifty borderline cases - Axes: field type, filing type, comparison tables, appendices, scanned handwritten forms, return reason defective versus item not as described - Numbers: 0.94 to 0.97 while wrong, 0.85 versus 0.65, 71 weak under 95 aggregate, 35 flagged versus 5 capacity

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Throttles volume.

Why it fails. Pulls medium-confidence while confident errors stay outside queue.

When it would be right. Only after per-segment measurement.

Proposal. Proposal.

Why it attracts. Reacts to weak segment.

Why it fails. Hand-picked remains uncalibrated.

When it would be right. After per-field calibration for that segment.

Proposal. Proposal.

Why it attracts. Fewer scores.

Why it fails. Averaging hides weak field.

When it would be right. Whole-document disposition with uniform per-field accuracy.

How the same rule gets re-asked
  • - Size 1000 to fifty preserves correctness because source remains human-adjudicated truth. - Action route to human to second independent instance correct only with same per-segment calibration. - Granularity per-field to per-return-reason category correct as category is the error-concentrated segment.
R14

Stratified random sampling of high-confidence outputs catches confident-but-wrong errors and measures improvement

Draw a fixed fraction of high-confidence extractions weekly, stratified by document type, field type, or confidence band. Check the sample, compute per-segment error rate, and validate fixes such as few-shot examples for comparison tables by remeasuring next cycle, forming measure, discover, fix, remeasure.

result.json
json
{
  "sampling": "fixed percentage of high-confidence extractions weekly",
  "dimensions": ["document type", "extraction type", "confidence band 85-90, 90-95, 95-100"],
  "strata": ["product pages", "comparison tables", "appendix-heavy docs"]
}

Confident-but-wrong errors are invisible to filters and include 30 minutes in quantity or competitor specs from a comparison table. They are deterministic, so re-extraction returns the same wrong value with zero variance. Stratification guarantees coverage of rare but error-prone types at 5 percent volume that pure random would miss, and yields a pipeline-wide estimate trackable over time such as 12 to 9 to 6 percent.

Boundary. Lowering the threshold, heuristic flagging of comparison tables or appendices, and re-extraction verification each fail one requirement: they may catch some known errors but do not measure whether improvement reduces the overall high-confidence error rate. Full manual review is correct only when capacity truly allows 100 percent weekly.

Recurring specifics. - Sampling: fixed percentage weekly, stratified across document types, 5 percent of ALL high-confidence, weekly sample - Sources: comparison tables, appendices, footnotes misattributed, unusual layouts, semantic errors that pass JSON schema such as 30 minutes, two-column legal format at 92 percent - Capacity: review 20 percent, handle only 5 while 35 flagged, review only 15 weekly, 12 percent of high-confidence also contain errors

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. More review.

Why it fails. Expands wrong direction, keeping high-confidence errors outside queue.

When it would be right. Never when errors live in the high-confidence bucket.

Proposal. Proposal.

Why it attracts. Two runs should expose inconsistency.

Why it fails. Structural errors have zero variance.

When it would be right. When errors are stochastic.

Proposal. Proposal.

Why it attracts. Targets audit sources.

Why it fails. Closed to new patterns and cannot estimate pipeline-wide rate.

When it would be right. As supplement to sampling, never as measurement.

How the same rule gets re-asked
  • - Axis document type versus extraction type versus confidence band preserves correctness while measurement remains pipeline-wide. - weekly versus quarterly preserves loop but weekly fixed fraction yields faster feedback. - stratified versus pure random flips correctness for rare types.
R15

Global confidence gates applied uniformly to every category suppress accurate categories along with noisy ones

A single self-rated confidence cutoff such as 0.85 or 9 of 10 is applied identically to bug, security, performance, and style or to every critical through low. Every finding below the single bar is withheld regardless of category.

When style alone produces roughly 70 percent of all findings, a uniform bar filters genuine security and correctness at 8 percent false positives alongside noisy ones, lowering recall where precision was already high. Evidence frames this as crushes the accurate categories too.

Boundary. Acceptable only after category-scoped quarantine and explicit criteria restore a trusted stream, and even then only as presentation within that trusted stream. Global gate while noisy category remains in delivery is consistently incorrect.

Recurring specifics. - Values: 0.85, 90 percent, 9 of 10, high-confidence only - Sets: bug, security, performance, style under one be conservative line, security and correctness 8 versus mismatch 40 where trust bleeds - Signals: tune cutoff each sprint against dismissed rate still incorrect when classification logic unchanged

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Simple knob.

Why it fails. Category-blind.

When it would be right. After debiasing as a throttle.

Proposal. Proposal.

Why it attracts. Volume control.

Why it fails. Quantity for quality.

When it would be right. Only when stream already debiased.

Proposal. Proposal.

Why it attracts. Average promise.

Why it fails. Hides segment collapse.

When it would be right. When noise is uniformly distributed.

How the same rule gets re-asked
  • - Numeric 0.85 to 0.90 preserves critique. - Domain to license violation versus clear preserves incorrect status. - Adding tune each sprint keeps incorrect status.
R16

The `detected_pattern` field is the finest-grained diagnostic signal for false-positive root cause analysis

Extend output with detected_pattern recording the trigger construct such as string concatenation in database query, user input passed to eval(), or list comprehension versus generator expression. Aggregate dismissals per pattern to compute 73 percent on concatenation versus 8 percent on eval and target the dominant pattern with explicit criteria.

result.json
json
{
  "file": "src/auth/login.py",
  "line": 47,
  "description": "Potential SQL injection vulnerability",
  "detected_pattern": "string concatenation in database query"
}

Ask which specific constructs are being flagged incorrectly. category such as security flagged 500 times is too coarse, confidence tracks certainty not shape, and is_false_positive circularly asks the same classifier to judge itself. Only detected_pattern records the trigger shape at capture time and makes per-pattern aggregation actionable.

Boundary. Diagnostic, not curative. Add detected_pattern while suppressing style from posted output is correct only because the field accompanies quarantine. Confidence or category alone remains incorrect as sole diagnostic. Correct only when the scenario explicitly asks for systematic construct-level analysis.

Recurring specifics. - Values: string concatenation in database query, user input passed to eval(), parameterized query safe counterpart, list comprehension, generator expression, env-var secret load - Identifiers: detected_pattern, category, confidence, is_false_positive, file, line - Anti-fields: is_false_positive boolean circular, category grouping coarse, confidence 0 to 100 wrong signal

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Prioritizes by certainty.

Why it fails. Cannot name construct to rewrite.

When it would be right. After pattern identified, confidence can prioritize within that shape.

Proposal. Proposal.

Why it attracts. Familiar.

Why it fails. Order-of-magnitude coarser.

When it would be right. Supplement for reporting.

Proposal. Proposal.

Why it attracts. Automates ground truth.

Why it fails. Same classifier evaluates itself.

When it would be right. Only when populated by human review after the fact.

How the same rule gets re-asked
  • - Downstream prompt refinement to monthly retraining preserves diagnostic value but shifts interval. - Pattern concatenation to eval preserves granularity test. - detected_pattern to confidence or category flips correctness regardless of domain.
R17

Per-category self-rated thresholds tune around the symptom while leaving misclassification logic intact

Add per-subcategory confidence on input handling, authentication, data exposure or separate bars for bug, security, performance, style calibrated to historical dismissal rate, posting only findings clearing their bar. Bars are self-rated not learned from labeled truth.

Each bar still gates an underspecified category, so parameterized query versus concatenated unsanitized input and env-var secret load versus hardcoded secret continue to produce false positives subset-filtered rather than corrected. Splitting security into subcategories with separate self-rated bars still delegates the boundary to self-assessment and moves dismissal 47 to 41 without changing which families misfire.

Boundary. Per-category bars become legitimate only after explicit per-category criteria and per-segment calibration exist. Per-category calibrated routing correct after criteria, per-category self-rated is distractor before criteria.

Recurring specifics. - Phrasings: per-subcategory confidence threshold, separate threshold to each subcategory, double-check each injection and hardcoded-secret finding - Splits: input handling, authentication, data exposure, injection and hardcoded-secret accounting for 70 percent - Failure: four-tier severity labels and the rest unchanged still incorrect

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Granular.

Why it fails. Each sub-bar self-assessed on vague definition.

When it would be right. After each subcategory has explicit reportable versus safe patterns and a calibrated bar.

Proposal. Proposal.

Why it attracts. Matches threshold to noise.

Why it fails. Tuned to self-report noise not correctness.

When it would be right. When historical measurement is correctness against labeled set.

Proposal. Proposal.

Why it attracts. Blends ideas.

Why it fails. Gate uncalibrated and few-shot must be contrastive.

When it would be right. When few-shot is contrastive and gate calibrated.

How the same rule gets re-asked
  • - Gate 0.85 on all to 0.9 on injection and hardcoded-secret preserves incorrect status. - Self-rated gate with positive-only few-shot keeps incorrect status. - Noisy families injection to documentation mismatch to style preserve same distractor shape.
R18

Repeating or intensifying a vague directive does not repair precision at any emphasis level

After be conservative fails, team doubles it with extremely conservative, repeats twice, or appends avoid false positives. No new predicate is added, so same ambiguous prior is sampled under higher pressure.

Interpretation is the bottleneck, not pressure. Higher pressure trims about a fifth without changing which categories misfire, so high false-positive security, style violation, documentation mismatch persistence remains and trust does not recover.

Boundary. Useful only when repetition adds new content such as explicit categorical criteria or concrete examples. Replace the vague line with explicit criteria correct, reinforce caution by repeating twice distractor.

Recurring specifics. - Emphasis: extremely conservative, use your best judgement, report only actionable concerns, avoid false positives, be more conservative, carefully flag vulnerabilities - Failure: same false positive rate as before, false positives remain high across multiple categories, still flags standard rounding and rate limiting - Ineffective companions: rate confidence 1 to 10 and filter below 8, lower temperature, increase reasoning effort while keeping vague

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Emphasis feels proportional.

Why it fails. No missing criterion.

When it would be right. Only when rewritten into explicit criteria.

Proposal. Proposal.

Why it attracts. More reminders.

Why it fails. Each maps to same ambiguous prior.

When it would be right. Never as substitute for defining what careful checks.

Proposal. Proposal.

Why it attracts. States outcome.

Why it fails. Outcome without behavioral definition leaves mapping missing.

When it would be right. After explicit criteria define the test.

How the same rule gets re-asked
  • - extremely to 100 percent certain preserves distractor status. - Intensifier plus self-rated confidence filter stays incorrect without new criteria. - Task comment accuracy to tag checks preserves incorrect status for intensifier-only fix.
R19

Same-thread self-review and rationale-fed verification are anchored and fail to correct the original error

The pipeline feeds the first pass finding plus its reasoning back into the same conversation and asks the model to re-examine or confirm it, often described as re-test each critical finding against the false-positive list before reaffirming it in the same thread or route to a second instance that is seeded with the original review's reasoning. The verifier inherits the framing, so reaffirmation stays near 92 or 88 percent even though a third are non-issues.

Language models anchor on provided reasoning and defend it rather than independently re-deriving the decision. The same-thread confirmation is self-review in disguise. Even a second instance fed the prior rationale is instructed to uphold or overturn that rationale, framing the task as defense rather than fresh judgment.

Boundary. Verification helps only when the verifier does not know the original reasoning and evaluates the artifact de novo. Evidence marks same-session or rationale-fed review as the failure shape and fresh-context independent instance as the success shape. The rule flips only when the second pass is independent and grounded in explicit criteria, not when it is merely a second turn in the same prompt.

Recurring specifics. - Phrasings: re-derive the contrast judgment, re-derive before including, same-thread confirmation pass, seeded with clause-level reasoning and severity rationale, weight every schedule equally and scrutinize each critical finding skeptically - Numbers: miss rate 4 percent on SOWs under 20 clauses to 26 percent over 50, 21 and 88 percent after adding weight equally, 92 percent reaffirmation with a third non-issues, standard carve-outs and mutual caps misread as one-sided exposure - Artifacts: SOWs exceeding 50 clauses, later schedules and appendices left unflagged, 150 endpoints across payment, internal, public Data

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Looks like self-correction.

Why it fails. Same context retains the prior judgment.

When it would be right. When the second pass is a fresh conversation with only the clause and relevant rule, without the first reasoning.

Proposal. Proposal.

Why it attracts. Second model feels independent.

Why it fails. Seeded reasoning anchors the verdict.

When it would be right. When the second instance receives only clause text plus relevant playbook rule and asserted severity without reasoning.

Proposal. Proposal.

Why it attracts. List appears to catch known shapes.

Why it fails. Same-thread anchoring persists.

When it would be right. With per-clause sharding plus integration pass and fresh-context verification.

How the same rule gets re-asked
  • - Switching domain from SOW clauses to PR severity or extraction confidence preserves anchored failure. - Changing second-pass instruction to re-compute metric deltas and return discrepancy preserves failure when severity criteria remain vague, because validation retries only address format errors, not judgment criteria variance. - Increasing retry count from one to three keeps incorrect status when the verifier remains rationale-fed.
R20

Independent fresh-context verification without the first pass reasoning is required for trustworthy confirmation

Split the work so each verifier sees only the artifact and the explicit rule, not the prior reasoning. For SOWs, split into per-clause independent API calls, each receiving one clause plus the playbook and aggregate findings, then run a separate cross-clause integration pass. Route each critical finding to a second Claude instance in a fresh conversation that receives only the clause text, the relevant playbook rule, and the asserted severity without the first pass reasoning and independently judges whether the clause is genuine.

Fresh context removes anchoring and forces the verifier to re-derive the decision from explicit criteria rather than defend a prior. Cross-clause integration catches exposure that emerges from interactions between schedules, which per-clause calls alone would miss, while per-clause sharding addresses position burial that a single long prompt would suffer.

Boundary. Fresh-context verification is unnecessary when the original decision already derives from concrete criteria and examples and residual errors are stochastic. It is required when the original reasoning is known to be anchored and the verification question is whether the finding is genuinely critical. The boundary is independence: any shared reasoning channel reintroduces anchoring.

Recurring specifics. - Sharding: per-clause independent passes plus a separate cross-clause integration pass - Verification payload: only the clause text, the relevant playbook rule, and the asserted severity, without the first pass reasoning - Anti-payload: seeded with original review's clause-level reasoning and severity rationale and asked to either uphold or overturn - Domains where this applies: SOWs over 50 clauses, 150 API reference pages, regulatory filings

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Per-clause plus second opinion.

Why it fails. Seeding reintroduces anchoring.

When it would be right. Never for trustworthy confirmation; use fresh context.

Proposal. Proposal.

Why it attracts. Instruction appears to equalize attention.

Why it fails. Position burial persists despite instruction.

When it would be right. Only after structural sharding addresses attention limits.

Proposal. Proposal.

Why it attracts. Iterative self-check.

Why it fails. Same context self-review.

When it would be right. When retries address format or structural validation, not judgment criteria variance.

How the same rule gets re-asked
  • - Artifact size from SOW 50 clauses to 150 endpoints or 180-file Go payments migration preserves sharding plus fresh verification shape. - Integration pass described as cross-clause versus cross-schedule preserves correctness as long as interactions spanning sections are checked. - Payload change from clause plus playbook rule to anomaly payload plus severity criteria preserves rule when independence is maintained.
R21

Long-prompt position effects bury later clauses and demand per-clause sharding plus integration pass

A single prompt ingests the entire redlined SOW plus the playbook, 150 endpoints across three categories, or 180 files in one session. Coverage of later sections degrades: for SOWs miss rate climbs from 4 percent under 20 clauses to 26 percent over 50, with later schedules and appendices disproportionately missed. Even a directive to weight every schedule equally cuts the miss rate only partially, from 26 to 21 percent. The model exhibits attention burial on long inputs.

Attention over long contexts is not uniform, so later tokens are diluted when uniform scrutiny is required. Equal-weight instructions do not change the distribution; sharding restores it by giving each clause equal length.

Boundary. Sharding is required when input length exceeds the model's effective uniform-scrutiny window and the task demands equal coverage. It is not required when inputs are naturally short or when the task is intentionally focused on early sections. The nearby opposite case is stratified sampling or threshold tuning, which addresses reviewer capacity but not position burial.

Recurring specifics. - Lengths: SOWs under 20 versus over 50 clauses, 150 endpoints, 180-file Go migration, 50 files in one session - Miss rates: 4 to 26 percent, 21 percent after equal-weight instruction, 92 percent reaffirmation persisting - Fixes: per-clause independent API calls plus cross-clause integration pass, fresh-context verification - Anti-fix: system-prompt section enumerating ten false-positive patterns plus allocate equal scrutiny instruction

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Simple instruction.

Why it fails. Does not overcome attention burial.

When it would be right. Only after sharding gives each schedule independent context.

Proposal. Proposal.

Why it attracts. List appears comprehensive.

Why it fails. Same long prompt with same burial.

When it would be right. With per-clause sharding and fresh verification.

Proposal. Proposal.

Why it attracts. Second look.

Why it fails. Same context self-review.

When it would be right. With structural sharding plus independent verifier.

How the same rule gets re-asked
  • - Length 50 clauses to 150 endpoints to 180 files preserves position rule. - Instruction weight every schedule equally to emphasize consistent severity assignment both fail as single-prompt fixes. - Shard per clause to per file to per endpoint preserves correctness.
R22

Role framing is one component inside the system prompt alongside task, format, and constraints, not a replacement

The system prompt bundles several components: role assignment such as you are a helpful and professional persona or security expert, task instructions, output format, and constraints or boundaries. Role prompting defines the persona within that bundle rather than replacing other components.

A role alone does not specify what to do, what output shape to produce, or what not to do. Evidence marks the role entirely replaces the system prompt as incorrect and the role is defined as one component within the system prompt, alongside task instructions, output format, and constraints as correct.

Boundary. Role in the user message rather than the system prompt is marked incorrect because user messages contain per-request input while system prompt establishes persistent behavior. Role and system prompt are not alternatives; they are designed to work together.

Recurring specifics. - Roles: helpful and professional persona, security expert, cautious financial analyst - System bundle: role assignment fits relative to other instructions, task handling returns and refunds, output format expectations, establish boundaries - Anti-pluralities: role entirely replaces system prompt, cannot be used together, role goes in the user message

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Persona feels sufficient.

Why it fails. Missing task, format, constraints.

When it would be right. Never for a production agent with bounded behavior.

Proposal. Proposal.

Why it attracts. Appears as alternative approaches.

Why it fails. Designed to work together.

When it would be right. Never.

Proposal. Proposal.

Why it attracts. Per-request flexibility.

Why it fails. Loses persistence.

When it would be right. Only for per-turn persona overrides, not baseline behavior.

How the same rule gets re-asked
  • - Role helpful and professional to security expert to cautious financial analyst preserves placement rule. - Adding task handling returns and refunds versus reviewing infrastructure as code preserves same bundling requirement.
R23

Ordering rule: explicit criteria first, confidence-based routing second, never skip the first step

The hierarchy is explicit criteria first, confidence-based routing second. Attempting confidence threshold as the primary gate before defining criteria is marked as skipping the required first step. Confidence routing belongs in a second layer after criteria define what counts.

Without explicit report-versus-skip definitions, confidence has no grounded category to route. Routing low-confidence to human when low-confidence is uncalibrated routes the wrong population while genuine findings are filtered by the wrong logic. Evidence explicitly states confidence scores earn their keep in routing as covered in Task 4.6 but are no substitute for explicit criteria that define what counts.

Boundary. After criteria and calibration, the ordering flips to allow confidence routing for efficiency. Add explicit escalation criteria with few-shot versus self-report confidence is the comparison where the former is the proportionate first response. The rule fails only when a compliance mandate forces immediate coverage without quarantine, where routing must still be calibrated rather than hand-chosen.

Recurring specifics. - Hierarchy phrasing: explicit criteria first, confidence-based routing second. Never skip the first step. - First-step examples: Flag comments only when claimed behavior contradicts actual code, Replace vague judgement with checklist of concrete categorical criteria - Second-step examples: self-report confidence alongside each finding to enable calibrated review routing, field-level confidence calibrated against labeled validation set

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Sounds like good engineering.

Why it fails. Poorly calibrated and lacks category definition.

When it would be right. After explicit criteria define the categories.

Proposal. Proposal.

Why it attracts. Single threshold simplicity.

Why it fails. Skips first step.

When it would be right. Only after first step validates per-category precision.

How the same rule gets re-asked
  • - Task comment accuracy to payment dispute to compliance suspicious preserves hierarchy. - Confidence target auto-remove only high-confidence violations correct only when paired with calibrated routing and human review for uncertain cases.
R24

Positive instruction describing the desired action outperforms negative prohibition of the undesired action

Instead of NEVER include the original content in your explanation, the prompt replaces the negative instruction with a positive one describing the desired behavior: Describe flagged content by violation category, pattern type, and severity level only. Positive framing gives the model an explicit action to take rather than an absence to maintain.

Negative instructions define what not to do without defining what to do instead, leaving the model to choose a replacement behavior that often preserves the undesired pattern under different wording. Positive instructions constrain the output space to a specific allowed set. Evidence marks strengthen the negative: You MUST NEVER, under ANY CIRCUMSTANCES, include original content and add examples showing what not to include as reinforcing the undesired pattern, and post-processing filter to remove quoted content as a brittle workaround.

Boundary. Positive framing fails when the allowed set is itself vague such as describe appropriately. It succeeds only when it names the exact replacement categories. Brief negative examples risk reinforcing the undesired pattern, while positive examples of correct behavior are effective.

Recurring specifics. - Negative phrasing that fails: NEVER include, MUST NEVER under ANY CIRCUMSTANCES, add examples showing what not to include, post-processing filter to remove quoted content - Positive replacement: Describe flagged content by violation category, pattern type, and severity level only - Anti-pairs: negative instruction versus positive instruction plus concrete behavioral description

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Emphasis feels proportional.

Why it fails. Intensifies the same negative instruction that already proved ineffective.

When it would be right. Never when the undesired behavior is verbatim reproduction.

Proposal. Proposal.

Why it attracts. Shows what to avoid.

Why it fails. Risks reinforcing the undesired pattern.

When it would be right. Only when paired with positive examples of correct behavior.

Proposal. Proposal.

Why it attracts. Mechanical removal.

Why it fails. Brittle and fails on edge cases.

When it would be right. As defense in depth after positive instruction, not as the first fix.

How the same rule gets re-asked
  • - Output flagged content to finding severity to extraction result all preserve positive versus negative framing effect. - Strategy filter low-severity versus group MEDIUM and skip LOW contrast preserves the same positive framing advantage.
R25

Relative per-PR severity ranking guarantees cross-PR and cross-run inconsistency by design

Prompt instructs rate each issue's severity relative to the other issues in the same PR, so the most severe is always critical or mark the worst as critical and scale the rest down. Severity is assigned by intra-PR ordering rather than by absolute properties of the finding.

Relative ranking makes severity a function of co-occurring findings rather than of the artifact. The same null-pointer risk may be critical in a PR with only minor style nits and medium in a PR with a genuine security flaw, guaranteeing cross-PR inconsistency even when within-PR ordering looks tidy. Evidence marks this as guarantees cross-PR inconsistency by design while a CLAUDE.md table mapping issue types to default severities is too coarse and explain reasoning for manual recalibration documents inconsistency without fixing it.

Boundary. Relative ranking would be correct only if the product requirement explicitly demanded a per-PR top-N critical selection rather than absolute risk communication to developers. No tested scenario carries that requirement. The correct shape is include explicit severity criteria with concrete code examples for each severity level so ratings derive from the finding's characteristics.

Recurring specifics. - Rankings: relative to the other issues in the same PR, most severe is always critical, mark the worst as critical and scale the rest down - Anti-patterns: Add a CLAUDE.md table mapping issue types to default severities, have Claude explain its reasoning for each severity so reviewers can manually recalibrate - Domains: identical null-pointer risks rated critical in one PR and medium in another, similar issues like null pointer risks rated inconsistently

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Ensures a critical exists per PR.

Why it fails. Inconsistency by design.

When it would be right. Only when product explicitly requires per-PR relative triage, which no tested scenario does.

Proposal. Proposal.

Why it attracts. Absolute mapping.

Why it fails. Too coarse, same type manifests at different risk levels.

When it would be right. Never alone; needs concrete per-severity code examples.

Proposal. Proposal.

Why it attracts. Auditability.

Why it fails. Documents inconsistency without fixing criteria.

When it would be right. After explicit criteria, as supplemental trace.

How the same rule gets re-asked
  • - Phrasing most severe is always critical to mark worst as critical and scale the rest down preserves same distractor status. - Artifact null-pointer risk to missing null check preserves rule.
R26

Field-level confidence granularity enables targeted routing while document-level confidence masks segment weakness

Pipeline emits field-level confidence scores such as quantity 0.31 flagged versus unit 0.94 skip and calibrates routing per field, rather than a single document-level confidence that averages across fields.

Calibration uses labeled validation set per field type and stratified sampling to continuously monitor accuracy by field segment.

result.json
json
{
  "ingredient": "flour",
  "quantity": "30 minutes",
  "confidence": 0.31,
  "unit": "cups",
  "confidence": 0.94
}

Semantic errors such as 30 minutes in ingredient quantity or party name 99 percent versus indemnification 71 percent under the same model report live at the field level. Document-level averaging mixes a weak field with strong fields and hides the field that drives errors, so the pipeline cannot target the right items for review.

Boundary. Document-level is adequate only when per-field accuracy is genuinely uniform or when disposition is whole-document, such as approve versus escalate the entire report. Evidence marks replace field-level with document-level as incorrect in the ingredient quantity scenario, while field-level plus calibrated threshold achieves 3x efficiency by routing 20 percent containing 60 percent of errors.

Recurring specifics. - Granularities: field-level confidence scores, single document-level confidence - Segment examples: party name 99 percent versus indemnification 71 percent, Duration 30 minutes in quantity, comparison tables and appendices - Capacity: human reviewers can check only 20 percent, enables 3x efficiency gain

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Simplifies logic.

Why it fails. Masks weak field.

When it would be right. Whole-document gate with uniform field accuracy.

Proposal. Proposal.

Why it attracts. Unbiased estimate.

Why it fails. No predictive targeting, wastes capacity on correct items.

When it would be right. For measuring overall rate, not for catching concentrated semantic errors.

Proposal. Proposal.

Why it attracts. Visible incompleteness.

Why it fails. Semantic errors are fully populated and valid behind schema, so they are invisible to that heuristic.

When it would be right. For completeness checking, not semantic correctness.

How the same rule gets re-asked
  • - Fields pros, cons, ratings, sentiment versus party name versus indemnification preserve field-level advantage. - Sampling random 20 percent versus field-level calibrated 20 percent preserves distractor versus correct contrast.
R27

Exhaustion-based and explicit-signal escalation is reliable while sentiment or generic frustration is not

Reliable escalation triggers are explicit failure signals: missing required tool, exceeded retry limit, ambiguous requirements, customer explicitly asks to speak to a person, policy exception requiring manager approval. Unreliable proxies are user sentiment or frustration, self-reported confidence scores, or agent explicitly requests assistance.

Confidence is poorly calibrated and sentiment is a lagging emotional signal that becomes apparent only after frustration accumulates and may miss the actual inability-to-proceed moment. Explicit signals such as stop troubleshooting, I want to speak to a person now are unambiguous and should be honored immediately without further diagnostic questions. Evidence marks explicit failure signals: missing required tool, exceeded retry limit, ambiguous requirements as correct and model confidence scores are accurate and sentiment detecting frustration as incorrect.

Boundary. Sentiment and confidence can supplement but not replace explicit escalation criteria. With explicit criteria plus few-shot examples showing when to escalate versus resolve autonomously, escalation becomes calibrated. Without that, even only escalate high-confidence cases or be conservative about escalation leaves behavior inconsistent.

Recurring specifics. - Signals: missing required tool, exceeded retry limit, ambiguous requirements, customer says stop troubleshooting, speak to a person now, requests scoring 7+ auto-resolve while lower escalate - Numbers: first-contact resolution 55 versus 80 target, escalates easy cases such as photo-backed damage replacements while handling complex policy exceptions itself, escalation below 60 percent - Anti-signals: sentiment analysis threshold, self-report 1 to 10 and auto-route below threshold, require at least three turns before escalation, separate classifier trained on historical tickets

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Emotion seems to indicate need.

Why it fails. Lags actual inability and misses explicit requests.

When it would be right. As supplement after explicit criteria, not as trigger.

Proposal. Proposal.

Why it attracts. Appears to catch uncertainty.

Why it fails. Poorly calibrated and uncorrelated with case complexity.

When it would be right. After explicit criteria, as additional routing.

Proposal. Proposal.

Why it attracts. Gives agent chance to resolve.

Why it fails. Fixed delay harms explicit human requests.

When it would be right. When policy explicitly demands minimum effort before human, which no tested scenario does.

How the same rule gets re-asked
  • - Support routine damage replacement to address change to photo-backed replacement keeps the same escalation miscalibration. - Trigger missing tool to retry exhausted to ambiguous requirement all count as correct explicit signals. - Alternative sentiment plus confidence preserves distractor status because neither is reliably tied to complexity.
R28

Fixed instruction to weight every schedule or section equally does not overcome attention burial without structural change

Single long prompt with instruction weight every schedule equally or allocate equal scrutiny to every section attempts to equalize attention by directive rather than by structure. The model still receives the entire SOW plus the playbook or 150 endpoints in one window, so later sections remain under-processed.

Equal-weight instructions operate on the same attention distribution. Evidence shows the miss rate dropping only from 26 to 21 percent after adding the instruction, without closing the gap, while per-clause sharding plus cross-clause integration closes it. Retry loops that add scrutinize each critical finding skeptically also fail to correct for the same reason.

Boundary. Equal-weight instruction becomes harmless only after sharding gives each section independent context. It is never sufficient as the sole fix for burial. It is tested as an anti-fix alongside validation recomputing metric deltas that retries until emitted tier and recomputed impact agree, which also fails when severity criteria remain vague prose.

Recurring specifics. - Instructions: weight every schedule equally, allocate equal scrutiny to every schedule regardless of position, apply uniform severity standards on every run, weigh business impact deterministically, avoid letting run-to-run variation affect ratings - Rates: 4 to 26 percent miss by SOW size, 21 after instruction, 40 percent of pages manually re-triaged - Fixes: per-clause independent API calls plus cross-clause integration, explicit per-tier criteria that bind concrete anomaly patterns with labeled examples

Wrong answers written against this rule

Proposal. Proposal.

Why it attracts. Simple equality.

Why it fails. Attention burial persists.

When it would be right. Only after sharding.

Proposal. Proposal.

Why it attracts. Stronger wording.

Why it fails. Narrative tiers remain vague.

When it would be right. When tiers are explicit per-tier criteria with examples.

Proposal. Proposal.

Why it attracts. Data-grounded correction.

Why it fails. Retry fixes measurement not judgment criteria.

When it would be right. When severity variance is caused by missing info rather than ambiguous criteria.

How the same rule gets re-asked
  • - Instruction weight equally to scrutinize skeptically to apply uniform standards all preserve incorrect status as single-prompt directive fixes. - Severity prose to count of severity levels from three to two also preserves incorrect status. - Measurement recompute metric deltas to re-examine anomaly and correct severity preserves failure when criteria remain narrative.
CI code review pipeline with a trust collapse and a three-part recovery
A production walkthrough with the reasoning chain made explicit.

A team operates a pull request review service whose system prompt starts as Review this code, be conservative, only report high-confidence findings. Performance degrades as categories are added, bugs, security, style, documentation mismatch, and performance. Some categories run at 98 percent accuracy and developers trust them, but the documentation mismatch category runs at a 40 percent false positive rate by flagging style preferences as documentation issues. Developers stop reading the entire review output, including the accurate security findings, because one unreliable category poisons trust in all of them.

The first attempted fix adds only report high-confidence documentation issues. It fails because confidence is poorly calibrated and does not address the missing criteria, the model has no definition of what counts as a documentation mismatch versus a style preference, so the same false positives continue at a different threshold. The trust collapse is driven by category precision, not by a missing cutoff.

The fix that works has three parts. The system prompt is rewritten with explicit criteria per category: flag comments only when claimed behaviour contradicts actual code behaviour, report bugs and security vulnerabilities, and skip minor style preferences and local patterns, each severity paired with a concrete code example rather than a description. The documentation mismatch category is temporarily disabled while its criteria and examples are reworked, because a 40 percent false positive rate destroys system-wide trust and the only way to recover is to remove the broken category until its precision improves. After repair the category is re-enabled with a shadow test comparing old versus new prompts on recent inputs before deployment.

The structural follow through prevents regression. Critical behavioural constraints are moved to the top of the system prompt, examples to the bottom, and the large stable prompt is wrapped with cache_control ephemeral so repeat calls share cost without sacrificing attention discipline. An uncertainty instruction is added so the model says it is not sure and offers to escalate rather than guessing when it lacks evidence, closing the opening that previously produced fluent guesses.

Distinctions that decide answers

ThisNot thisHow to tell them apart
System promptUser messageSystem persists across every turn and frames the assistant, user is per-turn input that can request a temporary shift. Universal standards belong in system, query-specific instructions belong in user.
Explicit categorical criteriaConfidence-based filteringCriteria define what to flag and what to skip with code examples per category. Confidence tries to filter by self-reported certainty, which is poorly calibrated. Hierarchy is criteria first, confidence routing second.
Negative constraintsLower temperatureNegative constraints directly suppress a pattern such as a preamble. Lowering temperature reduces randomness but does not guarantee pattern absence.
Primacy at topRecency near endBoth ends receive strong attention, primacy shapes core behaviour, recency shapes the specific output format. Put behavioural constraints first and examples last.
System prompt caching with cache_controlContext caching as a separate featureSystem prompt caching uses cache_control inside the system parameter. Context caching caches any repeated prefix including history and tool definitions. Same savings, different scope.
RACCE frameworkFree-form prose promptsRACCE decomposes the prompt into five testable components. Free-form prose reads like a specification but cannot be diagnosed systematically when quality drops.

Traps

Be conservative or only report high-confidence findings as prompt improvements

The tempting answer. Add the phrases be conservative or only report high-confidence findings because they sound engineering grade.

Why it fails. Both phrases give the model no actionable decision boundary. Conservative varies by context and high-confidence is an uncalibrated subjective threshold. The exam uses them as distractors for exactly their plausible tone.

What is correct. Replace them with explicit categorical criteria and concrete code examples per category that define what counts as a valid finding.

Confidence threshold to fix a high false positive category

The tempting answer. Add only report high-confidence documentation issues to curb a category running at 40 percent false positives.

Why it fails. LLM self-reported confidence is poorly calibrated and does not address the missing definition of what counts as a valid finding in that category, so the same false positives continue at a new cutoff.

What is correct. Define what counts as a valid finding with criteria and examples, then use confidence only as a routing signal to human review.

Keep all categories active while iterating on the broken one

The tempting answer. Keep the high false positive category live during repair because disabling it feels like losing functionality.

Why it fails. Trust bleeds across categories. A 40 percent false positive rate on one category destroys trust in security findings that remain highly accurate.

What is correct. Temporarily disable the unreliable category, rework its criteria and examples, and re-enable only once precision improves, with shadow testing before deployment.

Critical constraints buried in the middle of a long system prompt

The tempting answer. Grow the prompt by appending detail and leave the most important constraints where they were originally written.

Why it fails. Content at the very top receives stronger attention than content buried in the middle due to the primacy effect, so middle-placed constraints are reliably followed less often.

What is correct. Move critical behavioural constraints to the very top, supporting detail to the middle, and examples to the end.

Be ethical or be careful as a constraint

The tempting answer. Add aspirational constraints such as be ethical or be careful to cover safety.

Why it fails. Aspirational constraints are not verifiable from the output, there is no way to check whether Claude was ethical from the text alone.

What is correct. Use testable constraints such as never provide a specific diagnosis or always escalate when the user describes an emergency.

Caching removes the cost of system prompt size

The tempting answer. Treat a 10000 token system prompt as free once prompt caching is enabled.

Why it fails. Caching reduces repeat-call price but not per-call attention footprint. The 10000 tokens still occupy attention budget on every call.

What is correct. Keep the system prompt under 10 percent of working context even when caching, and treat caching as a price discount not an attention discount.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
Negative constraints as a paired complement to positive instructions

A positive instruction to respond only in JSON still permits a preamble such as Here is the JSON. A paired negative constraint that forbids any text before or after the object, preamble, explanation, or markdown fences, suppresses that pattern more reliably than sampling changes.

System Prompt Design
Explicit uncertainty handling as a first-class constraint

A prompt that covers tone and scope but never states what to do when uncertain leaves the model to produce fluent guesses. Adding an instruction to say it is not sure and offer to escalate closes that opening.

Prompt Anti-Patterns
Versioning and shadow testing discipline for prompt changes

Version the system prompt in source control, shadow test a sample of recent real inputs on old versus new prompt, change one RACCE component at a time, and monitor satisfaction, tool call patterns, and refusal rate after deployment.

System Prompt Design
Build it
Replace vague review guidance with explicit criteria and prove trust recovery
  1. Write a baseline system prompt with vague instructions review this code, be conservative, only report high-confidence findings and run it against five snippets containing known bugs, security issues, and style nitpicks.
  2. Rewrite the prompt with explicit categorical criteria per category report bugs and security vulnerabilities, skip style preferences and local patterns, and flag comments only when claimed behaviour contradicts actual code behaviour.
  3. Add concrete code examples for each severity level showing the actual pattern that defines that severity rather than a prose description.
  4. Compare false positive rates on the same five snippets between the vague and explicit prompts and record inconsistency across repeated runs.
  5. Temporarily disable any category whose false positive rate exceeds the threshold, document the specific criteria refinements needed for re-enablement, and shadow test a sample of recent real inputs on old versus new prompt before deploying the re-enabled category.

Verify. The explicit version classifies consistently across runs with measurably lower false positives, the unreliable category stops poisoning trust while disabled, and the re-enabled version holds precision because its boundary is defined by patterns not adjectives.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Task 4.216 min

Few-Shot Prompting

Show the pattern with 2 to 4 reasoning-annotated examples so Claude matches a demonstrated judgement instead of interpreting an adjective.

What you need to know

Few-shot examples are the most effective technique for achieving consistent, well formatted output from Claude, more effective than more instructions, confidence thresholds, or temperature adjustments. When output is inconsistent across invocations despite detailed instructions, the first tool to reach for is a small set of examples showing the exact format and judgement you want. The exam presents exactly that scenario and tests whether you choose more instructions or few-shot examples, with the latter as the correct answer.

Few-shot works through in-context learning: Claude generalises patterns from in-context examples more reliably than it follows verbal descriptions of those same patterns. Telling the model to respond in a warm, concise, professional tone with a brief empathy statement leaves warm and concise open to interpretation, while three actual responses exhibiting that tone remove the interpretation step entirely. The model matches the demonstrated pattern rather than reconstructing it from an adjective.

Three triggers tell you few-shot is needed. Detailed instructions produce inconsistent formatting, sometimes a bulleted list and sometimes a table for the same request, more prose will not converge it. Judgement calls are inconsistent on ambiguous cases, for code review the model flags variable shadowing as critical in one file and minor in another, or for routing it sends check my order to different tools depending on phrasing. Extraction produces empty or null fields for information that exists but appears in an unexpected format, inline citations versus bibliographies or narrative text versus structured tables. All three resolve faster with examples than with additional description.

The rules for constructing effective examples are tight. Use 2 to 4 targeted examples, fewer than 2 does not establish a pattern and more than 4 wastes tokens without proportional benefit. Point each example at a specific ambiguous scenario causing problems, not at generic happy paths. Each example must show reasoning, not just input and output, explaining why one action was chosen over plausible alternatives so the model learns the decision principle and generalises to novel inputs. Without reasoning the model learns literals such as queries mentioning order numbers go to lookup_order, with reasoning it learns the principle that specific identifiers route to specific lookup tools.

Examples also reduce hallucination in extraction. Varying document structures such as inline citations, bibliographies, narrative descriptions, structured tables, headers, and embedded text taught through examples help the model handle structural variety without inventing data. A report that lists expenses in a table on one page and buries them in a paragraph on the next is the canonical case where, without examples, the model extracts the table correctly but leaves narrative fields empty or fabricated, and with examples for both structures extraction quality climbs.

Where examples live and how many to use

Few-shot examples appear either embedded in the system prompt as part of the Examples component of RACCE for patterns that should shape every response, or inline in the messages array as a sequence of user and assistant pairs before the actual query for patterns specific to that query. Proximity matters, examples immediately before the user message influence the response more than examples earlier in context due to recency, the same effect that shapes system prompt placement.

Task complexity determines count, not input volume. Simple three-category sentiment classification needs 2 to 3 examples, a complex extraction with 20 fields, nested objects, and conditional logic may need 8 to 10. Few-shot with 2 to 5 offers the best reliability to cost ratio for most applications, zero-shot is cheapest but least reliable for format-sensitive tasks, and many-shot with 5 or more helps nuanced tasks at the cost of context and potential dilution of system instructions.

Ordering, balance, and format hygiene

Example ordering affects output because later examples carry more influence near the query. For balanced classification mix categories rather than grouping all positives then all negatives, grouping biases the model toward the last group seen. If chain-of-thought examples are used the last example's reasoning pattern is most likely to be emulated, and a 5 to 10 percent accuracy swing between orderings is common.

Label balance creates a prior that biases predictions. Four positives and one negative in a five-example sentiment set teaches the model a positive prior comparable to adding hundreds of majority-class training examples. For balanced classification use equal class counts, for genuinely skewed production distributions a proportionally skewed set may be intentional but trades minority accuracy for majority accuracy. Format consistency is non-negotiable, even a missing colon or inconsistent capitalisation is modelled as intentional and may be reproduced, so every example must use identical formatting.

Dynamic few-shot selection is the production pattern when the example bank is too large for a static prompt. Embedding similarity retrieval finds semantically similar examples, keyword overlap such as TF-IDF or BM25 finds term-sharing examples, hybrid combines both, and cluster-based selection serves very large banks of 1000 or more where nearest neighbour is noisy.

Examples with reasoning and the email classification case

Examples without reasoning teach literal pattern matching that fails on novel phrasing. Examples with reasoning that explains why a classification was chosen teach the underlying principle that transfers. For email triage the principle becomes the primary intent is what the customer asks the agent to do, not which keywords appear most often, and a billing question that mentions a refund in passing stays a billing question when the ask is about the bill, while an explicit money back request becomes a refund request.

The same principle resolves code review inconsistency for variable shadowing. Shadowing within an arrow function scope is limited and does not create a bug, so the example classifies it as minor with reasoning about scope. Shadowing across a function boundary that hides a parameter creates real risk and the example classifies it as critical with reasoning about the hidden parameter. With those two examples the model generalises that severity depends on scope and whether something important is hidden, not on whether the word shadow appears.

Authoritative mechanism reference

The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.

Mechanism reference: Count guidance and the documented versus exam divergence

The effective example count is the most tested surface of this task. The exam material centers on 2 to 4 examples and treats 2 as the floor below which no stable pattern forms and 4 as the practical ceiling beyond which token cost grows while accuracy does not. The live documentation states "include 3 to 5 examples for best results" and adds that you can ask Claude to evaluate your examples for relevance and diversity or to generate additional ones.

These two ranges are not in conflict once you read them as different lenses on the same underlying behavior. The documentation's 3 to 5 is a best-results recommendation for general use. The exam's 2 to 4 is the decision rule for the scenario where you are diagnosing an inconsistency and choosing the smallest demonstration that resolves it. Our lesson splits the difference by describing few-shot as 2 to 5 and then giving an exam tip that 2 to 4 well-chosen examples are optimal.

The shared, defensible principle is diminishing returns past four. Below two, the model cannot establish a pattern and may overfit to a single case. Above four to six, each added example buys little accuracy while linearly increasing token cost and latency, and a large homogeneous set can even dilute attention. The forensic analysis notes an open question where items range from "2-4" to "3-6"; the resolution is to present 2 to 4 as the recommended start and 3 to 6 as the nuanced ceiling for harder tasks, while always treating four as the point of diminishing returns. The number is a starting point to measure against, not a magical constant; the lesson explicitly says to benchmark your specific task rather than assume a fixed number.

The token-cost argument reinforces this: each example beyond the point of diminishing returns adds linear cost and latency while buying little accuracy, so the disciplined practice is to start at three, measure the failure modes that remain, and add a targeted example only for each uncovered edge case rather than scaling the whole set. A set of fifteen near-identical examples collapses to five diverse ones with equal quality once the pattern is saturated, which is why diversity and targeting outrank raw volume. The exam's recurring distractor of "ten to twenty examples for maximum coverage" fails for exactly this reason: near-identical examples add no information the model has not already generalized, and they crowd the context window that could hold diverse coverage instead.

One nuance the documentation adds that the exam material does not emphasize: you can use Claude itself to critique and extend your example set. Asking the model to evaluate your examples for relevance and diversity is a documented way to close the gap between a too-small set and a well-balanced one.

Mechanism reference: Relevance and diversity requirements

The live documentation names three properties every example set should have: relevant, diverse, and structured. These map directly onto exam principles.

Relevant means the examples mirror the actual use case closely. An example taken from a different domain teaches a pattern that does not transfer and can dilute the target signal. A representative example mirrors production length and content, demonstrating the pattern without irrelevant detail. Over-long or out-of-domain examples are explicitly discouraged.

Diverse means the examples vary enough that the model does not pick up unintended patterns, and that they cover edge cases. This is the formal grounding for two exam rules: target the failing scenario rather than the easy one, and cover the failing structure rather than the working one. If the model already extracts correctly from tables but returns empty fields on narrative text, adding more table examples controlled for nothing; the diversity requirement says to add the narrative-structure example. Diversity beats raw count: a handful of representative examples that include edge cases outperforms a large set of near-identical happy-path examples.

Structured means wrapping examples in <example> tags, with multiple examples inside <examples> tags, so Claude can distinguish them from instructions. This is the documented delimiter pattern and it is the safest way to satisfy the exam's requirement that examples have clean input/output demarcation. The XML lesson confirms that <example> inside <examples> makes it unambiguous which content is an example and which is the real task.

Mechanism reference: Why reasoning inside an example generalizes where a bare pair does not

This is the subtle mechanism the exam tests hardest, and the documentation supports it through the thinking note. A bare input-output pair teaches a literal mapping: for these surface features, emit this label. A pair that also shows the reasoning for the decision teaches the underlying principle, so the model can apply the same logic to novel inputs it has never seen.

The live documentation makes the same point from the other direction: multishot examples work with thinking, and placing <thinking> tags inside a few-shot example shows Claude the reasoning pattern, which it then generalizes to its own extended thinking blocks. In other words, the reasoning content inside an example is not discarded as decoration; it is absorbed as the decision logic and reused on new inputs.

The boundary matters. For purely mechanical format demonstration, such as "always emit three bullets," bare pairs suffice because there is no judgement to teach. For ambiguous or edge-case judgement, such as a borderline severity rating or a near-miss tool selection, the reasoning is what lets the model generalize instead of overfitting to the literal case shown. The common false claim that reasoning in examples forces the model to emit a chain-of-thought in its output is wrong: the reasoning teaches decision logic, it does not mandate that the output reproduce the trace.

The documented note also clarifies placement. When thinking is enabled, the model generalizes the <thinking> style from the example to its own internal reasoning. When thinking is off, you can still encourage step-by-step reasoning with structured tags such as <thinking> and <answer> to separate reasoning from the final output.

Mechanism reference: Ordering, label balance, and the nearest-example principle

Two secondary mechanisms are documented or lesson-supported. First, ordering: the model exhibits recency bias, so placing the example most similar to the target input last leverages that bias productively, provided every example uses consistent formatting. Our lesson states that later examples have more influence because they sit closer to the query in the context window. Second, label balance: the distribution of labels in the examples creates a prior that biases predictions, so unbalanced sets skew the model toward the over-represented class. The documentation does not quantify this prior, so any specific percentage about its strength should be treated as lesson-supported rather than documented.

Mechanism reference: How examples interact with structured output

The technique-selection boundary is where candidates most often lose marks, so it deserves full treatment. The live structured-outputs documentation defines two complementary, composable features: JSON outputs via output_config.format with {"type": "json_schema", "schema": ...}, and strict tool use via strict: true on a tool, which enforces schema compliance on tool names and inputs by grammar-constrained sampling. The older output_format top-level field still works for a transition period but is legacy; the current shape is output_config.format.

These features are the structural-enforcement layer. Few-shot examples are a different layer: they teach field semantics and cross-structure extraction. The two are not in competition; they are stacked. A schema guarantees that the response is valid JSON matching the shape, but it does not, by itself, teach the model where in a messy document a field lives, or how to classify a borderline value. That semantic teaching is what examples provide. The documentation states that structured outputs guarantee schema-compliant responses through constrained decoding, eliminating JSON.parse() errors, missing required fields, and inconsistent types. It also states, critically, that schema compliance is not absolute: a refusal returns stop_reason: "refusal" with a 200 status and billed tokens, and the refusal text takes precedence over the schema; hitting the token limit also truncates before the schema can be satisfied. This is why a validation layer still belongs in the pipeline even when a schema is present.

The exam's right answer for "malformed JSON output" is therefore tool use with JSON schemas, not few-shot, because the schema enforces structure on every call while examples only demonstrate it. The exam's right answer for "empty fields for data that exists in narrative text" is few-shot showing cross-structure extraction, because the schema is already satisfied (valid JSON) but the population is wrong. The two failures live in different layers, and the fix lives in the matching layer.

Mechanism reference: How examples interact with a validation layer

A validation layer checks the output against computable invariants that neither a schema nor a set of examples can guarantee. The clearest case is reconciliation: when extracted line items must sum to a stated total, a schema can require that both a line_items array and a total number exist, but it cannot require that they agree. That arithmetic closure is a validation concern, not a demonstration concern. The forensic analysis frames this as the distinction between a demonstration problem and a verification problem: few-shot examples show input-output pairs, not arithmetic closure, so a validation step that recomputes the discrepancy and feeds it back as specific error feedback is the correct intervention.

The documentation supports the need for a separate verification step through its guidance on self-correction and self-checking. The prompting best practices page recommends appending an instruction such as "before you finish, verify your answer against the test criteria," and it describes the common chaining pattern of generating a draft, reviewing it against criteria, and refining. That is a validation-retry loop at the application layer. A blind retry without specific feedback reproduces the same error; the loop must carry the specific discrepancy.

The interaction is therefore: schema enforces shape, examples teach semantics and cross-structure location, and the validation layer enforces computable invariants the other two cannot. Examples do not remove the need for validation, and validation does not remove the need for examples. They are stacked, and the technique-selection matrix tells you which to reach for first when a specific symptom appears.

The validation layer has its own boundaries. It can catch computable mismatches, such as a sum that fails to reconcile or a cross-field type conflict, because those are deterministic checks the application code performs after the response is parsed. It cannot catch semantic hallucinations that are internally consistent, such as a plausible but wrong funder name, because no invariant distinguishes a wrong name from a right one without an external source of truth. That is precisely where few-shot examples earn their keep: they teach the model to locate the correct funder in varied structures, reducing the hallucination at the source rather than detecting it after the fact. A blind retry ("regenerate valid output") is explicitly the wrong move for a reconciliation mismatch, because the same prompt reproduces the same error; the retry must carry the specific discrepancy, such as "sum is 520 but line items total 500," so the model has new information to act on. The documentation's self-check guidance supports this by recommending verification against explicit test criteria before finishing.

A subtle point: the validation layer and the schema are both "enforcement," but at different stages. The schema acts during generation via constrained decoding, before the response is accepted. The validation layer acts after generation, on the accepted response, and may trigger a chained retry. Few-shot sits outside both, acting during generation as demonstration. Recognizing which stage owns which guarantee is the core of the technique-selection boundary and the most reliable way to defeat the exam's distractors.

Mechanism reference: The technique-selection boundary, stated precisely

A demonstration problem is one where the model already understands the goal but applies it inconsistently across invocations: drifting format, inconsistent judgement on ambiguous cases, or empty fields for present data in an unexpected container. The fix is few-shot demonstration. A schema problem is one where the output must satisfy a hard structural contract, such as valid JSON with a fixed schema or a guaranteed tool-input shape; the fix is output_config.format or strict: true. A verification problem is one where the output must satisfy a computable invariant the model cannot enforce by demonstration, such as a sum matching a total or a cross-field consistency check; the fix is a validation-retry loop. A description problem (for tool selection) precedes demonstration: sharpen the tool descriptions first, then add few-shot only for the residual ambiguous requests.

The live documentation adds a composability point the older reference material omits: JSON outputs and strict tool use are independent and can be used together in one request, and few-shot examples sit on top of both to teach semantics. The hierarchy is not "prompt JSON versus tool use" as the older material implied; it is "enforce shape with a schema, teach meaning with examples, verify invariants with a loop."

Mechanism reference: Negative examples and contrastive pairs

Two specialized example shapes address false positives and near-neighbor confusion. A negative example shows a rejected or benign case and explains why it is out, paired with positive examples so the model sees both sides of the boundary. This is the "show what to ignore as well as what to flag" principle. A contrastive pair places an acceptable borderline next to a genuine violation that looks almost the same, with reasoning naming the separating feature, so the model generalizes the boundary to novel phrasings it never saw. The documentation's diversity requirement supports both: varying enough that the model does not pick up unintended patterns includes varying the sign of the example (accept versus reject). Negative examples must be clearly marked (for example Correct:/Incorrect:) to avoid confusing the model; a poorly labelled negative is worse than none.

Mechanism reference: Representative length and the out-of-domain trap

Examples should mirror production length and content. An example far longer than production inputs introduces irrelevant structure the model may imitate and wastes tokens; an example from a different domain teaches a pattern that does not transfer. The goal is a representative input with the correct output that clearly demonstrates the pattern, not the longest or most exotic pair available. Edge cases matter, but the set should still cover typical cases, not only the hardest, because the model sees typical inputs most often.

Mechanism reference: Dynamic and per-type example selection

When one prompt must serve multiple document types, a single static set mixing all types forces the model to reconcile conflicting extraction patterns and raises token cost on every call. The lesson and forensic analysis recommend classifying the document type first, then selecting the relevant examples for that type, keeping the count low and the patterns coherent. The loop engineering lesson describes dynamic few-shot selection that retrieves the most relevant examples per input using embedding similarity, keyword overlap, or hybrid retrieval, and notes that a diverse static set is the cache-friendly baseline. A within-type axis also matters: the same field can appear as a table, a bulleted list, or embedded prose, and few-shot coverage must address that presentation-format variation explicitly, not just document-type variation.

Mechanism reference: Caching and the static-prefix discipline

When the few-shot examples are fixed and sit in the static prefix of the prompt, the prefix stays byte-identical across calls, which enables prompt caching and a real cost saving. Rotating fresh random examples into the prefix on every call mutates it and defeats caching, trading a certain saving for an uncertain coverage gain. The disciplined design keeps curated examples static and appends only the item to classify. Dynamic per-type selection intentionally varies the prefix per call, which is a deliberate trade for relevance; there, caching is per-type rather than global. Examples in the system prompt cost the same tokens as examples in messages, so the saving comes from prefix stability, not from location.

Mechanism reference: When few-shot is the wrong tool

Our lesson enumerates situations where few-shot backfires: tasks requiring precise numerical output (use tool use with function calling), outputs that must follow a very long specification (break into calls or use structured outputs), examples that would leak private data (use synthetic examples or strict format), tasks already handled zero-shot (test zero-shot first), examples that require frequent updating (use dynamic retrieval), and tasks needing creativity or diversity (zero-shot or a single broad example). These are the inverse of the three triggers and they reinforce the technique-selection boundary: demonstration is for consistency, judgement, and present-but-misplaced data, not for arithmetic, structural contracts, or genuine absence.

Mechanism reference: Ordering details and the nearest-example rule restated

The recency bias means the example placed last has the strongest influence on the next output. The productive use is to place the example most similar to the target input last, while keeping every example in consistent format. A malformed example given primacy (placed first) worsens drift rather than helping, so the twin rule is never to give a bad example primacy. If examples are diverse with no single nearest match, consistent formatting matters more than ordering. Random ordering is suboptimal because it wastes the exploitable recency bias.

Mechanism reference: Schema enforcement limits and how they interact with examples

The structured-outputs documentation specifies concrete limits that matter when you stack a schema under few-shot examples. Strict tools are capped at 20 per request; optional parameters across all strict schemas and JSON output schemas are capped at 24; parameters using union types (such as {"type": ["string", "null"]}) are capped at 16 because they create exponential compilation cost. These limits are the reason the nullable-schema technique in Example 5 must be applied judiciously: making many fields nullable pushes toward the optional-parameter and union-type caps, and deeply nested objects with optional fields compound grammar complexity. The documented mitigation order is to mark only critical tools as strict, reduce optional parameters by making them required where a default exists, flatten nested structures, and split complex schemas across separate requests.

The interaction point for few-shot is that examples teach the model which fields to populate and how to locate them, while the schema enforces the shape those populated values must take. When the schema is large, examples become more valuable, not less, because they guide the model through the field space the grammar alone does not explain. But examples cannot rescue a schema that violates the documented limits; a 400 error for "schema too complex for compilation" is an API ownership issue, not a demonstration issue.

A further documented caveat: property ordering in structured output places required properties first, then optional ones, so the output order may differ from the schema order unless all properties are required. Few-shot examples that show a specific field order do not override this API behavior; the application must account for reordering in its parsing logic. This is a concrete place where the demonstration layer and the enforcement layer have independent rules, and the candidate must know which owns the behavior.

Mechanism reference: Label balance and recency bias in depth

The label-balance prior deserves more than a one-line mention because it is a frequent distractor. If all examples belong to one class, the model develops a prior that over-predicts that class, including on ambiguous inputs that should land elsewhere. The documentation frames this through the diversity requirement (vary enough that the model does not pick up unintended patterns), and our lesson quantifies the effect as a strong prior comparable to many training examples of the majority class, though that specific strength is lesson phrasing rather than a documented measurement. The practical rule: for binary tasks use one example of each class; for multi-class use one to two per class; keep the distribution roughly proportional to the real task distribution, or equal representation when the real distribution is unknown. A set of four positive and zero negative examples biases the model toward positive, which is the exact failure the exam tests.

Recency bias is the mirror image at the sequence level. The last examples weigh most, so the nearest match to the target should sit last, and a malformed example must never sit first (primacy makes drift worse). Consistency of format across all examples is the precondition: if one example uses a different delimiter or casing, the model may latch onto that variant regardless of position. The documentation's structured-example guidance (wrap in <example> tags) is what makes consistent demarcation achievable.

Ownership map

Which layer owns which guarantee matters for answering "who is responsible" questions on the exam.

The model owns pattern matching and generalization from the examples it is shown. It decides, from the demonstrated input-output mappings, how to transform the real query. It does not own structural enforcement; it can deviate from a demonstrated shape.

The application code (your prompt assembly) owns example selection, demarcation, ordering, and the static prefix that enables caching. In a loop harness, the harness owns which examples are retrieved per call and whether the set is static or dynamic.

The SDK and API own schema enforcement when you use output_config.format or strict: true. Constrained decoding is performed by the runtime, so a valid schema means the response matches the shape regardless of what the model "intended".

The validation layer (your code, or a chained follow-up call) owns computable invariants. Neither the model nor the schema enforces reconciliation; your code does, by recomputing and feeding back specific errors.

The configuration layer (prompt caching) owns the cost saving from a stable example prefix. A fixed curated set in the static prefix is cached and reused; rotating fresh examples each call defeats that cache.

The boundary between model-owned and API-owned is the single most important ownership distinction for this task: format consistency is model-owned and fixed by demonstration, while JSON structural validity is API-owned and fixed by schema.

Version and terminology currency

The documented API surface has shifted. The current structured-outputs entry point is output_config.format carrying {"type": "json_schema", "schema": ...}. The older output_format top-level field and the beta header structured-outputs-2025-11-13 are accepted only for a transition period; the Python SDK (v1.0 and later) no longer accepts output_format on client.beta.messages.create() or count_tokens() and raises a TypeError, so output_config must be used.

Strict tool use is a separate, independent feature set by strict: true on an individual tool. It enforces schema compliance on tool names and inputs through grammar-constrained sampling. It is not the same as JSON outputs, and the two compose in one request.

The historical name "multishot prompting" now redirects into the prompting best practices page under the "use examples effectively" section. The terms few-shot and multishot are used interchangeably in the live documentation. Candidates should answer with either term; the exam material uses "few-shot."

Thinking configuration has also moved. On Claude 4.6 and later models, extended thinking uses adaptive thinking (thinking: {type: "adaptive"}) and the effort parameter, rather than a manually set budget_tokens. The prompting best practices page notes that manual chain-of-thought prompting is a fallback when thinking is off. This matters because the reasoning-in-examples mechanism described earlier interacts with thinking: <thinking> tags inside few-shot examples generalize to the model's own extended-thinking blocks.

Prompt prefilling for format control is deprecated on Claude 4.6 and later; the documentation's migration path is structured outputs, tool calling, or direct instructions instead of a prefilled assistant message.

Official versus community divergence

The clearest divergence is the example count. The exam material and our lesson exam tip say 2 to 4; the live documentation says 3 to 5. A candidate should answer with the exam's 2 to 4 in an exam scenario (because the scenario is phrased around the smallest fix), while understanding that the documentation's 3 to 5 is the general recommendation and that both converge on "diminishing returns past four.".

A second divergence is hierarchy framing. Older reference material presents "prompt-requested JSON versus tool use" as the whole structure. The live documentation adds JSON outputs through output_config.format and strict tool use as separate, composable guarantees, with few-shot examples as a third, orthogonal layer for semantics. Documentation wins; the defensible answer is that shape enforcement, meaning demonstration, and invariant verification are three stacked concerns.

A third area is confidence thresholds. Community and some training material imply a confidence gate can fix inconsistent judgement. The lesson and forensic analysis state that self-reported confidence is poorly calibrated and cannot create the missing standard; the fix is explicit criteria plus examples. The live documentation does not address confidence thresholds in this context, so this position is lesson-supported and should be cited as such rather than as documented fact.

A fourth item the forensic analysis flags is the reasoning requirement. The exam insists every example carry reasoning. The documentation does not mandate it, but it does confirm that reasoning patterns inside examples generalize (via the thinking note). So the exam rule is REFINED against documentation: reasoning is required for ambiguous judgement examples and is documented as beneficial through thinking generalization, but it is not mandated for pure format pairs.

Beyond the task statement

The task statement covers the core demonstration technique, but our lesson set extends well past it. These adjacent topics are part of the same domain and a candidate should recognize how each connects to few-shot prompting.

chain-of-thought (slug: chain-of-thought) is the reasoning technique that interacts most directly with few-shot. The lesson distinguishes zero-shot CoT, few-shot CoT (providing two to three worked reasoning examples), and structured CoT (XML-tagged reasoning). The key boundary: few-shot CoT is the right tool when the task is ambiguous or conditional reasoning, while plain few-shot example pairs are right for format and pattern recognition. The forensic analysis frames chain-of-thought as the targeted exception that outranks examples for genuinely conditional logic and for genuinely ambiguous source text. The lesson also warns that CoT cannot fix a bad base prompt and that explicit CoT beats implicit "think step by step" for accuracy.

xml-structured-prompting (slug: xml-structured-prompting) supplies the delimiter pattern few-shot depends on. The lesson shows that <examples> containing <example> blocks make it unambiguous which content is a demonstration and which is the real task, and that <thinking>/<answer> tags separate reasoning from the final output. It also warns about empty optional tags: an empty <reference_documents></reference_documents> is semantically ambiguous and can cause the model to invent content, so either omit the tag or state "None provided". This is the structural companion to clean example demarcation.

prompt-anti-patterns (slug: prompt-anti-patterns) explains why more prose fails. Its "vague instructions" and "conflicting instructions" sections are the root cause of the instruction-plateau trigger: when instructions are already detailed and output is inconsistent, the problem is not missing prose but missing demonstration. The lesson's over-flagging and aggressive-safety sections also connect to the false-positive half of few-shot, where negative and contrastive examples teach the model what to ignore.

loop-eng-prompt-engineering (slug: loop-eng-prompt-engineering) reframes few-shot inside autonomous loops. In a loop, examples become training data the agent sees on every run, and dynamic few-shot selection retrieves the most relevant examples per input using similarity search. The lesson warns about example bias amplification: a biased static set can be amplified across iterations, so diversify and include counter-examples. It also describes example accumulation, where a successful output is promoted to a few-shot example for later iterations, and meta-prompting, where the loop rewrites its own example library.

extended-thinking (slug: extended-thinking) is the API capability that the thinking note in the best-practices page references. When thinking is enabled, the model reasons in a separate thinking content block, and few-shot <thinking> tags generalize to that block.

system-prompt-design (slug: system-prompt-design) is where the example prefix typically lives for caching, and role-prompting (slug: role-prompting) focuses tone before examples refine format. meta-prompting (slug: meta-prompting) can generate or critique example sets, which parallels the documentation's suggestion to ask Claude to evaluate examples for relevance and diversity.

The adjacent reliability lessons matter for the validation layer. validation-strategies (slug: validation-strategies) covers the retry-with-feedback loop that handles reconciliation mismatches, and guardrails (slug: guardrails) covers the trust-erosion problem where high false-positive categories poison confidence in accurate ones, which few-shot alone cannot repair.

system-prompt-design (slug: system-prompt-design) is where the example prefix typically lives, and the RACCE framing (Role, Audience, Criteria, Constraints, Examples) places examples as one of five components rather than the whole prompt. A common mistake is to pour all effort into examples while leaving criteria vague; the anti-patterns lesson shows that vague criteria are the root cause of inconsistency that examples then have to rescue.

role-prompting (slug: role-prompting) sets tone and scope before examples refine format, and it pairs naturally with few-shot when the demonstration must match a persona's voice. meta-prompting (slug: meta-prompting) can generate or critique example sets, which directly parallels the documentation's suggestion to ask Claude to evaluate examples for relevance and diversity.

sampling-parameters (slug: sampling-parameters) is where the temperature myth is settled. Lowering temperature reduces sampling randomness but does not define a missing standard, so it cannot fix criteria ambiguity; the forensic analysis is explicit that temperature zero makes the model deterministically wrong in the same way. This is why the exam treats "set temperature to zero" as a distractor for inconsistent judgement calls.

constrained-decoding (slug: constrained-decoding) and schema-definition (slug: schema-definition) are the lessons that explain how output_config.format is enforced at the token level, complementing the structured-outputs documentation. json-mode (slug: json-mode) covers the legacy approach that the current output_config.format replaces, useful for understanding version currency.

tool-definition-schemas (slug: tool-definition-schemas) and tool-use-blocks (slug: tool-use-blocks) are the lessons behind the "wrong tool selection" branch of the technique-selection matrix: sharpen tool descriptions first, then add few-shot only for residual ambiguity.

Worked production examples

The following examples are realistic end-to-end demonstrations. Each shows the reasoning chain and the failure mode it avoids. Every example block uses a language tag and inline backticks for identifiers, per the citation discipline.

Worked production examples: Example 1: a wrapped example set with clear delimiters

The first requirement is a wrapped set where the model can tell examples from instructions and from the real query. The documented pattern is <example> tags inside <examples>, and each example uses consistent Input:/Output: markers. This resolves the format-drift trigger: the demonstration removes interpretation.

protocol.xml
xml
<task>
Classify each support ticket into exactly one category: BILLING, TECHNICAL, ACCOUNT.
Return only the uppercase category label and nothing else.
</task>

<examples>
  <example>
    <input>My card was charged twice for the March subscription and I want a refund.</input>
    <output>BILLING</output>
  </example>
  <example>
    <input>The desktop app crashes every time I export a PDF larger than 10 MB.</input>
    <output>TECHNICAL</output>
  </example>
  <example>
    <input>I cannot log in after the password reset email, it says the link expired.</input>
    <output>ACCOUNT</output>
  </example>
</examples>

<input>I was billed for a plan I cancelled last month, please reverse the charge.</input>
<output>

What this proves: the <examples> wrapper separates demonstrations from the live <input>, and the uniform Input:/Output: shape teaches the model to emit a single uppercase label with no surrounding text. The failure boundary is format drift: if one example said sentiment: negative while others said NEUTRAL, the model would imitate the inconsistency. Observable output is a bare BILLING token for the final input, with no preamble.

Worked production examples: Example 2: an example that carries its own reasoning for a borderline judgement

The second requirement is a borderline example whose reasoning shows why one action was chosen over a plausible alternative. This is the mechanism that lets the model generalize instead of matching surface form.

output.txt
text
Select the correct tool for each request. Show the reasoning, then the tool.

Input: "check my order #12345"
Selected tool: lookup_order
Reasoning: The user provides a specific order number (#12345), which indicates they
want order-specific information. Even though this could be read as a general customer
question, the concrete order identifier routes it to lookup_order rather than the
broader get_customer tool.

Input: "I think my account details are wrong after the merger"
Selected tool: get_customer
Reasoning: There is no order identifier and the concern is about the customer profile
itself, not a specific purchase, so get_customer is the right scope.

Input: "help with my recent purchase, not sure which one"
Selected tool: lookup_order
Reasoning: "recent purchase" implies a specific transaction even without a number. The
phrase "not sure which one" signals the user expects us to find it, which is exactly
what lookup_order does when given the customer context. This is the borderline case:
absent an explicit number, a bare get_customer would lose the purchase intent.

What this proves: the Reasoning: line names the decisive feature (a specific identifier, or the presence of purchase intent), so the model learns the principle that specific identifiers route to specific lookup tools. Without the reasoning, the model would learn only "mentions order numbers go to lookup_order" and would misroute the borderline "recent purchase" case. The failure boundary is overfitting to a literal cue; the reasoning protects against it. Observable output for a new borderline request is a tool name chosen by the same principle, not by keyword matching.

Worked production examples: Example 3: examples covering the failing structure, not the working one

The third requirement is to demonstrate the structure that currently fails. Here the model extracts correctly from tables but returns empty fields on narrative text, so the example set leads with the narrative (failing) structure.

output.txt
text
Extract the funder name and funding amount from each grant paragraph.

Example (narrative, the failing structure):
Text: "The city council awarded Riverside School a grant, and the total committed
comes to four hundred thousand dollars spread across three years."
funder: city council
recipient: Riverside School
amount: 400000
currency: USD
note: amount written as words, normalized to digits

Example (narrative, a second variant):
Text: "According to the memo, the Brown Foundation will support the clinic with
roughly half a million dollars."
funder: Brown Foundation
recipient: the clinic
amount: 500000
currency: USD
note: "roughly" preserved, not converted to an exact figure

Now extract:
Text: "The county board approved aid for the harbor project; the contribution is
around two hundred and fifty thousand dollars."

What this proves: both examples show extraction from prose, the structure that was failing, so the model learns to locate the funder and amount regardless of presentation. The table structure is deliberately not demonstrated because it already works. The failure boundary is structural recognition: if the example set had shown only tables, the narrative empty-field rate would not improve. Observable output is a populated amount of 250000 for the final narrative input rather than null.

Worked production examples: Example 4: contrastive pairs showing what to flag and what to ignore

The fourth requirement is a contrastive pair that teaches the boundary between a genuine issue and a benign look-alike, reducing false positives while preserving real detection.

output.txt
text
Review the following code for variable shadowing. Flag severity only if the shadow
causes a real risk.

Example A (flag as minor):
Code:
  function process(items) {
    const result = items.map(item => {
      const result = transform(item); // shadows outer 'result'
      return result;
    });
    return result;
  }
Severity: minor
Reasoning: The inner 'result' shadows the outer variable but only within the arrow
function scope. The code stays readable and the shadow does not cause a bug. This is a
style preference, not a defect. Flag as minor only if style consistency is in scope.

Example B (do NOT flag):
Code:
  function render(user) {
    const config = loadConfig();
    const config = sanitize(config); // assignment to a new const in same scope
    return paint(user, config);
  }
Severity: none
Reasoning: This is the benign look-alike. The second 'config' is a re-declaration that
the JavaScript engine rejects at parse time, so it is caught by the build, not a subtle
runtime shadow. Treating it as a shadowing defect would be a false positive. Ignore it.

What this proves: the pair sits the acceptable pattern (Example B) next to the genuine but minor issue (Example A), with the separating feature named in each Reasoning: line. The model learns to distinguish a real shadow from a parse-time re-declaration, cutting false positives without losing the ability to flag true shadows. Observable output is none for a new benign re-declaration and minor for a true scope-limited shadow.

Worked production examples: Example 5: an example set paired with a nullable schema so absence is demonstrated

The fifth requirement is a nullable schema with few-shot examples that return null for absent fields, so the model demonstrates absence rather than inventing a value.

result.json
json
{
  "type": "object",
  "properties": {
    "company_name": {"type": "string"},
    "founded_year": {"type": ["integer", "null"]},
    "headquarters": {"type": ["string", "null"]},
    "source_excerpt": {"type": "string"}
  },
  "required": ["company_name", "founded_year", "headquarters", "source_excerpt"],
  "additionalProperties": false
}
output.txt
text
Extract company facts. Return null for a field when the source is silent about it.
Always populate source_excerpt with the supporting text or "not stated".

Example (field present):
Text: "Acme Logistics was founded in 2009 and is based in Rotterdam."
company_name: Acme Logistics
founded_year: 2009
headquarters: Rotterdam
source_excerpt: "founded in 2009 and is based in Rotterdam"

Example (field absent, demonstrate null):
Text: "Globex Health provides telemedicine services across the region."
company_name: Globex Health
founded_year: null
headquarters: null
source_excerpt: "not stated"

What this proves: making founded_year and headquarters nullable removes the schema pressure to populate them, and the second example demonstrates returning null plus a source_excerpt of "not stated" when the source is silent. Together they eliminate fabrication at the source rather than detecting it after the fact. The failure boundary is a required field with genuinely absent data: a required field alone still pressures fabrication, and examples alone cannot make null legal, so both halves are required. Observable output is null for absent fields instead of an invented year.

Worked production examples: Example 6: reasoning tags that generalize to extended thinking

The documented note that <thinking> tags inside few-shot examples generalize to the model's own extended-thinking blocks is worth a concrete example.

output.txt
text
Decide whether a request is COMPLIANT or REQUIRES_REVIEW.

Example:
Request: "Our vendor agreement lets us store EU customer data in a US region."
<thinking>
The request mentions EU customer data, which triggers a geographic constraint. The
agreement explicitly permits US-region storage, so the stated practice matches the
contract. There is no obvious conflict with the documented policy.
</thinking>
Decision: COMPLIANT

Now decide:
Request: "We will keep audit logs for 30 days then delete them."

What this proves: the <thinking> block inside the example teaches the model the reasoning shape, and when thinking is enabled the model reuses that shape in its own thinking content block. This grounds the claim that reasoning inside an example generalizes, not merely as output formatting but as internal reasoning structure. Observable output is a COMPLIANT or REQUIRES_REVIEW decision preceded by a private reasoning trace.

Worked production examples: End-to-end scenario: the technique-selection decision in practice

A realistic pipeline makes the boundary concrete. Suppose an invoice-extraction system receives mixed documents: some have line items in a table, some bury totals in narrative prose, and the stated total must equal the sum of line items. The symptoms arrive separately.

Symptom A: the response is not valid JSON, or required keys are missing. This is a schema problem. The fix is output_config.format with a json_schema defining the invoice object, or strict: true on an extraction tool. Few-shot alone cannot enforce this; examples only demonstrate.

Symptom B: the JSON is valid, but total is null for invoices where the total sits in narrative prose while tables extract fine. This is the cross-structure empty-field problem. The fix is few-shot examples showing extraction from narrative prose, leading with the failing structure as in Example 3.

Symptom C: the valid JSON populates every field, but total is 520 while the line items sum to 500. This is a verification problem. Neither the schema nor the examples can guarantee arithmetic closure. The fix is a validation step that recomputes the sum, detects the 20 discrepancy, and feeds it back as specific error text before a retry.

Symptom D: a required field such as tax_id is absent from the source, and the model invents a plausible value. This is a fabrication problem. The fix is a nullable schema plus few-shot examples demonstrating null, exactly as in Example 5.

The decision matrix a candidate should internalize is: malformed or missing structure goes to schema; empty fields for present data go to few-shot; sum or reconciliation mismatches go to validation; fabricated absent values go to nullable schema plus demonstration; ambiguous tool routing goes to sharper descriptions then few-shot. Few-shot is the right first move only when the failure is pattern or judgement inconsistency, which is exactly the scenario the exam presents with "detailed instructions already exist but output is still inconsistent."

Observable outcome of applying this matrix: after adding narrative-structure few-shot examples, the empty-field rate on prose invoices drops while table invoices stay correct; after adding a nullable tax_id with null demonstrations, fabrication stops; after adding a validation-retry loop, sum mismatches are caught and corrected rather than passed downstream. Each intervention is measurable against the baseline from Step 1 of the build exercise.

Worked production examples: How the reasoning-in-example mechanism connects to extended thinking

The documentation note that <thinking> tags inside few-shot examples generalize to the model's own extended-thinking blocks is the formal basis for why reasoning inside an example outperforms a bare pair on novel inputs. The example's reasoning is not surface text the model copies; it is absorbed as the decision logic and replayed in the private thinking block on new inputs. This is distinct from forcing the model to emit a chain-of-thought in its visible output. The lesson on chain-of-thought makes the same distinction: structured CoT with <thinking>/<answer> tags separates reasoning from the answer so the application can parse and discard the trace, and the model still gains the accuracy benefit of writing intermediate steps. Few-shot reasoning examples are the demonstration-time version of that separation.

Build exercise material

The reference page's build exercise asks you to construct a few-shot enhanced extraction prompt and measure its impact. The steps below give verifiable outcomes so you can prove each step worked.

Step 1: Build a base extraction prompt with detailed instructions but zero examples. Test it against ten documents with varied structures (tables, narrative paragraphs, mixed). Observable outcome: inconsistent extraction across the ten documents, with fields filled correctly from tables but empty or wrong from narrative paragraphs, and different output shapes across runs. This establishes the baseline that proves the consistency problem.

Step 2: Record which fields are consistently empty or inconsistent per document type. Observable outcome: a table or log naming the failing field-document combinations, such as dates extracted from tables but missed in narrative, or amounts inconsistent when written as words.

Step 3: Create three few-shot examples targeting the failing patterns, each with reasoning explaining the extraction decision. Observable outcome: three examples, each showing a different document structure (table, narrative, mixed) with both the correct extraction and a Reasoning: section.

Step 4: Re-run the same ten documents with the few-shot enhanced prompt and compare empty-field rate, format consistency, and accuracy. Observable outcome: a measurable reduction in empty fields on narrative documents and improved format consistency, with the largest gain on the document types that previously failed.

Step 5: Document which structural patterns benefited from few-shot and which require a different technique. Observable outcome: a decision matrix. Narrative extraction and format consistency improve with few-shot. Fabrication of missing data does not improve and needs a nullable schema instead. Malformed JSON needs a schema. Sum discrepancies need a validation loop.

Build exercise material: Worked trace of the build exercise

A concrete trace makes the measurable improvement explicit. Baseline run on ten documents: table invoices extract with total populated in 10 of 10 cases, but narrative invoices populate total in only 2 of 4 cases and funder in 1 of 4, with output shape drifting between a JSON object and a prose sentence. After adding the three narrative-structure few-shot examples from Example 3 and the nullable tax_id demonstrations from Example 5, the second run populates total on all four narrative invoices and returns null for tax_id instead of an invented value, with a stable JSON shape across all ten. The decision matrix then routes any remaining sum mismatch to the validation loop, which catches a 20 discrepancy on one document and returns the corrected total on retry. The observable, quantified delta is the proof the exam expects: few-shot moved the narrative empty-field rate from 50 percent toward zero, and the schema plus nullable demonstration removed fabrication, while the validation loop handled the arithmetic case few-shot cannot.

Resolved flagged uncertainties

The forensic analysis flagged several open questions and tensions. Each is resolved here against the live documentation.

Uncertainty 1: the example-count range (2 to 4 versus 3 to 6). Resolution: the live documentation recommends 3 to 5, our lesson exam tip says 2 to 4, and both converge on diminishing returns past four. Present 2 to 4 as the recommended start and 3 to 6 as the nuanced ceiling for harder tasks; treat four as the practical bound.

Uncertainty 2: examples versus chain-of-thought for ambiguous source or conditional logic. Resolution: this is a scope boundary, not a contradiction. Few-shot is the default for pattern and judgement inconsistency; chain-of-thought outranks examples for genuinely conditional logic and for genuinely ambiguous source text where the model must locate evidence rather than pattern-match.

Uncertainty 3: examples versus explicit criteria. Resolution: they are complementary, not opposed. Explicit criteria name the boundary; examples generalize it; the strongest fixes combine both, especially for false-positive reduction.

Uncertainty 4: the older "prompt JSON versus tool use" hierarchy. Resolution: the live documentation adds JSON outputs via output_config.format and strict tool use as separate, composable guarantees, with few-shot as a third orthogonal layer for semantics.

Uncertainty 5: whether reasoning is required in every example. Resolution: REFINED. The documentation does not mandate it but confirms reasoning patterns inside examples generalize via the thinking note; reasoning is required for ambiguous judgement examples and optional for pure format pairs.

Documentation gaps and unverified claims

This section records where the documentation is less prescriptive than the exam material, and where a claim cannot be sourced.

The live documentation does not state a hard upper bound of four examples; it says 3 to 5 for best results and invites you to ask the model to critique diversity. The exam's 2 to 4 is therefore more prescriptive than the documentation, and the candidate should treat four as the diminishing-returns point rather than a documented ceiling.

The documentation does not mandate a Reasoning: field inside every example. It supports reasoning generalization through the thinking note but stops short of requiring it. The exam's "each example must show reasoning" is therefore lesson- and forensic-supported, not documentation-mandated.

The documentation does not discuss confidence thresholds or temperature as fixes or non-fixes for inconsistent judgement. The claim that they do not repair ambiguous criteria is sound per our lessons and the forensic analysis but is not stated in the live prompting page, so it is marked lesson-supported.

Specific percentages that appear in the forensic analysis, such as a 16 to 38 percent fabrication rate on required fields when the source omits data, or a 35 to 40 percent edge-case misclassification rate with label-only examples, are derived from the forensic study of assessment material, not from official Anthropic documentation. They are cited as and must not be presented as official measured figures.

The label-balance prior strength ("comparable to adding hundreds of training examples") is phrased in our lesson and is lesson-supported rather than documented with a measured value.

Everything else in this grounding is either directly quoted from or strictly inferred from the live documentation at the URLs cited, or from the named lessons.

A further note on prescriptiveness: the live documentation does not enumerate the three exam triggers (inconsistent formatting, ambiguous judgement, empty fields for present data) as a numbered list. It states the general principle that examples improve accuracy and consistency and should be relevant, diverse, and structured, which subsumes those triggers without naming them. The exam material is more prescriptive about triggers and counts; a candidate should treat the documentation's principle as the foundation and the exam's triggers as the operational checklists built on top of it. Likewise, the documentation does not use the phrase "few-shot beats more instructions"; it expresses the same idea by ranking examples as one of the most reliable steering mechanisms and by the migration guidance that newer models follow complex schemas when told to, reducing the need for format prompting altogether.

The documentation is also silent on the precise token-cost curve of adding examples beyond four; it confirms that examples cost tokens and that a static prefix is cached, but it does not quantify the diminishing-returns point. The "diminishing returns past four" framing is therefore lesson- and forensic-supported rather than documented with a measured threshold. Candidates should answer with the principle (measure, don't assume a fixed number) and avoid presenting any specific count as an official measured figure.

Consolidated checklist for the exam

The following checklist distills the verified material into the decision rules the exam tests. Each rule is grounded in the sections above.

When detailed instructions already exist but output is still inconsistent, add two to four few-shot examples that demonstrate the exact desired format rather than more prose. This is the instruction-plateau trigger and the single most reliable fix.

When the model makes inconsistent judgement calls on ambiguous cases, require reasoning inside the examples so the model learns the principle, not the literal pair. Temperature and confidence thresholds do not repair this because they do not define the missing standard.

When extraction returns empty fields for data that exists in an unexpected container, add cross-structure examples showing extraction from the failing structure, leading with that structure. Increasing the context window or pre-processing into a canonical format are the wrong moves.

When JSON is malformed or a hard structural contract is required, use output_config.format or strict: true rather than few-shot, because the schema enforces shape on every call.

When a field is required but the source omits it, make the field nullable and add few-shot examples demonstrating null; doing only one half leaves fabrication in place.

When a sum or reconciliation fails, add a validation-retry loop that carries the specific discrepancy; a blind retry reproduces the error.

When tool selection is wrong, sharpen tool descriptions first, then add few-shot only for residual ambiguity. For genuinely conditional logic or genuinely ambiguous source text, chain-of-thought outranks examples.

Wrap examples in <example> tags inside <examples>, keep formats consistent, place the nearest match last, balance the label distribution, and keep the set static to enable caching.

Mechanism and API surface

In-context learning from 2 to 4 examples
Targeted examples pointed at the ambiguous scenarios causing problems, fewer than 2 does not establish a pattern, more than 4 wastes tokens without proportional benefit for most tasks.
Examples with reasoning over bare pairs
Each example shows input, reasoning for why one choice beats alternatives, then output, so the model learns the principle that generalises rather than the literal mapping that does not.
Placement and proximity
Examples in system prompt shape every response, examples in messages as user assistant pairs before the query shape that query. Closer to the query means stronger influence due to recency.
Ordering and label balance
Later examples influence output more, so mix classes for classification rather than grouping positives then negatives. Equal class counts produce neutral prior, skewed counts produce a prior for the majority class.
Format consistency down to punctuation
Every example uses identical formatting, a missing colon or capitalisation difference is modelled as intentional and may be reproduced in the response.
Dynamic few-shot selection
Embedding similarity, keyword overlap, or hybrid retrieval selects the most relevant examples at request time for large banks, cluster-based selection scales to 1000 or more where nearest neighbour is noisy.

The decision rules in play

Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.

R1

Instruction plateau triggers demonstration, not more prose

The diagnostic signal in this family of items is a sentence of the form "detailed instructions exist but output is still inconsistent." The model already understands the goal; it is not applying the format or the judgment consistently across invocations. Adding more prose restates the same ambiguous target. Appending two to four worked examples changes the mechanism entirely: the model now pattern-matches against demonstrated outputs instead of interpreting an abstract specification. The demonstration supplies a concrete template, so the format-consistency problem disappears at the source rather than being re-described.

Prose is inherently ambiguous for specifying precise structure. A format specification written in words leaves room for interpretation: "use a table" can still yield a table with different columns, ordering, or casing each run. An example removes the interpretation layer. The model no longer has to decide what "consistent" means; it copies a shape it has seen. This is why the same scenario reappears with code review feedback, test generation, flashcard generation, and extraction, and the correct answer is always the demonstration rather than the longer instruction.

Boundary. The boundary is whether instructions have already been iterated on and failed. If prose was never tried, a precise instruction may be the right first step. The nearby opposite case is when the failure is a missing specification entirely, such as never having told the model which categories to check: there, adding an explicit list of categories is the fix, and examples reinforce it rather than replace it. When the stem says instructions were already detailed and output is inconsistent, the answer flips hard to examples.

Recurring specifics. The recurring phrasing is "adding more detailed instructions yields inconsistent output, sometimes detailed, sometimes vague." The recurring count is two to four examples, sometimes stated as three to four, sometimes three to six. The recurring fix verb is "demonstrate" or "show the exact format." The failing artifact is almost always a free-text or lightly structured response whose shape drifts: feedback with location, issue, severity, fix; test files with one assertion style; flashcards with stable house style.

Wrong answers written against this rule

Proposal. switch to a larger or different model so it follows formatting better.

Why it attracts. the symptom looks like a capability gap.

Why it fails. the model already produces the correct shape sometimes, proving capability is present; the problem is consistency, not ability.

When it would be right. only if the model fundamentally cannot perform the task, which these stems never describe.

Proposal. add a linter or post-processing step to normalise the output.

Why it attracts. it guarantees the downstream parser gets clean data.

Why it fails. it masks the problem, adds infrastructure, and does not improve the model's behavior on novel inputs.

When it would be right. as a stopgap when the model is out of your control, but it is never the "most effective next step" in these items.

Proposal. set temperature to zero to force determinism.

Why it attracts. determinism feels like consistency.

Why it fails. a model that has not been shown the target format still produces structurally different outputs for different inputs even at temperature zero.

When it would be right. after the format is pinned by examples, low temperature can further stabilise, but it is never the primary fix.

Proposal. expand the prose with more explicit requirements.

Why it attracts. it is the natural response to imprecise communication.

Why it fails. two prior attempts already showed prose is re-interpreted differently each time.

When it would be right. only when the prose was genuinely missing the relevant detail, not when it was already detailed and still failed.

How the same rule gets re-asked
  • - Same rule, but the artifact is JSON: still demonstration, though schema also helps. - Same rule, but the team already added examples and wants more: then diversity, not count, is the lever (see Rule 18). - Same rule, but the failure is judgment not format: still examples, but they must carry reasoning (see Rule 4 and Rule 7).
R2

Format variance is a demonstration problem, not a description problem

When the same logical answer comes back in different containers, the issue is the model's uncertainty about which container to use. Prose like "output only the category label" is a weak constraint because the model can still append synonyms, verbose explanations, or different casing. A few examples that show the exact string BILLING with no surrounding text train the model to emit that exact shape. The demonstration resolves the ambiguity that prose leaves open.

Format is a surface property. The model is good at imitation. Showing it three uppercase labels with nothing else teaches the contract more reliably than any number of "output only the label" sentences. This is why the technique-selection matrix places inconsistent output formatting squarely in the few-shot column.

Boundary. The boundary is whether the inconsistency is syntactic (shape, casing, key order) or semantic (wrong category). For pure shape drift, examples are the strongest lever. The nearby opposite case is when the output must satisfy a hard structural contract, such as valid JSON with a fixed schema: there, tool_use with a schema both demonstrates and enforces, and few-shot alone is insufficient for the enforcement half (see Rule 13).

Recurring specifics. Recurring failures: verbose explanations instead of a single label; synonyms like payment issue instead of BILLING; mixed casing; varying key order in JSON; varying units. The recurring fix is three to five examples each showing a ticket followed by a single uppercase label. The recurring distractor is "add a line saying output only the label," which helps but is weaker than the demonstration.

Wrong answers written against this rule

Proposal. add "output only the category label and nothing else" to the system.

Why it attracts. it directly addresses verbosity.

Why it fails. it is still prose; synonyms and casing drift persist.

When it would be right. as a supplement to examples, not as the sole fix.

Proposal. set temperature to zero.

Why it attracts. determinism.

Why it fails. does not constrain the vocabulary or casing.

When it would be right. never as the primary fix for format drift.

Proposal. a JSON schema constraint.

Why it attracts. structurally forces the shape.

Why it fails. only when the output is already tool-shaped; for plain text labels it is the wrong tool.

When it would be right. exactly when the task is structured output (Rule 13).

How the same rule gets re-asked
  • - Same rule with JSON key order: examples plus an explicit template. - Same rule with units: examples showing the normalised unit string. - Same rule across languages: examples in the production language (Rule 29).
R3

The effective example count is two to four, rarely more

the tested material consistently places the productive range at two to four examples, with some items stretching to three to six before diminishing returns. Below two, the model cannot establish a stable pattern and may overfit to a single case. Above four to six, each added example buys little accuracy while linearly increasing token cost and latency, and large homogeneous sets can even dilute attention. The recommended practice is to start with three and measure before adding more.

In-context learning is a few-shot phenomenon. The model needs enough demonstrations to infer the pattern, but once the pattern is clear, additional similar examples are redundant. Token budget is better spent on diversity (Rule 18) than on volume. The sweet spot is the point where common cases and important edge cases are both covered.

Boundary. The boundary is the appearance of a new failure mode not covered by the existing set. When a genuinely new edge case fails, add one targeted example; do not multiply the whole set. The nearby opposite case is a simple task where zero-shot already works: there, adding examples is optional, and one example may suffice. But for nuanced formatting or judgment tasks, one example is explicitly called out as too few.

Recurring specifics. Recurring numbers: "2-4 examples with reasoning," "3-4 examples," "3-6," "start with 3," "quality over quantity." Recurring distractor counts: "1 example," "8-10," "10-20," "50+." The recurring principle is diminishing returns beyond four. The recurring token note is that fifteen near-identical examples can be collapsed to five diverse ones with equal quality.

Wrong answers written against this rule

Proposal. one example is enough.

Why it attracts. looks efficient.

Why it fails. risks overfitting to that specific case and does not cover edge cases or all classes.

When it would be right. only for the simplest pattern-matching task.

Proposal. ten to twenty examples for maximum coverage.

Why it attracts. more data feels safer.

Why it fails. diminishing returns and token bloat; near-identical examples add no information.

When it would be right. never in these items; the guidance is explicit that more is not better.

Proposal. fifty-plus examples.

Why it attracts. exhaustive coverage intuition.

Why it fails. prohibitively expensive, negligible benefit.

When it would be right. never for in-context few-shot; that territory belongs to fine-tuning.

How the same rule gets re-asked
  • - The count range shifts to three to six when the task is nuanced; the principle of diminishing returns still holds. - The count interacts with diversity: a small diverse set outperforms a large similar one (Rule 18). - The count interacts with caching: a fixed small set sits in the cacheable prefix (Rule 28).
R4

Reasoning-inclusive examples teach generalisation, bare pairs do not

An example that shows only input and output teaches the model a literal mapping: for these specific inputs, produce these specific outputs. An example that also shows the reasoning for the decision teaches the underlying principle, so the model can apply the same logic to novel inputs it has never seen. The recurring evidence is a classifier that misclassifies edge cases at a high rate when examples show only the label, and improves sharply when the examples are rewritten to include the reasoning step before the label.

The model internalises the conceptual logic demonstrated in the reasoning, not merely the surface pairing. When the reasoning names the features that drove the decision (surface meaning, coded meaning, targeted group; or context, tone, intent), the model learns to evaluate those features on new inputs. Without reasoning, it can only match on surface similarity, which fails on structurally different but conceptually similar cases.

Boundary. The boundary is whether the task involves ambiguous or edge-case judgment. For purely mechanical format demonstration (always emit three bullets), bare pairs suffice because there is no judgment to teach. The nearby opposite case is when the example must show exactly three bullets even if the source could be two: there, consistency of the output, not reasoning, is what matters, and a bare pair is correct.

Recurring specifics. Recurring phrase: "include the reasoning step between clause and label." Recurring failure rate: edge-case misclassification around 35 to 40 percent with label-only examples, dropping when reasoning is added. Recurring distractor: "examples without reasoning teach the output format, which is enough" - wrong because format and judgment are different. Recurring false claim: "reasoning in examples forces the model to emit a chain-of-thought in its output" - wrong; it teaches decision logic, it does not mandate output reasoning.

Wrong answers written against this rule

Proposal. keep label-only examples to save tokens.

Why it attracts. shorter prompt.

Why it fails. edge-case accuracy stays low; the model cannot generalise the boundary.

When it would be right. only for mechanical format tasks with no ambiguity.

Proposal. rewrite examples with reasoning to "teach the model to always return.

Why it attracts. reasoning seems to require reasoning output.

Why it fails. the benefit is decision logic, not output format; the model need not echo the trace.

When it would be right. when you explicitly want the model to show its work for auditability, but that is a separate goal.

Proposal. add more label-only examples instead of adding reasoning.

Why it attracts. more coverage.

Why it fails. more of the same mechanism that already failed.

When it would be right. never, when the gap is generalisation not coverage.

How the same rule gets re-asked
  • - Reasoning length: a one-line rationale per example is enough; a long internal monologue for every case is explicitly discouraged. - Reasoning placement: reasoning before the label, not after. - Reasoning scope: name the decisive features, do not merely restate the answer.
R5

Target the failing scenario, never the easy one

Few-shot leverage is highest where the model already struggles. Examples of clear, unambiguous inputs that the model already handles correctly add no information about the failure mode. Examples that show the ambiguous request, the messy document, or the overlapping signal teach the model the exact decision it keeps getting wrong. the tested material frames this as "target examples at the specific cases where the model struggles, not the easy cases it already handles."

The model generalises from what is shown. If the shown examples are all easy cases, the demonstrated pattern is "easy case in, correct label out," which the model already knew. The hard cases are never demonstrated, so they remain hard. Pointing examples at the failure mode concentrates the demonstration budget on the precise boundary that needs anchoring.

Boundary. The boundary is whether the existing examples already cover the failing case. If they do and the model still fails, the issue is example quality or reasoning, not coverage of the easy case. The nearby opposite case is a brand-new task with no examples at all: there, a few representative examples across the range are appropriate, including typical cases, because the model has no pattern yet. But once baseline works, add only the failing edge.

Recurring specifics. Recurring framing: tool selection for "help me with my recent purchase," an ambiguous request where either tool could apply. Recurring correct count: four to six examples for ambiguous scenarios, each showing reasoning for choosing one tool over the plausible alternative. Recurring distractor: "ten to fifteen examples of clear unambiguous requests" - wrong because clear requests need no demonstration. Recurring distractor: "group examples by tool" - wrong because it does not contrast the ambiguous boundary.

Wrong answers written against this rule

Proposal. add many examples of clear typical requests.

Why it attracts. more examples feel like more training.

Why it fails. the model already routes clear requests; the ambiguity is untouched.

When it would be right. only at cold start with zero examples.

Proposal. add "use when" and "do not use when" clauses to tool descriptions instead.

Why it attracts. the question asks about examples, but descriptions seem cheaper.

Why it fails. for genuinely ambiguous requests, prose descriptions of tools do not resolve the comparative judgment; examples with reasoning do.

When it would be right. as the first step before few-shot (Rule 15), not as a replacement for it.

Proposal. group examples by tool.

Why it attracts. organisation feels helpful.

Why it fails. it never shows the near-miss where two tools compete.

When it would be right. never for ambiguous-selection tasks.

How the same rule gets re-asked
  • - The ambiguous case may be a novel bug class resembling known ones: then reasoning examples let it generalise (Rule 4). - The ambiguous case may be a never-shown policy gap the model handles correctly: that demonstrates generalisation worked (Rule 29). - The ambiguous case may be a borderline violation versus acceptable speech: then contrastive pairs (Rule 11).
R6

Cover the failing structure, not the working one

In extraction scenarios the model often handles one document structure well, such as inline tables, and fails on another, such as narrative paragraphs, footnotes, or bibliographies. The instinct to add examples of the already-working structure is wasted. The fix is to add examples that demonstrate correct extraction from the failing structure, showing the input document shape alongside the expected output.

Few-shot generalises the demonstrated mapping. If tables are already correct, more table examples teach nothing new. Narrative or footnote examples give the model the concrete input-output mapping for the layout it mishandles, so it can apply that mapping to future documents with similar layouts. This directly addresses the trigger "empty fields for information that exists in a different format."

Boundary. The boundary is which structures currently fail. If every structure fails equally, a diverse set covering several structures is right. The nearby opposite case is when the model fails on all structures because the field definitions themselves are wrong: there, fixing the schema or instructions precedes examples. But these items always specify that the working structure already succeeds, isolating the failing one.

Recurring specifics. Recurring failing structures: narrative paragraphs versus tables; footnotes versus dedicated sections; bibliography versus inline citations; signature-block versus header placement. Recurring correct count: two to three examples, each a failing structure with its correct extraction. Recurring distractor: "expand field definitions to enumerate every section" - wrong because that is the prose approach that already failed. Recurring distractor: "pre-process documents into a canonical structure" - wrong, adds complexity and new failure modes.

Wrong answers written against this rule

Proposal. expand field definitions to name every possible location.

Why it attracts. seems thorough.

Why it fails. continues the prose approach that already plateaued.

When it would be right. as a supplement, but not the primary fix for structural variance.

Proposal. normalise documents into a canonical layout before extraction.

Why it attracts. uniform input feels cleaner.

Why it fails. adds an architectural step with its own failure modes, disproportionate to the problem.

When it would be right. only when the source formats are so wild that examples cannot cover them, which these items never claim.

Proposal. retry with vaguer instruction to "search more thoroughly.".

Why it attracts. cheap.

Why it fails. repeating the same call without new information reproduces the same empty result.

When it would be right. never; retries need specific feedback (Rule 14).

How the same rule gets re-asked
  • - The failing structure may be presentation-format variation within one document type (Rule 21). - The failing structure may be a rare layout in a long tail: then examples must span the divergent layouts, not the dominant ones (Rule 20). - The failing structure may require both nullable schema and examples when absence is involved (Rule 12).
R7

Ambiguous judgement calls need examples with reasoning, not thresholds

When the model makes inconsistent severity or flagging decisions, the root cause is the absence of an absolute, context-independent standard. A confidence threshold does not create that standard; it filters after the inconsistent judgment is made. Examples anchored to concrete code or content, each carrying the reasoning for why a case is critical versus minor, give the model a checkable reference point so ratings do not drift with surrounding context.

Severity and flagging are calibration problems. The model needs an anchor: "this specific pattern is critical, that specific pattern is minor." Prose descriptions like "critical means clearly harmful" are interpreted differently each run. A concrete example removes the interpretive step. Confidence thresholds act on the output of a flawed process and cannot fix the process; poorly calibrated self-reported confidence is explicitly called out as unreliable.

Boundary. The boundary is whether the inconsistency is cross-run (same input, different label) or within-run (different inputs, defensibly different labels). If the labels are defensibly different, examples plus criteria clarify the boundary. The nearby opposite case is when the policy itself is contradictory ("always estimate" versus "never estimate without the tool"): there, resolving the instruction conflict at the source is the fix, not examples (Rule 26).

Recurring specifics. Recurring failure: identical null-pointer risk rated critical in one run, medium in another. Recurring fix: explicit severity criteria plus concrete code examples for each level, an absolute standard not relative to other findings. Recurring distractor: a confidence gate of 0.9. Recurring distractor: relative rating within a PR (worst is critical), which guarantees cross-PR inconsistency by design. Recurring distractor: a second pass re-evaluating with the same vague criteria, which reproduces the same inconsistency.

Wrong answers written against this rule

Proposal. require a confidence score above 0.9 before reporting.

Why it attracts. looks like rigor.

Why it fails. confidence is poorly calibrated; the same miscalibration persists.

When it would be right. never as the primary fix for judgment variance.

Proposal. rate each issue relative to others in the same PR.

Why it attracts. ranks findings.

Why it fails. guarantees cross-PR inconsistency because the baseline shifts per PR.

When it would be right. never for cross-PR consistency.

Proposal. a type-to-severity table in a config file.

Why it attracts. concise.

Why it fails. too coarse; the same issue type manifests at different risk levels.

When it would be right. as a coarse helper, but concrete examples per level are still needed.

How the same rule gets re-asked
  • - The judgment may be a classification boundary between two near categories: then contrastive pairs (Rule 11). - The judgment may be a false-positive problem across categories: then temporarily disable noisy categories (Rule 25). - The judgment may be a vague instruction: then explicit categorical criteria (Rule 26).
R8

Empty fields for present data call for cross-structure examples

This is the third documented trigger. The information exists in the source, but the model returns null or wrong values because the information appears in an unexpected format, such as narrative text rather than a table. The fix is few-shot examples showing correct extraction from both the structure that works and the structure that fails, so the model learns to locate the field regardless of presentation.

The model has a structural bias: it finds data where it expects to find it. When the field sits in prose, the model's table-oriented extraction misses it and returns null. Demonstrating extraction from narrative text teaches the model that the field can live there and shows how to pull it. This is distinct from fabrication (absent data), which needs a schema change (Rule 12); here the data is present, so examples alone are sufficient.

Boundary. The boundary is whether the data is present or absent. If present but misplaced, cross-structure examples fix it. If absent, examples cannot invent a value and a nullable schema is required (Rule 12). The nearby opposite case is when the field is required and the model fabricates to fill it: there, the schema is the root cause, not example coverage.

Recurring specifics. Recurring symptom: null for fields that exist in narrative paragraphs while tables work. Recurring correct fix: examples showing extraction from both structured tables and narrative paragraphs. Recurring distractor: increase context window (data is found in tables, so size is not the issue). Recurring distractor: pre-process narrative into tables (unnecessary complexity). Recurring distractor: post-process retry on empty fields (reproduces the same empty result).

Wrong answers written against this rule

Proposal. increase the context window.

Why it attracts. more text feels like more coverage.

Why it fails. the model already finds table data; the issue is structural recognition, not capacity.

When it would be right. never for this symptom.

Proposal. convert all narrative text to tables first.

Why it attracts. uniform input.

Why it fails. adds a fragile pre-processing stage; the model should handle varied formats directly.

When it would be right. only for pathological sources the model cannot parse.

Proposal. retry empty fields with the same prompt.

Why it attracts. cheap.

Why it fails. no new information, same incorrect result.

When it would be right. never without specific feedback (Rule 14).

How the same rule gets re-asked
  • - If the same field appears as table, list, and prose within one document type, the axis is presentation-format, not document type (Rule 21). - If absence is also possible, pair examples with a nullable schema (Rule 12). - If many document types each fail, use per-type examples (Rule 20).
R9

Example distribution must mirror the output distribution

The model develops a prior from the examples it sees. If all examples belong to one class, the model over-predicts that class, including on ambiguous inputs that should land elsewhere. Balanced examples across all expected output classes, roughly proportional to the real distribution, correct the prior. An unbalanced set is explicitly named as a cause of skew.

In-context examples act as a soft distribution signal. Four positive examples and zero negative examples bias the model toward positive classification, because the demonstrated world is all positive. Balancing the set gives the model both classes to pattern-match, so ambiguous inputs are not pushed toward the over-represented label by default.

Boundary. The boundary is whether the real distribution is genuinely skewed. If one class truly dominates in production, proportionally more examples of it are fine; the mistake is showing only one class while the real task has several. The nearby opposite case is a single-class task: there, balance is irrelevant and one example type suffices.

Recurring specifics. Recurring failure: four positive examples, zero negative, leading to high false-positive positive labels. Recurring fix: add three to four negative examples to balance. Recurring rule: for binary, roughly equal; for multi-class, proportional to expected distribution or equal representation. Recurring distractor: "the model is broken, switch models" - wrong; it is a distribution artifact. Recurring distractor: "example count does not affect output" - explicitly false.

Wrong answers written against this rule

Proposal. replace the model with a larger one.

Why it attracts. blames capability.

Why it fails. the skew is example-driven, not capability-driven.

When it would be right. never here.

Proposal. add many examples of the majority class to "reinforce" it.

Why it attracts. feels like strengthening signal.

Why it fails. worsens the bias.

When it would be right. never when the task is multi-class.

Proposal. ignore distribution, tune temperature.

Why it attracts. easy knob.

Why it fails. does not change the prior the examples set.

When it would be right. never.

How the same rule gets re-asked
  • - The skew may be subtle: all clear-cut examples miss the overlapping-signal cases (Rule 5). - The skew may be within a severity scale: anchor each level with examples (Rule 7). - The skew may be a confidence artifact: confidence does not repair it (Rule 24).
R10

Negative examples mark the boundary of acceptable behavior

A negative example shows an incorrect or rejected output and explains why it is wrong. Paired with positive examples, it marks the boundary around common mistakes, teaching the model what to avoid as well as what to do. This is the "show what to ignore as well as what to flag" principle: examples that separate acceptable patterns from genuine issues cut false positives while preserving real detection.

The model learns the decision boundary from both sides. A positive example says "this is in." A negative example says "this look-alike is out, and here is why." Without the negative, the model may treat the look-alike as in. Negative examples are especially powerful for false-positive reduction because they directly demonstrate the boundary between a benign pattern and a real defect.

Boundary. The boundary is whether the negative is clearly labelled. A poorly labelled negative confuses; a clearly marked one ("incorrect:" / "correct:") helps reliably. The nearby opposite case is a task with no plausible confusion: there, positive examples alone may suffice, but adding a negative still strengthens the boundary at low cost.

Recurring specifics. Recurring correct pattern: a rejected low-value test case with an explanation of why it was excluded, shown alongside accepted high-value cases. Recurring principle: negative examples work alongside positives, not as a replacement. Recurring distractor: "negative examples replace positive ones" - wrong, the model then has nothing to contrast against. Recurring distractor: "negative examples are inert padding" - wrong, they actively steer behavior. Recurring distractor: "negative examples reliably confuse" - wrong, that is only true if mislabeled.

Wrong answers written against this rule

Proposal. use only negative examples to teach mistakes.

Why it attracts. focuses on errors.

Why it fails. without positives, the model has no correct pattern to contrast against.

When it would be right. never in isolation.

Proposal. pad with negatives for token count.

Why it attracts. looks like more signal.

Why it fails. if not clearly marked, they add noise.

When it would be right. only when clearly labelled and targeted.

Proposal. trust negatives to confuse the model.

Why it attracts. a known failure of bad negatives.

Why it fails. well-formed negatives help; confusion is a labelling bug, not a property of the technique.

When it would be right. never for clearly marked negatives.

How the same rule gets re-asked
  • - Negative plus positive at the same boundary becomes a contrastive pair (Rule 11). - Negative for a vague instruction becomes explicit criteria (Rule 26). - Negative for style becomes a brand-voice anchor (Rule 19 area).
R11

Contrastive pairs teach near-neighbor category boundaries

When two categories differ by subtle industry or policy definitions, showing one example of each in isolation is not enough; the model needs a near-identical pair where the only difference is the decisive feature. A contrastive pair places an acceptable borderline next to a genuine violation that looks almost the same, with reasoning naming the feature that separates them. This teaches the model to generalise the boundary to wordings it never explicitly saw.

Near-neighbor categories are confused precisely because their surface features overlap. A single positive example of each leaves the overlapping region ambiguous. A paired example forces the model to attend to the decisive evidence (reclaimed versus directed slur, public versus on-request data, genuine threat versus rhetorical hyperbole). The reasoning cements which feature is load-bearing, so novel phrasings that share surface form but differ on that feature are classified correctly.

Boundary. The boundary is whether the confusion is between two specific close categories or a broad open set. Contrastive pairs target a known confused pair. The nearby opposite case is a task where categories are genuinely distinct with no overlap: there, simple per-class examples suffice and contrastive pairs add little. Also, if the rules are already written but the model still treats a surface feature as sufficient evidence, contrastive pairs beat stacking more criteria (Rule 26).

Recurring specifics. Recurring pairs: hate speech versus harsh criticism; satire versus genuine violation; reclaimed slur versus directed attack; public versus on-request data availability; permitted idiom versus real defect. Recurring correct fix: four contrastive pairs, each with reasoning naming the separating feature. Recurring distractor: "sort examples by label" - does not create the near-miss contrast. Recurring distractor: "add a rule that political commentary is always allowed" - a hardcoded carve-out that misses novel cases.

Wrong answers written against this rule

Proposal. sort examples by label to establish a visual boundary.

Why it attracts. looks organised.

Why it fails. never shows the near-miss that confuses the model.

When it would be right. as a presentation aid, but not as the teaching mechanism.

Proposal. add an itemized rule list with carve-outs.

Why it attracts. explicit.

Why it fails. carve-outs cannot enumerate every novel benign phrasing; contrastive examples generalise.

When it would be right. as a supplement to contrastive pairs, not a replacement.

Proposal. tune a confidence threshold.

Why it attracts. seems precise.

Why it fails. confidence does not repair a classification boundary (Rule 24).

When it would be right. never for boundary confusion.

How the same rule gets re-asked
  • - The pair may span languages: examples in the production language (Rule 29). - The pair may be a false-positive reduction: temporarily disable noisy categories while refining (Rule 25). - The pair may need explicit criteria to anchor: combine with Rule 26.
R12

Fabrication needs a nullable schema plus null demonstrations

When a field is required and the source omits it, the schema exerts structural pressure to populate the field, so the model fabricates a plausible value. The two-part fix is: make the field nullable so returning null is legal, and add few-shot examples that demonstrate returning null (or an explicit absent marker) for absent fields, so null becomes the expected behavior. Doing only one half leaves the problem: a nullable schema with no demonstration still yields fabrication because the model defaults to filling; a demonstration with a required field still pressures fabrication.

A required field is a hard architectural constraint the model tries to satisfy. A nullable field removes the pressure but does not tell the model to use null; the model's default is to be helpful by filling. The demonstration supplies the behavioral contract: when the source is silent, return null. Together they eliminate fabrication at the source rather than detecting it after the fact.

Boundary. The boundary is whether the data is genuinely absent versus present but misplaced. If absent, nullable plus null examples is the fix. If present but in narrative form, examples alone fix it (Rule 8) and a required field is fine. The nearby opposite case is when the source always contains the field: keep it required; making it optional there risks the model skipping present values, which a separate "extract if present, null if absent" instruction addresses (Rule 158-style two-part).

Recurring specifics. Recurring fabrication rate: 16 to 38 percent on required fields when the source omits data. Recurring fix: redesign schema so the field is nullable, add a required source_excerpt that quotes supporting text, and few-shot examples returning null for absent fields. Recurring distractor: "add instruction 'only extract explicitly stated values'" - lowers but does not remove fabrication because the required constraint wins. Recurring distractor: "post-processing validation that rejects non-null" - detects after the fact, cannot recover absent data. Recurring distractor: "confidence field per value" - high-confidence fabrication passes through.

Wrong answers written against this rule

Proposal. add "do not fabricate" to the prompt.

Why it attracts. direct.

Why it fails. probabilistic instruction loses to the hard required constraint.

When it would be right. as a supplement after the schema is made nullable.

Proposal. post-validation that retries on missing fields.

Why it attracts. catches errors.

Why it fails. retries the same prompt, reproduces the same fabrication; detects after the fact.

When it would be right. only for format/structural errors, not absence (Rule 14).

Proposal. confidence score to filter fabrication.

Why it attracts. measurable.

Why it fails. fabricated values are often high-confidence; filtering catches the wrong outputs.

When it would be right. never for fabrication.

How the same rule gets re-asked
  • - The absent marker may be null, "not found", or "not disclosed": pick one and demonstrate it consistently. - A required source_excerpt anchors honesty: nullable plus excerpt plus examples is the strongest form. - If only some fields can be absent, make only those nullable, keep others required.
R13

Malformed JSON is a schema job, not a few-shot job

When the failure is invalid JSON syntax or structurally non-conforming output, the correct intervention is tool_use with a JSON schema that forces valid structure on every call. Few-shot examples improve pattern recognition but do not guarantee structural compliance; the schema does. The technique-selection matrix places malformed JSON squarely in the schema column.

A schema is a structural contract enforced by the runtime before the response is accepted. Examples are demonstrations the model may still deviate from. For syntax correctness, enforcement beats demonstration. Few-shot remains valuable for field semantics and cross-structure extraction, but the envelope is a schema responsibility.

Boundary. The boundary is syntax versus semantics. Invalid JSON or wrong types: schema. Valid JSON but empty required fields for present data: few-shot (Rule 8). The nearby opposite case is a task that is not tool-shaped, such as free-text labels: there, examples are the right tool and a schema is unavailable.

Recurring specifics. Recurring split: after adding tool use with strict schema, syntax errors disappear but some required fields are empty despite present data. Recurring correct next step: few-shot examples showing extraction from varied structures. Recurring distractor: "mark fields optional to hide the failure" - lowers data quality, avoids the root cause. Recurring distractor: "regex post-processing" - brittle across formats.

Wrong answers written against this rule

Proposal. make required fields optional to clear validation.

Why it attracts. stops errors.

Why it fails. masks the extraction gap; data that exists stays missing.

When it would be right. only when the field can genuinely be absent, paired with null examples (Rule 12).

Proposal. regex post-processing for patterns.

Why it attracts. programmatic.

Why it fails. brittle on embedded prose.

When it would be right. never as the primary fix.

Proposal. retry on validation failure.

Why it attracts. self-healing.

Why it fails. same prompt reproduces the same empty result unless feedback is specific (Rule 14).

When it would be right. when the failure is genuinely transient.

How the same rule gets re-asked
  • - Schema plus examples combine: schema enforces, examples teach semantics (cross-rule). - Schema with nullable fields handles absence (Rule 12). - Schema cannot teach cross-structure location; examples must (Rule 6).
R14

Sum or reconciliation mismatches need a validation-retry loop

When the extracted figures fail a downstream arithmetic check, such as a line-item sum not matching the stated total, the fix is a validation step that recomputes the discrepancy and feeds it back as specific error feedback, then retries. Few-shot examples do not address a reconciliation gap because the gap is not a pattern the model failed to learn; it is a consistency check the model cannot perform by demonstration alone.

Reconciliation is a computable invariant. Examples show input-output pairs, not arithmetic closure. A validation loop that returns the specific discrepancy ("sum is 120 but total states 100") gives the model information it lacked, so the retry can correct. A blind retry without specific feedback reproduces the error, which is why "retry with vague instruction" is an explicit anti-pattern.

Boundary. The boundary is whether the error is a missing-field population (few-shot) or a computational mismatch (validation loop). The nearby opposite case is empty fields for present data: there, examples fix it; a retry loop alone does not because it lacks new information. Also, retries help only for format or misread errors, not for genuine within-document conflicts, which must be surfaced and escalated (Rule 221-style).

Recurring specifics. Recurring matrix entry: "extraction sum does not match total" maps to validation-retry loop. Recurring correct retry: append the specific validator error (offending lines, expected structure) and re-prompt. Recurring distractor: generic "review and regenerate" retry, which fails. Recurring distractor: blind retry until valid, which loops without progress.

Wrong answers written against this rule

Proposal. blind retry with "regenerate valid output.".

Why it attracts. simple.

Why it fails. same prompt, same error; no new information.

When it would be right. never.

Proposal. few-shot examples of correct sums.

Why it attracts. examples fix many things.

Why it fails. does not teach arithmetic closure; the model still cannot self-reconcile.

When it would be right. only if the failure were a format pattern, not a sum.

Proposal. post-processing that overwrites the total.

Why it attracts. forces agreement.

Why it fails. corrupts source intent.

When it would be right. never.

How the same rule gets re-asked
  • - Genuine conflict (not misread) must be escalated to a human, not auto-resolved. - Specific feedback (the discrepancy) is the differentiator versus vague retry. - Validation loop pairs with paired fields that preserve both values (Rule 221 area).
R15

Fix tool descriptions before adding few-shot for selection

For wrong tool selection, the first intervention is sharper tool descriptions that state each tool's use cases and when to use it versus the alternatives. Few-shot examples are the next step, reserved for genuinely ambiguous requests where descriptions still leave the model unsure. This ordering prevents wasting example budget on cases a good description would have resolved.

Tool selection is primarily a naming and description problem. Overlapping or vague tool descriptions cause most misrouting. Clarifying the description removes the ambiguity cheaply. Few-shot is then targeted at the residual ambiguous cases, where the comparative reasoning genuinely needs demonstration. Starting with examples skips the cheaper, often sufficient fix.

Boundary. The boundary is whether the selection error is systematic (description) or residual (ambiguous). Systematic misrouting across all phrasings: fix descriptions. Residual errors only on ambiguous phrasings like "help with my recent purchase": add few-shot. The nearby opposite case is four overlapping tools that encode duplicate capability: there, consolidate the interface before prompting (Rule 208 area), because examples cannot fix an incoherent contract.

Recurring specifics. Recurring correct first step: expand tool descriptions with specific use cases and when-to-use versus other. Recurring correct second step: four to six few-shot examples for the ambiguous requests with reasoning. Recurring distractor: "add few-shot examples grouped by tool" - wrong order and wrong framing. Recurring distractor: "add a routing layer" - unnecessary complexity when description fixes suffice.

Wrong answers written against this rule

Proposal. add a routing layer choosing among tools.

Why it attracts. explicit control.

Why it fails. unnecessary complexity if descriptions would suffice.

When it would be right. only when descriptions cannot disambiguate and examples still fail.

Proposal. few-shot before descriptions.

Why it attracts. examples feel powerful.

Why it fails. wastes example budget on cases a description would fix; may still leave overlap.

When it would be right. only after descriptions are sharpened and ambiguity remains.

Proposal. force tool choice per request type.

Why it attracts. deterministic.

Why it fails. brittle when phrasing varies; bypasses the model's judgment.

When it would be right. only for fully determined request types.

How the same rule gets re-asked
  • - If tools are duplicates, consolidate first (Rule 208). - If ambiguous, few-shot with reasoning (Rule 4, Rule 5). - If selection still drifts after both, it may be a confidence artifact (Rule 24).
R16

Few-shot examples suppress extraction hallucination

When the model invents values for ambiguous or informal source text, such as converting "a cartload" to "500 kg," few-shot examples that demonstrate verbatim preservation override the default helpful-fabrication behavior. The example shows the exact input and the correct output with the informal value kept and flagged, establishing a behavioral contract that beats the instinct to fill ambiguity with inference.

Hallucinated conversion is a behavioral pattern, not a knowledge gap; the model knows it does not know the weight but defaults to being helpful. A demonstration of "extract exactly as written, flag as informal, never convert" gives a concrete alternative the model copies. This is the documented side effect: examples showing correct handling of varied structures teach the model to handle structural variety without inventing data.

Boundary. The boundary is whether the value is absent (schema, Rule 12) or present-but-informal (examples, Rule 16). If the source says "a pinch" with no equivalent, examples teaching null-or-verbatim handle it. The nearby opposite case is a required field with truly absent data: examples alone cannot make null legal; the schema must change (Rule 12). Also, if the source is genuinely ambiguous and needs a located citation, evidence-locating reasoning may beat examples (Rule 30).

Recurring specifics. Recurring hallucination: informal measurements ("a dozen," "a couple of boxes," "to taste") converted to standard units. Recurring correct fix: examples preserving verbatim and flagging informal. Recurring distractor: a conversion table in the prompt - covers known terms, misses novel ones. Recurring distractor: a clarification loop asking the user - adds latency, unnecessary when the value should just be preserved. Recurring distractor: ignore sentences with informal terms - loses data.

Wrong answers written against this rule

Proposal. a comprehensive conversion table.

Why it attracts. looks thorough.

Why it fails. cannot cover all informal terms; novel ones still hallucinate.

When it would be right. only as a supplement, never primary.

Proposal. ask the user for clarification each time.

Why it attracts. accurate.

Why it fails. unnecessary latency; the correct behavior is preserve-and-flag, not block.

When it would be right. only when the value is genuinely required and unknowable.

Proposal. ignore informal sentences.

Why it attracts. avoids fabrication.

Why it fails. drops real data.

When it would be right. never.

How the same rule gets re-asked
  • - If fabrication is for absent required fields, schema change dominates (Rule 12). - If fabrication is high-confidence, confidence filtering fails (Rule 24). - If the value needs a source quote, pair with source_excerpt (Rule 12).
R17

Recency and ordering bias reward placing the nearest example last

The model exhibits recency bias: the last few examples in the prompt have stronger influence on the next output than earlier ones. Therefore, ordering examples so the one most similar to the actual target input appears last leverages this bias productively. Equally important, every example must use consistent formatting, because any variation in example format creates ambiguity about which format the output should follow.

Attention is not uniform across the context. The demonstration closest to the prediction point shapes the output most. Placing the nearest-match last means the model exits the demonstration phase primed on the relevant pattern. Consistent formatting across all examples prevents the model from latching onto a stray format variant shown once.

Boundary. The boundary is whether the target input has a clearly nearest example. If examples are diverse with no single nearest, consistent formatting matters more than ordering. The nearby opposite case is primacy bias for an inconsistent example: placing a malformed example first gives it extra weight and worsens the problem (recall the item where an inconsistent sixth example broke a working set; moving it first made things worse). So the rule has a twin: never give the bad example primacy.

Recurring specifics. Recurring guidance: "most similar to the target input last, with consistent formatting throughout." Recurring failure: an inconsistent example introduced into a working set causes drift; placing it first (primacy) makes it worse. Recurring correct fix: remove the inconsistent example, ensure all demonstrate the same format. Recurring distractor: random order to "prevent order-based patterns" - suboptimal because recency can be exploited.

Wrong answers written against this rule

Proposal. random example order.

Why it attracts. feels fair.

Why it fails. wastes the exploitable recency bias.

When it would be right. only if all examples are equally relevant and format is uniform.

Proposal. difficult examples first.

Why it attracts. primes hard cases.

Why it fails. does not exploit recency for the target input.

When it would be right. never for consistency goals.

Proposal. diversity first.

Why it attracts. range.

Why it fails. does not optimize for the target's nearest match.

When it would be right. as a coverage strategy, but order the nearest last anyway.

How the same rule gets re-asked
  • - Ordering interacts with count: a small set is easier to order well (Rule 3). - Ordering interacts with formatting: inconsistent formats defeat ordering (Rule 2). - Ordering interacts with caching: a fixed ordered set sits in the prefix (Rule 28).
R18

Diversity beats raw example count

A handful of representative examples that include edge cases outperforms a large set of near-identical happy-path examples. The model benefits from variety, not repetition. Adding twenty more examples of the same pattern cannot fix edge-case failures the examples never demonstrate; replacing redundant examples with a few that exercise the failing edge cases is the real lever.

In-context learning saturates on a pattern quickly. Once the model has the happy-path pattern, additional copies teach nothing. Edge-case failures require the edge case to be demonstrated. Diversity across the input range is what drives generalization on hard inputs; redundancy does not.

Boundary. The boundary is whether the existing set already covers the failing edge. If it does and the model still fails, the issue is reasoning or criteria, not diversity. The nearby opposite case is a task with genuinely diverse real inputs and a too-small set: there, adding diverse examples is correct, but the cap remains two to four optimal, three to six at most.

Recurring specifics. Recurring failure: growing from six to sixty near-identical examples with flat accuracy on unusual inputs. Recurring fix: replace redundant examples with diverse edge-case ones. Recurring principle: fifteen similar examples collapse to five diverse ones with equal quality. Recurring distractor: "duplicate the best example many times" - reinforces one pattern, ignores others. Recurring distractor: "add more happy-path examples past one hundred" - worst case.

Wrong answers written against this rule

Proposal. duplicate the highest-performing example.

Why it attracts. reinforces success.

Why it fails. ignores uncovered edge cases.

When it would be right. never for edge-case gaps.

Proposal. keep adding similar examples until the pattern generalizes.

Why it attracts. persistence.

Why it fails. the pattern already generalized; the gap is coverage, not repetition.

When it would be right. never.

Proposal. remove all examples and rely on one instruction.

Why it attracts. simplicity.

Why it fails. loses the working demonstration.

When it would be right. only if examples were actively harming, which is rare.

How the same rule gets re-asked
  • - Diversity interacts with count: diverse small set beats large similar set (Rule 3). - Diversity interacts with caching: a fixed diverse set is cacheable (Rule 28). - Diversity interacts with targeting: cover the failing edge (Rule 5).
R19

Examples must be representative, not maximal or out-of-domain

A good example mirrors the real production input: medium length, representative content, correct output, clearly demonstrating the pattern. Overly long or complex examples waste tokens and may confuse by including irrelevant detail. Out-of-domain examples confuse the model and do not help the target task. The example should demonstrate the classification or extraction pattern without noise.

The model generalizes from examples that resemble the task. An example far longer than production inputs introduces irrelevant structure the model may imitate. An example from a different domain teaches a pattern that does not transfer and can dilute the target signal. Representative brevity keeps the demonstration focused on the decision.

Boundary. The boundary is whether the example still covers the needed edge. Edge cases are important, but the set should cover the full range including typical cases, not only the hardest. The nearby opposite case is an example that is too short to show the pattern: there, a slightly longer representative example is better, but it should still mirror production length.

Recurring specifics. Recurring finding: medium-length representative examples work best; very long ones waste tokens and confuse. Recurring correct: a representative input with the correct output that clearly demonstrates the pattern. Recurring distractor: the longest possible pair for maximum context - wrong. Recurring distractor: an example from a completely different domain - wrong. Recurring distractor: only the most complex edge case - wrong, misses typical cases.

Wrong answers written against this rule

Proposal. use the longest possible input-output pair.

Why it attracts. more context.

Why it fails. includes irrelevant detail, wastes tokens.

When it would be right. never.

Proposal. use an example from a different domain to test generalization.

Why it attracts. robustness intuition.

Why it fails. confuses the model on the target task.

When it would be right. never for in-context examples.

Proposal. only the hardest edge case.

Why it attracts. stress test.

Why it fails. misses typical cases the model sees most.

When it would be right. only as part of a full-range set.

How the same rule gets re-asked
  • - Representative length interacts with real-input length: if production is long, examples must be long too (Rule 155 area). - Representative content interacts with language: match the production language (Rule 29). - Representative style interacts with negative examples for boundaries (Rule 10).
R20

Per-type dynamic example selection avoids conflicting patterns

When one prompt must serve multiple document types and the model works on some but not others, the effective design is to classify the document type first, then select the relevant examples for that type, keeping the example count low and the patterns coherent. A single static prompt mixing all types raises token cost on every call and forces the model to reconcile conflicting extraction patterns at once, which is the failure mode dynamic selection avoids.

Different document types imply different field layouts and extraction logic. Showing invoices, contracts, and emails together in one static prompt gives the model three conflicting patterns to blend, so contracts and emails suffer. Selecting per type gives each call a coherent, relevant demonstration set without paying for all types every time.

Boundary. The boundary is whether the types are genuinely different in structure. If they share structure, one diverse set may suffice. The nearby opposite case is when a single generic prompt already works across types: there, dynamic selection adds routing cost for no gain. But when per-type accuracy gaps appear, per-type examples are the standard remedy.

Recurring specifics. Recurring failure: examples all invoices, contracts and intake forms lag. Recurring fix: type-specific few-shot examples, two to three per type, or type-specific prompts/schemas. Recurring distractor: "add more invoice examples" - wrong, more of the working type. Recurring distractor: "remove the failing types from the pipeline" - abandons needed data. Recurring distractor: "one static prompt with all types" - the failure mode itself.

Wrong answers written against this rule

Proposal. one static prompt covering all types.

Why it attracts. simplicity.

Why it fails. conflicting patterns, token waste, lower accuracy on non-dominant types.

When it would be right. only when types share structure.

Proposal. remove the lagging types.

Why it attracts. clean metrics.

Why it fails. loses real data.

When it would be right. never.

Proposal. more examples of the dominant type.

Why it attracts. reinforce success.

Why it fails. does not demonstrate the lagging types.

When it would be right. never.

How the same rule gets re-asked
  • - Per-type selection interacts with presentation-format variation within a type (Rule 21). - Per-type selection interacts with caching: a stable per-type set is cacheable (Rule 28). - Per-type selection interacts with the long tail of rare layouts (Rule 6).
R21

Presentation-format variation is its own axis of coverage

Beyond document-type variation, the same logical field can appear as a table, a bulleted list, or embedded prose within what is nominally one document type. Few-shot coverage must address this presentation-format axis explicitly: examples should show the same field extracted from table, list, and prose so the model locates the value regardless of how it is presented. Assuming one presentation format leaves the other presentations failing.

The model keys on presentation cues. A total in a table is found differently from a total in a prose paragraph. Few-shot that only shows the table presentation teaches the table path. Adding the list and prose presentations teaches the model that the field is the target regardless of container, generalizing the extraction to whatever presentation appears.

Boundary. The boundary is whether the variation is across types or within a type. Across types: per-type examples (Rule 20). Within a type, across presentations: presentation-format examples. The nearby opposite case is a single presentation always used: there, one example suffices and adding presentations is unnecessary.

Recurring specifics. Recurring framing: pricing exhibits as table in one counterparty format, list in another, prose in a third. Recurring correct: few-shot covering the variation in presentation, plus a prompt that anticipates "pricing may appear as table, list, or prose." Recurring distractor: "reject agreements not matching a single standard template" - excludes real needed data. Recurring distractor: "separate pipeline per counterparty" - does not scale.

Wrong answers written against this rule

Proposal. reject any format not matching the standard template.

Why it attracts. uniform input.

Why it fails. excludes real counterparties and their data.

When it would be right. never.

Proposal. a completely separate pipeline per counterparty.

Why it attracts. clean isolation.

Why it fails. does not scale; every new counterparty needs a new pipeline.

When it would be right. never.

Proposal. only document-type examples.

Why it attracts. reuses Rule 20.

Why it fails. misses the within-type presentation axis.

When it would be right. only when presentation is constant within each type.

How the same rule gets re-asked
  • - Presentation variation combines with per-type selection (Rule 20). - Presentation variation combines with absence handling when the field is sometimes missing (Rule 12). - Presentation variation combines with cross-structure empty fields (Rule 8).
R22

Chain-of-thought beats examples for conditional logic

For complex conditional extraction, such as "if product is discontinued and has pending orders, apply credit; otherwise refund," instructing the model to reason through each condition explicitly before producing output is more effective than few-shot examples. The model needs to reason, not just pattern-match; examples help recognition but are less effective than explicit step-by-step reasoning for novel conditional combinations.

Conditional logic is compositional. A few-shot example shows one resolved case; it does not teach the model to evaluate arbitrary condition combinations it has not seen. Chain-of-thought transforms implicit pattern-matching into explicit evaluation of each condition, which generalizes to novel combinations. This is a documented exception where the reasoning technique outranks demonstration.

Boundary. The boundary is whether the task is conditional reasoning or pattern recognition. Conditional logic with novel combinations: chain-of-thought. Pattern recognition or missing-data extraction: few-shot. The nearby opposite case is simple missing-field extraction: there, examples are the fix and chain-of-thought is less direct (Rule 8). Also, for genuinely ambiguous source material, evidence-locating reasoning reduces hallucination (Rule 30).

Recurring specifics. Recurring correct fix: "before extracting conditional values, think through each condition explicitly, showing your reasoning." Recurring distractor: "add more similar conditional extractions in few-shot format" - less effective than reasoning. Recurring distractor: "break into separate calls per field" - loses the cross-field context the condition needs. Recurring distractor: "upgrade the model" - more capable but still needs the reasoning prompt.

Wrong answers written against this rule

Proposal. add more few-shot conditional examples.

Why it attracts. examples fix many things.

Why it fails. less effective than reasoning for novel combinations.

When it would be right. only for pattern recognition, not novel conditionals.

Proposal. separate calls per field.

Why it attracts. isolation.

Why it fails. loses the cross-field context the condition depends on.

When it would be right. never for dependent conditions.

Proposal. upgrade the model.

Why it attracts. capability.

Why it fails. does not add the reasoning structure.

When it would be right. only as a supplement.

How the same rule gets re-asked
  • - Conditional logic may need evidence locating when source is ambiguous (Rule 30). - Conditional logic may need a schema with paired fields for conflicts (Rule 221 area). - Conditional logic combines with examples for the surrounding format (Rule 2).
R23

Temperature and confidence do not repair ambiguous criteria

When classification or severity variance is rooted in ambiguous criteria, lowering temperature or raising a confidence threshold does not fix the root cause. Temperature reduces sampling randomness but does not define the missing standard; the model still interprets the vague criterion differently each run. A confidence threshold filters the output of a flawed process and cannot create the absolute standard that is absent. The fix is explicit criteria plus concrete examples (Rule 7, Rule 26).

Inconsistency from ambiguous criteria is a specification gap, not a sampling gap. Lower temperature makes the model deterministically wrong in the same way; it does not supply the missing definition. Confidence is computed after the decision and inherits the same ambiguity. Only an observable standard (criteria anchored by examples) removes the variance.

Boundary. The boundary is whether the variance is sampling noise or criteria ambiguity. If truly random token variance on an already-clear task, low temperature helps. These items describe criteria ambiguity, where temperature fails. The nearby opposite case is a task where the format is already pinned by examples: there, low temperature can further stabilise, but it is never the primary fix for criteria variance.

Recurring specifics. Recurring failure: "be consistent and conservative" added, no measurable reduction in run-to-run disagreement. Recurring fix: explicit per-tier criteria plus concrete examples, one per level. Recurring distractor: temperature zero. Recurring distractor: confidence above 0.8 or 0.9 gate. Recurring distractor: "expand the context window" - unrelated to criteria.

Wrong answers written against this rule

Proposal. set temperature to zero.

Why it attracts. determinism.

Why it fails. does not define the missing standard; same ambiguous interpretation, deterministically.

When it would be right. only after criteria and examples pin the format.

Proposal. confidence gate at 0.9.

Why it attracts. looks rigorous.

Why it fails. confidence inherits the ambiguity; miscalibrated (Rule 24).

When it would be right. never for criteria variance.

Proposal. expand context window.

Why it attracts. more info.

Why it fails. the issue is the criterion, not information.

When it would be right. never here.

How the same rule gets re-asked
  • - Ambiguous criteria may need explicit categorical definitions (Rule 26). - Ambiguous criteria may need per-level examples (Rule 7). - Ambiguous criteria may be a confidence artifact (Rule 24).
R24

Self-reported confidence is poorly calibrated

The model's self-reported confidence score is not a reliable signal of correctness. It can be high on fabricated or misclassified outputs and low on correct ones. Therefore, using a confidence threshold to filter false positives, fabrication, or low-quality extractions is an explicit anti-pattern; the threshold filters the wrong population. Structural fixes (criteria, schema, examples) are required instead.

Confidence reflects the model's own assessed certainty, which does not track ground truth. A fabricated value is produced with high apparent confidence because the model constructed something plausible. A genuine false positive may be flagged with high confidence. Relying on the score moves the wrong lever; the score cannot substitute for an observable standard or a nullable schema.

Boundary. The boundary is whether confidence is used for routing or for fixing. As a routing hint it is weak; as a sole fix it fails. The nearby opposite case is a well-calibrated score against labeled data: there, thresholding can help, but these items describe uncalibrated self-report, where it does not. Also, confidence can be a useful diagnostic signal when paired with pattern capture (Rule 215 area), but not as a quality gate.

Recurring specifics. Recurring principle: "self-reported confidence from the generating model is not calibrated to output quality." Recurring failure: confidence above 0.75 still escalates ordinary items. Recurring distractor: "raise threshold to 0.95" - still miscalibrated. Recurring distractor: "per-category calibrated threshold" - still inherits the underlying miscalibration. Recurring distractor: "confidence field per extracted value" - high-confidence fabrication passes.

Wrong answers written against this rule

Proposal. raise the confidence threshold.

Why it attracts. tighter filter.

Why it fails. miscalibrated; wrong population filtered.

When it would be right. never as sole fix.

Proposal. per-category confidence calibration.

Why it attracts. nuanced.

Why it fails. still built on uncalibrated self-report.

When it would be right. only with labeled recalibration, which these items do not assume.

Proposal. confidence field to catch fabrication.

Why it attracts. measurable.

Why it fails. fabricated values are often high-confidence.

When it would be right. never.

How the same rule gets re-asked
  • - Confidence interacts with criteria variance (Rule 23). - Confidence interacts with fabrication (Rule 12, Rule 16). - Confidence interacts with false-positive trust erosion (Rule 25).
R25

High false-positive categories erode trust in accurate ones

When a few high-false-positive categories (such as style, naming, documentation) contaminate a review stream, developers begin dismissing all findings unread, including the accurate high-precision categories (security, correctness). The effective fix is to temporarily disable the noisy categories while improving their prompts offline, immediately restoring the signal-to-noise developers experience, then re-enable once precision is acceptable. Few-shot examples nudge behavior but do not guarantee compliance and cannot restore trust while the noise continues.

Trust is a system-level property of the whole stream. A 50 percent false-positive rate on noisy categories poisons the perceived reliability of the 8 percent-false-positive accurate categories. Disabling the noisy ones removes the contamination immediately, letting the accurate categories demonstrate their value. Examples alone improve probabilistically and slowly, during which developers keep dismissing everything.

Boundary. The boundary is whether the false positives are concentrated in identifiable categories. If spread uniformly, per-category disable is less clean and criteria-plus-examples across all is the path. The nearby opposite case is a single category with moderate false positives: there, refine prompts with examples rather than disable. Also, permanently silencing a category is wrong; the move is temporary disable, then improve, then re-enable.

Recurring specifics. Recurring rates: security and correctness 8 percent false positive; style and naming 52 percent; documentation 48 percent. Recurring fix: temporarily disable style, naming, documentation; run only high-precision categories; improve prompts offline. Recurring distractor: show confidence scores per finding - developers still dismiss unread. Recurring distractor: keep all categories and add few-shot gradually - too slow, trust already eroded. Recurring distractor: uniform strictness reduction - lowers overall rate but keeps contamination.

Wrong answers written against this rule

Proposal. show confidence scores per finding.

Why it attracts. lets developers decide.

Why it fails. adds a decision step to a workflow where developers already dismiss everything; scores miscalibrated.

When it would be right. never as the trust fix.

Proposal. keep all categories, add few-shot over weeks.

Why it attracts. gradual improvement.

Why it fails. trust keeps eroding during the slow fix.

When it would be right. only after noisy categories are quarantined.

Proposal. uniform strictness reduction.

Why it attracts. lowers aggregate rate.

Why it fails. still mixes noisy and accurate; contamination remains.

When it would be right. never.

How the same rule gets re-asked
  • - Trust erosion may need explicit report/skip criteria (Rule 26). - Trust erosion may need contrastive examples for the noisy category (Rule 11). - Trust erosion interacts with confidence miscalibration (Rule 24).
R26

Explicit categorical criteria anchor vague instructions

Vague qualitative instructions like "flag anything suspicious" or "be conservative" leave the model to interpret an unbounded criterion, producing high false positives and inconsistency. The fix is to replace the vague instruction with explicit categorical criteria that name exactly what constitutes a reportable finding versus an accepted pattern, often paired with few-shot examples that contrast a genuine issue against a compliant look-alike so the boundary generalizes.

A vague instruction is interpreted through the model's priors, which over-trigger on surface features. Explicit criteria give a binary, checkable test: "only flag if the query concatenates un-sanitized variables from external requests." That test removes interpretive freedom. Examples then generalize the boundary to unseen code. This is why the technique matrix pairs "wrong tool selection" and "vague criteria" with concrete criteria, and few-shot with ambiguous judgment.

Boundary. The boundary is whether the instruction is genuinely vague or merely missing. If missing entirely, add the explicit list. If vague, replace with criteria. The nearby opposite case is a contradictory instruction set (two rules that conflict): there, resolve the conflict at the source and define the no-evidence fallback, because neither examples nor criteria repair a contradiction (Rule 126 area).

Recurring specifics. Recurring vague phrasing: "seems suspicious," "be conservative," "use your best judgement." Recurring correct fix: explicit categorical criteria plus contrastive few-shot. Recurring distractor: "raise confidence threshold" - does not operationalize the criterion. Recurring distractor: "add more examples of violations" - stacks more of the same vague signal. Recurring distractor: "lower temperature" - does not define the boundary.

Wrong answers written against this rule

Proposal. raise the confidence threshold.

Why it attracts. precision feel.

Why it fails. vague criterion remains; confidence miscalibrated.

When it would be right. never as primary.

Proposal. add more violation examples.

Why it attracts. more signal.

Why it fails. does not define the boundary; may still over-trigger on surface features.

When it would be right. only paired with explicit criteria.

Proposal. lower temperature.

Why it attracts. determinism.

Why it fails. does not specify what constitutes a violation.

When it would be right. never for vague criteria.

How the same rule gets re-asked
  • - Vague criteria may need contrastive pairs (Rule 11). - Vague criteria may need per-class report/skip lists (Rule 188 area). - Vague criteria may need temporary category disable while refined (Rule 25).
R27

Examples need clean input/output demarcation

For the model to learn the transformation, each example must be clearly separated into its input and its output, and the real query must be distinguishable from the examples. Consistent markers such as Input: and Output:, or <example> tags around each pair with a distinct separator before the actual task, prevent the model from treating the final input as another example or blending examples together.

Few-shot learning relies on the model recognizing the input-to-output mapping. If examples blur into each other or the real query looks like another example, the model stays in demonstration mode and may echo a pattern instead of predicting. Clear demarcation switches it from demonstration to prediction at the right point.

Boundary. The boundary is whether the examples and query are visually distinct. If the prompt already uses clear tags, no change is needed. The nearby opposite case is a single example with no separation: there, adding markers is the fix, not adding more examples.

Recurring specifics. Recurring correct: clear Input: / Output: labels and a transition phrase like "Now classify the following." Recurring failure: all examples blended into one paragraph so the pattern is unrecognized. Recurring distractor: "place examples in the system prompt, query in user message" - helps but separation within still matters. Recurring distractor: "always exactly five examples" - count is irrelevant to demarcation.

Wrong answers written against this rule

Proposal. blend examples into one paragraph.

Why it attracts. compact.

Why it fails. model cannot distinguish the mapping.

When it would be right. never.

Proposal. rely on message boundaries alone.

Why it attracts. API structure.

Why it fails. within a single message, labels still matter.

When it would be right. as a supplement to in-message markers.

Proposal. number examples.

Why it attracts. order.

Why it fails. numbering does not separate input from output.

When it would be right. never as the primary mechanism.

How the same rule gets re-asked
  • - Demarcation interacts with ordering (Rule 17): nearest last, then separator. - Demarcation interacts with count (Rule 3): a small clean set beats a large blurry one. - Demarcation interacts with caching (Rule 28): a fixed marked set is cacheable.
R28

Static curated examples enable prompt caching

When the few-shot examples are fixed and sit in the static prefix of the prompt, the prefix stays byte-identical across calls, which enables prompt caching and a real cost saving. Rotating fresh random examples into the prefix on every call mutates it and defeats caching, trading a certain saving for uncertain coverage gain. The disciplined design keeps curated examples static and appends only the item to classify.

Caching keys on the prefix content. A stable prefix is cached and reused; a changing prefix is recomputed each call. Rotating examples every call changes the prefix, so no cache hit occurs, raising cost without a demonstrated accuracy benefit. Fixed curated examples are both effective and cache-friendly.

Boundary. The boundary is whether the examples are truly static. Dynamic per-type selection (Rule 20) intentionally varies the prefix per call, which is a deliberate trade for relevance; there, caching is per-type, not global. The nearby opposite case is a genuinely static set: keep it static to capture the cache.

Recurring specifics. Recurring correct: fixed curated examples in the static portion, item appended at the end. Recurring distractor: "rotate fresh examples each call to improve coverage" - defeats caching. Recurring note: examples in the system prompt cost the same tokens as in messages, so caching is the saving, not location.

Wrong answers written against this rule

Proposal. rotate examples every call.

Why it attracts. freshness.

Why it fails. defeats caching, raises cost, uncertain gain.

When it would be right. never when a fixed set works.

Proposal. move examples to the system prompt to save cost.

Why it attracts. location myth.

Why it fails. tokens cost the same; only prefix stability enables caching.

When it would be right. only if it also stabilizes the prefix.

Proposal. generate examples per call.

Why it attracts. adaptability.

Why it fails. no cache, high cost.

When it would be right. only under dynamic selection with accepted trade-off.

How the same rule gets re-asked
  • - Static set interacts with count (Rule 3): keep the optimal small set static. - Static set interacts with per-type selection (Rule 20): cache per type instead. - Static set interacts with ordering (Rule 17): fix the order too.
R29

Examples generalise across language and domain

Few-shot examples demonstrate the underlying judgment, not just the surface case, so the model can apply the learned principle to a novel language or domain it never saw in the examples. For multilingual classification, examples in the production language calibrate directly without translation overhead. For cross-domain synthesis, examples teaching conflict resolution on one topic let the model handle a different topic by applying the same attribution reasoning.

Reasoning-inclusive examples (Rule 4) encode the decision logic. That logic is language- and domain-independent: the features that drive a decision (source attribution, conflict annotation) transfer. A Spanish-language example teaches the pattern in the linguistic context the model will actually see, which is more reliable than a language-agnostic instruction.

Boundary. The boundary is whether the new context shares the decision logic. If the logic differs, new examples are needed. The nearby opposite case is a task requiring language-specific idiom the model cannot transfer: there, examples in that language are necessary, not optional.

Recurring specifics. Recurring correct: Spanish-language few-shot examples for Spanish inputs. Recurring demonstration: a synthesis subagent with finance examples handling a public-health conflict well by applying attribution. Recurring distractor: "add language-agnostic instruction" - weaker than in-language examples. Recurring distractor: "pre-translate all inputs" - added overhead, examples avoid it.

Wrong answers written against this rule

Proposal. language-agnostic instruction.

Why it attracts. one prompt for all languages.

Why it fails. weaker calibration than in-language examples.

When it would be right. only as a supplement.

Proposal. pre-translate inputs to English.

Why it attracts. uniform pipeline.

Why it fails. translation overhead; examples in the source language avoid it.

When it would be right. only when translation is already free.

Proposal. assume the model memorized the example topic.

Why it attracts. seems like generalization.

Why it fails. the win is judgment transfer, not topic memory.

When it would be right. never; the mechanism is logic transfer.

How the same rule gets re-asked
  • - Generalisation interacts with reasoning inclusion (Rule 4). - Generalisation interacts with contrastive pairs for boundaries (Rule 11). - Generalisation interacts with representation matching (Rule 19).
R30

Genuine source ambiguity needs evidence-locating reasoning

When the source material itself is genuinely ambiguous, such as overlapping dates in historical letters or conflicting signals in a long document, the most effective technique is to instruct the model to locate each field in the document and surface ambiguities before committing, rather than to pattern-match from examples. Evidence-locating reasoning grounds the output in the source and reduces hallucinated values for absent or ambiguous fields. This is a documented exception where chain-of-thought extraction outranks few-shot for ambiguous source.

Ambiguity lives in the source, not in the model's pattern. Examples show resolved cases but do not teach the model to interrogate uncertain source text. Reasoning that asks "what contextual clues support this reading, what alternatives exist, what is the most defensible extraction, and flag if uncertain" produces grounded output and a clear review path. Few-shot alone leaves the model guessing on genuinely unclear source.

Boundary. The boundary is whether the ambiguity is in the source or in the model's behavior. Source ambiguity: reasoning. Model inconsistency on clear source: few-shot. The nearby opposite case is missing but present data in a clear structure: there, cross-structure examples fix it (Rule 8), and reasoning is less direct.

Recurring specifics. Recurring correct: "for each name and date, reason through contextual clues, alternatives, and flag as uncertain if no confident determination." Recurring distractor: "very low temperature for the most likely extraction" - does not resolve ambiguity. Recurring distractor: "increase max_tokens" - more room, not more grounding. Recurring distractor: "few-shot only" - less effective than locating reasoning for genuine ambiguity.

Wrong answers written against this rule

Proposal. set temperature to zero.

Why it attracts. determinism.

Why it fails. does not resolve source ambiguity; just picks one reading deterministically.

When it would be right. never for genuine ambiguity.

Proposal. increase max_tokens.

Why it attracts. more room.

Why it fails. room does not add grounding.

When it would be right. never.

Proposal. few-shot only.

Why it attracts. examples fix many things.

Why it fails. less effective than evidence-locating reasoning for ambiguous source.

When it would be right. only when the source is clear and the model is inconsistent.

How the same rule gets re-asked
  • - Locating reasoning interacts with chunking for long docs (Rule 219 area). - Locating reasoning interacts with conditional logic (Rule 22). - Locating reasoning interacts with null demonstration for absence (Rule 12).
Email triage that stabilises only after reasoning-annotated examples
A production walkthrough with the reasoning chain made explicit.

A customer support triage classifies inbound email as refund_request, billing_question, or general_inquiry based on primary concern. The initial prompt lists detailed prose rules and works on explicit cases, but ambiguous emails such as a billing question with refund language produce inconsistent classification across runs, sometimes refund_request and sometimes billing_question on the same email in consecutive invocations.

Adding more precise prose does not converge the output because prose still relies on interpretation. The correct fix is three few-shot examples with reasoning. The first example shows an email that explicitly requests a refund with I would like my money back classified as refund_request, with reasoning that the explicit refund request is the primary intent and the requested action. The second shows an email that mentions a refund in passing but asks Why is my bill higher this time, please do not make me request a refund classified as billing_question, with reasoning that the primary intent is understanding the bill and the refund mention is conditional. The third shows a general inquiry with no billing or refund content classified as general_inquiry with reasoning that neither keyword appears. With these examples the model generalises that primary intent is what the customer asks the agent to do, not which keywords appear.

A second scenario shows the same mechanism for code review. Variable shadowing is flagged inconsistently as minor in one file and critical in another. Two examples demonstrate the principle with reasoning, shadowing within an arrow function scope classified as minor because the shadow is limited and does not cause a bug, and shadowing across a function boundary that hides a parameter classified as critical because it creates real risk. The model generalises that severity depends on scope and whether the shadow hides something important, and consistency improves across files without a new rule per file.

The hygiene lesson is durable. Mixing classes rather than grouping positives then negatives prevents recency bias toward the last group, keeping formatting identical prevents the model from learning the inconsistency itself, and placing the examples near the query maximises their influence for the next turn.

Distinctions that decide answers

ThisNot thisHow to tell them apart
Zero-shotFew-shotZero-shot relies on instructions alone, few-shot shows examples. Few-shot is more reliable for format sensitive tasks, zero-shot is cheaper for simple transformations.
Few-shot examplesMore detailed instructionsExamples demonstrate the pattern via input-output pairs, more instructions describe the pattern in prose. Examples eliminate interpretation variance, prose reduces it but does not eliminate it.
Examples with reasoningExamples without reasoningWith reasoning the model learns the decision principle and generalises to novel patterns, without reasoning it learns surface pattern matching that may not transfer.
Static few-shotDynamic few-shot selectionStatic uses a fixed set baked into the prompt, dynamic retrieves the most similar examples at request time using embedding or keyword search. Dynamic scales to large example banks, static is simpler to maintain.
Few-shot for classificationSchema changes for fabricationFew-shot teaches consistent extraction from varied structures, schema changes such as making fields optional or nullable prevent fabrication of absent values. The two address different problems and often stack.
Few-shot examplesConfidence thresholdsFew-shot teaches the correct judgement for ambiguous cases, confidence thresholds filter by self-reported certainty which is poorly calibrated. Few-shot fixes judgement, confidence fixes routing.

Traps

More instructions for inconsistent formatting

The tempting answer. Add more detailed prose when detailed instructions already produce inconsistent output structures across invocations.

Why it fails. Prose descriptions still rely on interpretation and more precise prose does not eliminate interpretation ambiguity.

What is correct. Add few-shot examples demonstrating the exact desired format, with reasoning when the format depends on a judgement.

Examples as literal templates only

The tempting answer. Treat input-output pairs as templates the model copies rather than principles it generalises.

Why it fails. Bare pairs teach surface matching, so novel phrasing fails when the literal token does not appear.

What is correct. Include reasoning for why each decision was made alongside the pair, so the model learns the principle behind the mapping that transfers to new inputs.

Confidence thresholds for inconsistent judgement

The tempting answer. Filter inconsistent judgement calls with a confidence cutoff instead of teaching the judgement.

Why it fails. Thresholds are poorly calibrated and do not address the root cause of inconsistency.

What is correct. Show few-shot examples demonstrating the correct judgement for ambiguous cases, with reasoning that names the decision boundary.

Grouped examples by class for classification

The tempting answer. Present all positives first then all negatives because grouping feels organised.

Why it fails. Later examples have more influence than earlier ones, grouping biases the model toward the last group seen and a 5 to 10 percent accuracy swing is common.

What is correct. Mix classes so no class dominates the recency window.

More examples instead of well-chosen ones

The tempting answer. Add many more examples to feel thorough rather than selecting a small well-chosen set.

Why it fails. Two to four well-chosen examples covering the standard case and key edge cases are enough, beyond that token cost rises without proportional benefit and system instructions may be diluted.

What is correct. Keep 2 to 4 targeted examples covering standard plus edge, then verify generalisation on a novel case.

Format inconsistency across examples

The tempting answer. Treat small formatting differences such as a missing colon or inconsistent capitalisation as harmless.

Why it fails. The model treats inconsistency as intentional pattern and may reproduce it, so a colon drop in one example becomes a random colon drop in output.

What is correct. Use identical formatting down to punctuation across every example.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
Selection strategies for which examples to include

Representative sampling for common patterns, edge case emphasis when edge correctness matters more than average, diversity maximisation for varied output styles, and difficulty curriculum from easiest to hardest to prime simple patterns before complex ones.

Few-Shot Prompting
Dynamic retrieval methods and bank size tradeoffs

Embedding similarity for semantic matches, keyword overlap with TF-IDF or BM25 for term matches, hybrid for both signals, and cluster-based selection for very large banks of 1000 or more where nearest neighbour is noisy.

Few-Shot Prompting
Few-shot combined with chain-of-thought for judgement tasks

Examples that pair input, reasoning trace, and output teach both format and decision principle in one demonstration and produce the strongest results for complex judgement.

Chain of Thought
Build it
Prove few-shot with reasoning fixes inconsistency that prose cannot
  1. Create a base extraction prompt with detailed instructions but no examples and test it against ten documents with varied structures including tables, narrative paragraphs, and mixed formats, recording empty field rate and format variance across runs.
  2. Record which fields fail on which document types, for example dates correct from tables but missed in narrative, amounts inconsistent in words versus digits, and line items empty when embedded in paragraphs.
  3. Create three few-shot examples targeting the failing patterns, each including reasoning that explains why the extraction was done that way, and keep formatting identical across the three examples.
  4. Re-run the same ten documents with the few-shot-enhanced prompt and compare empty field rate, format consistency, and extraction accuracy, especially on the document types that previously failed.
  5. Document which structural patterns improve with few-shot and which still need schema changes such as optional and nullable fields to prevent fabrication.

Verify. The same ten documents show a measurable drop in empty fields and format variance, with the greatest improvement on narrative documents, while absent values remain null rather than invented once the schema treats them as optional.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Task 4.318 min

Structured Output

Guarantee schema-valid JSON through tool schemas and constrained decoding, then design nullable enums and redundancy fields that handle real document variation.

What you need to know

When you need guaranteed schema-compliant structured output from Claude there is a clear reliability hierarchy, and committing it to memory pays directly on the exam. At the top is tool_use with JSON schemas or the API-level structured outputs mode, which eliminates JSON syntax errors entirely through constrained decoding. Below that is prompt-based JSON where the model is asked to output JSON in a text response, which is probabilistic and will periodically produce malformed output with missing brackets, trailing commas, unquoted keys, or markdown code fences that break parsers.

With tool_use the tool's JSON schema constrains the shape of what Claude returns, and the separate tool_choice parameter controls whether the model must call a tool at all. Prompt-based extraction gives no structural guarantees, so a production parser that expects pure JSON will eventually receive a preamble such as Here is the JSON, a forgotten required field, or commentary before the object, and will fail. The exam builds directly on this hierarchy by asking which approach guarantees schema-valid JSON and which still requires a fallback parser.

The tool_choice parameter has three modes that the exam tests directly. Auto is the default and lets the model decide whether to call a tool or return text, so it may answer conversationally without ever producing structured output. Any forces the model to call a tool but lets it choose which one from the tools array, which is correct when you have multiple extraction schemas and document type is unknown. Forced tool selection with type tool and name guarantees the model calls exactly that tool regardless of input, and the name must character-for-character match a tool in the array or the request returns a 400 error. Use forced selection when a step must run before other steps, such as metadata extraction that must precede enrichment.

Schema design prevents entire classes of errors at the structural level before any model judgement is involved. Optional and nullable fields are the primary defence against fabrication: if a field is required the model is pressured to produce a value even when the source has none, if the field is nullable the model can honestly return null. An unclear enum value handles genuinely ambiguous cases, an other value paired with a freeform detail string handles long-tail categories the predefined enum does not cover, and format normalisation rules in the prompt such as all dates in ISO 8601 or all currency as decimal numbers without symbols enforce consistency alongside the schema.

The structured outputs mode at the API level works through constrained decoding at the sampler. The sampler maintains the set of valid next tokens given the partial output and the target schema, and tokens that would violate the schema such as a string where a number is required or a closing brace before required fields are filled are assigned zero probability at generation time. The model cannot generate them. This is a logit-level intervention, not post-processing. Strict tool use with strict true on a tool definition applies the same mechanism to tool call arguments, guaranteeing the arguments exactly match the provided JSON schema with no missing required fields, no extra fields, and correct types throughout, provided additionalProperties false is set.

Hierarchy and the tool_choice contract

Prompt-based JSON depends on model compliance, which is best effort, while API-level structured outputs and strict tool_use depend on constrained decoding, which is enforced. That difference is why a pipeline that must produce machine-parseable output for a downstream system should never rely on prompt instructions alone; the exam marks the schema-backed path as the only guarantee.

Choosing among auto, any, and forced selection is a design decision. Use auto only when a conversational response is a legitimate alternative to a tool call. Use any when a document could match several schemas and the model should choose the best fit. Use forced selection when a mandatory step must run and the model's preference must not override the workflow order.

Schema patterns that prevent fabrication and brittleness

Make fields nullable with a type union such as string or null or with nullable true depending on the schema dialect, so absence is an honest signal rather than an invented value. Add unclear to enums for genuinely indeterminate cases rather than forcing the model to pick a precise label from an incomplete set.

Pair an other enum value with a freeform detail string so unseen categories do not fail validation and are still captured verbatim. Add a description to every property so a field named tier with description Customer tier one of bronze, silver, gold is unambiguous where tier alone could be a string, integer, or label. Add format normalisation rules in the prompt because the schema enforces structure while the prompt enforces formatting consistency.

Build self-correction into the schema with redundancy fields such as calculated_total versus stated_total plus total_discrepancy, or conflict_detected for contradictory source text, so discrepancy detection surfaces inline without external recomputation for every document.

Constrained decoding mechanics and strict mode requirements

Structured outputs mode is requested via output_config.format with type json_schema and a schema whose root is type object, every property carrying a description for best results. The older top-level output_format was the beta-era parameter and still works during transition, but output_config.format is the canonical form for new integrations. The Python SDK exposes client.messages.parse with output_format, returning validated Pydantic instances through parsed_output.

For tool calls the tools array carries the schema under input_schema and strict true enables constrained decoding for that tool's arguments. Strict mode requires additionalProperties false, all optional properties still present in properties even when not required, and the strict flag set at the tool definition level so a single request can mix strict and non-strict tools. Without additionalProperties false the strict flag has no enforceable boundary.

What tool_use guarantees and what it does not

Tool_use with JSON schemas eliminates JSON syntax errors, the class that includes malformed JSON, missing required fields, wrong data types for structurally enforced fields, and extra fields when strict. It does not prevent semantic errors: values that do not sum correctly, data placed in the wrong field, or fabricated values for missing information inside structurally valid JSON. Those require validation logic in a separate layer, which is the subject of the next task.

This separation is why production needs both layers. Schema catches structure, semantic validation catches correctness, and the two together determine whether a document is accepted, retried, or routed to human review.

Authoritative mechanism reference

The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.

Mechanism reference

This section documents every mechanism in Task 4.3 at full depth. The organizing principle is that Task 4.3 now spans two independent structured-output features that share a compilation pipeline, plus a control parameter that governs invocation, plus a schema design layer that governs truthfulness under the shape guarantee.

Mechanism reference: Two independent features that share a pipeline

Structured outputs on the platform consist of two complementary features that can be used independently or together in the same request.

  1. JSON outputs (output_config.format). The request carries output_config: {format: {type: "json_schema", schema: {...}}}. The model response is constrained to return valid JSON matching that schema in the text content block. This is the direct successor to prompt-based JSON and replaces the need to parse free text.
  1. Strict tool use (strict: true on a tool definition). The request carries tools: [{name, description, strict: true, input_schema: {...}}]. Each tool call input is constrained to match its input_schema and each tool name is constrained to be one of the provided tools. This is the successor to non-strict tool use where shape was advisory.

Both features compile their schemas into grammar artifacts that constrain token sampling. The documentation states that grammar compilation is cached for 24 hours from last use and invalidated when the schema structure or the tool set changes; changing only name or description does not invalidate.

Feature compatibility reported by the structured-outputs page includes batch processing, token counting, and streaming as compatible, and citations plus message prefilling as incompatible with JSON outputs. The page also notes that grammars apply only to direct model output, not to tool_result blocks or to thinking blocks, so the model can think freely while still producing structured final output.

Mechanism reference: Current canonical form

The generally available (GA) form requires no beta header and is enabled per request:

result.json
json
{
  "model": "MODEL_ID",
  "max_tokens": 1024,
  "messages": [{"role": "user", "content": "Extract the key information from this email: ..."}],
  "output_config": {
    "format": {
      "type": "json_schema",
      "schema": {
        "type": "object",
        "properties": {
          "name": {"type": "string", "description": "The person's full name"},
          "age": {"type": "number", "description": "The person's age in years"}
        },
        "required": ["name", "age"],
        "additionalProperties": false
      }
    }
  }
}

Requirements for this form include: type of the format object is json_schema, the schema root is type: "object", and additionalProperties must be false on objects when structured outputs are in use. The page also documents that output_config.format can be set to {"type": "text"} to explicitly request plain text when that is desired for a particular request.

SDK helpers surface the same capability through typed interfaces: Python client.messages.parse() with a Pydantic model, TypeScript zodOutputFormat() or typed JSON Schema literals via jsonSchemaOutputFormat(), Java plain classes through outputConfig(Class), Ruby BaseModel classes, PHP classes implementing the structured-output interface, C# generic Create<T>(), and Go structs reflected into schemas. In the TypeScript and Python SDKs, unsupported schema constraints are automatically folded into field description text and validated client side after the response; hand-constructed requests must manage that fallback manually.

Property ordering for JSON outputs follows a documented caveat: required properties are emitted first in schema order, followed by optional properties in schema order, regardless of the order in which they were declared in properties. Applications that depend on a specific key order should either mark all properties as required or handle reordering after parsing.

Mechanism reference: Legacy field and header accepted only transitionally

Two older surfaces are documented as accepted for a transition period only: the top-level output_format field (the beta-era location for the same schema) and the anthropic-beta: structured-outputs-2025-11-13 header that formerly gated the feature. New integrations must use output_config.format, and the Python SDK v1.0 and later raises TypeError if output_format is passed to client.beta.messages.create() or count_tokens() and requires output_config instead. The legacy surfaces remain backward compatible only to avoid breaking existing callers and are not to be used in new code.

Mechanism reference: What strictness means

Strict mode guarantees that every tool_use block input strictly follows its input_schema and that every tool name is one of the tools provided in the request. The platform page phrases this as guarantees on both input and name. The retrieval for tool-definition-schemas confirms the same guarantee and adds that the toolset entries computer_toolset_20260801 and browser_toolset_20260801 do not accept strict: true and are rejected if set.

Mechanism reference: Exact requirements that make strictness enforceable

A strict tool must satisfy all of the following, each of which is enforced at request validation time:

  • strict is set to true as a top-level property on the individual tool definition, alongside name, description, and input_schema. Strictness is per tool, so a single request can mix strict and non-strict tools.
  • input_schema is a JSON Schema object whose root is type: "object" with properties, required, and additionalProperties: false. The additionalProperties: false clause is required for strict mode and is also recommended generally.
  • All properties that the tool may return, including optional ones, are present in properties. Optional means not listed in required and typed to allow absence, not omitted from properties altogether.
  • input_schema uses only the supported JSON Schema subset documented on the structured-outputs page. Supported features include basic types (object, array, string, integer, number, boolean, null), enum over primitives (no complex types), const, anyOf and allOf with limitations, $ref/$defs/definitions (external $ref not supported), default, required, additionalProperties: false, string format values from a fixed set (date-time, time, date, duration, email, hostname, uri, ipv4, ipv6, uuid), and array minItems limited to 0 or 1. Unsupported features produce 400.

The constrained-decoding lesson notes that SDKs strip unsupported numeric and string constraints from the wire schema, fold them into description, and validate client side after the response; hand-built requests must treat those constraints as guidance and validate post-response.

Mechanism reference: Strictness share of the contract

Strict mode is the correct remedy when a pipeline sees type drift such as "2" instead of 2, missing required fields, or extra hallucinated properties. It is not, however, a remedy for values that satisfy the type but are factually wrong. The lesson states this explicitly as a design principle: strict mode guarantees shape, not business validity, and does not replace server-side validation that checks authorization, existence, or cross-field truth.

Mechanism reference: How the two features compose in one request

The structured-outputs page shows that output_config.format and strict: true can appear together. In a composed request the JSON format constrains any final text block and each strict input_schema constrains any tool_use input for that name, giving two disjoint grammar targets in one turn. The page combines a summary/next_steps JSON schema with a strict search_flights tool in one messages.create call to illustrate the branching. Cache identity includes both the JSON schema and the set of strict tools; changing only description text does not invalidate. Batch processing remains compatible with the composed form, with the incompatible batch parameters being stream: true, fast-mode speed, thread fields, cache hints, and max_tokens: 0.

Mechanism reference: Constrained decoding under the hood

Both features share the same implementation mechanism: at each generation step the sampler holds a constraint automaton compiled from the JSON Schema, identifies the valid next tokens given the partial output, sets logits for invalid tokens to negative infinity, and samples from the filtered distribution. The compilation cost is incurred once per distinct schema (or composition) and cached for 24 hours as noted above. When the schema and the model prior disagree about which token is most likely, the schema wins unconditionally by logit masking, which is also why constrained decoding can enforce shape but cannot force the model to pick the correct schema-valid value among the remaining options.

Mechanism reference: Documented cases where output can still fail to match a schema

The guarantee of schema compliance is strong but not absolute. The platform page enumerates specific failure modes with distinct observables.

Refusal (stop_reason: "refusal"). Claude retains safety behavior even under structured outputs. On a refusal the response carries stop_reason: "refusal", is returned with HTTP status 200, is billed for the tokens that were generated, and the refusal text takes precedence over the schema so the output may not match the JSON shape. The response also carries stop_details with a policy category when present.

Token limit truncation (stop_reason: "max_tokens"). When max_tokens is reached before the JSON object can be closed, the output is truncated and therefore incomplete and not schema-valid. The observable is stop_reason: "max_tokens". The fix is to retry with a larger max_tokens value. This is distinct from the cache-warming case where max_tokens: 0 intentionally produces no generation.

Enum capitalization drift. The page documents a narrow caveat that enum and const string values are not guaranteed on capitalization: the model may return a value that differs only in capitalization, typically the first letter after a space, for example Conversation Topic 3 versus Conversation topic 3. The response completes normally with no error and no special stop_reason. Applications must compare such values case-insensitively and avoid enum sets that differ only in casing.

Schema complexity and compilation limits. Strict tool count, optional parameter count, and union-type parameter count each have explicit limits (roughly 20 strict tools per request, 24 optional parameters, 16 union-type parameters), plus internal grammar-size limits and a 180 second compilation timeout. Exceeding them returns 400 with a complexity or schema error rather than a partial enforcement.

Prompt-based JSON surface still exists for callers that do not opt in. If a request uses neither output_config.format nor strict tool use and relies instead on a system prompt instruction such as "Respond in JSON", generation remains probabilistic and can still emit markdown fences, preamble, trailing commas, and other defects described in the lesson. This is not a failure of structured outputs but the absence of it.

Mechanism reference: Syntax versus semantics: the boundary of the guarantee

The canonical phrasing across the lesson set is that constrained decoding guarantees structural validity while semantic correctness remains the application responsibility. A table from the constrained-decoding lesson makes this explicit:

  • Guaranteed: all required fields present, field types match, enum membership holds, no extra fields when additionalProperties: false, valid JSON syntax.
  • Not guaranteed: field values are correct, numeric accuracy, correct enum choice among valid members, absence of hallucinated but well-typed values, factual accuracy.

Three production failure classes instantiate the semantic side and are exercised repeatedly in forensics:

  • Sum and reconciliation mismatches. When line_items plus tax_amount does not equal the document stated_total, or when subtotal disagrees with calculated_total, both numbers can be well-typed number values that satisfy the schema yet contradict each other. JSON Schema can require that each total be a number but cannot require the equality relation, because that is a relation between two values rather than a property of either.
  • Wrong-field placement. When delivery_date receives the invoice date, effective_date receives the signature date, shipping_address receives the billing address, or jurisdiction receives a party name, each field still holds a valid string or a valid member of its enum. No JSON Schema keyword states that one string must contain the delivery address rather than the invoice address when both fields share type: string. Only a content-aware check that cross-references the source can detect the swap, except in the rare case where the two fields have disjoint types.
  • Fabrication of plausible values for absent information. When a source document lacks a purchase order, tax identifier, phone, weight, or demographic signal, a required non-nullable string forces the model to invent a string. The invention can be a pattern-valid identifier or date that survives format validation. The structural fix is to make the sometimes-absent field both not in required and typed to allow null so that null becomes as legal as any string, combined with an explicit instruction to return null when information is not directly stated.

The exam presentation, the lesson, and the forensics all converge on a single operational conclusion: after structured outputs, the next required step is application-side semantic validation that checks cross-field arithmetic, field-to-source alignment, and source-presence before accepting or posting a record.

Mechanism reference: tool_choice deep dive: the four values and what each guarantees

tool_choice is the parameter that controls whether a tool must be invoked and, if so, under what constraint. The value space, when used with user-defined tools, is the four-element set described below plus the defaulting rule.

auto (default, omit the parameter). The model may return text or a tool_use block. This is correct for conversational turns where a text response is acceptable. It carries no structural guarantee on invocation. Single-tool extraction under auto can return plain text even when the application expected a tool call, which is why the lesson identifies auto as the common cause of the symptom "a fraction of documents receive no tool call".

any (also rendered as tool_choice: "any"). The model must call at least one tool and must not return text alone, but it may choose which tool to call. This is the correct guarantee when the document type is unknown and several type-specific extraction tools such as extract_invoice, extract_receipt, and extract_contract are available. The guarantee is that every response has stop_reason: "tool_use" and valid tool input for the chosen schema; the guarantee does not include which schema is chosen.

Forced named tool {"type": "tool", "name": "<exact_name>"}. The model must call exactly the named tool. The name must match a name in tools exactly or the request fails validation. This is the only setting that gives ordering guarantees for prerequisite steps: forcing extract_metadata on the first turn ensures the DOI or invoice number needed by later tools is present. The correct multi-turn pattern is forced named tool on turn one and then auto (or any if a tool call must continue) on subsequent turns so dependent tools can be selected by the model once the prerequisite is in history.

none (forbid tools). The model must not call any tool and must answer in text. This is the value the reference page omits and the area where the fourth value belongs. Its purpose is the opposite of the previous three: to disable tool use entirely for a turn where only text is desired despite tools being present in the request. The tool-choice pricing table on the tool-use overview confirms that none clusters with auto in token cost and behavior as a text-only setting. The system prompt alone (such as "Always call extraction first") is probabilistic and cannot provide this guarantee; tool_choice can.

Ordering, position, and token budget do not substitute for the guarantee. Placing a tool first in the tools array, describing it more prominently, or raising max_tokens does not change the auto or any contract.

Mechanism reference: JSON Schema subset and keyword support for structured outputs

Structured outputs support a deliberately narrow JSON Schema subset shared between JSON outputs and strict tool use. The supported vocabulary includes all basic types, enum over primitives, const, anyOf and allOf with stated limitations including that allOf with $ref is not supported, $ref/$defs/definitions with local reference only, default, required, additionalProperties: false, the enumerated string format set, and array minItems limited to 0 or 1.

Not supported are recursive schemas, complex types inside enum, external $ref, numeric range constraints, string length constraints, array constraints beyond the stated minItems, additionalProperties set to any value other than false, backreferences, lookahead, word boundaries, and complex quantifier ranges in pattern. The lesson and the platform page document the mitigation for unsupported constraints: the TypeScript, Python, Ruby, and PHP SDKs (and C# and Go when schemas are derived from native types) strip those constraints from the wire schema, fold them into the field description, and validate client side after the response. Hand-constructed requests must handle the same fallback manually, by writing the length or range requirement into description and adding post-response application validation.

Field description quality is part of enforcement: three to four sentences covering purpose, when to use, limitations, and an example is the documented bar, and descriptions are the contract the model reads alongside type and format.

Mechanism reference: Optional and nullable fields as the primary defence

The structural cause of fabrication is required-field pressure. When a field such as purchase_order, vendor_tax_id, phone, weight, hazmat_class, or grant_number is marked required with type: string, the model has no legal abstention path when the source genuinely lacks the information. The available output tokens for null are invalid under the schema, so a plausible synthetic string becomes the only schema-valid completion.

The fix is two operations applied together:

  • Remove the field from the required array so omission is legal (optional).
  • Widen the field type to include null, for example {"type": ["string", "null"]} or {"anyOf": [{"type": "string"}, {"type": "null"}]}, or use [] for absent arrays.

After both, returning null is as valid as returning a string and the model no longer needs to choose between an invalid response and a lie. The lesson set adds that null over sentinel strings such as "N/A" or "unknown" is preferred because downstream consumers can distinguish null as not mentioned from a string that could itself be legitimate data.

Nullable alone does not make null expected. Forensics Rule 6 documents that explicit instruction is the second half: a prompt statement such as Return null for any field where information is not directly stated in the source or Only extract information explicitly present; use null for missing values shifts the policy from permissible to expected. Without it the model may still populate the field with a plausible value even though null is allowed, because its prior favors content.

For array absence the same principle yields a choice between null and []. When the downstream contract expects a list, [] is often the clearer signal for no items, such as no line items or no tags, while null signals the property itself was not mentioned. The contract choice is downstream-visible and must be consistent.

Mechanism reference: unclear as the ambiguity home

When the source is genuinely ambiguous, such as a sarcastic customer message Well that was... interesting that does not map cleanly to a sentiment enum of positive, negative, and mixed, adding unclear (sometimes surfaced as unknown) to the enum gives that undecidable case a typed home. The distinction from neighboring values is deliberate:

  • unclear means evidence is insufficient to decide even though the concept is in scope.
  • other means evidence is present and clearly outside the known closed set.
  • neutral when present as a domain value means a measured neutral judgment, which is different from inability to judge.

Without unclear, a closed enum forces the model to choose the nearest fit and produces systematic misclassification that contaminates analytics, for example joint ventures being typed as mergers or ambiguous intents being forced into billing.

Mechanism reference: other plus a companion detail string as the extensibility home

When the world produces values outside the useful closed core, such as payment_method seeing cryptocurrency, property_type seeing tiny house, or event_type seeing JOINT_VENTURE, the documented extensible pattern is to add other (or OTHER) to the enum and to add a nullable free-form string such as category_detail, payment_method_detail, or intent_detail that is populated only when the category is other.

result.json
json
{
  "type": "object",
  "properties": {
    "intent": {
      "type": "string",
      "enum": ["BILLING", "SHIPPING", "RETURNS", "TECHNICAL", "OTHER"],
      "description": "Select OTHER when the intent falls outside the four listed values."
    },
    "intent_detail": {
      "type": ["string", "null"],
      "description": "When intent is OTHER, describe the specific intent in free text. Otherwise null."
    }
  },
  "required": ["intent"],
  "additionalProperties": false
}

This preserves queryability of the known categories while making the tail inspectable. A fifty-value enum that tries to anticipate every tail value is brittle and still finite, while dropping the enum to free text loses consistency on the majority. The middle path of a small stable core plus one open slot is the documented scalable choice.

Mechanism reference: Format normalisation alongside strict schemas

Source documents present dates, amounts, and identifiers in varied forms: 15 March 2024, 03/15/2024, $1,234.56, USD 1,234.56, bare 1234.56. The downstream contract requires one canonical form, for example YYYY-MM-DD for dates and plain number without currency symbols for amounts. The layered fix documented across the JSON-mode and constrained-decoding lessons is a schema-side declaration plus a prompt-side mapping rule:

  • Schema side: field description states the canonical target and example, format: "date" or pattern: "^\\d{4}-\\d{2}-\\d{2}$" constrains shape, type: "number" constrains amount to a numeric token.
  • Prompt side: instructions such as All dates in ISO 8601 YYYY-MM-DD and All currency amounts as decimal numbers without currency symbols or commas, plus few-shot pairs showing each variant mapped to its canonical target.

For exact decimal fidelity on amounts the documentation suggests typing amounts as string with a pattern such as ^\\d+\\.\\d{2}# Task 4.3 Structured Output with Tool Use - authoritative grounding

Mechanism reference

This section documents every mechanism in Task 4.3 at full depth. The organizing principle is that Task 4.3 now spans two independent structured-output features that share a compilation pipeline, plus a control parameter that governs invocation, plus a schema design layer that governs truthfulness under the shape guarantee.

Mechanism reference: Two independent features that share a pipeline

Structured outputs on the platform consist of two complementary features that can be used independently or together in the same request.

  1. JSON outputs (output_config.format). The request carries output_config: {format: {type: "json_schema", schema: {...}}}. The model response is constrained to return valid JSON matching that schema in the text content block. This is the direct successor to prompt-based JSON and replaces the need to parse free text.
  1. Strict tool use (strict: true on a tool definition). The request carries tools: [{name, description, strict: true, input_schema: {...}}]. Each tool call input is constrained to match its input_schema and each tool name is constrained to be one of the provided tools. This is the successor to non-strict tool use where shape was advisory.

Both features compile their schemas into grammar artifacts that constrain token sampling. The documentation states that grammar compilation is cached for 24 hours from last use and invalidated when the schema structure or the tool set changes; changing only name or description does not invalidate.

Feature compatibility reported by the structured-outputs page includes batch processing, token counting, and streaming as compatible, and citations plus message prefilling as incompatible with JSON outputs. The page also notes that grammars apply only to direct model output, not to tool_result blocks or to thinking blocks, so the model can think freely while still producing structured final output.

Mechanism reference: Current canonical form

The generally available (GA) form requires no beta header and is enabled per request:

result.json
json
{
  "model": "MODEL_ID",
  "max_tokens": 1024,
  "messages": [{"role": "user", "content": "Extract the key information from this email: ..."}],
  "output_config": {
    "format": {
      "type": "json_schema",
      "schema": {
        "type": "object",
        "properties": {
          "name": {"type": "string", "description": "The person's full name"},
          "age": {"type": "number", "description": "The person's age in years"}
        },
        "required": ["name", "age"],
        "additionalProperties": false
      }
    }
  }
}

Requirements for this form include: type of the format object is json_schema, the schema root is type: "object", and additionalProperties must be false on objects when structured outputs are in use. The page also documents that output_config.format can be set to {"type": "text"} to explicitly request plain text when that is desired for a particular request.

SDK helpers surface the same capability through typed interfaces: Python client.messages.parse() with a Pydantic model, TypeScript zodOutputFormat() or typed JSON Schema literals via jsonSchemaOutputFormat(), Java plain classes through outputConfig(Class), Ruby BaseModel classes, PHP classes implementing the structured-output interface, C# generic Create<T>(), and Go structs reflected into schemas. In the TypeScript and Python SDKs, unsupported schema constraints are automatically folded into field description text and validated client side after the response; hand-constructed requests must manage that fallback manually.

Property ordering for JSON outputs follows a documented caveat: required properties are emitted first in schema order, followed by optional properties in schema order, regardless of the order in which they were declared in properties. Applications that depend on a specific key order should either mark all properties as required or handle reordering after parsing.

Mechanism reference: Legacy field and header accepted only transitionally

Two older surfaces are documented as accepted for a transition period only: the top-level output_format field (the beta-era location for the same schema) and the anthropic-beta: structured-outputs-2025-11-13 header that formerly gated the feature. New integrations must use output_config.format, and the Python SDK v1.0 and later raises TypeError if output_format is passed to client.beta.messages.create() or count_tokens() and requires output_config instead. The legacy surfaces remain backward compatible only to avoid breaking existing callers and are not to be used in new code.

Mechanism reference: What strictness means

Strict mode guarantees that every tool_use block input strictly follows its input_schema and that every tool name is one of the tools provided in the request. The platform page phrases this as guarantees on both input and name. The retrieval for tool-definition-schemas confirms the same guarantee and adds that the toolset entries computer_toolset_20260801 and browser_toolset_20260801 do not accept strict: true and are rejected if set.

Mechanism reference: Exact requirements that make strictness enforceable

A strict tool must satisfy all of the following, each of which is enforced at request validation time:

  • strict is set to true as a top-level property on the individual tool definition, alongside name, description, and input_schema. Strictness is per tool, so a single request can mix strict and non-strict tools.
  • input_schema is a JSON Schema object whose root is type: "object" with properties, required, and additionalProperties: false. The additionalProperties: false clause is required for strict mode and is also recommended generally.
  • All properties that the tool may return, including optional ones, are present in properties. Optional means not listed in required and typed to allow absence, not omitted from properties altogether.
  • input_schema uses only the supported JSON Schema subset documented on the structured-outputs page. Supported features include basic types (object, array, string, integer, number, boolean, null), enum over primitives (no complex types), const, anyOf and allOf with limitations, $ref/$defs/definitions (external $ref not supported), default, required, additionalProperties: false, string format values from a fixed set (date-time, time, date, duration, email, hostname, uri, ipv4, ipv6, uuid), and array minItems limited to 0 or 1. Unsupported features produce 400.

The constrained-decoding lesson notes that SDKs strip unsupported numeric and string constraints from the wire schema, fold them into description, and validate client side after the response; hand-built requests must treat those constraints as guidance and validate post-response.

Mechanism reference: Strictness share of the contract

Strict mode is the correct remedy when a pipeline sees type drift such as "2" instead of 2, missing required fields, or extra hallucinated properties. It is not, however, a remedy for values that satisfy the type but are factually wrong. The lesson states this explicitly as a design principle: strict mode guarantees shape, not business validity, and does not replace server-side validation that checks authorization, existence, or cross-field truth.

Mechanism reference: How the two features compose in one request

The structured-outputs page shows that output_config.format and strict: true can appear together. In a composed request the JSON format constrains any final text block and each strict input_schema constrains any tool_use input for that name, giving two disjoint grammar targets in one turn. The page combines a summary/next_steps JSON schema with a strict search_flights tool in one messages.create call to illustrate the branching. Cache identity includes both the JSON schema and the set of strict tools; changing only description text does not invalidate. Batch processing remains compatible with the composed form, with the incompatible batch parameters being stream: true, fast-mode speed, thread fields, cache hints, and max_tokens: 0.

Mechanism reference: Constrained decoding under the hood

Both features share the same implementation mechanism: at each generation step the sampler holds a constraint automaton compiled from the JSON Schema, identifies the valid next tokens given the partial output, sets logits for invalid tokens to negative infinity, and samples from the filtered distribution. The compilation cost is incurred once per distinct schema (or composition) and cached for 24 hours as noted above. When the schema and the model prior disagree about which token is most likely, the schema wins unconditionally by logit masking, which is also why constrained decoding can enforce shape but cannot force the model to pick the correct schema-valid value among the remaining options.

Mechanism reference: Documented cases where output can still fail to match a schema

The guarantee of schema compliance is strong but not absolute. The platform page enumerates specific failure modes with distinct observables.

Refusal (stop_reason: "refusal"). Claude retains safety behavior even under structured outputs. On a refusal the response carries stop_reason: "refusal", is returned with HTTP status 200, is billed for the tokens that were generated, and the refusal text takes precedence over the schema so the output may not match the JSON shape. The response also carries stop_details with a policy category when present.

Token limit truncation (stop_reason: "max_tokens"). When max_tokens is reached before the JSON object can be closed, the output is truncated and therefore incomplete and not schema-valid. The observable is stop_reason: "max_tokens". The fix is to retry with a larger max_tokens value. This is distinct from the cache-warming case where max_tokens: 0 intentionally produces no generation.

Enum capitalization drift. The page documents a narrow caveat that enum and const string values are not guaranteed on capitalization: the model may return a value that differs only in capitalization, typically the first letter after a space, for example Conversation Topic 3 versus Conversation topic 3. The response completes normally with no error and no special stop_reason. Applications must compare such values case-insensitively and avoid enum sets that differ only in casing.

Schema complexity and compilation limits. Strict tool count, optional parameter count, and union-type parameter count each have explicit limits (roughly 20 strict tools per request, 24 optional parameters, 16 union-type parameters), plus internal grammar-size limits and a 180 second compilation timeout. Exceeding them returns 400 with a complexity or schema error rather than a partial enforcement.

Prompt-based JSON surface still exists for callers that do not opt in. If a request uses neither output_config.format nor strict tool use and relies instead on a system prompt instruction such as "Respond in JSON", generation remains probabilistic and can still emit markdown fences, preamble, trailing commas, and other defects described in the lesson. This is not a failure of structured outputs but the absence of it.

Mechanism reference: Syntax versus semantics: the boundary of the guarantee

The canonical phrasing across the lesson set is that constrained decoding guarantees structural validity while semantic correctness remains the application responsibility. A table from the constrained-decoding lesson makes this explicit:

  • Guaranteed: all required fields present, field types match, enum membership holds, no extra fields when additionalProperties: false, valid JSON syntax.
  • Not guaranteed: field values are correct, numeric accuracy, correct enum choice among valid members, absence of hallucinated but well-typed values, factual accuracy.

Three production failure classes instantiate the semantic side and are exercised repeatedly in forensics:

  • Sum and reconciliation mismatches. When line_items plus tax_amount does not equal the document stated_total, or when subtotal disagrees with calculated_total, both numbers can be well-typed number values that satisfy the schema yet contradict each other. JSON Schema can require that each total be a number but cannot require the equality relation, because that is a relation between two values rather than a property of either.
  • Wrong-field placement. When delivery_date receives the invoice date, effective_date receives the signature date, shipping_address receives the billing address, or jurisdiction receives a party name, each field still holds a valid string or a valid member of its enum. No JSON Schema keyword states that one string must contain the delivery address rather than the invoice address when both fields share type: string. Only a content-aware check that cross-references the source can detect the swap, except in the rare case where the two fields have disjoint types.
  • Fabrication of plausible values for absent information. When a source document lacks a purchase order, tax identifier, phone, weight, or demographic signal, a required non-nullable string forces the model to invent a string. The invention can be a pattern-valid identifier or date that survives format validation. The structural fix is to make the sometimes-absent field both not in required and typed to allow null so that null becomes as legal as any string, combined with an explicit instruction to return null when information is not directly stated.

The exam presentation, the lesson, and the forensics all converge on a single operational conclusion: after structured outputs, the next required step is application-side semantic validation that checks cross-field arithmetic, field-to-source alignment, and source-presence before accepting or posting a record.

Mechanism reference: tool_choice deep dive: the four values and what each guarantees

tool_choice is the parameter that controls whether a tool must be invoked and, if so, under what constraint. The value space, when used with user-defined tools, is the four-element set described below plus the defaulting rule.

auto (default, omit the parameter). The model may return text or a tool_use block. This is correct for conversational turns where a text response is acceptable. It carries no structural guarantee on invocation. Single-tool extraction under auto can return plain text even when the application expected a tool call, which is why the lesson identifies auto as the common cause of the symptom "a fraction of documents receive no tool call".

any (also rendered as tool_choice: "any"). The model must call at least one tool and must not return text alone, but it may choose which tool to call. This is the correct guarantee when the document type is unknown and several type-specific extraction tools such as extract_invoice, extract_receipt, and extract_contract are available. The guarantee is that every response has stop_reason: "tool_use" and valid tool input for the chosen schema; the guarantee does not include which schema is chosen.

Forced named tool {"type": "tool", "name": "<exact_name>"}. The model must call exactly the named tool. The name must match a name in tools exactly or the request fails validation. This is the only setting that gives ordering guarantees for prerequisite steps: forcing extract_metadata on the first turn ensures the DOI or invoice number needed by later tools is present. The correct multi-turn pattern is forced named tool on turn one and then auto (or any if a tool call must continue) on subsequent turns so dependent tools can be selected by the model once the prerequisite is in history.

none (forbid tools). The model must not call any tool and must answer in text. This is the value the reference page omits and the area where the fourth value belongs. Its purpose is the opposite of the previous three: to disable tool use entirely for a turn where only text is desired despite tools being present in the request. The tool-choice pricing table on the tool-use overview confirms that none clusters with auto in token cost and behavior as a text-only setting. The system prompt alone (such as "Always call extraction first") is probabilistic and cannot provide this guarantee; tool_choice can.

Ordering, position, and token budget do not substitute for the guarantee. Placing a tool first in the tools array, describing it more prominently, or raising max_tokens does not change the auto or any contract.

Mechanism reference: JSON Schema subset and keyword support for structured outputs

Structured outputs support a deliberately narrow JSON Schema subset shared between JSON outputs and strict tool use. The supported vocabulary includes all basic types, enum over primitives, const, anyOf and allOf with stated limitations including that allOf with $ref is not supported, $ref/$defs/definitions with local reference only, default, required, additionalProperties: false, the enumerated string format set, and array minItems limited to 0 or 1.

Not supported are recursive schemas, complex types inside enum, external $ref, numeric range constraints, string length constraints, array constraints beyond the stated minItems, additionalProperties set to any value other than false, backreferences, lookahead, word boundaries, and complex quantifier ranges in pattern. The lesson and the platform page document the mitigation for unsupported constraints: the TypeScript, Python, Ruby, and PHP SDKs (and C# and Go when schemas are derived from native types) strip those constraints from the wire schema, fold them into the field description, and validate client side after the response. Hand-constructed requests must handle the same fallback manually, by writing the length or range requirement into description and adding post-response application validation.

and converting in application code, because numeric multipleOf is in the unsupported subset on the wire. The weaker alternatives such as regex post-processing after generation are fragile across locales and ambiguous orderings and do not fix a fabricated value produced upstream.

Mechanism reference: Field-level shape guidance through type, format, pattern, and description

Beyond the three patterns above, every property benefits from precise type, format, and pattern at the property level and from a description that names the expected shape with an example. The forensics and schema-definition lesson cite examples such as type: string with format: date, format: email, format: uri, pattern: "^https://" and type: integer with bounded enum members as the kind of direct instruction the model follows at generation time. Descriptions that remain at the tool level rather than the property level are ineffective because the model needs the contract at the slot it is about to fill.

Ownership map

Each guarantee and failure surface in Task 4.3 belongs to a distinct layer. Misattributing ownership causes the wrong fix to be applied, for example tightening a schema when the failure is a tool_choice value or adding a prompt sentence when the failure is an unsupported JSON Schema keyword.

Model sampling layer. Owns token-level enforcement of output_config.format and strict: true through the compiled automaton that masks invalid tokens, including property-order reemission and enum casing treatment, and the interaction where thinking blocks remain unconstrained while final output is constrained.

API gateway and validation layer. Owns validation of legacy header and field, tool_choice forced-name matching with 400 on mismatch, JSON Schema support checks, strict tool count and optional or union parameter limits, the 180 second compilation timeout, emission of stop_reason values, and 24 hour grammar caching with invalidation on schema or tool-set change.

SDK layer. Owns convenience transformations that fold unsupported constraints into description and validate client side after the response; examples include Python client.messages.parse(), TypeScript zodOutputFormat(), Java class reflection, and Ruby, PHP, C#, and Go native helpers.

Application code layer. Owns semantic validation: deterministic total reconciliation, field-to-source placement checks, source-presence detection, bounded retry with specific feedback, routing on failure, and the null versus empty versus omitted contract plus *_detail logic.

Configuration and infrastructure layer. Owns batching, max_tokens tuning, prompt-cache invalidation when output_config.format changes, and handling of the 24 hour schema cache that must not contain protected health information.

Version and terminology currency

Task 4.3 is the site of the most visible terminology shift in the structured-output surface between the earlier task statements and the present platform docs.

Current canonical names. output_config.format with type: "json_schema" is the GA entry point for JSON outputs. strict: true on a tool definition is the GA entry point for strict tool use. No beta header is required for either. The lesson updates for January 2026 record this GA promotion and note that structured outputs became generally available on January 29, 2026.

Legacy names still accepted. output_format as a top-level request field and the anthropic-beta: structured-outputs-2025-11-13 header remain accepted only for the transition period. The structured-outputs lesson retains callouts that show the old output_format form with a warning to use output_config.format for new integrations. The DOC-URLS note is explicit that new code should not add the header and that the Python SDK no longer accepts the old field on the beta client and raises TypeError.

Exam versus current answer key. A candidate may encounter older study material that phrases Task 4.3 solely in tool_use terms with tool_choice: any as the only structured-output control. The defensible exam answer is the current documentation position: both JSON outputs and strict tool use provide grammar-constrained schema compliance and the two are independent and composable. Stating the older single-mechanism answer would omit a live tested surface.

Lesson slug currency. The single source of truth for lesson identity is src/lib/content-registry.ts. The structured-output domain owns four slugs: json-mode, constrained-decoding, schema-definition, and validation-strategies. The tool-choice deep dive that Task 4.3 depends on lives at tool-definition-schemas and tool-choice-deep-dive under the tool-use domain, with tool-use-blocks and parallel-tool-calling as adjacent prerequisites. Citing a slug that does not exist in the registry breaks linkage and evaluation should use the registry values above.

Official versus community divergence

Prompt-based JSON versus two structured-output features. Community summaries that predate the GA promotion often describe structured output as a single hierarchy of prompt-based JSON versus tool_use with JSON schemas, with tool_choice: any as the only control discussed. The live documentation treats JSON outputs and strict tool use as two separate features with the same underlying constrained-decoding implementation and documents both as independent and composable. Candidates should answer with the two-feature account and cite the output_config.format location as the current field.

Batch and tool-use scope. The live batch and structured-output pages explicitly list tool use including server tools and multi-turn conversations among batch-compatible capabilities, and state that batch requests are asynchronous single units with no interactive loop mid-request rather than a prohibition on tools in batch. The documented position wins, and the defensible phrasing is that batching supports tool use but does not support interleaving your code and the model interactively inside a single batch unit; workflows whose control flow depends on inspecting a tool result to decide the next request still require separate requests.

Strict mode enforcement surface. Community material sometimes places strict as a request-level flag or describes input_schema additionalProperties: false as optional. The documentation places strict on the individual tool definition and requires additionalProperties: false for strict mode as part of the validation contract. The strict granularity and requirement are testable and should be stated as documented.

Typing of absent data. Some community posts treat a prompt sentence such as "Do not fabricate" or a required field paired with a string sentinel as a valid handling of absent data. The platform docs and lesson set treat that as structurally coerced: a required string with no null keeps fabrication pressure intact, and the correct structural handling is to widen the type to allow null, remove the field from required, and pair with an explicit null-use instruction. Prompt alone reduces but does not eliminate the rate when the field remains required.

Enum comparison behavior. Community code often compares enum values with exact string equality. The platform page documents that enum and const string values are not guaranteed on capitalization and explicitly advises case-insensitive comparison and avoiding sets that differ only in casing. That caveat is part of the guarantee statement and should be included rather than omitted as an edge case.

Beyond the task statement

The four lessons assigned to Task 4.3 cover the mechanisms named in the reference, but adjacent lessons document topics that the reference omits yet a production candidate should be able to reason about.

Prefilling and truncation recovery. The JSON-mode lesson notes that a truncated long JSON array can be recovered by sending the partial output back as an assistant turn and asking for continuation.

Property ordering. Required properties are emitted before optional ones, each in schema order, which matters when a display expects a specific key order.

Array and primitive constraint stripping and SDK fallback (constrained-decoding section on not every keyword enforced). The lesson documents that minimum, maximum, minLength, maxLength, multipleOf, and similar constraints are not enforced at the automaton level and are folded into description by the SDKs, then validated client side. For Task 4.3 this means that a schema that relies solely on those constraints for financial totals or bounded lists has a weaker guarantee than one that uses enum, format, or type directly.

Parallel tool calling and batch parameter execution (tool-definition-schemas note on batch endpoints). When a tool is called repeatedly with slightly different parameters and each call is minutes long, the loop serializes wall-clock time. A batch endpoint that accepts an array of parameter sets and runs them concurrently server side replaces N round trips with one, reducing total time from, for example, three times two minutes to roughly two minutes. The accompanying guidance to prefer a new parallel-variant tool over a boolean switch preserves description clarity per tool. These patterns matter for the performance context of structured extraction at scale even though the reference focuses only on correctness.

Tool result handling and security context (tool-use domain adjacency). The lessons on tool-result-handling and tool-security cover how to format tool_result blocks, how to separate transient from permanent errors, and how to size or paginate tool outputs so they do not exhaust the context window. For structured-output pipelines that feed results back into the next turn, correct tool_result formatting with tool_use_id and the decision to keep bulky results out of context belong to this adjacent knowledge.

Validation strategies. The fourth slug validation-strategies continues Task 4.3 with deterministic arithmetic comparison, tagged retry payloads, and routable error categories.

Schema composition with $ref and anyOf/allOf. Reusable fragments such as a shared address definition via $ref to #/$defs/address keep schemas consistent when shipping_address and billing_address share the same structure, and anyOf expresses intentional union types such as nullable strings as type: ["string", "null"] or explicit anyOf branches. Care with external $ref and with allOf plus $ref avoids running into the documented limitation set.

Versioning surfaces for consumers. Adding a version field such as schema_version to output schemas, versioning tool names as extract_invoice_v2, or keeping new fields optional and never renaming existing ones are listed as consumer-safe evolution strategies alongside explicit change notes in description. These matter once a structured-output contract is consumed by a parser or database.

Worked production examples

Each example evolves a single invoice extraction pipeline so the five required substantial code samples read as a connected build rather than as isolated snippets. The pipeline handles two invoices: one complete and one missing optional fields, with a type-specific tool set for multi-document routing.

Worked production examples: Example 1: JSON-format request through the current output_config.format field

This is the canonical way to request guaranteed JSON shape for extraction without modeling extraction as a tool call. The response text block is JSON that can be read directly, with retries needed only for semantic errors rather than for parse failures.

example.ts
typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const contractExtractionSchema = {
  type: "object",
  properties: {
    invoice_number: {
      type: "string",
      description: "The invoice identifier as printed on the source document, for example INV-2026-0847. Return exactly as printed."
    },
    vendor_name: {
      type: "string",
      description: "The vendor legal name as printed in the header. Use the full legal name rather than an abbreviation."
    },
    payment_terms: {
      type: ["string", "null"],
      description: "Payment terms clause as printed, or null if no terms are stated. Return null if not directly stated in the source."
    },
    purchase_order: {
      type: ["string", "null"],
      description: "Purchase order number exactly as printed, or null if no purchase order appears in the source."
    }
  },
  required: ["invoice_number", "vendor_name"],
  additionalProperties: false
} as const;

const response = await client.messages.create({
  model: "MODEL_ID",
  max_tokens: 2048,
  system: "Extract invoice fields from the document text. Return null for any field where the information is not directly stated in the source. All dates must be normalized to YYYY-MM-DD in logic only; the current schema does not ask for dates.",
  messages: [
    {
      role: "user",
      content: `Extract the key fields from this invoice text:\n\n${invoiceText}`
    }
  ],
  output_config: {
    format: {
      type: "json_schema",
      schema: contractExtractionSchema
    }
  }
});

const textBlock = response.content.find((block) => block.type === "text");
if (!textBlock || textBlock.type !== "text") {
  throw new Error("No text block returned from JSON output request");
}
const parsed = JSON.parse(textBlock.text);
console.log("structured_output parsed", parsed);
// Observable: `textBlock.text` is parseable JSON on every successful call. With `output_config.format`
// present, malformed brackets, trailing commas, or markdown fences are not returned. `additionalProperties: false`
// prevents extra hallucinated keys such as `vendor_tax_id` from appearing when not in the schema.

What this block proves: the current field is output_config.format with type: "json_schema" at the request level and with additionalProperties: false at the schema root, not the legacy top-level output_format. Its failure boundary is the set of documented non-guarantees: a refusal (stop_reason: "refusal", status 200, billed tokens) may return non-JSON refusal text, and a hit to max_tokens (stop_reason: "max_tokens") may return truncated JSON. Both conditions are visible on the response and must be handled before parsing.

Worked production examples: Example 2: Strict tool definition with the exact requirements that make strictness enforceable

This shows an input_schema that satisfies every enforceable requirement for strict: true. It also shows why mixing strict and non-strict tools is permitted and useful: the reporting tool below is strict while an advisory tool could remain non-strict.

example.ts
typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const extractInvoiceStrictTool = {
  name: "extract_invoice",
  description: "Extract structured invoice data from a source text. Returns the invoice identifier, vendor, payment terms, and purchase order. Use when the document is known to be an invoice.",
  strict: true,
  input_schema: {
    type: "object",
    properties: {
      invoice_number: {
        type: "string",
        description: "The invoice identifier exactly as printed, for example INV-2026-0847. Do not invent or normalize."
      },
      vendor_name: {
        type: "string",
        description: "Vendor legal name as printed in the header."
      },
      payment_terms: {
        type: ["string", "null"],
        description: "Payment terms clause exactly as printed, or null if not stated. Use null when not directly stated."
      },
      purchase_order: {
        type: ["string", "null"],
        description: "Purchase order number exactly as printed, or null if no purchase order appears."
      }
    },
    required: ["invoice_number", "vendor_name"],
    additionalProperties: false
  }
} as const;

const response = await client.messages.create({
  model: "MODEL_ID",
  max_tokens: 2048,
  tools: [extractInvoiceStrictTool],
  tool_choice: { type: "tool", name: "extract_invoice" },
  messages: [{ role: "user", content: `Extract invoice data from this source:\n\n${invoiceText}` }]
});

const toolUse = response.content.find((b) => b.type === "tool_use");
if (!toolUse || toolUse.type !== "tool_use") throw new Error("No tool_use returned");
console.log("tool_use input", toolUse.input);
// Observable: `toolUse.input` has exactly `invoice_number` and `vendor_name` as strings, `purchase_order`
// as either a string or null, never as an extra invented string. Missing required would never occur on a
// `tool_use` response; absent optionals may be absent or null depending on the type choice.

Requirements visible in the block: strict is on the tool object itself, input_schema.type is object, every property including optional payment_terms and purchase_order appears in properties, only invoice_number and vendor_name are in required, and additionalProperties: false is present. Schema keywords used here are in the supported subset; constraints that are not supported such as minimum or maxLength are intentionally absent and would be handled by folding into description if needed.

Worked production examples: Example 3: The three selection modes side by side with what each guarantees

The following TypeScript comparison shows the three selection modes from the reference ordering on the same tool set. The guarantee annotations state what each mode promises and what it does not promise.

example.ts
typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const tools = [
  {
    name: "extract_invoice",
    description: "Extract data from an invoice document.",
    strict: true,
    input_schema: {
      type: "object",
      properties: {
        invoice_number: { type: "string", description: "As printed." },
        vendor_name: { type: "string", description: "As printed." },
        purchase_order: { type: ["string", "null"], description: "As printed or null if absent." }
      },
      required: ["invoice_number", "vendor_name"],
      additionalProperties: false
    }
  },
  {
    name: "extract_receipt",
    description: "Extract data from a receipt document.",
    strict: true,
    input_schema: {
      type: "object",
      properties: {
        receipt_id: { type: "string", description: "As printed." },
        merchant_name: { type: "string", description: "As printed." }
      },
      required: ["receipt_id", "merchant_name"],
      additionalProperties: false
    }
  },
  {
    name: "extract_contract",
    description: "Extract data from a contract document.",
    strict: true,
    input_schema: {
      type: "object",
      properties: {
        contract_id: { type: "string", description: "As printed." },
        parties: { type: "array", items: { type: "string" }, description: "Parties named in the agreement." }
      },
      required: ["contract_id", "parties"],
      additionalProperties: false
    }
  }
] as const;

// Mode 1: auto (default) - model may return text or a tool call.
// Guarantee: none on invocation. The response may be text with no tool_use block.
// Use when a conversational answer is acceptable and structure is optional.
const responseAuto = await client.messages.create({
  model: "MODEL_ID",
  max_tokens: 2048,
  tools,
  tool_choice: { type: "auto" },
  messages: [{ role: "user", content: unknownDocumentText }]
});
console.log("auto stop_reason", responseAuto.stop_reason);
// Possible: "end_turn" with text, or "tool_use" if the model chose a tool.
// Not safe for mandatory ingestion: a text response is valid.

// Mode 2: any - model must call at least one tool but chooses which.
// Guarantee: at least one tool will be called, tool inputs match their schemas, but the
// tool name is chosen by the model from the provided set.
// Use when the document type is unknown and any correct schema is better than no call.
const responseAny = await client.messages.create({
  model: "MODEL_ID",
  max_tokens: 2048,
  tools,
  tool_choice: { type: "any" },
  messages: [{ role: "user", content: unknownDocumentText }]
});
console.log("any stop_reason", responseAny.stop_reason);
// Guaranteed: stop_reason is "tool_use" and at least one tool_use block is present.
// Not guaranteed: which tool. The model will route an invoice-like document to
// extract_invoice and a receipt-like document to extract_receipt.

// Mode 3: forced named tool - model must call exactly the named tool.
// Guarantee: the named tool is called, inputs match its schema, no other tool.
// Use when a specific step is mandatory regardless of document content.
const responseForced = await client.messages.create({
  model: "MODEL_ID",
  max_tokens: 2048,
  tools,
  tool_choice: { type: "tool", name: "extract_invoice" },
  messages: [{ role: "user", content: unknownDocumentText }]
});
console.log("forced tool name", responseForced.content.find((b) => b.type === "tool_use"));
// Guaranteed: exactly extract_invoice is called even if the document is obviously a receipt.
// Not guaranteed: semantic correctness - the input will match the extract_invoice schema
// even though it describes a receipt, which is wrong meaning in correct shape.

The fourth value that forbids tools, {"type": "none"} (or tool_choice: "none" in some SDK shorthands), is the complement of these three. It guarantees that no tool_use block appears and the response is text only. It belongs in turns where tools are present for future turns but must not be invoked, for example a confirmation or explanation step that should not trigger extraction. Default when tool_choice is omitted is auto, and a forced name mismatch returns 400.

Worked production examples: Example 4: Schema whose design prevents fabrication through nullable fields plus an explicit ambiguity member plus an extensible member with a companion detail string

The three fabrication and classification protections from the reference belong together in one schema when the domain includes sometimes-absent fields, ambiguous source language, and an open-ended category tail. This example builds that combined schema for a payment extraction that handles missing processors, ambiguous intents, and novel payment methods in one contract.

result.json
json
{
  "type": "object",
  "properties": {
    "transaction_id": {
      "type": "string",
      "description": "Transaction identifier exactly as printed in the source, for example TRX-48291. Return exactly as printed and never synthesize."
    },
    "merchant_name": {
      "type": "string",
      "description": "Merchant name as printed. Always present; this is one of the three always-present fields."
    },
    "amount": {
      "type": "number",
      "description": "Transaction amount as a plain number without currency symbols or commas, for example 1234.56. This field is always present."
    },
    "payment_processor": {
      "type": ["string", "null"],
      "description": "Payment processor name exactly as stated in the source. Return null if no processor is directly stated. Do not guess or normalize."
    },
    "payment_method": {
      "type": "string",
      "enum": ["card", "bank_transfer", "check", "mobile_wallet", "unclear", "other"],
      "description": "Select the payment method from the listed values. Choose unclear when the source is genuinely ambiguous about the method. Choose other when the method is clearly stated but outside the four known values."
    },
    "payment_method_detail": {
      "type": ["string", "null"],
      "description": "When payment_method is other, provide the verbatim method string as it appears in the source, for example cryptocurrency or buy-now-pay-later. Otherwise null."
    },
    "notes": {
      "type": ["string", "null"],
      "description": "Any additional notes from the source that do not fit the typed fields. Return null if none are stated."
    }
  },
  "required": ["transaction_id", "merchant_name", "amount"],
  "additionalProperties": false
}
example.ts
typescript
// System prompt that completes the null and classification contract above
const system = [
  "You are a transaction extraction assistant.",
  "Return null for any field where the information is not directly stated in the source.",
  "For payment_method, use unclear when the source does not allow a definite choice among known methods.",
  "For payment_method, use other and populate payment_method_detail when the stated method is outside the known set.",
  "Only extract information explicitly present; do not infer, estimate, or generate plausible values."
].join(" ");

const response = await client.messages.create({
  model: "MODEL_ID",
  max_tokens: 2048,
  system,
  tools: [
    {
      name: "extract_payment",
      description: "Extract transaction data with explicit handling for absent, ambiguous, and novel values.",
      strict: true,
      input_schema: combinedSchema
    }
  ],
  tool_choice: { type: "any" },
  messages: [{ role: "user", content: paymentDocumentText }]
});

This schema demonstrates each protection in its correct slot:

  • Nullable fields that remove fabrication pressure are payment_processor, payment_method_detail, and notes, typed as ["string", "null"] and omitted from required alongside payment_processor and purchase_order in earlier invoice schemas. The prompt statement Return null if not directly stated is the second half that makes null expected rather than merely legal.
  • Explicit ambiguity member unclear in the payment_method enum gives a document that mentions a method phrase ambiguously a valid bucket that does not inflate a known value count.
  • Extensible member other with companion detail keeps known payment methods typed and queryable while giving a cryptocurrency or buy-now-pay-later occurrence a typed flag (payment_method: "other") plus its verbatim value in payment_method_detail. The combination is the only design that covers both ambiguity and novelty in one enum without forcing misclassification or expanding the enum indefinitely.

Boundary note: unclear and other are not substitutes. Using unclear for clearly novel values or other for genuinely undecidable phrasing collapses the distinction that makes each useful. When a single schema must handle both, both values belong in the enum as shown.

Worked production examples: Example 5: Case where the shape is valid yet the values are wrong so the reader sees the boundary of the guarantee

This example shows a complete but factually wrong extraction that passes every schema check and strict tool validation, so an application that stops at shape has a silent data-quality failure. It is the intended demonstration that the guarantee is shape, not truth.

result.json
json
// What the source document actually contains (excerpt, not the model input):
// "Invoice Date: 2026-03-10  |  Delivery Date: 2026-03-28  |  Vendor: Acme Corp
//  Line items:  800.00 (service) + 200.00 (parts)  |  Stated total printed on footer: 950.00"

// What strict tool use returns and validates as structurally correct:
{
  "invoice_number": "INV-2026-0881",
  "vendor_name": "Acme Corp",
  "delivery_date": "2026-03-10",
  "payment_processor": "Stripe",
  "line_items": [
    {"description": "Consulting service", "amount": 800.0},
    {"description": "Parts", "amount": 200.0}
  ],
  "stated_total": 1000.0,
  "calculated_total": 1000.0,
  "conflict_detected": false
}

This output is valid on every structural check the platform enforces:

  • It parses as JSON, has all required fields, has no extra fields because additionalProperties: false, and every type matches (delivery_date is a string, amount values are number).
  • If payment_processor were defined with an enum of known processors that happens to contain Stripe, that enum membership is satisfied.
  • Strict validation passes.

It is nonetheless wrong on three distinct semantic axes that exercised the forensics variants:

  • Wrong-field placement: delivery_date holds 2026-03-10, which is the invoice date rather than the delivery date 2026-03-28. Both are values satisfying type: string with format: date, so the swap is invisible to the schema.
  • Fabrication: payment_processor is Stripe, but the excerpt contains no mention of a processor. The field is non-nullable in this invocation and the model filled a plausible value so the output would be valid for a required field. A nullable definition with a null-use instruction would have allowed null to be honest.
  • Sum mismatch and self-judgment drift: the two line items sum to 1000.0, but the document printed 950.00 as its stated footer total, meaning stated_total as extracted does not match the source printed figure and conflict_detected is asserted incorrectly because the model set that boolean from its own possibly wrong extraction rather than from deterministic application-side arithmetic.

The correction is therefore not a tighter schema. It is a pipeline step after structured extraction that performs calculated_total as the sum of line_items in code, reads stated_total from the source-extracted printed value, compares numerically, and sets conflict_detected deterministically before deciding whether to post or route to a human reviewer. Field placement similarly requires a content-aware check, and absent fields require the nullable redesign rather than length or required retention.

A companion failure surface that would have made the shape guarantee appear to fail when it did not is the refusal case. Replacing the invoice source with disallowed content that triggers safety intervention would produce stop_reason: "refusal" with a 200 status and billed tokens, and the refusal explanation takes precedence over the JSON schema. Callers that parse unconditionally without first inspecting stop_reason and stop_details will treat that refusal as a parse failure of an otherwise guaranteed path, when the correct handling branch is the refusal branch. Token-limit truncation produces the same branch risk with stop_reason: "max_tokens" and an incomplete JSON fragment that never closed.

Build exercise material

The build exercises below reproduce the reference page five-step lab with explicit observable outcomes. Each exercise states what to configure, what to invoke, and how to verify, including the API fields that evidence success. They assume access to any SDK that can set output_config.format and strict: true; the shell shape uses cURL style fields and the SDK shape uses TypeScript variables, both are valid evidence.

Build exercise material: Exercise 1: Define an extraction schema with three required fields, three nullable fields, an unclear value, and an other plus detail member

Goal: Produce a JSON Schema that prevents fabrication and gives ambiguous and novel classifications a typed home, matching the Reference recommendation for other plus detail.

Steps:

  1. Draft a schema with six fields: two required strings invoice_number and vendor_name, three nullable strings payment_processor, purchase_order, and notes typed ["string", "null"] with null-if-absent descriptions, a category enum of ["invoice", "receipt", "contract", "unclear", "other"], and a nullable category_detail for other.
  2. Add required: ["invoice_number", "vendor_name"] and additionalProperties: false and confirm every property appears in properties.
  3. Add a category description that states when to use unclear versus other and when to populate category_detail.
  4. Validate with two fixtures: a complete document and one missing a purchase order with a novel category, checking that the second returns null for the absent fields and other with a populated detail.

Observable outcome that proves the step worked: Under output_config.format or strict tool use, sending the document that lacks a purchase order returns "purchase_order": null rather than a synthesized string, and sending a document with a clearly novel category returns "category": "other" with a non-empty category_detail string. The schema validator confirms required contains only the always-present fields and that category contains both unclear and other. A schema with additionalProperties: false confirms hallucinated vendor_tax_id or similar keys do not appear.

Build exercise material: Exercise 2: Observe tool_choice: "auto" producing text instead of a tool call

Goal: See why auto is unsuitable when guaranteed structured output is needed, directly matching the Reference and forensics observation for single-tool extraction under auto.

Steps:

  1. Register a single strict tool extract_invoice with the schema from Exercise 1 narrowed to invoice fields.
  2. Create a request with tool_choice: {type: "auto"} (or omitted, since that defaults to auto) and prompt the model with an ambiguous document such as a scanned remittance stub that is still parseable as an invoice.
  3. Run the request several times across ambiguous and clear documents and record stop_reason on each response.

Observable outcome that proves the step worked: At least one response returns stop_reason: "end_turn" with a content block of type: "text" that describes the document contents conversationally and contains no tool_use block. This demonstrates that auto allows the model to respond without calling the tool, which breaks an ingestion pipeline that expects structure on every record. The text response may also return stop_reason: "refusal" on disallowed content, which likewise carries no tool_use and shows why invocation guarantees must be stated separately from content safety.

Build exercise material: Exercise 3: Switch to tool_choice: "any" and verify every response is structured

Goal: Confirm that any supplies the missing invocation guarantee while preserving choice among schemas when the document type is unknown.

Steps:

  1. Register the two or three type-specific tools as in Worked Example 3: extract_invoice, extract_receipt, extract_contract, each with strict: true.
  2. Create a request with tool_choice: {type: "any"} (equivalently tool_choice: "any" in SDK shorthand) and pass an unknownDocumentText whose type is ambiguous.
  3. Send several documents that span invoice-like, receipt-like, and prose-form invoices. For each response, log stop_reason and the name of the returned tool_use block.

Observable outcome that proves the step worked: Every response has stop_reason: "tool_use" and contains exactly one tool_use block whose input matches the corresponding input_schema. No response returns text without a tool call. For at least one invoice-like document the returned tool is extract_invoice and for at least one receipt-like document the returned tool is extract_receipt, showing that the model retains the ability to route by content under the guarantee.

Build exercise material: Exercise 4: Force a specific tool with tool_choice: {type: "tool", name: "extract_metadata"} and verify the mandatory first step runs

Goal: Establish that a named tool forced on the first turn satisfies prerequisite ordering for enrichment such as lookup_citations or verify_doi.

Steps:

  1. Register extract_metadata, lookup_citations, and verify_doi (or any dependent tool) as in the lessons, with extract_metadata strict.
  2. Turn one: call messages.create with tool_choice: {type: "tool", name: "extract_metadata"} and the source content that requires enrichment. Observe that the first response contains only tool_use: extract_metadata.
  3. Execute extract_metadata locally, return its result as tool_result in conversation history, and turn two: call messages.create again with the same tool set but with tool_choice: {type: "auto"} (or any if a tool call must continue) and the prior assistant and tool_result messages in messages.

Observable outcome that proves the step worked: Turn one always calls extract_metadata regardless of whether the user phrasing foregrounds enrichment. Turn two then calls a dependent tool such as lookup_citations with the DOI that turn one extracted, showing that the prerequisite was present. Attempting the same workflow after forcing extract_metadata on every turn instead of only turn one would demonstrate the incorrect alternative: enrichment never runs after turn one because the call is locked. A request that forces a name not present in tools returns 400, confirming exact match enforcement.

Build exercise material: Exercise 5: Process three complete and two missing-field documents and verify that nullable fields return null rather than fabricated values

Goal: Confirm the central fix for hallucinated dates, amounts, and identifiers when information is absent from the source, pairing the nullable schema half with the explicit null-use instruction half.

Steps:

  1. Combine the nullable schema from Exercise 1 with a system instruction stating Only extract information explicitly present; return null for any field where the information is not directly stated in the source. Do not infer, estimate, or generate plausible values. This two-part pairing is the documented elimination of the structural pressure to invent.
  2. Prepare five documents: three complete with all requested fields present and valid, and two with deliberately absent fields such as a purchase order that was not printed, a vendor tax identifier that does not appear, and a phone absent from the header. Include a truncated excerpt example among the five where the absent field is genuinely not present rather than merely missed by layout.
  3. Send each document with output_config.format using the nullable schema (or with strict tool extract_invoice with the same tool_choice: any). Record payment_processor, purchase_order, and any tax identifier field on each response.

Observable outcome that proves the step worked: For the three complete documents, nullable fields are populated with correct source values. For the two documents missing information, the same nullable fields return null exactly, not an empty string, not N/A, not a guessed identifier or date. No invented values such as Unknown Vendor or synthesized dates appear among the five. Post-ingestion code can then distinguish null as not mentioned from absent as an extraction failure, and can route a record whose nullable analytics fields are null without flagging it as incorrect. Running the same five documents against a schema where every field remains required with type: string and no null-use sentence in the prompt reproduces the hallucination: all five responses carry a plausible string even where the source had none, illustrating why required-on-sometimes-absent is the driver.

Build exercise material: Exercise 6: Verify composition, legacy acceptance, and failure branches (supplemental)

Goal: Confirm composition, legacy transition, and the two guarantee-break branches.

Steps:

  1. Send a composed request with both output_config.format and a strict tool and verify that a text path returns valid JSON and a tool path returns strict tool_use.
  2. Send the same request with legacy output_format and the beta header, observe transitional success, then rewrite to output_config.format with no header and observe identical behavior. Note the Python SDK now raises TypeError for the old field.
  3. Send a refusal-triggering prompt and a very low max_tokens request and verify stop_reason: "refusal" with 200 status and billed tokens and non-schema text, and stop_reason: "max_tokens" with truncated JSON, both handled before parsing.

Observable outcome: Composition shows the pair shares a cache key, legacy shows transitional acceptance, and the two failure sends show that inspecting stop_reason before parsing is the correct branch.

Mechanism and API surface

Reliability hierarchy tool_use and structured outputs over prompt JSON
tool_use with JSON schemas and output_config.format with json_schema eliminate syntax errors via constrained decoding, prompt-based JSON in text is probabilistic and will produce unparseable output in production.
tool_choice auto, any, and forced
auto lets the model return text, any forces a tool call but lets the model choose which, forced with type tool and name guarantees exactly that tool and 400s on a name mismatch.
Strict mode with additionalProperties false
Setting strict true on a tool enables constrained decoding for its arguments, requires additionalProperties false, all optional properties still in properties, and can be mixed with non-strict tools per request.
output_config.format versus older output_format
output_config.format with type json_schema is canonical, schema root type object with descriptions, older top-level output_format is transitional. Python SDK parse with output_format returns parsed_output as validated Pydantic instances.
Nullable, unclear, and other patterns
Nullable type unions allow honest null when information is absent, unclear handles ambiguous cases, other paired with a freeform detail string captures long-tail categories without validation failure.
Self-correction redundancy fields
calculated_total versus stated_total plus total_discrepancy boolean, conflict_detected for contradictions, and detected_pattern per finding enable inline discrepancy detection and dismissal-driven prompt improvement.
Format normalisation alongside schema
Schema enforces structure, prompt rules such as all dates in ISO 8601 and all currency as decimal numbers without symbols enforce formatting consistency.

The decision rules in play

Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.

R1

Schema-constrained tool use eliminates syntax errors, not semantic errors

When extraction is defined as a tool_use tool with an input_schema, the API constrains generation to that JSON shape or validates the tool_use arguments server-side when strict enforcement is enabled. The application reads from the tool_use block or structured_output object instead of parsing free text. Missing brackets, trailing commas, unescaped quotes, truncated strings, explanatory preamble, and markdown fences are eliminated by construction. Values inside that valid shape are unchecked - the mechanism does not verify source truth, cross-field consistency, or correct field placement.

Syntax and semantics are enforced at different layers. Constrained decoding and schema validation check tokens and types: valid JSON, required fields present, string fields contain strings. They have no access to source truth. Whether delivery_date should hold the delivery date or the invoice date, whether line items sum to the stated total, or whether a jurisdiction string belongs in the jurisdiction field are content judgments. No JSON type system answers them. Tool use makes the structural part deterministic; semantic accuracy still depends on extraction quality, validation, and schema design.

Boundary. The guarantee holds for syntax only and degrades on truncation by max_tokens, safety refusals, or permissive enum casing. Prompt-only JSON can be adequate for low-stakes display, but when a downstream parser or ingestion service rejects non-conforming payloads, only schema-constrained generation gives a usable guarantee. Switching back to prompt JSON reintroduces syntax failures on top of existing semantic risk and is treated as a regression.

Recurring specifics. Types string and number with required arrays. Language tags json for schemas and typescript for API calls. Syntax defects: trailing commas, unescaped characters, missing brackets, unterminated strings, explanatory text before JSON. Read location: tool_use block arguments or structured_output validated object. Enforcement surface: input_schema or output_config.format with JSON Schema.

Wrong answers written against this rule

Proposal. Proposal: Tighter types will fix sum mismatches.

Why it attracts. Stricter types fixed syntax, so stricter types should fix arithmetic.

Why it fails. Types constrain shape, not arithmetic relations. When correct: See boundary.

Proposal. Proposal: Lower temperature or retry with the same prompt will fix semantic errors.

Why it attracts. Trying harder feels like it should improve values.

Why it fails. Temperature changes sampling but adds no validation or grounding. When correct: See boundary.

Proposal. Proposal: Switch back to prompt JSON because schema feels too rigid.

Why it attracts. Rigidity appears to cause the mapping error.

Why it fails. The shape is not the cause and prompt JSON adds syntax failures. When correct: See boundary.

How the same rule gets re-asked
  • Mutation 1: Change syntactic defect from trailing commas to markdown fences - tool use remains the fix. Mutation 2: Change semantic defect from swapped dates to misfiled category - semantic validation remains the fix. Mutation 3: Show 0 percent syntax errors after migration - the inference is any semantic class remains possible.
R2

Sum and reconciliation mismatches require paired-field comparison, not tighter types

Extract both the document-stated figure and an independently computed figure, then compare with code. For invoices this is stated_total as printed and calculated_total as the sum of line_items plus tax_amount, alongside conflict_detected or totals_match or discrepancy_detected. The comparison is arithmetic performed outside the model. Flagged records are routed to human review and auto-posting is blocked. Adding the paired fields is the schema change that makes comparison possible.

Arithmetic consistency is a relation between two values, not a property of either one. JSON Schema can require stated_total to be a number but cannot require it to equal the sum of an array. minimum or pattern cannot detect that two correct-looking numbers disagree. Only independent sourcing and comparison reveals the gap. Extracting calculated_total alongside stated_total makes the gap visible; computing outside the model makes the verdict trustworthy.

Boundary. Paired comparison is for contradictions where both figures are present and legible and the document itself is inconsistent, or where a misread produced one figure. When the total is genuinely absent, there is no stated figure to compare - nullable handling is correct, not forced reconciliation. When the mismatch is a transient misread of a consistent source, bounded retry with error feedback may resolve it before escalation. On genuine drafting inconsistency, retry cannot converge because the source does not agree with itself.

Recurring specifics. Field names stated_total, calculated_total, conflict_detected, totals_match, discrepancy_detected, line_items, tax_amount, subtotal. Percentages cited include 4 percent, 5 percent, 8 to 9 percent, 11 percent showing mismatch. Blind retry repeats the error in about 70 percent of attempts. Routing targets human reviewer, finance reviewer, reconciliation queue.

Wrong answers written against this rule

Proposal. Proposal: Tighten schema with type: number or required on totals.

Why it attracts. Stricter schema feels like it should make numbers agree.

Why it fails. Types guarantee presence, not reconciliation. When correct: See boundary.

Proposal. Proposal: Have the model set conflict_detected by own judgment.

Why it attracts. One fewer application step.

Why it fails. The model judges numbers it may have misread. When correct: See boundary.

Proposal. Proposal: Generic retry with values are inconsistent, please re-extract.

Why it attracts. Cheap and sometimes helps.

Why it fails. Generic feedback names no arithmetic delta and loops on genuine source inconsistency. When correct: See boundary.

How the same rule gets re-asked
  • Mutation 1: Totals to weights - move is identical, only names change. Mutation 2: Comparison inside model versus deterministic code - deterministic is preferred. Mutation 3: Model-set boolean versus app-set flag - app-set is preferred.
R3

Wrong-field placement is a semantic error invisible to schema validation

Schema validation checks that each declared field holds a value of the declared type and that required fields are present. It does not check that the value is the correct real-world value for that field. When delivery_date receives the invoice date, effective_date receives the signature date, or a dependency finding is filed under security, each field holds a valid string or valid enum member - the mapping is wrong. Detection needs semantic validation that cross-checks content patterns or source context, often paired with a retry naming the misplacement.

Types are not semantics. A date satisfying format: date is valid in any date field. Two address fields share type: string and no schema keyword says billing_address must contain the billing address. Enums reject unknown strings but cannot know a dependency finding was misfiled as security when both are members. Field swaps survive any tightening that does not add a content-aware check. Reliable detection compares extracted values against expected content or the source.

Boundary. Not every swap is undetectable - a number versus string swap or an enum-closed mismatch will be rejected. The hard case is when both fields share a type - string dates, string addresses, overlapping categories - which is the common extraction case. There, a semantic layer is needed. When types differ, schema alone catches the swap.

Recurring specifics. Pairs delivery_date versus order or invoice date, effective_date versus signature date, shipping_address versus billing_address, jurisdiction versus party name, salary_min versus salary_max on up to $120k, dependency versus security versus style. Keywords format: date, type: string, pattern, required. Phrasing schema validation passes without errors and 100% schema compliance signals the trap.

Wrong answers written against this rule

Proposal. Proposal: Make the field optional.

Why it attracts. Gives the model an out.

Why it fails. Error is placement not absence. When correct: See boundary.

Proposal. Proposal: Switch tool_choice from tool to auto for flexibility.

Why it attracts. Flexibility feels like it should improve mapping.

Why it fails. Tool choice governs call guarantees, not value placement. When correct: See boundary.

Proposal. Proposal: Generic validation-retry loop.

Why it attracts. Retry fixes many structural errors.

Why it fails. field is wrong without which value belongs where gives nothing to remap. When correct: See boundary.

How the same rule gets re-asked
  • Mutation 1: Swap dates versus swap addresses - same detection, different pattern. Mutation 2: 500 documents with 100 percent compliance - semantic errors still persist. Mutation 3: minimum: 1 on salary - blocks range violations not swaps.
R4

Required-field pressure is the structural cause of fabrication

When input_schema marks a field required or non-nullable string, the model must produce a valid value. If the source does not contain it - PO number not printed, tax_id never stated, hazmat_class blank, flood-zone absent, middle name missing - there is no honest value. The model emits a plausible synthetic: Unknown Vendor, N/A, No breaking changes detected, invented grant_number, fabricated phone.

Schema constraints are hard; do not fabricate is soft. When in tension, the hard constraint dominates because the model has no legal way to signal absence for a required field. Fabrication is therefore structural, not just a reasoning lapse. Prompt-only fixes lower the rate but do not remove it for genuinely absent information.

Boundary. Pressure applies only where information is genuinely absent or not applicable - spam with no target_demographic, theoretical paper with no grant_number. When the value is present but missed due to layout or smudging, the failure is extraction or layout handling, and retry or layout-aware few-shot applies. required remains correct for truly always-present fields such as invoice_number and vendor_name. Decision test: is this field present in every document - yes keeps required, no becomes optional and nullable.

Recurring specifics. Fields po_number, vendor_tax_id, tax_id, purchase_order_number, hazmat_class, phone, weight, middle_name, grant_number, conflicts_of_interest, irb_approval_id, breaking_change_description, target_demographic. Schema patterns required: ["weight", "dimensions"] versus required: ["dimensions"] with type: ["string", "null"]. Phrasing plausible-looking, hallucination behavior.

Wrong answers written against this rule

Proposal. Proposal: Stronger prompt instruction not to hallucinate.

Why it attracts. Directly addresses behavior.

Why it fails. Schema still required - instruction and schema conflict and schema wins. When correct: See boundary.

Proposal. Proposal: Post-extraction validation to detect fabrication.

Why it attracts. Feels like a safety net.

Why it fails. Fabrications are plausible and pass format checks; detection is after the fact. When correct: See boundary.

Proposal. Proposal: Remove the field from the schema.

Why it attracts. Removes pressure by removing the question.

Why it fails. Discards data where field is present. When correct: See boundary.

How the same rule gets re-asked
  • Mutation 1: Domain tax ID to phone to hazmat - fix remains optional and nullable. Mutation 2: One missing field versus seven of fifteen - redesign the sometimes-absent subset. Mutation 3: Few-shot showing N/A while keeping required - still violates honesty.
R5

Nullable and optional fields make null legal and remove fabrication pressure

Optional means removed from required; nullable means typed as ["string", "null"] or anyOf: [{type: "string"}, {type: "null"}] or empty array [] for arrays. The model can return null to represent absence and remain valid. The downstream consumer distinguishes null as not mentioned from absent as extraction failure. Most explicit is both: not in required and typed to allow null.

vendor_name and invoice_number stay required as always present; payment_processor and purchase_order are nullable because they are often absent. A document with no PO returns "purchase_order": null - valid and honest. If purchase_order were required with type: string, the same document would force a fabrication.

result.json
json
{
  "type": "object",
  "properties": {
    "vendor_name": { "type": "string" },
    "invoice_number": { "type": "string" },
    "payment_processor": { "type": ["string", "null"], "description": "Return null if no processor appears" },
    "purchase_order": { "type": ["string", "null"], "description": "Return null if no PO appears" }
  },
  "required": ["vendor_name", "invoice_number"]
}

By moving absence into the legal value set, the schema stops forcing a choice between invalid output and a lie. Returning null becomes as valid as returning a string, so the model no longer needs fabrication to satisfy validity. Instruction-following improves because the soft instruction and the hard constraint no longer conflict.

Boundary. Nullability solves absence, not extraction quality. If a nullable field is present in the source but skipped, flipping it to required does not fix the miss - it reintroduces fabrication on absents. The fix for misses is guidance and few-shot coverage. The opposite case where required is correct is the small always-present set; converting those to optional hides genuine failures.

Recurring specifics. Forms type: ["string", "null"], anyOf: [{type: "string"}, {type: "null"}], nullable: true legacy, removal from required. Array absence as [] versus null. Fields repeatedly made nullable: purchase_order_number, tax_id, vendor_tax_id, payment_processor, phone, middle_name, weight, breaking_change_description, target_demographic. Sentinel null preferred over string "N/A" or "unknown".

Wrong answers written against this rule

Proposal. Proposal: Retry loop checking for placeholder values.

Why it attracts. Catches N/A.

Why it fails. Realistic fabrications pass format checks. When correct: See boundary.

Proposal. Proposal: Switch to prompt JSON for flexibility.

Why it attracts. Null feels more natural.

Why it fails. Reintroduces syntax risk; benefit comes from schema. When correct: See boundary.

Proposal. Proposal: Add pattern to required field.

Why it attracts. Narrows fabrication space.

Why it fails. Model will fabricate a pattern-matching value that is harder to detect. When correct: See boundary.

How the same rule gets re-asked
  • Mutation 1: required with do not guess prompt - answer remains optional and nullable plus instruction. Mutation 2: empty string versus null - null is the inspectable signal; empty string is rejected downstream. Mutation 3: Omit versus null - both can be correct but consistency matters; evidence favors explicit null.
R6

Nullable alone does not make null expected - explicit null-use instruction is the second half

Nullable makes null legal; an instruction makes it expected. After nullability, the prompt must state when to use null: return null for any field where information is not directly stated in the source, only extract information explicitly present; use null for missing values. Without guidance the model may still fill a plausible value even though null is permitted, because its default is to fill rather than abstain. The two-part pattern is nullable type plus extract-if-present else null instruction. For arrays the form is return an empty array when no items are explicitly stated.

Legality and willingness are separate. The type system defines permissible outputs; the model selects which member to emit. Nullable with no guidance leaves selection underspecified and the prior favors content. An explicit instruction shifts policy toward abstention under uncertainty. One-sided fixes reduce but do not remove fabrication: nullable without instruction still fabricates on some docs, instruction without nullable still fabricates on all because null remains invalid.

Boundary. When fields are nullable and instructed yet fabrication persists, the driver is often remaining required fields or few-shot that always populates every slot. Vague wording like only extract values you are certain about leaves the threshold ambiguous and drives high unclear false positives. The precise form return null if not directly stated is tested as effective.

Recurring specifics. Phrases return null if not directly stated, only extract information explicitly present, do not infer, estimate, or generate plausible values. Schema description Output null if this information is not present in the email. Effect size: instruction alone on required fields moved unsupported rates from about 38 percent to 24 percent; full elimination needed the schema half.

Wrong answers written against this rule

Proposal. Proposal: Second LLM call to verify each value exists in source.

Why it attracts. Sounds thorough.

Why it fails. Doubles cost and latency with a second model that can also misjudge. When correct: See boundary.

Proposal. Proposal: Upgrade to a more capable model tier.

Why it attracts. Stronger models follow instructions better.

Why it fails. Capability without permission still fills. When correct: See boundary.

Proposal. Proposal: Make all fields required with strict validation.

Why it attracts. Strictness feels like quality.

Why it fails. Strictness on sometimes-absent fields is the driver. When correct: See boundary.

How the same rule gets re-asked
  • Mutation 1: Already nullable versus required starting point - which half is missing flips the answer. Mutation 2: Vague certain versus explicit return null if not directly stated - explicit wins. Mutation 3: Array fields and null - empty [] is often the contract, not null.
R7

Few-shot null demonstrations teach when to use null where schema only permits it

Four to six few-shot examples pair a truncated source excerpt with its correct target record, including cases where the correct output is null or []. Examples span heterogenous layouts: prose invoices where PO number maps to null, papers where grant_number maps to null, shipping manifests where an out-of-set classification uses the catch-all. The schema makes absence legal; the few-shot makes absence the observed norm.

Models generalise from demonstrations more robustly than from rules for boundary decisions. Whether weight should be null or 2.3 kg or whether an PO number is a job number or genuinely absent is a nuance rules describe poorly. Demonstrations remove ambiguity by showing the mapping. This is load-bearing for long tails: instructions covering two dominant formats do not generalise to novel ones, while examples spanning inline units, column headers, footnotes, split panels, and prose give a template for heterogeneous language to one canonical shape.

Boundary. Few-shot without the schema half teaches a contradiction - the model cannot satisfy the demonstration and remain valid if the field is still required. Few-shot drawn only from the dominant layout does not fix the tail; examples must be chosen from failing long-tail formats. Where the task is pure format normalisation on one layout, a pattern plus description may be enough.

Recurring specifics. Counts three to five or four to six pairs. Source patterns a dozen, roughly half a case, spacious, generous open-plan, standard terms. Mappings weight: null, grant_number: null, purchase_order: null. Layout labels prose-style, scanned-remittance, tabular, inline-unit, column-header, footnote-range, split-panel.

Wrong answers written against this rule

Proposal. Proposal: Keep few-shot limited to the dominant layout.

Why it attracts. Covers most traffic.

Why it fails. Failures concentrate in the tail. When correct: See boundary.

Proposal. Proposal: Rely on schema nullability alone.

Why it attracts. Feels sufficient.

Why it fails. Legality does not teach selection under ambiguity. When correct: See boundary.

Proposal. Proposal: Post-extraction normalisation for quantities.

Why it attracts. Handles loose phrasing.

Why it fails. Cannot fix a fabricated value produced upstream.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Clean omission versus informal phrasing - both need null demonstration while explicit numbers are extracted. Mutation 2: Schema half present but no detail field for other - novel handling collapses. Mutation 3: Retry loop as alternative - repeats same omission on absent data.
R8

The unclear enum value gives ambiguous cases a legal home

For closed vocabularies - category, sentiment, jurisdiction, intent_type, event_type, delivery_status - add a dedicated member for genuine ambiguity: unclear, unknown, or neutral where the ambiguity is balanced evidence versus lack of evidence. The field remains required with a valid enum value, so validation passes without coercing an uncertain document into a definite label. Instruction names when to use it: select unclear when the document does not determine the category.

Without an uncertainty member, a closed enum forces the model to select the nearest fit - semantically wrong from emission and contaminating analytics. Adding unclear turns a forced error into an honest classification: the model is correct by reporting inability to classify. Known categories remain typed and queryable while uncertainty becomes typed and filterable.

Boundary. unclear versus other matters. unclear means insufficient evidence even though the concept is in scope. other means evidence clearly outside the set, with a detail string to carry the novel category. unclear versus neutral also differs: neutral is a balanced judgment, unclear is inability to judge. Adding both when only one is needed over-engineers the enum and confuses selection. When failures are on novel real values, other plus detail is correct, not unclear.

Recurring specifics. Values unclear, unknown, neutral, N/A. Fields category, sentiment, intent_type, event_type, delivery_status, overall_sentiment, jurisdiction. Phrases genuinely ambiguous, cannot be determined from the text, Well that was... interesting, Great product, billing dispute about a charge for a technical service.

Wrong answers written against this rule

Proposal. Proposal: Make the field optional so ambiguous cases can be omitted.

Why it attracts. Omission signals uncertainty.

Why it fails. Omission hides whether extraction was uncertain or skipped.

When it would be right. See boundary case.

Proposal. Proposal: Keep enum strict and pick the closest value.

Why it attracts. Keeps every record in a definite bucket.

Why it fails. Forces systematic misclassification, for example joint ventures inflating MERGER.

When it would be right. See boundary case.

Proposal. Proposal: Confidence scores and low-confidence routing.

Why it attracts. Feels calibrated.

Why it fails. Models can be confidently wrong on forced choices.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Three values versus four with unclear - four wins when ambiguity is common. Mutation 2: N/A as free text on required string - typed unclear is the correct form; N/A is rejected. Mutation 3: Combine unclear with other when test mixes ambiguity and novelty - combined design covers both.
R9

The other plus detail string gives novel categories a typed home

When a category has a useful closed core but the world produces values outside it - payment_method beyond known methods including cryptocurrency, property_type beyond house and condo including tiny house, event_type beyond MERGER including JOINT_VENTURE, delivery_status beyond delivered including held at customs, jurisdiction beyond US and UK including CA - add other to the enum and a companion nullable string such as category_detail or payment_method_detail. The model selects other for the typed field and populates the detail with the freeform value. Known values stay queryable; novel values are inspectable and not coerced.

result.json
json
{
  "type": "object",
  "properties": {
    "intent": {
      "type": "string",
      "enum": ["BILLING", "SHIPPING", "RETURNS", "TECHNICAL", "OTHER"],
      "description": "Select OTHER when the intent falls outside the four listed values."
    },
    "intent_detail": {
      "type": ["string", "null"],
      "description": "When intent is OTHER, describe the specific intent in free text. Otherwise null."
    }
  },
  "required": ["intent"]
}

Pure enums fail on open-world inputs; pure free text fails on consistency. The other plus detail pattern keeps known values comparable and queryable while giving novel values a typed flag plus verbatim record. The long tail becomes monitorable - how often other appears and what detail strings cluster - so taxonomy expansion can be data-driven.

Boundary. An enum of fifty values to anticipate everything is brittle and still finite. Removing the enum to free text loses consistency on the majority. The middle is a small stable core plus one open slot. For phrasing drift such as hate speech versus Hate Speech, the fix is enum plus normalisation, not other.

Recurring specifics. Enums payment_method, property_type, event_type, delivery_status, category, intent. Detail names category_detail, payment_method_detail, property_type_detail, jurisdiction_detail. Novel examples cryptocurrency, buy-now-pay-later, studio, tiny house, hospital charity program, joint venture, held at customs pending inspection.

Wrong answers written against this rule

Proposal. Proposal: Continuously expand the enum.

Why it attracts. Keeps every value first-class.

Why it fails. No endpoint; every new value errors until redeploy.

When it would be right. See boundary case.

Proposal. Proposal: Drop enum to free text with post-processing.

Why it attracts. Never fails validation.

Why it fails. Unbounded input makes trustworthy normalisation hard.

When it would be right. See boundary case.

Proposal. Proposal: Map novel to closest known via few-shot.

Why it attracts. Keeps validation passing without schema change.

Why it fails. Silent miscategorisation - tiny house to house passes but is wrong.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: other to unclear - wrong catch-all for this class. Mutation 2: other without detail - flagged but not recorded. Mutation 3: Second enum with same values - double counts known instead of opening to unknown.
R10

Format normalisation lives in prompt descriptions and examples alongside a strict schema

Source dates appear as 15 March 2024, 03/15/2024, Jan 15, 2024, amounts as $1,234.56 or 1234.56. The downstream contract is canonical such as YYYY-MM-DD and plain numbers. The layered fix: field description naming the required format, schema constraint such as format: date or pattern: ^\d{4}-\d{2}-\d{2}$ or type: number with description, alongside prompt instructions All dates in ISO 8601 YYYY-MM-DD and few-shot pairs mapping varied input to canonical output.

Schema enforces shape at generation; prompt enforces mapping from whatever source format was seen to the canonical target.

result.json
json
{
  "type": "object",
  "properties": {
    "effective_date": {
      "type": "string",
      "format": "date",
      "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
      "description": "Use ISO 8601 YYYY-MM-DD, e.g. 2024-06-15"
    },
    "amount": {
      "type": "number",
      "description": "Plain number without currency symbol or comma, e.g. 1234.56"
    }
  },
  "required": ["effective_date", "amount"]
}

Constraints tell what output type must be; prompt rules tell how to get there from the input. format: date communicates a valid date string is required but not that 03/15/2024 should become 2024-03-15. The prompt mapping supplies the second piece. Either alone leaves a gap: pure schema can produce correct types in the wrong interpretation, pure prompt reduces variance but has no enforcement before parsing. Layered together they handle type and conversion.

Boundary. Placing guidance only in schema description can work for simple cases, but multi-format sources need prompt examples mapping each variant. For amounts, the strongest guarantee for exact decimals is typing as string with pattern: ^\d+\.\d{2}$ and converting in code, because multipleOf on number is less deterministic.

Recurring specifics. Formats YYYY-MM-DD, MM/DD/YYYY, ISO 8601, RFC 3339. Keywords format: date, format: date-time, pattern, multipleOf. Fields effective_date, delivery_date, start_date, end_date, amount, total_amount, purchase_date. Currency handling where a bare number assumed USD loses fidelity.

Wrong answers written against this rule

Proposal. Proposal: Regex post-processing to fix dates after.

Why it attracts. Keeps generation simple.

Why it fails. Fragile across locales and ambiguous orderings.

When it would be right. See boundary case.

Proposal. Proposal: Temperature to steer format compliance.

Why it attracts. Determinism feels related.

Why it fails. Determinism does not change type contracts.

When it would be right. See boundary case.

Proposal. Proposal: Per-vendor schemas for each layout.

Why it attracts. Tailors contract.

Why it fails. Multiplies maintenance and routing.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Target YYYY-MM-DD to USD - same layered principle. Mutation 2: pattern versus format - both effective, Both A and B appears. Mutation 3: type: number with pattern - needs type: string.
R11

Field descriptions and format and pattern constraints guide value shape at generation time

Each property carries type, optional format, optional pattern, and description that act as direct instructions at generation time. Examples: type: string, enum: ["sandbox", "staging", "production"], format: date, format: uri for RFC 3986, format: email, pattern: ^https:// for HTTPS. A quality description is three to four sentences covering purpose, when to use, limitations, and an example value. Load-bearing for date ranges, PatientID as ten digits, and amount shape.

Descriptions are the interface contract the model reads when constructing arguments. When a description says Use YYYY-MM-DD format, e.g. 2024-06-15 or 10 alphanumeric characters without hyphens, e.g. AB1234567C, the model has a pattern to copy. Without them the model infers from the field name and drifts. Schema vocabulary handles shape; description handles intent and example-guided precision.

Boundary. Descriptions fix single-field shape, not cross-field relations. endDate after startDate cannot be expressed in JSON Schema and must be checked in code. pattern: "date" is invalid and type: date does not exist - correct forms are type: string with format: date or a pattern regex. For genuinely tolerant inputs, server-side normalisation as fallback is reasonable but not the primary guarantee.

Recurring specifics. Names transactionId, city, url, email, start_date, end_date, PatientID, currency, status. Keywords enum, format, pattern, type, required, minLength, multipleOf. Example values sandbox, staging, production, 2024-06-15, AB1234567C.

Wrong answers written against this rule

Proposal. Proposal: Fix shape with lower max_tokens or temperature.

Why it attracts. Constraining output feels like it should fix shape.

Why it fails. Limits do not communicate contracts.

When it would be right. See boundary case.

Proposal. Proposal: Validation-retry without fixing description.

Why it attracts. Retry fixes some shape errors.

Why it fails. Fixes only after fact and repeats trips for a statable contract.

When it would be right. See boundary case.

Proposal. Proposal: Split value and measurement into two tools.

Why it attracts. Eliminates 23 percent invalid combinations by construction.

Why it fails. Only when concepts are structurally separate; for simple shape errors a correct type plus description is lighter.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Field date to PatientID - same fix. Mutation 2: format: email to pattern - format preferred. Mutation 3: Tool versus property level - value shape belongs at property.
R12

tool_choice auto allows text, any forces a tool call with model choice, forced name locks one tool

tool_choice has three families. auto is default: model may return text or tool_use depending on context. any means must call at least one tool but may choose which - structured guarantee with selection flexibility. Forced named selection tool_choice: {type: "tool", name: "extract_data"} guarantees exactly that named tool is called; the name must match name in tools character by character or the request returns 400. Complementary values none forbids tools and omission defaults to auto. CLI mirrors enforcement with --output-format json plus --json-schema validation and fail-closed gating on structured_output.

example.ts
typescript
const forced = await client.messages.create({
  model: "claude-sonnet-5",
  max_tokens: 4096,
  tool_choice: { type: "tool", name: "extract_metadata" },
  tools: [extractMetadataTool, lookupCitationsTool, verifyDoiTool],
  messages: [{ role: "user", content: documentText }]
});

Control versus flexibility is the tradeoff. auto preserves textual agency for explanation or clarification. any removes the ability to return plain text, which ingestion needs, while letting the model pick the fitting schema. Forced name removes both abilities for compliance gates or prerequisite steps. Wrong level admits text where only structure is safe or locks to the wrong schema.

Boundary. System prompt Always call extract_metadata first is probabilistic and drifts under long context or user phrasing. tool_choice guarantees at the API layer. The opposite case where any or forced is wrong is when text is legitimately acceptable - interactive Q and A or clarification. There auto is correct. For multi-step ordering, any guarantees a tool but not which one, so prerequisite steps must not rely on any.

Recurring specifics. Spellings tool_choice: "auto", tool_choice: "any", tool_choice: {type: "tool", name: "extract_metadata"}, tool_choice: "none", tool_choice: {type: "tool", name: "write_audit_log"}. Error 400 on mismatch. Tool names extract_metadata, extract_invoice, extract_agenda, classify_ticket, log_review_decision, write_audit_log.

Wrong answers written against this rule

Proposal. Proposal: Force by listing tool first or describing prominently.

Why it attracts. Ordering feels like priority.

Why it fails. Selection under auto or any is task-fit driven, not array position.

When it would be right. See boundary case.

Proposal. Proposal: Increase max_tokens to make tool choice reliable.

Why it attracts. More room seems to help.

Why it fails. Guarantees are independent of token budget.

When it would be right. See boundary case.

Proposal. Proposal: Remove other tools to implicitly force one tool.

Why it attracts. Reduces choice to one.

Why it fails. Creates debt for subsequent steps and is fragile.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: auto text failure to any success when type unknown - key distinction. Mutation 2: any success to forced extract_invoice failure - forced picks one regardless of fit. Mutation 3: Requirement to forbid tools - value flips to none.
R13

Unknown document type with a known tool set calls for tool_choice any

When the pipeline holds separate extraction tools for type-specific schemas - extract_invoice, extract_receipt, extract_contract - and inspection shows ambiguous documents trigger clarifying text like I am not sure which format this is instead of a tool call, tool_choice: "any" is the minimal change that guarantees a structured call while preserving correct schema selection. With any, every response has stop_reason: tool_use and a valid tool call conforming to the chosen schema.

The pipeline needs two guarantees simultaneously: at least one schema is applied and the schema matches the document. Forcing a single named extraction would misroute receipts to the invoice schema or contracts to the receipt schema. Leaving auto admits text. any is the only setting that gives the first guarantee without sacrificing the second - it forces structured output and lets the model classify by document content, which it does more reliably than a pre-routing heuristic based on filename or keyword.

Boundary. When the number of schemas grows large - ten or more distinct document types with duplicated prompt logic - any becomes inefficient. Every call carries all schemas and the model sees conflicting cues. The boundary case then moves to a two-stage pipeline: classify with a lightweight call, then tool_choice: {type: "tool", name: "extract_{doc_type}_schema"} on the second call targeting the right schema. The lightweight classification can itself be a small model. When only one extraction tool exists, any and forced named tool are both guaranteed, but forced named is more precise.

Recurring specifics. tool_choice: "any" repeated across many contexts. Tool sets of two to three type-specific extractors and also ten-schema variants. Symptom phrasing 12% of documents receive no tool call or conversational clarification questions. The guidance remove tool_choice entirely and rely on prompt is a recurring distractor that leaves auto default behavior.

Wrong answers written against this rule

Proposal. Proposal: Keep auto and add always use an extraction tool to prompt.

Why it attracts. Feels like it should steer selection.

Why it fails. Prompt is probabilistic; text still appears under auto.

When it would be right. See boundary case.

Proposal. Proposal: Force extract_invoice as a default fallback.

Why it attracts. Guarantees a call.

Why it fails. Applies a single schema to many types and produces structured but semantically wrong output for non-invoice documents.

When it would be right. See boundary case.

Proposal. Proposal: Merge all schemas into one generic tool with many optional fields.

Why it attracts. Avoids routing entirely.

Why it fails. Creates sparse output where most fields are null and increases hallucination pressure.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Two tools and ambiguous docs - any solves with model-picked schema. Mutation 2: Three type-specific tools where any might pick wrong one on superficial similarity - move to classify-then-force. Mutation 3: Documents arriving in nine recurring layouts - any handles type, few-shot handles layout.
R14

Mandatory first-step ordering calls for forced named tool on turn one then auto

When a pipeline requires extract_metadata or extract_fields to run before any enrichment - because enrichment tools need the DOI or document type or invoice number that only metadata provides - the first turn must use tool_choice: {type: "tool", name: "extract_metadata"}. After that turn returns, its output enters conversation history and subsequent turns switch to tool_choice: "auto" or tool_choice: "any" so dependent tools like lookup_citations and verify_doi or get_route_optimization and assign_driver can be chosen by the model now that their prerequisite data is present.

example.ts
typescript
// Turn 1: guarantee prerequisite
const t1 = await client.messages.create({
  model: "claude-sonnet-5",
  tool_choice: { type: "tool", name: "extract_metadata" },
  tools: [extractMetadataTool, lookupCitationsTool, verifyDoiTool],
  messages: [{ role: "user", content: prompt }]
});
// Turn 2: allow model judgment with history containing DOI
const t2 = await client.messages.create({
  model: "claude-sonnet-5",
  tool_choice: { type: "auto" },
  tools: [extractMetadataTool, lookupCitationsTool, verifyDoiTool],
  messages: [...t1.messages, t1.response]
});

Ordering is a hard dependency, not a preference. Under auto the model sometimes calls enrichment first because the user phrasing foregrounds the enrichment request - Extract metadata and tell me how often cited it is - and the model follows the salient task. Under any it must call a tool but still may choose enrichment. Forcing the named tool on turn one removes the model's ability to reorder; switching to auto afterward restores judgment once the dependency is satisfied. This two-phase pattern avoids locking every turn to metadata extraction.

Boundary. Forcing the named tool on every turn, not just turn one, locks the pipeline and prevents enrichment from ever running. The tested subtlety is that force on every API call looks correct but blocks dependent tools on the second turn. The opposite case where forced ordering is unnecessary is when tools have no data dependency - independent parallel tools like get_route_optimization and check_inventory may benefit from auto sequencing with model judgment rather than a forced order.

Recurring specifics. Tool names extract_metadata, lookup_citations, verify_doi, get_transaction_history, get_route_optimization, assign_driver, extract_fields, login_check. Patterns tool_choice: {type: "tool", name: "extract_metadata"} for turn one, then tool_choice: "auto" or tool_choice: "any" for follow-ups. Error case of calling lookup_citations before metadata, causing failure for missing DOI. Workflow labels classify first, then extract and two-stage pipeline.

Wrong answers written against this rule

Proposal. Proposal: Set tool_choice: "any" on turn one plus prompt guidance to extract first.

Why it attracts. Ensures some tool fires plus guidance feels sufficient.

Why it fails. any lets model choose enrichment and prompt guidance is probabilistic.

When it would be right. See boundary case.

Proposal. Proposal: Reorder tools array so metadata appears first.

Why it attracts. Feels like priority signal.

Why it fails. Model does not prioritize by array position.

When it would be right. See boundary case.

Proposal. Proposal: Increase max_tokens or add few-shot showing correct order while leaving auto.

Why it attracts. Shows correct pattern without constraint.

Why it fails. Still allows text or wrong tool under auto.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Present any as sufficient for prerequisite - not sufficient; forced named is required on turn one. Mutation 2: Present force on every turn versus force on turn one then auto - second pattern is preferred for enrichment. Mutation 3: Combine prerequisite with none to forbid tools - prevents rather than orders.
R15

Bounded retry with specific error feedback fixes format and structural errors

When validation rejects an extraction - policy_number with hyphens when ten alphanumerics without hyphens are required, incident_date in the future, a date 03/04/2025 when ISO 2025-03-04 is required, malformed JSON with truncated braces - the pipeline re-requests with the original document, the failed extraction, and the specific validation error messages and the correct format requirement, then allows the error to be corrected. The retry is bounded, typically capped at two to three attempts, after which the record goes to human review or a not_present bucket. The retry is also typed: only retryable failures such as format and structural errors are candidates.

Generic feedback please retry or the JSON was invalid gives the model nothing to change - resampling reproduces the same mistake in about 70 percent of blind retries. Specific feedback naming the field, the incorrect value, the required format, and the constraint violated gives the model an actionable delta. Validation Errors: policy_number must be 10 alphanumeric characters without hyphens plus an example AB1234567C directs attention to the exact transformation needed while preserving correctly extracted fields. Bounded retry prevents infinite loops while capturing the large share of format errors that clear on one retry - telemetry cites about 90 percent resolving on the first retry.

Boundary. The mechanism handles fixable surface errors where the source does contain the needed information in some readable form. When validation fails because the information is genuinely absent - a blank concomitant medication cell, an unreferenced exhibit never supplied, a renewal addendum legitimately omitting an effective date - no feedback message can conjure the value. That class must be routed out of the retry path. The opposite case where retry alone is sufficient is transient layout misreads such as split cells or skew that sometimes resolve on a second attempt with the error appended; there, one bounded retry with context is appropriate before classification.

Recurring specifics. Retry phrasing Validation Errors: policy_number: Extracted 'ABC-123' but must be 10 alphanumeric characters without hyphens (e.g., 'AB1234567C') and incident_date: Extracted '2027-03-15' but must be a date in the past. Retry caps up to three times, two retries, three attempts. Retry payload includes original document plus rejected JSON plus specific errors as tagged sections. Success rate cited 90% of failures on the first retry. Retry without error context repeats the error in 70%.

Wrong answers written against this rule

Proposal. Proposal: Resend the original document unchanged and hope for different sampling.

Why it attracts. Cheapest retry.

Why it fails. No new signal; reproduces the same format error.

When it would be right. See boundary case.

Proposal. Proposal: Tighten JSON schema so totals must equal line sums.

Why it attracts. Schema feels like it should enforce consistency.

Why it fails. Cross-field arithmetic is not a schema constraint.

When it would be right. See boundary case.

Proposal. Proposal: Retry indefinitely or raise max_tokens instead of error feedback.

Why it attracts. Feels more persistent or spacious.

Why it fails. Indefinite retry wastes compute on unfixable absences; max_tokens addresses truncation not value shape.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Error is generic The data is invalid. Please try again. versus field-specific - field-specific is preferred. Mutation 2: Retry carries source alone versus source plus failed output plus errors - latter is preferred and gives context to correct precisely. Mutation 3: Cap at one attempt versus bounded three - bounded three captures the first-retry resolution while limiting waste.
R16

Retries cannot conjure information absent from the source

When validation fails because the source legitimately contains no value - hazmat_class on a manifest that never mentions it, shipping_date blank on a claimant form, effective_date only in an unreferenced exhibit never supplied, shipping_date required when the document has none - the pipeline must not cycle the document through retries. The correct action is to classify the failure as genuine absence, emit null via a nullable field or a typed not_present or absent_field outcome, and route the record to human review or a document-gathering step. Retry is reserved for format and structural errors where the source does contain the value in a recoverable form.

No amount of re-prompting creates data that does not exist in the source. Repeated attempts on absence consume budget - telemetry cited 32 percent of extraction compute wasted on genuine absence plus 40 percent monthly spend overage from 2.7 average attempts per document - and leave inventing behavior unchanged or worsened. Nullable fields plus routing handle honesty and inspectability; retry handles recoverability. Mixing the two wastes resources on the wrong class.

Boundary. Not every missing-field error is genuine absence. A split-cell scan or low legibility smudge may produce a null that resolves on a second attempt with error feedback - that case is retryable as a layout_error or format class. The classification step is therefore load-bearing: each failure is tagged layout_error versus absent_field versus structural versus corruption. Only the first and structural categories are retryable; absence is routed out. The opposite case where retry is correct is precisely those layout and format categories.

Recurring specifics. Flags isRetryable, errorCategory, layout_error, absent_field, not_present. Fields hazmat_class, shipping_date, policy_effective_date, effective_date, shipping_date left blank by claimant. Metrics 2.7 attempts per document, 40% over forecast, 32% of compute budget. Prompt line tried only extract dates actually present that modestly reduced guessing but did not change retry spend.

Wrong answers written against this rule

Proposal. Proposal: Increase retry count from three to ten.

Why it attracts. More tries seems to help absence.

Why it fails. Absence never resolves; more tries just burn compute.

When it would be right. See boundary case.

Proposal. Proposal: Rewrite the error message to be more detailed so model tries harder.

Why it attracts. More detail feels like it should push the model to find the value.

Why it fails. Detail cannot locate a value that is not in the document.

When it would be right. See boundary case.

Proposal. Proposal: Force tool_choice to the extraction tool name to populate the field.

Why it attracts. Guarantees a value appears.

Why it fails. Forces a fabrication when source has none - structurally the same as required-field pressure.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Present 95 percent of missing vendor_tax_id retries failing - the inference is absence, not format. Mutation 2: Present half layout misreads and half absent absences - classification plus selective retry is the answer. Mutation 3: Offer isError: true blanket retry - must be replaced with isRetryable typed flag.
R17

Error payloads must be typed and routable with errorCategory and isRetryable

Tool validation errors are returned as structured objects with errorCategory, isRetryable, and a human-readable description, rather than a single boolean isError or processed: false or a numeric code alone. Genuinely absent fields are represented as valid null values rather than errors at all. The calling pipeline classifies transient or structural or layout_error as retryable and absent_field or not_present as non-retryable, and routes isRetryable: false cases to human review or a document-gathering step while resubmitting retryable cases with feedback.

A single boolean flattens distinct causes into one retry path: a corrupted PDF, a missing exhibit, and a schema violation all hit the same branch and are retried identically. That wastes compute on cases that will never resolve and obscures which failures are fixable. Typed categories separate concerns: transient corruption may clear on retry, structural violations may clear with specific feedback, absence never will. Representing absence as null instead of an error further separates valid emptiness from failure, so null routes to business logic and error routes to recovery logic. The result is precise retry spend.

Boundary. Not every absence should be non-retryable forever. If the pipeline can fetch a missing exhibit referenced by the source, the correct move is to route to a document-gathering step and then retry once the source is augmented. The boundary is that isRetryable is a property of the cause in the current source, not of the abstract ability to ever succeed. The opposite case where a simple code is enough is a pipeline with only one error class - there, a numeric code may suffice, but heterogeneous extraction with at least three classes needs the richer structure.

Recurring specifics. Fields errorCategory, isRetryable, description, human-readable description, error, isError. Categories transient, corruption, layout_error, structural, validation error, absent_field, not_present. The anti-pattern is validate_schema returning {"error": true} for all three causes. The corrective pattern is {"errorCategory": "layout_error", "isRetryable": true, "description": "merged cells in rider table"} versus {"errorCategory": "absent_field", "isRetryable": false}.

Wrong answers written against this rule

Proposal. Proposal: Single numeric error code mapped via lookup table.

Why it attracts. Compact.

Why it fails. Still requires out-of-band decoding and does not make null versus error explicit.

When it would be right. See boundary case.

Proposal. Proposal: Automatic retry up to five times before logging.

Why it attracts. Feels like eventual success.

Why it fails. Treats all causes identically and burns attempts on absence.

When it would be right. See boundary case.

Proposal. Proposal: Boolean processed: false with no detail, caller decides.

Why it attracts. Defers decision.

Why it fails. Caller has no detail to decide correctly.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Add not_present typed outcome for policy_effective_date - route straight to not-found bucket with no retry. Mutation 2: Keep isError plus add retry count - still blind to cause. Mutation 3: Represent absence as error versus valid null - valid null is correct.
R18

Self-correction fields inside the extraction enable inspection without a second pass

The schema is enriched to carry its own validation affordances: calculated_total alongside stated_total, conflict_detected boolean, line_items[] plus subtotal plus tax_amount plus declared_total_gross_weight plus computed_line_item_sum, hs_code_source_line linking each code to its line, source_excerpt required when a non-null figure is present, evidence_status with values explicit or inferred or absent or stated or inferred or documented. A semantic validation step then compares the paired values and sets or checks the flag, and routing is based on the flag. The whole reconciliation stays inside the single extraction response.

Self-correction fields make inconsistency inspectable at write time. Without them, downstream reconciliation must recompute from the full document or from an external second pass that no longer has the extraction context. Carrying both figures plus the link keeps the divergence visible in one record and makes automated routing trivial - filter on conflict_detected or totals_match. The pattern also handles within-document conflicts explicitly rather than collapsing genuine disagreements - rider rate versus table rate - into one silently chosen figure.

Boundary. Self-correction fields are for inspectability, not authority. The model-computed calculated_total is still a model reading; trusting it as the authoritative total without deterministic recomputation repeats the same-flaw problem at one remove. The flag itself is also a model judgment unless it is set by code. The boundary is that self-correction fields are the inputs to a deterministic check, not the check. When the downstream can recompute deterministically from extracted line items, the self-correction fields may be redundant - the deterministic step alone covers the arithmetic.

Recurring specifics. Field combinations rider_rate and table_rate plus conflict_detected, calculated_total and stated_total plus conflict_detected or totals_match or discrepancy_detected, declared_total_gross_weight plus computed_line_item_sum plus hs_code_source_line, evidence_status with explicit / inferred / absent or stated / inferred / absent or documented / inferred / absent, source_excerpt and source_clause and source_text_excerpt. The pattern is cited with page references to the exam guide.

Wrong answers written against this rule

Proposal. Proposal: Extract only one total and trust it.

Why it attracts. Fewer fields.

Why it fails. Hides contradictions that downstream audit later discovers.

When it would be right. See boundary case.

Proposal. Proposal: Rely on model is_correct boolean without paired fields.

Why it attracts. Single flag is simpler.

Why it fails. Model judges its own potentially misread arithmetic without exposing both values.

When it would be right. See boundary case.

Proposal. Proposal: Collapse conflicting leases by retrying until one rate wins.

Why it attracts. Produces a single figure the downstream expects.

Why it fails. Destroys genuine conflict that needs human reconciliation.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Two fields versus three plus detail - other plus detail adds provenance on top of pairing. Mutation 2: conflict_detected set by model versus by deterministic code - code-set is authoritative. Mutation 3: Single escalation_rate versus paired rider_rate and table_rate - paired is preferred.
R19

Deterministic application-side comparison beats model self-judgment for arithmetic

After the extract_po or expense or invoice tool returns, application code independently sums the extracted line_items amounts plus tax or plus subtotal, compares against stated_total, and sets a typed discrepancy_detected or disagreement flag. Auto-posting is blocked on flagged records and they are routed to a reviewer. The model may also emit a calculated_total for inspection, but the authoritative comparison is not the model's boolean - it is the code's recomputation from the same extracted line items.

A valid schema guarantees syntax, not meaning. The model that produced stated_total is the same model that produced the line items; a second judgment by the same pass carries the same misread risk. Recomputing outside the generation is deterministic and independent of the extraction sampling that may have misread one figure. This separates data capture from truth determination and prevents the pipeline from reusing a flawed reading to validate itself.

Boundary. Deterministic recomputation assumes line items are extracted accurately enough to recompute. If line items themselves are frequently fabricated or misread, recomputed sums are unreliable and the deeper fix is layout-aware few-shot or a second independent extraction. The complementary case where model self-comparison is useful is as a pre-filter to prioritise which records enter deterministic checking, but the gate decision must still be the code's flag.

Recurring specifics. Steps app-side sum of line amounts plus tax, compare against stated_total, set discrepancy_detected, block auto-posting, route to finance reviewer. The pattern is framed as the answer to 9 percent lease mismatches and procurement 11 percent weight plus 7 percent code misplacement combined.

Wrong answers written against this rule

Proposal. Proposal: Have the model return totals_match and trust it.

Why it attracts. Single-call simplicity.

Why it fails. Model self-validates its own potentially erroneous figures.

When it would be right. See boundary case.

Proposal. Proposal: Post orders where totals_match is true and auto-file the rest with a log.

Why it attracts. Recovers some automation.

Why it fails. Still trusts model judgment over code.

When it would be right. See boundary case.

Proposal. Proposal: Make all monetary fields required so every record is fully populated.

Why it attracts. Feels like completeness ensures correctness.

Why it fails. Required fields do not ensure arithmetic consistency; they ensure presence.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: calculated_total present for inspection plus deterministic flag versus calculated_total absent - first is more inspectable. Mutation 2: Code discrepancy_detected versus model totals_match - code flag is authoritative. Mutation 3: Block posting plus route versus file most recent and log - block plus route is correct for reconciliation requirements.
R20

Schema type hints remain soft without strict enforcement and post-validation

JSON Schema fields such as type: integer with minimum: 1 and maximum: 10 for risk_score, type: number for amount, or type: string for age, are hints to constrained decoding or to the model's argument construction. Without strict: true on the tool definition, the model may still emit a string "7" for a number field or "30" for an age. The pipeline must validate after generation and coerce or retry with error feedback on type mismatch. Where exact shape is critical, strict: true enforces that the model's output conforms to the declared input schema at the API layer.

Type constraints are soft hints interpreted by the model unless the API is told to treat them as hard constraints. Constrained decoding can enforce types at token generation, but not every surface enables it by default. Validation after generation closes the gap and gives the pipeline a place to feed a type-specific error back. Without post-validation, soft-hint violations propagate as string-typed amounts or enum deviations that crash downstream routing - requires_escalation as "yes" instead of boolean, risk_score as "7".

Boundary. For display-only outputs where "$1,234.56" as a string is acceptable, strict numeric enforcement is unnecessary. For exact formatting such as two-decimal amounts, type: string with pattern and conversion in code is more deterministic than type: number plus multipleOf, because number constraints are soft. The opposite case where strict enforcement is overwhelmingly preferred is typed gates feeding automation - PR verdict pass or fail, compliance log_review_decision, routing categories.

Recurring specifics. Keywords strict: true, type: integer, type: number, type: string, minimum, maximum, enum, format, pattern. Fields risk_score, age, amount, priority, requires_escalation, transactionId. Language tags json, typescript. The tested phrase strict tool use constrains the model's generated tool input so it validates against that tool's declared JSON input schema.

Wrong answers written against this rule

Proposal. Proposal: Adding required: true to the tool object will enforce types.

Why it attracts. required feels related to validation.

Why it fails. required enforces presence, not type or range.

When it would be right. See boundary case.

Proposal. Proposal: Raising temperature to zero guarantees integer type.

Why it attracts. Zero feels deterministic.

Why it fails. Determinism does not change type; string versus number is independent of sampling.

When it would be right. See boundary case.

Proposal. Proposal: Setting tool_choice to any also makes arguments conform.

Why it attracts. Tool choice feels like a validity control.

Why it fails. tool_choice governs whether and which tool runs, not argument types.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Present type: string with format: email versus type: string with pattern: .@. - both can work, format is semantically preferred. Mutation 2: Number with two decimals versus string with regex - string plus pattern is more deterministic. Mutation 3: response_format: json_object claimed as Anthropic field - response_format naming belongs to another provider; Anthropic uses output_config.format or tool_use.
R21

Splitting a generic free-text instruction tool into purpose-specific tools removes combination errors

A tool such as analyze_document(doc, instruction: string) with instruction values including extract financial metrics versus summarize the methodology produces inconsistent formats because output shape is inferred from free text. The fix is purpose-specific tools with defined inputs and outputs: extract_data_points(document, data_types: enum[]) returning data_points[], summarize_content(document, focus_area, summary_length) returning summary plus key_points[], verify_claim_against_source(document, claim) returning supported plus evidence plus confidence. When value and measurement mix across card categories - reps for running paired with miles for bench press at 23 percent - splitting into log_cardio_workout with duration_minutes and log_strength_workout with reps and sets eliminates the combination by construction.

Free-text instructions are ambiguous by definition. The same phrasing is interpreted differently across invocations, so no single description can cover both extraction and summarisation reliably. Separate tools make the desired shape a schema fact rather than an instruction interpretation - the model cannot return a narrative where a data table is required because the output schema for that tool demands the table shape. Parameter separation extends the same logic: when a field cannot be passed incorrectly because the field does not exist in that tool's schema, the error rate for invalid combinations drops to zero.

Boundary. Adding an analysis_type enum to a single generic tool can help when the task genuinely varies along one axis but the output shape is otherwise identical. The boundary is when outputs differ structurally - then enum alone leaves the format decision underspecified. The opposite case where splitting is wasteful is closely related queries with overlapping outputs, such as get_salary and get_debt: one tool with optional fields is the lighter contract.

Recurring specifics. Generic tool analyze_document with instruction: string. Specific tools extract_data_points, summarize_content, verify_claim_against_source, log_cardio_workout, log_strength_workout, log_workout. Input cases measurement: reps for running, measurement: miles for bench press. The corrected split removes the offending field from the schema entirely.

Wrong answers written against this rule

Proposal. Proposal: Enhance tool description with detailed examples mapping phrasings to formats.

Why it attracts. Richer description feels like it should cover behavior.

Why it fails. Still instruction interpreted on each call; drift persists.

When it would be right. See boundary case.

Proposal. Proposal: Add enum constraint on measurement alone.

Why it attracts. Restricts values to valid members.

Why it fails. miles is still a valid enum value and still wrong for bench press.

When it would be right. See boundary case.

Proposal. Proposal: Server-side validation returning descriptive errors plus retry.

Why it attracts. Catches errors after the fact.

Why it fails. 23 percent invalid rate means wasted calls even with better messages.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Coordinator pre-classifies analysis request - adds complexity and classification error rather than fixing the schema. Mutation 2: Keep single tool but add enum - insufficient for structurally different outputs. Mutation 3: Merge extract_data_points and verify_claim into one - reintroduces combination risk.
R22

Few-shot examples spanning the long-tail layouts teach canonical shape where instructions do not generalise

When the same field is transcribed inconsistently across source heterogeneity - billing frequency as clause text versus normalized code, quantity as a dozen versus 12 units, units as milligrams per deciliter versus mg/dL, reference_range as one string versus a lowhigh pair, presentation of dates as day-month-year versus month-day-year - detailed written instructions steeping several known layouts steady those specific templates but leave novel ones erratic. The fix is four to six few-shot examples spanning the divergent long-tail layouts - inline units, column-header units, footnote ranges, split-panel reports, prose and scanned prose - each pairing a representative source excerpt with the target canonical extraction, consistently using the normalized code and null for absent PO number or tax.

A schema guarantees a valid envelope; it does not teach mapping heterogeneous language to one canonical field shape. Instructions can describe the target shape, but without examples the model applies the description narrowly to layouts that closely match the described cases. Examples demonstrate the mapping decision itself: how to locate a field that is unlabeled or split or embedded in free text, when to group Python and SQL as two entries versus one, and when informal phrasing signals absence versus an explicit measurement. Coverage of the tail is load-bearing because dominant-layout examples already account for most volume but not most inconsistency.

Boundary. When source heterogeneity is low - for example all reports arrive from one template - instructions alone may be sufficient. When variation is extreme across forty labs or nine invoice layouts, instructions without tail-spanning examples do not generalise. The opposite case where schema alone suffices is when shape is encoded as enum or object type - there, typing gives the envelope and examples give the content mapping.

Recurring specifics. Layouts inline-unit, column-header, footnote-range, split-panel, prose-style, scanned-remittance, tabular. Normalisations mg/dL canonical versus milligrams per deciliter verbatim, lowhigh versus one string, payment terms to same normalized code. Example counts four to six pairs drawn from the failing tail, not the two highest-volume formats. Instruction phrases coerce every tool result into that schema versus drawn from the two highest-volume layouts as distractor.

Wrong answers written against this rule

Proposal. Proposal: Define the extraction as tool_use with correct enum and object typing and force the tool.

Why it attracts. Schema typing should enforce shape.

Why it fails. Schema gives envelope, not reading policy - variant prose still maps inconsistently.

When it would be right. See boundary case.

Proposal. Proposal: Add four examples drawn from the two dominant layouts.

Why it attracts. Covers most traffic.

Why it fails. Examples cover what already works; tail remains erratic.

When it would be right. See boundary case.

Proposal. Proposal: Tighten schema with stricter per-field descriptions and required fields.

Why it attracts. Feels more prescriptive.

Why it fails. Stricter fields do not teach cross-document normalisation.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Change weight drift to billing frequency drift - same few-shot remedy. Mutation 2: Present tool_use typing as sufficient - typing alone leaves the mapping inconsistent. Mutation 3: Present per-lab template maintenance - few-shot spanning layouts avoids per-template upkeep.
R23

Fixing argument shape starts with input_schema type and description

When the model sends query: ["term1", "term2"] where type: string is required, or city: "Paris, France" where the contract requires city name only, or date as natural language last month where ISO is required, the first fix is the tool input_schema: set type: string plus a clear description naming the expected format and an example. Example: description: "A single search query string. Example: quantum computing applications in medicine. Must be a string, not an array." For city: The city parameter must be the city name only, without country.

Schema definitions are the directly consulted source of truth at tool-call construction. Prompt notes lower in context are often ignored in favor of the implicit structure suggested by name or prior examples. A correct type plus example locks the shape before inference; hooks or retry only correct after a failed call.

Boundary. When shape errors persist after schema correction, a PostToolUse hook that converts lists to strings is a fallback, but it is a patch on top of a fixable contract. Splitting suits invalid combinations, not single-field type errors.

Recurring specifics. Fields query, city, date, search_database. Fixes type: string with description plus example, versus temperature, system prompt rule, or PostToolUse conversion.

Wrong answers written against this rule

Proposal. Proposal: System prompt rule NEVER pass a list.

Why it attracts. Feels directive.

Why it fails. Lower salience than schema at construction.

When it would be right. See boundary case.

Proposal. Proposal: Lower temperature for more deterministic calls.

Why it attracts. Determinism seems related.

Why it fails. Does not communicate contract.

When it would be right. See boundary case.

Proposal. Proposal: Hook to convert list to string.

Why it attracts. Deterministic repair.

Why it fails. Prevents rather than repairs is cheaper.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: List versus string - add type: string plus description. Mutation 2: City with country - add description narrowing to name only. Mutation 3: Date natural language - add format: date plus example.
R24

The retry request must carry source plus failed output plus specific errors, clearly tagged

A useful retry bundles four tagged sections: original document, the rejected JSON or tool output, the validator's specific errors, and instructions to return only corrected structured data. Clearly separated sections give the model everything required to correct precisely without regenerating correct fields. Generic please retry or source-only or error-only payloads are insufficient.

Correction needs grounding and delta. The source grounds which values are available; the failed output shows what was emitted; the specific errors name the gap; tagged separation prevents the model from confusing error text for source text. Without all four, the model either regenerates from scratch and risks fresh drift or applies the correction to the wrong span.

Boundary. When the failure is absence and the source legitimately has no value, the retry is not useful even with perfect tagging - routing is correct. When the failure is a single-field type error, the four-part retry still applies but the payload can be minimal.

Recurring specifics. Tagged sections Original Document, Rejected JSON, Validator Errors, Instructions: return only corrected structured data. The anti-patterns are send the same prompt unchanged and send only validator errors to keep the request short and regenerate from scratch without referencing the prior attempt.

Wrong answers written against this rule

Proposal. Proposal: Same prompt unchanged for different sampling.

Why it attracts. Sampling variance may produce different form.

Why it fails. No signal about what was wrong.

When it would be right. See boundary case.

Proposal. Proposal: Error only without source or prior output.

Why it attracts. Keeps request short.

Why it fails. Model cannot locate the field to correct without source and prior output.

When it would be right. See boundary case.

Proposal. Proposal: Full conversation history versus tagged sections.

Why it attracts. History contains all.

Why it fails. Untagged history is harder to parse than explicit sections.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Short versus tagged payload - tagged wins. Mutation 2: One field error versus three mixed errors - same structure, just more error entries. Mutation 3: Unchanged retry versus tagged retry - tagged is correct.
R25

Consistency failures on heterogenous layouts are mapping failures, not absence failures

When the same field is present but represented differently per source - shipping_date present yet unreadable, required field illegible, optional reference missing, unsupported manifest type - the schema may already allow null, yet routing behavior misclassifies which cases go to human review. The fix is not to make every field required but to give the model explicit criteria and labeled examples distinguishing a missing optional field from an unreadable required field or an unsupported manifest type. confidence thresholds alone without criteria do not resolve the distinction.

Null permission does not teach discrimination. Two documents can both yield null for different reasons - one is legitimately optional and absent, the other is required but illegible due to a smudge. Without examples of each, the model applies one rule uniformly and either over-routes optional absences or under-routes illegibility. Concrete edge-case examples teach the boundary that the schema only permits.

Boundary. When legibility is uniformly high and optional absence is rare, a comment in the schema plus a general rule may be enough. When smudged scans reach 30 percent of volume, as in photographed receipts stapled into one scan, explicit criteria plus examples become load-bearing.

Recurring specifics. Routing contrast illegible required field goes to dispatch blocked versus optional reference missing is valid null. Fields destination code smudged, customer reference number optional missing, unsupported manifest type. Schema already allows null; examples close the gap missing optional versus unreadable required versus unsupported type.

Wrong answers written against this rule

Proposal. Proposal: Make every field required so nothing can be blank.

Why it attracts. Forces review on any null.

Why it fails. Flags the wrong class - optional absences should not block.

When it would be right. See boundary case.

Proposal. Proposal: Raise confidence threshold so more go to review.

Why it attracts. More scrutiny seems safer.

Why it fails. Indiscriminate; does not learn the class distinction.

When it would be right. See boundary case.

Proposal. Proposal: Add a comment that required fields matter more.

Why it attracts. Lightweight.

Why it fails. Comment does not teach discrimination.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Smudged versus clean scan - routing differs. Mutation 2: Optional null versus unreadable null - examples distinguish. Mutation 3: Confidence versus criteria - criteria plus examples win.
R26

A unified generic schema trades selection error for sparsity and fabrication risk

When ten document types each need a distinct schema - invoices, receipts, contracts, forms - consolidating into one generic tool with all possible fields as optional and many fields absent per document avoids routing but creates sparse records where most fields are null. The model is then asked to emit many nulls correctly. The alternative architectures are tool_choice: "any" with type-specific tools letting the model pick, or a two-stage classify-then-force pipeline where the first call classifies the type and the second forces the matching tool. Generic schemas reduce selection error but increase hallucination and sparsity.

Precision per type benefits from a schema tailored to that type - required versus optional is then correctly scoped to that type's reality. A generic schema must mark any field that is sometimes present as optional, so every document passes with many nulls, and the model has more opportunity to invent values for fields that are declared but not relevant to the current type. Classification plus targeted extraction separates concerns: classifier chooses the schema family, extractor applies the right required set.

Boundary. A generic schema with an unclear or other pass may be reasonable when ten types share most fields and differ only in a few. The boundary is type overlap - high overlap favors one schema, low overlap favors separate tools. The opposite case where separate tools over-proliferate is closely related types such as similar financial forms where one tool with optional fields is the lighter contract.

Recurring specifics. Ten schemas versus one unified schema. Phrasing sparse output (most fields empty for most documents) and hallucinated data on freeform single-schema. Classification options separate Claude call or lightweight classifier, small model for classification, powerful model for extraction. The duplicate prompt for unknown document type appears with both any and classify-then-force answers depending on scale.

Wrong answers written against this rule

Proposal. Proposal: Send all ten schemas on every call and let constrained decoding pick.

Why it attracts. Single pass.

Why it fails. Conflicting cues and token waste.

When it would be right. See boundary case.

Proposal. Proposal: Train a custom classifier outside Claude.

Why it attracts. Dedicated accuracy.

Why it fails. Overkill when Claude itself classifies effectively.

When it would be right. See boundary case.

Proposal. Proposal: Generic schema with optional fields covering all types.

Why it attracts. No routing.

Why it fails. Sparse and hallucination-prone.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Two tools - any is correct. Mutation 2: Ten tools - classify then force is correct. Mutation 3: Single generic tool - sparse failure.
R27

The null versus empty array versus omitted field choice carries downstream contract meaning

Array fields may signal absence as null, as empty array [], or as an omitted key. Downstream systems often treat null and [] differently - null as unexpected type that can crash parsers versus [] as idiomatic no items. The schema must pick one contract and document it. For review pros and cons, the examined forms are allow empty arrays as valid output with [], allow null for arrays, make optional meaning possibly absent, and allow null versus empty. The inspected outcome is that empty array is idiomatic for no items while null on an array field creates typed inconsistency; making fields entirely absent forces consumers to check existence.

Contracts propagate. A consumer written to iterate skills[] expects an array - null breaks that path. Empty array preserves iteration while signalling no items. Nullable plus absent forces a double branch. The choice therefore belongs to downstream expectations, not to writing convenience. The same applies to pros and cons arrays and skills[] and line_items[] where branching cost differs.

Boundary. When the downstream distinguishes not yet extracted from extracted as empty, null may be the intended absent marker and [] the empty marker - then ["array", "null"] is the correct typed design. When the downstream expects always-present arrays, [] is the correct fallback under constrained decoding plus post-processing default.

Recurring specifics. Forms type: ["array", "null"], items: {type: "string"}, required versus not in required, [] versus null debate, Allow empty arrays for pros/cons as valid output versus Allow null values. Example skills: string[] with compound phrase handling.

Wrong answers written against this rule

Proposal. Proposal: Always allow null for arrays.

Why it attracts. Uniform null handling.

Why it fails. Null array breaks iteration consumers.

When it would be right. See boundary case.

Proposal. Proposal: Make array fields optional and omit when empty.

Why it attracts. Fewer keys.

Why it fails. Consumers must check existence not just emptiness.

When it would be right. See boundary case.

Proposal. Proposal: Expand enum with neutral plus unclear for sentiment while changing array nullability.

Why it attracts. Covers more cases.

Why it fails. Over-engineers beyond the identified issues.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: pros required to optional with [] - [] is preferred over null. Mutation 2: Make skills optional versus nullable - nullable preserves key presence. Mutation 3: Generic classifier versus other versus null array - array handling is orthogonal to classifier design.
R28

PostToolUse hooks give deterministic normalisation and outcome typing before the model reasons

A PostToolUse hook on a tool such as pricing, geocoding, or each of three MCP tools intercepts the raw result before it is appended to the conversation. The hook deterministically rewrites every payload into one canonical schema - uniform field names, ISO 8601 timestamps, single currency - and attaches a typed outcome such as ok or transient_error or empty so the model treats a transient failure as retryable rather than empty. The agent then updates its record only from normalized ok results.

Probabilistic guidance degrades under varied conditions - long context, unusual value combos, high ambiguity - which is why prompt-only normalisation dropped errors from 18 percent to 11 percent but did not reach zero. A hook removes ambiguity before the model sees it, so misreading Unix timestamps as order numbers or confusing status codes with amounts cannot happen because the model never sees the raw heterogeneous values. Attaching outcome typing also prevents writing stale state as empty - the agent previously recorded no active run on a transient failure that should have been retried.

Boundary. Hooks are appropriate when normalisation must run on every tool call without modifying the tool or the system prompt, and when the tool wraps an external or legacy system whose contract cannot be changed. When the tool is owned and can be updated to return a standard format at origin, fixing the tool itself is the simpler deterministic path. The boundary where prompt is enough is low-stakes, low-heterogeneity tasks where 11 percent residual error is tolerable.

Recurring specifics. Hook names PostToolUse, PreToolUse. Rewrites Unix epoch to ISO 8601, 1 or 0 status code to success or failure label, three currencies to one currency, inconsistent St.StreetST to canonical. Outcome values ok, transient_error, empty. Reported residual after prompt fix 11%, previous 18%, no active run / queue empty stale write.

Wrong answers written against this rule

Proposal. Proposal: Add detailed format definitions or few-shot examples to the system prompt.

Why it attracts. Covers each tool's format.

Why it fails. Still probabilistic; edge cases remain.

When it would be right. See boundary case.

Proposal. Proposal: Update the pricing tool to return a standard currency directly.

Why it attracts. Eliminates inconsistency at origin.

Why it fails. Assumes contract ownership that may not be available for legacy systems.

When it would be right. See boundary case.

Proposal. Proposal: Set tool_choice to force a tool to create a normalisation opportunity.

Why it attracts. Forces a call.

Why it fails. tool_choice governs whether a tool runs, not transformation of its result.

When it would be right. See boundary case.

How the same rule gets re-asked
  • Mutation 1: Prompt instruction versus hook for same normalisation - hook is deterministic. Mutation 2: Pre-call probe that writes a per-tool guide versus hook - guide is still model-interpreted; hook is deterministic. Mutation 3: Hook that normalises without outcome typing versus with typed outcome - typed wins because it prevents empty-on-failure.
Invoice extraction from markdown-wrapped failures to schema-redundant success
A production walkthrough with the reasoning chain made explicit.

A document processing team extracts invoice_number, vendor_name, line_items, and stated_total from invoices. The first implementation uses prompt-based JSON: Extract the fields and return JSON with invoice_number, vendor_name, line_items, and stated_total. In production the model sometimes wraps output in markdown fences, sometimes forgets vendor_name, and sometimes includes a preamble before the object. The downstream parser handles some cases but breaks on others, and the breakage rate is too high for production.

The fix moves to structured outputs via output_config.format with a JSON schema. The schema declares invoice_number and vendor_name as required strings, purchase_order and payment_terms as nullable strings, line_items as an array of objects with required description and amount, and stated_total as a required number, each property carrying a description. The markdown wrapping problem disappears because the API enforces the schema at generation time, and the missing field problem disappears because required fields are part of the schema. This is the hierarchy in action.

A second pass addresses semantic errors that remain inside valid JSON. The schema is extended to extract both calculated_total, the model sum of line item amounts, and stated_total, the total read verbatim from the document, plus total_discrepancy true when they differ. A Pydantic validator compares the two and routes discrepancies to human review, the schema redundancy pattern for math consistency. Fabrication is addressed in the same pass by making purchase_order nullable with string or null so that when no purchase order exists the model returns null rather than an invented PO number.

The choice of tool_choice completes the design. When document type is unknown the pipeline uses any so the model selects the right extraction tool, and when metadata extraction must precede enrichment it uses forced selection with type tool and name extract_metadata so that step is guaranteed regardless of model preference.

Distinctions that decide answers

ThisNot thisHow to tell them apart
Structured outputs with output_config.formattool_use with JSON schemaBoth eliminate syntax errors via constrained decoding. Structured outputs is for structured responses as the deliverable, tool_use is for structured responses that should trigger tool execution downstream.
Prompt-based JSONAPI-level structured outputsPrompt-based JSON is probabilistic and may produce malformed output. API-level structured outputs use constrained decoding to enforce the schema at generation time.
tool_choice autotool_choice anyauto lets the model respond with text instead of calling a tool, any requires a tool call but lets the model choose which. For guaranteed structured output with unknown document type use any.
tool_choice anyForced tool selectionany lets the model pick which tool, forced with type tool and name requires exactly the named tool. Use forced when a step must run before others such as mandatory metadata extraction.
Optional or nullable fieldsRequired fieldsRequired pressures the model to fabricate when information is absent, nullable allows honest null. Use nullable when the source may lack the field.
Schema validation for structureSemantic validation for correctnessSchema catches type errors and missing required fields, semantic validation catches arithmetic errors, misplaced values, and fabricated content. Both are required.
strict true on a toolNon-strict tool useStrict applies constrained decoding guaranteeing schema conformance, non-strict relies on best-effort compliance. Strict requires additionalProperties false and correct optional handling.

Traps

Tool schemas prevent all extraction errors

The tempting answer. Treat tool_use with JSON schemas as a complete guarantee against any extraction mistake.

Why it fails. Structural enforcement feels comprehensive but only covers syntax. Semantic errors such as wrong sums, values in wrong fields, or fabricated values for absent data remain inside valid JSON.

What is correct. Add semantic validation and retry with error feedback for correctness, the schema guarantees structure, validation guarantees correctness.

Auto and any are interchangeable

The tempting answer. Use auto when you need guaranteed structured output because both involve tools.

Why it fails. auto allows the model to return text instead of calling any tool, so structured output is not guaranteed. Any guarantees a tool call.

What is correct. Use any for guaranteed structured output with unknown document type, use forced selection when a specific tool must run.

Make every schema field required for completeness

The tempting answer. Mark every field required to ensure nothing is missed.

Why it fails. Required fields pressure the model to invent plausible values when the source lacks information, which is worse than an honest null.

What is correct. Make fields where the source may lack information nullable or optional so the model can return null honestly.

Omit additionalProperties false with strict mode

The tempting answer. Enable strict true without additionalProperties false because it looks like a minor detail.

Why it fails. Strict requires additionalProperties false to explicitly reject extra fields, without it the schema cannot be enforced through constrained decoding.

What is correct. Always set additionalProperties false and include all optional properties in properties even when not required when using strict.

Stay on prompt-based JSON for simplicity

The tempting answer. Keep prompt-based JSON because adding a JSON schema feels like overhead.

Why it fails. Prompt-based JSON periodically produces malformed output and downstream parsers break on that failure class.

What is correct. Use structured outputs or strict tool_use at the API level, which cost nothing extra and eliminate the syntax failure class.

Skip property descriptions as documentation

The tempting answer. Omit description on schema properties because the field name seems self-explanatory.

Why it fails. Descriptions guide the model's interpretation of ambiguous names, a field named tier without description could be a string, integer, or label, with description it is unambiguous.

What is correct. Add a description to every property, especially any name that admits multiple interpretations.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
The schema as the tool's instruction manual

The schema says what parameters exist, what types they accept, which are required, and what valid values are. A precise schema reduces errors and the need for downstream validation and helps Claude construct correct tool calls even on ambiguous inputs.

Schema Definition
Resilient catch-all with other plus detail string

An enum of invoice, receipt, and contract fails on an unseen type. Adding other to the enum and a category_detail string captures the long tail without breaking validation, which is the resilient schema pattern.

Validation Strategies
Evolution history as arrays of value plus source plus effective date

Storing amended fields as arrays of objects with value, source, and effective_date captures both original and amended values for contracts with amendments, invoices with revisions, and policies with updates.

Schema Definition
Build it
Guarantee structure and prove nullable honesty on absent data
  1. Define an extraction tool with a JSON schema containing three required fields, three optional nullable fields, an enum with unclear and other, and a freeform detail string for other, each property with a description.
  2. Send a request with tool_choice auto and observe at least one response where the model returns text instead of a tool call, documenting why auto is unsuitable for guaranteed structured output.
  3. Switch to tool_choice any and verify every response has stop_reason tool_use with a valid tool call conforming to the schema.
  4. Force a specific tool with type tool and name extract_metadata and verify the mandatory extraction step runs on every request even when document content suggests a different tool.
  5. Process five documents, three with complete data and two with missing fields, and verify nullable fields return null rather than fabricated values on the two sparse documents.

Verify. Structure is guaranteed by schema and choice, absent values surface as null instead of inventions, and the long tail is captured through other plus detail without validation failure.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Task 4.416 min

Validation and Retry Loops

Add semantic validation and retry with document plus failed output plus specific error so fixable mistakes self-correct and unfixable ones route to human review.

What you need to know

Production extraction systems fail for reasons no schema can prevent. Documents arrive in unexpected formats, line items do not sum to the stated total, values end up in the wrong fields, and plausible looking values appear for data that was never in the source. The question is not whether failures happen but how the system responds, and the validation retry pattern turns those failures into self-correcting workflows.

The correct retry pattern sends three pieces of information back to the model: the original document so the model has the source to re-examine, the failed extraction so it can see what it produced, and the specific validation error so it knows exactly what went wrong. This beats naive retry by a wide margin, because without the specific error the model has no guidance for what to fix and usually reproduces the same mistake, while with it the model can target its correction such as re-examining the document for missed line items, checking field placement, or recalculating a total.

Retries have a clear effectiveness boundary and the exam tests this boundary most aggressively in this task. Retries are effective for format mismatches such as wrong date format or inconsistent currency notation, structural output errors such as values in wrong fields or incorrect nesting, misplaced values where data exists but was extracted into the wrong field, and mathematical errors such as a missed line item affecting a total. Retries are not effective for information genuinely absent from the source document, data that exists only in an external document not provided to the model, or fields requiring knowledge the model does not have. If a document genuinely does not contain a department name, no amount of retrying will produce a correct value; the correct action is to flag for human review or return null when the schema allows it.

The exam distinguishes two error categories that must be handled separately. Schema syntax errors are malformed JSON, missing required fields, and wrong data types, and are eliminated entirely by tool_use with JSON schemas from the previous task. Semantic validation errors are correct JSON structure but incorrect values: line items that do not sum, dates with invalid ordering, values in the wrong fields. Schema validation cannot catch semantic errors because the JSON is structurally valid, only business-rule validation can. The overlap between the two tasks is intentional and the exam tests whether you understand what tool_use solves and what it does not.

Pydantic is the canonical Python validation layer alongside JSON Schema. A Pydantic model does two jobs at once: parsing enforces structure such as types, required fields, and enums, while validators enforce semantics such as cross-field arithmetic or date ordering. Both failure kinds surface through one ValidationError with machine-readable errors naming the field and the broken rule, and the except branch of that validation is the retry-with-error-feedback pattern where Pydantic supplies the specific error in a form that can be formatted straight into the retry prompt. Self-correction schema design reinforces this by building error detection into the extraction shape itself, for example calculated_total versus stated_total with total_discrepancy, conflict_detected for contradictory source text, or detected_pattern per finding for systematic prompt improvement.

The three-part retry message and the fixable boundary

A retry message that contains only the original prompt gives the model the same ambiguous input and produces the same mistake. A retry message that contains the original document, the failed JSON, and the specific validation error in the form what was expected versus what was found gives the model a target, such as Validation errors line_items sum to 450.00 but stated_total is 500.00, please re-extract ensuring all line items are captured.

The boundary decision precedes the retry. Format mismatches, structural errors, misplaced values, and arithmetic errors are fixable and should be retried. Absent information, external data not in source, and knowledge the model lacks are not fixable and should route to human review. The exam presents both shapes and expects the boundary to be applied before choosing retry, not as an afterthought.

Pydantic parsing versus validation and the SDK integration

Parsing enforces structure at parse time through model_validate_json and related entry points, while validation runs after parsing via model_validator with mode after and can access all fields to enforce cross-field rules. Raising ValueError inside the validator adds to the ValidationError returned by model_validate, with loc for the path to the failing field, msg for the error message, and type for the category, all directly formattable into the retry prompt without manual translation.

The Python SDK integrates both layers with client.messages.parse and output_format set to a Pydantic class, returning validated instances through parsed_output. The SDK enforces the schema, the model validator enforces business rules, and the retry loop consumes whichever layer fails. The maximum retry count is typically three, beyond which the cost of additional attempts exceeds value and the input should route to human review.

External validation logic recomputes checks after extraction, while self-correction fields in the schema surface discrepancies without external logic. The two are complementary: external validators are the enforcement, self-correction fields are the surface that makes discrepancy visible inline.

Schema redundancy and dismissal-driven improvement

Schema redundancy builds error detection into the extraction output. calculated_total is populated by summing line_items amount, stated_total is read verbatim from the document, total_discrepancy is true when they differ, conflict_detected flags contradictory text in the source such as payment due 30 days in one section and net 60 in another, and detected_pattern on each finding names the specific construct that triggered the finding.

The detected_pattern field creates a systematic improvement loop. When developers dismiss findings triggered by a specific pattern, that pattern likely needs prompt refinement, not a generic quality complaint. Tracking frequency and dismissal rate per pattern prioritises which prompts to refine next, closing the loop from extract to validate to collect dismissal data to refine prompts to repeat.

Authoritative mechanism reference

The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.

Mechanism reference: M1. The retry-with-error-feedback payload

A recovery request after a failed validation is not a fresh copy of the original prompt. It is a follow-up turn that bundles three distinct pieces of context: the source document so the model can re-read the tested material, the exact JSON it produced on the failed attempt so it can see its own mistake, and the precise validation error string naming what went wrong. The model then re-extracts, ideally correcting only the broken field rather than regenerating everything. The lesson validation-strategies calls this the largest single factor in retry success and contrasts specific feedback ("the delivery_date field 2042-13-01 is not a valid date; month must be between 01 and 12") with generic feedback ("date is invalid")

The three ingredients appear consistently as the original document, the failed extraction, and a "Validation error:" line. Effective error strings name the field (for example policy_number), the wrong value (ABC-123), the required format (10 alphanumeric characters without hyphens), and the violated constraint (must be a date in the past). The instruction almost always ends with "re-examine the source document and correct only these fields" so the model does not discard correct ones. The lesson validation-pipelines implements this by pushing the assistant's prior output and then a user turn containing the error list and a request to return corrected JSON

The mechanism is grounded in how the model behaves as a function of its context. If the context is unchanged, it tends to reproduce the same error. Supplying the specific error converts an opaque failure into an actionable instruction: the model now knows which field was wrong, what it produced, what the constraint was, and that it should re-examine the source. The boundary is the presence of a specific error signal. A validator that returns only {"error": true} with no field name or message makes the retry effectively naive, and it will reproduce the mistake.

The reason a naive retry fails is that the model is, in practice, a stable function of its context window. When the follow-up turn is byte-for-byte identical to the original (same document, same instruction, no error), the sampler has no new information to diverge on, so it tends to land on the same mis-extraction it produced before. This is not a matter of luck improving with more samples; it is a matter of the error signal being the only thing that changes the optimization target. The three-part payload works precisely because it changes the target: the model sees its own prior output (so it cannot silently repeat it), the source (so it can re-read the tested material), and the named rule (so it knows which constraint it broke). Without all three, you are asking the model to guess why it was wrong, and guessing is exactly what produced the first failure.

Mechanism reference: M2. The retry effectiveness boundary

The retry-with-error-feedback loop is effective for a defined set of failure classes: values in the wrong type or format (a date rendered as 03/04/2025 when ISO is required), values placed in the wrong structural location (a reading dropped under the wrong object), values swapped between two valid fields (net and gross amounts reversed), and arithmetic omissions (a line item missed so the sum is short). In each case the information exists in the source; the model simply did not place or compute it correctly. The specific error tells the model exactly where to look, and it can recover.

The boundary is the presence of the value somewhere in the source. The nearby opposite case is a field whose value is simply not in the document at all; there, the same retry produces the same failure because there is nothing to re-read. A misplaced meter_reading that was found correctly but mapped to billing_address is fixable; a meter_reading that never appears on the bill is not. The lesson validation-strategies frames this as the distinction between a validation error (the value is there but wrong) and a futile retry (the underlying condition will not change)

The class of non-recoverable failures is: a field genuinely absent from the source text, a value that lives only in an external document not supplied to the model (for example a reference ID mentioned only in the covering email, not in the attached bill), and a field that requires knowledge the model does not have. A retry re-prompts over the same document. It cannot introduce facts the document does not contain. Continued retrying is pure waste and, worse, tempts the model to fabricate a plausible value to satisfy a required field. The correct action is to recognize absence and either route the document to human review or emit null where the schema permits it. The lesson error-handling makes the same point from the tool side: a missing resource is an error outcome that is not fixed by retrying the same call

The practical cost of ignoring this boundary is twofold. First, each retry on an absent field spends a full model round trip and the latency that goes with it, returning either the same null or a freshly fabricated value that then has to be detected and discarded downstream. Second, and more damaging, a required schema under an absent source invites fabrication: the model, unable to satisfy the required field by returning nothing, constructs a plausible value that passes schema validation and may even pass a careless semantic check, silently injecting wrong data into the pipeline. The retry loop therefore does not merely fail to help; it actively harms the system unless the absence case is detected and routed out before any retry is attempted. This is why the classification gate (M8) and the nullable schema with its null-when-absent instruction (M7) are not optional extras but the same idea viewed from two sides: one stops the loop, the other gives the model a lawful way to say "not here".

Mechanism reference: M3. Schema syntax versus semantic validation

When extraction runs through a tool whose input_schema is a JSON schema, or through the structured-outputs API with output_config.format, the platform enforces structure at generation time: required fields are present, types match, enums are respected, and malformed JSON is rejected before it reaches downstream code. This erases the entire class of schema syntax errors. It does not, however, prevent semantic errors: a value that is the wrong number, the wrong value in a valid field, a sum that does not reconcile, or a date that is formatted correctly but logically wrong. Those require a validation layer outside the schema.

A JSON schema describes the shape of data, not its meaning. It can say "endDate is a string and matches the date format" but it cannot say "endDate must come after startDate" because comparing two property values is beyond static schema validation. The lesson schema-definition states that a schema "removes interpretation, not because it adds rules," and that if/then/else conditional schemas are advisory for the model and must be validated post-generation The lesson validation-strategies states plainly that the API guarantees the shape while the developer guarantees the meaning, and that semantic validation happens after receiving the structured output The Anthropic structured-outputs documentation confirms that structured outputs constrain the JSON shape and that schema compliance is not absolute: a refusal returns stop_reason: "refusal" with a 200 status and billed tokens, and the refusal text takes precedence over the schema; hitting the token limit also truncates before the schema can be satisfied

Two consequences follow for the retry design. First, a refusal is not a validation failure and must not be fed back as a correction request; the model has declined to produce the content, and re-prompting the same instruction usually reproduces the refusal. The correct handling is to surface the refusal to the caller and, where the workflow allows, to reformulate the request or escalate, never to append a "validation error" line that the model cannot act on. Second, a truncated output that fails schema validation may be a token-limit problem rather than a content problem; increasing max_tokens or continuing the conversation is the right fix there, whereas a genuine semantic mismatch needs the validation-retry loop. The lesson error-handling makes the same split at the API level: a 400 that is a context_length_exceeded requires reducing input size, never retrying with the same oversized prompt The three structured-output mechanisms (prompt-requested JSON, output_config.format, and strict tool use) are independent and composable, but none of them changes the fact that meaning is the developer's responsibility

Mechanism reference: M4. Self-correction schema fields

Rather than relying on the model to assert that totals are consistent, the extraction schema is enriched so the model returns both the total it computes by summing the line items (calculated_total) and the total printed on the document (stated_total). A deterministic post-extraction check compares the two. When they differ beyond a small tolerance, the record is flagged and routed for human review rather than auto-posted. The reference page also lists conflict_detected booleans (when a source document contradicts itself, both values are returned and the boolean is set rather than silently choosing one) and detected_pattern string fields (for analysis pipelines, naming the specific construct that triggered each finding).

The lesson validation-pipelines demonstrates the computed-total idea directly: the semantic stage derives computedTotal from the line items and compares it to totalAmount with a one-cent tolerance, pushing a specific error when they diverge The lesson validation-strategies teaches cross-field consistency as a core validation pattern ("total equals sum of parts") and warns that a response can be schema-valid and semantically wrong The crucial refinement is that the comparison must be deterministic and external to the model. A schema field that asks the model to set totals_match: true during the same pass that may have produced the arithmetic error is self-assertion, not validation; the same pass that miscomputed the total can also mis-set the flag. The robust design returns both calculated_total and stated_total and lets application code decide the discrepancy (see M6).

Mechanism reference: M5. The typed validation layer

In a Python pipeline, a Pydantic model acts as the validation layer. Parsing enforces structure (types, required fields, enums); validators enforce semantics (cross-field arithmetic, date ordering). Both failure kinds surface through a single ValidationError whose machine-readable entries name the field location and the broken rule. That error string is formatted straight into the retry prompt as the third ingredient of retry-with-error-feedback. The lesson validation-strategies lists Pydantic among the schema validation libraries and shows that validation errors should name the field and the rule The lesson validation-pipelines shows Zod performing the equivalent job in TypeScript, with safeParse producing issues that map a path to a message

The retry loop needs a specific, actionable error. A typed validator produces exactly that: a field path and a message stating what was expected versus what was found. This is the bridge between "validation failed" and "here is what to fix." The platform may enforce the schema; the validator enforces the business rules and formats the error the retry consumes. The boundary is structured error versus generic failure. A validator that raises a bare exception with no field path leaves the retry with no specific signal, and it behaves like a naive retry.

Mechanism reference: M6. Deterministic external comparison is the authority

The authoritative arithmetic comparison happens in code, not in a model-set boolean. The extraction returns both calculated_total (the sum the code recomputes from the line items the model returned) and stated_total (the figure the model read from the document). Application code recomputes the sum independently and compares. If abs(calculated - stated) > tolerance, it sets the discrepancy flag. The model's own opinion about whether things reconcile is never trusted, because the pass that may have erred is the same pass being asked to judge itself.

This is the same principle behind conflict_detected: when two genuine source values disagree, application code sets the flag after observing both values; the model does not get to "resolve" the contradiction into one value, because doing so destroys audit evidence. The lesson validation-strategies teaches cross-field and external verification as checks the developer owns, not the model The lesson validation-pipelines demonstrates the computed total being derived in code

Mechanism reference: M7. Nullable schema plus explicit null-when-absent instruction

Required fields force fabrication when the source omits the value. A schema that marks a field required and the source document does not contain the value forces the model to populate the field to produce a schema-compliant response; with no compliant way to omit it, the model invents a plausible value. Making the field optional or nullable gives the model a legitimate way to return null, so it reports absence honestly instead of fabricating.

A nullable schema is necessary but not sufficient. The model may still emit a plausible value for a field the source never mentions, because generating a value is its default behavior. The complete fix pairs the nullable schema with an explicit instruction: "return null if the information is not directly stated in the source." The schema defines what is allowed (null is legal); the instruction defines when to use it (use null when absent). With only one, fabrication can persist: a required field forces it, and a nullable field without guidance still defaults to filling. The lesson schema-definition teaches that optional-but-always-needed fields confuse the model about what it can omit, and that the most specific constraint possible is best, but it does not itself resolve the absence case; the null-when-absent instruction is the companion rule

The decision rule is: is this field present in every source document? If yes, keep it required. If no, make it optional or nullable and instruct the model to return null for any field not explicitly stated. For a field present in most but not all documents (for example a grant number on theoretical papers), required causes fabrication on the minority, so nullable is correct. The same rule applies inside nested objects and arrays: a line-item property that is sometimes absent should be marked optional in the item schema, and the surrounding instruction should tell the model to omit the whole item or null the property rather than invent a value to satisfy the array shape. A nullable schema that is only top-level but required-within-items still manufactures values at the item level, so the nullability must follow the field to wherever it actually lives in the structure.

Mechanism reference: M8. Classification gate before retry

Before spending a retry, the pipeline classifies the failure as a retryable format or structural error (split cells, merged fields, misreads that clear on a later attempt) or a genuine source-absence case (a field the document never contains). Retryable cases are re-submitted with the specific error appended. Source-absent cases are routed to human review or emitted as null without further retries. This prevents burning attempts on documents that will never succeed.

The split is described in the tool-error literature as layout_error or format_error versus absent_field or source_absent, with categories such as transient, validation, and permission. The lesson error-handling provides the full taxonomy: transient is retryable, permanent is not, auth and not-found and validation are not retryable, and rate-limit is retryable after delay The lesson validation-pipelines shows the pipeline failing fast at each stage so that data failing basic structural checks never consumes semantic retries The action "retry only the layout error, route the absent field to human with the field marked null" is the canonical outcome of this gate.

Mechanism reference: M9. Bounded retry with graceful fallback

The retry loop is bounded: a maximum number of attempts (commonly two or three) with the specific error appended on each. Once the cap is reached without a valid result, the document is escalated to human review or returned as a structured error (or a null where the schema permits), rather than retried forever or returned invalid to the user. The lesson validation-strategies sets the ceiling at three retries and lists the graceful fallbacks: default value, human escalation, or error return with enough context The lesson validation-pipelines caps retries at two to three and says to route irrecoverable failures to human review queues rather than silently returning invalid data

Unbounded retries waste cost and latency on unrecoverable cases and can block the pipeline. A cap with graceful degradation preserves system integrity: the automatable cases self-correct, and the residual hard cases reach a human or a clean error state instead of poisoning downstream systems. The lesson validation-strategies frames this as "futile retry detection": when the same input produces the same error repeatedly, or the error category is validation or permission, escalation is correct and continued retrying is not.

Mechanism reference: M10. Fail fast on source-side defects

When the root cause is in the source document itself (a genuine arithmetic error introduced by the issuer, a contradiction in the text, or information that lives only in an external document), the retry loop should stop early and escalate. Continuing to retry cannot change a source defect and only consumes resources. The lesson error-handling makes the same distinction: a missing resource or a broken external dependency is an error outcome requiring escalation, not another pass over the same input The lesson validation-strategies teaches that systematic failures in one document category indicate a prompt or schema issue, not random noise, which is the mirror image: if the failure is concentrated and fixable-by-design, change the prompt rather than retry the same call

The boundary is model error versus source defect. A model reasoning mistake on a perfectly good document is exactly what retry-with-error-feedback corrects. A mismatch that comes from an inconsistency in the source is what the loop must stop retrying. The canonical fail-fast case is an author list given only as "et al." pointing to an external paper, or a genuine bank-statement arithmetic error. Recognition of the defect type triggers immediate escalation.

Mechanism reference: M11. Structured tool-result errors

When a validation or tool step fails, it should not return a uniform "validation failed" string. It should return a structured error response carrying an errorCategory (for example transient, validation, permission), an isRetryable boolean, a human-readable description of the specific failure, and the affected field names. This lets the caller make a deterministic decision: retry with backoff, tell the user the input is invalid, or escalate to a human. The lesson error-handling defines exactly this ToolErrorResponse shape with isError, errorCategory, isRetryable, message, and context, and shows transient versus permanent versus auth examples

A bare error message gives the caller no basis for action. One uniform string forces the caller to guess, often retrying the unretryable or escalating the retryable. Structured metadata turns error handling into a deterministic decision tree driven by data.

A common misuse is to set isRetryable: true on every structured error "just in case," on the theory that retrying never hurts. It hurts in two ways. First, a validation or permission error will not change on retry because the request itself is the problem; retrying it burns a model round trip and delays the moment the caller learns the input must be fixed. Second, marking a genuine absence as retryable defeats the classification gate (M8) and pushes an absent field back into the loop, where it converges on a fabrication. The isRetryable flag should be derived from the error category, not from a default: transient and rate-limit are retryable, validation, permanent, auth, and not-found are not. The lesson error-handling warns against marking all errors retryable and against generic error messages that give the model nothing to act on The antipattern "Operation failed" as the entire error body is the canonical wrong answer. The principle the lesson states is to signal failures explicitly and with structured detail, not as ordinary-looking results

Mechanism reference: M12. The MCP isError flag

In the Model Context Protocol, a tool result carries an isError boolean. When true, it tells the model that the tool execution failed during this call and the model should treat the content as an error case and decide next steps. The flag must be paired with structured, actionable content; a bare isError: true with "Operation failed" gives the model nothing to act on. The lesson error-handling shows the tool_result message shape with is_error: true and a structured content object, and warns that throwing exceptions from tools bypasses the model's error reasoning

isError distinguishes a business-logic or runtime failure from a successful result. It is not a schema-validation error (those are caught before the tool runs) nor an API connection failure (those are HTTP-level). Its job is to let the model self-correct or escalate based on the error context. Without detail, the model cannot correct. The lesson tool-error-handling covers the same surface from the tool-design side, including distinguishing access failures from empty results The Anthropic MCP documentation describes tool results and the error signaling shape

Mechanism reference: M13. Transport-level API errors

Distinct from content validation, transport errors from the Anthropic API require their own handling. The lesson error-handling gives the full HTTP error reference: 400 (invalid_request_error, not retryable, fix the request), 401 (authentication_error, not retryable, check the key), 402 (billing_error, not retryable), 403 (permission_error, not retryable), 404 (not_found_error, not retryable), 413 (request_too_large, not retryable, reduce size), 429 (rate_limit_error, retryable with backoff, respect Retry-After), 500 (api_error, retryable with backoff), 504 (timeout_error, retryable with backoff) The Anthropic errors documentation confirms these classes and the Retry-After posture for 429

The retry posture is error-specific. A 429 carries a Retry-After header that specifies exactly how long to wait; the client should wait that duration, then retry. A 5xx server error is transient and should be retried with exponential backoff and jitter. A 4xx client error (400, 401, 403) is permanent and must not be retried; it requires fixing the request or escalating. The lesson error-handling warns explicitly against a generic catch-all retry for all error types and against retrying a 400 context_length_exceeded with the same oversized input Exponential backoff grows the delay (for example 1s, 2s, 4s, 8s), a random jitter avoids synchronized retries across many clients, and a maximum attempt count bounds the effort. These are orthogonal to content-retry logic: they govern transport, not validation.

The idempotent correlation discussed in M17 also applies at the transport layer. Each outgoing request should carry the same correlationId that the logical item already owns, and each captured response (including the 429 or 5xx that triggered a retry) should record that id. This keeps the transport retry attributable to the exact item rather than the batch as a whole, and it is what lets an operator see "item X hit rate limit, waited, succeeded" as one story instead of two disconnected events.

Mechanism reference: M14. Cross-field and temporal semantic checks

Beyond schema shape, a pipeline needs semantic or business-rule validation: a date that parses but is impossible (February 30), a value outside an allowed range or wrong sign, two dates whose ordering is violated (end_date before start_date), or a temporal inconsistency between an extracted date and contextual clues. These require application or validator logic, not JSON Schema. The lesson validation-pipelines demonstrates shippingDate required for shipped status, computed total reconciliation, and cancelled orders not having future shipping dates as semantic and business rules The lesson validation-strategies lists range checks, format checks, cross-field consistency, and external verification as the core validation patterns

JSON Schema validates static structure. It cannot know that 2025-02-30 is impossible, that a negative amount is invalid, or that endDate must exceed startDate, because those are relationships and real-world facts, not types. The boundary is format validity versus value validity. A date that parses as YYYY-MM-DD passes schema validation yet is impossible; the semantic layer must add a validity check beyond the pattern.

Temporal consistency is the subtler half of this check. An extracted incident_date of 2024-03-10 may be a perfectly valid calendar date, yet contradict a clause elsewhere in the same document that says the incident occurred "last Tuesday" relative to a reference date of 2024-03-20, making the extracted date a future date from the document's own frame of reference. No JSON Schema can express "this date must be in the past relative to that other date," because it requires reading two fields and reasoning about the document's internal timeline. That reasoning is the semantic layer's job, and when it fails the correct response is to flag the record for human review, never to silently shift the date to a plausible value. The lesson validation-strategies lists cross-field consistency and external verification among the checks the developer owns, precisely because they sit above what any schema can express

Mechanism reference: M15. Partial re-prompt, few-shot, and confidence limits

After validation identifies exactly which fields failed, the recovery request asks for only those fields, then merges the corrected values with the already-correct fields. This avoids re-extracting and risking the fields that were right. The contrast is "full re-extraction wastes computation on fields already correctly extracted." Where the failure is present-but-varied format or ambiguous layout (information is in the source but split, embedded, or unlabeled), few-shot examples that pair a source with its exact expected output teach the model to locate the field; retries cannot help there because the prompt already failed to guide extraction and resending it reproduces the same empty result. The lesson validation-strategies and validation-pipelines teach that the issue is guidance, and examples are the lever, while the prompt-engineering documentation covers few-shot and multishot techniques

Confidence scores do not catch fabricated or silent-failure outputs and cannot flag what the model had no signal to suspect. A self-reported confidence is the model judging its own certainty; fabricated values are produced with high confidence, and an absent continuation page is invisible to the model, so its confidence may be high while the extraction is incomplete. Confidence is a useful routing signal for known-uncertain cases, not a correctness guarantee. The lesson confidence-scoring covers field-level calibrated confidence as a reviewer-allocation signal for semantic errors that pass schema, and the lesson validation-strategies covers graduated confidence-based validation

Mechanism reference: M16. Failure-class tagging and observability

The validation error payload can carry a detected_failure_type (or detected_pattern) classifying each failure as MALFORMED_JSON, MISSING_FIELD, WRONG_TYPE, and so on. Logs can then route and aggregate by class, and the pipeline can distinguish fixable from absent failures for routing, all without altering the extraction prompt or model. A generic "validation failed" message makes a malformed-JSON error indistinguishable from a genuinely absent field, so the two cannot be routed differently and cannot be analyzed. Tagging makes each failure mode inspectable and routable. The lesson validation-pipelines achieves the same inspectability through named business rules with name, description, severity, and a check function that returns a message or null

Mechanism reference: M17. Idempotent correlation and traceability

A retried item must remain traceable to its original request so that logs, human-review queues, and downstream consumers can join every attempt under one identity. This is idempotent correlation: the same logical work, attempted several times, carries one stable correlation identifier from the first attempt through every retry and into the escalation record. Without it, a document retried twice appears as three unrelated rows, and a reviewer cannot tell that the escalated case is the same invoice that already failed twice with the same error. The grounding must cover this because the deliverable requires that a retried item be traceable to its original, and because correlation is what makes the classification gate observable across attempts.

The correlation id is assigned by the caller before the first attempt and threaded through every message, every validation result, and every structured error. In the batch API the analogous field is custom_id, which must match ^[a-zA-Z0-9_-]{1,64}$ and is returned unchanged on each request result so the caller can map outcomes back to inputs For live extraction the same idea is a correlationId or requestId propagated in the tool context and in the structured error's context map. The lesson error-handling shows a context field on the tool error carrying structured detail such as host and query; the correlation id belongs there alongside the affected fields

Idempotent correlation also protects against double-posting when a retry races a slow first attempt, and it is what lets the pipeline record "attempt 2 of 3, same error class, same field" which is the signal that distinguishes a futile retry from a transient one. The lesson validation-strategies teaches that repeated identical failures indicate a condition that will not change and should escalate; a stable correlation id is what makes that pattern observable across attempts instead of looking like three separate one-off events The transport retry wrapper (Example 5) should also stamp the correlation id on each outgoing request and on each captured response so that a 429 or 5xx that triggered a retry is attributable to the exact logical item, never to the batch as a whole.

Mechanism reference: M18. Compounding reliability and the residual that must degrade gracefully

A bounded retry loop does not make failures disappear; it makes the recoverable ones recover and leaves a measured residual that must be handled by fallback or escalation. The lesson validation-strategies frames this with a compounding-reliability view: a 5 percent initial failure rate combined with a 95 percent retry-success rate yields a much higher overall success after retries, but a small residual remains that no amount of retrying resolves. That residual is exactly the absence, contradiction, and source-defect population described in M2, M7, and M10, and it is why graceful degradation (M9) is not a backup plan but the defined terminal state for the loop. The exam tests whether a candidate remembers that the loop is bounded and that the unbounded tail must reach a human or a clean error, not loop forever.

Ownership map

Which layer owns which guarantee matters for the exam and for production design.

  • The model owns the reasoning: reading the source, proposing field placements, computing a candidate sum, and re-reading when given specific feedback. It does not own correctness of meaning.
  • The SDK and API own structural conformance when you use structured outputs (output_config.format) or strict tool use (strict: true). The platform enforces the JSON schema at generation time, rejects malformed JSON, and guarantees required fields and types. The Anthropic structured-outputs and strict tool-use documentation confirm this enforcement
  • Application code owns semantic validation: cross-field arithmetic, date ordering, range and external checks, the deterministic comparison between calculated_total and stated_total, and the setting of conflict_detected and discrepancy flags. This is the developer's responsibility and no API guarantees it, per the lesson validation-strategies
  • The validation layer (Pydantic, Zod, Ajv, or hand-written checks) owns producing the machine-readable, field-named error that becomes the third retry ingredient.
  • The retry orchestration owns bounding attempts, classifying failures, and choosing escalate versus retry versus null. The lesson validation-pipelines owns this in its pipeline coordinator
  • The caller of the API owns transport retries: exponential backoff with jitter for 5xx, honoring Retry-After for 429, never retrying 4xx. The lesson error-handling owns this taxonomy
  • The tool implementation owns returning structured isError results with errorCategory and isRetryable rather than throwing or returning a bare string. The lesson error-handling and tool-error-handling own this
  • The human reviewer owns the terminal decision for escalated, contradictory, or absent-source cases.

Version and terminology currency

Terminology has shifted and the exam guide may describe older names than the current product uses. The verified documentation notes are explicit about the following.

  • Structured outputs now use output_config.format with {"type": "json_schema", "schema": ...}. The older output_format top-level field and the beta header are accepted for a transition period only. A candidate should answer with output_config.format as the current form and recognize output_format as legacy
  • Strict tool use is a separate feature: strict: true on a tool definition enforces schema compliance on tool names and inputs by grammar-constrained sampling. It is independent of and composable with structured outputs in one request.
  • Schema compliance is not absolute: a refusal returns stop_reason: "refusal" with a 200 status and billed tokens, and the refusal text takes precedence over the schema. Hitting the token limit also truncates before the schema can be satisfied.
  • The exact client.messages.parse(..., output_format=...) method and parsed_output attribute named in the reference page are not confirmed in the verified URL set and are marked not independently confirmed; the capability (SDK-level parsing returning typed instances) aligns with the documented direction but the specific symbol should not be treated as settled.
  • Tool use and JSON-schema-structured outputs are two independent, composable guarantees. The reference page's hierarchy of "prompt-requested JSON versus tool use" is incomplete relative to the live documentation, which adds output_config.format JSON outputs and strict tool use as separate guarantees.
  • Retry caps are described variously as two, three, or five in different materials; the principle is merely that the loop is bounded. The lesson validation-strategies uses three as the ceiling and the lesson validation-pipelines uses two to three The reconciliation tolerance around 0.01 is illustrative, not a documented constant.

Official versus community divergence

The verified divergence notes for this domain that bear on Task 4.4 are these.

  1. The reference material and some community writing present prompt-requested JSON versus tool use as the whole hierarchy of structured output. The live documentation adds JSON outputs through output_config.format and strict tool use as separate, composable guarantees. Documentation wins. A candidate should list all three: prompt-requested JSON (oldest), structured outputs via output_config.format, and strict tool use via strict: true, and know they can be combined in one request.
  1. Community material sometimes claims the batch API supports no tool calling within a single batch request and treats that as a hard constraint forcing a synchronous step. The live documentation explicitly lists tool use, including server tools, and multi-turn conversations among what can be batched. Documentation wins. The defensible reading is that a batch request is a single asynchronous unit with no interactive loop between your code and the model mid-request, so a workflow whose control flow depends on inspecting a tool result and then deciding the next request still needs separate requests. That is a different statement from tools being unsupported. For Task 4.4 the practical point is that batching does not change the validation-retry boundary; a batch item that fails validation still needs the same classification and escalation logic, and a multi-turn extraction that depends on a tool result cannot be collapsed into one batch entry.
  1. The reference material states an up-to-24-hour window and no latency SLA for batch. The live page adds that most batches finish within 1 hour, that results become available when all requests finish or at 24 hours whichever comes first, that batches expire at 24 hours, and that results stay downloadable for 29 days. The fuller documented set should be used where batch context is relevant.

The divergence that matters most for this task is the first: candidates must not answer as if tool use alone is the only structured-output path, and must not conflate the three mechanisms. All three enforce syntax; none enforce semantics.

Beyond the task statement

The reference page focuses on extraction validation and retry, but the body of lessons covers adjacent material the exam rewards and the grounding should surface.

  • retry-strategies (reliability domain). General retry design: when to retry, backoff shapes, idempotency, and avoiding thundering herds. Directly supports M9, M13, and the bounded-retry requirement. Why it matters: the extraction retry loop is one instance of a broader retry discipline; the exam can ask about retry posture for transport versus content in the same scenario
  • fallback-patterns (reliability domain). What to do when retries are exhausted: default values, simpler methods, degraded modes. Supports M9 graceful fallback. Why it matters: "escalate to human review or emit a structured error" is a fallback decision, and the exam tests which fallback fits which failure
  • escalation-patterns (reliability domain). Routing to humans, queues, and the terminal state for hard cases. Supports M3, M9, M10. Why it matters: escalation is the correct terminal state when the source cannot supply the value, and the exam tests recognizing that boundary
  • confidence-scoring (reliability domain). Field-level calibrated confidence as a reviewer-allocation signal for semantic errors that pass schema. Supports M15. Why it matters: confidence is often confused with correctness; the grounded position is that confidence routes review but does not catch fabrication or unaware absence
  • tool-error-handling (tool-use domain). Distinguishing access failures from empty results at the tool boundary, and returning structured errors. Supports M11, M12. Why it matters: the extraction tool itself must return isError with structured detail, not a bare string, and must not conflate empty with error
  • tool-result-handling (tool-use domain). How tool results are shaped and fed back into the conversation. Supports M1, M12. Why it matters: the retry payload is delivered as tool results and user turns; their shape determines whether the model can act
  • constrained-decoding (structured-outputs domain). The mechanism by which the API guarantees valid JSON shape. Supports M3. Why it matters: understanding that constrained decoding eliminates syntax but not semantics is the foundation of the boundary
  • json-mode (structured-outputs domain). The baseline structured-output path. Supports M3. Why it matters: it is the predecessor to output_config.format and the exam may reference both
  • streaming-reliability (reliability domain). Early rejection of invalid streamed output and handling partial failures. Supports M14, M16. Why it matters: streaming validation enables failing fast before the full response arrives
  • mcp-tools and mcp-production (mcp domain). Tool result error signaling over MCP, including isError. Supports M12. Why it matters: MCP is where the isError flag is explicitly specified and where structured error content must travel
  • testing-ai-systems (reliability domain). How to measure extraction quality per segment and per field rather than by an aggregate. Supports the warning that aggregate pass rates mask concentrated errors. Why it matters: the exam rewards per-segment evaluation over green averages

Worked production examples

The five examples below are the required language-tagged implementations. Each names what it proves, its failure boundary, and its observable output. They form one connected design: an invoice-extraction service that validates, classifies, retries with feedback, and escalates.

Worked production examples: Example 1. A typed validation layer that raises one machine-readable error naming the field and the broken rule (python)

This example proves that the validation layer produces a single actionable, field-named error rather than a generic failure. It is the supplier for the retry payload's third ingredient (M1, M5, M11).

example.py
python
import json
from typing import Any
from pydantic import BaseModel, ValidationError, model_validator


class LineItem(BaseModel):
    description: str
    amount: float


class Invoice(BaseModel):
    line_items: list[LineItem]
    stated_total: float

    @model_validator(mode="after")
    def totals_must_match(self) -> "Invoice":
        calculated = round(sum(item.amount for item in self.line_items), 2)
        if abs(calculated - self.stated_total) > 0.01:
            raise ValueError(
                f"line items sum to {calculated} but stated_total is {self.stated_total}"
            )
        return self


def validate_extraction(tool_input: dict[str, Any]) -> list[str]:
    """Return a list of field-named, rule-named errors; empty means valid."""
    try:
        Invoice.model_validate(tool_input)
        return []
    except ValidationError as exc:
        return [
            f"{'.'.join(map(str, err['loc'])) or 'invoice'}: {err['msg']}"
            for err in exc.errors()
        ]


# Observable output for a mismatch:
#   line_items: line items sum to 450.0 but stated_total is 500.0
#   stated_total: Input should be a valid number   (if type were wrong)
errors = validate_extraction({
    "line_items": [
        {"description": "Widget A", "amount": 150.00},
        {"description": "Widget B", "amount": 300.00},
    ],
    "stated_total": 500.00,
})
print(errors)

What it proves: parsing enforces structure (types, required fields) and the validator enforces the cross-field arithmetic rule. Both failures surface through one ValidationError whose entries name the field path and the broken rule. Failure boundary: if the error were a bare raise Exception("bad invoice"), the retry would receive no specific signal and would behave like a naive retry. Observable output: a list of strings like line_items: line items sum to 450.0 but stated_total is 500.0, each directly formattable into the retry prompt

Worked production examples: Example 2. A retry message carrying the source plus the prior output plus the specific error (typescript)

This example proves the three-part payload (M1, M2). It assembles one follow-up user turn that concatenates the original document, the failed extraction, and the specific validation error, then requests a targeted correction.

example.ts
typescript
interface RetryContext {
  originalDocument: string;
  failedExtraction: unknown;
  errors: string[];
}

function buildRetryMessage(ctx: RetryContext): string {
  return [
    `Original document:\n${ctx.originalDocument}\n`,
    `Your extraction:\n${JSON.stringify(ctx.failedExtraction, null, 2)}\n`,
    `Validation errors:\n${ctx.errors.join("\n")}\n`,
    "Please re-extract, correcting only the fields named in the errors above.",
  ].join("\n");
}

// Usage inside the loop after validate_extraction returns a non-empty error list.
const retryContent = buildRetryMessage({
  originalDocument: invoiceText,
  failedExtraction: priorToolInput,
  errors: [
    "line_items: line items sum to 450.0 but stated_total is 500.0",
  ],
});

// The next messages.push is a single user turn carrying all three parts.
messages.push({ role: "user", content: retryContent });

What it proves: the model is given the source to re-read, its own prior output to see its mistake, and the specific error to localize the fix. Failure boundary: omitting any one part degrades the retry. Omitting the prior output leaves the model without context for its mistake; omitting the error leaves it guessing; omitting the source removes the tested material to re-read. Observable output: a single concatenated user message whose three labeled sections are present, and on retry the model returns a corrected extraction where stated_total reconciles with the line items

Worked production examples: Example 3. A schema that self-reports discrepancy through a calculated value alongside a stated value, with deterministic recomputation in code as the authority (json and python)

This example proves the self-correction schema and, critically, that the authoritative comparison runs in code, not in a model-set boolean (M4, M6). The model returns both calculated_total and stated_total; application code recomputes and decides.

request.json
json
{
  "name": "extract_invoice",
  "input_schema": {
    "type": "object",
    "properties": {
      "line_items": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "description": { "type": "string" },
            "amount": { "type": "number" }
          },
          "required": ["description", "amount"]
        }
      },
      "calculated_total": {
        "type": "number",
        "description": "Sum you compute from line_items. Return the arithmetic sum, not the document total."
      },
      "stated_total": {
        "type": "number",
        "description": "The total printed on the document, transcribed exactly."
      },
      "total_discrepancy": {
        "type": "boolean",
        "description": "Set by APPLICATION CODE after comparing calculated_total and stated_total; do not assert from memory."
      }
    },
    "required": ["line_items", "calculated_total", "stated_total"]
  }
}
example.py
python
def decide_discrepancy(extraction: dict[str, float]) -> dict[str, object]:
    # Code is the authority. Recompute from the parts the model returned.
    calculated = round(sum(item["amount"] for item in extraction["line_items"]), 2)
    stated = round(float(extraction["stated_total"]), 2)
    discrepancy = abs(calculated - stated) > 0.01
    return {
        "calculated_total": calculated,
        "stated_total": stated,
        "total_discrepancy": discrepancy,  # set here, not by the model
    }

# Observable output:
#   {"calculated_total": 450.0, "stated_total": 500.0, "total_discrepancy": True}
result = decide_discrepancy({
    "line_items": [
        {"description": "Widget A", "amount": 150.00},
        {"description": "Widget B", "amount": 300.00},
    ],
    "stated_total": 500.00,
})
print(result)

What it proves: the schema makes the discrepancy visible by carrying both values, but the boolean is set by deterministic code that re-sums the line items. The model's own calculated_total is treated as a returned value to be checked, not as the judge. Failure boundary: asking the model to set total_discrepancy: true inside the same pass that miscomputed the sum would let the error self-assert; the external comparison dominates and the self-assertion is discarded. Observable output: a record with total_discrepancy: true when the sums differ, which routes to human review rather than auto-posting

Worked production examples: Example 4. A bounded retry loop with typed outcome classification that separates a retryable format failure from genuine absence (typescript)

This example proves M2, M8, M9, and M10 together: the loop classifies each failure, retries only the retryable class, and escalates genuine absence instead of looping.

example.ts
typescript
type Outcome =
  | { kind: "ok"; data: unknown }
  | { kind: "escalate"; reason: string; field: string | null }
  | { kind: "error"; error: string };

function classify(errorText: string): "retryable" | "absent" {
  // A source-absence signal is routed out of the loop immediately.
  if (/not (mentioned|present|stated|contained) in the (source|document)/i.test(errorText)) {
    return "absent";
  }
  return "retryable"; // format, structural, misplaced, arithmetic
}

async function extractWithBoundedRetry(
  sourceText: string,
  validate: (input: unknown) => string[],
  maxRetries = 3,
): Promise<Outcome> {
  const messages = [
    { role: "user", content: `Extract invoice data from this text:\n${sourceText}` },
  ];

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    const raw = await callModel(messages);
    const parsed = safeParse(raw);
    if (parsed === null) {
      // Parse failure: retryable format error, append the specific error.
      messages.push({ role: "user", content: "Response was not valid JSON. Return only a JSON object." });
      continue;
    }
    const errors = validate(parsed);
    if (errors.length === 0) {
      return { kind: "ok", data: parsed };
    }
    const category = classify(errors.join(" "));
    if (category === "absent") {
      // Genuine absence: stop retrying, escalate. Do not burn attempts.
      return { kind: "escalate", reason: errors.join("; "), field: null };
    }
    messages.push({
      role: "user",
      content: `Validation errors:\n${errors.join("\n")}\n\nPlease re-extract, correcting only these fields.`,
    });
  }
  return { kind: "escalate", reason: "max retries exceeded", field: null };
}

What it proves: the classification gate routes absent failures out of the loop on the first detection, while retryable failures receive the specific error and another attempt up to the cap. Failure boundary: retrying an absent field converges on the same null or fabrication and only wastes compute; the gate prevents that. Observable output: a retryable format error is corrected within one or two attempts and returns kind: "ok"; a genuinely absent field returns kind: "escalate" on the first pass, never looping

Worked production examples: Example 5. API-level error handling that distinguishes a client validation error from a transient server or rate condition, with the documented retry posture for each (typescript)

This example proves M11, M12, and M13: transport errors get their own posture, distinct from content validation, and the client honors the documented retry rules (429 waits for Retry-After, 5xx backs off with jitter, 4xx is not retried).

example.ts
typescript
import { randomInt } from "node:crypto";

async function withTransportRetry(
  fn: () => Promise<Response>,
  maxAttempts = 5,
): Promise<Response> {
  let delay = 1000;
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    const res = await fn();
    if (res.status < 500 && res.status !== 429) {
      // 4xx client errors: permanent. Do not retry; surface for input fix.
      return res;
    }
    if (attempt === maxAttempts) {
      return res;
    }
    if (res.status === 429) {
      // Rate limit: honor the server's Retry-After exactly.
      const retryAfter = Number(res.headers.get("retry-after") ?? "1");
      await sleep(retryAfter * 1000);
      continue;
    }
    // 5xx transient: exponential backoff with jitter, capped.
    const jitter = randomInt(0, Math.floor(delay / 2));
    await sleep(Math.min(delay + jitter, 32000));
    delay = Math.min(delay * 2, 32000);
  }
  return fn(); // unreachable, satisfies types
}

// Inside a tool, a validation error from our own logic is reported with structure,
// not thrown, so the model can reason about it (M11/M12):
function toolResultError(field: string, message: string) {
  return {
    type: "tool_result" as const,
    is_error: true,
    content: JSON.stringify({
      isError: true,
      errorCategory: "validation", // client-side, not retryable at transport
      isRetryable: false,
      description: `${field}: ${message}`,
      affectedFields: [field],
    }),
  };
}

What it proves: the transport retry loop treats 4xx as permanent (fix the request, never retry), 429 as retryable after the documented Retry-After wait, and 5xx as retryable with exponential backoff plus jitter capped at 32 seconds. A content-validation error from our own logic is returned as a structured isError tool result with errorCategory: "validation" and isRetryable: false, so the model gets actionable detail rather than a bare string. Failure boundary: retrying a 400 with backoff wastes attempts because the request is the problem; treating 429 like 5xx ignores the explicit timing signal. Observable output: a 429 waits the server-specified duration then succeeds; a 400 returns immediately for input correction; a 500 retries with growing, jittered delays and either recovers or returns after the cap

Worked production examples: End-to-end walkthrough

A production invoice pipeline wires these together. Document A has a line-item sum of 450 but a stated total of 500. Document B is missing the department field entirely from the source text.

Step 1: request extraction via a tool whose input_schema enforces structure (M3). Both documents return schema-valid JSON. Step 2: the typed validation layer (Example 1) returns ["line_items: line items sum to 450.0 but stated_total is 500.0"] for Document A and ["department: field not stated in the source document"] for Document B. Step 3: classify. Document A is retryable (arithmetic), Document B is absent. Step 4: for Document A, build the three-part retry message (Example 2) and re-prompt. The model re-sums, returns stated_total: 450.0, and validation passes. Step 5: for Document B, the nullable schema already returns department: null because of the null-when-absent instruction (M7); the pipeline routes it to human review with the field marked null, never retrying. Step 6: if a 5xx interrupted any call, the transport retry (Example 5) recovered it transparently. The observable outcome is that Document A self-corrects within two attempts and Document B is escalated once, with no fabrication and no wasted loops.

The correlation id assigned before Step 1 travels through every message and every structured error, so the human-review queue shows Document B as one item with a single stable id, and the transport retry shows the 5xx as attributable to that same item rather than to the batch as a whole (M17).

This walkthrough also makes the compounding-reliability point from M18 concrete. Document A's arithmetic miss is the recoverable class: one well-formed retry recovers it, and the overall pipeline success rate climbs because the retry succeeds most of the time on this class. Document B's absence is the residual: no retry helps it, and the loop's job is to spend exactly zero additional attempts on it and hand it to a human. A pipeline that confused the two would either retry Document B until the cap and then escalate anyway (wasted compute, delayed review) or, worse, never escalate because a required schema coerced a fabricated department. The disciplined design keeps the two populations separate end to end, from the validator's field-named error, through the classifier, to the fallback, each stage carrying the same correlation id.

Build exercise material

The following steps reproduce the recommended lab from the reference page and the lesson guidance. Each step lists the observable outcome that proves it worked.

Step 1. Define an extraction tool with calculated_total and stated_total number fields, a total_discrepancy boolean, a conflict_detected boolean, and a detected_pattern string on each finding in the line items array. Observable: the JSON schema compiles and the tool accepts an input carrying both totals and the booleans. The model returns both totals rather than asserting a single reconciled value

Step 2. Implement validation logic that checks field completeness, numerical consistency (calculated sum matches stated total within 0.01), enum validity, and date ordering. Observable: the validator returns an array of specific, actionable error messages, each stating what was expected versus what was found, not merely that validation failed. The Example 1 code is the template

Step 3. Build the retry loop: on validation failure, construct a follow-up message containing the original document, the failed extraction, and the specific validation error. Observable: the retry message contains all three labeled sections, and the model produces a corrected extraction on retry, correcting only the named fields

Step 4. Test with five documents: two with fixable errors (misplaced values, wrong totals) and three with unfixable errors (absent information). Observable: the two fixable documents succeed after one or two retries with corrected totals or field placements; the three unfixable documents are correctly identified as having absent information and routed to human review rather than retried

Step 5. Log detected_pattern data for each finding and analyze which patterns are most frequently dismissed, to identify prompt-refinement priorities. Observable: a log or table showing each detected_pattern, its frequency, its dismissal rate, and a prioritized list of patterns needing prompt refinement, with high-dismissal patterns at the top

Step 6. Add the transport retry wrapper (Example 5) and a structured isError tool result (M11/M12) so that content validation and API transport are handled by distinct, correctly-postured logic. Observable: a simulated 429 causes exactly one wait of the Retry-After duration then success; a simulated 400 returns immediately for input correction; a content-validation failure returns a structured error the model can act on

Mechanism and API surface

Three-part retry with error feedback
Original document plus failed extraction plus specific validation error in the retry message. Without the specific error the model reproduces the same mistake, with it the model targets correction such as missed line items or misplaced fields.
Fixable versus unfixable boundary
Fixable: format mismatches, structural errors, misplaced values, arithmetic errors. Not fixable: genuinely absent information, data in an external document not provided, knowledge the model lacks. Retries only on the first category.
Pydantic structure versus semantics
Parsing enforces types, required fields, and enums. Validators with model_validator mode after enforce cross-field arithmetic, date ordering, and conditional rules. Both surface via ValidationError with loc, msg, and type.
SDK parse with parsed_output
client.messages.parse with output_format returns validated Pydantic instances through parsed_output, combining schema enforcement and validator business rules in one call that the retry loop can consume.
Self-correction fields in the schema
calculated_total versus stated_total plus total_discrepancy, conflict_detected for contradictions, and detected_pattern per finding build detection into the output shape rather than only in external logic.
Retry budget and routing
Maximum typically three retries, beyond that route to human review. Routing for absent fields uses nullable schema and null return, while low-confidence routing uses calibrated thresholds from labelled validation sets.

The decision rules in play

Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.

R1

The retry payload must carry the original document, the failed extraction, and the specific validation error

When an extraction fails validation, the recovery request is not a fresh copy of the original prompt. It is a follow-up turn that bundles three distinct pieces of context: the source document so the model can re-read the tested material, the exact JSON it produced on the failed attempt so it can see its own mistake, and the precise validation error string naming what went wrong. The model then re-extracts, ideally correcting only the broken field rather than regenerating everything. The payload is typically assembled as a single user turn that concatenates the three parts with clear delimiters.

example.ts
typescript
const retryMessages = [
  {
    role: "user",
    content:
      `Original document:\n${originalDocument}\n\n` +
      `Your extraction:\n${JSON.stringify(failedExtraction)}\n\n` +
      `Validation error: Line items sum to 450.00 but stated_total is 500.00. ` +
      `Please re-extract, ensuring all line items are captured.`,
  },
];

A naive retry that resends the original document and prompt gives the model no new signal. The model is a deterministic-ish function of its context; if the context is unchanged, it tends to reproduce the same error, often at the same rate. Supplying the specific error converts an opaque failure into an actionable instruction. The model now knows which field was wrong, what it produced, what the constraint was, and that it should re-examine the source. That targeted context is what lets self-correction work rather than looping.

Boundary. The boundary is the presence of a specific error signal. If the validator only returns a generic flag such as {"error": true} with no field name or message, the retry is effectively naive and will reproduce the mistake. The nearby opposite case is the generic "try again" instruction, which looks like feedback but carries zero information. Both produce the same poor outcome: the error persists across attempts.

Recurring specifics. The three ingredients appear consistently as originalDocument, failedExtraction, and a Validation error: line. Effective error strings name the field (for example policy_number), the wrong value (ABC-123), the required format (10 alphanumeric characters without hyphens), and the violated constraint (must be a date in the past). The instruction almost always ends with "re-examine the source document and correct only these fields" so the model does not discard correct ones.

Wrong answers written against this rule

Proposal. Resend the original document unchanged and hope for a different sampling outcome.

Why it attracts. Non-determinism might help, and it is the simplest change

Why it fails. Without the error, the model has no guidance and usually repeats the mistake

When it would be right. Only when the failure is pure sampling noise on a trivial field, which is rare

Proposal. Send the validation error alone, without the document or the prior output.

Why it attracts. Keeps the request short and cheap

Why it fails. The model lacks the source to re-read and the context of its own mistake

When it would be right. Never, because the model cannot self-correct without its own prior output

Proposal. Include only a generic "your previous output was invalid, try again" message.

Why it attracts. Mirrors typical error handling in other systems

Why it fails. Generic text gives no field, no wrong value, no constraint, so correction is a guess

When it would be right. Never for structured extraction; specificity is the entire mechanism

Proposal. Regenerate the entire output from scratch on each retry.

Why it attracts. Feels like a clean restart

Why it fails. Throws away correctly extracted fields and loses the localization of the error

When it would be right. Only when the whole document is short and every field is suspect

How the same rule gets re-asked
  • The examiner mutates which of the three parts is omitted. Sometimes the retry carries the document and error but not the prior output; sometimes it carries the error alone. Sometimes the framing swaps "append the error to the same prompt" versus "construct a new follow-up turn". The correct answer always requires all three. Another mutation asks whether the retry should regenerate everything or correct only the named fields; the precise answer is correct-only-the-failed-fields.
R2

Retries recover format mismatches, structural errors, misplaced values, and arithmetic omissions

The retry-with-error-feedback loop is effective for a defined set of failure classes: values in the wrong type or format (a date rendered as 03/04/2025 when ISO is required), values placed in the wrong structural location (a reading dropped under the wrong object), values swapped between two valid fields (net and gross amounts reversed), and arithmetic omissions (a line item missed so the sum is short). In each case the information exists in the source; the model simply did not place or compute it correctly. The specific error tells the model exactly where to look, and it can recover.

These are self-correctable reasoning mistakes. The data the model needs is present in the document it is allowed to re-read. The error message localizes the mistake. Given both, the model can reassociate the value with the correct field, reformat the date, or re-sum the line items. The loop works precisely because nothing new needs to be created.

Boundary. The boundary is presence of the value somewhere in the source. The nearby opposite case is a field whose value is simply not in the document at all; there, the same retry produces the same failure because there is nothing to re-read. A misplaced meter_reading that was found correctly but mapped to billing_address is fixable; a meter_reading that never appears on the bill is not.

Recurring specifics. The fixable classes are repeatedly listed together: format mismatch, structural output error, misplaced value, mathematical error. Common concrete instances are net_amount versus gross_amount swaps, billing_address versus shipping_address swaps, ISO 8601 versus locale date formats, and a summed line-item total that is short by one row. The retry success rate after a single well-formed retry is described as high (often resolving within two or three attempts).

Wrong answers written against this rule

Proposal. Increase the retry count from 1 to 5 because extraction is non-deterministic.

Why it attracts. More attempts feel like more chances

Why it fails. If the model maps fields wrong with the same prompt and no error, more attempts reproduce the same error

When it would be right. Only if the error signal were already present and the failure were marginal

Proposal. Switch to a different model for retries.

Why it attracts. A "fresh perspective" sounds plausible

Why it fails. The problem is missing error context, not model capability

When it would be right. When the issue is genuinely a capability gap, which is a different rule

Proposal. Add a post-processing rule that auto-swaps the two fields.

Why it attracts. Seems to fix the symptom cheaply

Why it fails. Brittle, masks the underlying extraction error, and breaks on the third variant

When it would be right. Never as a primary fix; detection plus retry is preferred

Proposal. Hard-code a swap whenever the pattern appears.

Why it attracts. Looks deterministic

Why it fails. Conceals the real error and cannot generalize to novel misplacements

When it would be right. Never; it prevents the pipeline from learning the true cause

How the same rule gets re-asked
  • The examiner swaps which fixable class is shown: sometimes a date format, sometimes a field swap, sometimes a missing line item. The correct answer is always retry-with-error-feedback. A frequent mutation pairs a fixable case with an unfixable one in the same prompt (Document A has a sum mismatch, Document B is missing a field) and asks you to retry only the fixable one, which is the bridge to Rule 3.
R3

Retries cannot recover information genuinely absent from the source document

When a required field's value does not exist anywhere in the source material, no number of retries can produce it. The retry loop can only help the model re-read and re-reason over what is present. If the information is absent, the loop converges on the same null or fabrication forever, consuming compute and latency. The correct action is to recognize absence and either route the document to human review or emit null where the schema permits it.

A retry is a re-prompt over the same document. It cannot introduce facts the document does not contain. The model cannot "find" a department that the source text never mentions, a shipping_date that no clause states, or a tax_id that the invoice omits. Continued retrying is pure waste, and worse, it tempts the model to fabricate a plausible value to satisfy a required field.

Boundary. The boundary is source containment. If the value appears anywhere in the provided material, even in an unreferenced exhibit or a different page the pipeline was not given, then retrying with better context can work. The nearby opposite case is when the value lives only in an external document not supplied (for example a reference ID mentioned only in the covering email, not in the attached bill). There, the fix is to fetch the external document or escalate, not to retry the same input.

Recurring specifics. Absent-field examples are repeated across domains: a department missing from source text, a shipping_date never mentioned, a hazmat_class absent from the manifest, a lease_start genuinely omitted from the agreement, a sample_size not stated in the paper, an effective_date that appears only in an exhibit never supplied. The phrasing "genuinely absent from the source document" is the canonical trigger. Persistent null after several retries is the tell that the value is absent rather than merely misread.

Wrong answers written against this rule

Proposal. Raise the retry limit from 3 to 10 or 15.

Why it attracts. More attempts feel safer

Why it fails. Retrying cannot conjure absent data; the null persists

When it would be right. Never for true absence; only delays the escalation

Proposal. Rewrite the validation error to be more detailed.

Why it attracts. Detail helped in other cases

Why it fails. The model already knows the field is missing; more words do not create the value

When it would be right. Never here; detail helps only when the value exists

Proposal. Force tool_choice to the extraction tool to guarantee the field is populated.

Why it attracts. Guaranteeing the call seems like progress

Why it fails. Forces fabrication rather than recovery

When it would be right. Never; it converts absence into hallucination

Proposal. Instruct the model to "search the metadata" or "try harder".

Why it attracts. Sounds like a nudge

Why it fails. The data is not there to find

When it would be right. Only if the data were present in a location the model had not checked

How the same rule gets re-asked
  • The examiner pairs an absent field with a retryable one and asks for the right action per case, or shows a document that fails every retry with the same "missing field" error and asks for the interpretation. Another mutation asks whether to make the field nullable or to keep retrying; the answer depends on whether the field is sometimes present (nullable plus retry) or never present (nullable, no retry, or escalate).
R4

Schema validation via tool use eliminates syntax errors but not semantic errors

When extraction is performed through a tool whose input_schema is a JSON schema, the platform enforces structure at generation time: required fields are present, types match, enums are respected, and malformed JSON is rejected before it reaches downstream code. This erases the entire class of schema syntax errors. It does not, however, prevent semantic errors: a value that is the wrong number, the wrong value in a valid field, a sum that does not reconcile, or a date that is formatted correctly but logically wrong. Those require a validation layer outside the schema.

example.py
python
from pydantic import BaseModel, field_validator

class Invoice(BaseModel):
    line_items: list[LineItem]
    stated_total: float

    @field_validator("stated_total")
    @classmethod
    def total_must_reconcile(cls, v, info):
        # Schema passed; this semantic check still has to run.
        ...

A JSON schema describes the shape of data, not its meaning. It can say "endDate is a string and matches the date format" but it cannot say "endDate must come after startDate" because comparing two property values is beyond static schema validation. Similarly it cannot know that a governing_law string actually contains jurisdiction text rather than termination language. The schema guarantees the container; it cannot guarantee the contents are correct.

Boundary. The boundary is structural conformance versus value truth. The nearby opposite case is assuming that because tool use guarantees valid JSON, it also guarantees correct extraction. That assumption is the trap: teams report zero malformed JSON yet persistent wrong sums and misplaced values. The schema did its job; the semantic layer was never built.

Recurring specifics. The contrast appears as "malformed JSON dropped to zero after switching to tool use, but sum mismatches still occur". Field names stated_total, calculated_total, conflict_detected, governing_law, termination_clause, end_date, start_date recur. The statement "strict schemas via tool use eliminate syntax errors but not semantic errors" is stated almost verbatim across many scenarios. JSON Schema cannot natively compare two property values; that needs application or validator logic.

Wrong answers written against this rule

Proposal. Tighten the JSON schema so totals must equal the sum of line items.

Why it attracts. Schema enforcement feels like the right lever

Why it fails. Schemas constrain structure and types, not arithmetic consistency

When it would be right. Never; arithmetic is cross-field and semantic

Proposal. Switch tool_choice from forced to "auto" to fix arithmetic.

Why it attracts. Tool choice is a common knob

Why it fails. Unrelated to whether totals reconcile

When it would be right. Never for this purpose

Proposal. Increase max_tokens so the model computes totals correctly.

Why it attracts. More room sounds like more accuracy

Why it fails. Token budget does not make totals reconcile

When it would be right. Only when truncation caused the bad output, a different failure

Proposal. Trust the schema as sufficient validation.

Why it attracts. Convenient and already in place

Why it fails. Passes shape but not meaning, so wrong values flow downstream

When it would be right. Never as the sole check

How the same rule gets re-asked
  • The examiner mutates the framing between "tool use already used, still wrong sums" and "free-form JSON, malformed output too" to test whether you reach for schema enforcement (only fixes the latter) or a semantic layer (fixes both). Another mutation asks what a schema can express versus what it cannot, probing the endDate > startDate limit directly.
R5

A semantic validation layer is required to catch values placed in the wrong field

A value can be semantically correct yet placed in the wrong field: the content that belongs in governing_law lands in termination_clause and vice versa. Both fields accept strings, so schema validation passes silently. The retry loop, receiving no error signal, reproduces the swap. The fix is a semantic validation step that compares extracted content against expected patterns (for instance, checking that governing_law contains jurisdiction-like text rather than termination language) and only then emits a specific mismatch description into the retry prompt.

Schema validation is blind to meaning. Two free-text fields are interchangeable from the schema's point of view. Only a layer that knows what each field should contain can detect the swap. Without that layer, the failure is invisible to the retry machinery, so retries never trigger and the error persists. The examiner frames this as the precise case where "schema validation cannot detect semantic field placement errors, so the retry loop never triggers".

Boundary. The boundary is whether a validation signal exists. If a semantic check can name the mismatch, retry-with-error-feedback fixes it. The nearby opposite case is a pure enumeration error: if governing_law were constrained to an enum of jurisdiction codes, the schema itself would reject the misplaced termination text. But free-text placement errors need pattern-based semantic validation, not enums.

Recurring specifics. The canonical pair is termination_clause versus governing_law. Other swaps: billing_address versus shipping_address, net_amount versus gross_amount, department versus a sibling field, and code-review categories (a dependency issue filed under security). The keyword "semantic validation logic that cross-checks field values against expected content patterns" recurs whenever the error is wrong placement of valid-looking text.

Wrong answers written against this rule

Proposal. Add enum constraints to both fields.

Why it attracts. Enums catch categorical errors well

Why it fails. Free-text placement errors are not categorical; the text is valid in either field

When it would be right. When the field is genuinely a fixed category set

Proposal. Enable extended thinking on retry requests.

Why it attracts. More reasoning budget seems to help

Why it fails. Reasoning cannot compensate for a missing error signal in the prompt

When it would be right. When the error signal is already present and the task is hard reasoning

Proposal. Make the fields nullable so the model returns null.

Why it attracts. Nullable helps absence cases

Why it fails. The information is present, just misrouted; null would discard correct data

When it would be right. When the value is truly absent, a different rule

Proposal. Tighten the JSON schema with more required fields.

Why it attracts. Schema tightening is the usual fix

Why it fails. Does not address placement of valid text between two string fields

When it would be right. Never for this failure class

How the same rule gets re-asked
  • The examiner mutates whether the swap is between two free-text fields (needs semantic validation) or a categorical field (enum would catch it). Another mutation asks whether the retry needs extended thinking; the answer is no, it needs the semantic error signal. A third asks whether nullable fields fix it; the answer is no, because the data is present.
R6

Self-correction via `calculated_total` versus `stated_total` surfaces arithmetic discrepancies

Rather than relying on the model to assert that totals are consistent, the extraction schema is enriched so the model returns both the total it computes by summing the line items (calculated_total) and the total printed on the document (stated_total). A deterministic post-extraction check compares the two. When they differ beyond a small tolerance, the record is flagged and routed for human review rather than auto-posted.

result.json
json
{
  "line_items": [
    { "description": "Widget A", "amount": 150.00 },
    { "description": "Widget B", "amount": 300.00 }
  ],
  "calculated_total": 450.00,
  "stated_total": 500.00,
  "total_discrepancy": true
}

LLMs are probabilistic at arithmetic, and OCR or extraction can drop a line item. By extracting both figures, the discrepancy becomes machine-checkable rather than dependent on the model's self-assessment. The comparison is deterministic code, not a model judgment, so it catches extraction errors the model would otherwise certify as correct.

Boundary. The boundary is extracting both values versus extracting one and trusting it. The nearby opposite case is asking the model to "double-check its math" in the prompt. That is unreliable and not independently verifiable; the model may still certify a wrong sum. Schema redundancy plus a deterministic compare is the robust pattern.

Recurring specifics. Field names calculated_total, stated_total, total_discrepancy, totals_match, discrepancy_detected, and conflict_detected recur. Tolerance is often a small float (around 0.01). The pattern applies to invoices, purchase orders, duty computations, and balance-sheet reconciliation (total_assets versus liabilities + equity). The phrase "extract both a calculated total and a stated total so a discrepancy is visible" is near-canonical.

Wrong answers written against this rule

Proposal. Ask the model to "double-check that totals are consistent".

Why it attracts. Simple prompt change

Why it fails. Not independently verifiable; model may still assert a wrong sum

When it would be right. Never as the sole control

Proposal. Tighten the schema with stricter typing on stated_total.

Why it attracts. Type safety is good practice

Why it fails. Types do not enforce arithmetic reconciliation

When it would be right. Never for reconciliation

Proposal. Trust only the stated total and omit line items.

Why it attracts. Simpler output

Why it fails. Loses the ability to detect internal disagreement

When it would be right. Never; you need both to compare

Proposal. Auto-adjust line items to match the stated total.

Why it attracts. Feels like self-healing

Why it fails. Corrupts the data and hides the real discrepancy

When it would be right. Never; preserve and flag instead

How the same rule gets re-asked
  • The examiner mutates whether the comparison is done deterministically outside the model (correct) or asserted by the model inside the same pass (wrong, see Rule 12). Another mutation asks whether to discard one total; the answer is keep both. A third asks whether to auto-correct; the answer is route to human review.
R7

`conflict_detected` booleans preserve in-document contradictions instead of silently picking one value

When a source document contradicts itself (for example, a payment term stated as 30 days in one section and net 60 in another), the extractor should return both values and set a conflict_detected boolean rather than silently choosing one. Downstream code routes any record with the flag set to a reconciliation queue or human reviewer, preserving both values for audit.

A model that silently collapses a contradiction into a single value destroys evidence the downstream consumer needs to make a correct decision. Surfacing the conflict as structured data makes the contradiction inspectable and routable. The boolean is the signal that triggers review; the preserved values are the tested material.

Boundary. The boundary is contradiction inside the source versus a single extractable truth. The nearby opposite case is a genuine extraction error where the model read one clause wrong; there, retry-with-error-feedback corrects it. But when both values are real and present in the document, retry is useless and coercion is forbidden: you must keep both and flag.

Recurring specifics. Field triples recur: rider_rate and table_rate with conflict_detected for lease escalation; declared_total_gross_weight and computed_line_item_sum with conflict_detected for manifests; computed_duty and declared_duty with conflict_detected for customs; stated_total_assets and calculated_total_assets with conflict_detected for balance sheets. The instruction "keep both values and route to reconciliation" is consistent across scenarios.

Wrong answers written against this rule

Proposal. Have the model resolve the conflict into one value.

Why it attracts. Feels decisive

Why it fails. Masks audit evidence and may pick the wrong clause

When it would be right. Never when both are genuine source facts

Proposal. Overwrite the declared total with the computed sum.

Why it attracts. Seems to "fix" the record

Why it fails. Coercion hides the real disagreement

When it would be right. Never; preserve both

Proposal. Retry the extraction expecting consistency.

Why it attracts. Retry works elsewhere

Why it fails. The contradiction is in the source, not the extraction

When it would be right. Never for source contradictions

Proposal. Emit a single value plus a low confidence score.

Why it attracts. Confidence signals uncertainty

Why it fails. A single score hides which value was chosen and why

When it would be right. Inferior to explicit both-values-plus-flag

How the same rule gets re-asked
  • The examiner mutates whether the conflict is between two real source values (flag and preserve) or a misread of one value (retry). Another mutation asks whether to overwrite; the answer is no. A third asks whether a single confidence enum suffices; the answer is no, both values and the flag are required.
R8

`detected_pattern` fields make dismissal and failure analysis inspectable for prompt refinement

For analysis and code-review pipelines, structured findings carry a detected_pattern field naming the specific construct that triggered the finding (for example, "string concatenation in SQL query"). When downstream reviewers dismiss findings, the system logs which detected_pattern each dismissal concerned. Aggregating dismissals by pattern reveals which constructs are routinely false positives, directing prompt refinement at the highest-noise patterns first.

result.json
json
{
  "finding": "Potential SQL injection vulnerability",
  "severity": "critical",
  "detected_pattern": "string concatenation in SQL query",
  "file": "user_service.py",
  "line": 42
}

Without a pattern tag, a dismissal is just "reviewer closed this finding", carrying no signal about why. With the tag, dismissals become a dataset: pattern X dismissed 80% of the time is a prompt-refinement priority. This closes the loop from extraction, through validation, through human dismissal, back to prompt improvement.

Boundary. The boundary is structured reason externalized versus reason left implicit in prose. The nearby opposite case is a reasoning field written in free text; it is auditable in principle but not machine-groupable. The detected_pattern field is the machine-readable handle that makes systematic analysis possible.

Recurring specifics. The detected_pattern field (and its array form detected_patterns) recurs in moderation classification, security findings, and dismissal-analysis scenarios. The primary benefit is stated as enabling the validation step to detect internal reasoning inconsistencies and provide targeted error feedback. A secondary benefit is auditability.

Wrong answers written against this rule

Proposal. Auto-correct the category by overriding the model's classification.

Why it attracts. Sounds efficient

Why it fails. The system should feed back inconsistency, not dictate the answer

When it would be right. Never; override removes the model's judgment

Proposal. Log only the final validated output, discarding failed attempts.

Why it attracts. Simpler storage

Why it fails. Destroys the dismissal dataset needed for refinement

When it would be right. Never when improvement loops matter

Proposal. Increase few-shot examples without tracking dismissal reasons.

Why it attracts. Examples help

Why it fails. Does not tell you which patterns to target

When it would be right. Useful but insufficient alone

Proposal. Set isRetryable false for all validation errors.

Why it attracts. Reduces retry volume

Why it fails. Kills the analysis signal and blocks legitimate retries

When it would be right. Never as a blanket setting

How the same rule gets re-asked
  • The examiner mutates whether the field's benefit is described as auditability (a distractor framed as primary) or as enabling targeted self-correction feedback (the correct primary benefit). Another mutation pairs detected_pattern with a reasoning field to test whether you see the pattern as the machine-readable handle.
R9

Required fields force fabrication when source data is absent; nullable fields remove that pressure

When a schema marks a field required and the source document does not contain the value, the model is structurally forced to populate the field to produce a schema-compliant response. With no compliant way to omit it, the model invents a plausible value. Making the field optional or nullable gives the model a legitimate way to return null, so it reports absence honestly instead of fabricating.

A required field is a hard architectural constraint. The model satisfies constraints over instructions: telling it "do not fabricate" while the schema says "this field is required" creates a conflict the schema usually wins. Making the field nullable removes the conflict entirely; null becomes the correct, schema-valid answer for absence.

Boundary. The boundary is whether the field is sometimes absent across the document population. If a field is present in every document, keeping it required is correct and nullable would merely permit silence. The nearby opposite case is a field present in most but not all documents (for example grant_number on theoretical papers): there, required causes fabrication on the minority, so nullable is correct.

Recurring specifics. The fabrication pattern recurs with tax_id, weight, hazmat_class, grant_number, conflicts_of_interest, purchase_order_number, and vendor_tax_id. The decision rule is repeated: "Is this field present in every source document? Yes, keep required. No, make optional or nullable." Fabricated values are described as "realistic-looking" and "plausible".

Wrong answers written against this rule

Proposal. Keep the field required and add a prompt instruction not to invent values.

Why it attracts. Instruction feels like a fix

Why it fails. Schema pressure wins over instruction; the model still fabricates

When it would be right. Never when the field is sometimes absent

Proposal. Add a validation-retry loop that re-prompts when the field is missing.

Why it attracts. Retry fixes other errors

Why it fails. There is no value to find, so retry reproduces the gap or fabrication

When it would be right. Never for true absence

Proposal. Lower the temperature to reduce fabrication.

Why it attracts. Temperature affects variation

Why it fails. Does not remove the structural pressure to populate

When it would be right. Never as the primary fix

Proposal. Post-extraction validation to detect and remove fabricated values.

Why it attracts. Detection sounds useful

Why it fails. Cannot reliably distinguish fabrication from a lucky correct guess

When it would be right. At best a secondary control

How the same rule gets re-asked
  • The examiner mutates whether the field is required (fabrication) or already nullable (a different rule, see Rule 10). Another mutation offers "make optional" versus "delete the field" versus "add a confirmation flag"; only making it optional or nullable is correct. A third asks whether to keep retrying; the answer is no.
R10

A nullable schema is necessary but not sufficient; an explicit null-when-absent instruction is also required

Making a field nullable permits null, but the model may still emit a plausible value for a field the source never mentions, because generating a value is its default behavior. The complete fix pairs the nullable schema with an explicit instruction: "return null if the information is not directly stated in the source". The schema defines what is allowed (null is legal); the instruction defines when to use it (use null when absent).

Schema and prompt are two cooperating layers. The schema removes the penalty for null; the prompt supplies the behavioral rule that triggers it. With only one, fabrication can persist: a required field forces it, and a nullable field without guidance still defaults to filling. Both together eliminate the behavior at the root.

Boundary. The boundary is nullable-only versus nullable-plus-instruction. The nearby opposite case is the earlier rule: if the field is required, even a strong instruction fails because the schema forbids the empty answer. Here the field is already nullable, so the missing piece is purely the behavioral instruction.

Recurring specifics. The pairing appears as "make fields optional or nullable" plus "instruct the model to return null for missing data". The instruction wording is specific: "only extract values explicitly present in the source" and "if a field's information is not mentioned, return null". The anyOf: [{type: 'string'}, {type: 'null'}] shape recurs for nullable fields.

Wrong answers written against this rule

Proposal. Make the field required and warn against fabrication.

Why it attracts. Seems symmetrical to Rule 9

Why it fails. Required forbids null, so the instruction cannot be obeyed

When it would be right. Never; contradicts the nullable premise

Proposal. Upgrade to a more capable model.

Why it attracts. Capability feels like the issue

Why it fails. More capable models still default to filling missing fields

When it would be right. Never; it is a prompt/schema design problem

Proposal. Add a second verification model to catch fabrications.

Why it attracts. Verification sounds rigorous

Why it fails. Expensive, adds a failure mode, treats the symptom

When it would be right. Never as the primary fix

Proposal. Rely on the nullable schema alone.

Why it attracts. Schema change is the obvious step

Why it fails. Model still defaults to generating a value

When it would be right. Insufficient by itself; needs the instruction

How the same rule gets re-asked
  • The examiner presents a two-sided problem: required causes fabrication, optional-alone causes missed present values. The correct answer is optional schema plus explicit "extract if present, null if absent" instruction. A mutation asks whether few-shot examples alone suffice; they help but the nullable schema is what makes null legal.
R11

A typed validation layer raises one machine-readable error naming the field and the broken rule

In a Python pipeline, a Pydantic model acts as the validation layer. Parsing enforces structure (types, required fields, enums); validators enforce semantics (cross-field arithmetic, date ordering). Both failure kinds surface through a single ValidationError whose machine-readable entries name the field location and the broken rule. That error string is formatted straight into the retry prompt as the third ingredient of retry-with-error-feedback.

example.py
python
from pydantic import BaseModel, ValidationError, model_validator

class Invoice(BaseModel):
    line_items: list[LineItem]
    stated_total: float

    @model_validator(mode="after")
    def totals_must_match(self):
        calculated = round(sum(i.amount for i in self.line_items), 2)
        if abs(calculated - self.stated_total) > 0.01:
            raise ValueError(
                f"line items sum to {calculated} but stated_total is {self.stated_total}"
            )
        return self

try:
    invoice = Invoice.model_validate(tool_input)
except ValidationError as e:
    errors = "\n".join(
        f"{'.'.join(map(str, err['loc'])) or 'invoice'}: {err['msg']}"
        for err in e.errors()
    )

The retry loop needs a specific, actionable error. A typed validator produces exactly that: a field path and a message stating what was expected versus what was found. This is the bridge between "validation failed" and "here is what to fix". The platform may enforce the schema; the validator enforces the business rules and formats the error the retry consumes.

Boundary. The boundary is structured error versus generic failure. The nearby opposite case is a validator that raises a bare exception with no field path; the retry then has no specific signal and behaves like a naive retry. The error must name the location and the rule.

Recurring specifics. The Pydantic model_validator (mode after) and ValidationError pair recurs. Error format is loc: msg, for example line_items.2.amount: ... or stated_total: .... The retry message template concatenates Original document, Your extraction, and Validation errors. The note "Pydantic simply supplies the third ingredient (the specific error) in a form you can format straight into the prompt" is explicit.

Wrong answers written against this rule

Proposal. Treat Pydantic as redundant once tool use enforces the schema.

Why it attracts. Tool use already validates

Why it fails. Schemas eliminate syntax, not cross-field semantic rules

When it would be right. Never; validators encode business rules

Proposal. Raise only a generic exception without field paths.

Why it attracts. Simpler to write

Why it fails. Retry gets no specific signal and reproduces the error

When it would be right. Never for retry-driven pipelines

Proposal. Use a JSON repair library to fix the schema errors.

Why it attracts. Feels automated

Why it fails. Repair libraries fix syntax, not semantic or enum violations

When it would be right. Only for pure JSON syntax, a different layer

Proposal. Switch tool choice to force the extractor.

Why it attracts. Forces the call

Why it fails. Does not produce a specific per-field error message

When it would be right. Never for this purpose

How the same rule gets re-asked
  • The examiner mutates whether the validator catches a structural issue (type mismatch, handled by parsing) or a semantic one (sum mismatch, handled by a validator). Both feed the same ValidationError. Another mutation tests whether tool-use schema alone removes the need for Pydantic; the answer is no, because semantic rules remain.
R12

Self-asserted correctness booleans from the same generation pass are unreliable; comparison must be deterministic and external

A schema that asks the model to set totals_match: true or conflict_detected: true during the same extraction pass that may have produced the error is self-assertion, not validation. The same pass that miscomputed the total can also mis-set the flag. The robust pattern is to have the model return both calculated_total and stated_total, then run an application-side step that independently compares them and sets the flag.

The model that made the arithmetic error has no special insight into whether its total is correct; asking it to judge its own work invites it to confirm its own mistake. A deterministic comparator (code, not model) is the only trustworthy arbiter because it recomputes from the extracted parts rather than trusting the model's verdict.

Boundary. The boundary is who computes the check. The nearby opposite case is a boolean the model sets by judgment (unreliable) versus a boolean a deterministic step sets after the fact (reliable). The pattern "keep both values in the schema, compare outside" is the correct design.

Recurring specifics. The unreliable variant appears as totals_match set by the model inside the extraction, or confidence enum used to hide a conflict. The reliable variant appears as discrepancy_detected or conflict_detected set by an application-side comparison. The phrase "never self-asserted by the same pass that may have produced the error" recurs.

Wrong answers written against this rule

Proposal. Have the model set totals_match inside the extraction.

Why it attracts. Keeps reconciliation in one call

Why it fails. The erring pass also sets the flag, defeating the check

When it would be right. Never; self-assertion is unreliable

Proposal. Have the model emit a confidence enum instead of a flag.

Why it attracts. Confidence feels informative

Why it fails. A single score hides which value was chosen and may be wrong

When it would be right. Inferior to explicit external comparison

Proposal. Route only records the model marked consistent.

Why it attracts. Efficient

Why it fails. The model may mark its own errors consistent

When it would be right. Never as the sole gate

Proposal. Auto-post whatever passes the model's own check.

Why it attracts. Fast

Why it fails. Unbalanced records get posted

When it would be right. Never; external check required

How the same rule gets re-asked
  • The examiner presents options where the comparison happens inside the model pass (wrong) versus in a deterministic external step (right). A mutation asks whether to block auto-posting on the externally-set flag; the answer is yes, block and route to review.
R13

Contradictory source data must be preserved and surfaced, never silently overwritten or auto-resolved

When a source document is internally inconsistent (for example, a bank statement whose own arithmetic is wrong, or a filing where assets do not equal liabilities plus equity), the pipeline must not silently correct the record into a single trustworthy value. It must extract and pass through both the stated value and the derived value, set a conflict flag, and let the contradiction reach human reconciliation. Coerion hides audit evidence and can mask a genuine source error.

The contradiction is a property of the source, not an extraction mistake. Recomputing or overwriting picks one side and destroys the other, so a later reviewer cannot tell the document was ever inconsistent. Preserving both values plus the flag keeps the tested material intact and routes the decision to a human who can consult the original.

Boundary. The boundary is source contradiction versus extraction error. The nearby opposite case is a single misread value (for example, one figure read from the wrong column); there, retry or deterministic recomputation corrects the extraction. But when both numbers are real and the document itself conflicts, correction is forbidden and surfacing is required.

Recurring specifics. The pattern "extract stated_X and calculated_X and set conflict_detected" recurs for bank statements, balance sheets, and customs entries. The phrasing "contradictory source data must be preserved and surfaced, not silently corrected" is explicit. A genuine arithmetic error in the source is the canonical reason to stop retrying (Rule 16) and to preserve both figures.

Wrong answers written against this rule

Proposal. Overwrite the declared total with the computed sum.

Why it attracts. Seems to "fix" the record

Why it fails. Coerces away the real contradiction and hides audit evidence

When it would be right. Never; preserve both

Proposal. Have a second model recompute and substitute.

Why it attracts. Feels like validation

Why it fails. Masks the conflict and adds a failure mode

When it would be right. Never; surface instead

Proposal. Retry until the numbers agree.

Why it attracts. Retry works elsewhere

Why it fails. The mismatch is in the source, not the extraction

When it would be right. Never for source contradictions

Proposal. Trust the stated figure alone.

Why it attracts. Simpler

Why it fails. Silently treats contradictory data as trustworthy

When it would be right. Never; flag it

How the same rule gets re-asked
  • The examiner mutates whether the inconsistency is a source defect (preserve and flag) or an extraction slip (retry). Another mutation asks whether to silently correct; the answer is no. A third pairs the conflict flag with a routing rule: the flagged record must block auto-posting.
R14

Classification gates before retry: route format or structural failures to retry, source-absent failures out of the loop

Before spending a retry, the pipeline classifies the failure as a retryable format or structural error (split cells, merged fields, misreads that clear on a later attempt) or a genuine source-absence case (a field the document never contains). Retryable cases are re-submitted with the specific error appended. Source-absent cases are routed to human review or emitted as null without further retries. This prevents burning attempts on documents that will never succeed.

result.json
json
{
  "errorCategory": "source_absent",
  "isRetryable": false,
  "description": "hazmat_class not mentioned anywhere in the manifest",
  "affectedFields": ["hazmat_class"]
}

Retries are only useful when the failure is self-correctable. Spending them on absent data wastes compute and latency and, worse, invites fabrication under a required schema. Classifying first ensures retries are spent only where they can recover, and absent cases escalate immediately.

Boundary. The boundary is retryable versus absent. The nearby opposite case is treating all failures identically (retry everything up to a cap); that recovers the fixable ones but wastes the bulk of the budget on the unfixable. The classification gate is what separates the two populations.

Recurring specifics. The split is described as layout_error or format_error versus absent_field or source_absent. Telemetry fractions recur: for example, 55% layout-related and 45% genuinely absent, with the absent class consuming a disproportionate share of compute. The action "retry only layout_error, route absent_field to human with the field marked null" is canonical.

Wrong answers written against this rule

Proposal. Retry every failed document up to three times.

Why it attracts. Uniform policy is simple

Why it fails. Wastes attempts on absent fields that never resolve

When it would be right. Only if you cannot classify; classification is better

Proposal. Route everything to human review.

Why it attracts. Safe

Why it fails. Abandons recoverable extractions and overloads reviewers

When it would be right. Never as a blanket rule

Proposal. Keep all fields required and retry with feedback.

Why it attracts. Required feels rigorous

Why it fails. Forces fabrication on absent fields and still fails

When it would be right. Never when fields are sometimes absent

Proposal. Make fields nullable but keep retrying all failures.

Why it attracts. Nullable helps

Why it fails. Still retries the unfixable class needlessly

When it would be right. Use classification to skip retries

How the same rule gets re-asked
  • The examiner mutates the telemetry mix (more layout versus more absent) to test whether you still classify rather than raise the global retry cap. Another mutation asks whether to make fields nullable and keep retrying; the answer is classify and skip the absent class. A third combines classification with a nullable schema for the absent case.
R15

Bounded retry with graceful fallback: cap attempts, then escalate to human review or emit null

The retry loop is bounded: a maximum number of attempts (commonly two or three) with the specific error appended on each. Once the cap is reached without a valid result, the document is escalated to human review or returned as a structured error (or a null where the schema permits), rather than retried forever or returned invalid to the user.

Unbounded retries waste cost and latency on unrecoverable cases and can block the pipeline. A cap with graceful degradation preserves system integrity: the automatable cases self-correct, and the residual hard cases reach a human or a clean error state instead of poisoning downstream systems.

Boundary. The boundary is bounded versus unbounded. The nearby opposite case is "retry indefinitely until it works"; that is explicitly wrong because some failures never resolve and indefinite looping wastes resources. Another opposite is "discard the document on first failure"; that abandons recoverable cases.

Recurring specifics. The cap is described as "max 3x" or "bounded retry + graceful fallback". The pattern "validate, targeted re-prompt with specific errors, retry, if still failing after N attempts fall back to human review" recurs. Compounding reliability is sometimes illustrated: a 5% failure rate with 95% retry success yields 99.75% overall, but a residual remains that must degrade gracefully.

Wrong answers written against this rule

Proposal. Retry indefinitely until success.

Why it attracts. Sounds thorough

Why it fails. Wastes compute on unrecoverable cases and can block the pipeline

When it would be right. Never; always bound

Proposal. Discard the document on first failure.

Why it attracts. Simple

Why it fails. Throws away easily recoverable extractions

When it would be right. Never; retry first

Proposal. Return the last invalid JSON to the user.

Why it attracts. Feels like a result

Why it fails. Propagates corruption downstream

When it would be right. Never; escalate or error cleanly

Proposal. Switch models and retry from scratch.

Why it attracts. Model change feels decisive

Why it fails. Often reproduces the same failure without addressing root cause

When it would be right. Only when the failure is a capability gap

How the same rule gets re-asked
  • The examiner mutates the cap value or asks whether to raise it when a field stays null. The answer depends on whether the null is an absence (escalate, do not raise the cap) or a fixable format error (cap already covers it). Another mutation asks for the fallback: human review or structured error, both acceptable; silent pass-through is not.
R16

Retry loops must fail fast and escalate on source-side defects rather than loop indefinitely

When the root cause of a failure is in the source document itself (a genuine arithmetic error introduced by the issuer, a contradiction in the text, or information that lives only in an external document), the retry loop should stop early and escalate. Continuing to retry cannot change a source defect and only consumes resources. Recognition of the defect type triggers immediate escalation or exception handling.

A retry re-prompts over the same input. If the input is the problem, no re-prompt helps. The loop's value is in correcting the model's handling of good input, not in fixing bad input. Failing fast on source defects protects throughput and prevents fabricated or forced values.

Boundary. The boundary is model error versus source defect. The nearby opposite case is a model reasoning mistake on a perfectly good document; there, retrying with error feedback is exactly right. The examiner contrasts "the mismatch comes from an inconsistency in the source" (stop retrying) with "the model missed a line item" (retry).

Recurring specifics. The "author list only given as 'et al.' pointing to an external document" scenario is a canonical fail-fast case. A genuine bank-statement arithmetic error is another. The principle "recognize when to fail fast and escalate instead of looping" is stated directly. Terminology includes "unbounded retries waste cost and latency on unrecoverable cases".

Wrong answers written against this rule

Proposal. Continue retrying indefinitely.

Why it attracts. Hoping it eventually works

Why it fails. Source defect never changes; waste compounds

When it would be right. Never for source defects

Proposal. Increase max_tokens assuming truncation.

Why it attracts. Token budget is a common knob

Why it fails. Mismatch is in the source, not the output length

When it would be right. Only for truncation, a different failure

Proposal. Switch to a different model.

Why it attracts. Different model might "infer"

Why it fails. Cannot create information absent from the source

When it would be right. Never for absence

Proposal. Route to a document-gathering step.

Why it attracts. Sounds productive

Why it fails. Correct only if the missing doc can be fetched; otherwise escalate

When it would be right. When the external doc is retrievable

How the same rule gets re-asked
  • The examiner mutates whether the missing information is retrievable from an attached document (route to a gathering step) or truly absent (escalate). Another mutation asks whether to surface ambiguous mapping rules for human confirmation before committing; the answer is yes when the intent is absent from the source.
R17

Tool-result errors need structured metadata: `errorCategory`, `isRetryable`, `description`, affected fields

When a validation or tool step fails, it should not return a uniform "validation failed" string. It should return a structured error response carrying an errorCategory (for example transient, validation, permission), an isRetryable boolean, a human-readable description of the specific failure, and the affected field names. This lets the caller make a deterministic decision: retry with backoff, tell the user the input is invalid, or escalate to a human.

result.json
json
{
  "isError": true,
  "errorCategory": "validation",
  "isRetryable": false,
  "description": "Order ID must be in format #NNNNN. Received: order-abc.",
  "affectedFields": ["orderId"]
}

A bare error message gives the caller no basis for action. One uniform string forces the caller to guess, often retrying the unretryable or escalating the retryable. Structured metadata turns error handling into a deterministic decision tree driven by data, not by model judgment over an opaque message.

Boundary. The boundary is typed, actionable error versus opaque text. The nearby opposite case is returning the error as ordinary-looking content with an embedded "validation failed" phrase; then both the model and downstream systems may treat it as a success. Explicit error flags with structured detail prevent that confusion.

Recurring specifics. The fields errorCategory, isRetryable, description, retryAfterMs, and affectedFields recur. Categories are transient, validation, permission. The antipattern "Operation failed" as the entire error body is the canonical wrong answer. The principle "signal failures explicitly and with structured detail, not as ordinary looking results" is explicit.

Wrong answers written against this rule

Proposal. Return a more detailed plain-language error string.

Why it attracts. Detail feels helpful

Why it fails. Still opaque to programmatic routing; no retryable flag

When it would be right. Insufficient; needs structured fields

Proposal. Have the tool retry internally before returning.

Why it attracts. Hides retries from caller

Why it fails. Conceals error context; wrong errors get retried

When it would be right. Only for purely transient, tool-internal retries

Proposal. Add an analyze_error tool to classify after the fact.

Why it attracts. Seems systematic

Why it fails. Analyzes the same useless message; doubles latency

When it would be right. Never; fix the data at the source

Proposal. Add few-shot examples teaching the model to interpret error text.

Why it attracts. Examples help elsewhere

Why it fails. All errors look identical, so no pattern to learn

When it would be right. Never when errors are uniformly opaque

How the same rule gets re-asked
  • The examiner mutates whether the error carries a retryable flag (correct) or a single generic string (wrong). Another mutation varies the category and asks for the right action per category. A third tests whether server-side retry for all errors is right (only for transient; harmful for validation).
R18

The MCP `isError` flag signals execution failure and must carry actionable detail, not a bare text string

In the Model Context Protocol, a tool result carries an isError boolean. When true, it tells the model that the tool execution failed during this call and the model should treat the content as an error case and decide next steps. The flag must be paired with structured, actionable content; a bare isError: true with "Operation failed" gives the model nothing to act on.

isError distinguishes a business-logic or runtime failure from a successful result. It is not a schema-validation error (those are caught before the tool runs) nor an API connection failure (those are HTTP-level). Its job is to let the model self-correct or escalate based on the error context. Without detail, the model cannot correct.

Boundary. The boundary is execution failure versus definition or connection failure. The nearby opposite case is a schema-validation failure, which happens server-side before the tool executes and is not what isError signals. Another opposite is an API connection failure, which returns an HTTP error, not a tool result with isError.

Recurring specifics. The isError field appears in MCP tool results with content describing the failure. The canonical correct body pairs isError: true with errorCategory, isRetryable, and a description. The wrong body pairs isError: true with content "Operation failed". The note "isError signals a runtime execution error, not a definition error" recurs.

Wrong answers written against this rule

Proposal. Return isError: false with text "validation failed".

Why it attracts. Keeps the call "successful"

Why it fails. Model and downstream may treat it as success

When it would be right. Never; signal the error explicitly

Proposal. Use isError to mean a schema-definition error.

Why it attracts. Schema issues are caught earlier

Why it fails. Misleads the model about the failure type

When it would be right. Never; schema errors are pre-execution

Proposal. Ignore the invalid argument and use defaults.

Why it attracts. Feels tolerant

Why it fails. Processes bad data silently

When it would be right. Never; validate and report

Proposal. Disconnect the client on invalid input.

Why it attracts. Drastic

Why it fails. Kills the session over a recoverable error

When it would be right. Never; return a structured error

How the same rule gets re-asked
  • The examiner mutates whether the tool returns a useful structured error or a bare flag. Another mutation asks what isError means among definition error, execution error, connection failure, and input-validation failure; only execution failure is correct. A third asks the model to retry on specific errors using the detail provided.
R19

Aggregate accuracy and aggregate pass rates mask per-segment and per-field quality problems

A single aggregate metric, such as "95% of extractions pass schema validation" or "95% overall accuracy", can hide that one document class or one field is far worse. Because that class is a small share of volume, its poor correctness is diluted in the average. The pipeline must track quality per document type and per field, and must distinguish schema-valid from actually correct.

Averages are dominated by the largest segment. A contract class at 70% actual correctness inside a 95% aggregate dominated by 98%-correct invoices is invisible to the average. Worse, schema-valid does not mean correct: a record can pass validation with the wrong value. The aggregate flatters the system while a segment silently fails.

Boundary. The boundary is aggregate versus segmented evaluation. The nearby opposite case is making the schema stricter so the weak segment also fails validation; that changes what fails but does not make extractions more correct, and it hides the real per-segment problem behind a shifted aggregate.

Recurring specifics. The "contracts are 10% of documents but only 70% correct versus 98% for invoices, hidden in a 95% aggregate" scenario recurs. The split "schema validation passing IS NOT correctness" is explicit. Per-type and per-field tracking, plus correctness-focused evaluation, are the prescribed remedies. Domain 5.5 calibration language appears in related scenarios.

Wrong answers written against this rule

Proposal. Make schema validation stricter so weak segments fail.

Why it attracts. Surfaces the problem in the metric

Why it fails. Does not improve extraction correctness; just shifts failures

When it would be right. Never as the fix; measure per segment instead

Proposal. Stop processing the hard document class.

Why it attracts. Removes the drag on the average

Why it fails. Abandons a valid use case

When it would be right. Never; fix via segmentation

Proposal. Trust the aggregate as representative.

Why it attracts. Simple

Why it fails. Masks the type-specific failure

When it would be right. Never when segments differ

Proposal. Raise a global confidence cutoff uniformly.

Why it attracts. Easy lever

Why it fails. Masks concentrated self-consistent-but-wrong errors

When it would be right. Only with per-segment calibration

How the same rule gets re-asked
  • The examiner mutates whether to act on the aggregate or to break out per-segment accuracy and calibrate thresholds per segment. The correct answer is per-segment calibration before reducing human review. A mutation pairs aggregate accuracy with internal consistency; both can miss concentrated errors.
R20

Confidence scores do not catch fabricated or silent-failure outputs and cannot flag what the model had no signal to suspect

A self-reported confidence score is the model judging its own certainty. Fabricated values are produced with high confidence, because the model is constructing a plausible value, not acknowledging uncertainty. Therefore confidence filtering catches genuinely uncertain extractions but passes fabricated ones. Separately, if the model has no signal that information is missing (for example, a continuation page it never received), no confidence score can flag the gap, because the model does not know to be uncertain.

Confidence reflects the model's introspected certainty, not ground truth. Fabrication is confident by construction. And confidence can only respond to a signal the model perceived; an absent page is invisible to it, so its confidence on the incomplete extraction may be high. Confidence is a useful routing signal for known-uncertain cases, not a detector for fabrication or unaware absence.

Boundary. The boundary is *calibrated confidence for review allocation versus confidence as a correctness guarantee*. The nearby opposite case is using a confidence threshold to auto-approve; that works for routing review to low-confidence fields but fails to catch high-confidence fabrications or unaware gaps.

Recurring specifics. The pairing "fabricated values are not low-confidence values" recurs. The "model has no basis to suspect a missing continuation page, so confidence cannot flag it" scenario is explicit. Field-level (not document-level) confidence plus a threshold calibrated against a labeled set is the right use for reviewer allocation. The consistency_confidence score variant is shown as inferior to explicit flagging.

Wrong answers written against this rule

Proposal. Filter out low-confidence extractions.

Why it attracts. Confidence feels like a quality gate

Why it fails. Passes high-confidence fabrications; catches the wrong population

When it would be right. For routing review, not for catching fabrication

Proposal. Use confidence to auto-approve above a threshold.

Why it attracts. Efficient

Why it fails. Masks concentrated high-confidence errors

When it would be right. Only with per-segment calibration and review

Proposal. Rely on confidence to detect missing pages.

Why it attracts. Seems proactive

Why it fails. Model may have no signal the page is missing

When it would be right. Never for unaware absence

Proposal. Treat a low confidence score as proof of fabrication.

Why it attracts. Intuitive

Why it fails. Fabrications are typically high confidence

When it would be right. Never; the direction is wrong

How the same rule gets re-asked
  • The examiner mutates whether confidence is used for review allocation (right) or as a correctness guarantee (wrong). Another mutation pairs confidence with a per-segment calibration set; the answer is calibrate per segment. A third asks whether confidence catches fabrication; the answer is no.
R21

Plausible-but-wrong silent failures escape retry logic and require semantic output-integrity validation

Some failures produce output that is well-formed, schema-valid, and confident, yet factually incorrect, with no error code raised. Because nothing errors, retry-on-error logic never fires and the bad output flows downstream. The only defense is a semantic validation gate that checks output integrity against the source or against business rules before release, independent of any error signal.

Retry logic is reactive: it triggers on an error. A silent failure emits no error, so the reactive machinery sits idle while wrong data passes. Semantic validation is proactive: it interrogates the content itself ("does this value actually follow from the source?") rather than waiting for a crash. That is the only layer that catches fluent, confident wrongness.

Boundary. The boundary is error-raising versus error-free. The nearby opposite case is a malformed-JSON or type-mismatch failure, which does raise and is caught by the normal retry loop. Silent failures are specifically the class that raises nothing, so they need a different control.

Recurring specifics. The phrasing "plausible but incorrect output with no error raised, so retry logic never fires" recurs. The control is described as "semantic validation of output integrity" or "validation gates checking output integrity before release". The failure class is named "silent failure". Related scenarios include corrupted-but-plausible outputs and confident-but-wrong summaries.

Wrong answers written against this rule

Proposal. More aggressive retry-on-error with backoff.

Why it attracts. Retry fixes other issues

Why it fails. No error is raised, so retry never triggers

When it would be right. Never for silent failures

Proposal. Larger context window.

Why it attracts. More context feels safer

Why it fails. Does not add an error signal or integrity check

When it would be right. Never as the control

Proposal. Higher reasoning effort.

Why it attracts. Effort feels like accuracy

Why it fails. Does not catch already-emitted wrong output

When it would be right. Never; needs a validation gate

Proposal. More few-shot examples.

Why it attracts. Examples help elsewhere

Why it fails. Does not intercept the silent wrong output

When it would be right. Never as the primary control

How the same rule gets re-asked
  • The examiner mutates whether the failure raises an error (retry works) or is silent (needs semantic validation). Another mutation pairs silent failure with aggregate-accuracy masking (Rule 19). A third asks what catches it; the answer is always semantic output-integrity validation.
R22

Transient API errors (429, 5xx) need exponential backoff with jitter and a maximum attempt cap

When the API returns a transient error (a 429 rate limit or a 5xx server error), the client should retry with exponential backoff: each delay grows (for example, 1s, 2s, 4s, 8s), a random jitter is added to avoid synchronized retries across many clients, and a maximum attempt count bounds the effort. This recovers from transient conditions without overwhelming the struggling service.

example.ts
typescript
async function withRetry(fn: () => Promise<Response>, max = 5): Promise<Response> {
  let delay = 1000;
  for (let attempt = 1; attempt <= max; attempt++) {
    const res = await fn();
    if (res.status < 500 && res.status !== 429) return res;
    if (attempt === max) return res;
    await sleep(delay + Math.random() * delay); // jitter
    delay *= 2; // exponential backoff
  }
  return fn();
}

Transient errors clear on their own given time. Exponential backoff spreads the load so the service can recover; jitter prevents the thundering-herd effect where many clients retry in lockstep and keep the service down; the cap prevents infinite loops that waste resources. Together they are the standard resilient pattern for transient failures.

Boundary. The boundary is transient versus permanent. The nearby opposite case is a 4xx client error (400, 401, 403), which is permanent and must not be retried; retrying it wastes attempts on a guaranteed failure. Another opposite is immediate or fixed-interval retries without jitter, which can amplify load.

Recurring specifics. The combination "exponential backoff combined with a jitter and a maximum retry cap" recurs. Status codes named: 429 (rate limit), 500/503/529 (server overloaded), 400 (bad request), 401/403 (auth). The phrase "unlimited retries worsen overload, immediate retry amplifies pressure" appears. Caps such as maximum 3 or 5 attempts and backoff capped at 32 seconds appear.

Wrong answers written against this rule

Proposal. Unlimited retries with a fixed 1-second delay.

Why it attracts. Simple

Why it fails. Worsens overload and can loop forever

When it would be right. Never; always cap and back off

Proposal. Immediate retry on every error without delay.

Why it attracts. Feels responsive

Why it fails. Amplifies pressure on a struggling service

When it would be right. Never for transient spikes

Proposal. Only linear backoff with no randomization.

Why it attracts. Linear is simpler

Why it fails. Lacks jitter, so retries can synchronize

When it would be right. Never; jitter is required

Proposal. Retry 4xx client errors the same way.

Why it attracts. Uniform handling feels consistent

Why it fails. 4xx are permanent; retries always fail

When it would be right. Never; do not retry 4xx

How the same rule gets re-asked
  • The examiner mutates the backoff parameters (cap value, jitter presence) and asks which combination is correct. Another mutation contrasts backoff (right for 5xx) with honoring Retry-After (right for 429), bridging to Rule 23. A third tests whether a single delayed retry or infinite polling is acceptable (both wrong).
R23

Error-specific retry: honor `Retry-After` for 429, backoff for 5xx, never retry 4xx client errors

Different error types demand different retry behavior. A 429 rate limit carries a Retry-After header that specifies exactly how long to wait; the client should wait that duration, then retry. A 5xx server error is transient and should be retried with exponential backoff and jitter. A 4xx client error (400 bad request, 401/403 auth) is permanent and must not be retried; it requires fixing the request or escalating.

Treating all errors the same wastes retries and ignores signal. The Retry-After header is an explicit server instruction; ignoring it either retries too early (worsening rate limiting) or waits too long (adding latency). 5xx will likely clear given backoff. 4xx will never succeed on retry because the request itself is the problem.

Boundary. The boundary is transient-and-retryable versus permanent-and-not. The nearby opposite case is retrying a 400 with backoff; the request is malformed and will fail identically, so the attempts are pure waste. Similarly, treating 429 like 5xx ignores the precise timing signal.

Recurring specifics. The mapping "429 plus Retry-After wait exactly, then resume backoff" recurs. 5xx uses "exponential backoff plus jitter". 400/401/403 are "do not retry". Error-type influence on backoff duration (using Retry-After when available) is explicit. The split "429 and 500 are transient and handled with exponential backoff; 400 is permanent" appears.

Wrong answers written against this rule

Proposal. Retry both 429 and 500 immediately with no delay.

Why it attracts. Uniform and simple

Why it fails. Intensifies rate limiting; creates thundering herd

When it would be right. Never; both need delay

Proposal. Treat 429 and 500 identically.

Why it attracts. Feels consistent

Why it fails. Ignores the explicit Retry-After signal from 429

When it would be right. Never; honor Retry-After for 429

Proposal. Retry only 429, treat 500 as permanent.

Why it attracts. 429 is clearly retryable

Why it fails. 500 is transient and should be retried

When it would be right. Never; 500 is retryable

Proposal. Retry 400 bad requests with backoff.

Why it attracts. Backoff helps 5xx

Why it fails. 400 is permanent; retries always fail

When it would be right. Never; fix the request

How the same rule gets re-asked
  • The examiner presents a mix of error codes and asks for the correct per-code strategy. Another mutation asks what to do with the Retry-After header (wait exactly that long). A third tests whether auth errors are retryable (no).
R24

Cross-field and temporal semantic checks (date validity, ranges, ordering) sit above schema validation

Beyond schema shape, a pipeline needs semantic or business-rule validation: a date that parses but is impossible (February 30), a value outside an allowed range or wrong sign, two dates whose ordering is violated (end_date before start_date), or a temporal inconsistency between an extracted date and contextual clues ("last week" relative to a different reference). These require application or validator logic, not JSON Schema.

JSON Schema validates static structure. It cannot know that 2025-02-30 is impossible, that a negative amount is invalid, or that endDate must exceed startDate, because those are relationships and real-world facts, not types. Cross-field and temporal checks are the semantic layer that catches "format-valid but meaning-wrong" output.

Boundary. The boundary is format validity versus value validity. The nearby opposite case is trusting a format check alone: a date that parses as YYYY-MM-DD passes schema validation yet is impossible. The semantic layer must add a validity check beyond the pattern.

Recurring specifics. Examples recur: effective_date of 2025-02-30 (format-valid, impossible), end_date preceding start_date, non-negative amount and range checks, and temporal consistency between extracted dates and article context. The phrasing "format validation only checks syntactic correctness; cross-field validation catches semantic errors" is explicit. JSON Schema cannot compare two property values (needs application logic).

Wrong answers written against this rule

Proposal. Validate the date format only.

Why it attracts. Format checks are easy

Why it fails. Misses impossible but well-formed dates

When it would be right. Never as the sole check

Proposal. Remove the timestamp field to avoid the problem.

Why it attracts. Avoids the error

Why it fails. Loses a useful field

When it would be right. Never; validate semantically

Proposal. Increase the confidence requirement for date fields.

Why it attracts. Confidence feels protective

Why it fails. Does not catch temporal inconsistencies

When it would be right. Never for this purpose

Proposal. Rely on schema type constraints alone.

Why it attracts. Schema already in place

Why it fails. Types do not enforce ordering or validity

When it would be right. Never as the sole layer

How the same rule gets re-asked
  • The examiner mutates whether the check is format-only (insufficient) or adds cross-field/temporal logic (required). Another mutation asks whether to silently correct an impossible date; the answer is flag for human review, do not guess. A third asks what JSON Schema can express (structure) versus what needs code (relations).
R25

Few-shot examples fix present-but-varied formats and ambiguous layouts; retries cannot create absent intent

When valid JSON is produced but fields are empty despite the information being in the source (because it appears in varied formats or ambiguous layouts), the fix is few-shot examples that pair a source with its exact expected output across those variations. The model generalizes the extraction judgment to novel templates. Retries cannot help here, because the prompt already failed to guide extraction and resending it reproduces the same empty result.

Detailed instructions plateau when the gap is generalization across layouts, not a correctable mistake on a known input. Few-shot examples show the target behavior directly, teaching the model to locate an unlabeled, split, or embedded field. Retries are reactive repair; they only work when the error signal exists and the value is recoverable. When the issue is guidance, examples are the lever.

Boundary. The boundary is present-but-varied versus absent intent. The nearby opposite case is a transform whose spec is silent on a mapping rule (multiple defensible readings, none in the source). There, few-shot cannot invent the intended rule; the correct move is to surface interpretations for human confirmation. Few-shot generalizes known intent, it does not create missing intent.

Recurring specifics. Examples pair "footnote billing, tiered committed amount, recital-buried renewal" with expected output. Optional or nullable fields plus a classification enum including "unclear" recur. Few-shot is prescribed for citations in inline versus bibliography formats and methodology across section types. The phrase "making ambiguous-layout handling consistent and generalizable to novel templates is the work of few-shot examples, not more schema constraints" appears.

Wrong answers written against this rule

Proposal. Make the missing fields required and retry.

Why it attracts. Required feels rigorous

Why it fails. Retry reproduces the empty result; required causes fabrication

When it would be right. Never here; guidance is the gap

Proposal. Add a regex post-processing layer.

Why it attracts. Automation feels robust

Why it fails. Brittle across varied formats, especially embedded prose

When it would be right. Never for complex structures

Proposal. Upgrade to a larger model.

Why it attracts. Capability feels like the fix

Why it fails. Does not teach the specific layout generalization

When it would be right. Never; it is a prompt-design gap

Proposal. Tighten schema format descriptions.

Why it attracts. Schema is the usual fix

Why it fails. Failures are learnable layout drift, not type errors

When it would be right. When failures are pure format, not layout

How the same rule gets re-asked
  • The examiner mutates whether the failure is present-but-varied (few-shot) or absent intent (surface for human confirmation). Another mutation offers few-shot for known templates only versus examples teaching generalizable reasoning; the latter is correct for novel templates.
R26

Partial re-prompting targets only the failed fields rather than regenerating the entire extraction

After validation identifies exactly which fields failed, the recovery request asks for only those fields, then merges the corrected values with the already-correct fields. This avoids re-extracting and risking the fields that were right.

Full re-extraction wastes computation on correct fields and can regenerate them incorrectly, discarding good work. Targeted re-prompting preserves successful extractions and focuses the model on the precise gap, which is both cheaper and more reliable.

Boundary. The boundary is targeted versus full. The nearby opposite case is a document that is short or where most fields are wrong; there, full re-extraction may be acceptable. But for multi-field extractions where one or two fields fail, partial re-prompt is clearly more efficient.

Recurring specifics. The pattern "validate output, identify exactly which fields failed, re-prompt for only the missing or invalid fields, merge with successful fields" recurs. Field-by-field validation enables the targeting. The contrast "full re-extraction wastes computation on fields already correctly extracted" is explicit.

Wrong answers written against this rule

Proposal. Regenerate the entire extraction from scratch.

Why it attracts. Clean restart feels safe

Why it fails. Wastes compute and risks re-breaking correct fields

When it would be right. Only when most fields are wrong

Proposal. Fill failed fields with null.

Why it attracts. Simple

Why it fails. Null may break downstream processing

When it would be right. Never as a fix

Proposal. Switch to a larger model.

Why it attracts. Capability feels relevant

Why it fails. Does not address occasional missing fields

When it would be right. Never; it is a routing issue

Proposal. Retry the identical full prompt.

Why it attracts. Uniform

Why it fails. Reproduces the same incomplete output

When it would be right. Never; target the gap

How the same rule gets re-asked
  • The examiner mutates whether to ask for only the missing fields (correct) or to regenerate everything (wrong). Another mutation pairs partial re-prompt with the three-part payload from Rule 1 for the targeted fields.
R27

Field-level confidence calibrated against a labeled set is the right reviewer-allocation signal for semantic errors that pass schema

When semantic errors pass schema validation and reviewer capacity is limited, the model emits field-level confidence scores. These are calibrated against a labeled validation set to find a threshold that routes the fields most likely wrong to human review, concentrating scarce review on the error-rich segment instead of random sampling.

Semantic errors are syntactically valid, so only a per-field signal can prioritize them. Calibration against ground truth turns raw confidence into a reliable router. Random sampling gives an unbiased rate estimate but wastes review on correct items; calibrated field-level routing targets the errors where they concentrate.

Boundary. The boundary is field-level calibrated versus document-level or uncalibrated. The nearby opposite case is reviewing only empty or explicitly "not found" fields; that catches a different, structural class and misses semantically wrong populated fields entirely.

Recurring specifics. The example shows field-level confidence 0.31 flagging a wrong quantity while 0.94 and 0.88 pass. A threshold around 0.65 routes roughly 20% of fields containing about 60% of errors. Required-empty checks target structural indicators, not semantic errors. The phrase "field-level confidence plus calibrated threshold equals targeted review allocation" recurs.

Wrong answers written against this rule

Proposal. Randomly sample 20% for review.

Why it attracts. Unbiased rate estimate

Why it fails. Distributes review without targeting errors

When it would be right. Good for measurement, poor for allocation

Proposal. Review only empty or not-found fields.

Why it attracts. Easy to implement

Why it fails. Misses semantic errors in populated fields

When it would be right. Targets a different error class

Proposal. Use document-level formatting heuristics.

Why it attracts. Heuristic feels predictive

Why it fails. Weak, static signal uncorrelated with semantic error

When it would be right. Never as the primary router

Proposal. Raise model effort or add examples.

Why it attracts. Helps extraction

Why it fails. Does not allocate limited review

When it would be right. Useful elsewhere, not for allocation

How the same rule gets re-asked
  • The examiner mutates field-level calibrated routing (correct) versus random sampling (wrong for allocation) versus empty-field checks (wrong class). Another mutation asks whether document-level confidence suffices; the answer is no, field-level is required.
R28

Failure-class tagging (`detected_failure_type`) makes retry routing and observability possible without changing extraction logic

The validation error payload carries a detected_failure_type (or detected_pattern) classifying each failure as MALFORMED_JSON, MISSING_FIELD, WRONG_TYPE, and so on. Logs can then route and aggregate by class, and the pipeline can distinguish fixable from absent failures for routing, all without altering the extraction prompt or model.

result.json
json
{
  "detected_failure_type": "MISSING_FIELD",
  "affectedFields": ["primary_endpoint"],
  "description": "primary_endpoint absent from the synopsis text"
}

A generic "validation failed" message makes a malformed-JSON error indistinguishable from a genuinely absent field, so the two cannot be routed differently and cannot be analyzed. Tagging makes each failure mode inspectable and routable, enabling the classification gate from Rule 14 and the observability the examiner rewards.

Boundary. The boundary is tagging for observability versus changing extraction. The nearby opposite case is logging only the raw output for later manual inspection; that preserves evidence but does not enable programmatic routing. Tagging is the machine-readable handle.

Recurring specifics. Values MALFORMED_JSON, MISSING_FIELD, WRONG_TYPE recur. The phrasing "add a detected_failure_type field classifying each failure so the team can route them correctly" is explicit. Tagging is described as solving the observability problem "without changing the core extraction logic".

Wrong answers written against this rule

Proposal. Increase the retry limit.

Why it attracts. Feels like more coverage

Why it fails. Does not distinguish the failure classes

When it would be right. Never for observability

Proposal. Send a different system prompt on the third retry.

Why it attracts. Feels adaptive

Why it fails. Adds noise; does not classify failures

When it would be right. Never as the fix

Proposal. Log only the raw output.

Why it attracts. Preserves evidence

Why it fails. No programmatic routing or aggregation

When it would be right. Partial; tagging is better

Proposal. Raise max_tokens before retries.

Why it attracts. Token budget is a knob

Why it fails. Unrelated to failure classification

When it would be right. Only for truncation

How the same rule gets re-asked
  • The examiner mutates whether to add detected_failure_type (correct) or raise retries (wrong) or log raw only (partial). Another mutation pairs tagging with the Rule 14 classification gate so routing becomes data-driven.
Invoice pipeline where totals diverge and purchase orders are invented
A production walkthrough with the reasoning chain made explicit.

A logistics team processes invoices with tool_use and a JSON schema but no semantic validation. The schema catches structural errors such as missing fields or wrong types but lets through semantic errors, and two production failure modes emerge. Line items do not sum to the stated total in 8 percent of invoices because a line item is missed, and the model fabricates plausible purchase order numbers for required fields when the source invoice has no purchase order at all.

The fix has three components. The schema adds calculated_total populated by summing line_items amount and stated_total read verbatim from the document as separate number fields, plus total_discrepancy true when they differ. This builds discrepancy detection into the schema with no external logic needed for the math check. purchase_order and payment_terms become nullable rather than required, allowing honest null returns when the source lacks them. A Pydantic validator enforces that calculated_total equals stated_total and raises a ValueError with the specific mismatch when they differ.

The retry loop catches the fixable errors. When the validator raises, the loop constructs a retry message containing the original invoice text, the failed extraction JSON, and the specific error Validation errors line_items sum to 450.00 but stated_total is 500.00, please re-extract ensuring all line items are captured. The model re-examines the source and self-corrects in one to two retries on most cases, because the specific error targets the missing line item.

The unfixable cases route to human review rather than infinite retry. When an invoice genuinely lacks a department name the loop recognises the field is null and the schema validates, so it flags the document for a human queue instead of retrying. A separate systematic loop tracks detected_pattern on dismissed findings, where a high dismissal rate for variable shadowing in nested scope indicates that pattern's prompt needs refinement rather than a generic quality problem, creating a durable improvement cadence beyond any single retry.

Distinctions that decide answers

ThisNot thisHow to tell them apart
Schema syntax errorsSemantic validation errorsSyntax is malformed JSON, missing required fields, wrong types, eliminated by tool_use with schemas. Semantic is correct JSON with incorrect values such as wrong sums or misplaced fields, requiring validation logic.
Retry with error feedbackNaive retryNaive resends the same prompt, retry with error feedback sends document, failed extraction, and specific error. The latter is dramatically more effective at producing corrected output.
Fixable errorsUnfixable errorsFixable is format mismatches, structural errors, misplaced values, and math errors. Unfixable is absent information, external data not in source, or unavailable knowledge. Retry only the first category.
Schema validationPydantic validationSchema enforces structure via JSON Schema, Pydantic enforces both structure and semantics via validators. Schema catches syntax, Pydantic catches arithmetic and business-rule violations.
Single total fieldcalculated_total plus stated_totalA single total hides discrepancies, the dual field plus total_discrepancy surfaces them inline, the schema redundancy pattern for math consistency.
External validation logicSelf-correction fields in schemaExternal recomputes checks after extraction, self-correction builds the check into the schema surfacing discrepancies without external recomputation for each document.

Traps

Retries always work for extraction failures

The tempting answer. Retry any extraction failure because retry is the default reflex when something fails.

Why it fails. Retries fix format mismatches, structural errors, misplaced values, and math errors, but they cannot produce information genuinely absent from the source, such as a department name the document never contains.

What is correct. Distinguish fixable from unfixable before retrying and route absent-data cases to human review or null when the schema allows it.

Retry without the specific validation error

The tempting answer. Retry by resending the same prompt or only the original document because the retry message is easier to build that way.

Why it fails. Without the specific error the model has no guidance and typically reproduces the same mistake.

What is correct. Include the original document, the failed extraction, and the specific validation error stating what was expected versus what was found.

Schema validation alone is sufficient

The tempting answer. Rely on schema validation because tool_use enforces the schema.

Why it fails. Schema validation catches syntax errors only, semantic errors such as wrong sums, misplaced values, and fabricated data remain inside valid JSON.

What is correct. Add semantic checks with Pydantic validators and route detected discrepancies through the retry loop.

Pydantic is redundant once tool_use enforces a JSON schema

The tempting answer. Drop Pydantic because the schema already enforces structure.

Why it fails. Schemas cannot express cross-field semantic rules such as sums that must match or dates that must be ordered, and validators produce the specific per-field messages the retry loop feeds back.

What is correct. Keep Pydantic validators for business rules and use their ValidationError loc, msg, and type directly in the retry prompt.

Retry indefinitely on the same input

The tempting answer. Keep retrying the same document because more attempts feel more thorough.

Why it fails. Beyond a budget of three the cost exceeds value and unfixable cases will never succeed regardless of attempts.

What is correct. Cap retries at three and route to human review on exhaustion.

Retry unfixable errors because they look similar to fixable ones

The tempting answer. Route any failure through the retry path because the retry infrastructure already exists.

Why it fails. Retries on absent information never succeed and waste API cost while the correct action would have been a null return or a human queue.

What is correct. Apply the fixable versus unfixable boundary before invoking retry, and treat absent data as a routing decision not a correction problem.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
Four validation strategies along speed, cost, and quality axes

Schema validation is very fast and cheap for structure, Pydantic semantic checks add business rules at similar cost, LLM-as-judge adds full semantic accuracy at the cost of an extra API call, and human evaluation gives ground truth at highest cost and slowest speed.

Validation Strategies
Parse versus validate and the role of detected_pattern

Parsing enforces structure at parse time, validators enforce semantics after parsing. The detected_pattern field per finding turns dismissal tracking into systematic prompt improvement by identifying which constructs are dismissed most often.

Schema Definition
Conflict detection as a required extraction pattern

When a source contains contradictory information such as payment due 30 days in one section and net 60 in another, extracting both plus conflict_detected true prevents the model from silently picking one.

Validation Strategies
Build it
Add semantic validation, the three-part retry, and the boundary test
  1. Define an extraction tool with calculated_total and stated_total, conflict_detected, and detected_pattern per finding so discrepancies surface inside the output shape.
  2. Implement validation logic for completeness, numerical consistency between calculated and stated totals, enum validity, and date ordering, each returning a specific expected versus found error message.
  3. Build the retry loop that on validation failure constructs a follow-up message containing the original document, the failed extraction JSON, and the specific validation error and sends it to the model.
  4. Test with five documents, two with fixable errors and three with unfixable absent information, and verify the loop retries only the fixable cases in one to two attempts while the unfixable cases route to human review.
  5. Log detected_pattern per finding and analyse which patterns are dismissed most frequently to prioritise prompt refinement, closing the systematic improvement loop.

Verify. Fixable arithmetic and misplacement errors self-correct with targeted errors, absent-data cases do not burn retries, and dismissal data points directly to the prompt that needs refinement next.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Task 4.514 min

Batch Processing

Use the Message Batches API for latency-tolerant work at 50 percent savings and keep blocking workflows synchronous, correlating every batch result through custom_id.

What you need to know

The Message Batches API is a cost optimisation tool with hard constraints that the exam tests directly, and you design around those constraints rather than working around them. The fixed properties are 50 percent cost savings compared to synchronous API calls, results that become available when every request finishes or at the 24 hour ceiling whichever comes first, no per-item latency promise even though most batches complete well inside an hour, and custom_id fields for correlating request-response pairs. These are not tuning knobs, they are the contract.

The matching rule is the single most tested concept from this task. The synchronous Messages API is for blocking workflows where someone or something is waiting for the result: pre-merge checks in CI, real-time code review feedback, or any workflow where developers are blocked pending completion. The Batch API is for latency-tolerant workflows where results are consumed later: overnight technical debt reports, weekly code audit summaries, nightly test generation runs, or batch document extraction. The exam presents a manager who proposes switching everything to batch for the cost savings, and the correct answer keeps blocking workflows synchronous while moving only latency-tolerant workflows to batch, because a 50 percent saving that breaks a merge gate is not a saving.

When designing batch schedules you must plan for the worst case, not the typical case. Results often arrive in well under an hour, but the platform guarantee is within 24 hours, so latency-tolerant designs must budget for the full window. If an organisation requires a 30 hour SLA for a report, the math is 30 hours minus 24 hours of processing equals 6 hours of buffer for collecting requests, validating inputs, and absorbing operational delay. The final batch must therefore be submitted at least 24 hours before the deadline, with submissions every 4 to 6 hours inside the buffer so a fresh batch is always in flight. Working backwards from the SLA is the pattern the exam tests, not polling for status.

A batch request is a single asynchronous unit with no interactive loop between your code and the model in the middle of it. Documentation confirms that tool use including server tools, multi-turn conversation history, system messages, vision, and extended thinking can all appear inside a batched request, so the naive claim that tools are unavailable is too strong. What is genuinely unavailable is a control flow where your own code inspects a tool result and then decides the next request, because nothing runs on your side until the batch ends. Any workflow whose next step depends on reading an intermediate result therefore needs separate requests, and if that decision must happen within seconds it needs the synchronous path. The unsupported request parameters are the ones that assume interactivity or per-request scheduling: streaming, fast-mode speed selection, stateful thread fields, cache and context routing hints, and a zero token ceiling.

Two production disciplines determine batch economics more than the per-token discount. First, sample set refinement before full submission: take 5 to 10 representative documents covering the range of formats, edge cases, and types, iterate on the sample refining prompts, examples, and schema until accuracy is high, then submit the full batch. A 90 percent first-pass success rate on 1000 documents means 100 retries, a 60 percent rate means 400 retries, four times the resubmission cost, so refinement is the highest leverage activity. Second, failure handling resubmits only the failures with targeted modifications, not the entire batch, with modifications such as chunking oversized documents, simplifying prompts for unusual structures, or adding format-specific few-shot examples, each retry batch reusing custom_id with a suffix so correlation stays unambiguous.

Request shape, limits, and correlation

The Message Batches API accepts up to 100000 requests per batch or 256 MB total request size, whichever limit is reached first. Each request carries a unique custom_id that is the correlation key to its eventual response, and the array of requests wraps parameters identical to a synchronous Messages call such as model, max_tokens, and messages. The batch creation response returns a batch identifier whose status can be queried and whose results are retrieved once processing completes.

Results iterate over the array and are checked per item. A successful item has type succeeded and carries the same shape as a synchronous response, a failed item has type errored with an error field. Failure handling filters by type, looks up the original document by custom_id, and constructs a retry batch containing only the failed items with targeted modifications. The per-request model and parameters can vary, so a single batch may contain heterogeneous requests as long as each has its own custom_id.

The matching rule and the 24 hour schedule math

Blocking workflows have someone waiting, latency-tolerant workflows consume results later, and the label determines the API. The exam probes this by naming overnight reports, weekly audits, and nightly runs as batch-eligible and pre-merge checks and real-time screening as synchronous-only, with a manager proposing to batch everything as the distractor. The correct answer preserves synchronous for anything blocking and reserves batch for anything tolerant, because batch has no SLA.

The 24 hour maximum is the planning number, not the observed typical latency. The schedule works backwards from the consumer deadline: consumer deadline minus 24 hours is the latest submission time for the final batch, with a buffer for collection and validation. Submitting every 4 to 6 hours inside that buffer keeps throughput high while respecting the guarantee, and a fallback that tries batch then switches to synchronous on timeout defeats the batch contract because latency cannot be predicted per item.

Cost leverage through sample refinement and targeted resubmission

Submitting a large unrefined batch feels fast but is the most expensive path. Refining on a small representative sample first raises first-pass success from the 60 percent range into the 90 percent range, and the difference on 1000 documents is 400 failures versus 100 failures that each require a retry batch. The cost of a few sample iterations is repaid in avoided resubmission.

Resubmission must be targeted, not wholesale. Oversized documents need chunking into line-item requests, documents with unusual structures need simplified prompts, and structurally varied documents need format-specific few-shot examples. Each pattern gets its own modification, and the retry batch carries the same custom_id convention with a retry suffix so a second failure can be traced to the original document without ambiguity.

Authoritative mechanism reference

The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.

Mechanism reference: Pricing and the 50 percent discount

The Message Batches API is priced at 50 percent of the standard Messages API price for the same model, applied identically to input tokens and output tokens and to any special token categories the model uses. The FAQ phrases it as "All usage is charged at 50% of the standard API prices. This applies to input tokens, output tokens, and any special tokens". The pricing table makes the arithmetic concrete: a model whose standard input price is 3 dollars per million tokens is listed at 1.50 dollars per million tokens on the batch path, and the same halving applies to output.

The discount is attached to the delivery path, not to the model. Choosing a different model changes absolute spend because base prices differ, but the fraction remains one half across every active model. Supported models are enumerated as all active models, and the retired-model rows are still shown at batch prices for completeness.

The discount stacks with prompt caching. The caching page notes explicitly that "These multipliers stack with other pricing modifiers such as the Batch API discount". The practical order is multiplicative. A token that qualifies for both the batch half-price and a cache read at 0.1 times base costs 0.05 times the original base input price. A token that is a cache write at 1.25 times or 2 times base on the batch path costs 0.625 times or 1.0 times base respectively. The page warns that because of high throughput and concurrent processing, batches may go slightly over a Workspace spend limit.

What is not billed is as important as what is. Per-request outcomes of expired and canceled produce no inference usage to charge, while succeeded and errored do, because the latter two represent work the model attempted. The reference page frames the pricing power correctly: batch is cost effective only when you do not need immediacy; fast mode is the opposite lever, a speed premium over the synchronous price, not a discount.

Ownership: pricing is owned by the platform. The SDK merely passes the requests array to POST /v1/messages/batches; the service determines the rate card by model and by path.

Mechanism reference: Batch size limits and the binding constraint

A single POST /v1/messages/batches call accepts at most 100,000 elements in the requests array and at most 256 MB of serialized JSON payload, whichever is hit first. The page repeats this twice, once in the header summary and once in the detailed limitations. The lesson restates the pair as the only two limits.

The binding constraint is whichever threshold is reached first. A payload of 200,000 small requests violates the count cap even though it may still be under 256 MB. A payload of 20,000 large requests each carrying a multi-page document can violate the byte cap even though it is well under 100,000 entries. Exceeding the byte cap returns 413 request_too_large. The correct response is to split the work into multiple batches. Splitting does not lose the discount, because each batch is independently priced at 50 percent.

Boundary behavior that matters in production:

  • Exactly 100,000 requests is accepted; 100,001 is rejected at the HTTP layer before any inference.
  • Exactly 256 MB is accepted within the usual JSON overhead accounting; the platform measures the wire payload size, not a token count.
  • Mixing request types does not change the limits. A batch can contain vision requests, tool-use requests, and multi-turn messages together, and the same pair of caps applies.
  • Rate limits apply orthogonally. Even under both size limits, the account may be subject to batches API HTTP limits and to per-workspace limits on the number of requests waiting to be processed.

Ownership: the wire limits are enforced by the API gateway. The application owns chunking logic that partitions a source dataset into count- and byte-bounded slices before calling batches.create.

Mechanism reference: Correlation identity: custom_id

Every element of requests carries two required members: custom_id and params. params is a standard Messages creation body: model, max_tokens, messages, and the optional system, tools, tool_choice, temperature, top_p, and similar fields except for the unsupported set listed below. custom_id is the correlation key.

Validation of custom_id is strict: it must match ^[a-zA-Z0-9_-]{1,64}$, meaning 1 to 64 characters, ASCII letters and digits plus hyphen and underscore only. No spaces, slashes, colons, dots, or other punctuation are allowed, and the empty string is rejected. A request that violates the pattern fails validation for that request. The platform validates params asynchronously: validation errors surface when processing of the batch has ended, not at creation time, so dry-running a single request shape with the synchronous Messages API is the recommended preflight.

Correlation is by value, not by position. The page notes to "Use meaningful custom_id values to easily match results with requests, since order is not guaranteed" and the best-practices repeat "order is not guaranteed". Batches are processed concurrently across shards and results are written as they complete, so the .jsonl result file may return invoice-043 before invoice-042 even when invoice-042 was submitted first. Parsing model output text to recover identity is fragile compared with joining on custom_id.

Naming discipline for production: use a stable, sanitized identifier derived from the source record and an attempt suffix for retries. The forensics file shows the pattern of replacing any character outside [a-zA-Z0-9_-] with underscore and slicing to 64 characters to preserve uniqueness while satisfying validation. A convention like {source}-{id}-retry-{n} preserves traceability across retries and lets audit logs join attempts to origins without touching model text.

Lifecycle of the identifier:

  • At creation, custom_id is stored alongside the request.
  • During processing, the service maintains the mapping.
  • At retrieval, each line of the .jsonl file repeats custom_id next to a result object of type succeeded, errored, canceled, or expired.
  • On selective resubmission, the caller chooses new custom_id values for retry attempts; reusing the original value in a new batch is legal but loses the attempt distinction, so a suffixed convention is preferred.

Ownership: identity is owned by the application. The platform guarantees that the value round-trips unchanged and is unique within the batch it was submitted in; it does not invent or repair a nonconforming value.

Mechanism reference: Lifecycle, timing, expiry, and retention

A batch lifecycle on the wire is observable through a small set of fields returned by POST /v1/messages/batches and GET /v1/messages/batches/{batch_id}:

result.json
json
{
  "id": "msgbatch_01HkcTjaV5uDC8jWR4ZsDV8d",
  "type": "message_batch",
  "processing_status": "in_progress",
  "request_counts": {
    "processing": 2,
    "succeeded": 0,
    "errored": 0,
    "canceled": 0,
    "expired": 0
  },
  "ended_at": null,
  "created_at": "2024-09-24T18:37:24.100435Z",
  "expires_at": "2024-09-25T18:37:24.100435Z",
  "cancel_initiated_at": null,
  "results_url": null
}

Field by field:

  • id is the batch handle, msgbatch_ plus an opaque suffix. It is the argument to retrieve, list, results, cancel, and delete.
  • processing_status is in_progress on creation and becomes ended once all requests have finished and results are ready. The page does not advertise intermediate polling states such as queued versus running; callers should treat any non-ended value as not yet retrievable.
  • request_counts breaks down the population by per-request outcome. While in_progress, most entries sit in processing; after ended, they distribute across succeeded, errored, canceled, and expired.
  • created_at and expires_at are 24 hours apart: expires_at = created_at + 24h.
  • ended_at is null until the batch reaches ended, then records when availability flipped.
  • cancel_initiated_at records when a cancel was requested, if any.
  • results_url is null until ended, then holds the download location for the .jsonl result file. The availability rule is that results_url becomes usable when all messages have completed or after 24 hours, whichever comes first.
  • The list endpoint supports pagination via limit and the has_more plus last_id as after_id pattern, with SDK auto-pagination.

Expiry and retention are two distinct windows anchored on different events:

  • Expiry is at 24 hours after creation. "Batches expire if processing does not complete within 24 hours". Any request still processing at expires_at moves to the per-request outcome expired rather than completing normally. The batch still reaches ended so that the caller can fetch results, but some lines will be expired.
  • Retention is for 29 days after creation. "Batch results are available for 29 days after creation. After that, you may still view the Batch, but its results will no longer be available for download" and the troubleshooting check is on created_at, not ended_at. The same section notes that you can delete a batch explicitly via DELETE /v1/messages/batches/{batch_id} and that to delete an in-progress batch you must cancel it first.

Timing note that consistently confuses planners: the page observes that most batches finish well inside the window, phrased as "most batches completing within 1 hour" and "many finish sooner". That observation does not create an SLA. The only promise that can be relied on for scheduling is the 24 hour ceiling and the "whichever comes first" availability rule. The lesson makes this explicit in its SLA planning section with the arithmetic of working backwards from a deadline.

Ownership: timing and state transitions are owned by the service. Polling and deadline arithmetic are owned by the application.

Mechanism reference: Per-request outcomes and billing

Each line of the result file carries a result discriminated union. The four terminal values are:

  • succeeded: the request completed and carries a standard Messages response in result.message. The message includes the usual content array, stop_reason, and usage block. This outcome is billed at batch price.
  • errored: the request failed validation or execution and carries result.error with a typed error object. Validation errors are surfaced here after asynchronous validation, which is why the page advises testing the request shape synchronously first. This outcome still represents attempted inference and is billed.
  • canceled: the request was part of a batch the caller canceled. No inference completes; no charge attaches to canceled work.
  • expired: the request was still not complete when the batch reached expires_at and was marked expired. Like canceled, it produces no inference output to bill.

The service also surfaces these counts aggregated at the batch level via request_counts. The split matters for reporting obligations: a product must report honest coverage as succeeded over total submitted, not as complete, when errored, canceled, and expired exist.

Batched requests are independent. The page emphasizes "The failure of one request in a batch does not affect the processing of other requests" and each request is "handled independently". Per-item error isolation is the mechanism that limits blast radius versus a batch-level failure model.

Ownership: outcome classification is owned by the service; error interpretation and retry policy are owned by the application.

Mechanism reference: Workspace scoping and access control

Batches are scoped to a Workspace. The limitations section and the FAQ each state that batches and their results are isolated within the Workspace they were created in and can only be accessed by API requests in that same Workspace or by users with permission to view Workspace batches in the Console. The Console exposes batches per Workspace at platform.claude.com/settings/workspaces/default/batches and the retrieval endpoint enforces the same isolation.

Isolation is per Workspace, not per API key alone. Two keys in the same Workspace can see each other's batches. Two keys in different Workspaces cannot, even within the same organization. Downloading batch results in the Console can be disabled at the organization or per-Workspace level.

Data retention for batch storage is that request and response data is held for up to 29 days after creation, and explicit deletion is via DELETE /v1/messages/batches/{batch_id}. The retention section adds that asynchronous processing requires server-side storage of both inputs and outputs until completion and retrieval.

Ownership: scoping is owned by the platform. The application must ensure that the API key it uses belongs to the intended Workspace and that Operator views respect the per-Workspace permission boundary.

Mechanism reference: What can be batched

The page enumerates the request shapes that can appear inside params. Almost any Messages request can be batched, including:

  • Vision: content blocks of type image or document content alongside text.
  • Tool use, including all server tools: web search, web fetch, code execution, MCP connectors, advisor, and tool search.
  • System messages: the system array or string as on the synchronous path.
  • Multi-turn conversations: an array of messages with interleaved user and assistant turns.
  • Extended thinking: the thinking configuration block and its interaction with caching and tool use.
  • Most beta features: the page adds "Most beta features" and calls out the extended output beta output-300k-2026-03-24 as supported only on the batch path, not the synchronous path. That beta raises max_tokens to 300,000 for the listed models and notes that a single 300k generation can take over an hour, so the 24 hour window still governs.

The design explicitly permits mixing types within one batch. One batch can contain a vision extraction request alongside a pure text classification request and a tool-using request, each processed independently.

Ownership: capability parity is owned by the platform. The application declares intent by populating params as it would on the synchronous path, minus the unsupported fields.

Mechanism reference: What cannot be batched

A small number of Messages parameters are not supported in batch requests. Including any of them returns a validation error:

ParameterWhy
stream: trueBatch results come back as a single file, not a stream.
speed (fast mode)Fast mode tunes synchronous latency, which does not apply to asynchronous batch processing.
store and previous_thread_event_id (Threads)Threads are stateful; batch requests are not.
cache_hint and context_hintRouting hints that apply to synchronous request scheduling only.
max_tokens: 0Cache pre-warming, not supported inside a batch because an ephemeral cache entry written during batch processing would likely expire before the follow-up request runs.
research_preview_2026_02: "active"Research preview mode is not available on the batch path.

Additional constraints from adjacent pages tighten the input contract: max_tokens must be at least 1 in a batched request, and cache and context routing hints are synchronous-only concepts that have no meaning for an asynchronous job.

Ownership: validation is owned by the service. The SDK surfaces the same check, but the service is authoritative because validation of params is performed asynchronously and reported at result time.

Mechanism reference: Server tools, the agentic loop, and pause_turn

The page contains a section titled "Server tools and the agentic loop" whose first sentence resolves the principal divergence: "All server tools (web search, web fetch, code execution, MCP connectors, advisor, and tool search) work in batch requests. The batch worker runs the same server-side agentic loop as the synchronous Messages API". That sentence directly contradicts the reference material that treated multi-turn tool calling as unsupported.

The batch loop differs from the synchronous loop in one documented tuning: because there is no open connection to maintain, the batch worker runs more iterations per turn than a synchronous request before it returns stop_reason: "pause_turn". A pause_turn result indicates the turn did not finish. The continuation pattern is to submit the paused assistant content in a follow-up request, either as a new batch request or as a synchronous request, following the pause_turn continuation pattern linked from the server-tools docs.

A related throttling note: the batch worker throttles web_search per organization so that highly concurrent batch processing does not exhaust the organization's web search rate limit. Throttled requests are retried automatically; the caller does not need to handle them, but very large web search batches may take longer to complete.

What remains absent is the interactive caller-in-the-middle loop where application code between tool calls inspects an intermediate result and decides the next prompt or tool set within the same request. A batch request is a single asynchronous unit. A workflow whose branch depends on reading a tool output mid-request still needs separate requests to carry the decision. That is the narrow, defensible reading of "no multi-turn tool calling within a single batch request": not that tool use is unsupported, but that caller-controlled iteration inside the request is not.

Ownership: the agentic loop and its batch-tuned iteration budget are owned by the platform. The pause_turn continuation decision is owned by the application.

Mechanism reference: Prompt caching inside batches, stacking, and TTL choice

Prompt caching is supported inside batches. The FAQ confirms "Yes, it is possible to use prompt caching with your Batches API requests. However, because asynchronous batch requests can be processed concurrently and in any order, cache hits are provided on a best-effort basis". The caching page repeats that batch cache hits are best effort and that the most cost effective pattern for shared prefixes is to use the 1 hour cache duration.

Mechanism: caching is enabled by placing cache_control: { type: "ephemeral" } on a content block or by using the top-level cache_control automatic mode. The cache is keyed by the exact token sequence of the prefix up to and including the marked block, with prefixes checked in the order tools, then system, then messages. Up to 4 breakpoints can be placed in one request; automatic caching consumes one of those slots.

Pricing inside batch stacks. The caching page states the multipliers as 1.25 times base for a 5 minute write, 2 times base for a 1 hour write, and 0.1 times base for reads in either case, and that these multipliers stack with the batch discount. The lesson prompt-caching restates the same table and the lesson caching-patterns repeats the TTL and cost rows.

The reason the 1 hour duration is the documented advice for batch is timing. The caching page notes that lifetime is measured from the start of the request that writes or reads the cache entry, not from the end of its response, and that time spent generating a response counts against the lifetime. The batch page adds that batches can take longer than 5 minutes to process and that discovery that max_tokens: 0 is not supported inside batch is explained by the fact that an ephemeral cache entry written during batch processing would likely expire before the follow-up request runs. The caching page quantifies the window: if a response takes 4 minutes to stream, a follow-up must start within about 1 minute of completion to hit a 5 minute cache, which batch concurrency and ordering make unlikely, so the 1 hour entry survives the asynchronous spread.

Best-effort semantics mean the platform does not guarantee that every request sharing a prefix will see a cache hit. The batch worker processes requests concurrently and in no defined order, so a prefix may be written by one shard after another shard has already read past the point where it could have hit. The 1 hour duration widens the window but does not convert best effort into guaranteed.

Invalidation is unchanged in batch: any change to the cached prefix, even one character, invalidates that breakpoint. Changing thinking parameters, including the effort setting, can invalidate cached prefixes because the thinking configuration is rendered into the prompt.

Ownership: cache storage and hit detection are owned by the platform. Prefix design, breakpoint placement, and TTL choice are owned by the application.

Mechanism reference: Rate limits, throughput, and spend control

The page notes that rate limits apply to both batches API HTTP requests and to the number of requests within a batch waiting to be processed, and that processing may be slowed based on demand and volume, which can cause more requests to expire after 24 hours. It also notes that usage of the batches API does not affect rate limits in the Messages API for HTTP-level limits, while the underlying inference load still contends on the same capacity.

A specific spend-control warning is called out: "Because of high throughput and concurrent processing, batches may go slightly over your Workspace's configured spend limit". The lesson message-batches-api expands this into practical mitigation: staggering large batches into chunks of roughly 1,000 to 2,000 requests, scheduling batches during off-peak hours, monitoring batch token consumption separately from synchronous consumption, and using separate keys for heavy batch workloads when appropriate.

Recent platform material also notes that throttling of web_search per organization inside batch is automatic and retried by the batch worker, so very large search-heavy batches may simply take longer rather than fail.

Ownership: HTTP rate limits and server-side throttling are owned by the platform. Submission pacing, chunk sizing, and Workspace spend-limit sizing are owned by the application and the platform operations team.

Mechanism reference: Cancellation, deletion, and the Console

A batch that has been submitted cannot be modified. The FAQ states "once a batch has been submitted, it cannot be modified. If you need to make changes, you should cancel the current batch and submit a new one. Note that cancellation may not take immediate effect".

Explicit deletion is via DELETE /v1/messages/batches/{batch_id}. The forensics file records that to delete an in-progress batch you must cancel it first, and that deletion removes the stored request and response data that would otherwise persist for the 29 day window. Listing and retrieval are Workspace-scoped and support pagination as described above.

The Console path for Workspace batch visibility is platform.claude.com/settings/workspaces/default/batches and downloading batch results in the Console can be disabled at the organization or per-Workspace level.

Ownership: state transitions and storage lifecycle are owned by the platform; operator approval for cancellation and retention compliance are owned by the organization.

Ownership map

Guarantee or decisionOwnerWhy the owner is correct
Discount fraction of 50 percent and per-model price cardPlatformThe batch path is priced by the service at request execution time.
Whether a batch is billed at all and at what rate (succeeded and errored versus canceled and expired)PlatformBilling attaches to inference attempts marked by per-request outcome, which only the service can classify.
Enforcement of the 100,000 count cap and the 256 MB byte cap with the whichever-first rulePlatform gatewayThe gateway validates payload size and array length before enqueueing and returns 413 request_too_large on violation.
Validation of custom_id against ^[a-zA-Z0-9_-]{1,64}$Platform gateway, asynchronously for paramsThe pattern is enforced at creation; deep validation of params is reported at result time after asynchronous checks.
processing_status transitions from in_progress to ended and the expires_at = created_at + 24h clockPlatformThe batch lifecycle is driven by the queue and worker pool, not by client code.
The "results available when all finish or at 24 hours whichever comes first" availability rulePlatformAvailability of results_url is gated by worker completion and the expiry timer.
Per-request outcome classification into succeeded, errored, canceled, expired and the request_counts aggregationPlatform workerEach request is processed independently and classified by the worker that handled it.
Execution of the server-side agentic loop and the batch-tuned iteration budget before pause_turnPlatform workerThe platform notes that the batch worker runs the same loop as synchronous but with more iterations per turn.
Throttling and automatic retry of web_search per organization inside batchPlatform workerDescribed as batch-worker throttling that retries automatically without caller handling.
Workspace scoping and Console visibilityPlatform identity and accessIsolation is per Workspace, enforced by the auth layer.
Result retention for 29 days after created_at and explicit deletion via DELETEPlatform storageThe retention window and the delete endpoint are storage contracts.
Rate limiting of Batches API HTTP calls and of requests waiting to be processed, plus spend-limit enforcementPlatform gateway and billingThese are gateway and billing constraints, with the caveat that high-throughput batches may slightly overshoot Workspace spend limits.
Support for the extended output beta output-300k-2026-03-24 only on the batch pathPlatform gateway and model servingThe beta is explicitly called out as batch-only and unavailable on the synchronous path.
Choice of custom_id values and retry identifier conventionApplicationCorrelation identity is client-chosen and must satisfy the character rule; retry naming is a product concern.
Chunking a source dataset into count- and byte-bounded batchesApplicationOnly the producer knows document sizes and can partition before calling batches.create.
Polling cadence for processing_status and fetching the .jsonl file after endedApplicationPolling observes progress but does not accelerate it; the app decides how often to check.
Deadline arithmetic that works backwards from a consumer deadline to decide latest submission and cadenceApplicationThe organization commits to the SLA; only it can trade buffer against freshness.
Failure triage that maps per-request error types to targeted modifications and selective resubmissionApplicationOnly the domain pipeline knows why a document failed and what remediation applies.
Sample-set refinement before the full batch to raise first-pass successApplicationPrompt iteration on a small representative set is a product engineering step.
Prefix design, breakpoint placement, and the 1 hour versus 5 minute TTL choice for shared-context batchesApplicationCache keying is by prefix content; only the producer controls what is stable versus variable.
Reporting honest coverage as succeeded over total when some outcomes are not succeededApplicationDownstream consumers depend on an accurate fraction; the platform emits the raw counts but does not choose the reporting language.
SDK transport to POST /v1/messages/batches and GET /v1/messages/batches/{id}SDKThe SDK shapes custom_id plus params and the polling loop but delegates enforcement to the service.

Version and terminology currency

Several surface changes have accumulated since earlier revisions of the exam guide and since the docs host redirect. A candidate who only remembers the old phrasing will still be correct on substance but should recognize the current terms in the Console and in code.

  • Docs host and citation form. The docs.claude.com host now redirects to platform.claude.com. The current canonical form for the pages used here is https://platform.claude.com/docs/en/build-with-claude/batch-processing and https://platform.claude.com/docs/en/build-with-claude/prompt-caching. Cite the platform host even when older lesson references show the previous host.
  • Message Batches versus Message Batches API. The header on the page is now "Message Batches API" and the type discriminator in the JSON example is "type": "message_batch". The older guide phrasing "Message Batches API" and the JSON key processing_status have not changed, so body code that checks processing_status === "ended" is current.
  • Batch versus Messages store and thread fields. The unsupported-params table now explicitly names store and previous_thread_event_id as Threads concepts that are stateful and therefore incompatible with batch, which is stateless. An older guide that only said "no multi-turn support" was imprecise: multi-turn messages arrays are supported, while stateful thread carry is not.
  • Fast mode speed. The table calls out speed (fast mode) as a synchronous latency tuning knob that does not apply to the asynchronous batch path. The forensics file frames fast mode and batch as mutually exclusive and oppositely priced. A candidate should not propose fast mode as a latency control for a batch.
  • Cache pre-warming with max_tokens: 0. The batch page now explains the rejection: max_tokens: 0 is cache pre-warming and an ephemeral cache entry written during batch processing would likely expire before the follow-up request runs. This is the same timing argument that motivates the 1 hour cache duration for shared-prefix batches.
  • Routing hints. The pair cache_hint and context_hint appear in the unsupported table as synchronous-only routing hints. They were not mentioned in the earlier task statement.
  • Research preview flag. The single research preview flag research_preview_2026_02: "active" is now listed as unsupported on batch.
  • Extended output beta. The section "Extended output (beta)" introduces output-300k-2026-03-24, available only on the batch path, not on the synchronous path, with a max_tokens cap of 300,000 for the listed frontier models. An earlier guide that omitted this would not be wrong, but would be incomplete when the task involves long-form generation inside batch.
  • API version header. The code examples show anthropic-version: 2023-06-01, which remains the required header for raw HTTP; the CLI form ant messages:batches create is the newer console path but the same resource.
  • JSON output guarantees versus tool use. The adjacent structured-outputs notes record that strict tool use with strict: true on a tool now provides grammar-constrained sampling for tool names and inputs, that structured outputs can alternatively be delivered through output_config.format with a JSON schema, that the older top-level output_format field and beta headers are accepted only transitionally, and that a refusal still returns stop_reason: "refusal" with a 200 status and billed tokens even when a schema is requested. Batch can carry either mechanism; they are independent and composable.
  • Cache TTL terminology. The caching page now describes both automatic caching via top-level cache_control and explicit breakpoints via per-block cache_control, with the same pricing multipliers and the 20-block lookback note, and clarifies that generation time counts against lifetime. Older lessons that only described per-block markers are still correct for batch but should be read alongside the automatic mode.

Official versus community divergence

Two divergences between widely circulated community reference material and the live documentation require the candidate to answer from documentation and to name the divergence explicitly when explaining the answer.

Official versus community divergence: Divergence 1: tool use and multi-turn inside batch

Community material, and the current site page's simplified summary, state that the batch API supports no multi-turn tool calling within a single batch request and treat that as a hard constraint that forces a synchronous step for any tool-using workflow. The exam forensics file flags this as the most tested tension and the DOC-URLS notes call it out as divergence 1.

The live documentation states the opposite at the mechanism level. The "What can be batched" enumeration lists tool use, including all server tools (web search, web fetch, code execution, MCP connectors, advisor, and tool search), system messages, multi-turn conversations, vision, extended thinking, and most beta features as supported. The server-tools section then confirms execution: "All server tools work in batch requests. The batch worker runs the same server-side agentic loop as the synchronous Messages API" and describes the pause_turn continuation pattern that applies to batch.

The defensible reconciliation, captured in the DOC-URLS notes, is that documentation wins and the correct statement is that tools are supported but the batch request is a single asynchronous unit with no interactive loop where caller code inspects an intermediate result mid-request and decides the next input. A workflow whose branch or next tool set depends on reading a tool result in caller code still needs separate requests, which is a different claim from tools being unsupported. The forensics file frames exactly this nuance as the documented position: tool use is batchable, the genuinely unsupported parameters are streaming, fast-mode speed, stateful thread fields, cache and context routing hints, and a zero token ceiling.

Exam strategy: when a question asks whether batch can run a tool-using workflow, answer yes when the workflow is self-contained within the server-side loop and requires no caller inspection mid-request. When a scenario describes a controller that reads a tool result, chooses a follow-up tool, or decides to branch, answer that the controller step must be outside the batch, with pause_turn continuation as the documented bridge. Note the divergence explicitly when the question stem repeats the older "no tool calling in batch" framing.

Official versus community divergence: Divergence 2: the 24 hour story, typical latency, and retention

Community material often states only that the window is up to 24 hours with no SLA and that results may arrive within that window. The live page supplies a fuller set: most batches finish in less than 1 hour but the ceiling remains 24 hours and is the only SLA-relevant bound, results become available when all requests have completed or after 24 hours whichever comes first, batches expire at 24 hours if not complete, and results remain downloadable for 29 days after creation (not after ended_at).

The divergence is one of completeness rather than contradiction. The DOC-URLS notes instruct that grounding must use the fuller set. For scheduling, the actionable form is: plan to the 24 hour ceiling, treat the 1 hour typical as an empirical observation that does not create a promise, and anchor both expiry and retention calculations to created_at. Troubleshooting guidance that checks 29 days since ended_at is incorrect per the page: "Ensure that it has been less than 29 days since batch created_at (not processing ended_at) time". A candidate who internalizes the shorter community form will under-specify expiry and retention and will be fragile to questions that probe the "whichever comes first" availability rule and the post-expiry expired outcome.

Beyond the task statement

The task statement frames batch as a discount versus latency trade with a small set of constraints. Four adjacent lessons add depth that the statement omits entirely. Each entry below names the lesson slug that carries the adjacent topic, what the topic is, and why it matters for batch design.

Beyond the task statement: Prompt caching: mechanism, breakpoints, and TTL economics

The lesson prompt-caching documents the full prompt caching contract: marking stable prefixes with cache_control: { type: "ephemeral" }, the 5 minute default TTL versus the 1 hour extended TTL, the cost math of 1.25 times base for a 5 minute write, 2 times base for a 1 hour write, and 0.1 times base for reads, the exact-prefix matching that invalidates on any change, the up-to-4 breakpoint layered model from most stable to least stable, the cacheable minimums per model, and the guidance to always cache system and tool definitions. The companion lesson caching-patterns repeats the TTL and cost table and adds the diagram of tiers per request.

Why it matters for batch: the batch page grants that prompt caching works inside batch but that hits are best effort because requests are processed concurrently and in arbitrary order. The caching page then explains that lifetime is measured from request start and that generation time counts against it, so a single long generation can consume most of a 5 minute window before any follow-up could start. That timing is exactly why the batch page advises the 1 hour duration for shared-prefix batches: the 5 minute entry written inside a batch will usually expire before a subsequent batch that reuses the same prefix. Batch plus caching also stacks discounts, so a shared 30k token system prompt that appears in every request of a batch is the standard high-ROI pattern for a shared-prefix batch.

Cross-link: a batch whose requests share no large stable prefix benefits little from caching; a batch built around one large instruction book plus per-document variable content benefits greatly, provided the 1 hour duration is chosen and the prefix is identical byte-for-byte across the batch.

Beyond the task statement: Caching patterns: tiers, storage, and application-level equivalents

The lesson caching-patterns broadens prompt caching into a pattern catalog: TTLS and costs, automatic invalidation on TTL expiry or content change or marker shift with no manual invalidation, response caching at the application layer for deterministic idempotent operations, distributed caching with a shared key-value store, cache-aside versus write-through, cache key design with normalization, and tool result caching with short TTLs inside agentic loops.

Why it matters for batch: batch is not a substitute for response caching. When the same deterministic extraction is requested twice within days, the cheapest path may be to avoid the API call entirely via a content hash key in a local or distributed cache, rather than to batch it again at half price. The lesson also frames the hierarchy that validates the batch recommendation to keep the stable book in the cached prefix and the variable document after the breakpoint. Candidates who conflate batch with response caching will reach for batch where a local cache lookup would have sufficed.

Beyond the task statement: Validation pipelines: multi-stage checks and retry with fix

The lesson validation-pipelines describes a five-stage pipeline of schema, format, semantic, cross-field, and external validation, with Zod or JSON Schema for stage one, custom rules for the later stages, typed failure results that force callers to handle invalid data, and a retry-with-fix loop that feeds validation errors back to the model as a new user turn rather than blindly retrying with the same prompt.

Why it matters for batch: each batch request that asks the model to produce structured output should be guarded by a pipeline after its succeeded result is downloaded, not just by schema validation at creation time. The forensics file ties this to batch failure handling: targeted modification by failure cause beats blind retry, and tracking failure rates by document type distinguishes systematic prompt or schema issues from transient noise. The validation lesson also caps retries at 2 to 3 attempts before routing to human review, which maps directly onto the batch pattern of resubmitting only the failed custom_id set with a modification and then stopping.

Cross-link: the structured-outputs lessons add that strict tool use with strict: true and output_config.format with a JSON schema are the platform's server-side counterparts to client-side validation, and that even those can be preempted by a refusal with stop_reason: "refusal" or by a token-limit truncation. A batch that depends on schema compliance should therefore still validate client side.

Beyond the task statement: Sampling, token management, and error handling (supporting lessons)

The lessons token-management, sampling-parameters, and the error-handling pages support two batch-adjacent decisions. First, each batched request must have a reasonable max_tokens that fits within the model's context window together with its input; a single over-long request can fail the batch line item and a conservative cap avoids truncated structured outputs. Second, batched work shares the same rate-limit pool as synchronous work in the lesson's characterization, so large batches must be staggered and monitored to avoid blocking interactive traffic, a pattern the message-batches-api lesson spells out with ITPM tables. Third, batch errored lines carry typed error objects that the API errors page and the lesson's error handling tables help classify as retryable versus fatal, which informs the selective-resubmission logic.

Worked production examples: Example A: nightly contract extraction for the morning review queue

A legal operations team processes the new contracts landed in the previous 24 hours. Each night between 5,000 and 12,000 documents arrive, each averaging 2,500 tokens of input. A human review queue expects extracted parties, effective date, term length, and renewal clause in normalized JSON by 09:00 local time the next morning. No one is blocked waiting for an answer overnight, but the queue must be accurate and complete enough to trust.

The pipeline owner chooses a shared-prefix batch. The system prompt of roughly 8,000 tokens combines role instructions, the JSON schema description, and three few-shot examples. That prefix is identical for every request and is marked with a cache breakpoint using the 1 hour TTL, because batch workers process requests concurrently and asynchronously, which makes 5 minute hits unreliable. The per-document variable content follows the breakpoint. This choice stacks discounts: each document's read of the shared book costs 0.1 times base on top of the 0.5 times batch factor, for an effective 0.05 times base on the shared portion.

Submission cadence is derived by working backwards from the 09:00 deadline. The live page says results are available when all requests have completed or after 24 hours whichever comes first, and that batches expire at 24 hours. The owner therefore requires that the last batch be submitted no later than 09:00 minus 24 hours, which is 09:00 the previous day. In practice the team accumulates documents until 03:00, submits a single batch at 03:00 for same-morning review (relying on the empirical most-finish-in-less-than-an-hour behavior for freshness), and retains a contingency window: if the batch has not reached ended by 07:00, a synchronous salvage path extracts the highest-priority subset interactively while the batch continues. No SLA is violated because the contingency was budgeted inside the 24 hour ceiling rather than assumed from the typical case.

Correlation is keyed by custom_id as contract-{source_doc_id} sanitized to ^[a-zA-Z0-9_-]{1,64}$. At retrieval, the application branches per line of the .jsonl file: succeeded lines go through the five-stage validation pipeline, errored lines are grouped by error.type for targeted fixes, expired lines are treated as retryable in a follow-up batch, and canceled lines only appear if an operator canceled the job. Only the failed custom_id values are resubmitted, with document chunking for context-length failures and format-specific few-shot augmentation for structure failures, and each retry is suffixed as contract-{id}-retry-1 to preserve traceability. Coverage is reported as succeeded over submitted, never as complete when errored or expired remain.

Failure avoided: assuming order equals correlation, which would misattribute extractions to the wrong contracts when shards complete out of order, and assuming the typical 1 hour latency is a promise, which would have produced a silent SLA breach on a tail night.

Worked production examples: Example B: platform scale classification of half a million historical tickets

A support platform needs to classify 520,000 historical tickets for routing, using the same classifier prompt across all items. The documents are independent, no downstream step filters the stream mid-batch, and consumers read aggregate slices the next day. This is the canonical latency-tolerant workload.

The count cap governs. One batch caps at 100,000 requests, so the job requires at least six batches (five full at 100,000 and one at 20,000) even before considering the 256 MB bound. The application chunks the dataset into count- and byte-bounded slices and submits them sequentially through batches.create, reusing the same shared-prefix cache entry across batches via the 1 hour duration to keep the cost of the classifier preamble low. Each chunk is independently priced at 50 percent; chunking does not lose the discount. Submission is spread across hours to respect the Batches API HTTP and waiting-request rate limits and to avoid slightly overshooting a Workspace spend limit in one burst.

Result retrieval fans out. Each batch yields its own .jsonl file, each line joined on custom_id to the ticket database. Per-item error isolation means a transient validation failure for ticket t-88219 does not affect neighboring tickets. After all six batches reach ended, the application collects every succeeded line, refines the prompt on a sample of the errored set to raise first-pass rate, and then resubmits only those failed custom_id values in a seventh small batch with the prompt fix applied. Honest coverage is reported as 519,100 succeeded over 520,000 submitted after the retry, with the remaining 900 flagged for human review rather than silently dropped.

Failure avoided: sending all 520,000 in one batch, which would be rejected at the gateway with 413 or validation error, and retrying entire batches instead of the failed custom_id subset, which would repay inference cost for work already succeeded.

Worked production examples: Example C: soil report extraction with progressive prompt refinement

An agricultural analytics pipeline processes 8,000 soil reports arriving over a week. Early reports are structurally messy and the initial extraction prompt achieves only about two-thirds first-pass success. The team has a fixed downstream deadline for the final aggregate, so completion time and cost both matter, but no interactive consumer blocks on any single report.

Instead of submitting all 8,000 at once, the team adopts the progressive sequential pattern flagged in the forensics: batches submitted in sequence with prompt refinement between submissions improve first-pass rate over time. The first batch of 1,000 is submitted as a sample. After it reaches ended, the team triages its errored lines by cause: a cluster of context-length failures, a cluster of unusual table layouts, and a small random tail. Before the next batch, the prompt is refined with a chunking hint for long documents and a format-specific few-shot example for the table variant. The second batch of 2,000 then achieves a materially higher succeeded rate, and the third and fourth batches repeat the loop with diminishing returns.

This pattern illustrates the largest cost lever the task names: sample-set refinement before scale. Each round of refinement is paid once in prompt engineering effort and then amortized over the remaining collection at batch price. The cadence is still governed by the 24 hour ceiling: each intermediate batch could take up to 24 hours, so the team's week-long arrival schedule provides ample slack versus a single tight SLA. If instead the aggregate were due 30 hours after the last report arrives, the final batch would again be worked backwards from that deadline, reserving 6 hours of buffer for retries.

Failure avoided: burning batch budget on an untested prompt at full scale, which would create a large errored tail requiring either global trimming or expensive blind retry at batch price, both of which blind-retry remediation loses to targeted fixes.

Build exercise material

The exercises below are sized to run in roughly 45 minutes each. Each step states what to do, the observable outcome that proves it worked, and the check that must pass before moving on. All commands and snippets assume the Node SDK form; raw HTTP shapes are included where the wire contract matters more than the SDK shorthand.

Build exercise material: Exercise 1: batch creation with correlation identifiers that satisfy the character rule

Goal: produce a valid POST /v1/messages/batches payload whose every custom_id conforms to ^[a-zA-Z0-9_-]{1,64}$ and whose per-document mapping is lossless.

Steps:

  1. Create 20 sample documents with source ids that deliberately violate the allowed alphabet, for example invoice/2024-042, contract:Q1_19, and soil.report.003. Keep the original id alongside the content in documentsById.
  2. Implement the sanitizer toCustomId that replaces any character outside [a-zA-Z0-9_-] with underscore and slices to 64 characters, and derive each custom_id as doc-{sanitized}-v0. Validate the resulting string against the regex before submission.
  3. Build the batch payload of 20 requests entries, each with model, max_tokens at least 1, and messages, and each with the sanitized custom_id. Confirm that the payload is under 256 MB by measuring JSON.stringify(payload).length.
  4. Dry-run a single request shape of the same params through client.messages.create synchronously first. Confirm a 200 response with a content array, which proves the shape is valid before asynchronous validation at batch result time.
  5. Submit via client.messages.batches.create.

Observable outcome: POST returns 200 with "processing_status": "in_progress", a non-null id prefixed msgbatch_, and expires_at exactly 24 hours after created_at.

Verification: assert requests.length === 20, assert every custom_id matches ^[a-zA-Z0-9_-]{1,64}$, and assert client.messages.batches.retrieve(batch.id).processing_status is in_progress within 30 seconds.

example.ts
typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

function toCustomId(raw: string, attempt = 0): string {
  const base = `doc-${raw}-v${attempt}`;
  return base.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
}

const documentsById: Record<string, string> = {
  "invoice/2024-042": "Extract fields from invoice 042 ...",
  "contract:Q1_19": "Extract parties and dates from contract Q1_19 ...",
};

const allDocs = Array.from({ length: 20 }, (_, i) => ({
  rawId: `doc-${i}/variant:${i}`,
  content: `Document ${i} body text for extraction.`,
}));

const requests = allDocs.map((doc) => ({
  custom_id: toCustomId(doc.rawId, 0),
  params: {
    model: "claude-sonnet-4-6",
    max_tokens: 4096,
    messages: [{ role: "user" as const, content: `Extract fields as JSON from:\n\n${doc.content}` }],
  },
}));

for (const r of requests) {
  if (!/^[a-zA-Z0-9_-]{1,64}$/.test(r.custom_id)) {
    throw new Error(`Invalid custom_id: ${r.custom_id}`);
  }
}

const singleShapeCheck = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 4096,
  messages: [{ role: "user", content: requests[0].params.messages[0].content }],
});
if (!Array.isArray((singleShapeCheck as unknown as { content: unknown[] }).content)) {
  throw new Error("Dry run did not return expected content shape");
}

const batch = await client.messages.batches.create({ requests });
console.log("Batch submitted", batch.id, batch.processing_status, batch.expires_at);

What this proves: that custom_id is the only reliable correlation key, that replacing illegal characters preserves uniqueness under the allowed alphabet, and that dry-running one shape prevents asynchronous validation surprises across the whole batch.

Failure boundary: a custom_id containing a slash or colon without sanitization fails validation. A page that assumes store or previous_thread_event_id can be included also fails validation per the unsupported table.

Build exercise material: Exercise 2: status polling and the retrieval of results with per-item outcome branching

Goal: observe the in_progress to ended transition, fetch the .jsonl file after ended, and branch correctly on the four per-request outcomes.

Steps:

  1. Use the batch id from exercise 1.
  2. Poll client.messages.batches.retrieve(batchId) on a fixed 60 second interval until processing_status === "ended". Log request_counts each cycle but never fetch results_url before ended; it is null until then.
  3. After ended, call client.messages.batches.results(batchId) and iterate the async iterable of result lines.
  4. Partition every line by result.type into four maps keyed by custom_id: succeeded, errored, canceled, expired.
  5. For errored lines, read result.error.type and group by error class to decide retryability.

Observable outcome: the loop terminates when processing_status flips to ended and the .jsonl stream yields exactly 20 lines, each with its original custom_id and a result.type in the set of four.

Verification: assert request_counts.processing === 0 after ended, assert the result stream length equals requests.length, and assert results_url is non-null only after ended.

example.ts
typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const batchId = "msgbatch_REPLACE_WITH_ID_FROM_EXERCISE_1";

let batch = await client.messages.batches.retrieve(batchId);
while (batch.processing_status !== "ended") {
  console.log(`Batch ${batchId} status=${batch.processing_status} counts=`, (batch as unknown as { request_counts: unknown }).request_counts);
  await new Promise((r) => setTimeout(r, 60_000));
  batch = await client.messages.batches.retrieve(batchId);
}

console.log(`Batch ended at ${batch.ended_at}, results_url=${batch.results_url}`);
const succeeded = new Map<string, unknown>();
const errored = new Map<string, unknown>();
const canceled = new Map<string, unknown>();
const expired = new Map<string, unknown>();

for await (const line of await client.messages.batches.results(batchId)) {
  const entry = line as unknown as { custom_id: string; result: { type: string; error?: unknown; message?: unknown } };
  if (entry.result.type === "succeeded") succeeded.set(entry.custom_id, entry.result.message);
  else if (entry.result.type === "errored") errored.set(entry.custom_id, entry.result.error);
  else if (entry.result.type === "canceled") canceled.set(entry.custom_id, entry);
  else if (entry.result.type === "expired") expired.set(entry.custom_id, entry);
  else throw new Error(`Unexpected result.type: ${entry.result.type}`);
}

console.log({ succeeded: succeeded.size, errored: errored.size, canceled: canceled.size, expired: expired.size });

for (const [customId, err] of errored) {
  const typed = err as { type?: string; message?: string };
  console.log(`errored ${customId}: ${typed.type ?? "unknown"} ${typed.message ?? ""}`);
}

What this proves: that results become available when all requests have finished or after 24 hours whichever comes first, that expiry surfaces as expired lines after expires_at, and that billing-relevant outcomes are only succeeded and errored while canceled and expired carry no inference cost.

Failure boundary: polling does not accelerate completion; it only observes it. Fetching the result file before ended yields a 404 or empty stream depending on SDK handling, because results_url is null.

Build exercise material: Exercise 3: selective resubmission of only failed items with a traceable identifier convention

Goal: resubmit only the custom_id set whose result.type is errored or expired, with targeted modifications and a suffixed identifier that preserves lineage without reusing bare ids.

Steps:

  1. From the partitioned maps, collect failedIds as the union of errored and expired keys. Leave succeeded and canceled entries out entirely.
  2. For each custom_id in failedIds, look up the original document by the pre-sanitized key, then classify the failure: context_length failures get chunked content and a higher max_tokens within the model's window, structural failures get a format-specific few-shot example appended, transient validation failures get an unchanged shape.
  3. Derive each retry custom_id as the original suffixed with -retry-1, sanitized and sliced to 64 characters, so that attempt count is observable in logs and lineage is preserved.
  4. Submit a retry batch containing only those requests, again dry-running one representative shape before submission.

Observable outcome: the retry batch is strictly smaller than the original batch, its custom_id values are distinct suffixed forms, and its submission succeeds with processing_status: in_progress while the succeeded lines from the original batch remain untouched.

Verification: assert retryRequests.length === failedIds.size, assert no retry custom_id equals a bare original id, assert the original succeeded lines are not present in the retry payload.

example.ts
typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

type OriginalDoc = { rawId: string; content: string };
const originalDocs: Record<string, OriginalDoc> = {
  // populated from exercise 1 mapping keyed by the sanitized custom_id
};

function chunkIfNeeded(text: string): string {
  const limit = 8000;
  return text.length > limit ? text.slice(0, limit) + "\n[truncated for retry]" : text;
}

const failedIds: string[] = Array.from(new Set([...errored.keys(), ...expired.keys()]));
if (failedIds.length === 0) {
  console.log("No failures to retry");
} else {
  const retryRequests = failedIds.map((id) => {
    const doc = originalDocs[id];
    const retriedCustomId = `${id}-retry-1`.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
    const isLongDoc = doc.content.length > 8000;
    return {
      custom_id: retriedCustomId,
      params: {
        model: "claude-sonnet-4-6",
        max_tokens: isLongDoc ? 8192 : 4096,
        messages: [
          {
            role: "user" as const,
            content: isLongDoc
              ? `Extract fields as JSON. Long documents are truncated:\n\n${chunkIfNeeded(doc.content)}`
              : `Extract fields as JSON. Example for this table layout:\n\`{"parties": ["A","B"], "date": "2024-01-15"}\`\n\nNow extract from:\n\n${doc.content}`,
          },
        ],
      },
    };
  });

  for (const r of retryRequests) {
    if (!/^[a-zA-Z0-9_-]{1,64}$/.test(r.custom_id)) throw new Error(`Invalid retry id ${r.custom_id}`);
  }

  const retryBatch = await client.messages.batches.create({ requests: retryRequests });
  console.log(`Retry batch ${retryBatch.id} with ${retryRequests.length} requests submitted`);
}

What this proves: that custom_id is how you select the retry set, that whole-batch resubmission wastes payment on already succeeded work, and that per-cause modification (chunking versus prompt augmentation) is what raises the retry pass rate.

Failure boundary: blind retry with the same shape reproduces the same failure; global trimming for a problem that only affects a format-specific subset biases all prompts without fixing the root cause.

Build exercise material: Exercise 4: a submission schedule derived by working backwards from a consumer deadline

Goal: guarantee a 30 hour SLA that depends on expires_at = created_at + 24h and an observable buffer for collection, validation, and retry.

Steps:

  1. Read the consumer deadline as an absolute timestamp deadline.
  2. Compute latestSubmission = deadline - 24h, which is the latest instant a batch can be created and still have its full window inside the SLA.
  3. Inside the window between now and latestSubmission, budget a fixed contingency for polling setup and retry: the forensics file recommends splitting the remaining buffer into a collection window and a retry window, and the lesson illustrates submitting every 4 to 6 hours so a fresh batch is always in flight.
  4. Implement the cadence as a scheduled job that enqueues up to batchSizeLimit requests each tick, with batchSizeLimit chosen to keep each submission well under 100,000 and 256 MB.
  5. Log the arithmetic and enforce it in code that refuses to submit after latestSubmission.

Observable outcome: the schedule log shows 30h SLA - 24h processing = 6h buffer and the submitter enforces now <= latestSubmission, never submitting a batch that would by definition expire after the consumer deadline.

Verification: assert that a submission attempt at deadline - 23h is rejected as SLA-breaching, and that a submission at deadline - 25h is accepted with at least 1 hour of margin remaining for polling.

example.ts
typescript
function computeSchedule(params: { deadline: Date; now: Date; cadenceHours: number }) {
  const MS_24H = 24 * 60 * 60 * 1000;
  const latestSubmission = new Date(params.deadline.getTime() - MS_24H);
  const bufferMs = latestSubmission.getTime() - params.now.getTime();
  const bufferHours = bufferMs / (60 * 60 * 1000);
  const canSubmit = params.now.getTime() <= latestSubmission.getTime();

  console.log(`SLA: ${params.deadline.toISOString()}`);
  console.log(`Latest submission: ${latestSubmission.toISOString()} (deadline - 24h)`);
  console.log(`Buffer hours: ${bufferHours.toFixed(1)}h`);
  console.log(`Cadence: every ${params.cadenceHours}h`);

  if (!canSubmit) throw new Error("Cannot submit: would exceed 24h window before deadline");
  return { latestSubmission, bufferHours, canSubmit };
}

const deadline = new Date("2026-03-10T09:00:00Z");
const now = new Date("2026-03-09T03:00:00Z");
const { latestSubmission } = computeSchedule({ deadline, now, cadenceHours: 5 });

async function cadencedSubmit(batches: Array<{ requests: unknown[] }>) {
  for (const payload of batches) {
    const at = new Date();
    if (at.getTime() > latestSubmission.getTime()) {
      throw new Error(`Cadence violated: submission at ${at.toISOString()} is after ${latestSubmission.toISOString()}`);
    }
    console.log(`Submitting batch at ${at.toISOString()}`);
    // await client.messages.batches.create(payload as any);
    await new Promise((r) => setTimeout(r, 5 * 60 * 60 * 1000));
  }
}

What this proves: that the 24 hour ceiling is the design bound, that the typical sub-hour completion is a convenience not a promise, and that cadence of 4 to 6 hours within the 6 hour buffer maintains freshness without assuming tail latency away.

Failure boundary: planning to the typical 1 hour instead of the ceiling produces silent breaches on the tail nights that do approach 24 hours, and polling with a short fallback does not convert batch into a blocking primitive for pre-merge gates.

Build exercise material: Exercise 5: a shared-prefix batch that uses caching with the longer duration for the documented reason

Goal: submit a shared-prefix batch where a large stable system preamble is marked with the 1 hour cache duration, because the 5 minute entry written inside concurrent batch execution will usually expire before a follow-up request could reuse it.

Steps:

  1. Build a shared system preamble of roughly 20,000 to 30,000 tokens combining role instructions, schema, and few-shot examples. Place identical system blocks in every params object.
  2. Mark the end of the shared preamble with cache_control: { type: "ephemeral", ttl: "1h" }. Place variable per-document content after the breakpoint. This exploits the prefix order tools, then system, then messages that the caching page defines.
  3. Split the source set into two sequential batches with the same prefix. Submit the first small batch of one request to warm the 1 hour cache, poll it to ended, then immediately submit the remaining batch.
  4. Inspect usage.cache_creation_input_tokens on the first batch and usage.cache_read_input_tokens on the second batch's succeeded lines to confirm that reads at 0.1 times base are stacking with the 0.5 times batch discount.

Observable outcome: first batch usage shows cache_creation_input_tokens near the shared prefix length at 2 times base for the 1 hour write, subsequent batches show high cache_read_input_tokens at 0.1 times base, and the guidance tip on the batch page is satisfied.

Verification: assert cache_control.ttl === "1h" is present on the shared block in every request, assert that no per-request custom_id varies the prefix, and assert that total input tokens for the second wave satisfy total_input_tokens = cache_read_input_tokens + cache_creation_input_tokens + input_tokens with most of the prefix counted as cache_read_input_tokens.

example.ts
typescript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const sharedPreamble = [
  {
    type: "text" as const,
    text: "You are a contract extraction assistant. Extract parties, effective date, term, and renewal clause as JSON matching the schema.",
  },
  {
    type: "text" as const,
    text: "Schema: { parties: string[], effective_date: string, term_months: number, renewal_clause: string }\nFew-shot 1: ...\nFew-shot 2: ...\nFew-shot 3: ...\n\n[Approximately 20k tokens of instructions and examples]",
    cache_control: { type: "ephemeral" as const, ttl: "1h" as const },
  },
];

const docs = Array.from({ length: 50 }, (_, i) => ({
  id: `contract-${String(i).padStart(5, "0")}`,
  text: `Contract document ${i} body ...`,
}));

function toCachedParams(doc: { id: string; text: string }) {
  return {
    model: "claude-sonnet-4-6",
    max_tokens: 2048,
    system: sharedPreamble,
    messages: [{ role: "user" as const, content: `Extract as JSON from:\n\n${doc.text}` }],
  };
}

const warmRequest = [{ custom_id: toSanitized(docs[0].id), params: toCachedParams(docs[0]) }];
const warmBatch = await client.messages.batches.create({ requests: warmRequest as never });
console.log(`Warm batch ${warmBatch.id}: 1h cache write for shared preamble`);

let warm = await client.messages.batches.retrieve(warmBatch.id);
while (warm.processing_status !== "ended") {
  await new Promise((r) => setTimeout(r, 60_000));
  warm = await client.messages.batches.retrieve(warmBatch.id);
}

const remaining = docs.slice(1).map((d) => ({ custom_id: toSanitized(d.id), params: toCachedParams(d) }));
const mainBatch = await client.messages.batches.create({ requests: remaining as never });
console.log(`Main batch ${mainBatch.id}: ${remaining.length} requests sharing 1h cached prefix`);

function toSanitized(id: string): string {
  return id.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
}

What this proves: that caching inside batch is supported and that discounts stack, that the 1 hour TTL is the documented choice for shared-prefix batches because generation time and asynchronous spread consume a 5 minute window, and that the best-effort hit policy still favors the longer duration for throughput.

Failure boundary: placing a breakpoint on a per-document variable block, changing any prefix character between requests, or using the 5 minute TTL for a batch that fans out over more than a few minutes will produce writes with no corresponding reads and repay the cache creation cost without benefit.

Mechanism and API surface

Fixed batch contract
50 percent savings versus synchronous, results available when every request finishes or at the 24 hour ceiling whichever comes first with no per-item latency promise, and custom_id correlation for request-response pairs.
Matching rule blocking versus latency-tolerant
Synchronous for blocking workflows where someone waits such as pre-merge checks, batch for latency-tolerant workflows such as overnight reports, weekly audits, and nightly generation where results are consumed later.
Batch limits and shape
Up to 100000 requests per batch or 256 MB total size whichever is first, requests array with custom_id plus params per item matching a synchronous call, models and parameters can vary per item in the same batch.
Result handling with custom_id
Iterate results, filter by type succeeded versus errored, use custom_id to look up original inputs, retry batch contains only failures with modifications and a suffixed custom_id for correlation.
SLA schedule working backwards
Consumer deadline minus 24 hours is latest submission for the final batch, with 4 to 6 hour submission cadence inside the buffer. Use worst-case latency for planning, not observed best case.
Sample refinement as cost leverage
Refine prompts on 5 to 10 representative documents before the full batch, covering formats, edge cases, and types. Higher first-pass success directly reduces retry count and total batch cost.

The decision rules in play

Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.

R1

The fifty percent discount trades latency for throughput and is uniform across models

The Message Batches API bills input and output tokens at 50 percent of the standard Messages API price for the same model. The discount attaches to the batch delivery path,.

Polling processing_status and downloading the .jsonl result file are the consumption steps. Discount, latency, and throughput handling move together.

result.json
json
{
  "requests": [
    {
      "custom_id": "invoice-0001",
      "params": {
        "model": "claude-sonnet-4-6",
        "max_tokens": 4096,
        "messages": [{"role": "user", "content": "Extract fields from: ..."}]
      }
    }
  ]
}

The provider can pack asynchronous work into flexible capacity and avoid reserving immediate inference slots. The caller trades immediacy.

Boundary. Where no consumer blocks on the response, the discount dominates and batch is the default. Where a human or gate waits within seconds to minutes, the.

Recurring specifics. Phrased as roughly 50 percent, 50 percent of standard prices, about half the cost. Applied to input and output tokens. Stacked with prompt caching.

Wrong answers written against this rule

Proposal. move everything to batch for savings.

Why it attracts. one number promises universal savings.

Why it fails. blocking workloads cannot tolerate the window; polling does not change it.

When it would be right. only when every workload is latency-tolerant.

Proposal. keep everything synchronous to avoid correlation complexity.

Why it attracts. uniform path feels simpler.

Why it fails. forfeits savings where custom_id already solves correlation.

When it would be right. when every workload is latency-sensitive.

Proposal. use fast mode or a smaller model to get a similar discount.

Why it attracts. both change a price number.

Why it fails. fast mode raises price for speed, model choice is governed by quality evals.

When it would be right. when evals show the smaller model passes, then downsizing plus batch can combine.

How the same rule gets re-asked
  • - Volume from 500 to 3 million without changing answer when latency tolerance is fixed. - Model tier from Haiku to Opus without changing the fraction, only absolute spend. - Combining batch with prompt caching to test stacking. - Contrasting batch with fast mode pricing to test confusion of a speed premium with a throughput discount.
R2

The twenty four hour ceiling with no per-item latency promise governs all batch design

Accepted batches enter processing_status: in_progress with expires_at = created_at + 24h. They stay there until processing_status: ended. Availability is.

Callers retrieve a .jsonl file after ended. No partial stream is defined before that.

example.ts
typescript
const status = await client.batches.retrieve(batchId);
// status.processing_status === "in_progress" | "ended"
// status.expires_at === createdAt + 24h
// status.results_url available only after ended

A single ceiling simplifies provider capacity planning. The caller trades predictability for price. Designing to the maximum keeps.

Boundary. Where consumers tolerate any arrival within a day, the lack of a promise costs nothing. Where consumers need seconds to minutes, the same lack is disqualifying. A.

Recurring specifics. Phrased as up to 24 hours, within 24 hours, 24-hour processing window, no guaranteed latency SLA, no latency promise, expires_at.

Wrong answers written against this rule

Proposal. assume fast arrival because batches often finish quickly.

Why it attracts. typical latency is lower than ceiling.

Why it fails. guarantees must be built on ceiling.

When it would be right. best-effort internal analytics with no external deadline.

Proposal. add a priority flag to get batch at real-time speed with discount.

Why it attracts. promises both.

Why it fails. no such flag exists; API is asynchronous by design.

When it would be right. never.

Proposal. poll faster to force completion.

Why it attracts. feels like a reliability control.

Why it fails. polling observes, it does not accelerate.

When it would be right. polling is for knowing when to fetch, not for changing availability.

How the same rule gets re-asked
  • - Deadline from 72 hours to 6 hours flips the same batch from suitable to unsuitable. - Adding a firm 30 hour SLA forces cadence calculation rather than binary choice. - Noting batches often finish well under 24 hours tests whether learner plans to typical or to ceiling.
R3

Typical completion near one hour does not create an SLA

Documentation notes most batches finish within about one hour while the tail can reach 24 hours. The batch.

A durable design treats the ceiling as the bound and plans buffer and cadence, not a single wait.

example.ts
typescript
// fragile: assumes typical equals guarantee
await submitBatch(requests);
await sleep(60 * 60 * 1000);
const results = await fetchResults(batchId); // fails when tail is 20h

Most work completes quickly but a fleet still has a tail. A single ceiling is simpler to document than a distribution. Callers who plan to the ceiling stay correct across the distribution.

Boundary. Best-effort reporting with no external promise can plan loosely. A production SLA with an external commitment must plan.

Recurring specifics. Cited as typical ~1 hour, most finish within about an hour, often well under 24 hours, tail 24 hours alongside the ceiling. Some items note requests not completed by expiry become expired.

Wrong answers written against this rule

Proposal. submit Sunday evening and expect Monday delivery because batches are typically fast.

Why it attracts. empirical support.

Why it fails. tail can still breach.

When it would be right. only when slack beyond the ceiling exists.

Proposal. set a short polling timeout and fall back to synchronous calls.

Why it attracts. combines paths.

Why it fails. adds complexity without fixing the matching error.

When it would be right. only to salvage a subset near deadline, not as primary design.

How the same rule gets re-asked
  • - Showing a batch that completed in 6 hours and asking whether the next can use the same offset tests single-sample generalization. - Providing a 72 hour window where typical speed is irrelevant because even the ceiling fits. - Adding polling to test confusion of observability with acceleration.
R4

Batch size is bounded by 100,000 requests or 256 MB whichever is reached first

One POST /v1/messages/batches call accepts at most 100,000 requests and at most 256 MB of payload. Whichever limit is hit.

Splitting preserves the discount per batch.

example.ts
typescript
const chunks = chunkByCountAndBytes(allDocs, 100000, 256 * 1024 * 1024);
for (const slice of chunks) {
  await client.batches.create({
    requests: slice.map(doc => ({
      custom_id: doc.id,
      params: { model: "claude-sonnet-4-6", max_tokens: 4096, messages: [{role: "user", content: doc.content}] }
    }))
  });
}

Count and byte limits bound both inference units and serialized payload for scheduling and storage. Splitting keeps each submission operable while still discounting each piece.

Boundary. Under both thresholds, one batch is simpler. Over either threshold, multiple batches are required even with one prompt template. 50,000 documents fit in one. 200,000 soil reports or 500,000 contracts require at least two.

Recurring specifics. Cited as 100,000 requests and 256 MB together with whichever comes first phrasing. Tested via splitting logic and connected to cost preservation and per-batch result handling.

Wrong answers written against this rule

Proposal. send 200,000 in one batch because async handles it.

Why it attracts. one call feels atomic.

Why it fails. count limit caps at 100K regardless of async.

When it would be right. never beyond limit.

Proposal. run everything synchronously or in one long conversation to avoid chunking.

Why it attracts. avoids managing multiple result files.

Why it fails. forfeits discount and hits context or rate limits.

When it would be right. only small interactive work.

Proposal. pack all items into one giant prompt.

Why it attracts. reduces request count.

Why it fails. coupling, context pressure, poor error isolation.

When it would be right. only when items are genuinely one document.

How the same rule gets re-asked
  • - Raising volume from 80,000 to 200,000 flips one batch to at least two. - Keeping count under 100K but bloating each payload to hit 256 MB tests the byte limit. - Adding a shared system prompt tests whether discounts still stack per chunk.
R5

`custom_id` is the only reliable correlation between a submitted request and its response

Each requests element carries a client-chosen custom_id. The result .jsonl contains one line per request with the same.

Consumption must join on custom_id, not on position, and must not parse model text for identity.

result.json
json
{"custom_id": "invoice-042", "result": {"type": "succeeded", "message": {"content": [{"text": "..."}]}}}
{"custom_id": "invoice-043", "result": {"type": "errored", "error": {"type": "invalid_request_error"}}}

Batches are processed concurrently across shards and written as they complete. Order is not defined, and subset retries create new.

Boundary. When only aggregate counts matter, weak schemes may appear to work. As soon as any failure handling, audit, or per-record downstream action.

Recurring specifics. Described as the mechanism for correlating request and response pairs, identifying failures, and selective resubmission. Noted that results return in unpredictable order and positional correlation is.

Wrong answers written against this rule

Proposal. rely on result order matching submission order.

Why it attracts. intuitive for small lists.

Why it fails. execution does not guarantee order.

When it would be right. never for batch results.

Proposal. parse file names from model text.

Why it attracts. output looks like an identifier.

Why it fails. brittle to phrasing and prompt compliance.

When it would be right. only as secondary hint, never as key.

Proposal. store only batch ID and use positional lookup.

Why it attracts. sounds like tracking.

Why it fails. batch ID identifies the job, not per-request identity.

When it would be right. storing custom_id itself is correct, storing only batch ID is not.

Proposal. submit each pull request as its own single-item batch so batch ID equals identity.

Why it attracts. avoids correlation logic.

Why it fails. wastes throughput and creates many jobs to poll.

When it would be right. only isolated one-offs.

How the same rule gets re-asked
  • - Result count from a handful to 100 tests whether learner still reaches for custom_id at small scale. - Adding routing to human review tests flagging unreviewed items. - Claiming single-item batches avoid correlation tests throughput understanding.
R1

The fifty percent discount trades latency for throughput and is uniform across models

The Message Batches API bills input and output tokens at 50 percent of the standard Messages API price for the same model. The discount attaches to the batch delivery path,.

Polling processing_status and downloading the .jsonl result file are the consumption steps. Discount, latency, and throughput handling move together.

result.json
json
{
  "requests": [
    {
      "custom_id": "invoice-0001",
      "params": {
        "model": "claude-sonnet-4-6",
        "max_tokens": 4096,
        "messages": [{"role": "user", "content": "Extract fields from: ..."}]
      }
    }
  ]
}

The provider can pack asynchronous work into flexible capacity and avoid reserving immediate inference slots. The caller trades immediacy.

Boundary. Where no consumer blocks on the response, the discount dominates and batch is the default. Where a human or gate waits within seconds to minutes, the.

Recurring specifics. Phrased as roughly 50 percent, 50 percent of standard prices, about half the cost. Applied to input and output tokens. Stacked with prompt caching.

Wrong answers written against this rule

Proposal. move everything to batch for savings.

Why it attracts. one number promises universal savings.

Why it fails. blocking workloads cannot tolerate the window; polling does not change it.

When it would be right. only when every workload is latency-tolerant.

Proposal. keep everything synchronous to avoid correlation complexity.

Why it attracts. uniform path feels simpler.

Why it fails. forfeits savings where custom_id already solves correlation.

When it would be right. when every workload is latency-sensitive.

Proposal. use fast mode or a smaller model to get a similar discount.

Why it attracts. both change a price number.

Why it fails. fast mode raises price for speed, model choice is governed by quality evals.

When it would be right. when evals show the smaller model passes, then downsizing plus batch can combine.

How the same rule gets re-asked
  • - Volume from 500 to 3 million without changing answer when latency tolerance is fixed. - Model tier from Haiku to Opus without changing the fraction, only absolute spend. - Combining batch with prompt caching to test stacking. - Contrasting batch with fast mode pricing to test confusion of a speed premium with a throughput discount.
R2

The twenty four hour ceiling with no per-item latency promise governs all batch design

Accepted batches enter processing_status: in_progress with expires_at = created_at + 24h. They stay there until processing_status: ended. Availability is.

Callers retrieve a .jsonl file after ended. No partial stream is defined before that.

example.ts
typescript
const status = await client.batches.retrieve(batchId);
// status.processing_status === "in_progress" | "ended"
// status.expires_at === createdAt + 24h
// status.results_url available only after ended

A single ceiling simplifies provider capacity planning. The caller trades predictability for price. Designing to the maximum keeps.

Boundary. Where consumers tolerate any arrival within a day, the lack of a promise costs nothing. Where consumers need seconds to minutes, the same lack is disqualifying. A.

Recurring specifics. Phrased as up to 24 hours, within 24 hours, 24-hour processing window, no guaranteed latency SLA, no latency promise, expires_at.

Wrong answers written against this rule

Proposal. assume fast arrival because batches often finish quickly.

Why it attracts. typical latency is lower than ceiling.

Why it fails. guarantees must be built on ceiling.

When it would be right. best-effort internal analytics with no external deadline.

Proposal. add a priority flag to get batch at real-time speed with discount.

Why it attracts. promises both.

Why it fails. no such flag exists; API is asynchronous by design.

When it would be right. never.

Proposal. poll faster to force completion.

Why it attracts. feels like a reliability control.

Why it fails. polling observes, it does not accelerate.

When it would be right. polling is for knowing when to fetch, not for changing availability.

How the same rule gets re-asked
  • - Deadline from 72 hours to 6 hours flips the same batch from suitable to unsuitable. - Adding a firm 30 hour SLA forces cadence calculation rather than binary choice. - Noting batches often finish well under 24 hours tests whether learner plans to typical or to ceiling.
R3

Typical completion near one hour does not create an SLA

Documentation notes most batches finish within about one hour while the tail can reach 24 hours. The batch.

A durable design treats the ceiling as the bound and plans buffer and cadence, not a single wait.

example.ts
typescript
// fragile: assumes typical equals guarantee
await submitBatch(requests);
await sleep(60 * 60 * 1000);
const results = await fetchResults(batchId); // fails when tail is 20h

Most work completes quickly but a fleet still has a tail. A single ceiling is simpler to document than a distribution. Callers who plan to the ceiling stay correct across the distribution.

Boundary. Best-effort reporting with no external promise can plan loosely. A production SLA with an external commitment must plan.

Recurring specifics. Cited as typical ~1 hour, most finish within about an hour, often well under 24 hours, tail 24 hours alongside the ceiling. Some items note requests not completed by expiry become expired.

Wrong answers written against this rule

Proposal. submit Sunday evening and expect Monday delivery because batches are typically fast.

Why it attracts. empirical support.

Why it fails. tail can still breach.

When it would be right. only when slack beyond the ceiling exists.

Proposal. set a short polling timeout and fall back to synchronous calls.

Why it attracts. combines paths.

Why it fails. adds complexity without fixing the matching error.

When it would be right. only to salvage a subset near deadline, not as primary design.

How the same rule gets re-asked
  • - Showing a batch that completed in 6 hours and asking whether the next can use the same offset tests single-sample generalization. - Providing a 72 hour window where typical speed is irrelevant because even the ceiling fits. - Adding polling to test confusion of observability with acceleration.
R4

Batch size is bounded by 100,000 requests or 256 MB whichever is reached first

One POST /v1/messages/batches call accepts at most 100,000 requests and at most 256 MB of payload. Whichever limit is hit.

Splitting preserves the discount per batch.

example.ts
typescript
const chunks = chunkByCountAndBytes(allDocs, 100000, 256 * 1024 * 1024);
for (const slice of chunks) {
  await client.batches.create({
    requests: slice.map(doc => ({
      custom_id: doc.id,
      params: { model: "claude-sonnet-4-6", max_tokens: 4096, messages: [{role: "user", content: doc.content}] }
    }))
  });
}

Count and byte limits bound both inference units and serialized payload for scheduling and storage. Splitting keeps each submission operable while still discounting each piece.

Boundary. Under both thresholds, one batch is simpler. Over either threshold, multiple batches are required even with one prompt template. 50,000 documents fit in one. 200,000 soil reports or 500,000 contracts require at least two.

Recurring specifics. Cited as 100,000 requests and 256 MB together with whichever comes first phrasing. Tested via splitting logic and connected to cost preservation and per-batch result handling.

Wrong answers written against this rule

Proposal. send 200,000 in one batch because async handles it.

Why it attracts. one call feels atomic.

Why it fails. count limit caps at 100K regardless of async.

When it would be right. never beyond limit.

Proposal. run everything synchronously or in one long conversation to avoid chunking.

Why it attracts. avoids managing multiple result files.

Why it fails. forfeits discount and hits context or rate limits.

When it would be right. only small interactive work.

Proposal. pack all items into one giant prompt.

Why it attracts. reduces request count.

Why it fails. coupling, context pressure, poor error isolation.

When it would be right. only when items are genuinely one document.

How the same rule gets re-asked
  • - Raising volume from 80,000 to 200,000 flips one batch to at least two. - Keeping count under 100K but bloating each payload to hit 256 MB tests the byte limit. - Adding a shared system prompt tests whether discounts still stack per chunk.
R5

`custom_id` is the only reliable correlation between a submitted request and its response

Each requests element carries a client-chosen custom_id. The result .jsonl contains one line per request with the same.

Consumption must join on custom_id, not on position, and must not parse model text for identity.

result.json
json
{"custom_id": "invoice-042", "result": {"type": "succeeded", "message": {"content": [{"text": "..."}]}}}
{"custom_id": "invoice-043", "result": {"type": "errored", "error": {"type": "invalid_request_error"}}}

Batches are processed concurrently across shards and written as they complete. Order is not defined, and subset retries create new.

Boundary. When only aggregate counts matter, weak schemes may appear to work. As soon as any failure handling, audit, or per-record downstream action.

Recurring specifics. Described as the mechanism for correlating request and response pairs, identifying failures, and selective resubmission. Noted that results return in unpredictable order and positional correlation is.

Wrong answers written against this rule

Proposal. rely on result order matching submission order.

Why it attracts. intuitive for small lists.

Why it fails. execution does not guarantee order.

When it would be right. never for batch results.

Proposal. parse file names from model text.

Why it attracts. output looks like an identifier.

Why it fails. brittle to phrasing and prompt compliance.

When it would be right. only as secondary hint, never as key.

Proposal. store only batch ID and use positional lookup.

Why it attracts. sounds like tracking.

Why it fails. batch ID identifies the job, not per-request identity.

When it would be right. storing custom_id itself is correct, storing only batch ID is not.

Proposal. submit each pull request as its own single-item batch so batch ID equals identity.

Why it attracts. avoids correlation logic.

Why it fails. wastes throughput and creates many jobs to poll.

When it would be right. only isolated one-offs.

How the same rule gets re-asked
  • - Result count from a handful to 100 tests whether learner still reaches for custom_id at small scale. - Adding routing to human review tests flagging unreviewed items.
R1

The fifty percent discount trades latency for throughput and is uniform across models

The Message Batches API bills input and output tokens at 50 percent of the standard Messages API price for the same model. The discount attaches to the batch delivery path,.

Polling processing_status and downloading the .jsonl result file are the consumption steps. Discount, latency, and throughput handling move together.

result.json
json
{
  "requests": [
    {
      "custom_id": "invoice-0001",
      "params": {
        "model": "claude-sonnet-4-6",
        "max_tokens": 4096,
        "messages": [{"role": "user", "content": "Extract fields from: ..."}]
      }
    }
  ]
}

The provider can pack asynchronous work into flexible capacity and avoid reserving immediate inference slots. The caller trades immediacy.

Boundary. Where no consumer blocks on the response, the discount dominates and batch is the default. Where a human or gate waits within seconds to minutes, the.

Recurring specifics. Phrased as roughly 50 percent, 50 percent of standard prices, about half the cost. Applied to input and output tokens. Stacked with prompt caching.

Wrong answers written against this rule

Proposal. move everything to batch for savings.

Why it attracts. one number promises universal savings.

Why it fails. blocking workloads cannot tolerate the window; polling does not change it.

When it would be right. only when every workload is latency-tolerant.

Proposal. keep everything synchronous to avoid correlation complexity.

Why it attracts. uniform path feels simpler.

Why it fails. forfeits savings where custom_id already solves correlation.

When it would be right. when every workload is latency-sensitive.

Proposal. use fast mode or a smaller model to get a similar discount.

Why it attracts. both change a price number.

Why it fails. fast mode raises price for speed, model choice is governed by quality evals.

When it would be right. when evals show the smaller model passes, then downsizing plus batch can combine.

How the same rule gets re-asked
  • - Volume from 500 to 3 million without changing answer when latency tolerance is fixed. - Model tier from Haiku to Opus without changing the fraction, only absolute spend. - Combining batch with prompt caching to test stacking. - Contrasting batch with fast mode pricing to test confusion of a speed premium with a throughput discount.
R2

The twenty four hour ceiling with no per-item latency promise governs all batch design

Accepted batches enter processing_status: in_progress with expires_at = created_at + 24h. They stay there until processing_status: ended. Availability is.

Callers retrieve a .jsonl file after ended. No partial stream is defined before that.

example.ts
typescript
const status = await client.batches.retrieve(batchId);
// status.processing_status === "in_progress" | "ended"
// status.expires_at === createdAt + 24h
// status.results_url available only after ended

A single ceiling simplifies provider capacity planning. The caller trades predictability for price. Designing to the maximum keeps.

Boundary. Where consumers tolerate any arrival within a day, the lack of a promise costs nothing. Where consumers need seconds to minutes, the same lack is disqualifying. A.

Recurring specifics. Phrased as up to 24 hours, within 24 hours, 24-hour processing window, no guaranteed latency SLA, no latency promise, expires_at.

Wrong answers written against this rule

Proposal. assume fast arrival because batches often finish quickly.

Why it attracts. typical latency is lower than ceiling.

Why it fails. guarantees must be built on ceiling.

When it would be right. best-effort internal analytics with no external deadline.

Proposal. add a priority flag to get batch at real-time speed with discount.

Why it attracts. promises both.

Why it fails. no such flag exists; API is asynchronous by design.

When it would be right. never.

Proposal. poll faster to force completion.

Why it attracts. feels like a reliability control.

Why it fails. polling observes, it does not accelerate.

When it would be right. polling is for knowing when to fetch, not for changing availability.

How the same rule gets re-asked
  • - Deadline from 72 hours to 6 hours flips the same batch from suitable to unsuitable. - Adding a firm 30 hour SLA forces cadence calculation rather than binary choice. - Noting batches often finish well under 24 hours tests whether learner plans to typical or to ceiling.
R3

Typical completion near one hour does not create an SLA

Documentation notes most batches finish within about one hour while the tail can reach 24 hours. The batch.

A durable design treats the ceiling as the bound and plans buffer and cadence, not a single wait.

example.ts
typescript
// fragile: assumes typical equals guarantee
await submitBatch(requests);
await sleep(60 * 60 * 1000);
const results = await fetchResults(batchId); // fails when tail is 20h

Most work completes quickly but a fleet still has a tail. A single ceiling is simpler to document than a distribution. Callers who plan to the ceiling stay correct across the distribution.

Boundary. Best-effort reporting with no external promise can plan loosely. A production SLA with an external commitment must plan.

Recurring specifics. Cited as typical ~1 hour, most finish within about an hour, often well under 24 hours, tail 24 hours alongside the ceiling. Some items note requests not completed by expiry become expired.

Wrong answers written against this rule

Proposal. submit Sunday evening and expect Monday delivery because batches are typically fast.

Why it attracts. empirical support.

Why it fails. tail can still breach.

When it would be right. only when slack beyond the ceiling exists.

Proposal. set a short polling timeout and fall back to synchronous calls.

Why it attracts. combines paths.

Why it fails. adds complexity without fixing the matching error.

When it would be right. only to salvage a subset near deadline, not as primary design.

How the same rule gets re-asked
  • - Showing a batch that completed in 6 hours and asking whether the next can use the same offset tests single-sample generalization. - Providing a 72 hour window where typical speed is irrelevant because even the ceiling fits. - Adding polling to test confusion of observability with acceleration.
R4

Batch size is bounded by 100,000 requests or 256 MB whichever is reached first

One POST /v1/messages/batches call accepts at most 100,000 requests and at most 256 MB of payload. Whichever limit is hit.

Splitting preserves the discount per batch.

example.ts
typescript
const chunks = chunkByCountAndBytes(allDocs, 100000, 256 * 1024 * 1024);
for (const slice of chunks) {
  await client.batches.create({
    requests: slice.map(doc => ({
      custom_id: doc.id,
      params: { model: "claude-sonnet-4-6", max_tokens: 4096, messages: [{role: "user", content: doc.content}] }
    }))
  });
}

Count and byte limits bound both inference units and serialized payload for scheduling and storage. Splitting keeps each submission operable while still discounting each piece.

Boundary. Under both thresholds, one batch is simpler. Over either threshold, multiple batches are required even with one prompt template. 50,000 documents fit in one. 200,000 soil reports or 500,000 contracts require at least two.

Recurring specifics. Cited as 100,000 requests and 256 MB together with whichever comes first phrasing. Tested via splitting logic and connected to cost preservation and per-batch result handling.

Wrong answers written against this rule

Proposal. send 200,000 in one batch because async handles it.

Why it attracts. one call feels atomic.

Why it fails. count limit caps at 100K regardless of async.

When it would be right. never beyond limit.

Proposal. run everything synchronously or in one long conversation to avoid chunking.

Why it attracts. avoids managing multiple result files.

Why it fails. forfeits discount and hits context or rate limits.

When it would be right. only small interactive work.

Proposal. pack all items into one giant prompt.

Why it attracts. reduces request count.

Why it fails. coupling, context pressure, poor error isolation.

When it would be right. only when items are genuinely one document.

How the same rule gets re-asked
  • - Raising volume from 80,000 to 200,000 flips one batch to at least two. - Keeping count under 100K but bloating each payload to hit 256 MB tests the byte limit. - Adding a shared system prompt tests whether discounts still stack per chunk.
R5

`custom_id` is the only reliable correlation between a submitted request and its response

Each requests element carries a client-chosen custom_id. The result .jsonl contains one line per request with the same.

Consumption must join on custom_id, not on position, and must not parse model text for identity.

result.json
json
{"custom_id": "invoice-042", "result": {"type": "succeeded", "message": {"content": [{"text": "..."}]}}}
{"custom_id": "invoice-043", "result": {"type": "errored", "error": {"type": "invalid_request_error"}}}
example.ts
typescript
function toCustomId(dbId: string, attempt: number): string {
  const raw = `${dbId}-retry-${attempt}`;
  return raw.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 64);
}
const requests = documents.map(doc => ({
  custom_id: toCustomId(doc.recordId, 0),
  params: { model: "claude-sonnet-4-6", max_tokens: 4096, messages: [{role: "user", content: doc.text}] }
}));

Batches are processed concurrently across shards and written as they complete. Order is not defined, and subset retries create new.

Boundary. When only aggregate counts matter, weak schemes may appear to work. As soon as any failure handling, audit, or per-record downstream action.

Recurring specifics. Described as the mechanism for correlating request and response pairs, identifying failures, and selective resubmission. Noted that results return in unpredictable order and positional correlation is.

Wrong answers written against this rule

Proposal. rely on result order matching submission order.

Why it attracts. intuitive for small lists.

Why it fails. execution does not guarantee order.

When it would be right. never for batch results.

Proposal. parse file names from model text.

Why it attracts. output looks like an identifier.

Why it fails. brittle to phrasing and prompt compliance.

When it would be right. only as secondary hint, never as key.

Proposal. store only batch ID and use positional lookup.

Why it attracts. sounds like tracking.

Why it fails. batch ID identifies the job, not per-request identity.

When it would be right. storing custom_id itself is correct, storing only batch ID is not.

Proposal. submit each pull request as its own single-item batch so batch ID equals identity.

Why it attracts. avoids correlation logic.

Why it fails. wastes throughput and creates many jobs to poll.

When it would be right. only isolated one-offs.

How the same rule gets re-asked
  • - Result count from a handful to 100 tests whether learner still reaches for custom_id at small scale. - Adding routing to human review tests flagging unreviewed items.. Allowed characters are ASCII letters, digits, underscore, and hyphen. Length is 1 to 64 inclusive. Any request with spaces,.
  • Direct use of a database key is valid when it already matches the pattern. Sanitizing composite keys preserves uniqueness while satisfying validation.
  • ### Why it holds
  • Result .jsonl lines, log indexes, and retry maps use custom_id as a literal token. A short alphanumeric-safe alphabet avoids escaping, encoding, and filesystem issues in tooling.
  • ### Boundary and the nearby opposite case
  • Inside the alphabet and length, any string works: invoice-042, pr_1234, soil-000001. Outside it, validation rejects even though the inference would succeed. invoice/042 or invoice:042.
  • ### Concrete details that recur
  • Cited as custom_id must match ^[a-zA-Z0-9_-]{1,64}# Task 4.5 Batch Processing - question forensics
  • ## Rule inventory
  • 1. The fifty percent discount trades latency for throughput and is uniform across models 2. The twenty four hour ceiling with no per-item latency promise governs all batch design 3. Typical completion near one hour does not create an SLA 4. Batch size is bounded by 100,000 requests or 256 MB whichever is reached first 5. custom_id is the only reliable correlation between a submitted request and its response 6. custom_id must match ^[a-zA-Z0-9_-]{1,64}$ 7. Lifecycle is in_progress to ended with expires_at = created_at + 24h and results become available when all requests finish or at 24 hours 8. Results remain downloadable for 29 days and deletion is explicit via DELETE /v1/messages/batches/{batch_id} 9. Batch storage is not covered by Zero Data Retention and carries retention obligations 10. Per-request outcomes are succeeded, errored, canceled, expired and expired or canceled are not billed 11. Blocking versus latency-tolerant matching is the primary routing test 12. A pre-merge or merge-gate that a developer waits on must stay synchronous even when a discount is offered 13. Working backward from a consumer deadline determines submission cadence 14. SLA buffer and timeout buffer reserve time for retries without breaching the deadline 15. Failure recovery resubmits only the failed custom_id set, never the whole batch 16. Targeted modification by failure cause beats blind retry or global trimming 17. Sample-set refinement before the full batch is the largest cost lever 18. Progressive sequential batches with prompt refinement between submissions improve first-pass rate over time 19. The batch request model and the multi-turn tool loop tension and its documented position 20. Unsupported parameters inside a batch request include stream: true, fast-mode speed, thread fields, cache and context routing hints, and max_tokens: 0 21. Prompt caching works inside batches, the discounts stack, and cache duration matters 22. Fast mode and batch are mutually exclusive and oppositely priced 23. Positional or ordering correlation is unreliable and fails under batch semantics 24. Parsing model output to recover correlation is fragile versus using custom_id 25. Polling with a short fallback does not make batch suitable for blocking work 26. Capacity isolation requires separate queues or workspace-scoped rate limits, not timeouts or model swaps 27. Per-item error isolation limits blast radius compared with batch-level failure handling 28. Coverage must be reported honestly as a fraction that succeeded, not as complete 29. Hybrid routing inside one product splits latency-sensitive and latency-tolerant workloads across APIs 30. Choosing among batch, managed agents, and a single prompt depends on interaction shape not on the word asynchronous
R1

The fifty percent discount trades latency for throughput and is uniform across models

The Message Batches API bills input and output tokens at 50 percent of the standard Messages API price for the same model. The discount attaches to the batch delivery path,.

Polling processing_status and downloading the .jsonl result file are the consumption steps. Discount, latency, and throughput handling move together.

result.json
json
{
  "requests": [
    {
      "custom_id": "invoice-0001",
      "params": {
        "model": "claude-sonnet-4-6",
        "max_tokens": 4096,
        "messages": [{"role": "user", "content": "Extract fields from: ..."}]
      }
    }
  ]
}

The provider can pack asynchronous work into flexible capacity and avoid reserving immediate inference slots. The caller trades immediacy.

Boundary. Where no consumer blocks on the response, the discount dominates and batch is the default. Where a human or gate waits within seconds to minutes, the.

Recurring specifics. Phrased as roughly 50 percent, 50 percent of standard prices, about half the cost. Applied to input and output tokens. Stacked with prompt caching.

Wrong answers written against this rule

Proposal. move everything to batch for savings.

Why it attracts. one number promises universal savings.

Why it fails. blocking workloads cannot tolerate the window; polling does not change it.

When it would be right. only when every workload is latency-tolerant.

Proposal. keep everything synchronous to avoid correlation complexity.

Why it attracts. uniform path feels simpler.

Why it fails. forfeits savings where custom_id already solves correlation.

When it would be right. when every workload is latency-sensitive.

Proposal. use fast mode or a smaller model to get a similar discount.

Why it attracts. both change a price number.

Why it fails. fast mode raises price for speed, model choice is governed by quality evals.

When it would be right. when evals show the smaller model passes, then downsizing plus batch can combine.

How the same rule gets re-asked
  • - Volume from 500 to 3 million without changing answer when latency tolerance is fixed. - Model tier from Haiku to Opus without changing the fraction, only absolute spend. - Combining batch with prompt caching to test stacking. - Contrasting batch with fast mode pricing to test confusion of a speed premium with a throughput discount.
R2

The twenty four hour ceiling with no per-item latency promise governs all batch design

Accepted batches enter processing_status: in_progress with expires_at = created_at + 24h. They stay there until processing_status: ended. Availability is.

Callers retrieve a .jsonl file after ended. No partial stream is defined before that.

example.ts
typescript
const status = await client.batches.retrieve(batchId);
// status.processing_status === "in_progress" | "ended"
// status.expires_at === createdAt + 24h
// status.results_url available only after ended

A single ceiling simplifies provider capacity planning. The caller trades predictability for price. Designing to the maximum keeps.

Boundary. Where consumers tolerate any arrival within a day, the lack of a promise costs nothing. Where consumers need seconds to minutes, the same lack is disqualifying. A.

Recurring specifics. Phrased as up to 24 hours, within 24 hours, 24-hour processing window, no guaranteed latency SLA, no latency promise, expires_at.

Wrong answers written against this rule

Proposal. assume fast arrival because batches often finish quickly.

Why it attracts. typical latency is lower than ceiling.

Why it fails. guarantees must be built on ceiling.

When it would be right. best-effort internal analytics with no external deadline.

Proposal. add a priority flag to get batch at real-time speed with discount.

Why it attracts. promises both.

Why it fails. no such flag exists; API is asynchronous by design.

When it would be right. never.

Proposal. poll faster to force completion.

Why it attracts. feels like a reliability control.

Why it fails. polling observes, it does not accelerate.

When it would be right. polling is for knowing when to fetch, not for changing availability.

How the same rule gets re-asked
  • - Deadline from 72 hours to 6 hours flips the same batch from suitable to unsuitable. - Adding a firm 30 hour SLA forces cadence calculation rather than binary choice. - Noting batches often finish well under 24 hours tests whether learner plans to typical or to ceiling.
R3

Typical completion near one hour does not create an SLA

Documentation notes most batches finish within about one hour while the tail can reach 24 hours. The batch.

A durable design treats the ceiling as the bound and plans buffer and cadence, not a single wait.

example.ts
typescript
// fragile: assumes typical equals guarantee
await submitBatch(requests);
await sleep(60 * 60 * 1000);
const results = await fetchResults(batchId); // fails when tail is 20h

Most work completes quickly but a fleet still has a tail. A single ceiling is simpler to document than a distribution. Callers who plan to the ceiling stay correct across the distribution.

Boundary. Best-effort reporting with no external promise can plan loosely. A production SLA with an external commitment must plan.

Recurring specifics. Cited as typical ~1 hour, most finish within about an hour, often well under 24 hours, tail 24 hours alongside the ceiling. Some items note requests not completed by expiry become expired.

Wrong answers written against this rule

Proposal. submit Sunday evening and expect Monday delivery because batches are typically fast.

Why it attracts. empirical support.

Why it fails. tail can still breach.

When it would be right. only when slack beyond the ceiling exists.

Proposal. set a short polling timeout and fall back to synchronous calls.

Why it attracts. combines paths.

Why it fails. adds complexity without fixing the matching error.

When it would be right. only to salvage a subset near deadline, not as primary design.

How the same rule gets re-asked
  • - Showing a batch that completed in 6 hours and asking whether the next can use the same offset tests single-sample generalization. - Providing a 72 hour window where typical speed is irrelevant because even the ceiling fits. - Adding polling to test confusion of observability with acceleration.
R4

Batch size is bounded by 100,000 requests or 256 MB whichever is reached first

One POST /v1/messages/batches call accepts at most 100,000 requests and at most 256 MB of payload. Whichever limit is hit.

Splitting preserves the discount per batch.

example.ts
typescript
const chunks = chunkByCountAndBytes(allDocs, 100000, 256 * 1024 * 1024);
for (const slice of chunks) {
  await client.batches.create({
    requests: slice.map(doc => ({
      custom_id: doc.id,
      params: { model: "claude-sonnet-4-6", max_tokens: 4096, messages: [{role: "user", content: doc.content}] }
    }))
  });
}

Count and byte limits bound both inference units and serialized payload for scheduling and storage. Splitting keeps each submission operable while still discounting each piece.

Boundary. Under both thresholds, one batch is simpler. Over either threshold, multiple batches are required even with one prompt template. 50,000 documents fit in one. 200,000 soil reports or 500,000 contracts require at least two.

Recurring specifics. Cited as 100,000 requests and 256 MB together with whichever comes first phrasing. Tested via splitting logic and connected to cost preservation and per-batch result handling.

Wrong answers written against this rule

Proposal. send 200,000 in one batch because async handles it.

Why it attracts. one call feels atomic.

Why it fails. count limit caps at 100K regardless of async.

When it would be right. never beyond limit.

Proposal. run everything synchronously or in one long conversation to avoid chunking.

Why it attracts. avoids managing multiple result files.

Why it fails. forfeits discount and hits context or rate limits.

When it would be right. only small interactive work.

Proposal. pack all items into one giant prompt.

Why it attracts. reduces request count.

Why it fails. coupling, context pressure, poor error isolation.

When it would be right. only when items are genuinely one document.

How the same rule gets re-asked
  • - Raising volume from 80,000 to 200,000 flips one batch to at least two. - Keeping count under 100K but bloating each payload to hit 256 MB tests the byte limit. - Adding a shared system prompt tests whether discounts still stack per chunk.
R5

`custom_id` is the only reliable correlation between a submitted request and its response

Each requests element carries a client-chosen custom_id. The result .jsonl contains one line per request with the same.

Consumption must join on custom_id, not on position, and must not parse model text for identity.

result.json
json
{"custom_id": "invoice-042", "result": {"type": "succeeded", "message": {"content": [{"text": "..."}]}}}
{"custom_id": "invoice-043", "result": {"type": "errored", "error": {"type": "invalid_request_error"}}}

Batches are processed concurrently across shards and written as they complete. Order is not defined, and subset retries create new.

Boundary. When only aggregate counts matter, weak schemes may appear to work. As soon as any failure handling, audit, or per-record downstream action.

Recurring specifics. Described as the mechanism for correlating request and response pairs, identifying failures, and selective resubmission. Noted that results return in unpredictable order and positional correlation is.

Wrong answers written against this rule

Proposal. rely on result order matching submission order.

Why it attracts. intuitive for small lists.

Why it fails. execution does not guarantee order.

When it would be right. never for batch results.

Proposal. parse file names from model text.

Why it attracts. output looks like an identifier.

Why it fails. brittle to phrasing and prompt compliance.

When it would be right. only as secondary hint, never as key.

Proposal. store only batch ID and use positional lookup.

Why it attracts. sounds like tracking.

Why it fails. batch ID identifies the job, not per-request identity.

When it would be right. storing custom_id itself is correct, storing only batch ID is not.

Proposal. submit each pull request as its own single-item batch so batch ID equals identity.

Why it attracts. avoids correlation logic.

Why it fails. wastes throughput and creates many jobs to poll.

When it would be right. only isolated one-offs.

How the same rule gets re-asked
  • - Result count from a handful to 100 tests whether learner still reaches for custom_id at small scale. - Adding routing to human review tests flagging unreviewed items. with class a-zA-Z0-9_- and 1 to 64. Contrasted with file names containing slashes or long composite keys. Noted that partial-failure tracking requires this field on every request.
  • ### Distractors seen against this rule
  • - Proposal: use s3://bucket/invoice/042.pdf as custom_id. Why attractive: uniquely identifies source. Why it fails: colons and slashes violate pattern. When it would be right: after sanitizing to s3_bucket_invoice_042_pdf or using a short key. - Proposal: omit custom_id to let the service assign. Why attractive: fewer fields. Why it fails: caller must supply for correlation. When it would be right: never for batch. - Proposal: reuse same custom_id for multiple items in one batch. Why attractive: preserves original identity. Why it fails: duplicates create ambiguity. When it would be right: across batches with suffix such as -retry-1 while staying per-batch unique.
  • ### Scenario framings
  • Document extraction keyed by record identifiers, CI keyed by pull request number or diff hash, retry flows appending -retry-1 within 64 characters. Edge cases with UUIDs and composite keys test sanitization.
  • ### Variant mutations
  • - A key already matching the pattern tests direct use versus unnecessary transformation. - A key with dots or spaces tests sanitization. - Two items sharing the same custom_id in one batch tests per-batch uniqueness enforcement.
R7

Lifecycle is `in_progress` to `ended` with `expires_at = created_at + 24h` and results become available when all requests finish or at 24 hours

A new batch has processing_status: in_progress and expires_at = created_at + 24h. It stays in progress while requests execute. When all finish,.

Partial results are not streamed before ended.

example.ts
typescript
const batch = await client.batches.create({ requests });
let status = batch;
while (status.processing_status !== "ended") {
  await sleep(60_000);
  status = await client.batches.retrieve(batch.id);
}
const lines = await downloadJsonl(status.results_url!);

The 24 hour expiry bounds how long the service holds and schedules work. Within the window it can retry or redistribute shard.

Boundary. Where 24 hours is ample, submit once and wait. Where a consumer deadline is tighter, the window must be sliced into a cadence so no item.

Recurring specifics. Cited as processing_status from in_progress to ended, expires_at = created_at + 24h, GET /v1/messages/batches/{id} for polling, results_url as .jsonl matched.

Wrong answers written against this rule

Proposal. expect incremental tool_result delivery inside batch.

Why it attracts. synchronous loops stream them.

Why it fails. batch delivery is file after ended, not live stream.

When it would be right. synchronous Messages API loops.

Proposal. cancel and resubmit because in_progress after minutes feels stuck.

Why it attracts. impatience.

Why it fails. minutes to an hour is normal.

When it would be right. only when a systematic prompt error guarantees total failure.

Proposal. treat expires_at as purge time.

Why it attracts. 24 hour number appears in multiple places.

Why it fails. expiry ends processing, not storage; retention is 29 days.

When it would be right. never, completion and retention are distinct.

How the same rule gets re-asked
  • - Adding 29 day retention tests confusion between processing window and storage window. - Adding stream: true to a batch request tests knowledge that streaming is unsupported. - Switching consumer from a poller to a human reviewer who checks next morning still tests timing.
R8

Results remain downloadable for 29 days and deletion is explicit via `DELETE /v1/messages/batches/{batch_id}`

After ended, results_url points to a .jsonl file with one object per request keyed by custom_id. It remains available for 29 days after batch creation. Retrieval.

Prompt cache TTLs (5 minutes or 1 hour) govern prefix caching, not batch payload retention.

example.ts
typescript
const status = await client.batches.retrieve(batchId);
if (status.processing_status === "ended" && status.results_url) {
  const text = await fetch(status.results_url).then(r => r.text());
  // parse .jsonl and index by custom_id
  await client.batches.delete(batchId); // remove stored data
}

Asynchronous processing requires storing inputs until processing finishes and outputs until retrieval. A bounded 29 day window caps storage..

Boundary. For non-sensitive workloads, lazy retrieval within 29 days is fine. For GDPR storage-limitation or HIPAA minimization, prompt fetch and delete is required. Research.

Recurring specifics. Cited as results downloadable for 29 days, retrieval via results_url as .jsonl matched on custom_id, DELETE /v1/messages/batches/{batch_id} to remove data,.

Wrong answers written against this rule

Proposal. rely on 24 hour processing window as automatic purge.

Why it attracts. both use 24.

Why it fails. 24 ends processing, not storage.

When it would be right. never.

Proposal. rely on prompt caching TTL to expire batch contents.

Why it attracts. cache expiry sounds like deletion.

Why it fails. caching governs prefix reuse, not batch storage.

When it would be right. never for batch retention.

Proposal. wait for automatic 29 day expiry.

Why it attracts. no extra call.

Why it fails. keeps personal data for weeks, violating minimization.

When it would be right. only for non-sensitive best-effort jobs.

How the same rule gets re-asked
  • - Changing data from customer feedback to patient intake changes regulation but not mechanism. - Changing retrieval from immediate to days later tests whether prompt deletion is still advised. - Adding in-progress batch needing deletion tests knowledge that cancellation is required first.
R9

Batch storage is not covered by Zero Data Retention and carries retention obligations

ZDR is per-feature, not organization-wide. Synchronous Messages API handling of document blocks and context editing can be ZDR-eligible where processing is stateless. The Message Batches API is.

example.ts
typescript
// ZDR-eligible path for sensitive content
await client.messages.create({
  model: "claude-sonnet-4-6",
  messages: [{ role: "user", content: [{ type: "document", source: { type: "base64", media_type: "application/pdf", data: pdfBase64 }}]}]
});
// Non-ZDR path: batch retains payloads until fetch and delete
await client.batches.create({ requests: [{ custom_id: "doc-1", params: { model: "claude-sonnet-4-6", messages: [{role:"user", content: docText}] }}] });

ZDR means no storage after the response. Batch violates that by definition because asynchrony requires holding work. Marking batch.

Boundary. Where data is non-sensitive or retention with prompt deletion is acceptable, batch with fetch-then-delete is viable. Where baseline forbids provider-side retention beyond the response, batch cannot be used for.

Recurring specifics. Cited as Zero Data Retention, data-minimization, storage limitation, standard retention policy, Agent Skills not covered, PDF blocks as ZDR-eligible, context editing as eligible,.

Wrong answers written against this rule

Proposal. extend organization ZDR to cover batch.

Why it attracts. compliance without architecture change.

Why it fails. eligibility is feature-specific and batch is excluded.

When it would be right. never for batch; use synchronous ZDR-eligible surface.

Proposal. rely on prompt caching TTL to satisfy ZDR for batch.

Why it attracts. short TTL sounds like short retention.

Why it fails. caching and batch storage are disjoint.

When it would be right. never for batch payloads.

Proposal. encrypted workspace equals ZDR.

Why it attracts. encryption feels like protection.

Why it fails. ZDR is about retention, not ciphertext; batch still stores plaintext for processing.

When it would be right. as defense-in-depth, not substitute.

How the same rule gets re-asked
  • - Pairing batch with Skills versus PDF support tests distinguishing two non-eligible from two eligible. - Adding 29 day retention tests whether explicit deletion is advised. - Switching regulation from GDPR to HIPAA tests that batch is non-eligible under both.
R10

Per-request outcomes are `succeeded`, `errored`, `canceled`, `expired` and `expired` or `canceled` are not billed

Each line in the result .jsonl carries result.type of succeeded, errored, canceled, or expired. The batch has processing_status: in_progress to ended and expires_at = created_at + 24h. Requests completed.

Handler separates billable from non-billable before deciding retry.

result.json
json
{"custom_id": "doc-1", "result": {"type": "succeeded", "message": {"content": [{"type": "text", "text": "..."}]}}}
{"custom_id": "doc-2", "result": {"type": "errored", "error": {"type": "invalid_request_error", "message": "prompt is too long"}}}
{"custom_id": "doc-3", "result": {"type": "expired", "error": {"type": "expired", "message": "request expired before completion"}}}
{"custom_id": "doc-4", "result": {"type": "canceled"}}

Billing follows work done. Never-processed or caller-canceled requests consume no tokens and are not charged. Processed requests that error still consumed input tokens, so they are charged. Contract makes distinction explicit.

Boundary. For succeeded and errored, caller pays and must decide resubmit based on cause. For expired and canceled, caller does not pay and normally resubmits unmodified.

Recurring specifics. Cited as succeeded, errored, canceled, expired, note that expired or canceled are not billed, and expired connects to 24.

Wrong answers written against this rule

Proposal. treat expired as billable and avoid retry to save cost.

Why it attracts. avoids extra spend.

Why it fails. expired is not billed and retry is correct since work never ran.

When it would be right. only if business no longer needs the item.

Proposal. retry errored unchanged when error is prompt too long.

Why it attracts. uniform retry habit.

Why it fails. same oversized input fails again; chunking required.

When it would be right. transient errored only.

Proposal. infer billing from batch processing_status.

Why it attracts. one field looks global.

Why it fails. billing is per-request.

When it would be right. never.

How the same rule gets re-asked
  • - Error from size to transient server error flips correct retry from chunk to simple retry. - Changing type from errored to expired flips correct retry from modified to unmodified. - Adding explicit billing question tests separation of expired/canceled from succeeded/errored.
R11

Blocking versus latency-tolerant matching is the primary routing test

Routing asks whether someone or something blocks on the result. Blocking means completion is required before the next step can proceed: a pull request cannot merge, a checkout cannot complete, a nurse cannot schedule while on the phone. Latency-tolerant.

example.ts
typescript
// blocking: stays synchronous
const verdict = await client.messages.create({
  model: "claude-sonnet-4-6",
  max_tokens: 4096,
  messages: [{role: "user", content: prDiff}]
});
if (verdict.content[0].text.includes("pass")) allowMerge();

// tolerant: batch
await client.batches.create({
  requests: nightlyDocs.map((doc, i) => ({
    custom_id: `audit-${i}`,
    params: { model: "claude-sonnet-4-6", max_tokens: 4096, messages: [{role: "user", content: doc}] }
  }))
});

Batch trades immediacy for cost and fleet flexibility. Any workflow whose correctness or user experience depends on bounded latency inherits the batch tail.

Boundary. Same model and same prompt can sit on opposite sides of the boundary depending on consumption timing. Archive extraction reviewed next morning is tolerant. The same extraction attached to a checkout-time.

Recurring specifics. Phrased as blocking versus non-blocking, latency-tolerant, overnight, weekly, consumed later versus someone is waiting, developer is waiting, merge gate, real-time. The exam guide is cited as page 20, domain 4.5 for this matching.

Wrong answers written against this rule

Proposal. route both to batch and poll for completion.

Why it attracts. one path with savings.

Why it fails. polling does not create a latency SLA.

When it would be right. only when both are tolerant.

Proposal. keep both synchronous to avoid ordering issues.

Why it attracts. fears result ordering problems.

Why it fails. ordering is solved by custom_id, the real issue is wasted savings.

When it would be right. when both are blocking.

Proposal. route tolerant work synchronously because it is simpler.

Why it attracts. one architecture everywhere.

Why it fails. pays full price for slack that could be discounted.

When it would be right. only when volume is tiny and operational simplicity outweighs cost.

How the same rule gets re-asked
  • - Adding a second scheduled job turns a two-way routing into maximizing savings on two tolerant branches while still protecting the blocking branch. - Framing both as extraction with the same JSON schema but different deadlines tests whether learner keys off deadline not payload. - Moving the gate from style to security review keeps blocking character while changing domain.
R12

A pre-merge or merge-gate that a developer waits on must stay synchronous even when a discount is offered

A pre-merge check appears as a CI job that reads a diff, may query services, and emits pass or fail before the merge button releases. Developers remain at their terminal waiting. The batch API offers 50 percent savings but can take up.

A wrong assignment moves the gate to batch and polls:

The correct assignment keeps the gate synchronous and reserves batch for the overnight report.

example.ts
typescript
// wrong for blocking gate
const batch = await client.batches.create({ requests: [{ custom_id: prId, params: { model: "claude-sonnet-4-6", messages: [{role:"user", content: diff}] }}] });
let status = await client.batches.retrieve(batch.id);
while (status.processing_status !== "ended") { await sleep(5000); status = await client.batches.retrieve(batch.id); }
// developer waits hours

Developer wait is a latency SLA in human terms. Any queueing behind an overnight run, or any asynchronous window, directly lengthens.

Boundary. A deep analysis that runs overnight and posts suggestions as a non-blocking comment can use batch. The same analysis run as a commit hook that must return before merge cannot. Nightly evaluation.

Recurring specifics. Cited as pre-merge check, blocking pre-merge check that must complete before developers can merge, merge gate, blocks merging until complete, developer waits at terminal. Contrasted with technical debt report generated.

Wrong answers written against this rule

Proposal. move both to batch with custom_id per work item and poll aggressively.

Why it attracts. one execution mode.

Why it fails. aggressive polling does not create a bound.

When it would be right. when neither workload blocks.

Proposal. move the gate to batch and keep the nightly job synchronous to give the gate savings.

Why it attracts. gate is higher value per run.

Why it fails. swaps the matching, applying savings where it breaks the SLA.

When it would be right. never for a blocking gate.

Proposal. switch both to batch with a timeout fallback to real-time after minutes.

Why it attracts. appears to hedge.

Why it fails. adds complexity and duplicated spend; correct design matches each workload to its wait property once.

How the same rule gets re-asked
  • - Changing the tolerant job from technical debt to nightly test generation keeps the same blocking versus tolerant contrast while rotating domain language. - Changing the gate from style to code review to security audit keeps blocking but tests whether learner overfits to a specific check type. - Adding rate-limit or cost pressure to the nightly job tests whether learner still keeps the gate synchronous despite cost appearing on the other branch.
R13

Working backward from a consumer deadline determines submission cadence

Given a consumer SLA measured from document arrival to result availability S, and a batch path with maximum processing P = 24h, the time a document can wait before submission W must satisfy W + P <= S. Therefore maximum wait is S - P. Cadence.

A single end-of-day batch fails this math because a document arriving at 8 AM waits 16 hours before submission and can total 40 hours.

example.ts
typescript
const S = 30, P = 24;
const maxWait = S - P; // 6h
const frequency = 4; // 28h worst case: 4h wait + 24h processing = 28h, 2h buffer

The batch clock starts at submission, not at arrival. Arrival-to-deadline includes both queue time and processing time. The only way to bound the sum.

Boundary. Where S is larger than P plus cadence, batch works with a calculated cadence. Where S is less than or equal to P, batch cannot guarantee without splitting the workload type: urgent items must go real-time..

Recurring specifics. Cited as calculation of batch submission frequency based on SLA constraints, 4-hour windows to guarantee 30-hour SLA with 24-hour processing, 30 minus 24 equals 6 hours maximum wait, every.

Wrong answers written against this rule

Proposal. submit one batch per day at a fixed time and accept occasional misses.

Why it attracts. simple schedule.

Why it fails. wait can be up to 24 hours before submission, so worst case 48 hours.

When it would be right. only where occasional misses are acceptable and SLA is not firm.

Proposal. submit every 6 hours to exactly fill 30 hours.

Why it attracts. math hits 30 exactly.

Why it fails. no buffer for submission jitter or fleet tail at 99.9 percent reliability.

When it would be right. where 100 percent guarantee is not required.

Proposal. switch to real-time for all items to avoid math.

Why it attracts. guarantees latency trivially.

Why it fails. eliminates 50 percent savings where cadence already satisfies SLA.

When it would be right. only where SLA is tighter than 24 hours.

How the same rule gets re-asked
  • - Changing cadence from every 4 to every 6 tests buffer understanding. - Changing arrival pattern from batch to continuous tests queue-time calculus. - Changing SLA from 30 to 72 versus 20 tests whether learner recognizes the boundary where batch becomes unavailable.
R14

SLA buffer and timeout buffer reserve time for retries without breaching the deadline

Even where W + P <= S, failures require retry. A buffer reserves part of S for handling retries, quarantining, or re-enqueue. With S = 30h and P = 24h, 6 hours exist beyond.

Single-item retry budgets and dead-letter queues implement the buffer operationally.

example.ts
typescript
const arrival = Date.now();
const deadline = arrival + 30*60*60*1000;
const batchWindow = 24*60*60*1000;
const cadence = 4*60*60*1000;
const buffer = deadline - (arrival + cadence + batchWindow); // 2h for retries

Partial failures are expected at volume. Without reserved time, one transient failure or one oversized document can push the consumer deadline. A buffer decouples the.

Boundary. Best-effort internal jobs can run without buffer and accept late stragglers. A consumer-facing report that feeds a filing or a morning standup cannot. The first can use max.

Recurring specifics. Cited as SLA buffer, retry budget, timeout buffer, safety buffer, 2 hours buffer when submitting every 4 hours, 6 hours maximum wait minus buffer equals.

Wrong answers written against this rule

Proposal. use the whole 6 hour wait and no buffer.

Why it attracts. simpler frequency equals exact SLA.

Why it fails. no time left for retry when failures occur.

When it would be right. only where zero failures occur, which volume invalidates.

Proposal. reserve buffer by delaying batch start.

Why it attracts. buffer sounds like idle time.

Why it fails. buffer should be after processing, not before.

When it would be right. never, the buffer is for recovery, not for starting late.

Proposal. allow unlimited retries inside the buffer.

Why it attracts. maximizes eventual success.

Why it fails. one hanging item can consume the whole buffer and breach the batch.

When it would be right. only with per-item retry budget and quarantine.

How the same rule gets re-asked
  • - Changing reliability from 99 to 99.9 percent increases the required buffer and tightens cadence. - Changing failure count from a few to a cluster tests whether learner still preserves buffer via quarantine rather than sequential retry.
R15

Failure recovery resubmits only the failed `custom_id` set, never the whole batch

After a batch ends, the result .jsonl is filtered by result.type === "errored" or by expired versus succeeded. The custom_id values of the failed lines are extracted and.

example.ts
typescript
const results = await downloadJsonl(resultsUrl);
const failedIds = results.filter(r => r.result.type === "errored").map(r => r.custom_id);
const retryRequests = failedIds.map(id => ({
  custom_id: `${id}-retry-1`,
  params: { model: "claude-sonnet-4-6", max_tokens: 8192, messages: [{role:"user", content: chunkIfNeeded(documentsById[id])}] }
}));
if (retryRequests.length) await client.batches.create({ requests: retryRequests });

Reprocessing successes at the batch discount still pays for tokens already delivered and still risks new failures on already good outputs. Selective retry pays only for the.

Boundary. Where failures are rare and the success set is large, selective retry is strictly better. Where the entire prompt is wrong, a full re-run with a corrected prompt after sample refinement may be warranted,.

Recurring specifics. Phrased as resubmit only the failed documents identified by custom_id, never resubmit the entire batch, resubmit only failures with modifications, surgical retry. Paired with counts such.

Wrong answers written against this rule

Proposal. resubmit the entire batch to keep ordering and handling simple.

Why it attracts. one code path and no filtering.

Why it fails. doubles spend on successes and can introduce new failures on already good items.

When it would be right. never as recovery; only as a fresh run after a prompt overhaul with new batch IDs.

Proposal. discard failures and proceed with successes.

Why it attracts. successes already meet most of the report.

Why it fails. leaves gaps downstream and underreports coverage.

When it would be right. only where downstream tolerates missing items, which compliance and financial jobs do not.

Proposal. switch the pipeline to synchronous to avoid batch failures.

Why it attracts. avoids learning selective retry.

Why it fails. forfeits discount on the tolerant bulk and does not fix per-item causes such as size.

When it would be right. only for the blocking subset.

How the same rule gets re-asked
  • - Changing failure cause from size to transient server error tests whether retry is still selective but without chunking. - Splitting failures into two cause groups tests whether learner separates handling per cause while still remaining selective. - Changing success fraction from 92 percent to 99 percent tests whether learner resists the temptation to ignore the remainder.
R16

Targeted modification by failure cause beats blind retry or global trimming

Not all errored outcomes share a cause. Oversized documents that exceed the context window fail deterministically with prompt too long or similar validation. Transient server errors or network flakes fail probabilistically and may succeed on simple retry. Required fields.

Global trimming of every prompt or splitting every document regardless of cause wastes work on items that were fine.

example.ts
typescript
const buckets = groupByCause(failedResults); // oversize, transient, absentField
const retryOversize = buckets.oversize.map(r => ({
  custom_id: `${r.custom_id}-chunked`,
  params: { model: "claude-sonnet-4-6", max_tokens: 8192, messages: [{role:"user", content: chunkText(documentsById[r.custom_id]) }] }
}));
const retryTransient = buckets.transient.map(r => ({
  custom_id: `${r.custom_id}-retry-1`,
  params: originalParams[r.custom_id]
}));
const manualQueue = buckets.absentField.map(r => ({ recordId: r.custom_id, flag: "field_absent_in_source" }));

Blind retry repeats deterministic failures indefinitely and global modifications pay cost on successes that never needed them. Cause-specific handling fixes only.

Boundary. Where failure mode is uniform, uniform handling is fine: a batch where all 12 failures are oversize can chunk just those 12. Where failure modes are mixed, uniform handling fails: retrying oversize unchanged fails again,.

Recurring specifics. Cited as chunking oversized documents, splitting documents that exceeded context window, increasing max_tokens for oversize, simplifying prompts for unusual structures, adding format-specific few-shot examples, retry transient failures unchanged, diverting genuinely.

Wrong answers written against this rule

Proposal. resubmit all failed requests with identical inputs hoping a fresh attempt clears them.

Why it attracts. uniform and simple.

Why it fails. oversize deterministically fails again.

When it would be right. only for purely transient failures.

Proposal. globally trim each prompt and split every document to prevent any oversize.

Why it attracts. seems preventive.

Why it fails. pays chunking cost on already successful items and on transient failures that needed no change.

When it would be right. only where every document is near the limit.

Proposal. keep retrying absent-field failures with error feedback appended.

Why it attracts. feedback loop feels helpful.

Why it fails. field is genuinely absent, retry never resolves and most retry spend is wasted there.

When it would be right. only for layout or parse errors where feedback actually adds missing structure.

How the same rule gets re-asked
  • - Splitting a 12-failure batch into two cause groups tests per-cause handling within selective retry. - Presenting a proof-of-concept that a batch request tried to call a chunking tool mid-request and returned partial results tests whether learner knows not to repair oversize via in-request tool loops. - Changing oversize handling from chunking to trimming tests that chunking preserves content while trimming risks loss.
R17

Sample-set refinement before the full batch is the largest cost lever

Prompt quality is iterated on a small representative sample covering the range of formats, edge cases, and document types before committing the full volume. A sample of 5 to 10, 50 to 100, or 200 documents is.

example.ts
typescript
const sample = pickRepresentative(documents, 100); // cover formats and edge cases
let prompt = initialPrompt;
for (let i = 0; i < 3; i++) {
  const sampleResults = await testSampling(sample, prompt);
  if (passRate(sampleResults) > 0.9) break;
  prompt = refinePrompt(prompt, failureModes(sampleResults));
}
await client.batches.create({ requests: documents.map(d => ({ custom_id: d.id, params: { model: "claude-sonnet-4-6", messages: [{role:"user", content: prompt + d.text}] }})) });

A 90 percent first-pass rate on 1,000 documents means about 100 retries. A 60 percent rate means 400 retries, four times the retry cost.

Boundary. Where volume is small or deadline is loose enough to allow reprocessing, sampling matters less. Where volume is tens of thousands and deadline is tight,.

Recurring specifics. Cited as refine prompts on a sample set before batch processing, sample set testing 5 to 10 documents, 50 to 100 documents, 200 synchronous or real-time samples, take 5 to 10.

Wrong answers written against this rule

Proposal. submit all 50,000 immediately and iterate on failures.

Why it attracts. starts early and feels productive.

Why it fails. front-loads a high failure rate into the first costly batch before learning.

When it would be right. only where failures are rare and cheap.

Proposal. split into many small sequential batches without sample refinement.

Why it attracts. limits blast radius per batch.

Why it fails. high failure rate is distributed, not reduced.

When it would be right. only where prompt is already validated.

Proposal. use 200 synchronous calls to validate quality before committing to batch.

Why it attracts. real-time feedback.

Why it fails. synchronous sample incurs full price versus a batched or small offline sample.

When it would be right. only where synchronous observability is strictly needed.

How the same rule gets re-asked
  • - Changing sample size from 50 to 2,000 tests cost of the sampling phase itself. - Changing timeline from two weeks to one night tests whether sampling still fits. - Adding real-time API for the sample tests whether learner knows that sampling can be done more cheaply and still apply to the batch prompt.
R18

Progressive sequential batches with prompt refinement between submissions improve first-pass rate over time

Where a two-week deadline allows many 24 hour cycles, the document set is split into sequential batches of a few thousand, and the prompt is refined between each based on observed failure modes. Later batches inherit improved.

example.ts
typescript
let prompt = initialPrompt;
const slices = chunk(documents, 5000);
let allFailed: string[] = [];
for (const slice of slices) {
  const batch = await client.batches.create({ requests: slice.map(d => ({ custom_id: d.id, params: { model: "claude-sonnet-4-6", messages: [{role:"user", content: prompt + d.text}] }})) });
  const fails = await waitAndClassify(batch);
  prompt = refinePrompt(prompt, fails);
  allFailed.push(...fails.map(f => f.custom_id));
}

Failure modes are not uniform across a heterogeneous document set. Early batches reveal layout, date, and party patterns that a single upfront sample may not.

Boundary. Where deadline is tight, one well-sampled batch plus selective retry may be faster than sequential cycles. Where deadline is long, sequential refinement yields higher total success with.

Recurring specifics. Cited as progressive sequential batches with prompt refinement between each, 10 batches of 5,000 over 14 days, 10 days minimum plus analysis time still within 14 days, batch 1 to 2.

Wrong answers written against this rule

Proposal. submit all 50,000 via batch, then resubmit failures in successive batches refining between each.

Why it attracts. one large submission is simpler.

Why it fails. waits for 50,000 to complete before learning anything, burning a cycle.

When it would be right. only where document set is homogeneous and failure modes are already known.

Proposal. do a single large upfront sample of 2,000 via real-time API and then one full batch.

Why it attracts. learns before committing.

Why it fails. sample not fully representative at scale, one-time refinement misses modes that appear later, and real-time sample is expensive.

When it would be right. where modes are well understood.

Proposal. use real-time for all due to deadline risk.

Why it attracts. avoids 24 hour window anxiety.

Why it fails. 14 days comfortably fits multiple 24 hour cycles, so risk is false and cost doubles.

When it would be right. where deadline is shorter than one batch window.

How the same rule gets re-asked
  • - Changing deadline from two weeks to two days tests whether sequential still fits. - Changing document set heterogeneity from homogeneous invoices to heterogeneous contracts tests value of progressive learning. - Changing refinement target from adding few-shot date examples to fixing party identification tests incremental nature of improvement.
R19

The batch request model and the multi-turn tool loop tension and its documented position

The reference material often states that the batch API does not support multi-turn tool calling within a single request and that tool execution mid-request is impossible, directing blocking or tool-heavy work to synchronous calls. The live platform documentation lists tool use including server tools, vision, system messages, and extended thinking as batched capabilities. The documented position is broader.

Where items claim tool calling is impossible inside a batch, flag the tension as an open question rather than asserting either side alone. Note both positions and that the documented scope is wider.

example.ts
typescript
// synchronous loop: caller can observe tool_use and feed back tool_result in the next turn
let messages = [{role:"user", content: prDiff}];
while (true) {
  const resp = await client.messages.create({ model: "claude-sonnet-4-6", tools: [{name:"fetch_file"}], messages, tool_choice: {type:"auto"} });
  if (resp.stop_reason === "end_turn") break;
  const toolResults = await executeTools(resp.content);
  messages.push({role:"assistant", content: resp.content}, {role:"user", content: toolResults});
}
// batch: each requests element is one self-contained unit, no mid-unit caller interposition.
// For tool-dependent steps, split into separate batch requests rather than expecting one request to pause.

The batch surface is asynchronous and file-based by design. Even if tool definitions and tool use can be batched, the turn-by-turn decision of which tool to call next based on.

Boundary. A single-turn extraction with tools that do not require caller branching can be expressed as one batched request and is not disqualified by the tool shape alone. An iterative loop where Claude requests imports, base classes, and tests mid-analysis and must receive each file before continuing.

Recurring specifics. Items phrase the limitation as no multi-turn tool calling within a single batch request, cannot execute tools mid-request and return results for Claude to continue, cannot do multi-turn conversation state, needs multi-turn interaction. Items that.

Wrong answers written against this rule

Proposal. the batch API does not accept tool definitions at all.

Why it attracts. explains the failure as a field error.

Why it fails. the documented broader position lists tool use as batched.

When it would be right. never as the current documented position.

Proposal. the issue is only latency, not architecture, and polling fixes it.

Why it attracts. retains batch for everything.

Why it fails. even with patience, a control-flow loop that needs mid-request interposition does not fit one batch unit.

When it would be right. only where latency alone is the blocker.

Proposal. lack of custom_id is the blocker.

Why it attracts. correlation is a frequent batch topic.

Why it fails. custom_id solves correlation, not mid-request continuation.

When it would be right. never for this loop issue.

How the same rule gets re-asked
  • - Changing the loop from one tool definition to several tests whether learner thinks more tools helps. - Changing the workload from code review to translation clarification tests domain transfer of the same limitation. - Claiming two sequential calls per item are allowed inside one batch tests whether learner accepts an invented limit.
R20

Unsupported parameters inside a batch request include `stream: true`, fast-mode speed, thread fields, cache and context routing hints, and `max_tokens: 0`

Batch requests are asynchronous, file-based, and self-contained. Several synchronous or stateful parameters are therefore unsupported inside batch params. Specifically: stream: true for live token streaming, fast-mode speed (the premium speed path on Opus),.

Batch output is not streamed, fast mode is a synchronous latency product, threads are stateful while batch requests are independent, routing hints are synchronous, and the max-tokens-zero cache-write trick is a synchronous playground feature.

result.json
json
{
  "requests": [
    {
      "custom_id": "maint-log-001",
      "params": {
        "model": "claude-sonnet-4-6",
        "max_tokens": 4096,
        "messages": [{"role": "user", "content": "Analyze maintenance log ..."}]
      }
    }
  ]
}

Streaming assumes a held connection, batch assumes file delivery. Fast mode trades cost for speed on a synchronous path and cannot combine with a discount that trades speed for cost..

Boundary. Inside batch, those fields must be absent. Outside batch, on the synchronous Messages API, many are valid and useful: stream: true for interactive display, cache breakpoints for shared.

Recurring specifics. Cited as unsupported parameters are stream: true, fast-mode speed, thread fields, cache and context routing hints, and max_tokens: 0, remove unsupported streaming, synchronous-speed,.

Wrong answers written against this rule

Proposal. keep stream: true inside batch and consume tokens as generated.

Why it attracts. streaming feels like faster delivery.

Why it fails. batch output is not a live stream.

When it would be right. only on synchronous calls.

Proposal. keep thread state to coordinate items inside a batch.

Why it attracts. suggests coordination.

Why it fails. batch requests are independent, threads are stateful.

When it would be right. only in a synchronous multi-turn session.

Proposal. use max_tokens: 0 to pre-warm cache inside batch.

Why it attracts. sounds like an optimization.

Why it fails. that cache-write path is not supported inside batch.

When it would be right. only on synchronous cache-priming requests.

How the same rule gets re-asked
  • - Rotating which unsupported field is included tests whether learner recognizes the whole set. - Adding a valid batched field like output_config.format tests whether learner strips valid structured output alongside invalid fields.
R21

Prompt caching works inside batches, the discounts stack, and cache duration matters

Prompt caching operates on prefix reuse across requests that share a leading block such as a system prompt or a shared rubric. Inside batches, the same prefix detection applies: requests that share the prefix can read it from cache. Both discounts apply together: cache-read tokens are billed at the cached rate and then.

A priming request with the shared prefix can warm the cache before the bulk batch is submitted.

example.ts
typescript
const sharedPrefix = complianceReference; // 12,000 tokens stable
const requests = documents.map(d => ({
  custom_id: d.id,
  params: {
    model: "claude-sonnet-4-6",
    max_tokens: 4096,
    system: [{ type: "text", text: sharedPrefix, cache_control: { type: "ephemeral", ttl: "1h" } }],
    messages: [{ role: "user", content: d.text }]
  }
}));

Caching saves reprocessing of a repeated prefix. Batch saves cost for tolerating latency. The two are orthogonal and therefore composable. Duration matters because batch execution stretches.

Boundary. Where requests share a large stable prefix, caching inside batch is high ROI and the 1-hour TTL is preferred. Where each request has a unique body and no stable prefix, such.

Recurring specifics. Cited as prompt caching works inside batches and discounts stack, 1-hour cache improves hit rates because batches often run 5 to 60 minutes, use 1-hour duration with shared-prefix priming,.

Wrong answers written against this rule

Proposal. caching is disabled inside batches.

Why it attracts. async feels separate from caching.

Why it fails. platform enables caching inside batches.

When it would be right. never currently.

Proposal. use only the 5-minute cache for all batch requests.

Why it attracts. shorter TTL sounds cheaper.

Why it fails. many batches exceed 5 minutes, so hits are low.

When it would be right. only where every batch completes well under 5 minutes, which is uncommon at bulk.

Proposal. disable caching for batches because count discount already applies.

Why it attracts. avoids a second mechanism.

Why it fails. discounts are additive and together yield highest savings.

When it would be right. only where no shared prefix exists.

How the same rule gets re-asked
  • - Switching TTL from 5 minutes to 1 hour tests whether learner matches duration to execution length. - Adding priming request tests whether learner understands warming before bulk. - Removing shared prefix tests whether learner still recommends caching where no hit is possible.
R22

Fast mode and batch are mutually exclusive and oppositely priced

Fast mode is a premium speed feature on Claude Opus models that delivers faster output at higher per-token pricing. The Message Batches API is an asynchronous throughput feature that.

Message Batches API: processing model asynchronous, up to 24h, price direction 50 percent discount on input and output. Fast mode: processing model synchronous, low latency, price direction premium, for example Opus 4.8 at 10 per million input and 50 per million output.

A real-time SLA where leadership accepts higher spend is a fast-mode candidate. An overnight bulk job where cost is the pillar is a batch candidate. Selecting one excludes the other.

Fast mode spends resources to reduce time. Batch saves cost by allowing time. One buys speed with money, the other buys money.

Boundary. Live match summaries with a strict real-time SLA where smaller models miss the accuracy bar are fast-mode suitable. Nightly report generation where analysts accept a 24 hour window is.

Recurring specifics. Cited as fast mode is not available with the Batch API, the Batch API gives a 50 percent discount while fast mode is premium-priced, batch cannot be combined with fast mode for extra.

Wrong answers written against this rule

Proposal. enable fast mode inside batch to get both speed and discount.

Why it attracts. promises the best of both.

Why it fails. combination is unsupported; fast mode and batch are exclusive.

When it would be right. never.

Proposal. use batch to cut cost on a live workload with a tight SLA.

Why it attracts. discount is tempting.

Why it fails. batch makes real-time delivery impossible.

When it would be right. only where SLA tolerates asynchronous delivery.

Proposal. replace batch with fast mode for an overnight job to finish sooner.

Why it attracts. speed feels good.

Why it fails. overnight has no speed requirement; premium pricing wastes budget.

When it would be right. only where the overnight window is actually tight.

How the same rule gets re-asked
  • - Fixing model choice with evals that forbid downsizing tests whether learner still chooses fast mode for speed rather than model swap. - Changing SLA from strict real-time to overnight tests whether learner swaps from fast mode to batch.
R23

Positional or ordering correlation is unreliable and fails under batch semantics

Batch result files are not guaranteed to be in submission order. A consumer that does results[i] to match requests[i] assumes order. That assumption breaks when.

Sorting by output length or by any model-generated field also does not recreate submission order.

example.ts
typescript
const map = new Map(results.map(r => [r.custom_id, r]));
for (const doc of documents) {
  const line = map.get(doc.id);
  if (!line || line.result.type !== "succeeded") handleFailure(doc.id, line);
}

Concurrent execution decouples completion order from submission order. Result generation is a merge of completion events, not a replay of input order. Position is an artifact of iteration order, not a contract.

Boundary. Where a single request is submitted synchronously, order is trivially preserved because there is only one item. For any batch with more than one item,.

Recurring specifics. Phrased as batch results return in unpredictable order, results are not guaranteed to return in submission order, positional array correlation is unreliable, batch.

Wrong answers written against this rule

Proposal. rely on submission order for correlation.

Why it attracts. mirrors how the request array was built.

Why it fails. generation order is undefined.

When it would be right. never for batch results.

Proposal. sort by output length to recover order.

Why it attracts. length is visible and seems deterministic.

Why it fails. length has no relation to position.

When it would be right. never for correlation.

Proposal. avoid batch entirely to avoid ordering issues.

Why it attracts. removes the ordering question by removing batch.

Why it fails. custom_id solves ordering; avoidance forfeits savings.

When it would be right. only where ordering is genuinely required and correlation cannot be added, which batch already allows.

How the same rule gets re-asked
  • - Changing batch size from micro to large tests whether learner still rejects positional matching. - Adding a claim that single-item batches preserve order tests whether learner scales understanding.
R24

Parsing model output to recover correlation is fragile versus using `custom_id`

Some consumers add the database record ID into the prompt and ask the model to echo it back, then parse the identifier from text. That couples correctness to model phrasing. A preamble such as Here are the.

versus brittle:

Model output is probabilistic, conversion of structured tasks to text is lossy, and validation failures can still produce partial text. custom_id is deterministic.

Boundary. Where human review is in the loop and the output is read by a person, echoing an identifier may be convenient. For any automated downstream join, parsing is the wrong.

Recurring specifics. Cited as parse the review content to identify which pull request it refers to based on file names, include the database.

Wrong answers written against this rule

Proposal. parse file names mentioned in the review to identify pull request.

Why it attracts. names are in the diff.

Why it fails. parsing couples to wording and omits results.

When it would be right. as a secondary display hint only.

Proposal. include record ID in prompt and extract from response.

Why it attracts. avoids adding a request field.

Why it fails. same fragility as above.

When it would be right. only for human-readable rendering, not for join.

Proposal. store batch request IDs in database for manual correlation.

Why it attracts. sounds like proper tracking.

Why it fails. batch ID identifies the job, not per-request identity, and still leaves matching to brittle text.

When it would be right. only as a superset that includes per-request custom_id.

How the same rule gets re-asked
  • - Adding a well-formed JSON request to echo an ID tests whether learner still rejects parsing despite cleaner output. - Changing text format from verbose prose to templated JSON still tests that parsing is the wrong layer.
R25

Polling with a short fallback does not make batch suitable for blocking work

A common hedge for the blocking mismatch is to submit the gate to batch, poll briefly, and fall back to a synchronous call if the batch does not return quickly. That adds a poll loop, a timeout, duplicate spend.

Versus simple:

example.ts
typescript
// anti-pattern: batch with short fallback
const batch = await client.batches.create({ requests: [gateRequest] });
await Promise.race([pollUntilEnded(batch.id), sleep(5*60*1000)]);
if (!batchDone) return await client.messages.create(gateParams); // paid twice when slow
example.ts
typescript
// correct for blocking
return await client.messages.create(gateParams);

Fallback does not change the batch window, it only adds a timer and a second code path. The synchronous result is still.

Boundary. Fallback can be justified as a last-mile salvage for a tolerant workload near its consumer deadline, such as rescuing a few stragglers.

Recurring specifics. Phrased as switch both to batch with a timeout fallback to real-time if batches take too long, polling does not.

Wrong answers written against this rule

Proposal. move both to batch with status polling to verify completion before consumption.

Why it attracts. retains batch discount.

Why it fails. no latency SLA, developers still wait.

When it would be right. only where consumption is later.

Proposal. move both to batch with fallback after five minutes.

Why it attracts. feels like hedging.

Why it fails. adds complexity and duplicate cost without fixing the matching error.

When it would be right. only to salvage a tolerant tail.

Proposal. keep polling aggressively.

Why it attracts. faster detection.

Why it fails. detection speed does not bound availability.

How the same rule gets re-asked
  • - Changing fallback timeout from five minutes to thirty seconds makes the flaw more obvious but does not change correctness. - Changing the gate from style to security review preserves the blocking character while testing whether fallback temptation persists.
R26

Capacity isolation requires separate queues or workspace-scoped rate limits, not timeouts or model swaps

Interactive and batch workloads that share an organization-level endpoint contend on rate limits measured as requests per minute and input and output tokens per minute. Large batch runs can legitimately consume capacity that the production assistant needs, producing 429 rate_limit_error responses and missed SLAs.

Retries on 429 must back off according to server guidance, not tight-loop hammer the exhausted limit.

example.ts
typescript
// organization-level pool: shared
// workspace-isolated: batch capped
await anthropic.workspaces.update(batchWorkspaceId, { rate_limits: { requests_per_minute: 500 } });

Organization limits cap aggregate usage, not guaranteed capacity per workload. Without partitioning, the batch job and the production assistant draw from the same.

Boundary. Where daily throughput is affordable but bursts cause spikes, priority queues can help during moderate contention but still share the same saturated endpoint. Where the outage.

Recurring specifics. Cited as rate limits at organization level across requests per minute and input and output tokens per minute, 429 rate_limit_error,.

Wrong answers written against this rule

Proposal. set a monthly spend limit on the organization so batch cannot crowd interactive.

Why it attracts. spend feels like a cap.

Why it fails. spend limits govern dollars per month, not requests per minute; a batch can cause 429s while spending little.

When it would be right. for budgeting, not for throughput isolation.

Proposal. retry immediately in a tight loop on 429 until success.

Why it attracts. seems resilient.

Why it fails. hammers an exhausted limit and degrades responsiveness.

When it would be right. never; back off per retry guidance.

Proposal. move interactive to a smaller model but keep shared queue.

Why it attracts. smaller model may reduce latency.

Why it fails. both workloads still share the saturated endpoint, quality change is imposed without isolation.

When it would be right. only after isolation is already in place and model choice is independently evaluated.

Proposal. restate SLA using documented rate limits as guaranteed throughput.

Why it attracts. documentation looks like a promise.

Why it fails. limits are maximum allowed, not guaranteed minimums.

When it would be right. never.

How the same rule gets re-asked
  • - Adding a burst that is short versus sustained tests whether pause-and-restart versus workspace caps is appropriate. - Changing data from meter analytics to document classification preserves the noisy-neighbor shape with different domain language.
R27

Per-item error isolation limits blast radius compared with batch-level failure handling

When 100 documents share one grouped API call, one malformed document can affect handling of the whole group. When each document is a separate request inside a batch keyed by.

Retry budgets and dead-letter queues operationalize isolation: a failed item gets bounded retries within the SLA buffer, then is quarantined without holding up the batch, and the batch reports partial success.

example.ts
typescript
// per-item isolation inside batch
const requests = documents.map(d => ({ custom_id: d.id, params: { model: "claude-sonnet-4-6", messages: [{role:"user", content: d.text }] } }));
// versus grouped prompt: one malformed document can tangle per-item error handling

Coupling items into one prompt or one handler couples their fate. Decoupling into independent requests or tasks lets the majority.

Boundary. Where items are genuinely one document that requires cross-item reasoning, a grouped prompt may be correct. Where items are independent such as invoices, posts, or tickets, per-item isolation is correct. Middleware.

Recurring specifics. Cited as batch processing must isolate individual document failures so malformed inputs do not affect the rest, per-document error isolation, blast radius,.

Wrong answers written against this rule

Proposal. increase batch size to reduce failure proportion.

Why it attracts. dilution sounds like it helps.

Why it fails. larger group amplifies blast radius and cost per failure event.

When it would be right. never as a fix for error handling.

Proposal. catch at batch level and retry entire batch.

Why it attracts. one handler.

Why it fails. wastes successes and amplifies cost.

When it would be right. never for partial failures.

Proposal. run all items in a shared thread with a global timeout.

Why it attracts. simple concurrency.

Why it fails. one hanging item blocks all; single try-catch cannot prevent hangs.

When it would be right. only where items are strictly sequential and order matters.

How the same rule gets re-asked
  • - Changing document set from invoices to posts preserves independence but changes domain. - Changing failure from malformed input to timeout or arithmetic error preserves isolation need but changes detection.
R28

Coverage must be reported honestly as a fraction that succeeded, not as complete

When a batch of 500 returns 462 successes and 38 expiries, errors, or deletions, downstream consumers must be told that coverage is partial. The durable reporting shape distinguishes reviewed.

Rollup metrics hide gaps when failures are dropped. Stakeholder decisions assume coverage is complete when the label says complete. Distinguishing.

Boundary. Where downstream is best-effort and gaps have no consequence, a summary count may suffice. Where coverage is contractual or security-relevant, gaps must be.

Recurring specifics. Cited as report audit complete without qualification is inaccurate, must report 462/500 with reasons, distinguish reviewed and found nothing from could not be reviewed, marking.

Wrong answers written against this rule

Proposal. report Audit complete because 92 percent is close enough.

Why it attracts. high fraction feels sufficient.

Why it fails. misrepresents coverage and hides unreviewed risk.

When it would be right. only where no downstream action depends on the missing 8 percent.

Proposal. silently exclude the 38 from the final report.

Why it attracts. report looks clean.

Why it fails. gaps become invisible and unrecoverable.

When it would be right. never for audit or review.

Proposal. re-run all 500 because 38 failed.

Why it attracts. seems thorough.

Why it fails. wastes 462 successes; targeted follow-up is cheaper.

When it would be right. only where the entire run shared a systemic prompt error.

Proposal. mark all 12 errors as approved since probably fine.

Why it attracts. removes uncertainty.

Why it fails. treats unreviewed as passing, which is the most dangerous misreport.

When it would be right. never.

How the same rule gets re-asked
  • - Changing failure from timeout to deleted pull request tests whether learner still reports distinctly. - Changing consistency from random to repeated on the same subset tests whether learner investigates structural cause rather than treating it as random tolerance.
R29

Hybrid routing inside one product splits latency-sensitive and latency-tolerant workloads across APIs

A single product often hosts both a checkout-time upgrade that must populate while a driver or nurse waits and an archival digitization job for a research dashboard due next quarter. The correct architecture keeps the interactive path.

Continuous ingestion scenarios apply the same split by batching standard reports on a cadence while routing urgent exception reports to real-time.

example.ts
typescript
function route(task) {
  if (task.latency === "blocking") return client.messages.create(params(task));
  return queueForBatch(task); // collected and submitted every few hours
}

Latency requirement is per item, not per product. Treating all items uniformly either breaks the interactive SLA or overspends on the tolerant bulk. Splitting by wait.

Boundary. Where every item shares the same deadline, routing is uniform. Where deadlines differ, hybrid is correct. A support ticket system that needs results within 30 minutes for exception reports but can archive standard reports is.

Recurring specifics. Phrased as route standard reports to the Batch API for 50 percent savings and route urgent exception reports to the real-time Messages API, keep real-time for customs declarations and use.

Wrong answers written against this rule

Proposal. use batch for both to apply savings uniformly.

Why it attracts. one savings number everywhere.

Why it fails. urgent path misses its 30 minute business alert window.

When it would be right. only where every path tolerates the 24 hour window.

Proposal. use synchronous for both to keep latency consistent.

Why it attracts. one architecture everywhere.

Why it fails. wastes discount on volume that needs no speed.

When it would be right. only where every path is blocking.

Proposal. queue all and submit hourly batches, flagging urgent documents for expedited handling when results return.

Why it attracts. feels like prioritization.

Why it fails. hourly queue still waits up to 60 minutes before submission, so 30 minute urgent SLA already breached before flag is read.

When it would be right. only where urgent SLA tolerates the batch window.

How the same rule gets re-asked
  • - Switching urgency from 30 minutes to seconds tightens the failure of batch on the urgent branch. - Keeping schema constant across both paths tests whether learner keys on deadline rather than extraction complexity. - Changing volume split from 70 percent batchable to 50 percent tests savings math but not the routing principle.
R30

Choosing among batch, managed agents, and a single prompt depends on interaction shape not on the word asynchronous

Three surfaces overlap on the word asynchronous but serve different shapes. The Message Batches API processes many independent, self-contained requests at a discount. Claude Managed Agents provide a managed harness that executes long-running, multi-step agentic loops with tool use in Anthropic-managed infrastructure for tasks the.

Raising effort or adding extended thinking does not replace tool iteration. Holding a streaming connection open for hours is fragility, not a design.

example.ts
typescript
// many independent: batch
await client.batches.create({ requests: docs.map(d => ({ custom_id: d.id, params: { model: "claude-sonnet-4-6", messages: [{role:"user", content: d.text}] }})) });
// long investigation with many tool calls over hours: managed agents session
await agents.createSession({ task: "investigate anomaly", tools: ["query_pipeline_logs", "run_diagnostics"] });
// cohesive fit: single prompt
await client.messages.create({ model: "claude-sonnet-4-6", max_tokens: 4096, messages: [{role:"user", content: singleDocument }] });

Batch is a throughput and pricing mechanism, not a managed execution environment for tool loops. Delegating fetch_full_text style tools to the model still requires the caller to execute those client-side tools and return.

Boundary. For large independent offline volumes with no cross-item reasoning and no per-request tool loop, batch is correct. For autonomous investigations that autonomously query logs, read configs, and run scripts across many steps over hours, managed agents are correct. For a.

Recurring specifics. Cited as the Message Batches API cannot execute tools mid-request and continue, managed harness runs sessions in Anthropic-managed infrastructure, custom agent loops on Messages API require the application.

Wrong answers written against this rule

Proposal. route everything through batch because it is asynchronous.

Why it attracts. one async label everywhere.

Why it fails. batch does not host a long interactive tool loop.

When it would be right. only for independent units.

Proposal. build a custom synchronous loop that holds streaming open for hours.

Why it attracts. keeps control.

Why it fails. operational burden and connection fragility the team wanted to avoid.

When it would be right. only where fine-grained control is mandatory.

Proposal. add a reviewer subagent chain for a cohesive single-document task.

Why it attracts. rigor signal.

Why it fails. coordination cost threatens latency for no measurable benefit.

When it would be right. only where evaluation shows the chain improves quality.

How the same rule gets re-asked
  • - Stating that results are reviewed the next morning preserves asynchronous timing but does not make batch correct for tool-heavy iteration. - Switching from anomaly investigation to transcode validation preserves the long-running, multi-tool shape with different domain language.
Weekly compliance audit that saves 50 percent without breaking the transaction screen
A production walkthrough with the reasoning chain made explicit.

A financial compliance team runs two workflows. The first is a weekly audit over 5000 transactions whose results are consumed Monday morning, taking 6 hours of compute at full price on the synchronous API. The second is a real-time transaction screening system that must complete within 200 milliseconds of each transaction. A proposal to move the entire audit to the Batch API for 50 percent savings is evaluated against the matching rule: the weekly audit is latency-tolerant with results consumed later and fits batch, the real-time screen is blocking and latency-intolerant and must remain synchronous. The final decision moves only the weekly audit, so the saving applies to that cost alone.

Implementation starts with a 50-transaction sample covering the full range of edge cases: large amounts, international currencies, unusual merchant categories, refunds, and partial captures. Three prompt iterations on the sample refine few-shot examples for the unusual categories until sample first-pass accuracy reaches 92 percent. The full batch of 5000 transactions is then submitted with the refined prompts. The first-pass success rate holds at 90 percent, producing 500 failures, compared to the 60 percent baseline without refinement that would have produced 2000 failures, a direct cost demonstration.

Failure handling parses the batch results, groups the 500 failures by custom_id, and clusters them into three patterns. Oversized transactions that hit context limits account for 280 cases and are chunked into separate line-item requests for the retry batch. International transactions with unusual currency notation account for 150 cases and receive a format-specific few-shot example. Low confidence classifications account for 70 cases and receive a refined prompt with stricter criteria. The retry batch of 500 completes in about 3 hours at batch pricing, well inside the overnight window.

The scheduling discipline preserves the consumer guarantee. The consumer deadline is audit results by Monday morning, so with a 24 hour maximum processing window the final batch must be submitted by Sunday morning. The team submits every 6 hours starting Friday afternoon, so the last batch goes out Saturday evening and finishes by Sunday evening with the 24 hour safety margin intact regardless of whether individual batches finish early.

Distinctions that decide answers

ThisNot thisHow to tell them apart
Synchronous Messages APIBatch APISynchronous returns immediately at full price, batch returns within 24 hours at 50 percent lower cost. Use synchronous for blocking, batch for latency-tolerant.
Blocking workflowLatency-tolerant workflowBlocking has someone waiting for the result such as pre-merge checks, latency-tolerant consumes results later such as overnight reports. The matching rule is the most tested concept in this task.
Best-case latencyWorst-case latencyBest case is often much less than 24 hours, worst case is the guaranteed 24 hours. Design around worst case, not best case, because there is no SLA.
custom_id correlationAnonymous requestscustom_id is the unique identifier per request used to match responses to requests. Without it batch results cannot be correlated and failure handling is impossible.
Resubmitting only failuresResubmitting the entire batchResubmitting only the failed custom_id values with targeted modifications saves cost on already successful documents, resubmitting the whole batch wastes cost.
Queue with bounded latencyBatch with fixed collectionA queue processes items as they arrive with bounded per-item latency, batch processes a fixed collection together with no per-item guarantee. Queues suit continuously arriving tolerant workloads, pure batch suits one-shot large jobs.

Traps

Switch all workflows to batch for cost savings

The tempting answer. Move every workflow to batch because 50 percent savings applies uniformly.

Why it fails. Blocking workflows where developers wait for results must remain synchronous. Batch has up to 24 hours with no SLA and will block merges unpredictably regardless of how often it finishes early in practice.

What is correct. Keep blocking workflows on synchronous and move only latency-tolerant workflows to batch, with the saving applied to the tolerant subset alone.

Assume batch results arrive quickly because they often do

The tempting answer. Design a blocking workflow around typical batch speed such as minutes rather than the guaranteed maximum.

Why it fails. Typical latency is much less than 24 hours but there is no SLA, so designing for best case is unsafe for anything blocking.

What is correct. Design around the 24 hour maximum and keep any workflow that cannot tolerate that window on synchronous.

Assume tools cannot appear in a batched request at all

The tempting answer. Read the absence of an interactive loop as tools being unsupported inside a batch.

Why it fails. Documentation lists tool use including server tools, multi-turn history, and extended thinking among what can be batched. What is missing is your own code reading an intermediate result and choosing the next request, because nothing runs on your side until the batch ends.

What is correct. Batch anything that completes as one asynchronous unit, and use the synchronous path only when your control flow must inspect an intermediate result before deciding what to send next.

Resubmit the entire batch when some items fail

The tempting answer. Resubmit the full batch because tracking which items failed requires extra bookkeeping.

Why it fails. Already successful documents are billed again for no reason, which directly wastes the cost savings the batch was meant to capture.

What is correct. Identify failures by custom_id, look up the original inputs, and resubmit only the failed items with the modification each pattern needs.

Skip prompt refinement before a large batch

The tempting answer. Submit the full 5000-document batch first and refine after seeing results because refinement feels like extra work.

Why it fails. A 60 percent first-pass rate means four times the retry count of a 90 percent rate, so the cost of unrefined submission exceeds the cost of a few sample iterations.

What is correct. Refine on 5 to 10 representative documents first, then submit the full batch with the improved prompts.

Poll for status instead of working backwards from SLA

The tempting answer. Monitor batch status with polling to decide when to submit the next batch.

Why it fails. Polling does not solve the scheduling problem, the platform may still hold a batch for the full window regardless of polling frequency.

What is correct. Work backwards from the consumer deadline to determine the latest submission time for the final batch, then submit at fixed intervals of 4 to 6 hours inside the buffer.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
Maximum batch size and the split discipline

Up to 100000 requests or 256 MB per batch, whichever is first, so a 200000-document audit must be split across multiple batches and the per-batch processing window applies to each.

Message Batches API
Batch versus queue as distinct latency patterns

A queue is FIFO with bounded per-item latency, batch is a fixed collection with no per-item guarantee. Queues fit continuously arriving tolerant workloads that collect every 6 hours then submit, pure batch fits one-shot large jobs.

Message Batches API
Grouped failure patterns drive targeted modifications

Failures cluster into oversized documents needing chunking, unusual structures needing simplified prompts, and structural variety needing format-specific few-shot examples, each handled with its own retry modification rather than a single generic retry.

Message Batches API
Build it
Route by latency class, refine on a sample, and schedule from the SLA
  1. List five workflows in a hypothetical organisation and categorise each as blocking synchronous or latency-tolerant batch-eligible with a one-sentence justification per workflow.
  2. Define a batch submission for twenty documents using the Message Batches API format with a unique custom_id, model, max_tokens, and messages per document.
  3. Implement failure handling that parses batch results, filters by type errored versus succeeded, extracts the custom_id values of failures, looks up the original documents, and creates a retry batch with a targeted modification per pattern.
  4. Calculate the submission frequency needed to guarantee a 30 hour SLA given the 24 hour maximum window, showing 30 minus 24 equals 6 hours of buffer and submission at least 30 hours before the consumer deadline every 4 to 6 hours.
  5. Create a five-document sample set covering the range of document types and edge cases, iterate the extraction prompt two to three times on the sample to raise accuracy, then submit the full twenty-document batch and compare first-pass success with and without refinement.

Verify. Each workflow lands in the correct API class, the batch is correlated through custom_id, the schedule respects the 24 hour guarantee, and sample refinement measurably reduces retry count before the full batch runs.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Task 4.616 min

Multi-Pass Review

Split large audits into per-file passes plus an integration pass from an independent instance, with calibrated confidence routing to focus human review where it matters.

What you need to know

When Claude reviews its own output in the same session it starts at a disadvantage because it still carries the reasoning it used to generate that output. The model remembers why it chose each approach, classified each finding at a particular severity, or selected certain values, and is less likely to question those decisions. This is not a defect to argue with but a property to design around, and the design that works is an independent review instance: a separate Claude invocation without the prior reasoning context that approaches the output fresh and judges it on what it actually says.

The exam tests this decision directly. When presented with options for improving review quality, an answer that adds please review carefully to the same session is never the correct improvement over an answer that uses a separate model instance. The separate instance is significantly more effective at catching subtle issues because it carries no prior commitment to the decisions it is about to evaluate, while the self-review instance filters evidence through the lens of why it already chose that path.

Large reviews such as multi-file pull requests, complex extraction pipelines, and broad code audits suffer from attention dilution when processed in a single pass. The symptoms are recognisable: detailed feedback on some files and superficial comments on others, obvious bugs missed in the middle of the review, and contradictory findings where a pattern is flagged as problematic in one file while identical code is approved in another. A larger context window does not fix this. The problem is not capacity but attention quality, the model can hold more text but still spreads its attention unevenly across files.

The fix is to split the review into focused passes. Pass one is per-file local analysis, where each file is examined individually with a focused review prompt so every file receives full attention regardless of its position in the batch. This eliminates the inconsistent depth and the middle-file misses, because each invocation sees only one file. Per-file analysis also parallelises naturally since files are independent inputs, so all per-file reviews can run concurrently. Pass two is cross-file integration, where a separate pass receives all per-file findings and checks for cross-file issues that no single-file review could identify: data flow between modules, consistent API usage across services, dependency conflicts, and contradictions among the per-file findings themselves.

Confidence-based routing complements multi-pass review. The review prompt asks the model to report a confidence value typically from 0.0 to 1.0 alongside each finding plus the reasoning for the score. High confidence findings report directly to developers, low confidence findings route to a human review queue. Raw self-reported confidence is poorly calibrated and must not be used for automated decisions without calibration, which uses labelled validation sets where correct outputs are already known. Running the system against the labelled set and measuring how reported confidence tracks actual accuracy identifies the threshold that best separates usually correct from often wrong, and the set should cover the full range of finding types and severity levels rather than an aggregate accuracy number.

Independent instances and why self-review confirms

Independent review uses a fresh API call whose messages array starts without the generation system prompt or conversation history, containing only the output to review plus a focused review prompt. The fresh invocation approaches the output as if seeing it for the first time, which is why its findings are measurably more thorough on subtle issues even though the model and prompt may be the same otherwise.

The cost-quality tradeoff is explicit. Independent multi-pass review with confidence routing costs more than single-pass self-review, and the tradeoff is worth it when review quality directly affects production reliability such as CI pipelines, financial extraction, or compliance analysis. It is not worth it for low-stakes outputs where occasional misses are tolerable, which is why the exam frames the choice around downstream impact rather than abstract thoroughness.

Per-file local passes plus integration pass

Per-file passes ensure consistent depth by giving each file a dedicated context window with the same focused prompt such as Review this file for bugs, security issues, and logic errors. Each file is an independent input that can run concurrently, so total wall-clock time drops even though more API calls are made, and middle files receive the same scrutiny as the first and last.

The integration pass runs sequentially after all per-file reviews complete because it depends on their findings. Its prompt names the cross-file checks explicitly: data flow inconsistencies between modules, contradictory patterns flagged in different files, and API contract violations across service boundaries. Skipping the integration pass saves one call but removes the only mechanism that catches these systemic issues.

Calibrated confidence routing and dismissal-driven refinement

Confidence scores are reported in the structured output alongside each finding with reasoning for the score, and the routing decision of direct report versus human review is based on the score relative to a calibrated threshold. Calibration validates the threshold against labelled sets, so the score becomes a routing signal with a known accuracy boundary rather than an uncalibrated claim.

The detected_pattern field per finding creates a systematic improvement loop beyond calibration. When developers dismiss findings triggered by a specific pattern, that pattern's dismissal rate indicates a prompt refinement priority. Patterns with high dismissal rates need refined prompts, patterns with low dismissal rates are working correctly, and tracking frequency plus dismissal rate per pattern produces a prioritised refinement backlog that continuously improves review quality from production data.

Authoritative mechanism reference

The sections below expand the tested rules into the complete documented surface: ownership, lifecycle, version differences, production boundaries, and adjacent mechanisms.

Mechanism reference: M1. Reasoning context anchoring and the self-review deficit

When one invocation first produces an artifact and then, within the same session, is asked to review that artifact, the review turn inherits the entire generation transcript. That transcript contains not only the output but the justification chain: why each decision was made, which edge cases were considered, and the conclusion that the approach was acceptable. The review step therefore begins from a state that already contains a favorable verdict about every line it is about to inspect. It does not start from the artifact alone. It starts from the artifact plus a pre-committed story about why the artifact is fine.

This is structural, not a matter of effort or instruction quality. A context that already holds "this handler returns null here because the edge case is handled" will, when asked to review, re-read the same code through that conclusion rather than re-deriving it from scratch. The reasoning context acts as an anchor that primes the reviewer to confirm. Confirmation is consistent with the context; challenge requires reconstructing an independent judgment that contradicts the stored one. Because the stored judgment is readily available, the path of least resistance is to align with it.

The boundary that matters for the exam is a single question: is a quality verdict being produced and trusted? If yes, the same session cannot be the verifier. If no, if the task is merely "make the generation better," then a self-review instruction or more internal reasoning can raise the odds of a cleaner first output, and the bias is irrelevant because no independent gate is required.

Mechanism reference: M2. Fresh-context independence

The fix for the self-review deficit is to give the reviewing step its own invocation that starts from a clean context. The reviewer receives the artifact (the diff, the draft, the extracted record) and the evaluation criteria (the standard, the rubric, the checklist), and nothing else from the generation step. No reasoning trace, no working notes, no "lines I was unsure about." It approaches the artifact the way a human peer would, seeing what was written without being told why.

A clean-context invocation cannot be anchored because there is no prior verdict in its context. It must derive its judgment from the artifact and the criteria alone. That derivation is exactly the independent check the self-review lacks. The mechanism is "a second context," not "a second model with different parameters." Two calls with identical weights but separate contexts already remove the bias. This is why the canonical correct option is consistently "a separate instance that receives only the code and the review criteria."

The nearby opposite case is when the reviewer legitimately needs the generation inputs, not its reasoning. Passing the source documents, the regulatory checklist, or the diff is correct and required. The boundary is reasoning versus inputs: inputs are fine, the generator's decision narrative is not. A second boundary: if the reviewer is asked to improve the draft rather than gate it, shared context is harmless and sometimes helpful.

Mechanism reference: M3. Extended thinking and why it does not cure the deficit

Extended thinking is a first-class API feature that causes Claude to generate internal reasoning before producing the final answer, returned in a thinking content block with a signature for multi-turn continuity. The crucial point for this task is what extended thinking does and does not change.

Extended thinking deepens the reasoning that happens inside a single pass. It expands how thoroughly the model argues from the premises it already holds. It does not replace those premises, and it does not reset the commitment the generating session already made. When you enable extended thinking on a review turn that lives in the same session as generation, the extra reasoning is spent inside the anchored context. The anchored conclusion, that the approach was correct, sits in the context as given. Deeper reasoning interrogates the artifact through that conclusion more carefully, which can surface marginal issues, but it cannot undo the commitment to "this was correct" that the generation step established.

The extended-thinking lesson is explicit that extended thinking is valuable for making a better first draft (generation-time depth) but that the self-review scenario is different: the question there is verification, not production. The documented behaviour gives us a precise statement the writer should preserve: extended thinking inside the generating session deepens reasoning but does not remove the commitment the session already made, so it is not a substitute for a fresh context.

This is also why "switch on extended thinking for the review turn" is a named distractor. The lever that fixes the deficit is session isolation, not reasoning budget. A separate instance that also uses extended thinking is fine and even good, but the independence, not the thinking, is what fixes the problem.

Mechanism reference: M4. Documented evaluator and reviewer separation

The independence argument is not invented for this task. It is the same principle that underlies two workflow patterns Anthropic documents.

The evaluator-optimizer pattern is described as a loop where one component generates output and a distinct evaluator scores it and provides feedback. The evaluator is a separate call, not the generator reviewing itself. In the documented pattern the generator then refines, but the scoring step is structurally independent of the generation context.

The orchestrator-subagent pattern is described as a central planner that dynamically breaks a task into pieces and delegates each to worker instances, with the workers operating as separate invocations. The multi-agent research system write-up reinforces this: separate research agents run with isolated context and their results are aggregated by a different component. The documented design keeps the generator's reasoning trace out of the worker's context; the worker receives its assigned task and the inputs it needs, not the orchestrator's private deliberation.

For multi-pass review, the documented separation gives us the grounding: a reviewer is a worker or evaluator instance that receives the artifact and the criteria, never the generator's reasoning trace. That is exactly the fresh-context rule from M2, and it is consistent with how Anthropic's own orchestration guidance separates generation from evaluation.

Mechanism reference: M5. Attention dilution and per-unit decomposition

When a review covers many units (files, schedules, encounters, clauses) in one call, the model's attention is distributed across all of them simultaneously. Some units receive deep analysis, others receive shallow treatment, and units in the middle of a long input are especially likely to be missed. The fix is to give each unit its own focused pass so every unit gets the model's full attention in isolation.

The limiting factor is attention quality, not context capacity. A single pass over many units optimizes for breadth and produces uneven depth: first and last units covered, middle ones lost, identical patterns judged inconsistently because they are far apart. Per-unit passes remove the competition for attention; each invocation sees exactly one unit and applies the full review mandate to it.

The nearby opposite case is a small review where all units fit comfortably and a single pass is fine. The boundary is scale and objective mixing: once the input is large enough that depth becomes uneven, or once one pass must serve several concerns at once, decomposition is required. Below that threshold, a single focused pass is acceptable and cheaper.

Mechanism reference: M6. Per-unit passes versus the integration pass: different input selection

Per-unit passes and the integration pass are not the same kind of step, and they need different inputs.

A per-unit pass examines one unit in isolation. Its input is that unit's content plus a focused review mandate (bugs, security, logic errors). It is intentionally blind to everything else, because blinding is what gives it full attention on the one unit.

The integration pass exists to catch what per-unit passes cannot see: interactions between units. A function signature changed in one file and its callers updated inconsistently in others; a disclosure established in an early unit but omitted in a later one; a renamed field breaking downstream consumers. None of these is visible when each unit is reviewed alone, because in isolation each unit is locally correct.

Therefore the integration pass needs a different input selection than a per-unit pass. It does not need the raw body of every unit; it needs the seams. Concretely, it needs the public interfaces each unit exposes (exported symbols, their signatures, their contracts) and the call sites that connect units (who calls what, with what argument shapes, across which boundaries). Feeding the integration pass the full file bodies reintroduces dilution and defeats its purpose. Feeding it only the interface map and the consolidated per-unit findings gives it exactly the cross-unit view it exists to examine. The integration pass must also receive the complete aggregated set of per-unit findings, not a subset, because cross-unit interactions are only detectable when the relevant units are co-present.

Mechanism reference: M7. Stable finding identity and cross-pass consolidation

Because the same underlying defect can be surfaced by more than one per-unit pass, or by a per-unit pass and the integration pass, the consolidation step needs a way to recognize "these two findings are the same finding" so it can merge them rather than report them twice. This is done with a stable finding identity: a deterministic key built from the fields that make a finding what it is, independent of which pass produced it or which instance reported it.

A stable finding key is built from the unit identifier, the location (file and line span, canonicalized so that trivial renumbering does not change the key), the rule or check identifier, and a normalized form of the symptom text. It must not include the pass identifier, the instance identifier, or anyๆ—ถ้—ดๆˆณ, because those vary run to run and would prevent collapse. It must canonicalize the location and the symptom so that two reports of the same issue produce the same key. When two findings share a key, consolidation keeps one merged record, preserving the highest severity observed, the union of evidence, and the list of passes that reported it. Findings with the same rule but different locations or different symptoms keep distinct keys and survive as separate findings.

Mechanism reference: M8. Confidence as calibrated routing, not filtering

Many pipelines attach a self-reported confidence value (typically 0.0 to 1.0) to each finding and then use a fixed cutoff to decide what to do: above the cutoff, auto-release; below, send to human review. That use is the failure mode. A self-reported confidence is the model's read on its own certainty, not a measured probability of correctness. Without a reference set where the ground truth is known, there is no established mapping from the number to actual accuracy. The model can be overconfident exactly on the items it gets wrong.

The correct role of confidence is to route limited human attention to the findings most likely to be wrong. It is a triage signal, not a verdict. Above the calibrated cutoff, the finding still proceeds through a verification path (often an independent instance or a final human check at the segment level). It is never silently auto-released. Below the cutoff, and for any segment still below target accuracy, the finding reaches a human. Confidence narrows the human's queue; it does not replace the human.

Mechanism reference: M9. Labelled validation set and threshold fitting

Calibration means measuring, for each confidence level, the actual accuracy the system achieves, using a reference set where the correct answer is known. You collect the model's reported confidence across many examples, compare against ground truth, and find the score at which accuracy drops below your required bar. That learned threshold, not a hand-picked number, becomes the routing rule.

Calibration must be done per field, per type, and per segment, because difficulty varies widely across them. A given reported confidence on one field type may correspond to a very different measured accuracy than the same reported confidence on another. A single global threshold therefore over-trusts some fields and over-routes others. The labelled set is what makes the threshold meaningful: it is the only thing that converts a self-reported feeling into a measured, actionable cutoff. Until that measurement exists, the score must not gate delivery.

Mechanism reference: M10. Execution profiles and workload matching

The shape of the review pipeline should match how the workload will actually run. A batch of independent, non-blocking, single-turn review jobs is a good fit for asynchronous batch execution, where each unit review is one request and the integration pass is another. But batch execution is unsuitable for work whose control flow depends on inspecting a result mid-request and then deciding the next request, because a batch request is a single asynchronous unit with no interactive loop between your code and the model mid-request. Decomposition into per-unit passes plus an integration pass is a control lever you choose when review quality affects production reliability; it is not mandatory for every task, and for small, dependent, shared-context work a single call can be the right choice.

Ownership map

This table states which layer owns each guarantee in a multi-pass review system.

GuaranteeOwning layerNotes
The artifact is producedApplication code via the Messages API or Claude CodeThe generator invocation is yours to control
The review runs in a separate contextApplication code / CLI invocationAchieved by a new process, a headless session, or a separate API call; the model does not enforce this, your architecture does
The reviewer receives only artifact plus criteriaApplication codeYou must not attach the generator's reasoning; the model will use whatever context you provide
Per-unit attention isolationApplication code (input selection)The model attends to what you give it; isolation is your design, not a model feature
Integration sees interfaces and call sitesApplication code (derived interface map)You build the seam view; the model consumes it
Stable finding identityApplication code (key function)Deterministic key logic is yours; the model reports findings, you consolidate
Confidence is self-reportedModelThe number is the model's; its meaning is uncalibrated until you measure it
Calibration thresholdApplication code against a labelled setYou fit and store the threshold; the model does not know your bar
Routing to human reviewApplication codeYour pipeline decides dispatch; confidence only informs it
Extended thinking deepens in-session reasoningModel / API featureDoes not remove session commitment; not a review fix
Batch execution of independent passesInfrastructure / APISuitable only for non-blocking single-turn work

The recurring theme: every structural guarantee in this task is owned by application code or the CLI invocation shape, not by the model. The model supplies the reasoning and the self-reported confidence; you supply the isolation, the input selection, the consolidation, and the calibrated routing.

Version and terminology currency

  • The reference page's API shape, client.messages.create({ messages: [...] }), matches the current Messages API. The messages array with role and content is the current request structure. No deprecated field naming appears in the reference page itself.
  • Extended thinking has evolved. On current models the manual thinking: {type: "enabled", budget_tokens: N} form is being replaced by adaptive thinking with an output_config effort parameter, and on some models thinking is always on. None of this changes the self-review argument: whatever the thinking configuration, thinking happens inside the session and does not remove the generation commitment. The grounding should not imply that a particular thinking flag "fixes" review.
  • The validation-retry loop and the separation of parse from validate are current and stable. The structured-outputs surface now uses output_config.format with a JSON schema, and strict tool use is a separate, composable feature. These are relevant when review findings are themselves structured (for example when a reviewer returns JSON findings), but they are not the focus of Task 4.6.
  • The term "evaluator-optimizer" used here aligns with Anthropic's documented workflow naming. The reference page's "independent instance" language is consistent with that pattern's separated evaluator.
  • Claude Code headless review uses -p for non-interactive mode and --output-format json (with --json-schema for structured findings). There is no generic preferences override flag; per-invocation behaviour comes from specific named flags. This matters because the "separate session" requirement is most reliably met by a distinct claude -p invocation in CI.

Official versus community divergence

This task sits at the boundary between Anthropic's documented patterns and third-party or lesson material that sometimes simplifies them. The divergences below are worth stating plainly so a candidate answers with the documented position.

Divergence 1: Does extended thinking inside the session fix self-review? Some third-party and community advice implies that enabling deeper reasoning on the review turn makes self-review rigorous. The documented position, supported by the extended-thinking lesson, is that extended thinking deepens in-session reasoning but does not remove the commitment the generating session already made, so it is not a substitute for a fresh context. Answer with: separate instance, not more thinking in the same session.

Divergence 2: Does high self-reported confidence justify auto-release? The validation-strategies lesson presents a confidence-based validation table in which "High (>= 0.9)" maps to "Use directly; no human review needed". That table is a useful teaching simplification for validation gating, but it conflicts with the documented routing principle of this task: confidence, even when high, must not auto-release findings; it routes scarce human review. The candidate should understand that the lesson's table describes a graduated filter for low-risk validation, whereas Task 4.6 is explicit that review findings must keep a verification path regardless of confidence. The reconciliation: use the lesson's graduated response for internal validation signals, but for gating review you route, never release, until a labelled set has calibrated the threshold and a human path remains.

Divergence 3: Does lowering temperature make a reviewer independent? The validation-strategies lesson lists "use lower temperature for review" as an anti-pattern fix alongside separate sessions. Temperature governs sampling variation, not independence from prior context. Lowering temperature can sharpen a review, but it does not delete the reasoning already in context, so it is a secondary refinement, not the structural fix. The documented fix remains a separate context.

Divergence 4: Is majority voting across repeated full passes a reliability technique? A common community intuition is to run the same large review three times and keep only findings that appear in at least two runs. The peer forensics file identifies this as an anti-pattern: detection of subtle issues is not perfectly reproducible run to run, so a genuinely present but intermittently detected bug gets discarded, while easy repetitive findings dominate the consensus. The documented fix is distinct focused passes (per-unit plus integration), not consensus filtering of repeated identical passes.

Divergence 5: Is a larger context window the fix for uneven review? The reference page already names this as a distractor, and it is included here for completeness: context capacity is not attention quality. A bigger window lets more fit, but attention is still distributed unevenly.

Beyond the task statement

The reference page covers self-review bias, per-unit plus integration passes, attention dilution, and confidence routing. The project lessons cover adjacent material the reference page omits entirely. Each item below names the lesson slug and why it matters for this task.

  • validation-strategies - This lesson is the strongest "beyond" topic. It presents multi-pass review as three separate sessions (Generator, Reviewer, Refiner) and explicitly names "reasoning context bias" as the problem. It also covers structural versus semantic validation, which matters because a reviewer's findings are themselves structured output that benefits from schema validation, and the confidence-based validation table (with the refinement from Divergence 2 above). It also covers the validation-retry loop with specific feedback, relevant when a review finding is sent back to a generator for fixing.
  • workflow-patterns - This lesson provides the documented patterns that ground the independence argument: evaluator-optimizer (separate evaluator), orchestrator-subagents (separate worker instances receiving only their task), and parallelization (independent subtasks run concurrently, then aggregated). It also covers CI/CD session isolation with explicit claude -p separate-session examples, which is the operational way to guarantee the "separate instance" requirement in automation. The anti-pattern table there lists "self-review in same session" directly.
  • extended-thinking - This lesson is the source for M3: what extended thinking is, the thinking block and signature, the difference from chain-of-thought, and the critical point that extended thinking helps make a better draft but does not reset session commitment. It is the documented basis for rejecting "enable extended thinking on the review turn" as the fix.
  • prompt-anti-patterns - This lesson explains why review prompts must be specific rather than vague. "Review this code" is the first anti-pattern: no criteria, so the reviewer guesses the dimension. This connects directly to per-unit passes, which must carry a focused mandate. The lesson also covers over-flagging (review noise that buries real issues) and assumed context (a reviewer must be given the criteria and the artifact, not told "as we discussed earlier," which would require the generator's reasoning). These are the prompt-design corollaries of the architectural rules.

Together these lessons show that multi-pass review is not an isolated trick but the convergence of three ideas: separate generation from evaluation (workflow-patterns, validation-strategies), understand what extended thinking does and does not do (extended-thinking), and write review prompts with specific criteria (prompt-anti-patterns).

Worked production examples

The following five examples are the core deliverable. Each is a substantial, language-tagged code block with an explanation of what it proves, its failure boundary, and its observable output. They build on one another as a single connected implementation: a code-review pipeline that generates, reviews independently, decomposes into per-unit and integration passes, consolidates, and routes by calibrated confidence.

Worked production examples: Example 1 - Generation followed by a genuinely independent review

The first example shows the canonical correct shape: one invocation generates the artifact, then a second invocation, with no shared message history and no generator reasoning attached, reviews it. The review call carries only the artifact and the evaluation criteria.

example.ts
typescript
// Example 1: generation then a fresh-context review.
// The review call shares nothing with the generation call.

const MODEL = process.env.ANTHROPIC_MODEL_ID; // the model identifier you configure

async function generateArtifact(spec: string): Promise<string> {
  const res = await client.messages.create({
    model: MODEL,
    max_tokens: 4000,
    messages: [{ role: "user", content: spec }],
  });
  return res.content.filter((b) => b.type === "text").map((b) => (b as any).text).join("");
}

async function reviewIndependently(code: string): Promise<ReviewFindings> {
  // Fresh call. No generation transcript, no reasoning, no "I wrote this because...".
  const res = await client.messages.create({
    model: MODEL,
    max_tokens: 2000,
    messages: [
      {
        role: "user",
        content:
          "Review the following code for bugs, security issues, and edge cases.\n" +
          "Report findings as JSON with fields: rule_id, severity, location, symptom.\n\n" +
          code,
      },
    ],
  });
  return JSON.parse(res.content.filter((b) => b.type === "text").map((b) => (b as any).text).join(""));
}

const generated = await generateArtifact("Write a function that processes orders...");
const review = await reviewIndependently(generated);

What this proves: the review invocation is a second context. It has no access to why the generator chose each approach, so it judges the code as written. Its failure boundary is the opposite mistake: if you instead appended the generation reasoning (a summary, the "lines I was unsure about," or the full transcript), you would reintroduce the anchor even though the call is technically separate. The observable output is a ReviewFindings object containing only what the fresh reviewer derived from the code and the criteria.

Worked production examples: Example 2 - Same-session self-review shown as the anti-pattern

The second example shows the anti-pattern and states precisely why it under-reports. The review turn inherits the generation reasoning, so it starts from a pre-committed favorable verdict about every line.

example.ts
typescript
// Example 2: ANTI-PATTERN. Self-review in the same session.
// The review turn inherits the generation transcript and the favorable verdict it contains.

async function generateAndSelfReview(spec: string) {
  const res = await client.messages.create({
    model: MODEL,
    max_tokens: 6000,
    messages: [
      { role: "user", content: spec },
      // The model's own generated code is now in context as an assistant turn...
      // (assistant message elided for brevity)
      { role: "user", content: "Now review your code for bugs." },
      // ...so this review turn re-reads its own output through the conclusion
      // that the approach was already acceptable.
    ],
  });
  return res;
}

Why it under-reports: the generation step already established, in context, a justification for each decision. When the same session is asked to review, the path of least resistance is to confirm that stored justification rather than reconstruct an independent judgment. The review turn is not starting from the artifact alone; it is starting from the artifact plus a pre-committed story about why the artifact is fine. It can still catch gross errors, but it systematically misses the class of issue that contradicts the generator's own reasoning, because challenging requires overriding a conclusion the context already treats as settled. The failure boundary: this shape looks diligent ("it reviewed its work") but provides no independent verification. The observable output is a confident "looks good" or a shallow list that parallels the generator's own assumptions.

Worked production examples: Example 3 - Per-unit passes plus a separate integration pass that receives only interfaces and call sites

The third example shows the decomposition. Each file gets its own focused pass. Then a separate integration pass receives a derived interface map (exported symbols and their signatures, plus call sites across files) and the consolidated per-unit findings. It does not receive the raw file bodies, because its job is to examine seams, not re-read unit internals.

example.ts
typescript
// Example 3: per-unit passes, then an integration pass over interfaces and call sites.

interface UnitReview {
  file: string;
  findings: ReviewFindings;
}

interface InterfaceMap {
  file: string;
  exports: { symbol: string; signature: string }[];
  callSites: { from: string; to: string; argShape: string }[];
}

async function reviewPerUnit(files: { file: string; content: string }[]): Promise<UnitReview[]> {
  return Promise.all(
    files.map(async (f) => ({
      file: f.file,
      findings: await reviewIndependently(f.content),
    }))
  );
}

function buildInterfaceMap(files: { file: string; content: string }[]): InterfaceMap[] {
  // In production this parses each file for exported symbols and cross-file calls.
  // The integration pass consumes ONLY this derived view, not the raw bodies.
  return files.map((f) => ({
    file: f.file,
    exports: extractExports(f.content),
    callSites: extractCallSites(f.content),
  }));
}

async function integrate(perUnit: UnitReview[], interfaces: InterfaceMap[]): Promise<IntegrationFindings> {
  const res = await client.messages.create({
    model: MODEL,
    max_tokens: 3000,
    messages: [
      {
        role: "user",
        content:
          "Given these per-unit findings and this interface map, identify cross-unit issues:\n" +
          "- function signatures changed but callers updated inconsistently\n" +
          "- data passed between units in incompatible formats\n" +
          "- API contracts violated across boundaries\n" +
          "- contradictory findings across units\n\n" +
          "Per-unit findings:\n" + JSON.stringify(perUnit) + "\n\n" +
          "Interface map (signatures and call sites only):\n" + JSON.stringify(interfaces),
      },
    ],
  });
  return JSON.parse(res.content.filter((b) => b.type === "text").map((b) => (b as any).text).join(""));
}

const units = await reviewPerUnit(files);
const seams = buildInterfaceMap(files);
const crossUnit = await integrate(units, seams);

What this proves: the integration pass gets a different input selection than the per-unit passes. The per-unit passes see one file body each; the integration pass sees the seam view (interfaces and call sites) plus the aggregated findings. This is what lets it catch a signature change that breaks a caller in another file, which no single-file review could see. Its failure boundary is the wrong input selection: if you feed the integration pass only "the disagreements," it cannot discover a contradiction between a confirmed finding and an unexamined one; if you fold the cross-unit check into each per-unit pass, no single invocation ever holds the whole-set view needed to see the seam. The observable output is an IntegrationFindings object describing interactions that no per-unit pass reported.

Worked production examples: Example 4 - Consolidation that merges findings across passes using a stable finding key

The fourth example shows the consolidation step. The same underlying defect may be reported by a per-unit pass and again by the integration pass. A stable finding key lets the consolidator merge them instead of reporting the same issue twice. The key is built from stable fields only and never includes the pass or instance identifier.

example.ts
typescript
// Example 4: cross-pass consolidation with a stable finding key.

interface RawFinding {
  pass: string;          // "unit:auth.ts" or "integration" - NOT part of the key
  unitId: string;
  location?: { file: string; startLine: number; endLine: number };
  ruleId: string;
  severity: "low" | "medium" | "high" | "critical";
  symptom: string;
  evidence: string;
}

function normalize(text: string): string {
  return text.toLowerCase().replace(/\s+/g, " ").trim();
}

function hash(input: string): string {
  // Deterministic, stable across runs (e.g. a simple FNV-1a or your hash of choice).
  let h = 0x811c9dc5;
  for (let i = 0; i < input.length; i++) {
    h ^= input.charCodeAt(i);
    h = Math.imul(h, 0x01000193);
  }
  return (h >>> 0).toString(16);
}

function findingKey(f: RawFinding): string {
  const loc = f.location
    ? `${f.location.file}:${f.location.startLine}-${f.location.endLine}`
    : "global";
  // Key uses unit, canonical location, rule, and a normalized symptom hash.
  // It deliberately omits `pass` and any instance/timestamp field.
  return `${f.unitId}|${loc}|${f.ruleId}|${hash(normalize(f.symptom))}`;
}

function consolidate(findings: RawFinding[]): ConsolidatedFinding[] {
  const byKey = new Map<string, ConsolidatedFinding>();
  for (const f of findings) {
    const key = findingKey(f);
    const existing = byKey.get(key);
    if (existing) {
      // Merge: keep highest severity, union evidence, record both passes.
      const order = { low: 0, medium: 1, high: 2, critical: 3 };
      existing.severity =
        order[f.severity] > order[existing.severity] ? f.severity : existing.severity;
      existing.evidence.push(f.evidence);
      existing.reportedBy.push(f.pass);
    } else {
      byKey.set(key, {
        key,
        unitId: f.unitId,
        location: f.location,
        ruleId: f.ruleId,
        severity: f.severity,
        symptom: f.symptom,
        evidence: [f.evidence],
        reportedBy: [f.pass],
      });
    }
  }
  return [...byKey.values()];
}

What this proves: a stable finding identity lets the pipeline recognize that the per-unit pass on auth.ts and the integration pass both reported the same signature mismatch, and collapse them into one consolidated record with both sources noted. Findings that share a rule but differ in location or symptom keep distinct keys and survive as separate items, so legitimate duplicates are not lost. The failure boundary is an unstable key: including the pass name or a timestamp would prevent collapse and inflate the report; canonicalizing line numbers and symptom text prevents trivial renumbering from splitting one real issue into many. The observable output is a ConsolidatedFinding[] where each real issue appears exactly once with its full provenance.

Worked production examples: Example 5 - Confidence routing to human review against a threshold fitted on labelled outcomes

The fifth example shows confidence used correctly: as calibrated routing, not as a filter that suppresses findings. A labelled validation set (known-correct outcomes) is used to fit a per-segment threshold. Findings below the calibrated threshold, and any segment still below target accuracy, are routed to human review. Nothing is auto-released on high confidence.

example.ts
typescript
// Example 5: calibrated confidence routing. Confidence routes to humans; it never auto-releases.

interface LabelledExample {
  field: string;
  segment: string;
  reportedConfidence: number;
  actualCorrect: boolean;
}

interface Calibration {
  segment: string;
  field: string;
  calibratedThreshold: number;
  measuredAccuracyAtThreshold: number;
  segmentBelowTarget: boolean;
}

const REQUIRED_ACCURACY = 0.90;

function fitThreshold(examples: LabelledExample[]): Calibration[] {
  // Group by segment and field, then find the lowest reported confidence at which
  // measured accuracy still meets the required bar. This is MEASURED, not assumed.
  const groups = new Map<string, LabelledExample[]>();
  for (const e of examples) {
    const k = `${e.segment}|${e.field}`;
    (groups.get(k) ?? groups.set(k, []).get(k)!).push(e);
  }
  const out: Calibration[] = [];
  for (const [k, items] of groups) {
    const sorted = [...items].sort((a, b) => a.reportedConfidence - b.reportedConfidence);
    let threshold = 1.0;
    for (const it of sorted) {
      const above = items.filter((x) => x.reportedConfidence >= it.reportedConfidence);
      const acc = above.filter((x) => x.actualCorrect).length / above.length;
      if (acc >= REQUIRED_ACCURACY) {
        threshold = it.reportedConfidence;
        break;
      }
    }
    const above = items.filter((x) => x.reportedConfidence >= threshold);
    const acc = above.length ? above.filter((x) => x.actualCorrect).length / above.length : 0;
    const [segment, field] = k.split("|");
    out.push({
      segment,
      field,
      calibratedThreshold: threshold,
      measuredAccuracyAtThreshold: acc,
      segmentBelowTarget: acc < REQUIRED_ACCURACY,
    });
  }
  return out;
}

function routeFinding(
  finding: { field: string; segment: string; confidence: number },
  cal: Calibration[]
): "human_review" | "verification_path" {
  const c = cal.find((x) => x.segment === finding.segment && x.field === finding.field);
  if (!c) return "human_review"; // unknown segment: route to human, never auto-release
  if (c.segmentBelowTarget) return "human_review"; // segment not yet accurate enough
  if (finding.confidence < c.calibratedThreshold) return "human_review";
  // High confidence still goes through a verification path, not straight to dispatch.
  return "verification_path";
}

What this proves: confidence routes scarce human attention; it does not suppress or auto-release findings. The threshold is fitted on a labelled set where the ground truth is known, per field and per segment, so a given self-reported score means what the measurement says it means for that segment. Until that measurement exists, the code routes everything to human review (the !c and segmentBelowTarget branches), which is the safe default. The failure boundary is the uncalibrated cutoff: if you replace fitThreshold with a hard-coded >= 0.90 and treat "verification_path" as silent auto-release, you ship the confident errors. The observable output is a routing decision of human_review or verification_path, where even verification_path retains a check rather than dispatching unverified.

Worked production examples: Example 6 - Operational separation in CI via headless sessions

A sixth example, included for operational completeness, shows the "separate instance" requirement met in automation using Claude Code headless mode. Each step is its own claude -p invocation, so the review session has no memory of the generation session.

terminal
bash
# Example 6: separate sessions in CI. Generation and review never share a session.

# Step 1: generate the change in an isolated headless session
claude -p "Implement the order-processing handler in src/orders.ts" \
  --output-format json > gen-output.json

# Step 2: review in a NEW session, with only the artifact and the criteria
claude -p "Review the diff in gen-output.json for correctness and security issues" \
  --output-format json --json-schema review.schema.json > review-output.json

# Step 3: apply only after the separate review passes its own gates
./apply-if-reviewed.sh

What this proves: session isolation is enforced by the invocation shape, not by a prompt instruction. The review command receives the artifact file and a criteria string; it does not receive the generation session's reasoning. This is the operational realization of M2 and of the workflow-patterns CI/CD guidance. The failure boundary is reusing one session for both steps, which collapses generation and review into the same context and reintroduces the anchor.

Build exercise material

The following steps are verifiable: each states the action and the observable outcome that proves it worked. They map onto the five examples above.

Step 1 - Establish the single-pass baseline. Run one review call over a mock pull request of about ten files and record the output. Observable outcome: you observe the three symptoms of attention dilution - detailed feedback on the first and last files, superficial comments on middle files, at least one obvious bug missed in a middle file, and at least one contradictory finding where the same pattern is flagged in one file but approved in another. This proves the baseline is genuinely diluted, not merely short.

Step 2 - Add per-unit passes. Iterate the review over each file with a focused prompt that examines only that file. Observable outcome: consistent review depth across all ten files, and the middle-file bug that the single pass missed is now caught. This proves per-unit isolation restores attention.

Step 3 - Add the integration pass. Feed all per-unit findings plus a derived interface map (signatures and call sites) into a separate call. Observable outcome: a synthesis identifying at least one cross-file issue that no single-file review could see, such as a signature change breaking a caller or a data-format mismatch across a boundary. This proves the integration pass sees seams the per-unit passes cannot.

Step 4 - Add confidence and routing. Attach a self-reported confidence (0.0 to 1.0) to each finding and implement routing where low-confidence and below-target segments go to a human queue. Observable outcome: each finding is annotated with a confidence score, a reasoning for the score, and a routing decision. Critically, the high-confidence items still retain a verification path rather than being silently dispatched. This proves routing, not release.

Step 5 - Calibrate with an independent instance. Use a separate, fresh instance to review a labelled subset of the generated findings, then compare its assessment to the original self-reported confidence. Observable outcome: a calibration dataset showing where reported confidence tracks independent verification and where it does not, which adjusts your routing thresholds. Until this labelled measurement exists, the pipeline routes everything to human review. This proves the threshold is measured, not assumed.

Deeper mechanism detail: why a fresh context changes the verdict

It helps to be precise about why the same model, with the same weights, produces a different quality of review depending only on whether the review call shares context with generation. The generation transcript is not a neutral record. It contains the artifact and, woven through it, the generator's own account of why each choice was made. When the review turn is appended to that transcript, the model conditions its review on a context that already asserts the artifact is acceptable. The review is therefore not a fresh evaluation; it is a continuation of a thread whose earlier turns concluded "this is fine."

A fresh-context review removes that continuation. The review call opens with the artifact and the criteria and nothing that pre-judges the outcome. The model must derive its verdict from the artifact. That derivation is the independent check. The point is not that the fresh instance is a different "brain"; it is that the fresh instance lacks the prior verdict. Two identical calls with separate contexts already differ in outcome quality because only one of them is anchored. This is why the canonical correct option in the exam is consistently "a separate instance that receives only the artifact and the criteria," and why instructions like "review your work carefully" inside the same session cannot reproduce it.

The same logic explains the variant mutations documented in the peer forensics file. A later turn rather than the immediately following turn is still wrong, because context persistence, not turn adjacency, is what matters. A "be skeptical" directive added to the same session is still wrong, because the directive competes with, rather than replaces, the stored favorable verdict. A genuinely separate process that is nonetheless handed the generation transcript is still wrong, because a fresh process that receives the reasoning is reasoning-anchored self-review in disguise. The only reliable fix is a separate context that receives the artifact and the criteria and nothing else from generation.

Extended thinking nuance for reviewers

The extended-thinking lesson provides the precise vocabulary for the "enable more thinking on the review turn" distractor. Extended thinking produces a thinking content block before the final answer, with a signature that binds the thinking to subsequent turns for continuity. It is a first-class API feature that changes the model's inference path: the model reasons before it answers. The key limitation for this task is that the reasoning it produces is internal to the request in which it runs. If that request is the same session that generated the artifact, the extended reasoning is deeper reasoning inside the anchor, not a removal of the anchor.

This is why the lesson's framing matters: extended thinking is recommended for making a better first draft, where no independent verdict is required, and it measurably improves accuracy on complex tasks. It is not a verification substitute. A reviewer that is a separate instance may certainly use extended thinking, and doing so can improve the quality of the independent check. But the independence, not the thinking budget, is the lever that fixes the self-review deficit. Conflating the two is the exact trap the exam sets: the option "add extended thinking to the review turn in the same session" is attractive because deeper reasoning feels like more objectivity, but the budget is spent inside the anchor.

There is also a practical interaction worth noting for batch execution. Extended thinking interacts with prompt caching: cache hits are reduced because extended thinking produces new content each turn, and toggling thinking modes between turns invalidates the message history cache. When per-unit review passes are run as asynchronous batch requests, thinking can be enabled per request, but the caching discount that would normally stack with batch pricing is reduced. This is a cost detail, not a correctness one, but it belongs in the ownership and version picture because it affects how the pipeline is priced.

Failure mode catalogue

The following catalogue collects the concrete ways a multi-pass review system goes wrong, with the symptom that reveals it, the root cause, how to detect it, and the fix. Each entry is grounded in the documented behaviour or the peer forensics file.

Failure modeSymptomRoot causeDetectionFix
Self-review in same sessionReview agrees with generation; subtle bugs persistReview turn inherits generation verdictCompare same-session review against a fresh-instance review on a labelled subsetSeparate review invocation, artifact plus criteria only
Reasoning-fed reviewer"Independent" instance still confirms generator's framingGenerator's summary or uncertainty notes passed inCheck that the review prompt contains no generator-authored narrativePass only the raw artifact and the criteria
Majority-vote suppressionIntermittently detected real bugs disappear from consensusConsensus filtering drops non-reproducible findingsMeasure recall on known bugs across repeated runsUse distinct focused passes, not repeated identical passes filtered by agreement
Larger-window illusionSame uneven depth after upgrading contextCapacity, not attention quality, was changedObserve middle-of-input misses persistDecompose into per-unit passes
Uncalibrated auto-releaseConfident errors ship to productionRaw self-reported confidence used as a gateAudit released items for false positives at high confidenceCalibrate; route, never release
Integration pass given only disagreementsCross-unit breaks missedIntegration never sees the whole setCheck integration input includes all per-unit findings and the full interface mapFeed integration the complete aggregated set
Per-unit pass told about other filesDilution returns inside the "per-unit" stepRe-introduces whole-set view and uneven attentionInspect per-unit prompt for cross-file contextKeep per-unit prompt scoped to one unit
Composite confidence hiding weak fieldsOne wrong field ships inside an otherwise strong recordSingle score masks per-field variationCheck per-field versus composite confidence usageEmit and evaluate confidence per field
No verification path on high confidenceHigh-confidence findings dispatched unverifiedConfidence treated as a verdictConfirm every finding retains a checkKeep a verification path for all findings

This catalogue is the operational translation of the rule inventory. A candidate who can name the symptom and the root cause for each row is answering from the documented position rather than from intuition.

Decision guide: when decomposition pays off

Decomposition is a control lever, not a moral obligation. The exam tests not only that you can build per-unit plus integration passes, but that you know when the pattern is warranted and when a single call is correct. The guidance below is derived from the peer forensics boundaries.

Decompose when all of the following hold, or when any single one is strong enough to matter: the review covers many units (the forensics file places the onset of uneven depth past roughly six units, though the exact number depends on unit size and concern mixing); the single pass must serve several concerns at once (bugs, security, logic, style) so attention is split; or the artifact has cross-unit interactions that no isolated view can catch. In these cases per-unit passes plus an integration pass are the correct design, and the integration pass must receive the full aggregated set and the interface map.

Do not decompose when the review is small and single-concern, when all units fit comfortably in one focused pass with even depth, or when the work is dependent and shared-context by nature (for example a draft and its immediate revision where the reviewer legitimately needs the generation inputs and the prior reasoning to improve the draft). In those cases a single, well-scoped call is cheaper and no less reliable. The anti-pattern is not "single pass" in general; it is "single pass over a large, mixed, cross-unit artifact."

The execution profile also informs the decision. If the workload is a batch of independent, non-blocking, single-turn review jobs, asynchronous batch execution is a good fit, with each unit review as one request and the integration pass as another. If the control flow depends on inspecting a review result mid-request and deciding the next request from it, batch is the wrong profile because a batch request is a single asynchronous unit with no interactive loop between your code and the model mid-request. In that case chained or interactive separate invocations are required.

Confidence calibration in practice

The confidence routing example above is intentionally compact. In practice, calibration has several details that the exam treats as required knowledge.

First, the labelled set must contain known-correct outcomes. You collect a set of findings where a human or an independent authoritative source has already established whether each was correct. For each finding you record the model's self-reported confidence and the actual correctness. Without this set, the confidence number is uncalibrated and must not gate delivery. The labelled set is the only thing that converts a feeling into a measurement.

Second, calibration is per field, per type, and per segment. A given reported confidence on one field type may correspond to a very different measured accuracy than the same reported confidence on another. A single global threshold therefore over-trusts some fields and over-routes others. The peer forensics file is explicit that calibration must be measured per field, per filing type, and per document segment because difficulty varies widely. The fitted output is a per-type, per-field threshold, not one global number.

Third, use per-field confidence rather than a composite score. When an extraction returns several fields, a single composite confidence for the whole record hides field-level variation. If five of six fields are near-perfect but the sixth is wrong a meaningful fraction of the time, a high composite score routes the whole record past review and the weak field's errors ship. The fix is to emit and evaluate confidence per field, and to route any record to human review when any individual field falls below its calibrated threshold.

Fourth, route both low-confidence findings and below-target-accuracy segments to human review. A segment whose measured accuracy has not yet reached the required bar stays routed to humans regardless of any single finding's score. Confidence narrows the human's queue; it does not replace the human, and it does not declare any segment "done" until the measurement says so.

Fifth, an uncalibrated score must never gate delivery. Until the labelled measurement exists, the safe default is to route every finding to human review. Shipping on a self-reported number that has not been measured against ground truth is the exact failure mode the exam identifies as an anti-pattern. The calibration loop is continuous: as new labelled examples arrive, the thresholds are refit, and segments that reach target accuracy can be moved to a lighter verification path while still retaining a check.

Connection to the evaluator-optimizer and orchestrator patterns

It is worth restating the grounding explicitly, because the exam may present the idea inside a workflow-design scenario rather than a pure review scenario. The evaluator-optimizer pattern is documented as a generator followed by a distinct evaluator that scores and gives feedback, with the generator refining until criteria are met. The multi-pass review principle is the same separation applied to verification: the evaluator (reviewer) is a separate instance that receives the artifact and the criteria. The orchestrator-subagent pattern is documented as a planner that delegates to separate worker instances, each receiving its assigned task and the inputs it needs, not the orchestrator's private reasoning. A subagent reviewer is exactly such a worker: it receives the artifact, never the coordinator's reasoning trace. The multi-agent research write-up reinforces this with separate research agents running isolated context and a different component aggregating results.

The through-line is that Anthropic's own orchestration guidance already separates generation from evaluation and keeps the generator's reasoning out of the worker's context. Multi-pass review is the verification-specific instance of that same architectural rule. A candidate who can connect "separate reviewer instance" to "evaluator-optimizer's separate evaluator" and to "orchestrator-subagent's isolated worker" is demonstrating the documented understanding, not a memorized slogan.

Practical prompt criteria for reviewers

A separate context is necessary but not sufficient; the reviewer also needs specific criteria, or it will guess the dimension to assess. The prompt-anti-patterns lesson names "Review this code" as the first anti-pattern: with no criteria, the reviewer chooses whether to look at style, bugs, security, or performance, and the output is off-target or too broad. For multi-pass review this means each pass should carry an explicit, concrete mandate.

For a per-unit pass, the mandate should name the dimensions and the expected output shape: "Review this file for bugs, security issues, and logic errors. Report findings as JSON with fields rule_id, severity, location, symptom." For the integration pass, the mandate should name the cross-unit concerns: signature and caller consistency, data-format compatibility across boundaries, API contract adherence, and contradictions among per-unit findings. Vague instructions such as "review thoroughly" or "check everything" reproduce the over-flagging anti-pattern, where the reviewer emits noise and the real issues are buried. Specificity is a dial: "review for security issues" is better than "review," and "review for SQL injection via parameterized query usage" is better still.

The reviewer also must not assume prior context. A prompt that says "as we discussed earlier" or "based on your analysis" presupposes session memory that a fresh instance does not have. Every reviewer prompt must be self-contained: it includes the artifact (or the interface map for integration) and the criteria, and nothing that references a generation conversation the reviewer never saw. This is the prompt-design corollary of the architectural rule that the reviewer receives no generator reasoning.

Common exam scenario walkthrough

The reference page's practice scenario is a fourteen-file pull request that receives inconsistent review: detailed feedback on some files, superficial comments on others, obvious bugs missed, and contradictory findings where the same pattern is flagged in one file but approved in another. The question asks how to restructure the review. Walking the options grounds the answer.

Option A, switching to a larger context window, is the documented distractor. It changes capacity, not attention quality; the model can now hold all fourteen files but still gives uneven depth, so the middle-file misses and the contradictory findings persist. Option B, per-file local passes plus a separate cross-file integration pass, is correct: the per-file passes restore consistent depth and catch the missed middle bugs, and the integration pass catches the cross-file inconsistencies that produced the contradictory findings. Option C, three independent full-pass reviews with agreement filtering, is an anti-pattern: it suppresses genuinely intermittent findings, discarding the subtle bugs that need the most attention. Option D, forcing smaller submissions, reduces scale but does not itself restructure the review architecture; it is a process workaround, not the structural fix the scenario asks for.

A candidate who can explain why B is correct and why A, C, and D are each wrong for the stated reason is demonstrating the full mechanism: attention dilution is fixed by decomposition and integration, not by capacity; consensus filtering harms recall; and process limits do not substitute for the per-unit plus integration design.

Summary of the architectural rules

The peer forensics file distils this task into a rule inventory. The rules that the exam treats as load-bearing, restated here as a single reference, are:

  • Self-review retains the generator's reasoning and confirms rather than challenges; the fix is a fresh invocation with no prior reasoning that receives only the artifact.
  • Extended thinking and larger reasoning budgets do not cure the self-review deficit, because they spend compute inside the anchor rather than removing it.
  • "Critique your own output" instructions cannot fix a structural anchoring bias; they compete with, rather than replace, the stored verdict.
  • Feeding the generator's notes, summary, or uncertainty into the reviewer recreates the bias in a separate process; pass the raw artifact instead.
  • Large single-pass reviews suffer attention dilution; split into per-unit local passes, each scoped to one unit.
  • The integration pass catches interactions that per-unit passes cannot see, and it must receive the full aggregated set plus the interface map, not a subset.
  • A larger context window does not fix attention dilution; it changes capacity, not attention quality.
  • Majority voting across repeated full passes is an anti-pattern that suppresses real, intermittently detected bugs.
  • Raw self-reported confidence is not a valid routing signal until calibrated against a labelled set; calibration is per field, per type, and per segment.
  • Confidence routes scarce human review; it must not auto-release or suppress findings, and per-field confidence prevents weak fields from hiding behind strong ones.
  • Subagent reviewers must receive the artifact, never the coordinator's reasoning trace; the generator's self-reported uncertainty passed to the reviewer re-anchors the bias.
  • Not every task should be decomposed; single calls win for dependent, shared-context work, and iterative review needs explicit done criteria to avoid infinite fix-find loops.

These rules are mutually consistent and all reduce to the same architectural principle: separate generation from evaluation by context, decompose large or cross-unit work into focused passes plus an integration pass, and treat confidence as a measured, calibrated routing signal rather than a verdict.

Mechanism and API surface

Independent review instance
Separate API call with no prior generation system prompt or conversation history, only the output to review plus a focused review prompt, so the model judges the code on its content alone without bias from I chose this because.
Per-file local analysis
One focused review invocation per file ensuring consistent depth for every file including middle files, parallelisable because files are independent, carries the same prompt and criteria for each file.
Cross-file integration pass
Sequential pass after all per-file reviews that receives all per-file findings and checks data flow, API contract violations, dependency conflicts, and contradictions among per-file findings.
Confidence reporting per finding
Review prompt requests a 0.0 to 1.0 confidence value plus reasoning per finding, enabling per-finding routing rather than a single document-level threshold.
Calibration on labelled validation sets
Run the system against a labelled set where correct outputs are known, compare reported confidence to actual accuracy, and set the routing threshold that best separates usually correct from often wrong across the full range of finding types and severities.
detected_pattern for systematic refinement
Each finding carries the specific pattern that triggered it, so dismissal tracking by pattern identifies which prompts need refinement and creates a feedback loop from production data.

The decision rules in play

Each rule below is a distinct decision the exam tests, stated with its mechanism, the boundary where the answer flips, and the wrong answers written against it.

R1

Self-review retains the generator's reasoning context and confirms rather than challenges

When one invocation first produces an artifact and then, within the same session, is asked to review that artifact, the review turn inherits the entire generation transcript. That transcript contains not just the output but the justification chain: why each decision was made, which edge cases were considered, and the conclusion that the approach was acceptable. The review step therefore begins from a state that already contains a favorable verdict about every line it is about to inspect. It does not start from the artifact alone; it starts from the artifact plus a pre-committed story about why the artifact is fine.

In a code-generation pipeline this shows up as a run that both writes a change and then inspects its own diff. In an extraction pipeline it shows up as the same call that drafts structured fields later being asked to validate them. In a document pipeline it shows up as an appended quality-check section at the end of the generation response. In every case the second pass is reading content it already "decided" on.

example.ts
typescript
// Anti-pattern: self-review in the same session
const generation = await client.messages.create({
  messages: [
    { role: "user", content: "Write a function to process orders" },
    { role: "assistant", content: generatedCode },
    { role: "user", content: "Now review your code for bugs" }
    // Model retains its reasoning - less likely to find its own mistakes
  ]
});

The deficit is structural, not a matter of effort or instruction quality. A model that has already concluded "this handler returns null here because the edge case is handled" will, when asked to review, re-read the same code through that conclusion rather than re-deriving it from scratch. The reasoning context acts as an anchor that primes the reviewer to confirm. Confirmation is cheap and consistent with the context; challenge requires reconstructing an independent judgment that contradicts the stored one. Because the stored judgment is readily available, the path of least resistance is to align with it, which is why self-confirmation rates sit around 90 percent while independent re-checks overturn a meaningful share.

Boundary. The opposite answer becomes correct when the question is about first-pass quality, not verification. If the task is merely "make the generation better," then a self-review instruction or more internal reasoning can raise the odds of a cleaner first output, and the bias is irrelevant because no independent gate is required. The line is: is a quality verdict being produced and trusted? If yes, the same session cannot be the verifier. If no, self-improvement passes are acceptable and even useful.

Recurring specifics. The recurring numbers are confirmation rates near 88 to 96 percent for self-review, contrasted with independent overturn rates around 19 to 33 percent on the items the self-review upheld. The recurring artifact is the phrase where a follow-up turn re-reads its own output. The recurring field is confidence emitted in the same call as the finding. The recurring session shape is one non-interactive run that does generation then review back to back. The anti-pattern shows the generator and reviewer sharing one message list, so the review turn inherits the generation reasoning:

Wrong answers written against this rule

Proposal. the reviewer needs more detailed review instructions or a checklist.

Why it attracts. it mirrors how humans add rigour.

Why it fails. instructions cannot delete the reasoning already in context; the anchor remains.

When it would be right. when improving the generator's own output, not gating it.

Proposal. lower the temperature so the reviewer is more precise.

Why it attracts. temperature feels like a "strictness" knob.

Why it fails. temperature governs sampling variation, not independence from prior context.

When it would be right. never for this deficit.

Proposal. the original code was already correct and independent findings are false positives.

Why it attracts. it absolves the design.

Why it fails. it assumes the answer instead of explaining the pattern.

When it would be right. only when an independent audit confirms zero misses.

How the same rule gets re-asked
  • - Mutation: same session but a later turn rather than the immediately following turn. Effect on answer: unchanged; context persistence is what matters, not turn adjacency. - Mutation: same session but with a "be skeptical" directive added. Effect: still wrong; directive does not erase the anchor. - Mutation: a genuinely separate model instance in a new process that is nonetheless handed the generation transcript. Effect: still wrong; a fresh process that receives the reasoning is reasoning-anchored self-review in disguise.
R2

Review independence requires a fresh invocation with no prior reasoning: pass only the artifact

The fix for the self-review deficit is to give the reviewing step its own invocation that starts from a clean context. The reviewer receives the artifact (the diff, the draft, the extracted record, the image set) and the evaluation criteria (the standard, the rubric, the checklist), and nothing else from the generation step. No reasoning trace, no working notes, no "lines I was unsure about." It approaches the artifact the way a human peer would, seeing what was written without being told why.

example.ts
typescript
// Correct: independent review instance, artifact plus criteria only
const review = await client.messages.create({
  messages: [
    {
      role: "user",
      content: `Review this code for bugs, security issues, and edge cases:\n\n${generatedCode}`
    }
    // Fresh instance - no prior reasoning context
  ]
});
terminal
bash
claude -p --output-format json --json-schema review.schema.json \
  "Review the diff at \$PR_DIFF for safety-critical standard violations"

A clean-context invocation cannot be anchored because there is no prior verdict in its context. It must derive its judgment from the artifact and the criteria alone. That derivation is exactly the independent check the self-review lacks. The mechanism is not "a second model" in the sense of a different parameter set; it is "a second context." Two calls with identical weights but separate contexts already removes the bias. This is why the canonical correct option is consistently "a separate instance that receives only the code and the review criteria."

Boundary. The nearby opposite case is when the reviewer legitimately needs the generation inputs, not its reasoning. Passing the source documents, the regulatory checklist, or the diff is correct and required. The boundary is reasoning versus inputs: inputs are fine, the generator's decision narrative is not. Another boundary: if the reviewer is asked to improve the draft rather than gate it, shared context is harmless and sometimes helpful.

Recurring specifics. The recurring instruction phrasing is "without the generating run's reasoning or transcript." The recurring good configuration is a second non-interactive Claude Code invocation (-p) for the review, or a second Messages API call carrying only the artifact plus criteria. For structured output the recurring addition is --output-format json with --json-schema so the findings parse reliably; that is a separate concern from independence but co-occurs. The correct review call passes only the artifact and the criteria, with no generation transcript: For a CI gate the review runs as its own headless invocation so it has no memory of the authoring session:

Wrong answers written against this rule

Proposal. spawn a fresh process but pass the generation reasoning as a starting point.

Why it attracts. feels like giving the reviewer a head start.

Why it fails. the head start is the anchor.

When it would be right. never for verification.

Proposal. run the same session but clear history between generation and review.

Why it attracts. appears to isolate.

Why it fails. unless the review truly receives only the artifact and criteria, residual framing leaks; the safe implementation is a separate call.

Proposal. use a different, more powerful model tier for the reviewer.

Why it attracts. capability feels like the bottleneck.

Why it fails. a stronger model still inherits the self-review bias if it shares context.

How the same rule gets re-asked
  • - Mutation: independent instance but also given the generator's "least sure" lines. Effect: wrong; that note re-anchors. - Mutation: independent instance for the whole artifact, plus a separate integration pass. Effect: correct and reinforced; independence at both layers. - Mutation: independent instance but the artifact is a generator-written summary rather than the raw diff. Effect: wrong; the summary carries the generator's framing (see Rule 5).
R3

Extended thinking and larger reasoning budgets do not cure the self-review deficit

A common wrong turn is to keep the same session but enable deeper internal reasoning on the review turn, instructing the model to re-derive each line from the standard or to question its earlier decisions. The assumption is that more thinking budget lets the reviewer "escape" its earlier conclusion. It does not, because the extra reasoning happens inside the same anchored context; the budget increases how thoroughly the model argues from its existing premises but does not replace them.

Extended thinking expands the depth of reasoning within a single pass, not the independence between passes. The anchored conclusion sits in the context as given. Deeper reasoning interrogates the artifact through that conclusion more carefully, which can surface marginal issues, but it cannot undo the commitment to "this approach was correct" that the generation step already established. the tested material consistently shows confirmation rates dropping only a few points (for example from 92 to 88 percent) under extended thinking, leaving the core miss unchanged.

Boundary. The nearby opposite case is generation-time extended thinking used to produce a better first draft. There, deeper reasoning genuinely improves the artifact because no independent verdict is required. The boundary is again verification versus production: extended thinking helps make, not check.

Recurring specifics. Recurring phrasing: "enable extended thinking so the model reasons more deeply before producing findings." Recurring observed effect: confirmation or miss rates move only slightly. Recurring contrast in explanations: "session isolation, not reasoning budget, is the correct lever."

Wrong answers written against this rule

Proposal. raise the thinking budget to give the reviewer more room.

Why it attracts. more compute feels like more objectivity.

Why it fails. budget is spent inside the anchor. When

When it would be right. for first-pass depth only.

Proposal. add a retry loop so the same instance re-reviews up to three times.

Why it attracts. repetition feels like diligence.

Why it fails. each retry replays the same bias. When

When it would be right. never for verification.

How the same rule gets re-asked
  • - Mutation: extended thinking on a separate instance. Effect: fine and even good, but the independence, not the thinking, is what fixes the deficit. - Mutation: extended thinking plus a stricter checklist in the same session. Effect: still wrong; checklist rides on the anchor. - Mutation: extended thinking proposed as the fix for "missed bugs in the middle of a long input." Effect: wrong there too; that is attention dilution, a different rule, but the same distractor shape appears.
R4

"Critique your own output" style instructions cannot fix a structural anchoring bias

The cheapest proposed fix, and the one most tempting to managers watching token cost, is to append a sentence to the generation prompt: "review your own diff critically," "re-examine each finding skeptically," "treat every stage as potentially incomplete." This adds intent but not independence. The instruction sits in the same context that already contains the favorable generation verdict, so it competes with, rather than replaces, that verdict.

Prompt instructions shape what the model tries to do, but they cannot delete the reasoning already present. A "be more critical" directive raises the salience of criticism in the output language, which is why miss or confirmation rates dip a few points, but it does not give the model a fresh perspective on the artifact. The model still knows why it wrote what it wrote and still defaults to confirming. The structural cause (shared context) is untouched.

Boundary. The nearby opposite case is using explicit categorical criteria plus worked counter-examples in a genuinely independent review. There, instructions help because the reviewer is fresh and the criteria give it concrete, checkable targets. The boundary: instructions help a fresh reviewer; they cannot rescue an anchored one.

Recurring specifics. Recurring directive shapes: "review with equal care," "be more conservative when confirming," "reconsider what the first pass may have missed." Recurring observed effect: rates move slightly (for example miss from 26 to 21 percent, confirmation from 92 to 88 percent) but neither gap closes. Recurring correct framing: "prompt instructions cannot fix structural defects."

Wrong answers written against this rule

Proposal. add a comprehensive checklist of what to look for during self-review.

Why it attracts. checklists are a known human rigour tool.

Why it fails. applied inside the anchor, the checklist confirms more than it challenges. When

When it would be right. in an independent review for precision.

Proposal. tell the model to imagine it is a skeptical senior engineer.

Why it attracts. role-play feels like perspective shift.

Why it fails. the role is adopted within the same context that holds the original reasoning. When

When it would be right. never for verification.

How the same rule gets re-asked
  • - Mutation: instruction plus categorical criteria in the same session. Effect: still wrong; criteria ride the anchor. - Mutation: instruction in a separate instance. Effect: fine and even recommended, because the separate instance removes the anchor. - Mutation: instruction that asks the model to "list edge cases it considered." Effect: produces a list but does not gate; still wrong as a verification substitute.
R5

Feeding the generator's notes, summary, or reasoning into the reviewer re-creates the bias

A subtle variant of the self-review trap appears when a team tries to get "the best of both worlds": it keeps a second instance but seeds it with the generator's own account of its work, such as a summary of changes, a list of "lines I was least sure about," or the full reasoning trace that produced each severity rating. The second instance is technically separate, but it is primed by the generator's framing. It evaluates the generator's narrative about the code rather than the code itself.

The value of an independent reviewer is that it examines the artifact fresh. When the generator writes the summary that the reviewer reads, the generator chooses what to emphasise, what to omit, and how to justify each decision. The reviewer then critiques that account, which already encodes the original conclusions. The anchor is transmitted through the summary. The canonical correct design passes the raw artifact (the diff, the draft, the record), not a generator-authored abstraction of it.

Boundary. The nearby opposite case is passing the generator's inputs and the external criteria to the reviewer, which is correct. The boundary is authored-by-generator versus supplied-from-source. A summary written by the generator is the former; the source documents and the checklist are the latter.

Recurring specifics. Recurring wrong option: "pass the generating run's notes on which lines it was least sure about into the review instance." Recurring correct contrast: "the reviewer must have access to the raw artifact to provide truly independent assessment." Recurring scenario: a security bot that hands its reasoning trace to a second instance "to target each flagged step."

Wrong answers written against this rule

Proposal. seed the reviewer with the generator's reasoning so it can target weak spots.

Why it attracts. efficiency of attention.

Why it fails. targets the generator's own framing, not the artifact. When

When it would be right. never for verification.

Proposal. pass the generator's severity rationale to a second instance asked to uphold or overturn it.

Why it attracts. feels like structured adjudication.

Why it fails. the rationale is the anchor. When

When it would be right. only if the second instance is also given the raw artifact and instructed to re-derive independently.

How the same rule gets re-asked
  • - Mutation: pass only the raw diff plus the generator's uncertainty flags but no reasoning. Effect: still risky; the uncertainty flag is the generator's self-assessment (see Rule 22). - Mutation: pass the generator's summary but also the full raw artifact. Effect: better, but the summary still biases; the clean design omits it. - Mutation: second instance receives generator reasoning plus is told to ignore it. Effect: unreliable; residual framing leaks.
R6

Large single-pass reviews suffer attention dilution: split into per-unit local passes

When a review covers many units (files, schedules, encounters, variants, clauses, studies) in one call, the model's attention is distributed across all of them simultaneously. Some units receive deep analysis, others receive shallow treatment, and units in the middle of a long input are especially likely to be missed. The fix is to give each unit its own focused pass so every unit gets the model's full attention in isolation.

The recurring structure is a per-unit local pass followed by an aggregation step. For code this is per-file analysis; for filings this is per-schedule drafting plus a cross-schedule pass; for extractions this is per-encounter or per-clause passes.

example.ts
typescript
// Pass 1: per-file local analysis
const perFileFindings = await Promise.all(
  files.map((file) =>
    client.messages.create({
      messages: [{
        role: "user",
        content: `Review this file for local issues (bugs, security, logic errors):\n\n${file.content}`
      }]
    })
  )
);

// Pass 2: cross-file integration, holds the whole set at once
const integrationReview = await client.messages.create({
  messages: [{
    role: "user",
    content:
      "Given these per-file findings, identify cross-file issues:\n" +
      "- Data flow inconsistencies between modules\n" +
      "- Contradictory patterns flagged in different files\n" +
      "- API contract violations across service boundaries\n\n" +
      `Findings:\n${JSON.stringify(perFileFindings)}`
  }]
});

Attention quality, not context capacity, is the limiting factor. A single pass over many units optimises for breadth and produces uneven depth: first and last units covered, middle ones lost, identical patterns judged inconsistently because they are far apart. Per-unit passes remove the competition for attention; each invocation sees exactly one unit and applies the full review mandate to it.

Boundary. The nearby opposite case is a small review where all units fit comfortably and a single pass is fine. The boundary is scale and objective mixing: once the input is large enough that depth becomes uneven, or once one pass must serve several concerns at once, decomposition is required. Below that threshold, a single focused pass is acceptable and cheaper.

Recurring specifics. Recurring symptom numbers: miss rate 4 percent on small inputs rising to 26 percent on large ones; omission rate "rising sharply" past about six units; "detailed feedback on first files, superficial on later files." Recurring unit counts: 14 files, 18 files, 22 files, 30 variants, 35 encounters, 50 clauses. Recurring fix phrasing: "per-file local analysis passes plus a separate cross-file integration pass." The two-pass shape separates local depth from cross-unit interaction:

Wrong answers written against this rule

Proposal. increase max_tokens so the model can write longer comments.

Why it attracts. length feels like thoroughness.

Why it fails. length is output budget, not attention quality. When

When it would be right. never for dilution.

Proposal. increase the context window to a larger model.

Why it attracts. capacity feels like coverage.

Why it fails. the model can now hold more but still attends unevenly. When

When it would be right. only when the real limit was literally fitting the input.

Proposal. randomise file order before each review.

Why it attracts. spreads which files get shallow treatment.

Why it fails. different files are shallow each time; the dilution remains.

How the same rule gets re-asked
  • - Mutation: one pass but with an explicit per-file checklist gating the report. Effect: still wrong; the checklist rides on the same diluted attention. - Mutation: per-unit passes but no separate integration pass. Effect: fixes local depth but misses cross-unit issues (see Rule 7). - Mutation: per-unit passes plus a cross-unit integration pass. Effect: correct and the canonical answer.
R7

The cross-unit integration pass catches interactions that per-unit passes cannot see

Per-unit passes are blind by design to anything that spans units. A function signature changed in one file and its callers updated inconsistently in others; a disclosure established in an early schedule but omitted in a later one; a bundled-code conflict across encounters; a renamed field breaking downstream consumers. None of these is visible when each unit is reviewed alone. A separate integration pass receives the aggregated results (or the full set of changed symbols and their relationships) and looks specifically for cross-unit interaction.

Cross-unit defects live in the seams between units. A per-file pass judges each file correct in isolation because, in isolation, it is. The defect only appears when the units are considered together: the interface no longer matches, the shared disclosure is missing, the data flow breaks. Only a pass whose explicit mandate is the relationships between units can catch these, which is why the integration pass traces data flow, API consistency, and pattern usage across boundaries.

Boundary. The nearby opposite case is a defect that is local to one unit; there, the per-unit pass is the right tool and the integration pass adds nothing. The boundary is locality: if the issue would be visible inside one unit, per-unit review suffices; if it requires comparing two or more units, integration is required. A correct design includes both because real reviews contain both kinds.

Recurring specifics. Recurring defect shapes: "function signature changes in one module and callers updated inconsistently"; "renamed field breaks three downstream consumers"; "disclosure omitted in later schedules"; "bundled-code conflict across encounters." Recurring integration mandate wording: "data flow and call-site compatibility," "cross-file dependencies," "cross-schedule consistency," "cross-encounter conflicts."

Wrong answers written against this rule

Proposal. fold the cross-file check into each per-file pass by telling it about the other files.

Why it attracts. keeps one pass count.

Why it fails. re-introduces dilution and the per-file pass still lacks the whole-set view. When

When it would be right. never as a substitute for a true integration pass.

Proposal. a second per-file pass over the changed file alone.

Why it attracts. "re-read it."

Why it fails. still sees only one file; the mismatch is elsewhere. When

When it would be right. only for local re-checks.

Proposal. rely on the generator's memory of why it changed the signature.

Why it attracts. the generator "knows."

Why it fails. that is self-review bias (Rule 1) and it still misses the caller.

How the same rule gets re-asked
  • - Mutation: integration pass folded into per-file passes. Effect: wrong (see above). - Mutation: integration pass given the full symbol set at once. Effect: correct and required. - Mutation: integration pass scoped only to "disagreements." Effect: wrong; it must see all findings to catch interactions (see Rule 16 boundary).
R8

A larger context window does not fix attention dilution; it changes capacity, not quality

A frequently offered distractor is to switch to a model tier with a larger context window so that all units "fit comfortably" in one pass. This treats dilution as a capacity problem. It is not. The model can now hold the entire input, but it still allocates attention unevenly across it, so the same symptoms (shallow middle, contradictory judgments of identical code) persist.

Context window determines how much text the model can process, not how evenly it attends to each part. Attention dilution is about distribution of analytical focus across simultaneous objectives, not about running out of room. A bigger window lets the model see all 14 files at once, but it still gives file 7 less than file 1. The exam treats "larger context window" as a named anti-pattern for this reason.

Boundary. The nearby opposite case is when the genuine blocker is that the input literally cannot be loaded, causing truncation. There, a larger window is the correct fix. The boundary is fit versus focus: if content is being dropped, enlarge the window; if content is present but unevenly reviewed, decompose instead.

Recurring specifics. Recurring wrong option wording: "switch to a higher-tier model with a larger context window so all N files get adequate attention in one pass." Recurring correct contrast: "larger context windows do not solve attention quality issues." Recurring symptom persistence: same inconsistent depth despite the upgrade.

Wrong answers written against this rule

Proposal. larger context plus a more complex XML system prompt.

Why it attracts. structure feels like rigour.

Why it fails. still one multi-objective pass. When

When it would be right. for formatting only.

Proposal. larger context plus few-shot examples of the missing pattern.

Why it attracts. teaches the model the bug class.

Why it fails. examples do not redistribute attention. When

When it would be right. as a precision aid in an independent review.

How the same rule gets re-asked
  • - Mutation: larger window plus per-unit decomposition. Effect: fine; the decomposition, not the window, does the work. - Mutation: larger window cited as the fix for cross-file bugs. Effect: doubly wrong; that needs the integration pass too. - Mutation: larger window as the fix for "lost in the middle." Effect: wrong; lost-in-the-middle is a focus symptom.
R9

Majority voting across repeated full passes is an anti-pattern that suppresses real bugs

Another distractor is to run the same large review three times and report only findings that appear in at least two runs. The intuition is redundancy equals reliability. In practice it is harmful because bug detection across runs is inconsistent: a real bug detected in one run but missed in the others gets discarded, while the consensus set is biased toward easy, repetitive findings.

Detection of subtle issues is not perfectly reproducible run to run. A genuinely present defect may be caught in run one, slipped in run two, caught again in run three, and then dropped because it lacked two-of-three agreement. Meanwhile, superficial stylistic findings that the model reliably produces appear every time and dominate the consensus. The filter therefore removes exactly the high-value, hard-to-detect items the review exists to find.

Boundary. The nearby opposite case is using multiple independent reviewers for genuinely separate perspectives (for example a multi-agent setup where each agent has a different, focused mandate), which is valuable. The boundary is consensus-filtering versus perspective-diversity: filtering a single unstable signal by agreement suppresses signal; running distinct focused passes adds coverage.

Recurring specifics. Recurring wrong option: "run three independent review passes on the full PR and only flag issues found in at least two runs." Recurring correct contrast: "majority voting across runs suppresses real bugs that are detected intermittently." Recurring co-occurrence with attention dilution scenarios (14-file PRs).

Wrong answers written against this rule

Proposal. run the same single pass three times and merge.

Why it attracts. cheap redundancy.

Why it fails. same bias, filtered. When

When it would be right. never as a verification gate.

Proposal. two independent instances in parallel and accept only on agreement.

Why it attracts. looks like peer review.

Why it fails. same suppression problem when the finding is intermittent. When

When it would be right. only if each instance has a distinct, well-scoped mandate.

How the same rule gets re-asked
  • - Mutation: vote across per-unit passes rather than full passes. Effect: still suppresses intermittent real findings. - Mutation: distinct focused agents (security, logic, perf) rather than repeated identical passes. Effect: correct; that is stage specialisation (Rule 19). - Mutation: vote but keep all findings, routing disagreements to humans. Effect: acceptable, because nothing is suppressed.
R10

Per-unit passes plus integration pass must give the integration step the full set at once

A correct multi-pass design gives the integration pass the complete aggregated set: every per-unit finding, or every changed symbol and its dependency relationships, presented together. The integration step then examines cross-unit consistency across the whole. A common wrong mutation is to give the integration pass only a subset, such as "only the disagreements," or to fold the cross-unit check into each per-unit pass.

Cross-unit interactions are only detectable when the relevant units are co-present. If the integration pass sees only disagreements, it cannot discover a contradiction between a confirmed finding and an unexamined one, nor a cross-unit data-flow break that no single unit flagged. If the check is folded into per-unit passes, no single invocation ever holds the whole-set view needed to see the seam. The integration pass must be the one place that holds everything at once.

Boundary. The nearby opposite case is the per-unit pass itself, which should be narrow. The boundary is scope: the local pass is intentionally one-unit; the integration pass is intentionally all-units. Confusing the two scopes produces either diluted local passes or blind integration passes.

Recurring specifics. Recurring correct phrasing: "aggregate the results and run a separate cross-file integration pass over the changed symbols and their dependency relationships." Recurring wrong phrasing: "scope the integration pass to disagreements only" or "have each per-file pass also check cross-file consistency." Recurring mandate: "surface mismatches spanning files."

Wrong answers written against this rule

Proposal. integration pass over only the flagged items.

Why it attracts. less to process.

Why it fails. misses interactions involving unflagged units. When

When it would be right. never for integration.

Proposal. each per-file pass told about the other files.

Why it attracts. seems to add cross-file awareness cheaply.

Why it fails. reintroduces dilution and lacks whole-set view. When

When it would be right. as a local hint only, not as the integration substitute.

How the same rule gets re-asked
  • - Mutation: integration pass given full set but only checking data flow. Effect: correct for that concern; pair with other integration concerns as needed. - Mutation: integration pass given full set plus categorical criteria. Effect: strongest form. - Mutation: integration pass given full set but in same session as generation. Effect: wrong; independence also required (Rule 2).
R11

Long multi-step workflows split into per-step passes plus a cross-step integration pass

The per-unit plus integration pattern generalises beyond files. A workflow that builds many units in one continuous session (drafting nine schedules, auditing many encounters, reviewing many clauses) suffers the same dilution, and the same fix applies: split the build into per-step local passes, each handling one unit from its source inputs, then run a separate cross-step integration pass over the assembled units to reconcile shared concerns.

A single continuous session that produces many units exhibits position-dependent quality: later units omit what earlier units established, and the draft's own review re-reads the same session and confirms its own choices. Splitting the production into per-step passes restores per-unit attention, and a separate integration pass reconciles the cross-step shared disclosures or conflicts no single step can see.

Boundary. The nearby opposite case is a workflow with few steps and no shared cross-step state, where a single session is fine. The boundary is step count and shared state: many steps or shared disclosures across steps demand decomposition plus integration.

Recurring specifics. Recurring numbers: nine schedules, omission "rising sharply" past six; 35 encounters; 50 clauses. Recurring correct option: "split the build into per-schedule passes, then a cross-schedule integration pass; route review to an independent instance." Recurring wrong option: "keep one continuous session but maintain a running ledger."

Wrong answers written against this rule

Proposal. keep one session but maintain a running ledger the model consults.

Why it attracts. looks like a fix for omission.

Why it fails. the same session still confirms its own choices on review; ledger does not remove the self-review bias. When

When it would be right. as a generation aid, not a verification gate.

Proposal. keep one session, instruct equal scrutiny.

Why it attracts. cheap.

Why it fails. instruction cannot fix structural dilution plus bias. When

When it would be right. never.

How the same rule gets re-asked
  • - Mutation: per-step passes but review stays in-session. Effect: wrong; bias remains (Rule 1). - Mutation: per-step passes plus independent review but no cross-step integration. Effect: fixes bias and local depth, misses cross-step conflicts. - Mutation: per-step passes plus cross-step integration plus independent review. Effect: the complete correct design.
R12

Raw self-reported confidence is not a valid routing signal; it must be calibrated

Many pipelines attach a self-reported confidence value (typically 0.0 to 1.0) to each finding or extraction and then use a fixed cutoff to decide routing: above the cutoff, auto-release; below, send to human review. the tested material shows raw confidence is poorly calibrated. Items reported at 0.94 to 0.97 are later corrected, while items near 0.80 were actually correct. A fixed cutoff therefore both leaks errors and overloads reviewers.

result.json
json
{
  "finding": "Potential race condition in order processing",
  "severity": "major",
  "confidence": 0.95,
  "route": "auto_release"
}

A self-reported confidence is the model's read on its own certainty, not a measured probability of correctness. Without a reference set where ground truth is known, there is no established mapping from the number to actual accuracy, and the model may be overconfident on exactly the items it gets wrong. Using the raw number as a gate substitutes self-assessment for measurement.

Boundary. The nearby opposite case is using confidence after it has been calibrated against a labeled set (see Rule 13). The boundary is calibrated versus raw: raw scores route on the model's feeling; calibrated scores route on observed accuracy. The scores themselves are fine; using them uncalibrated as a hard gate is the flaw.

Recurring specifics. Recurring field: confidence 0.0 to 1.0. Recurring wrong cutoff: >= 0.90 auto-release. Recurring observed failure: fields at 0.94 to 0.97 corrected; fields at ~0.80 correct. Recurring correct contrast: "raw self-reported confidence is not a valid routing signal." A raw-score routing decision looks plausible but leaks errors, because the score is the model's self-read, not measured accuracy: Here a 0.95 score sent the item straight through, yet the same class of finding was later corrected by humans. The score must not gate release until calibrated (Rule 13).

Wrong answers written against this rule

Proposal. route only low-confidence findings to a second instance, auto-release the rest.

Why it attracts. saves human time.

Why it fails. the subtle errors the model is confident about are never routed. When

When it would be right. only after calibration, and paired with human review for low segments.

Proposal. lower the threshold from 0.90 to 0.70.

Why it attracts. sends more to review.

Why it fails. still uncalibrated; the cutoff is arbitrary. When

When it would be right. never as the sole fix.

Proposal. trust scores above 8/10 on a self-certification.

Why it attracts. simple.

Why it fails. uncalibrated self-trust. When

When it would be right. never for gating.

How the same rule gets re-asked
  • - Mutation: per-field confidence but still uncalibrated global cutoff. Effect: wrong; field-level helps but calibration still required. - Mutation: confidence used to route to a second instance, not to auto-release. Effect: better, but still needs calibration to know the cutoff. - Mutation: confidence calibrated per type, then routed. Effect: correct.
R13

Calibration requires a labeled validation set measured per field, type, and segment

Calibration means measuring, for each confidence level, the actual accuracy the system achieves, using a reference set where the correct answer is known. You collect the model's reported confidence across many examples, compare against ground truth, and find the score at which accuracy drops below your required bar. That learned threshold, not a hand-picked number, becomes the routing rule. Calibration must be done per field, per filing type, and per document segment because difficulty varies widely across them.

result.json
json
{
  "field": "insurance_policy_number",
  "document_type": "intake_form",
  "calibration": [
    { "reported_confidence": 0.95, "measured_accuracy": 0.78 },
    { "reported_confidence": 0.85, "measured_accuracy": 0.71 },
    { "reported_confidence": 0.65, "measured_accuracy": 0.58 }
  ],
  "required_accuracy": 0.90,
  "calibrated_threshold": 0.97,
  "segment_below_target": true
}

Confidence means different things in different contexts. A 0.85 on one field type may correspond to 88 percent accuracy; a 0.85 on another may correspond to 58 percent. A single global threshold therefore over-trusts some fields and over-routes others. Measuring per segment aligns the routing cutoff with the actual error distribution of that segment, which is the only way the signal becomes reliable.

Boundary. The nearby opposite case is a system where accuracy has already been validated per segment and the threshold is known; there, you route on the calibrated cutoff. The boundary is measured versus assumed: if you have not measured accuracy against a reference set, you do not have a calibrated threshold.

Recurring specifics. Recurring setup: "build a labeled reference set per filing type, fit each field's reported confidence to its measured accuracy." Recurring step wording: "find the score where accuracy drops below your requirement." Recurring output: a per-type, per-field threshold rather than one global number. Recurring segment language: "segments still below target accuracy." Calibration maps reported score to measured accuracy on a labeled set, then sets the cutoff at the required bar: The segment stays routed to humans until measured accuracy reaches the required bar, regardless of any single finding's score.

Wrong answers written against this rule

Proposal. use the model's stated confidence directly as the threshold.

Why it attracts. zero setup.

Why it fails. uncalibrated (Rule 12). When

When it would be right. never.

Proposal. raise global threshold uniformly.

Why it attracts. sends more to review.

Why it fails. ignores per-field difficulty. When

When it would be right. as a stopgap only.

Proposal. random sampling for review instead of calibration.

Why it attracts. easy measurement of overall rate.

Why it fails. does not target where errors concentrate. When

When it would be right. for measuring rate, not for routing.

How the same rule gets re-asked
  • - Mutation: calibrate globally only. Effect: wrong for heterogeneous fields. - Mutation: calibrate per field but ignore per-type difficulty. Effect: partially right; per-type still needed. - Mutation: calibrate per type and per field, then route below-target segments. Effect: correct.
R14

Confidence routes scarce human review; it must not auto-release or suppress findings

The correct role of a confidence score is to route limited human attention to the findings most likely to be wrong. It is a triage signal, not a verdict. Above the calibrated cutoff the finding still goes through a verification path (often an independent instance or a final human check at the segment level); it is never silently auto-released. Below the cutoff, and for any segment still below target accuracy, the finding reaches a human. Confidence narrows the human's queue; it does not replace the human.

Human review is the scarce resource the pipeline protects. Confidence, once calibrated, tells you which findings are most uncertain, so you send those to humans and let the rest proceed through a lighter verification. Treating high confidence as "definitely correct" converts self-assessment into the final gate: the confident errors slip through. Routing, not filtering, keeps a verification path on everything.

Boundary. The nearby opposite case is a fully automated, low-risk pipeline where no human review is required at all; there, confidence may gate auto-release because the cost of error is acceptable. The boundary is risk and scarcity: when errors are costly and reviewers are scarce, confidence routes; it does not release.

Recurring specifics. Recurring correct option: "route low-confidence and below-target segments to the human queue, auto-releasing the rest once that segment's accuracy is validated." Recurring wrong option: "auto-release everything above the cutoff." Recurring field: route: human_review versus direct dispatch. Recurring combined design: independent instance disputes findings, humans handle only what it disputes.

Wrong answers written against this rule

Proposal. send HIGH-confidence drafts straight to dispatch, LOW to humans.

Why it attracts. maximises throughput.

Why it fails. confident errors ship. When

When it would be right. never for high-stakes.

Proposal. auto-clear every finding the independent instance agrees with.

Why it attracts. clean queue.

Why it fails. still no human on the confident wrong ones. When

When it would be right. only with per-type calibration plus human on sub-threshold.

Proposal. trust the original drafting instance's self-assessment for final routing.

Why it attracts. one less call.

Why it fails. that is the generator gating itself. When

When it would be right. never.

How the same rule gets re-asked
  • - Mutation: confidence routes low to human, high to a second instance. Effect: acceptable and common. - Mutation: confidence routes low to human, high auto-released. Effect: wrong for high stakes. - Mutation: confidence plus per-type calibration plus human on sub-threshold segments. Effect: correct.
R15

Use per-field confidence, not a composite score, so weak fields cannot hide behind strong ones

When an extraction returns several fields, a single composite confidence for the whole record hides field-level variation. If five of six fields are near-perfect but the sixth is wrong 22 percent of the time, a high composite score routes the whole record past review and the weak field's errors ship. The fix is to emit and evaluate confidence per field, and to route any record to human review when any individual field falls below its calibrated threshold.

result.json
json
{
  "ingredient": "flour",
  "quantity": "30 minutes",
  "quantity_confidence": 0.31,
  "unit": "cups",
  "unit_confidence": 0.94,
  "preparation": "sifted",
  "preparation_confidence": 0.88,
  "route": "human_review"
}

Error rates are not uniform across fields. Some fields (for example policy numbers, coverage types, specific clinical codes) are systematically harder and the model is overconfident on them. Averaging across fields masks that concentration. Per-field scoring lets the routing decision respond to the weakest link, which is where the errors actually are. This is why the correct design requires "a separate per-field confidence score for each extracted value."

Boundary. The nearby opposite case is a single-field extraction where a composite equals the field score by definition; there, per-field is trivially satisfied. The boundary is field count: with multiple fields, composite scoring is unsafe; with one field, it does not matter.

Recurring specifics. Recurring weak fields: insurance_policy_number wrong 22 percent; coverage_type near-perfect elsewhere but systematically off; total_assets versus derived. Recurring correct option: "require a separate per-field confidence score and route when any field is below threshold." Recurring wrong option: "raise the global threshold from 0.85 to 0.95." Per-field scoring exposes the weak field that a composite score hides: The composite over the three fields would be high, but quantity at 0.31 is exactly the semantic error that should reach a human. Route on the weakest field, not the average.

Wrong answers written against this rule

Proposal. raise the global threshold.

Why it attracts. sends more records to review.

Why it fails. still composite; the weak field hides behind strong ones at any global cutoff. When

When it would be right. only as a blunt stopgap.

Proposal. add a second call that re-extracts all fields and flags differences.

Why it attracts. independent check.

Why it fails. does not target the systematically weak field efficiently; calibration per field is sharper. When

When it would be right. as a complementary check.

Proposal. expand training data for the weak field.

Why it attracts. fixes root cause long term.

Why it fails. does not address routing now. When

When it would be right. as a later improvement, not the routing fix.

How the same rule gets re-asked
  • - Mutation: per-field confidence but uncalibrated. Effect: still wrong (Rule 12). - Mutation: per-field confidence, global threshold. Effect: better but threshold should be per field. - Mutation: per-field confidence calibrated per field. Effect: correct.
R16

Route both low-confidence and below-target-accuracy segments to human review

Calibration often reveals that entire segments (certain filing types, document types, or field classes) sit below the required accuracy regardless of per-finding confidence. The routing rule must therefore combine two triggers: any finding below the calibrated confidence cutoff, and any finding from a segment whose measured accuracy is still below target. Only once a segment's accuracy is validated against the reference set is it eligible for auto-release.

Confidence and segment accuracy capture different failure modes. A field can report high confidence yet belong to a segment the model systematically mishandles, so confidence alone would release it. A segment may be generally reliable but still produce occasional low-confidence findings worth a human eye. Routing on both triggers covers blind spots that confidence numbers miss.

Boundary. The nearby opposite case is a segment whose accuracy has been validated above target and whose findings are above the calibrated cutoff; there, lighter verification suffices. The boundary is validated versus unvalidated: unvalidated segments always route to humans regardless of the per-finding score.

Recurring specifics. Recurring correct option: "route only the findings whose calibrated confidence is low, plus those from segments still below target accuracy, to the human queue, auto-releasing the rest once that segment's accuracy is validated." Recurring segment language: "by document type," "per filing type." Recurring validation step: "validate per-segment accuracy before auto-releasing."

Wrong answers written against this rule

Proposal. route only on the confidence cutoff, ignore segment accuracy.

Why it attracts. simpler.

Why it fails. systematic segment errors leak. When

When it would be right. never for heterogeneous workloads.

Proposal. auto-release a segment as soon as one batch looks clean.

Why it attracts. speed.

Why it fails. one batch is not validation. When

When it would be right. never.

Proposal. route only disagreements between two instances.

Why it attracts. targets uncertainty.

Why it fails. misses confident segment errors. When

When it would be right. as a complement.

How the same rule gets re-asked
  • - Mutation: segment routing without confidence. Effect: catches systematic errors but wastes review on confident-correct items. - Mutation: confidence without segment. Effect: catches uncertain items but leaks systematic segment errors. - Mutation: both combined. Effect: correct.
R17

Match the workload to its execution profile: batches suit only non-blocking single-turn work

A pipeline may contain several workloads with different latency and interaction needs. A high-volume, non-blocking, single-turn, no-tool extraction job fits the Message Batches API for cost savings. A blocking, iterative, tool-using review gate does not; it must stay on the synchronous API and, where noise is the issue, use an independent instance. Trying to apply one execution change to both workloads improves neither cost nor quality.

Batch APIs are designed for asynchronous, high-volume, stateless, single-shot work where a delayed result is acceptable. A pre-merge gate that blocks a merge and calls tools across several turns has a hard latency and interactivity requirement that batching breaks. Conflating the two hides the real fixes: cost for the batch job, independence for the noisy gate. The correct design matches each workload to its profile.

Boundary. The nearby opposite case is a genuinely non-blocking, single-turn extraction at scale; there, batching is the right call. The boundary is blocking versus non-blocking and tool-use versus no-tool: blocking or multi-turn stays synchronous; non-blocking single-turn goes to batch.

Recurring specifics. Recurring correct option: "move the nightly enrichment job to the Message Batches API with one custom_id per product, resubmitting only failed items; keep the pre-merge gate synchronous and route its findings to an independent instance." Recurring wrong option: "move both workloads to Batches." Recurring detail: custom_id per item, resubmit failed only.

Wrong answers written against this rule

Proposal. move the blocking gate to batch too for cost parity.

Why it attracts. uniform infra.

Why it fails. gate must block and use tools. When

When it would be right. never for blocking gates.

Proposal. keep both on synchronous API, add independence only to the gate.

Why it attracts. minimal change.

Why it fails. leaves the enrichment cost problem unsolved. When

When it would be right. acceptable if cost is not the concern.

Proposal. one shared change for both.

Why it attracts. single lever.

Why it fails. addresses neither root cause. When

When it would be right. never.

How the same rule gets re-asked
  • - Mutation: batch the extraction, synchronous the gate, independent instance on gate findings. Effect: correct. - Mutation: batch everything. Effect: wrong for the gate. - Mutation: synchronous everything, fix gate with independence. Effect: correct on quality, suboptimal on cost.
R18

Decomposition is a control lever; chain when you need auditable, gateable stages

Some workflows must produce each stage as a distinct, inspectable artifact: a draft, a review against a checklist, and a refined version, each logged and individually evaluable, with the ability to pause for human sign-off between stages. The correct architecture is explicit prompt chaining: separate API calls in a fixed sequence, each passing its output to the next, with logging and gates at every boundary. A single call, even at maximum effort, collapses all stages into one opaque turn with no enforcement point.

example.ts
typescript
// Stage 1: draft
const draft = await client.messages.create({
  messages: [{ role: "user", content: `Draft the submission summary for:\n${source}` }]
});
logArtifact("draft", draft);

// Stage 2: review against checklist, separate call
const review = await client.messages.create({
  messages: [
    { role: "user", content: `Review this draft against the compliance checklist:\n${draft.content}` }
  ]
});
logArtifact("review", review);

// Human sign-off gate before stage 3
await requireHumanApproval(review);

// Stage 3: refine
const final = await client.messages.create({
  messages: [{ role: "user", content: `Refine based on the review:\n${review.content}` }]
});
logArtifact("final", final);

Modern models handle multistep reasoning inside a single turn well, so chaining is no longer needed purely for answer quality. Its remaining role is architectural control: when a business or compliance requirement demands that the review findings be captured as a separate artifact, that the pipeline can halt at a stage, or that an auditor can trace each stage, only separate calls provide those guarantees. The shape of the workflow is enforced by code, not by hoping the model follows instructions.

Boundary. The nearby opposite case is a workflow whose only requirement is final output quality over shared context, with no audit or gate need; there, a single call is preferable (see Rule 23). The boundary is governance versus quality: requirements about how the process must be observed or controlled dictate chaining; requirements about what the output should be often do not.

Recurring specifics. Recurring requirement language: "each stage must be logged as a distinct artifact," "independently evaluable," "pause for human sign-off." Recurring correct option: "break the workflow into sequential API calls for drafting, reviewing, refining, logging each step's output." Recurring wrong option: "one request at max effort with adaptive thinking." Recurring citation theme: chain complex prompts when you need inspectable boundaries. The chain makes each stage a separate call whose output is logged and gateable:

Wrong answers written against this rule

Proposal. one request with XML-tagged draft, review, final sections.

Why it attracts. text for each stage exists.

Why it fails. no independence, no gate; review cannot stop the pipeline. When

When it would be right. never as a governance substitute.

Proposal. raise effort to reason more thoroughly.

Why it attracts. deeper thinking.

Why it fails. changes depth, not workflow shape. When

When it would be right. for first-pass quality only.

Proposal. delegate stages to parallel subagents.

Why it attracts. speed.

Why it fails. parallel agents mismatch a strictly sequential dependent pipeline. When

When it would be right. for independent workstreams only.

How the same rule gets re-asked
  • - Mutation: chain but review stays in-session (no independence). Effect: logs the artifact but review is biased (Rule 1); acceptable only if the gate is human. - Mutation: single call with adaptive thinking. Effect: wrong when auditability required. - Mutation: chain with independent review instance per stage. Effect: strongest form.
R19

Stage specialisation by concern uses focused system prompts for each review dimension

For a complex artifact spanning several concerns (security, logic, performance, style, compliance), the review is more effective when each concern is handled by a focused pass or agent with its own system prompt and tool set, rather than one generalist pass trying to serve all concerns at once. The security pass focuses on vulnerabilities, the logic pass on correctness and edge cases, the performance pass on efficiency, the style pass on conventions.

Attention dilution applies across concerns as well as across units (Rule 6). A single pass asked to judge security, logic, and performance simultaneously spreads analytical focus and produces shallow coverage on each. Focused concern passes let each reviewer apply deeper, targeted expertise without the cognitive overhead of juggling dimensions. This mirrors how human review splits into specialist teams.

Boundary. The nearby opposite case is a small, single-concern review where one pass is sufficient. The boundary is concern count and criticality: multiple critical concerns on a large artifact demand specialisation; one concern on a small artifact does not.

Recurring specifics. Recurring concern list: security, logic, performance, style. Recurring correct option: "review security, then logic, then performance, each as a focused session." Recurring wrong option: "review the entire PR at once for all issue types." Recurring agent split: Style Agent versus Logic Agent with distinct system prompts.

Wrong answers written against this rule

Proposal. one agent with extended thinking for all concerns.

Why it attracts. depth.

Why it fails. still multi-objective; dilution persists. When

When it would be right. only for a single concern.

Proposal. parallel subagents on different files (not different concerns).

Why it attracts. speed.

Why it fails. speeds coverage but gives no secondary verification per file. When

When it would be right. as a complement, not the specialisation pattern.

Proposal. auto-approve if no critical bugs found.

Why it attracts. throughput.

Why it fails. removes human judgment; inappropriate. When

When it would be right. never.

How the same rule gets re-asked
  • - Mutation: specialisation by concern plus per-unit passes. Effect: correct and comprehensive. - Mutation: specialisation by concern but same session. Effect: bias remains on the gating step. - Mutation: specialisation by concern as independent agents. Effect: correct.
R20

Exactly-one-owner work partitioning prevents overlap and coverage gaps at the source

When several specialist subagents work the same content with overlapping scope, two defects appear: the same unit is edited by more than one agent (producing conflicting or overwritten changes), and some units are assumed covered by another agent and left untouched. The fix is to partition the work at the source so each content unit and each concern has exactly one owner, then sweep any unclaimed unit to the appropriate owner before assembly. Downstream duplicate suppression cannot reliably tell a true duplicate from a distinct issue.

Overlap and gaps are both work-partitioning defects. If two agents can both touch the same string, one will soften a finalized disclaimer or both will rewrite it, and the merge carries conflict. If no agent owns a section, it is skipped. Assigning exactly-one-owner per unit at dispatch removes both failure modes by construction. A later duplicate-suppression pass only treats the symptom and risks discarding legitimate distinct findings, because it cannot know whether a near-identical description is a repeat or a separate issue in a different location.

Boundary. The nearby opposite case is genuinely independent workstreams with no shared units, where overlap is impossible and partitioning is trivial. The boundary is shared scope: when agents' territories overlap, partition upfront; when they are naturally disjoint, overlap is not a risk.

Recurring specifics. Recurring wrong symptom: "same string rewritten by more than one subagent," "18 percent conflicting edits," "whole sections returned untouched." Recurring correct option: "partition each locale's content into non-overlapping ownership slices keyed by content unit and concern, assign each to exactly one subagent, sweep unclaimed units." Recurring wrong option: "add a duplicate-suppression pass after all agents return."

Wrong answers written against this rule

Proposal. instruct each subagent to avoid overlap.

Why it attracts. light touch.

Why it fails. reduces but does not eliminate; gaps unchanged. When

When it would be right. as a reinforcement, not the fix.

Proposal. downstream duplicate suppression keeping one representative per cluster.

Why it attracts. post-hoc cleanup.

Why it fails. cannot distinguish duplicate from distinct; discards real issues. When

When it would be right. never as the primary fix.

Proposal. fixed sequential ordering so later stages cannot overwrite earlier.

Why it attracts. blocks overwrite.

Why it fails. misses coverage gaps; misaligns with root cause. When

When it would be right. as a partial mitigation only.

How the same rule gets re-asked
  • - Mutation: partition but skip the unclaimed-unit sweep. Effect: fixes overlap, leaves gaps. - Mutation: partition plus sweep. Effect: correct. - Mutation: no partition, downstream duplicate suppression. Effect: wrong.
R21

Subagent reviewers must receive the artifact, never the coordinator's reasoning trace

In a multi-agent setup, the coordinator dispatches a reviewer subagent. The reviewer must be given the artifact under review (the code, the draft, the report) and the evaluation criteria, not the coordinator's reasoning about how the artifact was produced or why it is believed correct. Passing only the artifact ensures the subagent evaluates the outcome rather than the process, which is what keeps it unbiased.

If the reviewer can see the generator's or coordinator's chain of thought, it aligns with that reasoning, the same anchoring that defeats single-session self-review (Rule 1). Isolation of subagent context is the design pattern: the artifact alone forces the reviewer to derive its judgment. A shared context window between coder and reviewer produces "sycophancy," where the reviewer agrees with the coder's logic rather than testing it.

Boundary. The nearby opposite case is a subagent that is meant to build on prior reasoning (for example a refinement agent that should see the draft and the review notes to act on them); there, sharing context is intended. The boundary is review versus refinement: a reviewer gets the artifact only; a refiner may get the review findings to act on.

Recurring specifics. Recurring correct option: "pass only the Coder's final output to the Reviewer, without the Coder's internal chain-of-thought." Recurring wrong option: "share a single context window so they collaborate in real time." Recurring citation theme: isolating subagent contexts is a key design pattern. Recurring small-model distractor: "use a smaller reviewer model" (size is not the issue; isolation is).

Wrong answers written against this rule

Proposal. shared context so reviewer sees coder reasoning.

Why it attracts. richness.

Why it fails. bias and sycophancy. When

When it would be right. never for review.

Proposal. smaller reviewer model.

Why it attracts. cheaper.

Why it fails. misses subtle errors; isolation is the real fix. When

When it would be right. never as the primary fix.

Proposal. coordinator uses extended thinking to evaluate both.

Why it attracts. depth.

Why it fails. single-pass, not isolation. When

When it would be right. never.

How the same rule gets re-asked
  • - Mutation: reviewer gets artifact plus generator reasoning. Effect: wrong (anchored). - Mutation: reviewer gets artifact only, independent instance. Effect: correct. - Mutation: reviewer gets artifact plus the independent instance's own findings to dispute. Effect: correct, common.
R22

The generator's self-reported uncertainty passed to the reviewer re-anchors the bias

A tempting efficiency move is to have the generator emit, alongside its output, a note about which parts it was "least sure about," and to pass that note to the independent reviewer as a hint. This feels like targeting scarce review attention. In fact it re-anchors the reviewer to the generator's self-assessment: the reviewer now scrutinises what the generator already doubted and trusts what the generator already endorsed, which is the same confirmation pattern in a new form.

The generator's uncertainty flag is itself a self-reported signal subject to the same calibration problems as confidence (Rule 12), and more importantly it is the generator's framing of its own work. An independent reviewer seeded with "these are the lines I doubted" evaluates through that framing rather than from the artifact. The independence that makes the second instance valuable is compromised the moment the generator's self-assessment enters its context.

Boundary. The nearby opposite case is the independent instance itself emitting a per-flag confidence after judging the artifact fresh; that confidence is the reviewer's, not the generator's, and is legitimate. The boundary is whose self-assessment: the reviewer's own judgment is fine; the generator's is the contaminate.

Recurring specifics. Recurring wrong option: "pass the generating run's notes on which lines it was least sure about into the review instance." Recurring correct contrast: "the reviewer must approach the code without the generator's reasoning." Recurring field: confidence attached by the first pass then used to route in the second.

Wrong answers written against this rule

Proposal. pass generator uncertainty to target reviewer attention.

Why it attracts. efficiency.

Why it fails. re-anchors. When

When it would be right. never.

Proposal. have reviewer emit its own confidence.

Why it attracts. legit routing signal.

Why it fails. only if it is the reviewer's, not the generator's. When

When it would be right. correct when self-generated.

Proposal. feed generator reasoning to a second instance "to target weak steps.".

Why it attracts. focus.

Why it fails. same anchor. When

When it would be right. never.

How the same rule gets re-asked
  • - Mutation: generator uncertainty passed, reviewer also given raw artifact. Effect: still biased by the note. - Mutation: reviewer generates its own confidence on the artifact. Effect: correct. - Mutation: no generator note at all, artifact only. Effect: correct and cleanest.
R23

Not every task should be decomposed; single calls win for dependent, shared-context work

Decomposition is a tool with costs, not a default. When a workflow's steps are strictly sequential and each depends on the previous step's conclusions over the same document, when there is no requirement to log or inspect intermediates, and when latency matters for an interactive tool, a single request with clearly enumerated steps is the right choice. Current models with adaptive thinking handle most such sequential reasoning internally, so chaining would add round trips and context-reconstruction tax without benefit.

Explicit chaining earns its overhead only when you need observability or control at a stage boundary (Rule 18). If you only care about the final output and the steps share context, a single call lets the model plan and execute the sequence in one pass, which is faster and keeps the shared reasoning coherent. Fanning dependent steps out to parallel subagents is an anti-pattern here because isolated contexts sever exactly the shared reasoning that makes the final result coherent.

Boundary. The nearby opposite case is exactly Rule 18: when auditability, logging, or a human gate is required, chain. The boundary is requirement: governance needs chain; pure shared-context sequential reasoning with no inspection need stays single-call. The mistake is decomposing by reflex.

Recurring specifics. Recurring scenario: four steps over one document, each depending on the prior, no audit requirement, interactive, latency matters. Recurring correct option: "send the whole task as a single request with the steps described clearly." Recurring wrong option: "run each step in a fresh session persisting output to a file." Recurring anti-pattern: "delegate each step to its own parallel subagent."

Wrong answers written against this rule

Proposal. fresh session per step, rehydrate from files.

Why it attracts. isolation.

Why it fails. pays context-reconstruction tax; severs shared reasoning. When

When it would be right. only when a single context cannot hold the work.

Proposal. parallel subagents per dependent step.

Why it attracts. speed.

Why it fails. breaks dependencies; isolation severs coherence. When

When it would be right. never for dependent steps.

Proposal. chain everything for safety.

Why it attracts. caution.

Why it fails. unnecessary latency when no governance need. When

When it would be right. only with governance requirements.

How the same rule gets re-asked
  • - Mutation: single call but with an audit requirement added. Effect: then chain is required (Rule 18). - Mutation: single call, no audit need. Effect: correct. - Mutation: single call where steps are independent. Effect: also fine, but parallel subagents would be faster.
R24

Iterative review needs explicit done criteria to avoid infinite fix-find loops

When a review finds issues, fixes are applied, and a further review finds new issues, the loop can continue indefinitely: each pass surfaces a few more findings, never reaching zero. The fix is to define done criteria up front: a maximum number of iterations, a severity threshold below which remaining findings are accepted, or an explicit completion signal. This bounds the loop practically without sacrificing the value of multi-pass review.

Without a stopping rule, the marginal value of another pass shrinks while the cost continues. Some findings are low severity or stylistic and not worth another full cycle. An explicit done criterion converts "review until perfect" (unreachable) into "review until the defined bar is met," which is operable. The criterion is a product decision, not a model behaviour.

Boundary. The nearby opposite case is a safety-critical gate where every finding must be resolved before release; there, the done criterion is "zero open critical findings," not "stop after N." The boundary is risk: high-risk gates require resolution of all severe findings; lower-risk loops can accept a severity floor or iteration cap.

Recurring specifics. Recurring loop shape: "first review finds 5, second finds 3, third finds 2, continues." Recurring correct option: "define done criteria: max iterations, severity threshold, or completion signal." Recurring wrong option: "continuous iterations until no issues found." Recurring alternative: "separate independent instance for each pass" (helps independence but does not bound the loop).

Wrong answers written against this rule

Proposal. continuous review until no issues found.

Why it attracts. maximal quality.

Why it fails. infinite; impractical. When

When it would be right. never as the sole rule.

Proposal. separate instance per pass.

Why it attracts. independence.

Why it fails. does not stop the loop. When

When it would be right. as a quality measure, paired with done criteria.

Proposal. single comprehensive review upfront.

Why it attracts. one shot.

Why it fails. misses what multi-pass catches. When

When it would be right. only for small inputs.

How the same rule gets re-asked
  • - Mutation: done criteria of max iterations only. Effect: correct, simple. - Mutation: done criteria of severity threshold. Effect: correct, risk-aware. - Mutation: no criteria. Effect: wrong, infinite loop.
R25

Contradictory source data must be preserved, not silently reconciled into one value

Sometimes the "error" is not in the extraction but in the source: two figures that should agree do not (for example stated assets versus liabilities plus equity). A review pass that recomputes and overwrites the value with a single "correct" figure destroys audit evidence. The correct design enriches the schema to carry both the stated value and the derived value, plus a conflict_detected boolean, and lets a downstream validator block filing or routing when a conflict exists.

result.json
json
{
  "stated_total_assets": 4800000,
  "calculated_total_assets": 4600000,
  "conflict_detected": true,
  "liabilities": 2100000,
  "equity": 2500000
}

A semantic conflict in contradictory source data is not an extraction error; it is a real property of the input that downstream consumers must see. Silently correcting it masks the discrepancy and removes the ability to investigate or reconcile. Preserving both values plus a flag makes the conflict explicit and actionable while keeping the original data intact for audit. This is distinct from routing on confidence: here the issue is data integrity, not model uncertainty.

Boundary. The nearby opposite case is a genuine extraction error where the model misread a single clear value; there, correction or flagging of the misread is appropriate. The boundary is source conflict versus extraction mistake: when two authoritative-looking sources disagree, preserve both; when one clear source was misread, the misread is the defect to catch.

Recurring specifics. Recurring schema fields: stated_total_assets, calculated_total_assets, conflict_detected. Recurring wrong option: "recompute and overwrite total_assets when they disagree." Recurring correct option: "extend the schema so the tool extracts the filing's stated value alongside a derived value, and set a conflict flag, passing all three downstream." Recurring blocker: "downstream validator blocks filing where conflict_detected is true." The schema preserves both values and the conflict flag rather than overwriting: A downstream validator blocks filing whenever conflict_detected is true, so the contradictory source data is surfaced for reconciliation instead of being silently replaced by a single recomputed figure.

Wrong answers written against this rule

Proposal. second instance recomputes and substitutes a corrected value.

Why it attracts. clean output.

Why it fails. destroys evidence of the conflict. When

When it would be right. never for source conflicts.

Proposal. self-review pass re-reads own output and overwrites.

Why it attracts. internal fix.

Why it fails. same-session bias plus evidence loss. When

When it would be right. never.

Proposal. add a flag but keep emitting only the single stated value.

Why it attracts. small change.

Why it fails. downstream cannot see the derived figure. When

When it would be right. never as the sole fix.

How the same rule gets re-asked
  • - Mutation: preserve both plus flag, block on conflict. Effect: correct. - Mutation: preserve both but route on confidence only. Effect: wrong; confidence hides the structural conflict. - Mutation: single reconciled value plus confidence enum. Effect: wrong; evidence lost.
R26

Independent review needs categorical criteria and counter-examples to raise precision

When the failure mode is not missed findings but false positives (the reviewer flags compliant items as violations), the independent reviewer must be given explicit categorical criteria mapping concrete artifact language to severity, plus worked counter-examples of items that look like violations but are not. This equips the fresh reviewer to distinguish genuine exposures from benign patterns, raising precision without sacrificing the independence that makes it effective.

A fresh reviewer solves the bias problem but, without criteria, may apply its own loose heuristics and over-flag. Categorical criteria plus counter-examples give it checkable targets and teach it the boundary between a real issue and a near-miss. This is why the strongest designs pair independence with "explicit categorical criteria for each violation type, including a worked example of compliant wording that must not be flagged." The criteria live in the independent review, not as a self-review instruction.

Boundary. The nearby opposite case is a recall problem (missed findings), where the priority is independence plus thoroughness, and criteria mainly ensure consistent coverage. The boundary is precision versus recall: when false positives dominate, add counter-examples; when misses dominate, add thoroughness mandates. Both still require independence.

Recurring specifics. Recurring correct option: "applies explicit categorical criteria for each violation type, including a worked example of compliant disclaimer wording that must not be flagged." Recurring wrong option: "instruct the agent in the same thread to be more conservative." Recurring precision distractor: "lower the confidence threshold so fewer findings reach reviewers" (wrong; hides more). Recurring QA precision fix: "redefine instructions to separate real errors from acceptable variations with examples."

Wrong answers written against this rule

Proposal. lower the confidence threshold to reduce flagged findings.

Why it attracts. fewer items.

Why it fails. hides real violations. When

When it would be right. never for precision.

Proposal. ask reviewers to manually sort findings weekly.

Why it attracts. human judgment.

Why it fails. does not scale; treats symptom. When

When it would be right. never as the design.

Proposal. add a second QA pass re-checking the first.

Why it attracts. verification.

Why it fails. if both share context or lack criteria, bias and looseness remain. When

When it would be right. only if independent and criteria-driven.

How the same rule gets re-asked
  • - Mutation: independence plus criteria. Effect: correct. - Mutation: criteria in same session. Effect: wrong (bias remains). - Mutation: independence without criteria. Effect: fixes bias but precision still low.
R27

Route by risk-bearing path, not only by confidence, when reviewer bandwidth is scarce

When a pipeline produces many findings but human reviewers are few, an effective allocation strategy is to route by the risk-bearing path of the finding: findings in security-critical areas (authentication, payments) go to humans, while low-impact findings elsewhere are deprioritised. This is a distinct concern from confidence-based routing. Risk-based filtering ensures scarce attention lands on high-impact areas rather than being swamped by low-value alerts across the whole codebase.

Confidence routing targets model uncertainty; risk routing targets consequence. A low-confidence but cosmetic finding and a high-confidence but security-critical finding have different stakes. When bandwidth is the constraint, consequence should dominate: humans should see the findings whose failure costs the most, regardless of the model's self-reported certainty. This is why "filter by security-critical path" is the correct answer for scarce-reviewer allocation, while "route only low-confidence findings to humans" is flagged as overwhelming reviewers with potentially false positives.

Boundary. The nearby opposite case is a workload where all findings are high-consequence and the differentiator is genuinely model uncertainty; there, confidence routing is appropriate. The boundary is consequence versus uncertainty: when stakes vary by location, route by path; when stakes are uniform, route by calibrated confidence.

Recurring specifics. Recurring correct option: "isolate findings in security-critical paths (authentication, payments) and route those to human reviewers." Recurring wrong option: "route only low-confidence findings to humans." Recurring contrast: risk-based filtering optimises reviewer bandwidth on high-impact areas. Recurring distractor: "aggregate by category and show one example" (obscures individual bugs).

Wrong answers written against this rule

Proposal. route low-confidence findings to humans.

Why it attracts. targets uncertainty.

Why it fails. overwhelms with false positives, misses high-impact confident items. When

When it would be right. only with calibration and as a complement.

Proposal. prioritise by lines of code affected.

Why it attracts. size feels like impact.

Why it fails. a one-line auth bug is more critical than a large cosmetic change. When

When it would be right. never as sole signal.

Proposal. one representative per category.

Why it attracts. volume reduction.

Why it fails. hides individual critical bugs. When

When it would be right. never.

How the same rule gets re-asked
  • - Mutation: risk routing alone. Effect: correct for allocation, ignores model uncertainty. - Mutation: confidence routing alone. Effect: wrong when stakes vary by path. - Mutation: risk plus calibrated confidence. Effect: strongest.
R28

The independent reviewer judges the code as written, not as the generator intended

The deepest framing of the self-review deficit is intent versus output. A generating session reviews what it intended to write, because its context records the intention ("I used null return here because edge case X"). An independent reviewer sees only the code as actually written, with no access to that intention, and therefore evaluates the output against the criteria. This is why logic errors in error-handling paths are missed by self-review but caught immediately by a human or a fresh instance: the generator "remembers intending it to be correct."

The generation reasoning encodes the author's mental model, which may be wrong. When the review reuses that model, it validates the intention rather than testing the artifact. The fresh reviewer has no intention to defend, so it reads the artifact literally and notices where written behaviour diverges from correct behaviour. This is confirmation bias at the architectural level: the generator cannot objectively review its own output because its context contains the reasoning that produced it.

Boundary. The nearby opposite case is when the intent and the output coincide and the task is merely to polish; there, self-review is harmless. The boundary is gating versus polishing: any time a verdict of correctness is produced and trusted, intent must be stripped; when only improvement is sought, intent can stay.

Recurring specifics. Recurring contrast phrasing: "reviews what it intended to write, not what it actually wrote" versus "reviews the code as written, the same way a human reviewer would." Recurring symptom: "logic error in error handler missed, style issues found" (low mental investment items caught, high-investment intent-defended items missed). Recurring rule statement: "never use the same instance to both generate and review."

Wrong answers written against this rule

Proposal. run the review immediately after generation to keep focus.

Why it attracts. freshness.

Why it fails. context still holds the intent. When

When it would be right. never.

Proposal. add more review criteria to the generation session.

Why it attracts. focus.

Why it fails. reviews intent, not output. When

When it would be right. as a generation aid only.

Proposal. switch to a larger model tier for review.

Why it attracts. capability.

Why it fails. still sees intent if context shared. When

When it would be right. never as the independence substitute.

How the same rule gets re-asked
  • - Mutation: independent instance, artifact only. Effect: correct; judges output. - Mutation: same instance, more criteria. Effect: wrong; judges intent. - Mutation: independent instance but given generator reasoning. Effect: wrong; intent leaks back in.
Fourteen-file pull request that hides a SQL injection in the middle
A production walkthrough with the reasoning chain made explicit.

A pull request modifying fourteen files arrives for automated review at a financial services team. The single-pass review produces the canonical attention dilution symptoms: detailed feedback on the first and last files, superficial comments on the middle files, a missed SQL injection vulnerability in file seven, and contradictory findings where the same shadowing pattern is flagged as problematic in file three but approved in file eleven. A larger context window is proposed as a fix, but the correct answer rejects it because larger capacity does not correct uneven attention quality.

The restructured review splits into per-file local analysis. Each of the fourteen files is reviewed in its own invocation with the same focused prompt for bugs, security issues, and logic errors, so file seven receives the same focused attention as every other file. The SQL injection is now caught because middle files are no longer diluted by neighbours, and parallel execution reduces total wall time since the fourteen per-file reviews run concurrently.

The cross-file integration pass receives the structured findings from all per-file reviews and runs a separate prompt that asks for data flow inconsistencies between modules, contradictory patterns flagged in different files, and API contract violations across service boundaries. It catches the data flow issue between the API service and the data layer that no single-file review could identify, and it surfaces the contradiction between the file three and file eleven shadowing findings so they can be resolved rather than shipped as conflicting guidance.

Each finding is annotated with a confidence score from 0.0 to 1.0 and a reasoning string, with high confidence findings routing directly to the developer and low confidence findings routing to a human review queue. The routing threshold is not taken from raw scores but calibrated against a labelled validation set of past pull requests, so the boundary reflects observed accuracy across the range of finding types rather than self-reported certainty. The result costs more than single-pass self-review but is the correct tradeoff when review quality governs production reliability and regulatory exposure.

Distinctions that decide answers

ThisNot thisHow to tell them apart
Self-review in same sessionIndependent review instanceSelf-review retains reasoning context and tends to confirm prior decisions, independent has no prior reasoning and approaches output fresh, with significantly higher effectiveness on subtle issues.
Single-pass reviewMulti-pass review with per-file plus integrationSingle pass produces inconsistent depth, missed bugs, and contradictions from attention dilution. Multi-pass splits into per-file local passes plus a cross-file integration pass for consistent depth and systemic coverage.
Larger context windowFocused per-file passesLarger capacity does not improve attention quality, the model still gives uneven attention across files. Focused per-file passes ensure consistent depth regardless of window size.
Raw confidence scoresCalibrated confidence thresholdsRaw confidence is poorly calibrated self-reported certainty, calibrated thresholds are validated against labelled sets and suitable for routing decisions. Using raw confidence for automation is an anti-pattern.
Per-file analysisCross-file integrationPer-file catches local issues within a single file such as bugs, security, and logic errors. Integration catches systemic issues across files such as data flow, contract violations, and contradictions.
Routing to human reviewAuto-approving findingsHigh confidence findings auto-report to developers, low confidence findings route to human review. The threshold determines treatment and must be calibrated.

Traps

Self-review in the same session as a viable review strategy

The tempting answer. Review the generated output in the same session to avoid extra API calls because the model already has full context.

Why it fails. The model retains its generation reasoning and is less likely to question decisions it already justified, so subtle issues survive that an independent instance would catch.

What is correct. Use a separate Claude instance with a fresh session and no prior generation history so the reviewer approaches the output without the bias of prior reasoning.

Single pass for large multi-file reviews

The tempting answer. Run a single review pass over fourteen files because single-pass is simpler to implement.

Why it fails. Single-pass multi-file reviews produce inconsistent depth, miss bugs in the middle, and generate contradictory findings due to attention dilution.

What is correct. Split into per-file local passes for consistent depth plus a cross-file integration pass for systemic issues.

Larger context window to fix attention dilution

The tempting answer. Switch to a higher-tier model with a larger context window so fourteen files fit more easily.

Why it fails. The problem is attention quality, not capacity. A larger window holds more text but the model still spreads attention unevenly across files.

What is correct. Keep focused per-file passes, which ensure consistent depth regardless of window size.

Uncalibrated confidence scores for automated routing

The tempting answer. Route findings automatically by raw confidence scores such as treating 0.85 and above as high confidence.

Why it fails. Raw self-reported confidence is poorly calibrated and the score does not reliably track actual accuracy.

What is correct. Calibrate thresholds against labelled validation sets that cover the range of finding types and severities, then route by the calibrated boundary.

Sequential per-file reviews instead of parallel

The tempting answer. Run per-file reviews sequentially because sequential execution is simpler to code.

Why it fails. Per-file reviews are independent inputs that can run concurrently, so sequential execution wastes wall-clock time for no quality gain.

What is correct. Parallelise per-file reviews and run the integration pass sequentially only after all per-file results are available.

Skip the integration pass when per-file reviews find no cross-file issues

The tempting answer. Skip the integration pass to save an API call when per-file reviews appear self-consistent.

Why it fails. The integration pass is the only mechanism that catches cross-file issues such as data flow between modules, API contract violations, and contradictions among per-file findings, which per-file passes cannot evaluate.

What is correct. Always run the integration pass after per-file analysis, regardless of whether per-file findings appear independent.

Going deeper than the task statement
Adjacent material that shows up in harder scenario questions.
Calibration methodology across finding types

Calibration requires labelled sets covering the range of finding types and severities, not an aggregate accuracy number, with threshold selection based on how reported confidence tracks actual accuracy per type.

Validation Strategies
Production review architecture as a five-component workflow

Generation plus per-file review plus integration review plus confidence routing plus calibration loop forms the production architecture, skipping any component reduces review quality in a measurable way.

Confidence Scoring and Uncertainty Handling
Detected_pattern as the refinement backlog signal

High dismissal rate per detected_pattern identifies prompt refinement priorities from real production data, with low dismissal patterns left alone and high dismissal patterns refined first for continuous quality improvement.

Escalation Patterns
Build it
Show single-pass dilution, multi-pass consistency, and calibrated routing
  1. Create a single-pass review prompt and run it against a ten-file mock pull request, documenting inconsistent depth such as detailed feedback on some files and superficial comments on others, at least one missed bug in a middle file, and at least one contradictory finding for the same pattern.
  2. Implement per-file local analysis by iterating over each file with the same focused review prompt for bugs, security, and logic errors, and compare missed bugs between single-pass and per-file results.
  3. Implement a cross-file integration pass that receives all per-file findings and checks for data flow inconsistencies, contradictory findings across files, and API contract violations, producing a synthesis that no single-file pass could produce.
  4. Add confidence scoring from 0.0 to 1.0 per finding with reasoning and implement routing where high confidence goes directly to the developer and low confidence goes to a human review queue.
  5. Use a separate Claude instance with a fresh session and no prior generation history to review a subset of findings and compare its assessment to the original confidence scores, calibrating the routing threshold from the labelled comparison.

Verify. Single-pass shows dilution and contradictions, per-file catches middle-file bugs with consistent depth, integration catches systemic cross-file issues, and calibrated routing separates usually correct from often wrong by measured accuracy rather than raw self-report.

Answer real questions on this task
8 exam-style questions written against the rules above. You get the reasoning and a note on every wrong option after each answer.
Prompt Engineering and Structured Output exam
12 questions, the same number this domain contributes to the real 60-question paper. Drawn fresh from a larger pool each attempt, so a retake is a different paper. You can reveal the answer to any question while taking the exam, which locks that question. Otherwise answers stay hidden until you submit.
Cross-cutting decision table
DecisionChoose thisOver thisWhy
Where to shape every responseExplicit categorical criteria in the system prompt with RACCEBe conservative or high-confidence language in the system promptExplicit criteria define a testable boundary per category, vague adjectives give none and are the classic distractor.
Inconsistent formatting or judgementFew-shot examples with reasoning, 2 to 4 examplesMore detailed prose instructionsExamples remove interpretation variance by demonstrating the principle, prose leaves room for interpretation.
Guaranteed schema-valid JSONtool_use with JSON schemas or output_config.format with constrained decodingPrompt asking for JSON in a text responseSchemas enforce structure at generation time, prompt-based JSON is probabilistic and will produce malformed output.
Which tool must runtool_choice any for choice, forced type tool name for mandatory steptool_choice auto when structured output must be guaranteedauto may return text instead of a tool, any guarantees a tool call, forced guarantees the specific tool.
Fields where source may lack dataOptional or nullable fields with unclear and other plus detail stringRequired fields for every propertyRequired pressures the model to fabricate, nullable plus unclear and other allow honest null and long-tail capture.
Schema versus correctnessSemantic validation with Pydantic plus retry with specific errorSchema validation alone with naive retrySchema catches syntax, semantics catches wrong values, and error feedback tells the model what to fix rather than repeating the mistake.
Fixable versus unfixable extraction failureRetry with document plus failed JSON plus specific error on fixable casesRetry on absent information or inverseFormat, structure, misplacement, and arithmetic are fixable, genuinely absent data is not, threshold is typically three retries then human review.
Latency class for a workflowSynchronous for blocking where someone waitsBatch API up to 24 hours with no SLABatch gives 50 percent savings for tolerant overnight and weekly work, synchronous keeps merge gates and real-time screens responsive.
Batch schedulingWork backwards from consumer deadline minus 24 hours, submit every 4 to 6 hoursPoll for status or design around best-case minutesThere is no SLA, plan around worst case and keep a fresh batch in flight.
Batch failure handlingResubmit only the failed custom_id values with pattern-targeted modificationsResubmit the entire batch or resubmit with no changeAlready successful documents should not be billed again, and each failure pattern needs its own fix such as chunking or a format-specific example.
Review quality for generated outputIndependent review instance with no generation historySelf-review in the same sessionSame-session review carries prior reasoning and confirms prior decisions, independent judges the output fresh.
Large multi-file audit coveragePer-file local passes in parallel plus one sequential cross-file integration passSingle pass even with a larger context windowPer-file ensures consistent depth including middle files, integration catches data flow and contradictions that no single-file pass can evaluate.
Confidence for automated routingCalibrated thresholds validated on labelled sets across finding typesRaw self-reported scoresRaw confidence is poorly calibrated, calibrated thresholds separate usually correct from often wrong by measured accuracy.
Why wrong answers keep looking correct
Be conservative or only report high-confidence findings are valid prompt improvements. They sound engineering grade but give no actionable decision boundary. The exam uses them as distractors because conservative varies by context and high-confidence is an uncalibrated subjective threshold that does not define what counts as a valid finding.
More detailed instructions will fix inconsistent formatting the way examples would. Prose descriptions still rely on interpretation and more precise prose does not eliminate interpretation variance. Few-shot examples demonstrating the exact desired format eliminate that variance by showing the pattern rather than describing it.
Tool schemas prevent all extraction errors, so extra validation is unnecessary. Schemas eliminate JSON syntax errors but not semantic errors such as wrong sums, values in the wrong field, or plausible inventions for absent data. Those require semantic validation and retry with error feedback.
Required schema fields are more rigorous than nullable fields. Required pressures the model to fabricate plausible values when the source lacks information, which is worse than an honest null. Nullable and optional fields with unclear and other allow the model to be honest and keep the long tail without validation failure.
Retries always improve extraction, so retry even when information is absent from the source. Retries fix format mismatches, structural errors, misplaced values, and arithmetic errors but cannot produce information genuinely absent from the document or available only in an external document. Absent data should route to human review or null, not infinite retry.
Batch savings apply equally to blocking workflows, so move everything to batch. Batch has no latency SLA and may take up to 24 hours. Blocking workflows where developers wait for a merge gate must stay synchronous, the 50 percent saving is only for latency-tolerant overnight and weekly work.
A larger context window fixes attention dilution in large reviews. The problem is attention quality, not capacity. A larger window holds more text but the model still spreads attention unevenly across files and still produces detailed feedback on some files and superficial comments on others. Focused per-file passes are the correct fix.
Last five minutes
Rules
  • If the system prompt uses be conservative or high-confidence, treat it as the vague distractor and look for explicit categorical criteria with concrete code examples per severity.
  • If output formatting or judgement is inconsistent despite detailed instructions, choose 2 to 4 few-shot examples with reasoning, not more prose, not confidence thresholds, and not temperature changes.
  • If schema-valid JSON must be guaranteed, choose tool_use with JSON schemas or output_config.format with constrained decoding over prompt asking for JSON in text.
  • If tool_choice is offered, remember auto may return text, any guarantees a tool call with model choice, and forced type tool name guarantees the exact tool and 400s on a name mismatch, plus strict true requires additionalProperties false.
  • If a field may be absent, choose nullable or optional with unclear and other plus detail string over required, required invites fabrication.
  • If validation fails, choose retry that sends the original document plus the failed extraction plus the specific validation error, capped around three retries, and route genuinely absent information to human review.
  • If a workflow is described as overnight, weekly, or results consumed later, choose batch with custom_id correlation and 50 percent savings, if someone is waiting for a merge result, choose synchronous.
  • If the SLA is 30 hours, subtract 24 hours to get the 6 hour buffer and require the final batch to be submitted at least 24 hours before the consumer deadline, submitting every 4 to 6 hours.
  • If a large pull request review shows detailed feedback on some files and superficial comments on others, choose per-file local passes plus a cross-file integration pass over single pass or larger context window.
  • If review is already done in the same session, choose an independent instance with no prior reasoning, and if routing uses confidence choose calibrated thresholds on labelled sets over raw scores.
Trigger phrases

Look for be conservative versus explicit criteria, detailed instructions still inconsistent versus few-shot, prompt JSON versus tool schema or structured outputs, auto versus any versus forced, required versus nullable with other, retry with versus without the specific error, genuinely absent versus misplaced value, blocking versus latency-tolerant, best-case minutes versus guaranteed 24 hours, custom_id missing versus correlated, self-review same session versus independent fresh session, single pass fourteen files versus per-file plus integration, raw 0 point 8 confidence versus calibrated labelled threshold.

If you see X, think Y

If you see review the code be conservative or only report high-confidence findings think vague boundary that fails verifiability and replace with categories plus examples. If you see detailed instructions produce different structures across invocations think few-shot with reasoning not more prose. If you see output the JSON in the response think prompt-based risk and choose schema with constrained decoding. If you see tool_choice auto for guaranteed structured output think the model may still return text and choose any or forced. If you see purchase order required think fabrication risk and make it nullable. If you see line_items sum does not match stated_total think calculated_total versus stated_total plus retry with the specific mismatch. If you see weekly audit consumed Monday morning think batch eligible, if you see pre-merge check where developers are blocked think synchronous. If you see fourteen files and missed SQL injection in the middle think per-file plus integration, not larger window. If you see please review carefully in the same session think independent instance without prior reasoning.

Official documentation
Every claim on this page traces to Anthropic documentation or a lesson on this site. Verify version-specific limits, flags, and pricing against the live docs.