You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/09/04 13:15:45 UTC
(skywalking-ai-sessionizer) branch main updated: Define what asz conversation -json and -yaml print, with a complete example
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-ai-sessionizer.git
The following commit(s) were added to refs/heads/main by this push:
new a67b640 Define what asz conversation -json and -yaml print, with a complete example
a67b640 is described below
commit a67b6408f51b69ed17a3a93f8dc87c002fe469f9
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Fri Sep 4 21:15:38 2026 +0800
Define what asz conversation -json and -yaml print, with a complete example
The asz.view page read as a schema. It now says what produces the
document, the two flags and the page's own response, how each view of a
conversation is drawn from it, the transcript, the flow timeline,
cross-stream flow, the evidence and the verification, and how the YAML
rendering relates to the JSON. A complete example, the whole document of
the fixture session as the command prints it, sits beside the page,
generated from the fixture scenario by make asz-view-example, and a test
fails when the committed example no longer matches what the code
produces.
---
.licenserc.yaml | 6 +-
Makefile | 9 +
docs/en/changes/changes-0.2.0.md | 4 +-
docs/en/formats/asz-view-example.yaml | 734 ++++++++++++++++++++++++++++++++++
docs/en/formats/asz-view.md | 59 ++-
tests/docs_test.go | 78 ++++
6 files changed, 877 insertions(+), 13 deletions(-)
diff --git a/.licenserc.yaml b/.licenserc.yaml
index 152deab..cd72a29 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -53,9 +53,9 @@ header:
- 'go.sum'
- '**/*.md'
- '**/testdata/**'
- # Fixture corpora are captured data, not source. They reproduce another
- # program's file format byte for byte, so a header would corrupt them.
- - '**/claudecode-files/**'
+ # The complete example document on the asz.view format page is what
+ # the command prints, byte for byte, and a test holds it to that.
+ - 'docs/en/formats/asz-view-example.yaml'
- 'dist/**'
- 'bin/**'
# The default storage root. It holds collected data, never source, and a
diff --git a/Makefile b/Makefile
index 2451871..a264eaa 100644
--- a/Makefile
+++ b/Makefile
@@ -71,6 +71,15 @@ scenarios: build
e2e-collector: build
tools/e2e-collector.sh
+## asz-view-example: regenerate the complete example document on the asz.view format page from the fixture scenario
+.PHONY: asz-view-example
+asz-view-example: build
+ @dir=$$(mktemp -d); \
+ $(BIN_DIR)/asz scenario build tests/scenarios/fixture.yaml --format sd --out $$dir --at 2026-01-01T00:00:00Z >/dev/null && \
+ $(BIN_DIR)/asz parse -config $$dir/asz.yaml >/dev/null && \
+ $(BIN_DIR)/asz conversation -config $$dir/asz.yaml -yaml 00000001-0000-4000-8000-000000000001 > docs/en/formats/asz-view-example.yaml && \
+ rm -rf $$dir && echo "docs/en/formats/asz-view-example.yaml"
+
## coverage: run tests with a coverage profile
.PHONY: coverage
coverage:
diff --git a/docs/en/changes/changes-0.2.0.md b/docs/en/changes/changes-0.2.0.md
index 00d3c3a..e657878 100644
--- a/docs/en/changes/changes-0.2.0.md
+++ b/docs/en/changes/changes-0.2.0.md
@@ -66,7 +66,9 @@
segments, the relations, and the rounds and files it was built from, each verified, with a gap or
a failed digest written into the document rather than returned as an error. Package
`pkg/sessionview` defines and owns the shape and `asz view` serves it at `/api/c/{id}/view`;
- `asz conversation -json` or `-yaml` prints it. It is never a file. A server holding the same files, such as the SkyWalking OAP, builds the same
+ `asz conversation -json` or `-yaml` prints it. It is never a file. The format page says how each
+ view of a conversation is drawn from the document, and carries a complete example generated from
+ the fixture scenario, which a test keeps current. A server holding the same files, such as the SkyWalking OAP, builds the same
document. See the format page.
- The page reads Session Data and Session Flow and nothing else. It took every record's time from
diff --git a/docs/en/formats/asz-view-example.yaml b/docs/en/formats/asz-view-example.yaml
new file mode 100644
index 0000000..480b84c
--- /dev/null
+++ b/docs/en/formats/asz-view-example.yaml
@@ -0,0 +1,734 @@
+format: asz.view
+version: "1.0"
+conversation: 00000001-0000-4000-8000-000000000001
+sessions:
+ - 00000001-0000-4000-8000-000000000001
+head:
+ round: 1
+ digest: 3ad0dcd4cd53fa06c502a2649b8788bfbe0a382bbc72d7f1f0fe68d9a18e96e2
+parser: v1
+policy: v1+idle=10m0s
+summary:
+ title: build and check
+ state: verified
+ problems: []
+ talks: 3
+ steps: 21
+ streams: 2
+ segments: 1
+ rounds: 1
+ unresolved: 0
+ from: 1767225600000
+ to: 1767225611100
+ kinds:
+ agent.call: 1
+ agent.launch_ack: 1
+ agent.output: 1
+ context.injection: 1
+ epoch: 3
+ epoch.boundary: 1
+ epoch.summary: 1
+ llm.call: 6
+ message.assistant: 3
+ message.external: 1
+ message.synthetic: 1
+ run: 4
+ runtime.notification: 1
+ segment: 1
+ session: 1
+ stream: 2
+ talk: 3
+ thinking: 1
+ tool: 2
+ relation_types:
+ ends_with: 1
+ follows: 1
+ in_segment: 3
+ reports: 1
+ starts: 1
+ summarizes: 1
+ quality:
+ exact_unique: 7
+ strong_inference: 1
+rounds:
+ - round: 1
+ digest: 3ad0dcd4cd53fa06c502a2649b8788bfbe0a382bbc72d7f1f0fe68d9a18e96e2
+ previous: null
+ from_seq: 1
+ through_seq: 3
+ input_digest: 6872d48ef5d3e736d0bd9f5bc03844653fb304b4b98b9ef6a27342478d950c1b
+ from_time: 1767225600000
+ through_time: 1767225611100
+ verified: true
+files:
+ - file: 00000001-0000-4000-8000-000000000001/streams/main/transcript-20260101T000000.000000000Z-000001.sd
+ format: sd
+ kind: transcript
+ seq: 1
+ round: null
+ stream: main
+ run: null
+ lines: 18
+ bytes: 5868
+ digest: 944ed3d1209c76afb98f0622eb9239f95adb79fd0d0262d634c2f20bc041b1f7
+ from_time: 1767225600000
+ through_time: 1767225611100
+ - file: 00000001-0000-4000-8000-000000000001/streams/a0a10ef0666c4dc7e/transcript-20260101T000000.000000000Z-000002.sd
+ format: sd
+ kind: transcript
+ seq: 2
+ round: null
+ stream: a0a10ef0666c4dc7e
+ run: null
+ lines: 4
+ bytes: 995
+ digest: 39b8a446c3a87d2b8da5210bba7f66ee2fd04caccea1fa0a4efdc3345b7ff7ca
+ from_time: 1767225606100
+ through_time: 1767225607100
+ - file: 00000001-0000-4000-8000-000000000001/streams/a0a10ef0666c4dc7e/meta-20260101T000000.000000000Z-000003.sd
+ format: sd
+ kind: agent_meta
+ seq: 3
+ round: null
+ stream: a0a10ef0666c4dc7e
+ run: null
+ lines: 3
+ bytes: 676
+ digest: de085eda31cdf99231fb212cb8fefbbcf95bd4f88c007c70446ecc652db3b005
+ from_time: null
+ through_time: null
+ - file: _conversations/00000001-0000-4000-8000-000000000001/rounds/r000001-3ad0dcd4cd53.sf
+ format: sf
+ kind: round
+ seq: null
+ round: 1
+ stream: null
+ run: null
+ lines: 45
+ bytes: 10468
+ digest: 4f33cf8e85afcf104c823ea12be797059b4d3d10abbbb2ceb5916c2bd7e81d73
+ from_time: 1767225600000
+ through_time: 1767225611100
+streams:
+ - id: stream/main
+ name: main
+ role: main
+ label: ""
+ parent: ""
+ records: 16
+ steps: 16
+ talk: talk/main/s1-cycle
+ named_by: ""
+ opened_by: []
+ - id: stream/a0a10ef0666c4dc7e
+ name: a0a10ef0666c4dc7e
+ role: child
+ label: checker
+ parent: main
+ records: 3
+ steps: 3
+ talk: talk/a0a10ef0666c4dc7e
+ named_by: ""
+ opened_by:
+ - step: tool/s5-tool
+ stream: main
+ talk: talk/main/s1-cycle
+ quality: exact_unique
+segments:
+ - id: segment/at_1_2
+ state: open
+ committable: false
+ talks: 3
+ from: 1767225600000
+ to: 1767225610700
+talks:
+ - id: talk/main/s1-cycle
+ kind: talk
+ parent: epoch/main/0
+ stream: main
+ at: 1767225600000
+ ref:
+ seq: 1
+ row: 2
+ attrs:
+ loops: 2
+ runs: 2
+ trigger: external
+ label: run the build
+ reply: Build passed and tests are green.
+ runs: 2
+ steps: 16
+ tools: 3
+ from: 1767225600000
+ to: 1767225610100
+ segment: segment/at_1_2
+ children:
+ - id: run/talk_main_s1-cycle/s1-cycle
+ kind: run
+ parent: talk/main/s1-cycle
+ stream: main
+ at: 1767225600000
+ ref:
+ seq: 1
+ row: 2
+ attrs:
+ trigger: external
+ children:
+ - id: input/1/2
+ kind: message.external
+ parent: run/talk_main_s1-cycle/s1-cycle
+ stream: main
+ at: 1767225600000
+ ref:
+ seq: 1
+ row: 2
+ text: run the build
+ state: available
+ bytes: 13
+ flags:
+ - external_input
+ - id: inject/1/3
+ kind: context.injection
+ parent: run/talk_main_s1-cycle/s1-cycle
+ stream: main
+ at: 1767225600100
+ ref:
+ seq: 1
+ row: 3
+ text: 'skills: 1'
+ state: available
+ bytes: 9
+ flags:
+ - injected
+ - id: call/s3-call
+ kind: llm.call
+ parent: run/talk_main_s1-cycle/s1-cycle
+ stream: main
+ at: 1767225601000
+ ref:
+ seq: 1
+ row: 4
+ refs:
+ - seq: 1
+ row: 4
+ - seq: 1
+ row: 5
+ - seq: 1
+ row: 6
+ attrs:
+ fragments: 3
+ usage: observed_replayable
+ usage_at:
+ row: 6
+ seq: 1
+ usage_from: last_fragment_in_line_order
+ usage:
+ in: 2
+ out: 50
+ cache_read: 900
+ cache_write: 100
+ children:
+ - id: think/1/4:0
+ kind: thinking
+ parent: call/s3-call
+ stream: main
+ at: 1767225601000
+ ref:
+ seq: 1
+ row: 4
+ block: 0
+ state: unavailable
+ dropped:
+ - what: reasoning signature
+ bytes: 3
+ why: a provider verifies it; a reader cannot read it
+ - id: msg/1/5:0
+ kind: message.assistant
+ parent: call/s3-call
+ stream: main
+ at: 1767225601100
+ ref:
+ seq: 1
+ row: 5
+ block: 0
+ text: Building now.
+ state: available
+ bytes: 13
+ - id: tool/tool-run-make-build
+ kind: tool
+ parent: call/s3-call
+ stream: main
+ at: 1767225601200
+ ref:
+ seq: 1
+ row: 6
+ block: 0
+ refs:
+ - seq: 1
+ row: 6
+ block: 0
+ - seq: 1
+ row: 7
+ block: 0
+ attrs:
+ name: Bash
+ result: available
+ result_join: exact_unique
+ timing: unavailable
+ text: '{"command":"make build","description":"build the project"}'
+ state: available
+ bytes: 58
+ flags:
+ - finished
+ name: Bash
+ result: build succeeded
+ result_state: available
+ result_bytes: 15
+ request_to_result_ms: 800
+ request_to_result_join: exact_unique
+ - id: call/s4-call
+ kind: llm.call
+ parent: run/talk_main_s1-cycle/s1-cycle
+ stream: main
+ at: 1767225603000
+ ref:
+ seq: 1
+ row: 8
+ refs:
+ - seq: 1
+ row: 8
+ attrs:
+ fragments: 1
+ usage: observed_replayable
+ usage_at:
+ row: 8
+ seq: 1
+ usage_from: last_fragment_in_line_order
+ usage:
+ in: 2
+ out: 10
+ cache_read: 900
+ cache_write: 100
+ children:
+ - id: tool/srvtool-websearch
+ kind: tool
+ parent: call/s4-call
+ stream: main
+ at: 1767225603000
+ ref:
+ seq: 1
+ row: 8
+ block: 0
+ refs:
+ - seq: 1
+ row: 8
+ block: 0
+ - seq: 1
+ row: 9
+ block: 0
+ attrs:
+ name: WebSearch
+ result: available
+ result_join: exact_unique
+ timing: unavailable
+ text: '{"query":"go build cache"}'
+ state: available
+ bytes: 26
+ flags:
+ - finished
+ name: WebSearch
+ result: search results
+ result_state: available
+ result_bytes: 14
+ request_to_result_ms: 1000
+ request_to_result_join: exact_unique
+ - id: call/s5-call
+ kind: llm.call
+ parent: run/talk_main_s1-cycle/s1-cycle
+ stream: main
+ at: 1767225605000
+ ref:
+ seq: 1
+ row: 10
+ refs:
+ - seq: 1
+ row: 10
+ attrs:
+ fragments: 1
+ usage: observed_replayable
+ usage_at:
+ row: 10
+ seq: 1
+ usage_from: last_fragment_in_line_order
+ usage:
+ in: 2
+ out: 20
+ cache_read: 900
+ cache_write: 100
+ children:
+ - id: tool/s5-tool
+ kind: agent.call
+ parent: call/s5-call
+ stream: main
+ at: 1767225605000
+ ref:
+ seq: 1
+ row: 10
+ block: 0
+ refs:
+ - seq: 1
+ row: 10
+ block: 0
+ - seq: 1
+ row: 11
+ block: 0
+ attrs:
+ name: Agent
+ result: available
+ result_join: exact_unique
+ timing: unavailable
+ text: '{"description":"checker","prompt":"check the tests"}'
+ state: available
+ bytes: 52
+ flags:
+ - finished
+ name: Agent
+ result: launched
+ result_state: available
+ result_bytes: 8
+ request_to_result_ms: 100
+ request_to_result_join: exact_unique
+ edges:
+ - type: starts
+ other: stream/a0a10ef0666c4dc7e
+ dir: out
+ quality: exact_unique
+ via: parent tool result
+ - id: ack/1/11
+ kind: agent.launch_ack
+ parent: run/talk_main_s1-cycle/s1-cycle
+ stream: main
+ at: 1767225605100
+ ref:
+ seq: 1
+ row: 11
+ text: launched
+ state: available
+ bytes: 8
+ flags:
+ - launch_ack
+ - id: run/talk_main_s1-cycle/s5-cycle-notification
+ kind: run
+ parent: talk/main/s1-cycle
+ stream: main
+ at: 1767225608100
+ ref:
+ seq: 1
+ row: 12
+ attrs:
+ trigger: notification
+ children:
+ - id: notify/1/12
+ kind: runtime.notification
+ parent: run/talk_main_s1-cycle/s5-cycle-notification
+ stream: main
+ at: 1767225608100
+ ref:
+ seq: 1
+ row: 12
+ text: |-
+ <task-notification>
+ <task-id>a0a10ef0666c4dc7e</task-id>
+ <tool-use-id>s5-tool</tool-use-id>
+ <status>completed</status>
+ </task-notification>
+ state: available
+ bytes: 139
+ edges:
+ - type: reports
+ other: stream/a0a10ef0666c4dc7e
+ dir: out
+ quality: exact_unique
+ via: task id on the notification
+ - id: call/s6-call
+ kind: llm.call
+ parent: run/talk_main_s1-cycle/s5-cycle-notification
+ stream: main
+ at: 1767225609100
+ ref:
+ seq: 1
+ row: 13
+ refs:
+ - seq: 1
+ row: 13
+ attrs:
+ fragments: 1
+ usage: observed_replayable
+ usage_at:
+ row: 13
+ seq: 1
+ usage_from: last_fragment_in_line_order
+ usage:
+ in: 2
+ out: 30
+ cache_read: 900
+ cache_write: 100
+ children:
+ - id: msg/1/13:0
+ kind: message.assistant
+ parent: call/s6-call
+ stream: main
+ at: 1767225609100
+ ref:
+ seq: 1
+ row: 13
+ block: 0
+ text: Build passed and tests are green.
+ state: available
+ bytes: 33
+ flags:
+ - finished
+ - id: call/s7-synthetic-call
+ kind: llm.call
+ parent: run/talk_main_s1-cycle/s5-cycle-notification
+ stream: main
+ at: 1767225610100
+ ref:
+ seq: 1
+ row: 14
+ refs:
+ - seq: 1
+ row: 14
+ attrs:
+ fragments: 1
+ stop_reason: unavailable
+ usage: unavailable
+ usage_from: last_fragment_in_line_order
+ children:
+ - id: msg/1/14:0
+ kind: message.synthetic
+ parent: call/s7-synthetic-call
+ stream: main
+ at: 1767225610100
+ ref:
+ seq: 1
+ row: 14
+ block: 0
+ text: 'API Error: Connection lost mid-response.'
+ state: available
+ bytes: 40
+ flags:
+ - synthetic
+ edges:
+ - type: in_segment
+ other: segment/at_1_2
+ dir: out
+ quality: exact_unique
+ via: activity window
+ - id: talk/main/s8-cycle-compact
+ kind: talk
+ parent: epoch/main/s8-boundary
+ stream: main
+ at: 1767225610700
+ ref:
+ seq: 1
+ row: 16
+ attrs:
+ loops: 1
+ runs: 1
+ trigger: external
+ runs: 1
+ from: 1767225610700
+ to: 1767225610700
+ segment: segment/at_1_2
+ children:
+ - id: run/talk_main_s8-cycle-compact/s8-cycle-compact
+ kind: run
+ parent: talk/main/s8-cycle-compact
+ stream: main
+ at: 1767225610700
+ ref:
+ seq: 1
+ row: 16
+ attrs:
+ trigger: external
+ edges:
+ - type: in_segment
+ other: segment/at_1_2
+ dir: out
+ quality: exact_unique
+ via: activity window
+ - id: talk/a0a10ef0666c4dc7e
+ kind: talk
+ parent: epoch/a0a10ef0666c4dc7e/0
+ stream: a0a10ef0666c4dc7e
+ at: 1767225606100
+ ref:
+ seq: 2
+ row: 1
+ attrs:
+ loops: 1
+ runs: 1
+ trigger: unknown
+ reply: Tests pass.
+ runs: 1
+ steps: 3
+ from: 1767225606100
+ to: 1767225607100
+ child: true
+ segment: segment/at_1_2
+ children:
+ - id: run/talk_a0a10ef0666c4dc7e/a0a10ef0666c4dc7e-cycle
+ kind: run
+ parent: talk/a0a10ef0666c4dc7e
+ stream: a0a10ef0666c4dc7e
+ at: 1767225606100
+ ref:
+ seq: 2
+ row: 1
+ attrs:
+ trigger: external
+ children:
+ - id: call/checker-s1-call
+ kind: llm.call
+ parent: run/talk_a0a10ef0666c4dc7e/a0a10ef0666c4dc7e-cycle
+ stream: a0a10ef0666c4dc7e
+ at: 1767225607100
+ ref:
+ seq: 2
+ row: 2
+ refs:
+ - seq: 2
+ row: 2
+ attrs:
+ fragments: 1
+ usage: observed_replayable
+ usage_at:
+ row: 2
+ seq: 2
+ usage_from: last_fragment_in_line_order
+ usage:
+ in: 2
+ out: 42
+ cache_read: 900
+ cache_write: 100
+ children:
+ - id: msg/2/2:0
+ kind: message.assistant
+ parent: call/checker-s1-call
+ stream: a0a10ef0666c4dc7e
+ at: 1767225607100
+ ref:
+ seq: 2
+ row: 2
+ block: 0
+ text: Tests pass.
+ state: available
+ bytes: 11
+ flags:
+ - finished
+ - id: output/a0a10ef0666c4dc7e
+ kind: agent.output
+ parent: run/talk_a0a10ef0666c4dc7e/a0a10ef0666c4dc7e-cycle
+ stream: a0a10ef0666c4dc7e
+ at: 1767225607100
+ ref:
+ seq: 2
+ row: 2
+ refs:
+ - seq: 2
+ row: 2
+ attrs:
+ returned_value: unavailable
+ text: Tests pass.
+ state: available
+ bytes: 11
+ flags:
+ - finished
+ edges:
+ - type: ends_with
+ other: stream/a0a10ef0666c4dc7e
+ dir: in
+ quality: exact_unique
+ via: the last response in the child stream, and what it returned
+ edges:
+ - type: in_segment
+ other: segment/at_1_2
+ dir: out
+ quality: strong_inference
+ via: inside the window of the talk that delegated it
+relations:
+ - id: rel/ends_with/stream_a0a10ef0666c4dc7e/output_a0a10ef0666c4dc7e
+ type: ends_with
+ from: stream/a0a10ef0666c4dc7e
+ to: output/a0a10ef0666c4dc7e
+ quality: exact_unique
+ via: the last response in the child stream, and what it returned
+ evidence:
+ - seq: 2
+ row: 2
+ - id: rel/follows/epoch_main_s8-boundary/epoch_main_0
+ type: follows
+ from: epoch/main/s8-boundary
+ to: epoch/main/0
+ quality: exact_unique
+ via: explicit context reset
+ evidence:
+ - seq: 1
+ row: 15
+ - id: rel/in_segment/talk_a0a10ef0666c4dc7e/segment_at_1_2
+ type: in_segment
+ from: talk/a0a10ef0666c4dc7e
+ to: segment/at_1_2
+ quality: strong_inference
+ via: inside the window of the talk that delegated it
+ evidence:
+ - seq: 2
+ row: 1
+ - id: rel/in_segment/talk_main_s1-cycle/segment_at_1_2
+ type: in_segment
+ from: talk/main/s1-cycle
+ to: segment/at_1_2
+ quality: exact_unique
+ via: activity window
+ evidence:
+ - seq: 1
+ row: 2
+ - id: rel/in_segment/talk_main_s8-cycle-compact/segment_at_1_2
+ type: in_segment
+ from: talk/main/s8-cycle-compact
+ to: segment/at_1_2
+ quality: exact_unique
+ via: activity window
+ evidence:
+ - seq: 1
+ row: 16
+ - id: rel/reports/notify_1_12/stream_a0a10ef0666c4dc7e
+ type: reports
+ from: notify/1/12
+ to: stream/a0a10ef0666c4dc7e
+ quality: exact_unique
+ via: task id on the notification
+ evidence:
+ - seq: 1
+ row: 12
+ - id: rel/starts/tool_s5-tool/stream_a0a10ef0666c4dc7e
+ type: starts
+ from: tool/s5-tool
+ to: stream/a0a10ef0666c4dc7e
+ quality: exact_unique
+ via: parent tool result
+ evidence:
+ - seq: 1
+ row: 11
+ - id: rel/summarizes/summary_1_16/boundary_1_15
+ type: summarizes
+ from: summary/1/16
+ to: boundary/1/15
+ quality: exact_unique
+ via: containment parent
+ evidence:
+ - seq: 1
+ row: 16
+unresolved: []
+
diff --git a/docs/en/formats/asz-view.md b/docs/en/formats/asz-view.md
index 0cb7d95..512874b 100644
--- a/docs/en/formats/asz-view.md
+++ b/docs/en/formats/asz-view.md
@@ -1,16 +1,25 @@
# asz.view
`asz.view` is one conversation, rebuilt from its Session Flow and its Session Data, as one document:
-everything a viewer renders, and nothing a viewer must compute. Every talk is a tree of runs and
+everything a viewer renders, and nothing a viewer must compute. It is the final form of a
+conversation, the one a page draws and the one a person reads. Every talk is a tree of runs and
steps with the text the referenced records carry, and the streams, the segments, the relations,
the rounds and the files sit beside them, each verified. The evidence is inside it once, so a
viewer never opens a `.sd` file; the `ref` on a node is a citation, not a pointer to fetch.
-It is never a file. `asz view` builds it in memory from the `.sf` rounds and the `.sd` files, once
-per fold, and serves it as one response at `/api/c/{id}/view`. A server that holds the same files,
-such as the SkyWalking OAP, builds the same document and answers a conversation query with it.
-Package `pkg/sessionview` defines and owns the shape. Every reader shares it, and a change to it is
-a change to the version: a 1.x adds keys and never removes or renames one; a 2.0 may do either.
+It is never a file that the project writes. Package `pkg/sessionview` defines and owns the shape,
+and three things produce it from the same code:
+
+| How | What you get |
+| --- | --- |
+| `asz conversation -json ID` | the document on standard output, indented |
+| `asz conversation -yaml ID` | the same document rendered as YAML, with the same keys in the same order |
+| `asz view`, at `/api/c/{id}/view` | the document as the page's own response, built once per fold |
+
+A server that holds the same `.sd` and `.sf` files, such as the SkyWalking OAP, builds the same
+document and answers a conversation query with it. Every reader shares the shape, and a change to
+it is a change to the version: a 1.x adds keys and never removes or renames one; a 2.0 may do
+either.
The document is JSON. Keys are `snake_case`, as in the two source formats, and are written in the
order this page lists them. Times are unix milliseconds, read from the `.sd` record a node
@@ -18,6 +27,12 @@ references; a view is read and never digested, so it carries no RFC 3339 strings
round over the same files gives the same document, so one built by `asz view` and one built by
another server compare equal as documents.
+**A complete example.** [asz-view-example.yaml](asz-view-example.yaml) is the whole document for
+the fixture session of the format pages, three talks across a main stream and a child agent, a
+tool, a synthetic error and a context reset, exactly as `asz conversation -yaml` prints it. It is
+generated from the scenario `tests/scenarios/fixture.yaml` by `make asz-view-example`, and a test
+fails when the committed file no longer matches what the code produces.
+
## Top level
```json
@@ -98,10 +113,36 @@ instead of a document.
Keys a node has no value for are absent, not null. Nothing in a document is inferred beyond what
the fold and the records say. Where the fold says `unavailable`, the document says it too.
+## Rendering the whole conversation
+
+The document is complete: a viewer draws every view of a conversation from it and fetches
+nothing else. This is how each view reads it.
+
+| View | Read |
+| --- | --- |
+| **Transcript** | `talks`, in order. Each talk's `label` is the person's input and its `reply` the last assistant message; its `children` are the runs, a run's children the steps, and a call's children what it produced: thinking, messages, tools. `text` is what to show for a step, `name` and `result` for a tool, `usage` on a call. |
+| **Flow timeline** | every node of every tree by `at`, with `kind` and `stream`; a node with `at` of `0` was never observed at a time and is placed by its position. |
+| **Cross-stream flow** | `edges` on a node, and `relations` as the whole list: `starts` from an agent call to the child's `stream`, `reports` from the notification that resumed the parent, `ends_with` from a stream to the child's output, `follows` between epochs across a reset, `summarizes` from a summary to its boundary, `in_segment` from a talk to its window. Containment never crosses a stream; a child's work is under the child. |
+| **Streams and segments** | `streams` with `role`, `label`, `parent` and `opened_by`, the step that started each; `segments` with the span of the talks placed in them. |
+| **Evidence** | a node's `ref` and `refs`, `{seq, row, block}`, name the record and the part it stands on. The text is already on the node, clipped to 2,000 bytes with the full size in `bytes`; only a reader that wants the whole of a longer part goes to the record, by that address, in `files`. |
+| **Verification** | `summary.state` and `summary.problems`, every round's `verified`, and every file's `digest`, `lines` and `bytes`. A gap or a failed digest is content here, never an error in place of the document. |
+| **Counts and time** | `summary`: the counts a list shows, the session's `from` and `to`, and the fold sized by `kinds`, `relation_types` and `quality`. |
+
+Nothing in a document is inferred beyond what the fold and the records say. Where the fold says
+`unavailable`, the document says it too, and a viewer shows that word rather than a guess.
+
+## The YAML rendering
+
+`-yaml` is a rendering of the JSON, not a second format. It is produced from the JSON, so the keys
+are the same and in the same order; mappings are blocks; scalars are plain, and quoted only where
+YAML would otherwise misread them, so `version` is `"1.0"` and a title with a colon is quoted; an
+empty map is `{}`; a text with line breaks is a block scalar. Reading the YAML back gives the same
+values as the JSON.
+
## Reading it
`asz view` serves the document at `/api/c/{id}/view` and builds it once per fold, so a second reader
pays nothing until a new round arrives. `asz conversation -json ID` prints the same document to
-standard output, and `-yaml` prints it as YAML with the same keys in the same order, for a
-terminal or a diff; the page stays the page. The largest conversation measured, 357 talks and 16,121
-steps, is 19 MB as one document and was built in 0.7 s.
+standard output, and `-yaml` prints it as YAML, for a terminal or a diff; the page stays the page.
+The largest conversation measured, 357 talks and 16,121 steps, is 19 MB as one document and was
+built in 0.7 s.
diff --git a/tests/docs_test.go b/tests/docs_test.go
new file mode 100644
index 0000000..8124e7d
--- /dev/null
+++ b/tests/docs_test.go
@@ -0,0 +1,78 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package tests_test
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+ "time"
+
+ "github.com/apache/skywalking-ai-sessionizer/internal/parse"
+ "github.com/apache/skywalking-ai-sessionizer/internal/scenario"
+ "github.com/apache/skywalking-ai-sessionizer/internal/storage"
+ "github.com/apache/skywalking-ai-sessionizer/internal/view"
+ "github.com/apache/skywalking-ai-sessionizer/pkg/sessionview"
+)
+
+// TestDocumentExampleIsCurrent regenerates the complete asz.view example
+// on the format page from the fixture scenario, the way make
+// asz-view-example does, and fails when the committed file differs: a
+// change to the document's shape must show in the docs in the same change.
+func TestDocumentExampleIsCurrent(t *testing.T) {
+ sc, err := scenario.Load("scenarios/fixture.yaml")
+ if err != nil {
+ t.Fatal(err)
+ }
+ out := t.TempDir()
+ b, err := scenario.Build(sc, scenario.FormatSD, out, scenario.Options{At: time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)})
+ if err != nil {
+ t.Fatal(err)
+ }
+ z := storage.NewZone(out)
+ for {
+ r, err := parse.Session(z, parse.Options{Conversation: b.Session, Session: b.Session})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if !r.Changed() || !r.More {
+ break
+ }
+ }
+ c, err := view.New(z, nil).Load(b.Session)
+ if err != nil {
+ t.Fatal(err)
+ }
+ doc, err := c.Build()
+ if err != nil {
+ t.Fatal(err)
+ }
+ got, err := sessionview.MarshalYAML(doc)
+ if err != nil {
+ t.Fatal(err)
+ }
+ got = append(got, '\n')
+ path := filepath.Join("..", "docs", "en", "formats", "asz-view-example.yaml")
+ want, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(got) != string(want) {
+ t.Fatalf("%s is not what the fixture scenario produces now; run make asz-view-example and commit the result", path)
+ }
+}