Skip to content

Commit cbe58cb

Browse files
docs(spec): CK#1 cross-message (assembly) semantics — §5.12 + invariants
Ufuk surfaced the per-message-vs-cross-message-assembly question; analyzed at source (@ai-sdk anthropic convert-to-anthropic-prompt.ts: groupIntoBlocks does system-pull + user/tool-combine + assistant-merge so N CkMessages -> M<=N wire messages; openai responses previous_response_id server-side chaining) and settled with SUBC. Adds §5.12: - 5.12.1 assembly is RENDER-side (restates §5.1/§6.4): CK preserves role+order+ content+provider_extras, renderer assembles, transform never pre-assembles. - 5.12.2 defines the cross-message terms the TRANSFORM reads as normative: merge-group (maximal consecutive same-role run) + is_last_assistant_turn; both frozen render-params, never re-derived. - 5.12.3 ASSEMBLY-DETERMINISM axiom (cache-critical): given frozen render-params, assembly is a pure local fn of the CK sequence — a part-drop near a merge boundary cannot re-flow a neighbor's assembled bytes (an invisible cache bust the golden vectors wouldn't catch). Cache-core states the dual byte-identity precond. - 5.12.4 MESSAGE-PRESENCE invariant (distinct from tool-pairing): no frozen-unit kind removes a whole CkMessage; a fully-reclaimed message keeps its role-slot via a placeholder (else assembly breaks user/assistant alternation). Confirmed on both legs by construction. - 5.12.5 request-chaining = a per-leg full-input PRECONDITION (Responses store:false), NOT a CK weakening; CK's full-snapshot assumption stays. Anthropic never chains = clean first MITM leg; Codex MITM gated on forceable full-input. Also tightens §5.11: pinned system keys on POSITION not ROLE (Anthropic mid-conversation-system beta => mid-conversation system messages are ordinary content, only the head prompt is pinned). Conformance §10 gains 7b (message-presence) + 7c (assembly-determinism merge-boundary test). gap3 (chaining) routed to the Oracle brief / MITM-Codex track, not a CK blocker. Co-authored-by: Alfonso [Magic Context] <288211368+alfonso-magic-context@users.noreply.github.com>
1 parent 975a22a commit cbe58cb

1 file changed

Lines changed: 101 additions & 10 deletions

File tree

docs/specs/ck-message.md

Lines changed: 101 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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
403412
the Anthropic `cch` nonce normalization). CK is unaffected; this is a render-config
404413
stabilization 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

408482
These 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
418492
spec 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
```
424498
keep_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.
551628
6. Gate every bucket-(b) policy on a required `serializer_profile` request field;
552629
apply only the residual (§6 law). `ReasoningContentRequired` default-off.
553630
7. 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.
554641
8. 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).
556644
9. `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.
558647
10. 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

Comments
 (0)