@@ -382,11 +382,20 @@ The system prompt (including any leading OAuth/required-identity string) is
382382 content. A decoder recovering the system block as "the first System message" is
383383 faithful and sufficient; the summarizer never interprets identity-vs-content
384384 because it never touches the system block.
385- - ** PINNED, never summarized.** The system block is OUTSIDE the compactable
386- conversation region. The MC Transform compacts message HISTORY (the conversation
387- prefix); it MUST NEVER summarize, drop, reorder, or mutate the system block. CK
388- MAY represent it as one or more ` Role::System ` messages at the head (so decode is
389- faithful round-trip), but those head messages are pinned.
385+ - ** PINNED keys on POSITION, not role.** The pinned-never-summarized property is
386+ the HEAD system prompt (the leading ` Role::System ` message(s)), NOT the system
387+ role in general. The MC Transform compacts message HISTORY (the conversation
388+ prefix); it MUST NEVER summarize, drop, reorder, or mutate the head system
389+ block. CK MAY represent it as one or more ` Role::System ` messages at the head
390+ (faithful round-trip), and those head messages are pinned.
391+ - ** Mid-conversation system messages are ordinary content.** A ` Role::System `
392+ message appearing MID-conversation (Anthropic supports this via the
393+ ` mid-conversation-system-* ` beta; the converter pushes inline ` role:'system' `
394+ blocks) is NOT the pinned head block - it is ordinary bucket-(a) CONTENT,
395+ summarizable/compactable like any other message. The distinction is POSITIONAL:
396+ head-system = pinned + HARD-bust marker; mid-conversation-system = content. A
397+ role-keyed pinning rule would be wrong (it would over-pin mid-conversation
398+ system content).
390399- ** Feeds the HARD-bust hash, not the summarizer.** The system block's bytes are a
391400 render-config HARD-marker input: a change to them is a HARD bust (MC's existing
392401 ` cachedM0SystemHash ` trigger). This is consistent with treating it as
@@ -403,6 +412,71 @@ neutralize such volatility (the same class as MC's sticky `Today's date` line an
403412the Anthropic ` cch ` nonce normalization). CK is unaffected; this is a render-config
404413stabilization requirement.
405414
415+ ## 5.12 Cross-message (assembly) semantics
416+
417+ CK is a PER-MESSAGE model, but the provider wire ASSEMBLES across messages: N
418+ CkMessages render to M <= N provider messages. Verified at source
419+ (` @ai-sdk/anthropic ` ` convert-to-anthropic-prompt.ts ` ): ` groupIntoBlocks() ` runs
420+ first, then system messages PULL to a top-level ` system ` field, consecutive
421+ user+tool messages COMBINE into one user message (tool_results absorbed into the
422+ adjacent user turn), consecutive assistant messages COMBINE into one, ` cache_control `
423+ is placed per-block, and positional `isLastBlock && isLastMessage &&
424+ isLastContentPart` governs the final reasoning/text. This section pins how that
425+ cross-message layer relates to CK.
426+
427+ ** 5.12.1 Assembly is RENDER-side (restates §5.1/§6.4).** System-pull, user+tool
428+ combine, assistant merge, cache-control placement, and last-part positioning are
429+ performed by ` render() ` (owned/MITM) or the harness serializer (plugin), NOT by the
430+ MC Transform or the codec. CK preserves ` role ` + part ORDER + per-message content +
431+ ` provider_extras ` ; the renderer assembles. CK MUST NOT pre-assemble (no double-assembly).
432+
433+ ** 5.12.2 Defined cross-message terms (NORMATIVE).** Two cross-message concepts the
434+ TRANSFORM reads (not just the renderer):
435+ - ** merge-group** : a maximal run of consecutive same-role (Assistant) CkMessages
436+ that the serializer will COMBINE into one provider message. ` decide_reasoning ` 's
437+ ` is_after_first_of_merge_group ` (§6.1) is membership in such a group after its
438+ first message.
439+ - ** is_last_assistant_turn** : the positional predicate ` rposition ` over
440+ non-(head-)system messages identifying the latest assistant turn (§6.1).
441+ Both are cross-message and body-derived; both are FROZEN render-params (§6.1
442+ FROZEN-BIT rule), never re-derived per pass.
443+
444+ ** 5.12.3 Assembly-determinism axiom (NORMATIVE, cache-critical).** GIVEN the frozen
445+ render-params (§6.1), assembly of the frozen prefix is a PURE LOCAL function of the
446+ CK sequence with NO hidden cross-message state. Dropping/strip a part in
447+ CkMessage[ i] MUST NOT unrepeatably change how CkMessage[ i+1] assembles, because the
448+ only cross-message inputs (positional + merge-group membership bits) are frozen at
449+ freeze time. Without this, a part-drop near a merge boundary could re-flow assembly
450+ and bust cached bytes the golden vectors would not catch (they assert
451+ ` cached_prefix_bytes ` equality assuming render-determinism). The cache-policy core
452+ states the dual: ` render ` is a deterministic pure function of the projected request
453+ + frozen params (the byte-identity precondition).
454+
455+ ** 5.12.4 Message-presence invariant (NORMATIVE, distinct from tool-pairing).**
456+ ` synthesize_hard_stop ` (§7) preserves tool_use<->tool_result pairing. SEPARATELY:
457+ NO frozen-unit kind may remove a whole ` CkMessage ` . A fully-reclaimed message
458+ RETAINS its role-slot via a placeholder (` [dropped §N§] ` content), never becoming
459+ absent or empty. Rationale: if a whole message vanished, assembly could collapse a
460+ role-slot and break user/assistant alternation, or merge two now-adjacent same-role
461+ neighbors differently than before (a cross-message bust). Confirmed to hold on both
462+ legs by construction (plugin/owned frozen-unit kinds drop/strip/skeleton/edit_marker/
463+ synthesized_region/injection are all PART-level; MITM v2 tail-reclaim is surgical
464+ byte-span edits inside the message's own bytes) - stated normatively so a future
465+ drop kind cannot silently introduce a whole-message removal.
466+
467+ ** 5.12.5 Request-chaining is a CODEC/render precondition, NOT a CK weakening.** CK's
468+ full-conversation-snapshot assumption is CORRECT and MUST NOT be weakened: each CK
469+ request represents the full conversation. Anthropic always sends full messages
470+ (never chains). OpenAI Responses CAN chain server-side (` previous_response_id ` +
471+ ` store:true ` , server holds prior items, request sends only the delta) - but a
472+ chained request that omits history is INCOMPATIBLE with compaction by construction
473+ (you cannot compact/rewrite what is not on the wire). Therefore full-input mode
474+ (` store:false ` / no server-side chaining) is a PRECONDITION of any compaction path
475+ on a Responses-family wire (the MITM/Codex leg), enforced by that leg, NOT a CK
476+ field. If a Responses harness cannot be forced full-input, compaction on that leg
477+ is infeasible and gated independently of CK. (Anthropic/Claude-Code is the clean
478+ first MITM leg precisely because it never chains.)
479+
406480## 6. Bucket (b): downstream typed policies (the quirk seam)
407481
408482These are NOT CK fields. They are per-unit decisions the downstream quirk pass
@@ -418,7 +492,7 @@ Per-provider `serializer_healing` coverage is implemented by codec spec #2; this
418492spec defines the policy SEAMS and their reference semantics.
419493
420494** 6.1 ` decide_reasoning ` - the composable keep predicate.** A reasoning block is
421- KEPT iff:
495+ KEPT iff (both predicates are the cross-message DEFINED TERMS of §5.12.2) :
422496
423497```
424498keep_reasoning(block) =
@@ -430,7 +504,10 @@ keep_reasoning(block) =
430504- ` is_last_assistant_turn ` is the recency term (older reasoning is cleared for
431505 tokens). Its realization MAY be a recency window rather than strictly the final
432506 turn, but the decision bit is body-derived.
433- - ` is_after_first_of_merge_group ` is the merge-safety term: within a run of
507+ - ` is_after_first_of_merge_group ` is the merge-safety term: within a ` merge-group `
508+ (§5.12.2, a maximal run of consecutive assistant messages) a merging serializer
509+ repositions later thinking after text, which Anthropic rejects; only the first
510+ assistant's reasoning may
434511 consecutive assistant messages a merging serializer repositions later thinking
435512 after text, which Anthropic rejects; only the first assistant's reasoning may
436513 survive. The whole term is gated by ` serializer_profile.merges_consecutive_assistants `
@@ -551,12 +628,26 @@ per-provider wire policy, and applies on every path.
5516286 . Gate every bucket-(b) policy on a required ` serializer_profile ` request field;
552629 apply only the residual (§6 law). ` ReasoningContentRequired ` default-off.
5536307 . Enforce ` synthesize_hard_stop ` tool-pairing (§7) on every path.
631+ 7b. Hold the MESSAGE-PRESENCE invariant (§5.12.4): no frozen-unit kind removes a
632+ whole ` CkMessage ` ; a fully-reclaimed message retains its role-slot via a
633+ ` [dropped §N§] ` placeholder. Covered by a test (reclaim every part of a message
634+ -> the message and its role-slot still render).
635+ 7c. Hold the ASSEMBLY-DETERMINISM axiom (§5.12.3): given frozen render-params,
636+ assembly of the frozen prefix is a pure local function of the CK sequence.
637+ Covered by a merge-boundary defer test (drop a part adjacent to a merge-group
638+ boundary -> assembled bytes of the neighbor message unchanged across the defer
639+ pass). The cross-message terms ` merge-group ` / ` is_last_assistant_turn ` (§5.12.2)
640+ are frozen, never re-derived.
5546418 . Keep ` step_context ` / ` cache_control ` as render params, never CK content or
555- bust inputs (§8).
642+ bust inputs (§8). System-pull / user+tool-combine / assistant-merge assembly is
643+ render-side, never pre-assembled by the transform or codec (§5.12.1).
5566449 . ` Media ` is CK content on all paths; owned-path undefined render is
557- fail-loud-partial, never a silent drop (§9).
645+ fail-loud-partial, never a silent drop (§9). The head system prompt is pinned by
646+ POSITION (§5.11); mid-conversation system messages are ordinary content.
55864710 . The MC Transform and cache core contain NO provider branch; all provider
559- knowledge is in the downstream quirk pass / codec (§1).
648+ knowledge is in the downstream quirk pass / codec (§1). CK's full-conversation-
649+ snapshot assumption is NOT weakened; request-chaining (Responses ` store:true ` )
650+ is a per-leg full-input PRECONDITION, not a CK field (§5.12.5).
560651
561652## 11. Provenance
562653
0 commit comments