Huginn/docs/research/connector-contract-v3-prior-art.md
Meta Vens 725f5a5a75 Research: connector contract v3 prior art and probes (reasoning state, spend, wire)
Serves eureka-body cut connector-contract-v3 and questions connector-v3-wire,
connector-v2-coexistence.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:27:59 +01:00

29 KiB

Connector contract v3: prior art and probes

Serves: cut eureka-body:cut_spec:cut-connector-contract-v3.r1 (map line B3, GameCult/ModelConnector), questions eureka-body:question:connector-v3-wire and eureka-body:question:connector-v2-coexistence, and the map line connector-spend (B3b), which is mapped from section 2 when it is next. Also answers follow-up eureka-body:follow_up:connector-reasoning-state-cheap-providers (section 1d) and v3-ciphertext-as-bytes (section 4).

Sections 1-3 are an Eyes facts pass (2026-10-28, session self-2026-10-28-eureka), fetched from current primary docs; UNVERIFIED marks what it could not confirm. Section 4 is Imagination's probes on Yggdrasil against ModelConnector ec0519d and CultLib a4eae90. Section 5 is what Imagination drew from them for the cut. Recommendations live in the questions; this page is their evidence.

Facts only, each with a pointer. Fetched 2026-10-28 from current docs. UNVERIFIED marks what I could not confirm. Provider docs have moved hosts: docs.claude.com -> platform.claude.com/docs; platform.openai.com -> developers.openai.com/api/docs. Model names in current Anthropic docs: Fable 5.1, Mythos 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5 (and older).

1. Opaque / reasoning state a caller must replay

1a. Anthropic Messages API

Pointers: T = https://platform.claude.com/docs/en/build-with-claude/thinking ; TS = .../thinking-troubleshooting ; P = .../preserved-thinking (linked from T, NOT fetched)

  • Thinking arrives as thinking content blocks (fields thinking, signature) and redacted_thinking blocks (field data, opaque, encrypted). [T "Thinking encryption", "Redacted thinking blocks"]
  • signature holds the full encrypted thinking; API uses it to verify blocks were generated by Claude; opaque, "don't interpret or parse". Streaming: arrives as signature_delta in a content_block_delta just before content_block_stop. [T "Thinking encryption"]
  • display: "omitted" (default on newer models) returns thinking blocks with empty thinking string and populated signature; such a block is complete and must be replayed; text put into the empty field is ignored not rejected. [TS "The thinking field is empty"; T "Preserving thinking blocks" Note]
  • Replay rules [T "Thinking with tool use", "Preserving thinking blocks"]:
    • Required: within a tool-use turn, pass thinking blocks back, complete and unmodified, with the tool_use block they accompanied.
    • Recommended: across turns pass everything back. Allowed: outside tool use, omit prior turns' thinking.
    • A tool-use loop is one assistant turn. Consecutive thinking blocks in the latest assistant message must match what the model generated: no reorder, edit, partial drop; includes redacted_thinking.
    • Filtering by block.type == "thinking" silently drops redacted_thinking and breaks the protocol. [T "Redacted thinking blocks" Tip]
  • Errors [TS]:
    • Modified/dropped in latest assistant message: 400 invalid_request_error, message contains "thinking or redacted_thinking blocks in the latest assistant message cannot be modified".
    • Signature present but fails verify (truncated/altered/empty): 400 "Invalid signature in thinking block" (ends there; no prefix clause).
    • Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 5.5: replayed block valid only while system, tools and preceding messages are unchanged ("prefix check"): 400 "...The block is bound to a different conversation. Remove the block, or set thinking.block_binding.prefix_mismatch_behavior to "drop_block"." Enforced by default for accounts created on/after 2026-08-31; older accounts only when the request sets that field. Option needs beta header thinking-binding-controls-2026-08-01; response then carries an input_transformations array listing dropped blocks. [T "Preserved thinking"; TS]
    • Mid-turn toggle of thinking: no error; API silently disables thinking for that request and may strip blocks. [T "Thinking with tool use"]
    • Manual mode (type:"enabled"): final assistant turn must start with a thinking block; type:"enabled" is 400 on 4.7+ models. [extended-thinking page "Turn structure in manual mode"; TS table]
  • Earlier-turn thinking stripped or kept: per model. Keep all prior turns: Opus 4.5+, Sonnet 4.6+, Haiku 5.5, Fable/Mythos 5/5.1. Keep last turn only: earlier Opus/Sonnet and Haiku through 4.5; API strips automatically on pass-back. Kept blocks bill as input. Override: clear_thinking_20251015 context-editing strategy. [T "Thinking block preservation by model", "Preserving thinking blocks"]
  • Portability:
    • Across platforms: signature values are compatible across Claude API, Bedrock, Google Cloud (Google managed); exception: Sonnet 5.5 and Haiku 5.5 blocks work only in the producing account or a linked account. [T "Thinking encryption"; P#account-bound-thinking not fetched]
    • Across models: "readable only by the model that produced it and certain other models"; API drops unreadable blocks without error and without billing. Fable 5.1 reads Opus 5 and (Claude API) Opus 5.5 blocks; Opus 5/5.5 do NOT read Fable 5.1 blocks; Fable 5.1 and Mythos 5.1 read each other only. Switching to them keeps reasoning; away from them drops it. [T "Thinking block preservation by model: Switching models", "Preserved thinking"]
  • Prompt caching [T "Thinking and prompt caching"]:
    • Thinking config and resolved effort are rendered into the prompt; changing mode, budget_tokens or effort invalidates message-level breakpoints (and may invalidate tool/system ones).
    • Thinking blocks in a tool loop are cached automatically with tool results and count as input when read from cache.
    • Last-turn-only models: after a non-tool-result user message, earlier thinking is stripped from context/cache.
    • 1-hour cache suggested for long thinking sessions.
  • Billing: thinking tokens billed as output; usage.output_tokens_details.thinking_tokens reports them (final message_delta when streaming). [extended-thinking page "Budget rules and tuning"]

Stop reasons (https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons, first 100k chars read):

  • end_turn, max_tokens, stop_sequence, tool_use, pause_turn (server-tool loop hit iteration limit, default 10; resend assistant content to continue), refusal (HTTP 200, stop_details names policy category, null for other reasons), model_context_window_exceeded (treat as truncated).
  • Streaming: stop_reason is null in message_start, appears in message_delta. max_tokens can end in an incomplete tool_use block.

Usage object (https://platform.claude.com/docs/en/build-with-claude/prompt-caching "Usage fields"):

  • input_tokens = tokens after last cache breakpoint (uncached); cache_creation_input_tokens = 5m + 1h writes; cache_read_input_tokens; cache_creation{ephemeral_5m_input_tokens, ephemeral_1h_input_tokens}; output_tokens. total_input = cache_read + cache_creation + input_tokens.
  • output_tokens_details.thinking_tokens (above). server_tool_use and service_tier fields: only seen via fetch-tool general-knowledge answer, API reference page was truncated before the schema: UNVERIFIED against docs. Token counts depend on tokenizer: Claude 4.7+ models (incl. Fable/Mythos 5/5.1) count ~30% more tokens for the same text. [token-counting page "Supported models", "Token counts on Claude Fable and Claude Mythos models"]
  • Min cacheable prompt: 512 (Fable/Mythos/Opus 5.5/Opus 5/Sonnet 5.5/Haiku 5.5), 1024 (Opus 4.8, Sonnet 5/4.6/4.5), 2048 (Opus 4.7, Mythos Preview), 4096 (Opus 4.6/4.5, Haiku 4.5). Lookback 20 block positions. [prompt-caching page]

1b. OpenAI Responses API

Pointer: https://developers.openai.com/api/docs/guides/reasoning (via fetch summary)

  • Reasoning items appear in output; opaque, no reasoning text returned (optional summary). Example item id prefix rs_; page gives no other id rules.
  • Stateless mode (store:false or ZDR org): reasoning items include encrypted_content by default; include: ["reasoning.encrypted_content"] still accepted as legacy/no-op.
  • Replay: stored -> previous_response_id. Manual -> pass prior output items. Function calling: pass back reasoning items returned with the last function call plus outputs; with several calls in a row, pass back all reasoning, call and output items since last user message. all_turns with store:false: keep every output item (encrypted reasoning, assistant phase) and replay full history. reasoning.context in the response reports effective mode (current_turn/all_turns).
  • Other models: reusable only within the same model family; switching families, API omits incompatible reasoning even with all_turns (GPT-5.6 Sol/Terra/Luna interchange, not GPT-5.5).
  • Usage: usage.output_tokens_details.reasoning_tokens, usage.input_tokens_details.cached_tokens; reasoning billed as output.

1c. Google Gemini

Official pages fetched were thin or truncated:

  • https://ai.google.dev/gemini-api/docs/thought-signatures redirects to thinking guide; page that did render describes the Interactions API: signatures are "encrypted representations of the model's internal reasoning"; limited to thought steps and built-in tool steps; stateless mode "MUST always resend all thought blocks exactly as they were received"; stateful (store:true, previous_interaction_id) server handles it; on generateContent signatures "can be attached to any part, such as living inside functionCall parts or the final part of a response"; streaming thought_signature delta arrives in last delta before step.stop; a thought block may carry only a signature.
  • https://ai.google.dev/gemini-api/docs/gemini-3 : stateless callers must include thought blocks with signatures (summary only).
  • generateContent function-calling rules (first functionCall part in parallel calls, each step in sequential, 400 on missing, skip_thought_signature_validator dummy): UNVERIFIED in official docs (Vertex page docs.cloud.google.com/vertex-ai/generative-ai/docs/thought-signatures body was truncated in fetch).
  • Observed third-party evidence (not official): error text "Unable to submit request because function call X in the N. content block is missing a thought_signature" and reports that only the first 1-2 parallel calls get signatures, https://discuss.ai.google.dev/t/gemini-3-flash-preview-inconsistent-thought-signature-generation-in-parallel-function-calls-causes-400-errors-and-potential-silent-data-loss/118936 ; thread quotes Google doc line "If you use the official SDKs and standard chat history, thought signatures are handled automatically". Text turns without a signature reportedly don't 400 (apiyi FAQ, https://docs.apiyi.com/en/faq/gemini-thought-signature-missing, third party). "Signature present but fails validation" is a distinct 400 reported when route/model/key changes mid-conversation (same FAQ, third party).

1d. OpenAI-compatible providers (cheap-providers follow-up)

Provider Field Form Must replay in tool loop Across turns Pointer
DeepSeek (deepseek-flash, deepseek-v4-pro on page) reasoning_content (sibling of content) plaintext Yes. With tools: omitting it -> documented 400. Each sub-request in a turn's loop must carry that turn's reasoning even if no tool call With tools: pass back all previous turns' reasoning (concatenated into context). Without tools: ignored if sent, not carried https://api-docs.deepseek.com/guides/thinking_mode
Moonshot Kimi reasoning_content at level of content plaintext Within a task keep all and send each request; append complete assistant message unchanged. Page names no error for omission Per model: kimi-k3.1-pro/k3 require it multi-turn; k2.7-code always keeps; k2.6 drops by default unless thinking.keep:"all" https://platform.kimi.ai/docs/guide/use-kimi-k2-thinking-model (page covers k2.6/k2.7/k3, not "K2 thinking" naming)
Zhipu GLM (z.ai) reasoning_content; request thinking:{type, clear_thinking} plaintext; page silent on signing Return with the assistant message that made tool calls; interleaved thinking on by default "Preserved thinking": clear_thinking:false keeps earlier turns (default on for Coding Plan endpoint, off standard); when on, must return complete unmodified; reorder/edit "may degrade" and lowers cache hits. GLM-5.3 cannot disable thinking https://docs.z.ai/guides/capabilities/thinking-mode
Qwen (Alibaba Model Studio) reasoning_content; enable_thinking, thinking_budget, preserve_thinking plaintext Page doesn't say; links to function-calling docs preserve_thinking:true (qwen3.8-max, 3.7-max/plus, 3.6-plus, some Kimi) makes model read historical reasoning, billed as input; omission not an error https://www.alibabacloud.com/help/en/model-studio/deep-thinking
MiniMax (page = M3.1-Flash; M2 not on that page) reasoning_content (OpenAI) / thinking blocks (Anthropic SDK) plaintext Append entire assistant message incl. reasoning_content; "essential" for best performance; no benchmark or error stated. Thinking is optional per turn n/a https://platform.minimax.io/docs/guides/text-m2-function-call . M2-specific <think> tags / reasoning_details / reasoning_split: UNVERIFIED (not on fetched page)
  • None of these pages describe signatures or encryption; the DeepSeek 400 is the only documented hard failure.

1e. Multi-provider libraries

  • LiteLLM (https://docs.litellm.ai/docs/reasoning_content): reasoning_content string on message "returned across all providers"; thinking_blocks list (Anthropic only; type, thinking, signature) must be included in the assistant message with tool_calls when returning tool results; provider_specific_fields unspecified (Bedrock example reasoningContentBlocks[].reasoningText{signature,text}). Known failure: Anthropic 400 when thinking_blocks missing ("assistant message doesn't start with a thinking block"). OpenAI-compatible clients (LibreChat, Open WebUI, Vercel AI SDK) ignore thinking_blocks and lose them. Anthropic->DeepSeek switch needs drop_params=True. litellm.modify_params=True disables thinking for a turn when blocks are missing.
  • OpenRouter (https://openrouter.ai/docs/guides/best-practices/reasoning-tokens): reasoning_details array at choices[].message / choices[].delta; common fields id (string|null), format, index; types reasoning.summary (summary), reasoning.text (text, optional signature), reasoning.encrypted (data; may stream as [REDACTED]). format values: unknown, openai-responses-v1, azure-openai-responses-v1, bedrock-openai-responses-v1, bedrock-xai-responses-v1, xai-responses-v1, meta-responses-v1, anthropic-claude-v1 (default), google-gemini-v1. Must preserve full consecutive sequence, no rearrange/modify. reasoning string field for plain-string models; reasoning_content alias. Doc claims switching OpenAI/Anthropic works without code change.
  • Vercel AI SDK: sendReasoning (default true) controls whether reasoning is included in requests; Anthropic provider surfaces providerMetadata.anthropic.inputTransformations; blockBinding.prefixMismatchBehavior option; providerOptions escape hatch; between-tools thinking blocks "sent back unchanged on the next step" (https://ai-sdk.dev/providers/ai-sdk-providers/anthropic, https://ai-sdk.dev/docs/ai-sdk-core/reasoning). Thinking signature/redactedData parts: UNVERIFIED (not on fetched pages).
  • Cross-provider replay failure evidence: LiteLLM page above (Anthropic<->DeepSeek, UI clients dropping fields); Gemini third-party reports of dropped thoughtSignature by OpenAI-compatible clients (https://discuss.ai.google.dev/t/openai-api-compatibility-broken-due-to-thought-signature-on-gemini-3-pro-preview/109823 , title only, body not fetched).

2. Spend accounting and budget enforcement

2a. Usage and money

  • Anthropic usage: tokens only (fields in 1a); no money field seen in docs fetched. Cost must be computed from pricing page. UNVERIFIED that the full Message schema has no cost field (schema page truncated).
  • OpenRouter: usage.cost = total charged in credits; usage.cost_details.upstream_inference_cost (BYOK only via generation endpoint); usage.prompt_tokens_details.cached_tokens and .cache_write_tokens; usage.completion_tokens_details.reasoning_tokens; usage:{include:true} deprecated/no effect, full usage always present; streaming usage in last SSE message. https://openrouter.ai/docs/guides/guides/usage-accounting
  • OpenAI: tokens (1b).

2b. LiteLLM

  • Per-request cost in header x-litellm-response-cost; spend log field is spend (USD). The cost-tracking page does not name a response_cost field or model_prices_and_context_window.json; says it uses the "model cost map" and can sync from GitHub. https://docs.litellm.ai/docs/proxy/cost_tracking (file name model_prices_and_context_window.json: UNVERIFIED on page). Zero-priced usage on non-zero-rate model logs spend 0 with WARNING and counter litellm_zero_cost_requests_total.
  • Budgets: max_budget on keys (/key/generate; budget_limits multiple windows), users, teams, team members, customers; global litellm_settings.max_budget; budget_duration ("30s/30m/30h/30d"; unset = never resets); reset check default every 10 min. https://docs.litellm.ai/docs/proxy/users
  • Timing: with budget reservation (default on) LiteLLM estimates max cost from the request and reserves before calling; rejects pre-provider if reservation exceeds budget; after response, reservation is replaced with actual cost. Batch: reservation released, final cost recorded at completion. Zero-cost models allowed over budget.
  • Errors vary by scope: ExceededTokenBudget (key), budget_exceeded code 400 or 429 (per-model), ExceededBudget with 401 (customer), 429 (agent sessions); fail_closed_budget_enforcement: true -> 503 when budget cannot be verified.
  • No DB: "Budgets require a database". Global max_budget fails open (warning, requests continue); key/team/user budgets unavailable ("No connected db.").

2c. OpenRouter limits

https://openrouter.ai/docs/api-reference/limits

  • Per-key limit, limit_reset, limit_remaining. HTTP 402 with error.metadata.limit_source: openrouter_key_limit, openrouter_credits, openrouter_in_flight_budget (wait Retry-After).
  • In-flight budget (certain prepaid accounts): request whose estimated cost doesn't fit is rejected pre-provider even with positive balance; estimate = input tokens + completion tokens allowed by max_tokens (fixed per-request cap if unset), excludes per-request fees/plugins/images; charged on finish.
  • Negative balance causes errors incl. free models.

2d. Pre-call reservation / bound patterns

  • LiteLLM reservation (2b) and OpenRouter in-flight estimate (2c) both = estimated input + max_tokens-bounded output, hold, settle on actual.
  • Anthropic token counting: POST /v1/messages/count_tokens, same inputs as Messages (system, tools, images, PDFs, thinking blocks); returns {input_tokens}; free; separate rate limit (5,000/10,000/20,000 RPM by tier Start/Build/Scale); result is an "estimate" and may include system-added tokens not billed; no caching logic; rejects server tools, MCP connector, url/file image/document sources; uses tokenizer of passed model. https://platform.claude.com/docs/en/build-with-claude/token-counting
  • Portkey / Helicone / cloud billing ledgers: NOT searched (budget). UNVERIFIED.

2e. Anthropic pricing (https://platform.claude.com/docs/en/about-claude/pricing, $/MTok: input / 5m write / 1h write / cache read / output)

  • Fable 5.1 and Mythos 5.1: 10 / 12.50 / 20 / 0.25 / 50 (read = 0.025x)
  • Opus 5.5: 4 / 5 / 8 / 0.20 / 20 (read = 0.05x)
  • Opus 5, 4.8, 4.7, 4.6, 4.5: 5 / 6.25 / 10 / 0.50 / 25
  • Sonnet 5.5: 2 / 2.50 / 4 / 0.10 / 10 (read = 0.05x)
  • Sonnet 5: 2 / 2.50 / 4 / 0.20 / 10 ; Sonnet 4.6, 4.5: 3 / 3.75 / 6 / 0.30 / 15
  • Haiku 5.5: prompts <=100k tokens 0.10 / 0.125 / 0.20 / 0.01 / 0.50 ; prompts >100k 0.50 / 0.625 / 1 / 0.05 / 2.50 (long-context tier keyed on prompt size)
  • Haiku 4.5: 1 / 1.25 / 2 / 0.10 / 5
  • Multipliers: 5m write 1.25x, 1h write 2x, read 0.1x base unless noted above. inference_geo:"us" = 1.1x on all categories (4.6+; 400 on earlier models). Batch API discount: table exists on page (lines show batch input/output at about half), exact multiplier not extracted. Multipliers stack with batch and residency.
  • Opus 5.5 / Sonnet 5.5 / Haiku 5.5 / Fable 5.1 listed as current at fetch time.

3. Wire choice for a typed local RPC

3a. MCP (spec revision 2026-07-28 is "latest" at fetch)

  • JSON-RPC 2.0, UTF-8. Standard transports: stdio (newline-delimited) and Streamable HTTP (POST per message; JSON or request-scoped SSE reply). https://modelcontextprotocol.io/specification/latest/basic/transports
  • Custom transports allowed; those over reliable byte streams (Unix sockets, TCP) "SHOULD reuse the stdio framing" (same page).
  • Versioning: latest revision has NO initialize handshake; every request carries protocol version in _meta.io.modelcontextprotocol/* (HTTP also MCP-Protocol-Version header); unsupported -> error -32022 with data{supported[], requested}; client retries; server/discover MUST be implemented. Legacy (<= 2025-11-25) used initialize negotiation. Dual-era servers MAY serve both on one endpoint, selecting by how the client opens (modern _meta vs initialize). https://modelcontextprotocol.io/specification/latest/basic/lifecycle (rendered as "Versioning and Compatibility")
  • Binary: base64 strings paired with mimeType (ImageContent.data, AudioContent.data, BlobResourceContents.blob).
  • Error codes: -32700, -32600, -32601, -32602, -32603 (JSON-RPC); -32020 HEADER_MISMATCH, -32021 MISSING_REQUIRED_CLIENT_CAPABILITY, -32022 UNSUPPORTED_PROTOCOL_VERSION. https://modelcontextprotocol.io/specification/2026-07-28/schema

3b. gRPC / protobuf (https://protobuf.dev/programming-guides/proto3/)

  • Unknown fields preserved on parse and re-serialized (lost when going through JSON or field-by-field copy). Never reuse field numbers; reserve removed numbers/names. Adding fields, removing (no reuse), adding enum values are wire-safe; changing a field number or moving a field into an existing oneof is not. Unknown enum values preserved (open enums store int; closed enums, e.g. Java, special-case).
  • bytes: arbitrary byte sequence (length limit garbled in fetch).
  • package prevents name clashes; page does NOT frame packages as a versioning mechanism ("versioning by package names" claim: UNVERIFIED on this page).

3c. MessagePack / rmp-serde

  • msgpack spec: bin 8/16/32 = 0xc4/0xc5/0xc6 with 8/16/32-bit BE length; str family separate (0xd9-0xdb, fixstr 0xa0-0xbf); ext 0xc7-0xc9 and fixext 0xd4-0xd8, app ext types 0-127, -1 = Timestamp; array 0x90-0x9f/0xdc/0xdd; map 0x80-0x8f/0xde/0xdf. Spec recommends deserializers accept bin and str8 in minor releases, serializers distinguish in major. https://github.com/msgpack/msgpack/blob/master/spec.md
  • rmp-serde: serde derive treats Vec<u8> as an array of values (about 50% overhead per docs); wrap with serde_bytes or use Serializer::with_bytes to emit bin. Re-exports to_vec and to_vec_named (the page does not explain the struct encoding difference; array-vs-map: UNVERIFIED here). Unknown-field handling not covered. https://docs.rs/rmp-serde/latest/rmp_serde/
  • Cap'n Proto: not searched. UNVERIFIED.

3d. Keeping two wire versions alive

  • MCP dual-era server (3a): one endpoint serves both generations; discriminator is how the first message opens (per-request _meta vs initialize); client probes server/discover (stdio) or attempts modern request and inspects 400 body (HTTP) and caches the result per server process/origin; a modern-only server SHOULD name its supported versions in the error it returns to initialize; compatibility matrix in the versioning page.
  • Protobuf field-number discipline (3b).
  • Version byte / frame tag, side-by-side schema ids, generic dual-stack guidance: no source fetched. UNVERIFIED.

Not done (budget)

Portkey, Helicone, Cap'n Proto, OpenAI pricing, Gemini usage fields (usageMetadata.thoughtsTokenCount, cachedContentTokenCount), Anthropic Messages API reference schema (page too large), P preserved-thinking page, Bedrock/Vertex-specific usage differences.

4. Probes (Imagination, Yggdrasil ygg-verify rust image, 2026-10-28)

Scratch clone of ModelConnector at ec0519d plus a probe-only commit (never pushed to the forge). Baseline: cargo test --workspace and --features model-connector/daemon green, 77 tests passed in all.

  • P2 Decoding the real v2 frame tests/fixtures/wire_v2/envelope_invocation.mp (the bytes CodexConnector fd7cec4 produced): 735-byte frame for 429 bytes of ciphertext (209 of them

    = 128), ratio 1.713. First byte 0x96: a positional six-element array. It decrypts under parity-distinct-test-key to caller parity-caller, request request-1, model gpt-5.4, 4 input items, 1 tool, schema gamecult.codex.transport_invocation.v2.

  • P2 The same envelope with the ciphertext as MessagePack bin (serde_bytes): 526 bytes, ratio 1.226 (the rest is ids and nonce). A 1 MiB all-high ciphertext: v2 frame 2,097,251 bytes, bin frame 1,048,675; the kit's frame_bytes_for_payload(1 MiB) = 2,097,750.
  • P2 The v2 TransportEnvelope type decodes a bin-encoded ciphertext and compares equal (rmp-serde reads bin into Vec<u8>). Encoding is the only change bin needs.
  • P3 ProviderRequest positional 267 bytes (0xdc array16), named (to_vec_named) 590 bytes (0xde map16). The v2 type decodes the named encoding and compares equal.
  • P4a A struct with one added field: positional encoding fails to decode in the old type ("array had incorrect length, expected 3"); named encoding decodes with the field ignored. So positional MessagePack cannot add a field without breaking every reader, and named encoding drops unknown fields silently unless the type denies them.
  • P4b A v2 InputItem decoding a ReasoningState variant fails: "unknown variant". A v2 peer cannot read a v3 transcript at all; there is no partial compatibility to preserve.
  • P5 A v3 envelope schema id under v2 decrypt_invocation: Err(Envelope), before any decryption. A v2 daemon given a v3 envelope drops the connection; it never answers.
  • PC CultNet carriage. CultNetMessage::OperationRequest (cultnet-rs at CultLib a4eae90) carrying the sealed envelope (named, bin ciphertext, ids at 256 bytes) as its messagepack-base64 payload, encoded with encode_cultnet_message_to_vec(.., CultNetSchemaV0) and decoded back, round-trips equal. Frame over ciphertext: 4.45 at 429 bytes (fixed overhead), 1.354 at 64 KiB, 1.335 at 1 MiB, 1.334 at 8 MiB; encode 6.5 ms and decode 1.0 ms at 1 MiB (release). The worst case is deterministic (base64 is exactly 4*ceil(n/3)), where v2's integer array is up to 2.0x and depends on the bytes.

Source reads behind the wire question (not probes):

  • CultNet's TcpFramed transport protocol (contracts.rs:112-119) uses the same four-byte big-endian length prefix as the kit (framing.rs:6-15; kit envelope.rs:152-184).
  • validate_message requires payload_encoding == "messagepack-base64" on operation messages (contracts.rs:780): CultNet has no binary operation payload today.
  • CultNet's security options hold one connection key per server (security.rs:51-75); the kit binds one key and epoch per caller and seals ids into the AES-GCM AAD (service.rs:19-65, envelope.rs:245-253).
  • Huginn already speaks this shape: huginn-mind/src/envelope.rs carries named MessagePack as base64 in cultnet.operation_request.v0, checks service_id, payload_schema and the operation against the decoded payload, and publishes its JSON Schema with schemars.

5. What the cut takes from this

  • Reasoning item granularity follows the providers: Anthropic requires the latest assistant turn's thinking blocks complete, unmodified and in place beside their tool_use; OpenRouter carries reasoning_details per block with an index. So one ReasoningState item per provider block, in transcript order, holding the connector's encoding of the whole block (Anthropic: thinking text and signature, or redacted_thinking data, since a summarized block must come back with both).
  • Foreign state: every provider that tags reasoning by model drops what it cannot read across completed turns (Anthropic drops unreadable blocks silently; OpenAI omits other families). Only the in-flight tool exchange fails hard (Anthropic 400, DeepSeek 400). So the kit refuses foreign state only after the last user text, and drops it before the backend sees it elsewhere, counting the drop on the receipt (as Anthropic's input_transformations does).
  • Anthropic's prefix check binds a block to the unchanged system, tools and earlier messages: the caller's verbatim replay (ruling connector-opaque-state-item) is necessary, and the Claude connector's lowering must be deterministic (B4).
  • The cheap providers (1d) all keep plaintext reasoning_content and want it replayed inside a tool loop; the item stays opaque to callers either way.
  • Spend (B3b): LiteLLM and OpenRouter both bound a request before the call as estimated input plus max_tokens times the output price, hold it, and settle to actual; LiteLLM without a database fails open, which ruling connector-owner refuses. Anthropic returns tokens only, so cost is the connector's arithmetic over a price table it owns. v3 therefore makes max_output_tokens required, so the bound exists.