Skip to content

Preserve canonical model transcripts end to end#179

Merged
raphael merged 4 commits into
mainfrom
fix/canonical-model-transcripts
Jul 13, 2026
Merged

Preserve canonical model transcripts end to end#179
raphael merged 4 commits into
mainfrom
fix/canonical-model-transcripts

Conversation

@raphael

@raphael raphael commented Jul 13, 2026

Copy link
Copy Markdown
Member

Why

A model turn should have one durable truth.

Previously, the runtime could rebuild a transcript from planner-facing events after the provider had already produced a richer ordered response. That created two competing representations of the same turn. Thinking blocks, citations, tool metadata, ordering, usage, or raw JSON could be lost or reshaped; retries could associate a plan with the wrong invocation; and provider history could become invalid when replayed.

This PR makes the provider-authored response canonical from receipt through workflow persistence and replay. Planners still receive the events they need, but those events are now a projection of the response—not a second transcript source.

What changes

  • One canonical response: ordered text, thinking, citations, tool calls, metadata, usage, and stop reason are validated and preserved together.
  • Atomic invocation selection: the runtime journals model invocations, matches planner output through provenance or exact tool-call identity, and commits the selected response once before tool effects begin.
  • Lossless provider replay: Anthropic, Bedrock, OpenAI, and Vertex preserve historical tool identity and provider metadata. Unsupported replay fails clearly instead of rewriting history heuristically.
  • A strict stream lifecycle: typed chunks replace loose framing. Gateways and consumers enforce valid sequencing, terminal stop, usage, tool-call, structured-output, and canonical-response agreement.
  • A single durable codec: transcript JSON uses canonical field names, lossless numbers, strict decoding, and one Temporal data converter. Obsolete compatibility fields and duplicate policy modes are removed.
  • Bedrock correctness: Smithy documents retain numeric constraints, non-tool content blocks may begin with their first delta, and empty initial tool-input fragments no longer produce invalid chunks.

How it works

  1. The provider adapter validates and deep-clones the completed response at the model boundary.
  2. The runtime records that response in an invocation journal while planner events are emitted from it.
  3. The planner's result selects exactly one recorded invocation using runtime provenance or bijective tool-call matching.
  4. The workflow appends the selected canonical response exactly once, then records tool results in call order.
  5. Subsequent turns replay the same ordered response without reconstructing or “repairing” provider history.

This removes the old transcript ledger and collapses response ownership into a single path.

Contract impact

This intentionally strengthens several public contracts:

  • model.Chunk is now a typed interface, and streams terminate with StopChunk plus a canonical Streamer.Response().
  • Tool inputs and schemas remain raw JSON through the runtime; invalid or lossy representations are rejected at the boundary.
  • Stream implementations must agree on content, tool calls, usage, stop reason, and structured completion before EOF is accepted.
  • Temporal payloads use only the new canonical representation. In-flight turns encoded with obsolete payload shapes may need to restart.
  • Removed compatibility APIs include legacy tag-policy modes and heuristic unknown-tool rewriting.

Review guide

A useful dependency order is:

  1. Core contracts: runtime/agent/model, rawjson, and transcript
  2. Selection and persistence: runtime/agent/runtime/model_invocation_journal.go and workflow transcript handling
  3. Streaming boundaries: features/model/gateway, runtime/agent/planner, and runtime/agent/completion
  4. Provider fidelity: Anthropic, Bedrock, OpenAI, and Vertex adapters
  5. Durability and adoption: Temporal converter, quickstart templates, and docs

Validation

  • make lint
  • go test -race ./...
  • CI build and integration tests
  • Provider replay, thinking, citations, tool correlation, structured completion, stop/usage agreement, and malformed-stream coverage
  • Downstream multi-turn smoke tests covering tool-heavy, text-only, and structured-output flows
  • Independent architecture reviews found no remaining high-severity transcript, provider-fidelity, streaming, or durability issues

raphael added 4 commits July 12, 2026 19:56
Capture provider responses once, enforce lossless replay and stream contracts, and keep Temporal workflow state aligned with the selected model invocation.
Convert lossless JSON numbers to Smithy-supported numeric types so Bedrock receives valid tool schemas instead of quoted numeric constraints.
Treat the first text, citation, or reasoning delta as the block boundary because Bedrock only emits explicit content-block starts for tool use.
Bedrock can emit an empty initial tool-input fragment; retain the open tool block without publishing an invalid empty typed chunk.
@raphael raphael merged commit a3049ee into main Jul 13, 2026
2 checks passed
@raphael raphael deleted the fix/canonical-model-transcripts branch July 13, 2026 07:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant