Table of Contents

ADR-020: AgentTrace v1.1 Schema (Glass Box additive fields)

Status

Accepted — delivered as Glass Box v0.11 on branch feat/glass-box-v0.11-chat-boundary-tracing. Companion to ADR-019; extends the trace model of ADR-004.

Date

2026-05-31

Context

Chat-boundary recording (ADR-019) needs to persist evidence the v1.0 AgentTrace schema cannot carry: which recording layer an entry came from, a per-invocation correlation id, the verbatim system prompt, the tool definitions sent to the model, the per-turn finish reason, the request options, and provider metadata. The change must be purely additive — existing v1.0 traces must continue to load and round-trip unchanged, no existing recorder behaviour may change, and no public API may break. The serializer is reflection-based System.Text.Json (PropertyNamingPolicy = CamelCase, DefaultIgnoreCondition = WhenWritingNull), and entries are a flat List<TraceEntry>.

Two distinct AgentTrace types exist and must not be confused: AgentEval.Tracing.AgentTrace (the working trace model, in Core) and AgentEval.Output.AgentTrace (an IOutputStore pipeline record, in Abstractions). This ADR concerns the Core one.

Decision

  1. Bump AgentTrace.Version "1.0" → "1.1". Version denotes the highest schema version of any content the file may contain; individual entries may still be v1.0-shaped. Consumers branch on per-entry data (e.g. TraceEntry.EffectiveScope), never on the header string.

  2. Add additive, nullable fields to TraceEntry: Scope (TraceEntryScope?), CorrelationId, SystemPrompt, ToolDefinitions (List<TraceToolDefinition>?), FinishReason, RequestOptions, ProviderMetadata, plus a non-serialized EffectiveScope convenience (null ⇒ AgentInvocation). Add the TraceEntryScope enum (AgentInvocation = 0 | ChatTurn | ToolExecution, [JsonStringEnumConverter]) and the TraceToolDefinition type.

  3. Scope is nullable. With WhenWritingNull, agent-boundary entries (Scope = null) serialize byte-identically to v1.0 — no snapshot churn — while chat-boundary entries explicitly carry "scope":"ChatTurn". Back-compat is therefore free: v1.0 files load with Scope == null (EffectiveScope == AgentInvocation); no serializer code change is required (proven by a round-trip regression test against an inline v1.0 fixture).

  4. No subclassing. Because entries serialize as a flat List<TraceEntry>, layer-specific entries are built by static factory methods on TraceEntry (ForChatRequest / ForChatResponse / ForChatError / ForToolExecution) that return a populated base TraceEntry.

  5. Index vs CorrelationId. Index is the request/response pairing key (TraceReplayingAgent.BuildRequestResponsePairs groups by it, filtering by Type); a round-trip's Request and Response share one Index and each round-trip gets a distinct one. CorrelationId is the per-invocation grouping key. Runtime gate verdicts are recorded into trace-level Metadata (not as entries) precisely so they never enter the Index pairing/replay path.

  6. migrate v1.0→v1.1 bumps the header on existing files (cosmetic, since the change is additive); doctor warns on double-wrapping.

Consequences

Positive

  • Existing v1.0 traces, exporters (JUnit/Markdown/JSON/SARIF/PDF — schema-driven), TraceReplayingAgent, and Mission Control all keep working with no change; richer evidence flows automatically once v1.1 fields are populated.
  • One schema carries agent-boundary, chat-boundary, and tool-execution entries.

Negative / costs

  • A v1.1 header now appears on traces produced by the new recorders (and on migrated files); documented as "max schema version present."
  • Consumers must treat null scope as AgentInvocation (the EffectiveScope helper standardises this).

Alternatives Considered

  • A new parallel trace type for chat-boundary data. Rejected: would fork the serializer, exporters, replay, and Mission Control; additive fields keep one model.
  • Non-nullable Scope defaulting to AgentInvocation. Rejected: a value-type field always serialises, changing existing agent-boundary trace JSON (snapshot churn). Nullable + WhenWritingNull keeps v1.0 output identical.
  • A SchemaVersion integer / breaking rename. Rejected: the field is Version (string) and must stay back-compatible.