Streams

A stream holds the records of exactly one session as JSON Lines: one JSON object per line. The media type is application/vnd.agent-session+jsonl. The RECOMMENDED file extension is .jsonl; .agent-session.jsonl MAY be used where the content type must be recognizable from the name. ASR-001 A stream MUST be UTF-8 without a byte order mark. Each record MUST be one JSON object followed by a single line feed (U+000A). ASR-002 Writers MUST NOT pretty-print records or emit blank lines. A record never contains a raw line feed; line feeds inside strings are escaped as JSON requires. ASR-003 The first line of a stream MUST be the session header, and no other line in the stream may have type: "session". ASR-004 All records in a stream MUST belong to the session that the header identifies. A producer that observes several sessions writes several streams. Facts scoped only to a conversation lane MUST NOT be assigned to a session by time or order. Readers MAY accept a carriage return before the line feed, since JSON treats it as whitespace. Writers MUST NOT emit one (this follows from ASR-001 and ASR-002).

Immutable records

ASR-005 Once a complete line has been written to a stream, no writer may change, reorder or remove it. A later fact, a correction or a deletion notice is a new record (see Missing data and revisions).
Design note. A reader can tail an appended stream without rereading its earlier lines. Evidence that arrives later from the source can be appended as revisions, rewinds, deletion notices or gaps. When the source rewrites or deletes positions, a converter writes a replacement capture. Differences between independent captures do not establish source revision history. A mutable source does not require invented revisions or deletion notices.

One writer

ASR-006 At most one writer appends to a stream at any time. The header names the writer in producer.instance. A different writer that takes over a stream (for example after a resume in a new process) MUST first append a declaration record whose producer.instance names itself (see Session records). Interleaved writes from concurrent writers to one file are a known source of corruption in native logs. ASR forbids it rather than trying to repair it.

Torn tails and malformed lines

A complete line is a sequence of bytes terminated by a line feed. Bytes after the last line feed are the tail. ASR-007 Readers MUST NOT treat a non-empty tail as a record. In a live stream the tail may be a record still being written; in a closed stream it is a torn record from a crash. Readers MAY report it. ASR-008 A writer that resumes appending to a stream that ends in a non-empty tail MUST first truncate the tail, then append. It MUST NOT append after the torn bytes. ASR-009 When a complete line is not a JSON object, or lacks a valid type, seq or id, a reader MUST report a diagnostic, continue reading the following lines, and treat the position as a gap of unknown content. Any active path or context view it produces MUST show a gap marker at that position. It MUST NOT present the records on either side as adjacent without the marker. ASR-010 A converter that finds corrupt or unparseable data in its source MUST emit a gap record with reason: "unparseable" at the corresponding position instead of skipping it silently. ASR-011 Readers MUST skip an empty complete line (a line feed alone) and MAY report it. An empty line is not a gap. A line that parses and has a valid envelope but fails the schema for its record type is an invalid record. Validators report it. Readers SHOULD use the fields they can interpret, MUST NOT drop the line when storing or rewriting the stream, and MUST NOT treat it as a gap.

seq

ASR-012 Every record MUST have a seq that is an integer from 0 to 253−1. ASR-013 seq values MUST be strictly increasing in line order. They need not be consecutive. ASR-014 seq defines serialization order within a capture. Readers MUST order records by seq (equivalently, line order), not by ts. Within each source store, producers MUST preserve its recorded order. When a source retains only display order, producers use that order and declare the loss order. Across stores without a common recorded order, producers MUST use a documented deterministic serialization policy, MUST NOT merge by ts, and MUST declare order. Readers MUST NOT treat the relative position of records from different stores as evidence of time order or cause. An immutable record from a side log MAY follow the last record it links to only when this preserves each store’s order and the snapshot-tail rule (ASR-034). ASR-015 An appended stream MUST keep every earlier record’s id and seq. Independent captures MUST preserve record IDs backed by stable source identity. seq applies only within one capture and MAY change between captures; readers MUST NOT compare it across captures. A producer MUST use idScope: "capture" only when the identity itself has no stable cross-capture basis. ASR-016 A producer that appends to its own earlier export MUST leave every existing line byte-identical. A producer that cannot reproduce its earlier records unchanged (for example after a converter upgrade) MUST write a new stream instead of rewriting the old one. The requirement to preserve IDs across captures does not apply to IDs derived only from positions the source may rewrite or delete. This includes sources rewritten as whole snapshots and event stores that have been compacted. Those positional IDs carry idScope: "capture" (ASR-070). Stable native identities in the same source keep their IDs.
Design note. Leaving room in seq lets a converter emit several records for one native item without renumbering later ones: for example seq = nativeIndex × 1000 + k. Codex rollouts carry a native per-file ordinal; most other sources have only line or row order.

Captures

A stream is one capture of a session. An export either appends to that capture under ASR-016 or writes a new capture. A capture does not imply an atomic read across independent databases. ASR-033 When the source rewrites or deletes positions, the producer MUST write a new capture instead of appending a reconstructed history to the earlier one. A capture that replaces an earlier capture MUST have observedAt in its header. Appends and no-op exports MUST NOT rewrite that value. Within a bundle, the manifest’s single entry for a session selects the authoritative capture. Across bundles, consumers MUST select a capture explicitly; observedAt provides information but does not determine which capture is authoritative. Consumers MUST NOT infer revision chains from differences between captures. ASR-034 A record with a source-defined transcript position keeps that position, even when its document or row is rewritten. An array index, native sequence or source-defined row order can supply a position. Arbitrary SQL order cannot, and neither can a ledger of when the destination first observed each row. The current order does not establish the historical order of events, and it does not let a producer attach today’s row content to an older audit event. Current-state rows without transcript positions MUST have assembly.mode: "snapshot" and follow all positioned records. Linked current-state rows also belong in this tail. A config_change in the tail MUST also have snapshot: true. If new positioned records arrive after the tail, the producer MUST write a new capture. The tail’s seq values can change in a new capture. A stable native row ID can still supply a stable record ID. Record digests can change even when record IDs stay the same.

Record IDs

ASR-017 Every record MUST have an id that is unique within its stream. ASR-018 A record ID MUST match ^[A-Za-z0-9][A-Za-z0-9._~-]{0,127}$. Record IDs are opaque: readers MUST NOT parse meaning out of them. Record IDs are assigned by the producer. They are distinct from native entity keys, which are kept verbatim in their own fields (see Identity). A native producer whose own IDs fit the pattern MAY use them as record IDs.

Bundles

A bundle is a directory, or a ZIP or tar archive of one (a tar archive MAY be compressed with gzip or zstd), with this layout:
ASR-019 A bundle MUST contain a manifest named bundle.json at its root. ASR-020 The manifest is authoritative for where streams are. Readers MUST locate streams through the manifest’s path values, and MUST NOT derive session identity from file or directory names. The sessions/ layout is a recommendation only. The RECOMMENDED stream file name is a safe storage key, not a raw session ID: for example sessions/<agent>/<first 32 hex digits of SHA-256 of the session reference>.jsonl. Raw native IDs can contain characters that are unsafe in file names, and can disclose information.

Manifest

Manifest members: Session entry members: ASR-021 A manifest path MUST be a relative POSIX path inside the bundle: it MUST NOT begin with /, contain a .. segment, contain a backslash, or contain a drive letter. ASR-022 For a present entry, digest.value MUST be the lowercase hex SHA-256 of the stream file’s exact bytes, and recordCount MUST equal the number of complete lines in it. ASR-023 A present entry MUST carry path, digest and recordCount. A missing entry MUST carry reason and MUST NOT carry path or digest. ASR-024 The agent, namespace and sessionId of a present entry MUST equal the session key in the referenced stream’s header (agent names compared after alias resolution, see Identity). ASR-025 Streams inside a bundle MUST NOT end in a non-empty tail, and are stored uncompressed. The bundle archive as a whole MAY be compressed. ASR-036 A bundle SHOULD account for every referenced session with a present or missing manifest entry. A missing entry with reason: "other_bundle" MUST identify that bundle in bundle. Accounting for a session in the manifest never excuses omitting a required reference or contradicting included data. A missing entry records a fact about the list of sessions, such as a session that a harness’s retention sweep deleted before export. It is not evidence about the session’s content. A stream digest identifies one captured byte sequence; appending one record changes it. It does not identify the session itself, and it does not prove who wrote the stream or that its content is accurate.

Native files

The native/ directory holds original native files, laid out as the producer chooses. Records point into it with native.ref (see Envelope). ASR-026 An exporter that writes a bundle for sharing MUST NOT include native/ files unless the user explicitly asked for them. Native files are outside any redaction the exporter applied to the streams.

Blobs

ASR-027 A blob MUST be stored at blobs/sha256/<hex>, where <hex> is the lowercase hex SHA-256 of the blob’s exact bytes. The file holds only those bytes. ASR-028 A reader that loads a blob MUST verify that its SHA-256 and size match the reference before using it, and MUST treat a mismatch as the blob being unavailable. A blob reference whose blob is not present (in a standalone stream, or a bundle that omitted it) is still valid: the content is unavailable, not wrong.

Blob references

ASR-029 A blob reference MUST have blob matching ^sha256:[0-9a-f]{64}$ and an integer size. ASR-030 Text MAY be inlined in records at any size. Inline binary content (data, base64) SHOULD be no larger than 64 KiB after decoding; larger binary content SHOULD be a blob.

Digests

ASR defines two digests. Neither is stored in records.
  • The stream digest is the SHA-256 of a stream file’s exact bytes. It appears in bundle manifests and in the integration companion’s notes ref.
  • The record digest is computed by readers and exporters when they need to cite one record exactly.
ASR-031 The record digest of a record MUST be computed as follows: remove the top-level native member if present; serialize the remaining object with the JSON Canonicalization Scheme (RFC 8785); compute SHA-256 over the UTF-8 bytes; write the result as sha256: followed by 64 lowercase hex digits. All other members, including seq, id and _meta, are included. ASR-032 Producers MUST NOT write numbers that RFC 8785 cannot serialize exactly: non-integer values that are not finite IEEE 754 doubles, or integers outside ±(253−1). ASR-035 When a source JSON value cannot be represented under ASR-032, producers MUST preserve the whole affected value without rounding, clamping or changing member types. Arguments use inputText with input absent. A native row uses native.blob; raw output uses capture.raw with state external. Other native values, including _meta, use an application/json BlobRef at their documented destination or _meta["<agent-namespace>/jsonText"]. This is an object keyed by the JSON Pointer of the displaced destination within the containing record or manifest. Each value is lossless JSON text for the entire displaced value. The displaced value MUST be absent when this text route is used. Text MUST NOT replace a required numeric field. A producer unable to represent the required fact MUST emit an appropriate gap or stop before publication. Preserve the original bytes when available; otherwise preserve each number literal exactly, without claiming to reproduce the original whitespace. Privacy and redaction rules still apply. Readers MUST NOT silently parse the text into numbers that lose precision.
Design note. native is excluded because the same converter may embed a native record in one export and point to it in another. _meta stays in, because the vendor data it holds is part of what the record states. seq and id stay in, so exact citations need the record digest or the selected stream digest as well as identity.