A .flight trace is a portable, tamper-evident, replayable record of an AI agent run. This
document is the normative specification; it is language-neutral — any runtime can produce or consume
a conforming trace. The Python reference implementation lives in this repository.
Normative keywords (MUST / SHOULD / MAY) follow RFC 2119. The full design rationale is in
plan/02-trace-format.md; the JSON Schema for the envelope is
schema/flight-event.schema.json.
A trace is a directory named <name>.flight:
<name>.flight/
├── manifest.json # sealed head hash + counts (written last)
├── events.jsonl # append-only, hash-linked event stream (one JSON object per line)
└── blobs/ # OPTIONAL content-addressed sidecar: blobs/<hh>/<hh>/<sha256hex>
events.jsonl MUST be JSON Lines (one \n-terminated JSON object per line), UTF-8. It MUST be
append-only: a writer MUST NOT rewrite a previously written line. manifest.json MUST be written
after the last event.
Every line MUST be a JSON object with these fields (see the JSON Schema for exact types):
| Field | Type | Meaning |
|---|---|---|
v |
int ≥ 1 | event-schema version |
seq |
int ≥ 0 | gap-free total order; the sole ordering authority |
run_id |
string | id of the run |
id |
string | run_id + "/" + seq |
type |
string | namespaced event type (§4) |
t_wall |
string | RFC3339 UTC timestamp — informational only, MUST NOT be used for ordering |
t_mono_ns |
int | nanoseconds since run.start |
parents |
int[] | causal/DAG edges, referencing seq numbers |
body |
object | type-specific content, already redacted |
body_hash |
sha256:<hex> |
digest of the content (§3) |
prev_hash |
sha256:<hex> |
hash of event seq-1 (or the genesis value for seq 0) |
hash |
sha256:<hex> |
integrity digest of the whole envelope minus hash (§3) |
Before hashing, a value MUST be serialized with a canonical JSON scheme (RFC 8785 / JCS): object keys sorted by UTF-16 code units, no insignificant whitespace, NFC-normalized strings, and stable number formatting. NaN and Infinity MUST NOT appear.
body_hash = sha256(JCS({ "type", "v", "body" })), computed over the body with volatile fields removed (latency_ns,duration_ns,truncated,mono_origin_ns). This is the content-equality key used for dedup and diff — two otherwise-identical events MUST share abody_hash.hash = sha256(JCS(envelope without "hash")). Because the envelope carriesprev_hash, eachhashtransitively commits to every prior event (an append-only chain).- The genesis
prev_hashforseq0 issha256:+ 64 zeros.
The sha256:<hex> digests use lowercase hex.
run.start, run.end, http.request, http.response, tool.call, tool.result, clock.read,
rng.draw, error. Types are namespaced (dot-delimited); vendors MAY add x-<vendor>.* types.
A reader encountering an unknown type MUST still order it by seq and MUST still be able to verify
the chain (hashing is structural, not type-aware). A reader that re-emits an event MUST preserve
fields it does not understand (or the hash breaks).
In v0.1 LLM traffic is recorded as
http.request/http.response(the raw wire is what makes replay byte-faithful and provider-agnostic). Semanticllm.*events are a later enrichment.
Large body payloads MAY be spilled to the blobs/ sidecar and referenced inline as
{"$blob": "sha256:<hex>", "media_type": "...", "size": <int>}. Non-JSON-serializable values MAY be
represented as a lossy surrogate {"$unrepr": true, "lang_type": "...", "repr": "..."}.
manifest.json MUST contain at least: spec_version, hash_alg ("sha256"), canonicalization,
run_id, head_hash (the hash of the last event), and event_count. It MAY advertise optional
features under capabilities.
A verifier re-walks events.jsonl in order, maintaining an expected prev_hash (starting at
genesis), and for each event asserts: seq increments by 1; prev_hash equals the running value;
the recomputed body_hash and hash match the stored values. Finally it asserts the last hash
equals manifest.head_hash and the count matches manifest.event_count. Verification needs only the
redacted canonical bytes — no secrets and no network — so any recipient can verify a shared trace
offline.
Redaction MUST run before canonicalization and hashing, so a secret is never hashed or written in
plaintext and redaction can never break the chain. A redacted value SHOULD become a stable placeholder
carrying a ref that is derived deterministically from the secret, so referential integrity is
preserved without revealing the secret.
spec_version is semver. Within a major version, fields are only added (optional); removal or
retyping requires a major bump. This document specifies 0.1.0.