wizardflow 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {wizardflow-0.1.0 → wizardflow-0.2.0}/.gitignore +5 -3
- {wizardflow-0.1.0 → wizardflow-0.2.0}/CONTRIBUTING.md +7 -6
- {wizardflow-0.1.0 → wizardflow-0.2.0}/PKG-INFO +147 -61
- {wizardflow-0.1.0 → wizardflow-0.2.0}/README.md +146 -60
- {wizardflow-0.1.0 → wizardflow-0.2.0}/examples/multibranch.html +16 -16
- {wizardflow-0.1.0 → wizardflow-0.2.0}/examples/multibranch.md +15 -15
- {wizardflow-0.1.0 → wizardflow-0.2.0}/examples/quickstart.html +10 -10
- {wizardflow-0.1.0 → wizardflow-0.2.0}/examples/quickstart.md +9 -9
- {wizardflow-0.1.0 → wizardflow-0.2.0}/examples/quickstart.py +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/pyproject.toml +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/__init__.py +4 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/404.html +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/__next.__PAGE__.txt +2 -2
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/__next._full.txt +2 -2
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/__next._head.txt +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/__next._index.txt +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/__next._tree.txt +1 -1
- wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0vq8h3vx9uqg5.js → wizardflow-0.2.0/src/wizardflow/_ui/_next/static/chunks/4158u-399exxn.js +4 -3
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_not-found/__next._full.txt +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_not-found/__next._head.txt +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_not-found/__next._index.txt +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_not-found/__next._not-found/__PAGE__.txt +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_not-found/__next._not-found.txt +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_not-found/__next._tree.txt +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_not-found.html +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_not-found.txt +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/index.html +2 -2
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/index.txt +2 -2
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/sitemap.xml +1 -1
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/cli.py +70 -16
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/client.py +108 -66
- wizardflow-0.2.0/src/wizardflow/constants.py +55 -0
- wizardflow-0.2.0/src/wizardflow/reader.py +134 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/tests/test_cli.py +69 -15
- {wizardflow-0.1.0 → wizardflow-0.2.0}/tests/test_html.py +13 -4
- {wizardflow-0.1.0 → wizardflow-0.2.0}/tests/test_markdown.py +13 -4
- wizardflow-0.2.0/tests/test_reader.py +154 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/tests/test_trace.py +81 -32
- wizardflow-0.1.0/src/wizardflow/constants.py +0 -44
- {wizardflow-0.1.0 → wizardflow-0.2.0}/LICENSE +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/assets/demo.gif +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/examples/data_types.py +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/examples/multibranch.py +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/scripts/build_ui.py +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_render.py +0 -0
- {wizardflow-0.1.0/src/wizardflow/_ui/_next/static/oQsmH12Xmzb6Zw4-R3ghe → wizardflow-0.2.0/src/wizardflow/_ui/_next/static/F7xCRRyix77IKo6qy-ltF}/_buildManifest.js +0 -0
- {wizardflow-0.1.0/src/wizardflow/_ui/_next/static/oQsmH12Xmzb6Zw4-R3ghe → wizardflow-0.2.0/src/wizardflow/_ui/_next/static/F7xCRRyix77IKo6qy-ltF}/_clientMiddlewareManifest.js +0 -0
- {wizardflow-0.1.0/src/wizardflow/_ui/_next/static/oQsmH12Xmzb6Zw4-R3ghe → wizardflow-0.2.0/src/wizardflow/_ui/_next/static/F7xCRRyix77IKo6qy-ltF}/_ssgManifest.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/05-c3ty_6dwfk.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/09s72r58ijxwx.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/0cz1d0mv5g_q7.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/0mz_v1wicwnmn.css +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/0u1x9l49cgb30.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/0ux_7aev0a2kt.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/142r2alz4_x6t.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/14mrh2-p_w84d.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/18clj6xsyhc_j.css +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/2-j1b74l3lxxu.css +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/27jktro2p5rq9.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/2gj5bmeov-r8f.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/310vm2bl3xxpt.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/3n7dm2ojtyzwn.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/chunks/turbopack-2fblmukzx7kws.js +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/4fa387ec64143e14-s.2tuy5pz7dlieh.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/53b9e256198e5412-s.390ncx5urfkfu.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/5ce348bf30bf5439-s.31988l_ccedte.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/6306c77e7c8268e4-s.2dbetqa9o8jxf.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/7178b3e590c64307-s.21jp631_3pja2.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/7d817b4c03b0c5f1-s.1uyisp29ctx0d.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/8a480f0b521d4e75-s.1qq4vpdcun5oj.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/bbc41e54d2fcbd21-s.1rgnod-3esatf.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/fef07dbb0973bf53-s.3p2_lha1f2xer.woff2 +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/_next/static/media/icon.3okpzkln1vq00.svg +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/icon.svg +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/opengraph-image +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/_ui/robots.txt +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/html.py +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/markdown.py +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/src/wizardflow/py.typed +0 -0
- {wizardflow-0.1.0 → wizardflow-0.2.0}/tests/conftest.py +0 -0
|
@@ -10,7 +10,9 @@ venv/
|
|
|
10
10
|
# uv resolves this in CI (uv run / uv build); a zero-dependency library doesn't
|
|
11
11
|
# ship a lockfile, and tracking it would only sweep it into the sdist.
|
|
12
12
|
uv.lock
|
|
13
|
-
# Generated traces from the example scripts (e.g. examples/
|
|
14
|
-
# regenerated on each run, not tracked. Scoped to examples/ so it never
|
|
15
|
-
# the embedded UI bundle (src/wizardflow/_ui/), which must ship in the
|
|
13
|
+
# Generated traces from the example scripts (e.g. examples/quickstart__*.jsonl)
|
|
14
|
+
# — regenerated on each run, not tracked. Scoped to examples/ so it never
|
|
15
|
+
# touches the embedded UI bundle (src/wizardflow/_ui/), which must ship in the
|
|
16
|
+
# package. (*.json covers traces from pre-JSONL SDK versions.)
|
|
16
17
|
/examples/*.json
|
|
18
|
+
/examples/*.jsonl
|
|
@@ -23,15 +23,16 @@ pytest
|
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
The tests pin the emitted schema shape and the recording semantics (step
|
|
26
|
-
folding, completed-only persistence,
|
|
27
|
-
fast-fail, …).
|
|
26
|
+
folding, completed-only persistence, append-only durability, `id=` targeting,
|
|
27
|
+
unknown-node fast-fail, …).
|
|
28
28
|
|
|
29
29
|
## Schema contract
|
|
30
30
|
|
|
31
|
-
The
|
|
32
|
-
monorepo frontend — that TypeScript type
|
|
33
|
-
the
|
|
34
|
-
|
|
31
|
+
The JSONL this SDK serializes to is defined by `src/types/agenttrace.ts` in the
|
|
32
|
+
monorepo frontend — that TypeScript type (including the `header` / `message` /
|
|
33
|
+
`seal` record types) is the schema of record. Change it and the serializer
|
|
34
|
+
(`src/wizardflow/client.py`) plus the reader (`src/wizardflow/reader.py`) and
|
|
35
|
+
their tests must change in lockstep; they must not drift.
|
|
35
36
|
|
|
36
37
|
## Refreshing the bundled UI
|
|
37
38
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wizardflow
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Summary: Python SDK for recording agent flows into the WizardFlow / AgentTrace file format.
|
|
5
5
|
Project-URL: Homepage, https://getwizardflow.com
|
|
6
6
|
Project-URL: Documentation, https://getwizardflow.com
|
|
@@ -50,15 +50,41 @@ Description-Content-Type: text/markdown
|
|
|
50
50
|
**A lightweight tracer for Python agents.** Drop three calls into your code —
|
|
51
51
|
`init`, `log`, `end_message` — and turn a messy multi-agent run into a portable
|
|
52
52
|
trace you can replay as an interactive graph or export to Markdown/HTML. Because
|
|
53
|
-
the trace is just a
|
|
53
|
+
the trace is just a JSONL file, anyone can replay it: hand it to a teammate or PM
|
|
54
54
|
and they drop it into **[getwizardflow.com](https://getwizardflow.com)** in the
|
|
55
55
|
browser — no Python, no install. Lightweight by design: pure Python, **zero
|
|
56
56
|
runtime dependencies**, no daemon, no setup.
|
|
57
57
|
|
|
58
58
|

|
|
59
59
|
|
|
60
|
-
The
|
|
61
|
-
|
|
60
|
+
The file it produces is JSON Lines with a small, documented schema: line 1 is a
|
|
61
|
+
`header` record carrying the `graph { nodes, edges }`, then one `message`
|
|
62
|
+
record per line (`steps[] → payloads[] { label, value }`). Every line is plain
|
|
63
|
+
JSON — `head -1 run.jsonl | jq .graph` just works.
|
|
64
|
+
|
|
65
|
+
## Why WizardFlow?
|
|
66
|
+
|
|
67
|
+
Most agent tracing means an observability platform (LangSmith, Langfuse, …): a
|
|
68
|
+
server or SaaS account, an ingestion pipeline, auto-instrumentation, dashboards
|
|
69
|
+
behind a login. WizardFlow takes the opposite bet:
|
|
70
|
+
|
|
71
|
+
- **The trace is a file.** No server, no account, no daemon — your run becomes
|
|
72
|
+
a `.jsonl` you can commit, diff, grep, or attach to a bug report.
|
|
73
|
+
- **Anyone can replay it.** Hand the file to a teammate or PM and they drop it
|
|
74
|
+
into [getwizardflow.com](https://getwizardflow.com) — no Python, no install,
|
|
75
|
+
and nothing is uploaded (the viewer is fully client-side).
|
|
76
|
+
- **Three calls, zero dependencies.** The whole API is `init`, `log`,
|
|
77
|
+
`end_message` — pure Python that pulls nothing into your environment, and no
|
|
78
|
+
framework required (LangGraph support just calls `app.get_graph()` on
|
|
79
|
+
whatever you pass — langgraph itself is never imported).
|
|
80
|
+
- **Explicit by design.** You place every `log()` call yourself, so a trace
|
|
81
|
+
contains exactly what you logged — nothing else. No auto-instrumentation
|
|
82
|
+
capturing things behind your back, no surprise PII in the payloads, no
|
|
83
|
+
guessing why a span exists.
|
|
84
|
+
|
|
85
|
+
If you need fleet-wide monitoring, token-cost dashboards, or eval pipelines,
|
|
86
|
+
use an observability platform. WizardFlow is for **understanding one run** —
|
|
87
|
+
and being able to hand that run to anybody.
|
|
62
88
|
|
|
63
89
|
## Install
|
|
64
90
|
|
|
@@ -82,7 +108,8 @@ wizardflow.init(
|
|
|
82
108
|
edges=[("user_input", "router"), ("router", "planner")],
|
|
83
109
|
)
|
|
84
110
|
|
|
85
|
-
# Every log names its message in the first argument
|
|
111
|
+
# Every log names its message in the first argument:
|
|
112
|
+
# log(message_id, node, payload_label, payload_value)
|
|
86
113
|
wizardflow.log("msg-1", "router", "llm_input", prompt) # same node, two payloads ->
|
|
87
114
|
wizardflow.log("msg-1", "router", "llm_output", output) # folded into one step
|
|
88
115
|
wizardflow.log("msg-1", "tool_node") # visited, no payloads
|
|
@@ -91,14 +118,17 @@ wizardflow.end_message("msg-1") # -> writes the trace
|
|
|
91
118
|
|
|
92
119
|
There is **no `save()`** and no autosave: `log()` only accumulates in memory,
|
|
93
120
|
and `end_message(id)` is the one call that writes the trace. Writes happen at
|
|
94
|
-
message boundaries — one
|
|
95
|
-
it contains
|
|
96
|
-
|
|
121
|
+
message boundaries — one **append** per finished message, however many `log()`
|
|
122
|
+
calls it contains, so a write costs the same no matter how large the trace has
|
|
123
|
+
grown. `init()` returns a client and also stashes it as the module default, so
|
|
124
|
+
the bare `wizardflow.log(...)` form above works.
|
|
97
125
|
|
|
98
126
|
**Concurrency-safe.** Multi-agent setups end messages from many threads/tasks at
|
|
99
|
-
once; an internal lock serializes the
|
|
100
|
-
|
|
101
|
-
the
|
|
127
|
+
once; an internal lock serializes the appends so the shared part file is never
|
|
128
|
+
corrupted and no message is lost or duplicated. Every ended message is durable
|
|
129
|
+
on disk the moment `end_message` returns — a crash can at worst tear the line
|
|
130
|
+
being appended, and readers drop a torn final line and load everything before
|
|
131
|
+
it.
|
|
102
132
|
|
|
103
133
|
## Targeting a message
|
|
104
134
|
|
|
@@ -107,6 +137,7 @@ collide — interleave them freely (as concurrent agents do) and each step route
|
|
|
107
137
|
to the right message:
|
|
108
138
|
|
|
109
139
|
```python
|
|
140
|
+
# log(message_id, node, payload_label, payload_value)
|
|
110
141
|
wizardflow.log("msg-1", "classifier", "input", text_a)
|
|
111
142
|
wizardflow.log("msg-2", "classifier", "input", text_b) # a different message
|
|
112
143
|
wizardflow.log("msg-1", "generator", "output", answer_a)
|
|
@@ -171,71 +202,96 @@ A non-LangGraph object raises `LangGraphExtractionError`; missing conditional
|
|
|
171
202
|
metadata never fails extraction (the edge is just emitted plain). Call it
|
|
172
203
|
**after `compile()`**, when the topology actually exists.
|
|
173
204
|
|
|
174
|
-
## API (v0.
|
|
205
|
+
## API (v0.2)
|
|
175
206
|
|
|
176
|
-
|
|
207
|
+
> **Breaking change in 0.2:** traces are now JSON Lines (`.jsonl`), written by
|
|
208
|
+
> appending — the old single-document `.json` format is gone from the SDK
|
|
209
|
+
> (writer *and* CLI readers). The web UI at getwizardflow.com still opens old
|
|
210
|
+
> `.json` traces.
|
|
211
|
+
|
|
212
|
+
- `init(output_dir=, file_prefix="wizardflow", name=, description=, nodes=, edges=, meta=, silent=False, max_bytes=16_000_000, max_messages=2_000) -> Client`
|
|
177
213
|
- `description` lands in `meta.description` (matches the schema field).
|
|
178
214
|
- `nodes=` enables fast-fail: `log()` to an undeclared node raises
|
|
179
215
|
`UnknownNodeError` immediately (unless silenced).
|
|
180
216
|
- `output_dir` is optional; omitted, traces are written in cwd.
|
|
181
217
|
- `file_prefix` is optional; omitted, filenames start with `wizardflow`.
|
|
182
|
-
- `max_bytes`
|
|
183
|
-
- `init_from_langgraph(app, output_dir=, file_prefix="wizardflow", name=, description=, meta=, node_colors=, silent=False, max_bytes=...) -> Client`
|
|
218
|
+
- `max_bytes` / `max_messages` cap each part file before rotation (see below).
|
|
219
|
+
- `init_from_langgraph(app, output_dir=, file_prefix="wizardflow", name=, description=, meta=, node_colors=, silent=False, max_bytes=..., max_messages=...) -> Client`
|
|
184
220
|
- same as `init`, but `nodes`/`edges` come from `app.get_graph()`.
|
|
185
221
|
- `node_colors` maps extracted node ids to CSS colors such as `"#A78BFA"`.
|
|
186
222
|
- `log(id, node, label=None, content=None)` — the first positional is the
|
|
187
223
|
**message id**, the second is the node. With `label`/`content` it records a
|
|
188
224
|
payload; bare `log(id, "node")` records a visit with no payloads. The message
|
|
189
225
|
is created on first reference; this only accumulates in memory.
|
|
190
|
-
- `end_message(id, title=None)` — finalize a message and
|
|
191
|
-
returns the current trace path. The **only** call that touches disk.
|
|
192
|
-
sets the message's human title. Idempotent.
|
|
226
|
+
- `end_message(id, title=None)` — finalize a message and append it to the
|
|
227
|
+
trace; returns the current trace path. The **only** call that touches disk.
|
|
228
|
+
Optional `title` sets the message's human title. Idempotent.
|
|
193
229
|
- `Client.current_path` — the trace file currently being written.
|
|
194
|
-
- `to_dict()` / `to_json()` — inspect the active part (completed messages)
|
|
230
|
+
- `to_dict()` / `to_json()` — inspect the active part (completed messages),
|
|
231
|
+
assembled into one `AgentTraceFile` object.
|
|
195
232
|
|
|
196
|
-
###
|
|
233
|
+
### The file format
|
|
234
|
+
|
|
235
|
+
A trace part is JSON Lines: one JSON object per line, each with a `type`:
|
|
236
|
+
|
|
237
|
+
```jsonl
|
|
238
|
+
{"type":"header","version":"0.2","name":"run","meta":{...},"graph":{"nodes":[...],"edges":[...]}}
|
|
239
|
+
{"type":"message","id":"msg-1","label":"First question","steps":[...]}
|
|
240
|
+
{"type":"message","id":"msg-2","steps":[...]}
|
|
241
|
+
{"type":"seal","nextPart":"run__...__part2.jsonl"}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
- **`header`** (always line 1) — everything about the run except the messages.
|
|
245
|
+
- **`message`** — one completed message, appended by `end_message`.
|
|
246
|
+
- **`seal`** — only on a part that rotated away; its presence means "this part
|
|
247
|
+
is complete, continue at `nextPart`". The active part has no seal.
|
|
197
248
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
249
|
+
Readers skip records with an unknown `type` (forward compat) and drop an
|
|
250
|
+
unparseable final line (a crash mid-append leaves a torn tail; everything
|
|
251
|
+
before it is intact). Only **completed** messages are written; an in-progress
|
|
201
252
|
message lives in memory until it ends.
|
|
202
253
|
|
|
254
|
+
### How saving works
|
|
255
|
+
|
|
256
|
+
`end_message` **appends one line** to the active part — O(1) no matter how
|
|
257
|
+
large the part already is, and the moment it returns, that message is durable
|
|
258
|
+
on disk. Nothing is ever rewritten.
|
|
259
|
+
|
|
203
260
|
### Rotation (no single huge file)
|
|
204
261
|
|
|
205
|
-
There's no natural "end" to a chatbot trace, so the SDK caps
|
|
206
|
-
|
|
262
|
+
There's no natural "end" to a chatbot trace, so the SDK caps part size instead.
|
|
263
|
+
The cap exists for the *reader*: a part is what you drop into the viewer, and an
|
|
264
|
+
oversized file makes the browser tab sluggish. Each run writes a timestamped
|
|
265
|
+
entry file whose name carries the run-start time:
|
|
207
266
|
|
|
208
267
|
```
|
|
209
|
-
wizardflow__2026-06-08T16-29-09-123Z.
|
|
268
|
+
wizardflow__2026-06-08T16-29-09-123Z.jsonl
|
|
210
269
|
```
|
|
211
270
|
|
|
212
271
|
The name's `wizardflow` is the `file_prefix`; the timestamp is captured when
|
|
213
|
-
`init()` creates the client. If the
|
|
214
|
-
|
|
272
|
+
`init()` creates the client. If the next message would push the active part past
|
|
273
|
+
`max_bytes` (16 MB by default) — or past `max_messages` (2,000 by default; many
|
|
274
|
+
tiny messages strain the viewer before many bytes do) — the part is sealed and
|
|
275
|
+
the message starts a fresh one:
|
|
215
276
|
|
|
216
277
|
```
|
|
217
|
-
wizardflow__2026-06-08T16-29-09-123Z.
|
|
218
|
-
wizardflow__2026-06-08T16-29-09-123Z__part2.
|
|
219
|
-
wizardflow__2026-06-08T16-29-09-123Z__part3.
|
|
278
|
+
wizardflow__2026-06-08T16-29-09-123Z.jsonl
|
|
279
|
+
wizardflow__2026-06-08T16-29-09-123Z__part2.jsonl
|
|
280
|
+
wizardflow__2026-06-08T16-29-09-123Z__part3.jsonl
|
|
220
281
|
```
|
|
221
282
|
|
|
222
283
|
Rotation only ever happens at a **message boundary**, never mid-message (a lone
|
|
223
|
-
message larger than the cap gets its own oversized part).
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
A single-part trace stays clean (no `part` metadata). There's no `partCount` —
|
|
236
|
-
the total is genuinely unknown while a continuous run is still logging; follow
|
|
237
|
-
`nextPart` to walk to the end. Since names are timestamped, read the real file
|
|
238
|
-
back from the client's `current_path` (also the path `end_message` returns).
|
|
284
|
+
message larger than the cap gets its own oversized part). `max_bytes` is
|
|
285
|
+
clamped to a hard ceiling (64 MB) — past that, parsed-object inflation makes
|
|
286
|
+
the viewer slow on ordinary hardware. Each part is **self-contained** (full
|
|
287
|
+
graph in its header + its slice of messages), chained backward via the header's
|
|
288
|
+
`meta.prevPart` and forward via the seal record's `nextPart`.
|
|
289
|
+
|
|
290
|
+
A single-part trace stays clean (no `part` metadata, no seal). There's no
|
|
291
|
+
`partCount` — the total is genuinely unknown while a continuous run is still
|
|
292
|
+
logging; follow the seal records to walk to the end. Since names are
|
|
293
|
+
timestamped, read the real file back from the client's `current_path` (also the
|
|
294
|
+
path `end_message` returns).
|
|
239
295
|
|
|
240
296
|
### Logging
|
|
241
297
|
|
|
@@ -269,26 +325,33 @@ Fuller runnable examples live in
|
|
|
269
325
|
|
|
270
326
|
## CLI
|
|
271
327
|
|
|
272
|
-
The `wizardflow` command has
|
|
273
|
-
one takes the trace file as a positional argument **or** via `--path`
|
|
274
|
-
not both):
|
|
328
|
+
The `wizardflow` command has four subcommands: `ui`, `md`, `html`, and `json`.
|
|
329
|
+
Every one takes the trace file as a positional argument **or** via `--path`
|
|
330
|
+
(pass one, not both):
|
|
275
331
|
|
|
276
332
|
```bash
|
|
277
|
-
wizardflow ui run.
|
|
278
|
-
wizardflow md run.
|
|
279
|
-
wizardflow html run.
|
|
333
|
+
wizardflow ui run.jsonl
|
|
334
|
+
wizardflow md run.jsonl
|
|
335
|
+
wizardflow html run.jsonl
|
|
336
|
+
wizardflow json run.jsonl
|
|
280
337
|
# --path is equivalent everywhere:
|
|
281
|
-
wizardflow ui --path run.
|
|
338
|
+
wizardflow ui --path run.jsonl
|
|
282
339
|
```
|
|
283
340
|
|
|
341
|
+
The SDK only ever **writes** JSONL, but these commands **read** either framing —
|
|
342
|
+
a `.jsonl` part or a single-document `.json` (what `wizardflow json` emits) — so
|
|
343
|
+
they stay at parity with the web viewer, which also accepts both.
|
|
344
|
+
|
|
284
345
|
### `wizardflow ui` — local viewer
|
|
285
346
|
|
|
286
347
|
```bash
|
|
287
|
-
wizardflow ui run.
|
|
348
|
+
wizardflow ui run.jsonl [--host 127.0.0.1] [--port 0] [--no-open]
|
|
288
349
|
```
|
|
289
350
|
|
|
290
351
|
Binds a stdlib HTTP server, serves the static WizardFlow UI bundled in the SDK
|
|
291
|
-
package, and opens the selected trace in your browser
|
|
352
|
+
package, and opens the selected trace in your browser (the JSONL is assembled
|
|
353
|
+
server-side and served to the UI as one JSON document, re-read on refresh — so
|
|
354
|
+
you can watch a still-running trace grow).
|
|
292
355
|
|
|
293
356
|
| flag | default | meaning |
|
|
294
357
|
| --- | --- | --- |
|
|
@@ -299,9 +362,9 @@ package, and opens the selected trace in your browser.
|
|
|
299
362
|
### `wizardflow md` — export to Markdown
|
|
300
363
|
|
|
301
364
|
```bash
|
|
302
|
-
wizardflow md run.
|
|
303
|
-
wizardflow md run.
|
|
304
|
-
wizardflow md run.
|
|
365
|
+
wizardflow md run.jsonl # -> stdout
|
|
366
|
+
wizardflow md run.jsonl -o run.md # -> file
|
|
367
|
+
wizardflow md run.jsonl --no-mermaid # omit the graph diagram
|
|
305
368
|
```
|
|
306
369
|
|
|
307
370
|
Renders the full trace as Markdown: a metadata table, a Mermaid `flowchart` of
|
|
@@ -318,8 +381,8 @@ dashed).
|
|
|
318
381
|
### `wizardflow html` — export to HTML
|
|
319
382
|
|
|
320
383
|
```bash
|
|
321
|
-
wizardflow html run.
|
|
322
|
-
wizardflow html run.
|
|
384
|
+
wizardflow html run.jsonl # -> stdout
|
|
385
|
+
wizardflow html run.jsonl -o run.html # -> file
|
|
323
386
|
```
|
|
324
387
|
|
|
325
388
|
Emits a single self-contained document — inline CSS, **no JavaScript, no
|
|
@@ -330,6 +393,29 @@ light/dark mode. Messages-only by design: no graph/Mermaid (use `md` for that).
|
|
|
330
393
|
| --- | --- | --- |
|
|
331
394
|
| `-o`, `--output` | — | write to this file instead of stdout |
|
|
332
395
|
|
|
396
|
+
### `wizardflow json` — assemble to one pretty-printed JSON document
|
|
397
|
+
|
|
398
|
+
```bash
|
|
399
|
+
wizardflow json run.jsonl # -> stdout
|
|
400
|
+
wizardflow json run.jsonl -o run.json # -> file
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The inverse of how the SDK writes. JSONL is built for appending, so the raw
|
|
404
|
+
file is one long line per record and not much fun to read. This assembles the
|
|
405
|
+
part — header plus all its message records, with the seal's `nextPart` folded
|
|
406
|
+
into `meta` — into the indented, single-document `AgentTraceFile` shape, for
|
|
407
|
+
eyeballing, diffing in review, or handing someone a canonical JSON. (The web UI
|
|
408
|
+
reads that single-document form too.)
|
|
409
|
+
|
|
410
|
+
| flag | default | meaning |
|
|
411
|
+
| --- | --- | --- |
|
|
412
|
+
| `-o`, `--output` | — | write to this file instead of stdout |
|
|
413
|
+
|
|
414
|
+
Already have `jq`? You don't need this for a quick look — `jq . run.jsonl`
|
|
415
|
+
pretty-prints every record, and `jq 'select(.type=="message")' run.jsonl` just
|
|
416
|
+
the messages. `wizardflow json` differs in that it *assembles* the part into one
|
|
417
|
+
document (and walks no external tool).
|
|
418
|
+
|
|
333
419
|
Rendered samples (`*.md`, `*.html`) live in the [repo's
|
|
334
420
|
`examples/`](https://github.com/lkleonk/wizardflow/tree/main/sdk/python/examples).
|
|
335
421
|
|