wizardflow 0.1.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.
Files changed (79) hide show
  1. wizardflow-0.1.0/.gitignore +16 -0
  2. wizardflow-0.1.0/CONTRIBUTING.md +51 -0
  3. wizardflow-0.1.0/LICENSE +21 -0
  4. wizardflow-0.1.0/PKG-INFO +340 -0
  5. wizardflow-0.1.0/README.md +293 -0
  6. wizardflow-0.1.0/assets/demo.gif +0 -0
  7. wizardflow-0.1.0/examples/data_types.py +84 -0
  8. wizardflow-0.1.0/examples/multibranch.html +129 -0
  9. wizardflow-0.1.0/examples/multibranch.md +105 -0
  10. wizardflow-0.1.0/examples/multibranch.py +76 -0
  11. wizardflow-0.1.0/examples/quickstart.html +92 -0
  12. wizardflow-0.1.0/examples/quickstart.md +50 -0
  13. wizardflow-0.1.0/examples/quickstart.py +45 -0
  14. wizardflow-0.1.0/pyproject.toml +45 -0
  15. wizardflow-0.1.0/scripts/build_ui.py +75 -0
  16. wizardflow-0.1.0/src/wizardflow/__init__.py +143 -0
  17. wizardflow-0.1.0/src/wizardflow/_render.py +38 -0
  18. wizardflow-0.1.0/src/wizardflow/_ui/404.html +25 -0
  19. wizardflow-0.1.0/src/wizardflow/_ui/__next.__PAGE__.txt +10 -0
  20. wizardflow-0.1.0/src/wizardflow/_ui/__next._full.txt +22 -0
  21. wizardflow-0.1.0/src/wizardflow/_ui/__next._head.txt +6 -0
  22. wizardflow-0.1.0/src/wizardflow/_ui/__next._index.txt +6 -0
  23. wizardflow-0.1.0/src/wizardflow/_ui/__next._tree.txt +5 -0
  24. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/05-c3ty_6dwfk.js +1 -0
  25. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/09s72r58ijxwx.js +1 -0
  26. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0cz1d0mv5g_q7.js +1 -0
  27. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0mz_v1wicwnmn.css +2 -0
  28. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0u1x9l49cgb30.js +1 -0
  29. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0ux_7aev0a2kt.js +36 -0
  30. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/0vq8h3vx9uqg5.js +114 -0
  31. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/142r2alz4_x6t.js +1 -0
  32. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/14mrh2-p_w84d.js +1 -0
  33. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/18clj6xsyhc_j.css +3 -0
  34. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/2-j1b74l3lxxu.css +1 -0
  35. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/27jktro2p5rq9.js +4 -0
  36. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/2gj5bmeov-r8f.js +2 -0
  37. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/310vm2bl3xxpt.js +31 -0
  38. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/3n7dm2ojtyzwn.js +1 -0
  39. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/chunks/turbopack-2fblmukzx7kws.js +1 -0
  40. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/4fa387ec64143e14-s.2tuy5pz7dlieh.woff2 +0 -0
  41. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/53b9e256198e5412-s.390ncx5urfkfu.woff2 +0 -0
  42. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/5ce348bf30bf5439-s.31988l_ccedte.woff2 +0 -0
  43. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/6306c77e7c8268e4-s.2dbetqa9o8jxf.woff2 +0 -0
  44. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/7178b3e590c64307-s.21jp631_3pja2.woff2 +0 -0
  45. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2 +0 -0
  46. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/7d817b4c03b0c5f1-s.1uyisp29ctx0d.woff2 +0 -0
  47. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/8a480f0b521d4e75-s.1qq4vpdcun5oj.woff2 +0 -0
  48. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/bbc41e54d2fcbd21-s.1rgnod-3esatf.woff2 +0 -0
  49. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2 +0 -0
  50. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/fef07dbb0973bf53-s.3p2_lha1f2xer.woff2 +0 -0
  51. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/media/icon.3okpzkln1vq00.svg +19 -0
  52. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/oQsmH12Xmzb6Zw4-R3ghe/_buildManifest.js +11 -0
  53. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/oQsmH12Xmzb6Zw4-R3ghe/_clientMiddlewareManifest.js +1 -0
  54. wizardflow-0.1.0/src/wizardflow/_ui/_next/static/oQsmH12Xmzb6Zw4-R3ghe/_ssgManifest.js +1 -0
  55. wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._full.txt +17 -0
  56. wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._head.txt +6 -0
  57. wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._index.txt +6 -0
  58. wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._not-found/__PAGE__.txt +5 -0
  59. wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._not-found.txt +5 -0
  60. wizardflow-0.1.0/src/wizardflow/_ui/_not-found/__next._tree.txt +2 -0
  61. wizardflow-0.1.0/src/wizardflow/_ui/_not-found.html +25 -0
  62. wizardflow-0.1.0/src/wizardflow/_ui/_not-found.txt +17 -0
  63. wizardflow-0.1.0/src/wizardflow/_ui/icon.svg +19 -0
  64. wizardflow-0.1.0/src/wizardflow/_ui/index.html +25 -0
  65. wizardflow-0.1.0/src/wizardflow/_ui/index.txt +22 -0
  66. wizardflow-0.1.0/src/wizardflow/_ui/opengraph-image +0 -0
  67. wizardflow-0.1.0/src/wizardflow/_ui/robots.txt +5 -0
  68. wizardflow-0.1.0/src/wizardflow/_ui/sitemap.xml +9 -0
  69. wizardflow-0.1.0/src/wizardflow/cli.py +316 -0
  70. wizardflow-0.1.0/src/wizardflow/client.py +470 -0
  71. wizardflow-0.1.0/src/wizardflow/constants.py +44 -0
  72. wizardflow-0.1.0/src/wizardflow/html.py +152 -0
  73. wizardflow-0.1.0/src/wizardflow/markdown.py +122 -0
  74. wizardflow-0.1.0/src/wizardflow/py.typed +0 -0
  75. wizardflow-0.1.0/tests/conftest.py +6 -0
  76. wizardflow-0.1.0/tests/test_cli.py +104 -0
  77. wizardflow-0.1.0/tests/test_html.py +140 -0
  78. wizardflow-0.1.0/tests/test_markdown.py +163 -0
  79. wizardflow-0.1.0/tests/test_trace.py +468 -0
@@ -0,0 +1,16 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ .eggs/
5
+ build/
6
+ dist/
7
+ .pytest_cache/
8
+ .venv/
9
+ venv/
10
+ # uv resolves this in CI (uv run / uv build); a zero-dependency library doesn't
11
+ # ship a lockfile, and tracking it would only sweep it into the sdist.
12
+ uv.lock
13
+ # Generated traces from the example scripts (e.g. examples/quickstart.json) —
14
+ # regenerated on each run, not tracked. Scoped to examples/ so it never touches
15
+ # the embedded UI bundle (src/wizardflow/_ui/), which must ship in the package.
16
+ /examples/*.json
@@ -0,0 +1,51 @@
1
+ # Contributing to the WizardFlow Python SDK
2
+
3
+ This SDK lives in `sdk/python/` of the WizardFlow monorepo. It's pure Python with
4
+ no runtime dependencies; `pytest` is the only dev dependency.
5
+
6
+ ## Local development
7
+
8
+ Work against a local checkout with an editable install:
9
+
10
+ ```bash
11
+ cd sdk/python
12
+ pip install -e ".[dev]"
13
+ ```
14
+
15
+ `tests/conftest.py` puts `src/` on the path, so the suite also runs without any
16
+ install.
17
+
18
+ ## Tests
19
+
20
+ ```bash
21
+ cd sdk/python
22
+ pytest
23
+ ```
24
+
25
+ The tests pin the emitted schema shape and the recording semantics (step
26
+ folding, completed-only persistence, atomic write, `id=` targeting, unknown-node
27
+ fast-fail, …).
28
+
29
+ ## Schema contract
30
+
31
+ The JSON this SDK serializes to is defined by `src/types/agenttrace.ts` in the
32
+ monorepo frontend — that TypeScript type is the schema of record. Change it and
33
+ the serializer (`src/wizardflow/client.py`) plus its tests must change in
34
+ lockstep; they must not drift.
35
+
36
+ ## Refreshing the bundled UI
37
+
38
+ The bundled viewer lives in `src/wizardflow/_ui/` and is committed so a
39
+ standalone SDK checkout (and the published package) works without the website
40
+ source or a Node build step. Refresh it from the monorepo frontend after UI
41
+ changes:
42
+
43
+ ```bash
44
+ cd sdk/python
45
+ python scripts/build_ui.py
46
+ ```
47
+
48
+ That script runs the shared Next frontend with
49
+ `NEXT_PUBLIC_WIZARDFLOW_TARGET=local`, copies the static export into
50
+ `src/wizardflow/_ui/`, and removes hosted-only legal route artifacts (`Impressum`
51
+ / `Datenschutz`). The SDK UI keeps the GitHub project link.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Leon Koch
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,340 @@
1
+ Metadata-Version: 2.4
2
+ Name: wizardflow
3
+ Version: 0.1.0
4
+ Summary: Python SDK for recording agent flows into the WizardFlow / AgentTrace file format.
5
+ Project-URL: Homepage, https://getwizardflow.com
6
+ Project-URL: Documentation, https://getwizardflow.com
7
+ Author: Leon Koch
8
+ License: MIT License
9
+
10
+ Copyright (c) 2026 Leon Koch
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
29
+ License-File: LICENSE
30
+ Keywords: agents,langgraph,llm,observability,tracing
31
+ Classifier: Development Status :: 3 - Alpha
32
+ Classifier: Intended Audience :: Developers
33
+ Classifier: License :: OSI Approved :: MIT License
34
+ Classifier: Operating System :: OS Independent
35
+ Classifier: Programming Language :: Python :: 3
36
+ Classifier: Programming Language :: Python :: 3.9
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
42
+ Classifier: Typing :: Typed
43
+ Requires-Python: >=3.9
44
+ Provides-Extra: dev
45
+ Requires-Dist: pytest>=7; extra == 'dev'
46
+ Description-Content-Type: text/markdown
47
+
48
+ # WizardFlow Python SDK
49
+
50
+ **A lightweight tracer for Python agents.** Drop three calls into your code —
51
+ `init`, `log`, `end_message` — and turn a messy multi-agent run into a portable
52
+ trace you can replay as an interactive graph or export to Markdown/HTML. Because
53
+ the trace is just a JSON file, anyone can replay it: hand it to a teammate or PM
54
+ and they drop it into **[getwizardflow.com](https://getwizardflow.com)** in the
55
+ browser — no Python, no install. Lightweight by design: pure Python, **zero
56
+ runtime dependencies**, no daemon, no setup.
57
+
58
+ ![WizardFlow replaying an agent run](https://raw.githubusercontent.com/lkleonk/wizardflow/main/sdk/python/assets/demo.gif)
59
+
60
+ The JSON it produces is a small, documented schema: a `graph { nodes, edges }`
61
+ plus `messages[] → steps[] → payloads[] { label, value }`.
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ pip install wizardflow
67
+ ```
68
+
69
+ No runtime dependencies. Developing the SDK itself? See
70
+ [CONTRIBUTING.md](https://github.com/lkleonk/wizardflow/blob/main/sdk/python/CONTRIBUTING.md).
71
+
72
+ ## Quickstart
73
+
74
+ ```python
75
+ import wizardflow
76
+
77
+ wizardflow.init(
78
+ output_dir="traces", # where trace files are written
79
+ file_prefix="run", # optional; defaults to "wizardflow"
80
+ description="A small router-based agent run.",
81
+ nodes=["user_input", "router", "planner", "tool_node", "final_response"],
82
+ edges=[("user_input", "router"), ("router", "planner")],
83
+ )
84
+
85
+ # Every log names its message in the first argument.
86
+ wizardflow.log("msg-1", "router", "llm_input", prompt) # same node, two payloads ->
87
+ wizardflow.log("msg-1", "router", "llm_output", output) # folded into one step
88
+ wizardflow.log("msg-1", "tool_node") # visited, no payloads
89
+ wizardflow.end_message("msg-1") # -> writes the trace
90
+ ```
91
+
92
+ There is **no `save()`** and no autosave: `log()` only accumulates in memory,
93
+ and `end_message(id)` is the one call that writes the trace. Writes happen at
94
+ message boundaries — one write per finished message, however many `log()` calls
95
+ it contains. `init()` returns a client and also stashes it as the module
96
+ default, so the bare `wizardflow.log(...)` form above works.
97
+
98
+ **Concurrency-safe.** Multi-agent setups end messages from many threads/tasks at
99
+ once; an internal lock serializes the mutation-and-write so the shared part file
100
+ is never corrupted and no message is lost or duplicated. Each write is atomic, so
101
+ the file on disk is always a complete, valid trace.
102
+
103
+ ## Targeting a message
104
+
105
+ The first argument to `log` is the message id, so overlapping messages never
106
+ collide — interleave them freely (as concurrent agents do) and each step routes
107
+ to the right message:
108
+
109
+ ```python
110
+ wizardflow.log("msg-1", "classifier", "input", text_a)
111
+ wizardflow.log("msg-2", "classifier", "input", text_b) # a different message
112
+ wizardflow.log("msg-1", "generator", "output", answer_a)
113
+ wizardflow.end_message("msg-1") # writes msg-1
114
+ wizardflow.end_message("msg-2") # writes msg-2
115
+ ```
116
+
117
+ Pass the **string `id`**, never a handle object — safe to hand to a callback or
118
+ across threads. A message is created on first reference and finalized by
119
+ `end_message`; `end_message(id, title="...")` optionally gives it a human title.
120
+
121
+ ## LangGraph: automatic topology
122
+
123
+ Instead of listing `nodes`/`edges` by hand, read them straight from a compiled
124
+ LangGraph app:
125
+
126
+ ```python
127
+ app = workflow.compile(checkpointer=memory)
128
+
129
+ wizardflow.init_from_langgraph(app, output_dir="traces", file_prefix="trace")
130
+
131
+ wizardflow.log("msg-1", "planner", "Input", state) # runtime logging unchanged
132
+ wizardflow.end_message("msg-1")
133
+ ```
134
+
135
+ You can keep the extracted LangGraph topology and still choose node accent
136
+ colors for important nodes:
137
+
138
+ ```python
139
+ wizardflow.init_from_langgraph(
140
+ app,
141
+ output_dir="traces",
142
+ file_prefix="trace",
143
+ node_colors={
144
+ "router": "#A78BFA",
145
+ "retriever": "#22D3EE",
146
+ "generator": "#60A5FA",
147
+ },
148
+ )
149
+ ```
150
+
151
+ By default, a color key that does not match an extracted node id raises a
152
+ `WizardFlowError` so typos fail fast. With `silent=True`, unknown color keys are
153
+ ignored.
154
+
155
+ It extracts node ids (keeping `__start__` / `__end__`) and directed edges, and
156
+ marks runtime branches with `"conditional": true` (deterministic and parallel
157
+ fan-out edges stay plain):
158
+
159
+ ```json
160
+ {
161
+ "edges": [
162
+ { "source": "__start__", "target": "router" },
163
+ { "source": "router", "target": "planner", "conditional": true },
164
+ { "source": "planner", "target": "final_response" }
165
+ ]
166
+ }
167
+ ```
168
+
169
+ LangGraph is **not** a dependency — extraction is duck-typed on `app.get_graph()`.
170
+ A non-LangGraph object raises `LangGraphExtractionError`; missing conditional
171
+ metadata never fails extraction (the edge is just emitted plain). Call it
172
+ **after `compile()`**, when the topology actually exists.
173
+
174
+ ## API (v0.1)
175
+
176
+ - `init(output_dir=, file_prefix="wizardflow", name=, description=, nodes=, edges=, meta=, silent=False, max_bytes=256_000) -> Client`
177
+ - `description` lands in `meta.description` (matches the schema field).
178
+ - `nodes=` enables fast-fail: `log()` to an undeclared node raises
179
+ `UnknownNodeError` immediately (unless silenced).
180
+ - `output_dir` is optional; omitted, traces are written in cwd.
181
+ - `file_prefix` is optional; omitted, filenames start with `wizardflow`.
182
+ - `max_bytes` caps each part file before rotation (see below).
183
+ - `init_from_langgraph(app, output_dir=, file_prefix="wizardflow", name=, description=, meta=, node_colors=, silent=False, max_bytes=...) -> Client`
184
+ - same as `init`, but `nodes`/`edges` come from `app.get_graph()`.
185
+ - `node_colors` maps extracted node ids to CSS colors such as `"#A78BFA"`.
186
+ - `log(id, node, label=None, content=None)` — the first positional is the
187
+ **message id**, the second is the node. With `label`/`content` it records a
188
+ payload; bare `log(id, "node")` records a visit with no payloads. The message
189
+ is created on first reference; this only accumulates in memory.
190
+ - `end_message(id, title=None)` — finalize a message and write the trace;
191
+ returns the current trace path. The **only** call that touches disk. Optional `title`
192
+ sets the message's human title. Idempotent.
193
+ - `Client.current_path` — the trace file currently being written.
194
+ - `to_dict()` / `to_json()` — inspect the active part (completed messages).
195
+
196
+ ### How saving works
197
+
198
+ `end_message` triggers an **atomic write**: write to `<part>.tmp`, then
199
+ `os.replace` over the part file. The file is always a complete, loadable
200
+ `AgentTraceFile`. Only **completed** messages are written; an in-progress
201
+ message lives in memory until it ends.
202
+
203
+ ### Rotation (no single huge file)
204
+
205
+ There's no natural "end" to a chatbot trace, so the SDK caps file size instead.
206
+ Each run writes a timestamped entry file whose name carries the run-start time:
207
+
208
+ ```
209
+ wizardflow__2026-06-08T16-29-09-123Z.json
210
+ ```
211
+
212
+ The name's `wizardflow` is the `file_prefix`; the timestamp is captured when
213
+ `init()` creates the client. If the active part would exceed `max_bytes` (~256
214
+ KB by default), it's sealed and the next message starts a fresh part:
215
+
216
+ ```
217
+ wizardflow__2026-06-08T16-29-09-123Z.json
218
+ wizardflow__2026-06-08T16-29-09-123Z__part2.json
219
+ wizardflow__2026-06-08T16-29-09-123Z__part3.json
220
+ ```
221
+
222
+ Rotation only ever happens at a **message boundary**, never mid-message (a lone
223
+ message larger than the cap gets its own oversized part). A smaller cap keeps
224
+ each rewrite — and the write lock held across it — short, which matters when
225
+ many agents end messages concurrently; raise it for fewer files at the cost of
226
+ heavier rewrites. `max_bytes` is clamped to a hard ceiling (1 MB) so an
227
+ accidental huge value can't stall concurrent writers. Each part is a
228
+ **self-contained, valid trace** (full graph + its slice of messages), chained
229
+ via `meta`:
230
+
231
+ ```json
232
+ "meta": { "part": 2, "prevPart": "...Z.json", "nextPart": "...__part3.json" }
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).
239
+
240
+ ### Logging
241
+
242
+ Rotation emits an `INFO` notice on the `wizardflow` logger. Following library
243
+ convention, the SDK attaches a `NullHandler` and configures nothing — you see
244
+ nothing unless you opt in:
245
+
246
+ ```python
247
+ import logging
248
+ logging.getLogger("wizardflow").setLevel(logging.INFO)
249
+ ```
250
+
251
+ This is separate from `silent=` (which governs raise-vs-swallow for *errors*).
252
+
253
+ ### Step folding
254
+
255
+ Consecutive `log()` calls to the **same** node within a message fold into a
256
+ single step with multiple payloads (e.g. a router step carrying both
257
+ `llm_input` and `llm_output`). A `log()` to a different node starts a new step.
258
+
259
+ ## Examples
260
+
261
+ Fuller runnable examples live in
262
+ [`examples/`](https://github.com/lkleonk/wizardflow/tree/main/sdk/python/examples):
263
+
264
+ - **`quickstart.py`** — a small linear agent; two messages, the second finalized
265
+ with an `end_message(..., title=...)` title.
266
+ - **`multibranch.py`** — `router` fans out into a planner/tool path and a
267
+ retriever path that rejoin at `generator`. Two messages take different
268
+ branches, so each logs only the nodes it actually visited.
269
+
270
+ ## CLI
271
+
272
+ The `wizardflow` command has three subcommands: `ui`, `md`, and `html`. Every
273
+ one takes the trace file as a positional argument **or** via `--path` (pass one,
274
+ not both):
275
+
276
+ ```bash
277
+ wizardflow ui run.json
278
+ wizardflow md run.json
279
+ wizardflow html run.json
280
+ # --path is equivalent everywhere:
281
+ wizardflow ui --path run.json
282
+ ```
283
+
284
+ ### `wizardflow ui` — local viewer
285
+
286
+ ```bash
287
+ wizardflow ui run.json [--host 127.0.0.1] [--port 0] [--no-open]
288
+ ```
289
+
290
+ Binds a stdlib HTTP server, serves the static WizardFlow UI bundled in the SDK
291
+ package, and opens the selected trace in your browser.
292
+
293
+ | flag | default | meaning |
294
+ | --- | --- | --- |
295
+ | `--host` | `127.0.0.1` | interface to bind |
296
+ | `--port` | `0` | port to bind; `0` asks the OS for a free port |
297
+ | `--no-open` | off | print the local URL instead of launching a browser |
298
+
299
+ ### `wizardflow md` — export to Markdown
300
+
301
+ ```bash
302
+ wizardflow md run.json # -> stdout
303
+ wizardflow md run.json -o run.md # -> file
304
+ wizardflow md run.json --no-mermaid # omit the graph diagram
305
+ ```
306
+
307
+ Renders the full trace as Markdown: a metadata table, a Mermaid `flowchart` of
308
+ the graph, then each message's steps and payloads (scalars inline; multi-line
309
+ strings and dict/list values in fenced code blocks; conditional edges drawn
310
+ dashed).
311
+
312
+ | flag | default | meaning |
313
+ | --- | --- | --- |
314
+ | `-o`, `--output` | — | write to this file instead of stdout |
315
+ | `--mermaid` | on | include the Mermaid graph diagram |
316
+ | `--no-mermaid` | — | omit the Mermaid graph diagram |
317
+
318
+ ### `wizardflow html` — export to HTML
319
+
320
+ ```bash
321
+ wizardflow html run.json # -> stdout
322
+ wizardflow html run.json -o run.html # -> file
323
+ ```
324
+
325
+ Emits a single self-contained document — inline CSS, **no JavaScript, no
326
+ external assets** — that opens offline in any browser and follows your OS
327
+ light/dark mode. Messages-only by design: no graph/Mermaid (use `md` for that).
328
+
329
+ | flag | default | meaning |
330
+ | --- | --- | --- |
331
+ | `-o`, `--output` | — | write to this file instead of stdout |
332
+
333
+ Rendered samples (`*.md`, `*.html`) live in the [repo's
334
+ `examples/`](https://github.com/lkleonk/wizardflow/tree/main/sdk/python/examples).
335
+
336
+ ## Status / not yet
337
+
338
+ - **Timestamps** are wall-clock at log time — fine for slow/live runs, wrong if
339
+ steps fire faster than ms resolution or you import after the fact. (Open
340
+ design item: explicit per-step timestamps.)