0057: LlmCompletionEvent Field-Set Extension¶
- Status: Accepted
- Author: Chris Colinsky
- Created: 2026-06-07
- Accepted: 2026-06-07
- Targets: spec/graph-engine/spec.md (§6 — extends the
LlmCompletionEventfield table from proposal 0049 with eight additive request-side / response-side fields, and renamesrequest_id→response_idso the field name matches the response-side data it carries); spec/observability/spec.md (§5.5.7 — updates the typed-event framing paragraph to acknowledge that the typed event now mirrors the request-parameter (§5.5.2), payload (§5.5.1), and prompt-identity (§5.5.4 / prompt-management §12) attribute surfaces in addition to the §5.5.3 response-side attributes it covered at v0.41.0); plus new conformance fixtures underspec/observability/conformance/covering each of the new fields' population semantics, plus an update to fixture 050 for theresponse_idrename (the only existing 0049 fixture asserting on this field). - Related: 0049 (typed LLM completion event — this proposal extends the field set the original Accepted via the alternative-5 follow-on hook), 0024 (LLM span payload + GenAI semconv — established the §5.5 attribute surface this proposal mirrors in typed-event form on the request side), 0033 (prompt-management active-prompt context binding — the source for the
active_prompt/active_prompt_groupfields), 0047 (implicit prefix cache wire stability —usage.cached_tokensandusage.cache_creation_tokensflow through the existingusagefield on the typed event unchanged; this proposal does not touch theusagefield shape) - Supersedes:
Summary¶
Extends the LlmCompletionEvent typed variant from proposal 0049 with the
request-side and prompt-identity fields observers consuming the §5.5 LLM
provider span attribute surface need from the typed event.
Eight new fields (input_messages, output_content, request_params,
request_extras, active_prompt, active_prompt_group, call_id,
response_model) land additively; one existing field (request_id) is
renamed to response_id so the field name matches the response-side data
it carries.
The extension completes the typed event's coverage of the observability
§5.5 LLM provider span attribute surface — at v0.41.0 the typed event
mirrored only the §5.5.3 response-side attributes plus identity / scoping;
this proposal adds the §5.5.1 payload attributes, the §5.5.2 GenAI request
parameters, the prompt-identity attribute family (per prompt-management
§12 / observability §8.4.4), and the missing gen_ai.response.model
attribute. With the extension landed, observers consuming the typed event
have the same data surface as observers reading the OTel span — the
remaining sentinel-namespaced NodeEvent emission becomes redundant for
LLM-call observability, and impl-level sentinel deprecation can proceed
on schedule without observer-side regressions.
Motivation¶
Proposal 0049 carved LLM completions into the first spec-normatively-typed event variant on the observer event union. Its alternative 5 deliberately scoped the v1 field set to outcome-side data (provider response, usage, finish_reason) plus identity / scoping, with the framing:
Request-side fields can be added in a follow-on if observer demand surfaces.
Observer demand has surfaced. OTel and Langfuse observers (observability §5.5 and §8 respectively) source attributes from the sentinel-namespaced NodeEvent payload across the §5.5 LLM provider span attribute surface and the prompt-identity attribute family (per prompt-management §12 / observability §8.4.4) that have no equivalent on the typed event today:
- Three §5.5.1 payload attributes (
input.messages,output.content,request.extras) - Seven §5.5.2 GenAI request parameters (
temperature,max_tokens,top_p,seed,frequency_penalty,presence_penalty,stop_sequences) - Five prompt-identity attributes (
prompt.name,prompt.version,prompt.label,prompt.template_hash,prompt.rendered_hash) + the prompt-group attribute (prompt.group_name) - The response-side
gen_ai.response.modelattribute (distinct fromgen_ai.request.model; surfaces when the provider returns a more specific identifier than requested)
Without these fields on the typed event, observers migrating off the sentinel-namespace filtering pattern at the v0.15.0 deprecation cutoff lose the span attribute surface they emit today. The §5.5.7 transition window's purpose — letting backends move to type-discrimination filtering without behavior regressions — is defeated if the typed event doesn't carry the data the backends render.
Why the v1 fallback paths don't solve this. Proposal 0049 alternative 5 named two fallback paths for observers needing request-side data:
Response.rawis the parsed provider response body per llm-provider §6 (verbatim wire response for original-bytes inspection: tool-call argument repair, vendor error codes). It does not carry request shape. The prompt-identity surface is sourced from prompt-context binding at LLM-call time, not from the wire response at all.- "The existing
NodeEvent's payload" is the sentinel pattern itself — the very pattern observers are migrating off of. Continuing to read it through the dual-emit transition window works only until the sentinel emission is removed.
The alternative-5 framing was correct to anticipate the follow-on; this proposal lands it.
Why the request_id rename. Proposal 0049's field table named one
field request_id while sourcing it from gen_ai.response.id (the
provider-returned response identifier). The naming mismatch is a
forever-confusing reader hazard. This proposal renames the field to
response_id so the typed event's response-side surface coheres
internally: response_id, response_model, usage, finish_reason
all named after the data they carry. The rename rides on this proposal
because the field table is already being edited for the additive
fields; deferring it would leave the inconsistency in the spec text
across additional proposal cycles, each of which would add consumers
that would then have to migrate.
Proposed change¶
graph-engine §6 — extend the LlmCompletionEvent field table¶
§6 today carries the LlmCompletionEvent field table introduced by
proposal 0049 (the LLM completion event paragraph and its embedded
table). Replace that table with the following extended version. The
changes are:
- One renamed field:
request_id→response_id. Type, source, and nullability unchanged; only the field name changes. The description updates to remove the now-anomalous "request_id" naming reference. - Eight new fields.
response_modelis interleaved betweenmodelandresponse_idso the response-side cluster reads coherently (model/response_model/response_id); the remaining seven —input_messages,output_content,request_params,request_extras,active_prompt,active_prompt_group,call_id— are appended after the v0.41.0 field set.
Below: the full replacement table. Rows from the v0.41.0 field table are
preserved verbatim except for the request_id → response_id rename;
the description column on the renamed row drops the old field name
mention. Eight new rows follow.
LLM completion event. A typed event variant on the observer event union signaling completion of an LLM provider call. Carries the call's identity / scoping / outcome data as typed fields:
Field Type Description invocation_idstring The outer invocation's identifier, per §5.1 of observability. correlation_idstring | null Cross-backend correlation ID, per §3.1 of observability. node_namestring The user-defined node that issued the call. namespacetuple of strings The calling node's namespace (NOT the sentinel namespace). attempt_indexint The retry-attempt index (0 on the first attempt). fan_out_indexint | null The fan-out instance index when the calling node ran inside a fan-out instance, per graph-engine §6 / pipeline-utilities §9. Null otherwise. Part of the §6 event-source identity tuple; required for disambiguating sibling fan-out instances. branch_namestring | null The parallel-branches branch name when the calling node ran inside a parallel-branches branch, per graph-engine §6 / pipeline-utilities §11 (with the resolved branch_namesper proposal 0044 governing the value space). Null otherwise. Part of the §6 event-source identity tuple; required for disambiguating sibling parallel branches.providerstring The LLM provider identifier (matches gen_ai.systemper observability §5.5.3).modelstring The model identifier the request was made against (matches gen_ai.request.model/openarmature.llm.modelper observability §5.5 / §5.5.3). The provider-returned model identifier — which MAY be more specific — is carried separately onresponse_modelbelow.response_modelstring | null The model identifier the provider returned in the response (matches gen_ai.response.modelper observability §5.5.3). Distinct frommodelbecause providers MAY return a more specific identifier than the one requested (e.g., requestedgpt-4o, response carriesgpt-4o-2024-08-06). Null when the provider does not return a response model.response_idstring | null The provider-returned response identifier, when present (matches gen_ai.response.idper observability §5.5.3).usagerecord | null Token usage record per llm-provider §6 Response.usageshape (including the prefix-cache fieldscached_tokensandcache_creation_tokensfrom proposal 0047 when populated). May be null when the provider does not report usage.latency_msfloat | null Wall-clock latency of the LLM call measured at the adapter boundary, in milliseconds. May be null when latency is not measured. Implementations MAY use a provider-reported latency value when the provider surfaces one, documenting which source is in use. finish_reasonstring | null The LLM call's finish reason per llm-provider §6 Response.finish_reason. May be null when the call did not complete normally.caller_invocation_metadatamapping | null OPTIONAL field — a snapshot of the caller-supplied invocation metadata (per §3.4 of observability) at the time of the LLM call, populated only when the observer is configured to include it (per-language opt-in mechanism). Default absent / null; off by default to avoid bloating every event with potentially-large metadata. Consumers wanting a fresh metadata view rather than a snapshot use the get_invocation_metadata()read API per proposal 0048.input_messageslist of message records The §3 message list the call was made with, in the typed-event-native form of the spec's message shape (NOT the JSON-encoded string form §5.5.1 emits on the OTel span). Each record carries {role, content, tool_calls?, tool_call_id?}per llm-provider §3, including content-block sequences for multimodal messages. Inline image bytes follow the §5.5.5 redaction rule (replaced with the redacted placeholder per §5.5.5) before population. Populated by the implementation on every typed event; the empty-history case is represented as an empty list, not null. Observer-side privacy gating applies at the rendering boundary per Privacy and observer-side gating below.output_contentstring | null The assistant's response content verbatim per llm-provider §6 Response.message.content. Null when the response was a tool-call-only assistant message with empty content (the structured-response and tool-call paths are mutually exclusive at the response level, matching the §5.5.1 framing foropenarmature.llm.output.content). Same privacy-gating posture asinput_messages.request_paramsmapping The §5.5.2 GenAI request-parameter family — temperature,max_tokens,top_p,seed,frequency_penalty,presence_penalty,stop_sequences. Keys are the GenAI semconv attribute names without thegen_ai.request.prefix (e.g.,temperature, notgen_ai.request.temperature). Values are the per-parameter types §5.5.2 specifies (double fortemperature/top_p/frequency_penalty/presence_penalty, int formax_tokens/seed, list-of-string forstop_sequences). Absence is meaningful: the mapping carries only parameters the caller actually supplied — a parameter not in the mapping means "not supplied on this call," distinct from "supplied with a zero value." Mapping-shape rather than flat fields to keep the typed event compact when most parameters are unset. Empty mapping when no §5.5.2 parameters were supplied.request_extrasmapping The RuntimeConfigextras pass-through bag per llm-provider §6 — vendor-specific sampling parameters callers supplied as un-declared fields (vLLMguided_decoding, OpenAIservice_tier, etc.). Values are opaque to the spec; the bag carries whatever the caller supplied, in the typed-event-native mapping form rather than the JSON-encoded string form §5.5.1 emits on the OTel span. Same privacy-gating posture asinput_messages. Empty mapping when no extras were supplied.active_promptrecord | null A snapshot of the active Promptidentity at LLM-call time, sourced from the implementation's prompt-context binding mechanism (per prompt-management §12 / observability §8.4.4 — the mechanism that drives theopenarmature.prompt.*span attributes; specific mechanism per-language idiomatic). Fields:{name, version, label, template_hash, rendered_hash}matching the §8.4.4 prompt-identity attribute family one-for-one. Null when the LLM call ran outside any prompt-context binding (noopenarmature.prompt.*attributes would have been emitted on the span).active_prompt_grouprecord | null A snapshot of the active PromptGroupidentity at LLM-call time, sourced from the same prompt-context binding mechanism. Fields:{group_name}matching the §8.4.4 / prompt-management §12 prompt-group attribute family. Null when no group was active.call_idstring A per-call disambiguator minted by the implementation. Always present (never null); implementations MUST mint a fresh identifier per provider.complete()call. The value MUST be stable for the call's lifetime and unique within the implementation's run. Wire shape unconstrained — UUID, ULID, monotonic counter, any stable string format works. Use cases: cross-language trace correlation, observer-side per-call buffering, log-line correlation with the impl-side trace exporter. Distinct fromresponse_id(which is the provider-returned identifier and MAY be absent or duplicated across providers);call_idis the implementation's own correlation token.The event MUST be dispatched on the observer delivery queue at the point of LLM call completion (after the adapter receives a successful response and before the call returns to the caller). Delivery semantics follow §6 — strictly serial across the invocation, async-delivered concurrently with graph execution, not blocking the engine's execution loop.
The event is dispatched ONLY for LLM call completions that produce a structured response per llm-provider §5. Failure cases (provider exceptions, malformed responses) do NOT emit this event variant; a future
LlmCallFailedEventtyped variant MAY be added if demand emerges (see Out of scope). The existing llm-provider §7 error categories —provider_invalid_response(malformed wire shape),provider_unavailable(transient unreachability),provider_authentication, etc. — cover failure surfaces today through the exception path, not the observer event surface.Phase subscription filter. Like the metadata-augmentation event mechanism from proposal 0040,
LlmCompletionEventis a typed event variant without aphasediscriminator and is NOT subject to the §6phasessubscription filter. Observers with aphases={"started"}orphases={"completed"}subscription still receiveLlmCompletionEvent; the phases filter applies only to phase-bearingNodeEventvariants. Observers that want to selectively consume the typed event filter via type discrimination (isinstanceor per-language equivalent) rather than via phase subscription.Privacy and observer-side gating. The
input_messages,output_content, andrequest_extrasfields carry potentially sensitive payload data. Implementations MUST populate these fields on the typed event by default; observer-side privacy gating applies at the rendering boundary, matching the observability §5.5.4disable_llm_payload/disable_state_payloadopt-out flag semantics for the equivalent §5.5.1 span attributes. The OTel and Langfuse observers honor their existingdisable_llm_payloadflag on the typed-event rendering path identically to the §5.5.1 span attribute path.Custom queryable observers (per observability §9) consuming the typed event are responsible for their own redaction posture — the §5.5.4
disable_llm_payloadflag gates OTel + Langfuse rendering; the typed-event field surface is uniform across observer types. Accumulator authors with payload-redaction requirements MUST gate at their own rendering / persistence boundary. The structured event surface is uniform; gating belongs at the rendering boundary, with the consumer's awareness.Inline image bytes in
input_messagesMUST be redacted per the §5.5.5 inline-image redaction rule before the field is populated, identically to how §5.5.1'sopenarmature.llm.input.messagesattribute treats inline images. The hard-rule prohibition on emitting inline image bytes (§5.5.5) applies to the typed event field identically.
observability §5.5.7 — update the typed-event framing paragraph¶
§5.5.7 today says:
The typed event carries the same identity / scoping / outcome data the §5.5 span attribute surface exposes —
gen_ai.system,gen_ai.request.model,gen_ai.response.id,gen_ai.usage.*,gen_ai.response.finish_reasons, plus the OA-namespaced attributes (openarmature.invocation_id,openarmature.node.name, etc.) — in a structured form rather than as separate span attributes.
Replace with:
The typed event carries the same identity / scoping / outcome data the §5.5 span attribute surface exposes — the §5.5.3 GenAI semconv response attributes (
gen_ai.system,gen_ai.request.model,gen_ai.response.model,gen_ai.response.id,gen_ai.usage.*,gen_ai.response.finish_reasons), the §5.5.1 payload attributes (openarmature.llm.input.messages,openarmature.llm.output.content,openarmature.llm.request.extras), the §5.5.2 GenAI request-parameter family (gen_ai.request.temperature,gen_ai.request.max_tokens, etc.), the §5.5.4 / prompt-management §12 prompt-identity attribute family (openarmature.prompt.name,.version,.label,.template_hash,.rendered_hash,.group_name), plus the OA- namespaced cross-cutting attributes (openarmature.invocation_id,openarmature.node.name, etc.) — in a structured form rather than as separate span attributes.The §5.5.4
disable_llm_payloadopt-out flag continues to gate rendering of payload-bearing data (openarmature.llm.input.messages,openarmature.llm.output.content,openarmature.llm.request.extras) at the OTel observer's rendering boundary. The equivalent typed-event fields (input_messages,output_content,request_extras) are populated by the implementation unconditionally; observers respect their owndisable_llm_payloadflag on the typed-event rendering path identically to the span attribute path.
The rest of §5.5.7 (the Backwards compatibility with the sentinel- namespace convention paragraph + the Backends SHOULD subscribe to one event variant paragraph) is unchanged.
Conformance test impact¶
Updated fixtures (rename)¶
Fixtures 050-056 under spec/observability/conformance/ assert
LlmCompletionEvent fields including request_id. The rename swaps
every assertion from request_id: <value> to response_id: <value>;
no semantic change, no fixture restructuring. The .md companion docs
for the affected fixtures get a one-line edit noting the field rename
per proposal 0057. Affected fixtures: 050, 051, 052, 053, 054, 055,
056 (the seven fixtures from 0049's conformance impact section that
assert against the field set).
New fixtures (additive fields)¶
Nine new fixtures, one per new field plus one combined-population case:
06X-llm-completion-event-input-messages-populated— a graph with one LLM-calling node; mocked provider receives a 2-message conversation; the typed event'sinput_messagescarries the spec-canonical message-list shape per llm-provider §3 (per-message{role, content, tool_calls?, tool_call_id?}records).06X-llm-completion-event-output-content-populated— assistant response withcontent="..."; assertoutput_contentpopulated verbatim. Companion case where the assistant returnstool_callswith empty content; assertoutput_content == null.06X-llm-completion-event-request-params-populated— call passesRuntimeConfig(temperature=0.7, max_tokens=512); assertrequest_params == {"temperature": 0.7, "max_tokens": 512}(the other §5.5.2 parameters absent from the mapping per the absence-is-meaningful rule).06X-llm-completion-event-request-extras-populated— call passesRuntimeConfig(extras={"vllm.guided_decoding": {...}}); assertrequest_extrascarries the extras mapping as a native object (not a JSON-encoded string).06X-llm-completion-event-active-prompt-populated— LLM call inside a prompt-context binding; assertactive_promptcarries the 5-field identity record matching the prompt'sname,version,label,template_hash,rendered_hash.06X-llm-completion-event-active-prompt-null— LLM call outside any prompt-context binding; assertactive_prompt == null(parallel to the §8.4.4openarmature.prompt.*attributes being absent on the span in this case).06X-llm-completion-event-active-prompt-group-populated— LLM call inside aPromptGroupcontext; assertactive_prompt_group == {group_name: "..."}.06X-llm-completion-event-call-id-always-present-and-distinct— sequence of 3 LLM calls in a graph; assert eachLlmCompletionEvent.call_idis non-null AND the three values are distinct (locks down the per-call freshness contract).06X-llm-completion-event-response-model-distinct-from-request— mocked provider returnsmodel: "gpt-4o-2024-08-06"for a request againstmodel: "gpt-4o"; assertresponse_model == "gpt-4o-2024-08-06"whilemodel == "gpt-4o". Plus a companion case where the provider does not return a response model; assertresponse_model == null.
Final fixture numbers assigned at acceptance; the rough block is 060-068.
Each fixture follows the existing 050-056 pattern (YAML directive +
.md companion).
Unaffected fixtures¶
All other observability fixtures (001-049) continue to pass unchanged.
The typed event's existing field set is preserved (the request_id →
response_id rename is the only existing-field change); the new fields
are additive on the typed-event shape. Fixtures asserting other event
types or other span attributes are unaffected.
Versioning¶
MINOR bump (pre-1.0). On acceptance the whole-spec SemVer increments:
- Eight new fields on the
LlmCompletionEventtyped event (additive — existing field set preserved). - One field rename:
request_id→response_id. Per the pre-1.0 SemVer convention documented inCHANGELOG.md, MINOR bumps MAY include breaking changes. The CHANGELOG entry calls out the rename in the Changed section so any consumer pinning againstrequest_idhas lead-time awareness. - §5.5.7 framing-paragraph update (informative-clarifying alongside the field-set extension).
- Nine new conformance fixtures plus seven updated fixtures (050-056).
The change is additive at the typed-event field set level; the rename is a breaking change on the field-name surface that this proposal deliberately makes pre-1.0 to preserve internal naming coherence for the response-side surface.
Alternatives considered¶
-
Hoist the seven §5.5.2 request parameters as flat top-level fields rather than bundling into a
request_paramsmapping. Define seven typed fields (temperature: float | null,max_tokens: int | null, etc.) on the event directly. Rejected: bloats the typed event with seven nullable fields the caller MAY never supply; the mapping shape matches the §5.5.2 attribute family more naturally (each request parameter is independently optional; the cross-vendor pattern is "supply a subset," not "supply all"). The flat-field shape also leaks vendor-specific parameter additions into the typed event surface every cycle —gen_ai.request.tool_choiceper future proposals would have to grow a new typed field; the mapping accommodates new parameters without spec edits. -
Sink at construction rather than at observer rendering. Provider populates
input_messages/output_content/request_extrasonly when no observer in the active subscription set hasdisable_llm_payloadset; other observers see the fields as null. Rejected: requires provider-side introspection of the active observer set, complicating the provider/observer boundary. Doesn't match how §5.5.1 + §5.5.4 work today — the span attributes are emitted unconditionally and each OTel observer gates at its own rendering boundary. Sink at observer rendering (the design adopted in Proposed change above) is the analog that preserves the symmetric pattern. -
Introduce a new event class (
LlmCompletionEventWithRequestSide) rather than extending the existingLlmCompletionEvent. Rejected: fragments the type discriminator surface for no behavioral gain. Proposal 0049's class-name rule explicitly anticipates field-set extension on the existing class — "the class name is spec-normative as an identifier ... provided the field set + dispatch contract are preserved." The field set extends; the dispatch contract is unchanged; the class identity is preserved. A new event class would force every typed- event consumer to update their discriminator filter for no behavioral upside. -
Defer the
request_id→response_idrename to a separate follow-on proposal. Land the eight additive fields now; do the rename later when no other field-table edits are pending. Rejected: this proposal is already editing the same field table for the additive fields and forresponse_model; a coordinated edit is cheaper than two sequential edits to the same table. Leaving the naming inconsistency to be addressed in a later cycle would mean every spec consumer reading the v0.51.0 field table encounters the inconsistency unaddressed. -
Defer
response_modelto a separate follow-on. Land the seven request-side fields now; addresponse_modellater when an observer demand specifically surfaces for it. Rejected: the demand IS the existing observer-migration case — observers readinggen_ai.response.modelfrom the OTel span surface today need the typed-event equivalent during the same migration. Bundling avoids a second follow-on cycle for a single one-field addition that completes the §5.5.3 response-attribute coverage on the typed event. -
SHOULD-emit-both alias during the
request_id→response_idrename transition. Mirror proposal 0049's sentinel-NodeEvent transition pattern by having implementations emit bothrequest_idandresponse_idcarrying the same value during a transition window, withrequest_iddeprecated in a follow-on cycle. Rejected: 0049's SHOULD-emit-both transition addressed a backwards-compat concern for a broadly-consumed pattern (sentinel-namespace string matching across many backends, accreted over the pre-0049 spec history). Therequest_idfield was introduced at v0.41.0 as part of the same typed-event variant the rename also touches; the consumer surface a SHOULD-emit-both alias would protect did not exist before v0.41.0 and has no broadly-accreted consumer code to migrate. The alias pattern's cost — every implementation MUST emit both fields; every observer MUST understand the transition window — is disproportionate to the migration burden a hard swap actually imposes. CHANGELOG-entry operator-awareness is the proportional path.
Open questions¶
None at draft time. All design choices are settled in the proposal text above:
- Field-set scope — eight new fields covering the §5.5.1 payload
attributes, the §5.5.2 GenAI request-parameter family, the §5.5.4 /
prompt-management §12 prompt-identity attribute family, plus
response_model(completing §5.5.3 coverage) andcall_id(the per-call disambiguator). Outcome-side and identity / scoping fields from 0049's v1 set preserved unchanged. - Privacy framing — sink at observer rendering (option b from the coord-thread direction). Symmetric with §5.5.1 + §5.5.4. Explicit callout to queryable-observer consumers that they're responsible for their own redaction posture.
- Class name — extends existing
LlmCompletionEventper the class- name rule from proposal 0049. request_id→response_idrename — bundled into this proposal (hard swap, no SHOULD-emit-both alias) per alternative 6.response_modelinclusion — bundled into this proposal per alternative 5.call_idwire shape — spec-neutral on format (UUID, ULID, monotonic counter all fine); spec mandates "stable, unique within impl run, freshly minted percomplete()call."request_paramsshape — mapping with absence-is-meaningful semantics per alternative 1.
If reviewers surface a substantive question during PR review, it gets resolved into the proposal text rather than left here as a defer.
Out of scope¶
LlmCallFailedEventtyped variant (alternative 3 from proposal 0049). Failure events have different field shapes and use cases; remain scoped to a separate follow-on if demand emerges. Today's failure surface flows through the exception path per llm-provider §7.LlmStreamChunkEventtyped variant ([[discuss-llm-token-streaming]] is the active design thread for the per-chunk streaming variant). This proposal's completion-event field-set extension does not pre-empt or constrain the streaming event variant's design.raw_request/raw_responsevendor-body fields. The typed event scopes to the abstracted shapes (Message,Tool,RuntimeConfig). Vendor-specific wire bodies remain accessible viaResponse.rawon the response side; adding wire-body fields to the typed event would couple it to per-vendor serialization details. Out of scope for v1; if a use case surfaces (e.g., a backend rendering the literal HTTP request body for debugging), a follow-on proposal can scope it.- Cross-impl byte-identical typed-event serialization. Same caveat as 0049 — event objects are language-native; cross-language byte equality is out of scope (matches the observability §5.5.1 cross-impl byte-stability caveat that applies identically to the typed event's field values).
- Event payload truncation contract for the typed-event field values. Unlike §5.5.1 attribute values (subject to §5.5.5 truncation), the typed event's field values flow through observer code unchanged. Observers SHOULD apply backend-specific truncation if rendering the event to a byte-bounded backend. The inline-image redaction rule from §5.5.5 applies (a hard rule, not a configurable cap), but the per-attribute byte cap does NOT — that's a span- attribute concern, not a typed-event-field concern.
- A typed-event field for
gen_ai.usage.cache_*.input_tokensattributes. These already flow through the existingusagefield on the typed event (per llm-provider §6Response.usage.cached_tokens/cache_creation_tokensfrom proposal 0047). The typed event'susagerecord carries the fullResponse.usageshape unchanged; no separate fields needed.