On this page
The Schema
The normalized event schema and the guarantees behind it.
A normalized session is an ordered list of events. Exactly one payload
field is set per event, matching its kind:
| Kind | Payload | ACP analog |
|---|---|---|
session_meta | Session identity: harness, version, session ID, cwd | none (extension) |
user_message | User-role content, with an origin marker: human or harness | user_message_chunk |
assistant_message | One complete assistant API message | agent_message_chunk |
thinking | Extended-thinking block | agent_thought_chunk |
tool_call | Tool invocation with full input | tool_call |
tool_result | Tool outcome, correlated by tool call ID | tool_call_update |
system | Harness/API activity: injected context, diagnostics, errors | none (extension) |
unknown | Unclassifiable record, preserved verbatim (permissive mode only) | none (extension) |
Points worth knowing
- The event vocabulary is shared. Event kinds and
tool classifications follow the
Agent Client Protocol’s
session-update vocabulary where an analog exists. Token usage fields
follow OTel GenAI semantic conventions. Every field in the schema is
tagged with its provenance (
acp,otel, orext), enforced by a test, so the mapping cannot rot. - Token fields carry provider semantics. The field names are
shared, and the meanings follow the provider: OpenAI-style usage
reports
input_tokensas the total prompt with cached tokens as a subset, while Anthropic-style usage reports it as only the uncached remainder alongside separate cache read and write fields. Adapters preserve what the harness recorded, so cross-harness cost comparisons should sum the cache-aware fields rather than compareinput_tokensdirectly. See the worked comparison for a real case where the naive reading is wrong by four orders of magnitude. assistant_messageis the accounting anchor. Harnesses may split one API message across many records with usage written as a growing snapshot. The adapter folds them and takes the final snapshot. Exactly oneassistant_messageis emitted per API message, even when all of its content becamethinkingortool_callevents, so token totals are always derivable. Events from the same API message share amessage_id.tool_callandtool_resultstay separate, in stream order. Ordering is data. Interleaving, parallel tool execution, and retries are visible in the sequence.Session.ToolInteractions()provides the joined view, including unanswered calls and orphaned results.- Results carry what the model saw, plus what the harness knew.
tool_result.contentis the post-pipeline content the model actually received. Harness sidecar data rides along verbatim inenrichment, with retrieval metrics promoted tofetch(URL, raw bytes fetched, status, duration) when present. For summarizing pipelines like Claude Code’s WebFetch, comparingfetch.raw_bytesagainst the content size measures the pipeline’s compression directly. - Every event points back at its source.
provenancecarries the 1-based line range in the native transcript, and optionally the verbatim records (--keep-raw). - The report closes the loop.
reportcounts skipped record types (harness UI bookkeeping with no model-visible content), unknown events, and orphaned tool results. Between events, skips, and errors, every input line is accounted for.
The JSON encoding of Session and Event is the cross-language output
contract; the agentminutes_schema field identifies its revision.
The Go types in
session
are documentation for it.