Skip to content

ID-71 Lane B — Structured outputs + citations API currency (2026-06-09)

Lane B research input for {71.1}. Question: what is the CURRENT state of Anthropic structured outputs and citations APIs, and what should Knowledge Hub’s grounding standards be? Baseline being re-verified: S195-era research (SDK 0.78.0, Claude 4.6 launch window) which established (a) strict: true as deferred hygiene (OPS-30/bl-50), (b) citations × structured outputs API incompatibility forcing the 3-pass lib/ai/draft.ts pipeline, (c) “schema enforcement ≠ semantic correctness”.

Evidence labels: VERIFIED = read directly from official docs/changelog (fetched 2026-06-09) or from the repo. INFERRED = reasoned from verified facts, not explicitly documented.

1. Repo grounding — KH’s current state (all VERIFIED from source)

Section titled “1. Repo grounding — KH’s current state (all VERIFIED from source)”
ItemState
SDK@anthropic-ai/sdk 0.96.0 (package.json:66) — well past the 0.78.0 S195 baseline
Model tiers (lib/anthropic.ts:28-30)analysis: claude-sonnet-4-5, drafting: claude-opus-4-6, quality: claude-haiku-4-5; getAIModel() default claude-sonnet-4-6 (line 18)
lib/ai/draft.ts Pass 1 (Sonnet)Already migrated to structured outputsoutput_config.format with json_schema (questionAnalysisSchema), JSON.parse with silent fallback default
lib/ai/draft.ts Pass 2 (Opus)search_result content blocks with citations: {enabled: true} (lines 232-245, 364-377), no beta header — correct, search results are GA (see §3)
lib/ai/quality-check.ts Pass 3 (Haiku)Already migrated to structured outputsoutput_config.format (line 142)
draft.ts header comment”Citations and Structured Outputs are incompatible in the Claude API, so these MUST remain as separate API calls” — still accurate (see §4)
lib/ai/classify.tsTwo forced-tool-use passes: Pass 1 return_classification (tool_choice {type:'tool'}, line 1358; model = getAIModel() → sonnet-4-6) and Pass 2 return_entity_validation (line 1039; claude-haiku-4-5). No strict: true; nested objects partially missing additionalProperties: false; uses type: ['string','null'] union types
lib/ai/extract-questions.ts:404model: 'claude-haiku-3-5' with forced tool use — not a published model alias. Haiku 3.5’s real ID was claude-3-5-haiku-20241022, retired on the first-party API 2026-02-19. This call site is either dead or 404ing; also absent from lib/ai/pricing.ts. Side-finding — flag into ID-71 scope
bl-50 backlog noteConfirms scope: strict: true + recursive additionalProperties: false on classify tool schemas; absorbed into ID-71 (D5), implementation = Phase-0.4, gated on this Lane B re-verification

Net: KH already adopted structured outputs in 2 of 3 drafting passes post-S195. The unfinished items are the classify/extraction tool schemas (strict: true) and the question of whether the 3-pass split can collapse.

2. Structured outputs — current state (VERIFIED)

Section titled “2. Structured outputs — current state (VERIFIED)”

Source: Structured outputs (platform.claude.com, fetched 2026-06-09); API release notes.

  • GA since 2026-01-29 on the Claude API (“no beta header required”). The old structured-outputs-2025-11-13 beta header still works during a transition period but is unnecessary. Public beta launched 2025-11-14.
  • Canonical parameter is output_config.format ({type: "json_schema", schema}). The top-level output_format parameter is deprecated API-wide (“has moved to output_config.format”). SDK messages.parse() accepts output_format as a convenience and translates internally.
  • Two independent features, combinable in one request: JSON output format (output_config.format) and strict tool use (strict: true per tool). Docs: “You can use these features independently or together in the same request… Claude can call tools with guaranteed-valid parameters AND return structured JSON responses.”
  • strict: true semantics: constrained decoding guarantees tool names and inputs conform to the schema. additionalProperties: false is required on every object in a strict schema.
  • Supported models (live doc): Opus 4.8, Opus 4.7, Opus 4.6, Sonnet 4.6, Sonnet 4.5, Opus 4.5, Opus 4.1, Haiku 4.5 (plus Mythos Preview). This covers every model KH has configured. (Claude Fable 5, launched today 2026-06-09, is listed as supporting structured outputs in Anthropic’s bundled claude-api reference; the live doc page fetched did not yet show it — INFERRED supported, immaterial to KH.)
  • Schema limitations (unchanged in kind from S195): no recursive schemas, no numerical constraints (minimum/maximum/multipleOf), no string length constraints, additionalProperties only false, array minItems only 0 or 1. Supported: enum, const, anyOf/allOf, internal $ref, string format (uuid, date-time, email, uri, …). Python/TS SDKs strip unsupported constraints and validate client-side.
  • Complexity limits (new since S195): max 20 strict tools per request, max 24 optional parameters and 16 union-typed parameters combined across all strict schemas in a request.
  • Latency/caching: first request with a new schema pays grammar compilation (max 180 s timeout); compiled grammars cached 24 h from last use. Cache is keyed on schema structure + tool set + output_config.format — changing only a tool’s name/description does not invalidate. The injected format system prompt counts toward input tokens.
  • Works with: tool use, streaming, token counting, batch processing (50% discount).
  • Failure modes: stop_reason: "refusal" → output may not match schema; stop_reason: "max_tokens" → JSON may be truncated. Both must be handled.
  • Prefill is dead: last-assistant-turn prefills return 400 on the entire 4.6/4.7/4.8 familyoutput_config.format is the prescribed replacement (migration guide, VERIFIED). KH has no prefill usage (confirmed by absence in lib/ai), so this is a standards point, not a remediation.

3. Citations + search results — current state (VERIFIED)

Section titled “3. Citations + search results — current state (VERIFIED)”

Source: Citations, Search results (fetched 2026-06-09); release notes.

  • Citations: supported on all active models except Haiku 3; enabled per document block via citations: {enabled: true} (must be all-or-none across the request’s documents). Three document types (plain text → char indices, PDF → page numbers, custom content → block indices). cited_text is free of output-token charges and free of input-token charges when passed back in later turns. Streaming emits citations_delta. ZDR-eligible.
  • Search result content blocks (type: "search_result" with source, title, citations: {enabled: true}): GA on the Claude API since 2025-08-08 — “The beta header search-results-2025-06-09 is no longer required.” Available on all current models incl. Opus 4.6/4.7/4.8, Sonnet 4.5/4.6, Haiku 4.5. KH’s beta-header-free usage in draft.ts on SDK 0.96.0 is the correct current form (and is empirically working in production).
  • Citations work with prompt caching (cache the document/search-result blocks via cache_control), token counting, and batches. Note from the prompt-caching invalidation hierarchy: toggling citations invalidates the system+messages cache tiers but preserves the tools-tier cache.

4. The critical question: can citations + structured outputs combine now? — NO (VERIFIED)

Section titled “4. The critical question: can citations + structured outputs combine now? — NO (VERIFIED)”

The live citations page (fetched 2026-06-09) carries an explicit warning, quoted verbatim:

“Citations and Structured Outputs are incompatible. Citations cannot be used together with Structured Outputs. If you enable citations on any user-provided document (Document blocks or RequestSearchResultBlock) and also include the output_config.format parameter (or the deprecated output_format parameter), the API will return a 400 error. This is because citations require interleaving citation blocks with text output, which is incompatible with the strict JSON schema constraints of structured outputs.”

  • The API changelog (scanned back through 2024) contains no entry lifting this restriction. The S195 finding stands as of 2026-06-09.
  • The incompatibility is architectural (citation blocks interleave with free text; a constrained-decoding grammar over the whole output cannot accommodate them), so it is unlikely to be lifted by a quiet flag flip — expect any future fix to be a first-class “cited structured outputs” feature, worth watching the changelog for.
  • Nuance (INFERRED): the 400 trigger is specifically output_config.format / output_format co-occurring with citation-enabled blocks. strict: true on tool definitions is not named, and the structured-outputs page does not mention citations at all. A request combining strict tools + citation-enabled search results (e.g. an agentic loop that searches with validated tool inputs and then writes cited prose) is plausibly legal — untested. If ID-71 ever needs this shape, run a 2-call empirical probe first. Not needed for any current KH pipeline.
DateEvent
2025-01-23Citations launched
2025-07-03Search result content blocks beta (search-results-2025-06-09)
2025-08-08Search result blocks GA on Claude API + Vertex (header dropped)
2025-11-14Structured outputs public beta (structured-outputs-2025-11-13; Sonnet 4.5 + Opus 4.1)
2025-12-04Structured outputs extended to Haiku 4.5
2026-01-29Structured outputs GA (no header; output_formatoutput_config.format)
No entry to date changing citations × structured-outputs incompatibility

(i) bl-50 / OPS-30 (strict: true on classify schemas) — still valid, now cheaper than ever

Section titled “(i) bl-50 / OPS-30 (strict: true on classify schemas) — still valid, now cheaper than ever”

Everything that made this “cheap hygiene” at S195 has improved: strict tool use is GA (no beta header), KH’s SDK 0.96.0 fully supports it, and the 24 h grammar cache amortises the one-off compile latency. Exact change shape:

  1. lib/ai/classify.ts Pass 1 tool return_classification (def ~line 1226) and Pass 2 tool return_entity_validation (def ~line 1000): add strict: true at the tool level; recursively add additionalProperties: false to every nested object (entities, relationships, temporal_references, validated_entities, the per-date-item objects — several currently have required but no additionalProperties).
  2. Pre-flight audit against the new strict limits (these did not exist in the S195 analysis): ≤ 24 optional parameters and ≤ 16 union-typed parameters per request. classify’s schemas use type: ['string','null'] unions — count them; if over the limit, convert optional-nullable fields to required + nullable (a required union still counts toward the 16) or trim the schema. This is the only genuine risk in the change; everything else is mechanical.
  3. Schema-stability caveat (INFERRED): the grammar cache is keyed on schema bytes. If any enum in the classify schema is built dynamically from the DB taxonomy, each taxonomy change forces a fresh compile (fine — 24 h cache per variant) but avoid anything per-request-dynamic in the schema.
  4. Extend the same treatment to lib/ai/extract-questions.ts TENDER_METADATA_TOOL — and fix its dead claude-haiku-3-5 model ID (→ claude-haiku-4-5, plus a pricing.ts entry) while in the file.
  5. Python pipeline (scripts/kb_pipeline/classify.py) is prompt-driven with no tool schema — unaffected, as the bl-50 note already records.
  6. The S195 lesson is unchanged: strict: true guarantees structural validity (no invented rel_type enum values, no malformed JSON); it does not guarantee the model picked the right enum value. Keep the existing semantic checks.

(ii) The 3-pass drafting split — remains forced; do not merge

Section titled “(ii) The 3-pass drafting split — remains forced; do not merge”

Pass 2 (Opus, citation-bearing drafting over search_result blocks) cannot carry output_config.format — verified 400 (§4). Passes 1 and 3 already use structured outputs and are on the right pattern. Even setting the 400 aside, a forced tool call inside the citations request would replace the cited-text output rather than augment it (citations attach to text blocks — INFERRED), so there is no merge shape available. The draft.ts header comment is current and should stay. The only worthwhile change in the drafting pipeline is hardening (§iii fallbacks), not architecture.

(iii) Proposed KH grounding standard (seed for {71.3} TECH + Phase 5 W4 ADR)

Section titled “(iii) Proposed KH grounding standard (seed for {71.3} TECH + Phase 5 W4 ADR)”

Decision rule — for every AI touchpoint, pick exactly one of three shapes:

  1. Structured data, no source attribution neededoutput_config.format with json_schema (GA). This is the default for analysis, quality checks, metadata extraction, summaries-as-data. Validate the parsed result with the existing zod schema (z.infer remains the canonical composed-type source per CLAUDE.md).
  2. Model-decides / multi-tool or forced-tool extraction → tool use with strict: true + additionalProperties: false on every tool (constrained decoding at the token level). Forced single-tool extraction (tool_choice: {type:'tool'}) and output_config.format are near-equivalent here; prefer output_config.format for new single-shot extractions (simpler response handling), keep strict tools where the call site already uses the tool pattern (classify) or genuinely needs tools.
  3. Output must be traceable to KB sources → citations over search_result content blocks (GA, no header). These calls return interleaved prose + citations and must not include output_config.format/output_format.

Needs both structure and citations? → split into two calls (the 3-pass pattern is the canonical instance): cited-prose call first, structured call second (or in parallel where independent). Record this as the standing constraint with a changelog watch — revisit only if Anthropic ships cited structured outputs.

Mandatory hardening at every structured touchpoint:

  • Handle stop_reason: "refusal" (schema conformance not guaranteed) and "max_tokens" (truncated JSON) explicitly — don’t let them fall into generic parse-failure paths.
  • No silent fallback defaults: draft.ts Pass 1’s bare try { JSON.parse } catch {} predates structured outputs; with output_config.format a parse failure now signals refusal/truncation, so log + surface it (keep a fallback value, lose the silence).
  • Never use assistant prefills (400 on all 4.6+ models) or prompt-only “respond in JSON” forcing.
  • Keep schemas static per call site (grammar-cache discipline); changing tool name/description is free, changing schema structure recompiles.
  • Schema enforcement ≠ semantic correctness — evals/spot-checks remain the quality gate (S195 lesson, re-affirmed).