diff --git a/docs/rfc/implemented/2026-06-11-content-block-vocabulary.md b/docs/rfc/implemented/2026-06-11-content-block-vocabulary.md index a3ef1c3202..49d55badac 100644 --- a/docs/rfc/implemented/2026-06-11-content-block-vocabulary.md +++ b/docs/rfc/implemented/2026-06-11-content-block-vocabulary.md @@ -12,10 +12,10 @@ The harness needs one internal language for messages that the loop, session log, Own it: messages are arrays of typed content blocks (`text`, `reasoning`, `tool-call`, `tool-result`, `image`), with the union derived from the merge-extensible `ContentBlockMap` so plugins add block types via declaration merging. The same merge-extensible-map pattern types every "stringly" field (`MessageSource`, `FinishReason`, `TurnTrigger`, `TurnEndReason`). Streaming is a raw chunk protocol; `BlockAssembler` is the single shared assembly implementation. Adapters translate to provider wire formats — mapping cost lives in adapters, where it belongs. -In-session context injection (`context/message`, `steering/message`) renders as tagged user-role envelopes (the system-reminder pattern) rather than a new role, so adapters carry zero burden. TODO(review): verify the tagged-envelope rendering against live model behavior; the real adapters that were the original precondition now exist (see the twin-adapter RFC). +In-session context injection (`context/message`, `steering/message`) renders as tagged user-role envelopes (the system-reminder pattern) rather than a new role, so adapters carry zero burden. Live-adapter review has since validated the tagged-envelope rendering against current DeepSeek behavior; a future provider-specific mismatch should be handled in that adapter rather than by adding a new role to the canonical content vocabulary. ## Consequences - Reasoning, prefill, cache hints, and multimodal content all have a home without provider contortions. -- Every adapter pays a translation cost; the streaming protocol carries a TODO(review) marker until the first real adapter validates it. +- Every adapter pays a translation cost; the first real adapters have since validated the streaming protocol, and new adapters should continue proving their provider-specific mapping in adapter-local tests. - IDs that cross package boundaries are branded (`CallId`, `SessionId`, `AgentId`) — nominal typing at zero runtime cost. diff --git a/packages/agent/src/types.ts b/packages/agent/src/types.ts index 2b0c02d78e..56805a4a23 100644 --- a/packages/agent/src/types.ts +++ b/packages/agent/src/types.ts @@ -72,9 +72,9 @@ export interface Agent { * (inject is synchronous): a failing flush is reported via `agent/error` * (step `0`) and the logger, never thrown into the caller. * - * TODO(review): verify the tagged-envelope rendering against live model - * behavior; the real adapters that were the original precondition now exist - * (see the twin-adapter RFC). + * Live-adapter review has validated the tagged-envelope rendering against + * current DeepSeek behavior; provider-specific mismatches belong in that + * adapter, not in the canonical session vocabulary. */ inject(content: ContentBlock[], options?: SendOptions): void diff --git a/packages/session/src/index.ts b/packages/session/src/index.ts index fd7dc74dd5..4796c05f51 100644 --- a/packages/session/src/index.ts +++ b/packages/session/src/index.ts @@ -42,9 +42,9 @@ declare module 'cordis' { * synthetic user-role message (the system-reminder pattern: zero adapter * burden, models distinguish it from real user prompts by the envelope). * - * TODO(review): verify the tagged-envelope rendering against live model - * behavior; the real adapters that were the original precondition now exist - * (see the twin-adapter RFC). + * Live-adapter review has validated the tagged-envelope rendering against + * current DeepSeek behavior; provider-specific mismatches belong in that + * adapter, not in the canonical session vocabulary. */ function renderTagged(tag: string, content: ContentBlock[], source: MessageSource): ContentBlock[] { const open = `<${tag} source=${JSON.stringify(source.kind)}>`