wizardflow 0.3.0__tar.gz → 0.5.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 (93) hide show
  1. {wizardflow-0.3.0 → wizardflow-0.5.0}/CONTRIBUTING.md +19 -0
  2. {wizardflow-0.3.0 → wizardflow-0.5.0}/PKG-INFO +33 -11
  3. {wizardflow-0.3.0 → wizardflow-0.5.0}/README.md +32 -10
  4. wizardflow-0.5.0/assets/demo.gif +0 -0
  5. {wizardflow-0.3.0 → wizardflow-0.5.0}/examples/multibranch.html +28 -11
  6. {wizardflow-0.3.0 → wizardflow-0.5.0}/examples/multibranch.md +21 -11
  7. {wizardflow-0.3.0 → wizardflow-0.5.0}/examples/multibranch.py +23 -2
  8. {wizardflow-0.3.0 → wizardflow-0.5.0}/examples/quickstart.html +20 -6
  9. {wizardflow-0.3.0 → wizardflow-0.5.0}/examples/quickstart.md +12 -6
  10. {wizardflow-0.3.0 → wizardflow-0.5.0}/examples/quickstart.py +17 -2
  11. {wizardflow-0.3.0 → wizardflow-0.5.0}/pyproject.toml +1 -1
  12. wizardflow-0.5.0/scripts/live_trace_demo.py +211 -0
  13. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/__init__.py +6 -2
  14. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_render.py +9 -0
  15. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/404.html +7 -25
  16. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/__next.__PAGE__.txt +2 -2
  17. wizardflow-0.5.0/src/wizardflow/_ui/__next._full.txt +23 -0
  18. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/__next._head.txt +1 -1
  19. wizardflow-0.5.0/src/wizardflow/_ui/__next._index.txt +7 -0
  20. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/__next._tree.txt +1 -1
  21. wizardflow-0.3.0/src/wizardflow/_ui/_next/static/chunks/0ux_7aev0a2kt.js → wizardflow-0.5.0/src/wizardflow/_ui/_next/static/chunks/07ur0v9hb4ida.js +3 -3
  22. wizardflow-0.3.0/src/wizardflow/_ui/_next/static/chunks/0u1x9l49cgb30.js → wizardflow-0.5.0/src/wizardflow/_ui/_next/static/chunks/1_way7swvumpv.js +1 -1
  23. wizardflow-0.5.0/src/wizardflow/_ui/_next/static/chunks/1fb6xczxpe9s3.js +260 -0
  24. wizardflow-0.5.0/src/wizardflow/_ui/_next/static/chunks/221oy04d3f6t2.js +7 -0
  25. wizardflow-0.5.0/src/wizardflow/_ui/_not-found/__next._full.txt +18 -0
  26. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_not-found/__next._head.txt +1 -1
  27. wizardflow-0.5.0/src/wizardflow/_ui/_not-found/__next._index.txt +7 -0
  28. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_not-found/__next._not-found/__PAGE__.txt +1 -1
  29. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_not-found/__next._not-found.txt +1 -1
  30. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_not-found/__next._tree.txt +1 -1
  31. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_not-found.html +7 -25
  32. wizardflow-0.5.0/src/wizardflow/_ui/_not-found.txt +18 -0
  33. wizardflow-0.5.0/src/wizardflow/_ui/index.html +7 -0
  34. wizardflow-0.5.0/src/wizardflow/_ui/index.txt +23 -0
  35. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/sitemap.xml +1 -1
  36. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/cli.py +102 -8
  37. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/client.py +20 -7
  38. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/html.py +9 -1
  39. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/markdown.py +6 -1
  40. wizardflow-0.5.0/tests/test_cli.py +311 -0
  41. {wizardflow-0.3.0 → wizardflow-0.5.0}/tests/test_html.py +23 -0
  42. {wizardflow-0.3.0 → wizardflow-0.5.0}/tests/test_markdown.py +21 -0
  43. {wizardflow-0.3.0 → wizardflow-0.5.0}/tests/test_reader.py +10 -0
  44. {wizardflow-0.3.0 → wizardflow-0.5.0}/tests/test_trace.py +37 -0
  45. wizardflow-0.3.0/assets/demo.gif +0 -0
  46. wizardflow-0.3.0/src/wizardflow/_ui/__next._full.txt +0 -22
  47. wizardflow-0.3.0/src/wizardflow/_ui/__next._index.txt +0 -6
  48. wizardflow-0.3.0/src/wizardflow/_ui/_next/static/chunks/142r2alz4_x6t.js +0 -1
  49. wizardflow-0.3.0/src/wizardflow/_ui/_next/static/chunks/2q_qu9w0vej6s.js +0 -115
  50. wizardflow-0.3.0/src/wizardflow/_ui/_not-found/__next._full.txt +0 -17
  51. wizardflow-0.3.0/src/wizardflow/_ui/_not-found/__next._index.txt +0 -6
  52. wizardflow-0.3.0/src/wizardflow/_ui/_not-found.txt +0 -17
  53. wizardflow-0.3.0/src/wizardflow/_ui/index.html +0 -25
  54. wizardflow-0.3.0/src/wizardflow/_ui/index.txt +0 -22
  55. wizardflow-0.3.0/tests/test_cli.py +0 -158
  56. {wizardflow-0.3.0 → wizardflow-0.5.0}/.gitignore +0 -0
  57. {wizardflow-0.3.0 → wizardflow-0.5.0}/LICENSE +0 -0
  58. {wizardflow-0.3.0 → wizardflow-0.5.0}/examples/data_types.py +0 -0
  59. {wizardflow-0.3.0 → wizardflow-0.5.0}/scripts/build_ui.py +0 -0
  60. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/05-c3ty_6dwfk.js +0 -0
  61. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/09s72r58ijxwx.js +0 -0
  62. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/0cz1d0mv5g_q7.js +0 -0
  63. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/14mrh2-p_w84d.js +0 -0
  64. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/18clj6xsyhc_j.css +0 -0
  65. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/2-j1b74l3lxxu.css +0 -0
  66. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/27jktro2p5rq9.js +0 -0
  67. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/2gj5bmeov-r8f.js +0 -0
  68. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/310vm2bl3xxpt.js +0 -0
  69. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/3lt2ss7dftc2n.css +0 -0
  70. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/3n7dm2ojtyzwn.js +0 -0
  71. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/chunks/turbopack-2fblmukzx7kws.js +0 -0
  72. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/4fa387ec64143e14-s.2tuy5pz7dlieh.woff2 +0 -0
  73. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/53b9e256198e5412-s.390ncx5urfkfu.woff2 +0 -0
  74. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/5ce348bf30bf5439-s.31988l_ccedte.woff2 +0 -0
  75. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/6306c77e7c8268e4-s.2dbetqa9o8jxf.woff2 +0 -0
  76. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/7178b3e590c64307-s.21jp631_3pja2.woff2 +0 -0
  77. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/797e433ab948586e-s.p.0r6juujl39pe6.woff2 +0 -0
  78. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/7d817b4c03b0c5f1-s.1uyisp29ctx0d.woff2 +0 -0
  79. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/8a480f0b521d4e75-s.1qq4vpdcun5oj.woff2 +0 -0
  80. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/bbc41e54d2fcbd21-s.1rgnod-3esatf.woff2 +0 -0
  81. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/caa3a2e1cccd8315-s.p.0wgildi0cnwt9.woff2 +0 -0
  82. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/fef07dbb0973bf53-s.3p2_lha1f2xer.woff2 +0 -0
  83. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/_next/static/media/icon.09qublg69ek7b.svg +0 -0
  84. {wizardflow-0.3.0/src/wizardflow/_ui/_next/static/AJjiS0WLQjNzSyuZBS0Ff → wizardflow-0.5.0/src/wizardflow/_ui/_next/static/ncaFVj6v0NRTp4NatoEBp}/_buildManifest.js +0 -0
  85. {wizardflow-0.3.0/src/wizardflow/_ui/_next/static/AJjiS0WLQjNzSyuZBS0Ff → wizardflow-0.5.0/src/wizardflow/_ui/_next/static/ncaFVj6v0NRTp4NatoEBp}/_clientMiddlewareManifest.js +0 -0
  86. {wizardflow-0.3.0/src/wizardflow/_ui/_next/static/AJjiS0WLQjNzSyuZBS0Ff → wizardflow-0.5.0/src/wizardflow/_ui/_next/static/ncaFVj6v0NRTp4NatoEBp}/_ssgManifest.js +0 -0
  87. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/icon.svg +0 -0
  88. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/opengraph-image +0 -0
  89. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/_ui/robots.txt +0 -0
  90. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/constants.py +0 -0
  91. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/py.typed +0 -0
  92. {wizardflow-0.3.0 → wizardflow-0.5.0}/src/wizardflow/reader.py +0 -0
  93. {wizardflow-0.3.0 → wizardflow-0.5.0}/tests/conftest.py +0 -0
@@ -50,3 +50,22 @@ That script runs the shared Next frontend with
50
50
  `NEXT_PUBLIC_WIZARDFLOW_TARGET=local`, copies the static export into
51
51
  `src/wizardflow/_ui/`, and removes hosted-only legal route artifacts (`Impressum`
52
52
  / `Datenschutz`). The SDK UI keeps the GitHub project link.
53
+
54
+ ## Checking live traces by hand
55
+
56
+ The viewer follows a trace file while it is still being written: it polls with
57
+ `If-None-Match`, extends the timeline without losing your place, and stops when
58
+ the part rotates away. None of that is covered by an automated test, so there is
59
+ a generator to watch it with:
60
+
61
+ ```bash
62
+ cd sdk/python
63
+ python scripts/live_trace_demo.py
64
+ ```
65
+
66
+ It records a synthetic run with the ordinary SDK calls, writing into the
67
+ monorepo's `web/public/` (so the trace is same-origin for `npm run dev` in
68
+ `web/`) and printing the viewer URL. It rotates after 18 messages, so the sealed
69
+ part, the part navigation, and the live pulse dot can all be seen in a couple of
70
+ minutes. Like `build_ui.py`, it needs the full monorepo checkout and is never
71
+ packaged.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wizardflow
3
- Version: 0.3.0
3
+ Version: 0.5.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
@@ -57,6 +57,10 @@ runtime dependencies**, no daemon, no setup.
57
57
 
58
58
  ![WizardFlow replaying an agent run](https://raw.githubusercontent.com/lkleonk/wizardflow/main/sdk/python/assets/demo.gif)
59
59
 
60
+ ▶ **[Watch this exact run live](https://getwizardflow.com/?example=doctor-consultation)** —
61
+ the flow from the GIF, replaying in your browser right now. No install, and
62
+ nothing is uploaded.
63
+
60
64
  The file it produces is JSON Lines with a small, documented schema: line 1 is a
61
65
  `header` record carrying the `graph { nodes, edges }`, then one `message`
62
66
  record per line (`steps[] → payloads[] { label, value }`). Every line is plain
@@ -150,7 +154,11 @@ wizardflow.end_message("msg-2") # writes msg-2
150
154
 
151
155
  Pass the **string `id`**, never a handle object — safe to hand to a callback or
152
156
  across threads. A message is created on first reference and finalized by
153
- `end_message`; `end_message(id, title="...")` optionally gives it a human title.
157
+ `end_message`; `end_message(id, title="...")` optionally gives it a human title,
158
+ and `end_message(id, meta={...})` attaches flat metadata about the message as a
159
+ whole (outcome, latency, user id, …) — the viewer shows it on the message's
160
+ timeline chip. Keep meta values short scalars; large structured data belongs in
161
+ `log()` payloads.
154
162
 
155
163
  ### Choosing message ids
156
164
 
@@ -169,8 +177,9 @@ Do **not** use a session id, user id, or thread id as the message id. Those
169
177
  identify a *conversation*, and a conversation contains many messages — after
170
178
  the first `end_message(session_id)`, every further `log(session_id, ...)` in
171
179
  that session raises, because that "message" has already ended. If you want the
172
- session visible in the trace, put it in `init(meta=...)` or in the message
173
- title (`end_message(msg_id, title=...)`) — never in the id. Ids are opaque:
180
+ session visible in the trace, put it in `init(meta=...)` or in the message's
181
+ meta (`end_message(msg_id, meta={"session": session_id})`) — never in the id.
182
+ Ids are opaque:
174
183
  the viewer only displays and groups by them, so encoding meaning into them
175
184
  buys nothing. Hardcoded ids like `"msg-1"` are fine for demo scripts; `uuid4`
176
185
  is the right default for real applications.
@@ -302,9 +311,11 @@ metadata never fails extraction (the edge is just emitted plain). Call it
302
311
  **message id**, the second is the node. With `label`/`content` it records a
303
312
  payload; bare `log(id, "node")` records a visit with no payloads. The message
304
313
  is created on first reference; this only accumulates in memory.
305
- - `end_message(id, title=None)` — finalize a message and append it to the
306
- trace; returns the current trace path. The **only** call that touches disk.
307
- Optional `title` sets the message's human title. Idempotent.
314
+ - `end_message(id, title=None, meta=None)` — finalize a message and append it
315
+ to the trace; returns the current trace path. The **only** call that touches
316
+ disk. Optional `title` sets the message's human title; optional `meta` (a
317
+ flat dict of short scalars) attaches message-level metadata shown on the
318
+ message's chip in the viewer. Idempotent.
308
319
  - `reinit(name=None, description=None, meta=None)` — start a **new trace
309
320
  file** (fresh timestamped name), keeping the graph and output configuration.
310
321
  Use it at natural boundaries of a long-lived process — a new user session, a
@@ -440,6 +451,11 @@ wizardflow html run.jsonl
440
451
  wizardflow json run.jsonl
441
452
  # --path is equivalent everywhere:
442
453
  wizardflow ui --path run.jsonl
454
+ # --latest: treat the path as a directory (default: the current directory) and
455
+ # open its most recently modified *.jsonl / *.json file — handy right after a
456
+ # run, or to attach to the trace an agent is writing right now:
457
+ wizardflow ui --latest
458
+ wizardflow ui --latest traces/
443
459
  ```
444
460
 
445
461
  The SDK only ever **writes** JSONL, but these commands **read** either framing —
@@ -449,16 +465,22 @@ they stay at parity with the web viewer, which also accepts both.
449
465
  ### `wizardflow ui` — local viewer
450
466
 
451
467
  ```bash
452
- wizardflow ui run.jsonl [--host 127.0.0.1] [--port 0] [--no-open]
468
+ wizardflow ui run.jsonl [--latest] [--host 127.0.0.1] [--port 0] [--no-open]
453
469
  ```
454
470
 
455
471
  Binds a stdlib HTTP server, serves the static WizardFlow UI bundled in the SDK
456
- package, and opens the selected trace in your browser (the JSONL is assembled
457
- server-side and served to the UI as one JSON document, re-read on refresh — so
458
- you can watch a still-running trace grow).
472
+ package, and opens the selected trace in your browser. The JSONL is assembled
473
+ server-side and served to the UI as one JSON document, re-read on every fetch —
474
+ and the UI **follows a still-running trace live**: it polls the file (cheap
475
+ ETag revalidation; an unchanged file is a single `stat()` on the server) and
476
+ extends the view in place without resetting your selection, playback position,
477
+ or arranged node layout. A pulse dot in the header shows the watch is active;
478
+ messages that arrive while you're inspecting an older one appear as a
479
+ "+N new" chip in the timeline.
459
480
 
460
481
  | flag | default | meaning |
461
482
  | --- | --- | --- |
483
+ | `--latest` | off | treat the path as a directory (default: cwd) and open its most recently modified `*.jsonl` / `*.json` |
462
484
  | `--host` | `127.0.0.1` | interface to bind |
463
485
  | `--port` | `0` | port to bind; `0` asks the OS for a free port |
464
486
  | `--no-open` | off | print the local URL instead of launching a browser |
@@ -10,6 +10,10 @@ runtime dependencies**, no daemon, no setup.
10
10
 
11
11
  ![WizardFlow replaying an agent run](https://raw.githubusercontent.com/lkleonk/wizardflow/main/sdk/python/assets/demo.gif)
12
12
 
13
+ ▶ **[Watch this exact run live](https://getwizardflow.com/?example=doctor-consultation)** —
14
+ the flow from the GIF, replaying in your browser right now. No install, and
15
+ nothing is uploaded.
16
+
13
17
  The file it produces is JSON Lines with a small, documented schema: line 1 is a
14
18
  `header` record carrying the `graph { nodes, edges }`, then one `message`
15
19
  record per line (`steps[] → payloads[] { label, value }`). Every line is plain
@@ -103,7 +107,11 @@ wizardflow.end_message("msg-2") # writes msg-2
103
107
 
104
108
  Pass the **string `id`**, never a handle object — safe to hand to a callback or
105
109
  across threads. A message is created on first reference and finalized by
106
- `end_message`; `end_message(id, title="...")` optionally gives it a human title.
110
+ `end_message`; `end_message(id, title="...")` optionally gives it a human title,
111
+ and `end_message(id, meta={...})` attaches flat metadata about the message as a
112
+ whole (outcome, latency, user id, …) — the viewer shows it on the message's
113
+ timeline chip. Keep meta values short scalars; large structured data belongs in
114
+ `log()` payloads.
107
115
 
108
116
  ### Choosing message ids
109
117
 
@@ -122,8 +130,9 @@ Do **not** use a session id, user id, or thread id as the message id. Those
122
130
  identify a *conversation*, and a conversation contains many messages — after
123
131
  the first `end_message(session_id)`, every further `log(session_id, ...)` in
124
132
  that session raises, because that "message" has already ended. If you want the
125
- session visible in the trace, put it in `init(meta=...)` or in the message
126
- title (`end_message(msg_id, title=...)`) — never in the id. Ids are opaque:
133
+ session visible in the trace, put it in `init(meta=...)` or in the message's
134
+ meta (`end_message(msg_id, meta={"session": session_id})`) — never in the id.
135
+ Ids are opaque:
127
136
  the viewer only displays and groups by them, so encoding meaning into them
128
137
  buys nothing. Hardcoded ids like `"msg-1"` are fine for demo scripts; `uuid4`
129
138
  is the right default for real applications.
@@ -255,9 +264,11 @@ metadata never fails extraction (the edge is just emitted plain). Call it
255
264
  **message id**, the second is the node. With `label`/`content` it records a
256
265
  payload; bare `log(id, "node")` records a visit with no payloads. The message
257
266
  is created on first reference; this only accumulates in memory.
258
- - `end_message(id, title=None)` — finalize a message and append it to the
259
- trace; returns the current trace path. The **only** call that touches disk.
260
- Optional `title` sets the message's human title. Idempotent.
267
+ - `end_message(id, title=None, meta=None)` — finalize a message and append it
268
+ to the trace; returns the current trace path. The **only** call that touches
269
+ disk. Optional `title` sets the message's human title; optional `meta` (a
270
+ flat dict of short scalars) attaches message-level metadata shown on the
271
+ message's chip in the viewer. Idempotent.
261
272
  - `reinit(name=None, description=None, meta=None)` — start a **new trace
262
273
  file** (fresh timestamped name), keeping the graph and output configuration.
263
274
  Use it at natural boundaries of a long-lived process — a new user session, a
@@ -393,6 +404,11 @@ wizardflow html run.jsonl
393
404
  wizardflow json run.jsonl
394
405
  # --path is equivalent everywhere:
395
406
  wizardflow ui --path run.jsonl
407
+ # --latest: treat the path as a directory (default: the current directory) and
408
+ # open its most recently modified *.jsonl / *.json file — handy right after a
409
+ # run, or to attach to the trace an agent is writing right now:
410
+ wizardflow ui --latest
411
+ wizardflow ui --latest traces/
396
412
  ```
397
413
 
398
414
  The SDK only ever **writes** JSONL, but these commands **read** either framing —
@@ -402,16 +418,22 @@ they stay at parity with the web viewer, which also accepts both.
402
418
  ### `wizardflow ui` — local viewer
403
419
 
404
420
  ```bash
405
- wizardflow ui run.jsonl [--host 127.0.0.1] [--port 0] [--no-open]
421
+ wizardflow ui run.jsonl [--latest] [--host 127.0.0.1] [--port 0] [--no-open]
406
422
  ```
407
423
 
408
424
  Binds a stdlib HTTP server, serves the static WizardFlow UI bundled in the SDK
409
- package, and opens the selected trace in your browser (the JSONL is assembled
410
- server-side and served to the UI as one JSON document, re-read on refresh — so
411
- you can watch a still-running trace grow).
425
+ package, and opens the selected trace in your browser. The JSONL is assembled
426
+ server-side and served to the UI as one JSON document, re-read on every fetch —
427
+ and the UI **follows a still-running trace live**: it polls the file (cheap
428
+ ETag revalidation; an unchanged file is a single `stat()` on the server) and
429
+ extends the view in place without resetting your selection, playback position,
430
+ or arranged node layout. A pulse dot in the header shows the watch is active;
431
+ messages that arrive while you're inspecting an older one appear as a
432
+ "+N new" chip in the timeline.
412
433
 
413
434
  | flag | default | meaning |
414
435
  | --- | --- | --- |
436
+ | `--latest` | off | treat the path as a directory (default: cwd) and open its most recently modified `*.jsonl` / `*.json` |
415
437
  | `--host` | `127.0.0.1` | interface to bind |
416
438
  | `--port` | `0` | port to bind; `0` asks the OS for a free port |
417
439
  | `--no-open` | off | print the local URL instead of launching a browser |
Binary file
@@ -18,6 +18,10 @@ blockquote {
18
18
  color: #8a8a8a; font-style: italic;
19
19
  }
20
20
  table.meta { border-collapse: collapse; font-size: .85rem; margin-bottom: 2rem; }
21
+ section.nodes { margin: -1rem 0 2rem; }
22
+ section.nodes h2 { font-size: 1.05rem; margin: 0 0 .2rem; }
23
+ section.nodes ul { margin: 0; padding-left: 1.2rem; font-size: .9rem; }
24
+ section.nodes li { margin: .15rem 0; }
21
25
  table.meta th, table.meta td {
22
26
  border: 1px solid #8884; padding: .25rem .6rem; text-align: left;
23
27
  }
@@ -27,6 +31,7 @@ section.message {
27
31
  padding: .25rem 1rem 1rem; margin-bottom: 1.5rem;
28
32
  }
29
33
  section.message > h2 { font-size: 1.15rem; margin: .8rem 0 .4rem; }
34
+ section.message > p.msg-meta { font-size: .85rem; color: #8a8a8a; margin: -.2rem 0 .6rem; }
30
35
  section.step { padding: .9rem 0; }
31
36
  section.step:first-of-type { padding-top: .2rem; }
32
37
  /* Divider between consecutive nodes in a message. */
@@ -57,48 +62,60 @@ pre code { background: none; padding: 0; }
57
62
  <table class="meta">
58
63
  <tr><th>version</th><td>0.2</td></tr>
59
64
  </table>
65
+ <section class="nodes">
66
+ <h2>Nodes</h2>
67
+ <ul>
68
+ <li><span class="label">router</span> — Classifies the request and picks a branch: planner or retriever.</li>
69
+ <li><span class="label">planner</span> — Decomposes a tool-using request into concrete tool calls.</li>
70
+ <li><span class="label">retriever</span> — Fetches the most relevant documents for the query.</li>
71
+ <li><span class="label">tool_node</span> — Runs the planned tool calls against external APIs.</li>
72
+ <li><span class="label">generator</span> — Writes the answer from the tool result or retrieved docs.</li>
73
+ </ul>
74
+ </section>
60
75
  <section class="message">
61
76
  <h2>Weather in Berlin</h2>
77
+ <p class="msg-meta"><span class="label">branch</span>: planner · <span class="label">outcome</span>: answered · <span class="label">latency_ms</span>: 10210</p>
62
78
  <section class="step">
63
- <h3>user_input <span class="time">· 21:39:58</span></h3>
79
+ <h3>user_input <span class="time">· 13:14:05</span></h3>
64
80
  <p class="scalar"><span class="label">Input</span> <code>What&#x27;s the weather in Berlin?</code></p>
65
81
  </section>
66
82
  <section class="step">
67
- <h3>router <span class="time">· 21:39:58</span></h3>
83
+ <h3>router <span class="time">· 13:14:05</span></h3>
68
84
  <p class="scalar"><span class="label">llm_input</span> <code>Pick a route for the request...</code></p>
69
85
  <p class="scalar"><span class="label">llm_output</span> <code>{&quot;route&quot;: &quot;planner&quot;, &quot;confidence&quot;: 0.92}</code></p>
70
86
  </section>
71
87
  <section class="step">
72
- <h3>planner <span class="time">· 21:39:58</span></h3>
88
+ <h3>planner <span class="time">· 13:14:05</span></h3>
73
89
  <p class="scalar"><span class="label">llm_input</span> <code>Decompose into tool calls...</code></p>
74
90
  <p class="scalar"><span class="label">llm_output</span> <code>{&quot;plan&quot;: [&quot;weather_api(city=&#x27;Berlin&#x27;)&quot;]}</code></p>
75
91
  </section>
76
92
  <section class="step">
77
- <h3>tool_node <span class="time">· 21:39:58</span></h3>
93
+ <h3>tool_node <span class="time">· 13:14:05</span></h3>
78
94
  </section>
79
95
  <section class="step">
80
- <h3>generator <span class="time">· 21:39:58</span></h3>
96
+ <h3>generator <span class="time">· 13:14:05</span></h3>
81
97
  <p class="scalar"><span class="label">llm_input</span> <code>Answer using the tool result...</code></p>
82
98
  <p class="scalar"><span class="label">llm_output</span> <code>It&#x27;s 19C and partly cloudy in Berlin.</code></p>
83
99
  </section>
84
100
  <section class="step">
85
- <h3>final_response <span class="time">· 21:39:58</span></h3>
101
+ <h3>final_response <span class="time">· 13:14:05</span></h3>
86
102
  <p class="scalar"><span class="label">Output</span> <code>It&#x27;s 19C and partly cloudy in Berlin.</code></p>
87
103
  </section>
88
104
  </section>
89
105
  <section class="message">
90
106
  <h2>Summarize research paper</h2>
107
+ <p class="msg-meta"><span class="label">branch</span>: retriever · <span class="label">outcome</span>: answered · <span class="label">docs_used</span>: 2 · <span class="label">latency_ms</span>: 7130</p>
91
108
  <section class="step">
92
- <h3>user_input <span class="time">· 21:39:58</span></h3>
109
+ <h3>user_input <span class="time">· 13:14:05</span></h3>
93
110
  <p class="scalar"><span class="label">Input</span> <code>Summarize the attached research paper.</code></p>
94
111
  </section>
95
112
  <section class="step">
96
- <h3>router <span class="time">· 21:39:58</span></h3>
113
+ <h3>router <span class="time">· 13:14:05</span></h3>
97
114
  <p class="scalar"><span class="label">llm_input</span> <code>Pick a route for the request...</code></p>
98
115
  <p class="scalar"><span class="label">llm_output</span> <code>{&quot;route&quot;: &quot;retriever&quot;, &quot;confidence&quot;: 0.88}</code></p>
99
116
  </section>
100
117
  <section class="step">
101
- <h3>retriever <span class="time">· 21:39:58</span></h3>
118
+ <h3>retriever <span class="time">· 13:14:05</span></h3>
102
119
  <div class="payload"><span class="label">Input</span><pre><code>{
103
120
  &quot;topK&quot;: 4,
104
121
  &quot;namespace&quot;: &quot;papers&quot;
@@ -115,12 +132,12 @@ pre code { background: none; padding: 0; }
115
132
  ]</code></pre></div>
116
133
  </section>
117
134
  <section class="step">
118
- <h3>generator <span class="time">· 21:39:58</span></h3>
135
+ <h3>generator <span class="time">· 13:14:05</span></h3>
119
136
  <p class="scalar"><span class="label">llm_input</span> <code>Summarize the retrieved documents...</code></p>
120
137
  <p class="scalar"><span class="label">llm_output</span> <code>The paper proposes a sparse attention variant with near-linear cost.</code></p>
121
138
  </section>
122
139
  <section class="step">
123
- <h3>final_response <span class="time">· 21:39:58</span></h3>
140
+ <h3>final_response <span class="time">· 13:14:05</span></h3>
124
141
  <p class="scalar"><span class="label">Output</span> <code>The paper proposes a sparse attention variant with near-linear cost.</code></p>
125
142
  </section>
126
143
  </section>
@@ -26,49 +26,59 @@ flowchart TD
26
26
  n5 --> n6
27
27
  ```
28
28
 
29
+ - **router** — Classifies the request and picks a branch: planner or retriever.
30
+ - **planner** — Decomposes a tool-using request into concrete tool calls.
31
+ - **retriever** — Fetches the most relevant documents for the query.
32
+ - **tool_node** — Runs the planned tool calls against external APIs.
33
+ - **generator** — Writes the answer from the tool result or retrieved docs.
34
+
29
35
  ## Weather in Berlin
30
36
 
31
- ### user_input · 21:39:58
37
+ **branch**: planner · **outcome**: answered · **latency_ms**: 10210
38
+
39
+ ### user_input · 13:14:05
32
40
 
33
41
  **Input**: `What's the weather in Berlin?`
34
42
 
35
- ### router · 21:39:58
43
+ ### router · 13:14:05
36
44
 
37
45
  **llm_input**: `Pick a route for the request...`
38
46
 
39
47
  **llm_output**: `{"route": "planner", "confidence": 0.92}`
40
48
 
41
- ### planner · 21:39:58
49
+ ### planner · 13:14:05
42
50
 
43
51
  **llm_input**: `Decompose into tool calls...`
44
52
 
45
53
  **llm_output**: `{"plan": ["weather_api(city='Berlin')"]}`
46
54
 
47
- ### tool_node · 21:39:58
55
+ ### tool_node · 13:14:05
48
56
 
49
- ### generator · 21:39:58
57
+ ### generator · 13:14:05
50
58
 
51
59
  **llm_input**: `Answer using the tool result...`
52
60
 
53
61
  **llm_output**: `It's 19C and partly cloudy in Berlin.`
54
62
 
55
- ### final_response · 21:39:58
63
+ ### final_response · 13:14:05
56
64
 
57
65
  **Output**: `It's 19C and partly cloudy in Berlin.`
58
66
 
59
67
  ## Summarize research paper
60
68
 
61
- ### user_input · 21:39:58
69
+ **branch**: retriever · **outcome**: answered · **docs_used**: 2 · **latency_ms**: 7130
70
+
71
+ ### user_input · 13:14:05
62
72
 
63
73
  **Input**: `Summarize the attached research paper.`
64
74
 
65
- ### router · 21:39:58
75
+ ### router · 13:14:05
66
76
 
67
77
  **llm_input**: `Pick a route for the request...`
68
78
 
69
79
  **llm_output**: `{"route": "retriever", "confidence": 0.88}`
70
80
 
71
- ### retriever · 21:39:58
81
+ ### retriever · 13:14:05
72
82
 
73
83
  **Input**
74
84
 
@@ -94,12 +104,12 @@ flowchart TD
94
104
  ]
95
105
  ```
96
106
 
97
- ### generator · 21:39:58
107
+ ### generator · 13:14:05
98
108
 
99
109
  **llm_input**: `Summarize the retrieved documents...`
100
110
 
101
111
  **llm_output**: `The paper proposes a sparse attention variant with near-linear cost.`
102
112
 
103
- ### final_response · 21:39:58
113
+ ### final_response · 13:14:05
104
114
 
105
115
  **Output**: `The paper proposes a sparse attention variant with near-linear cost.`
@@ -43,6 +43,16 @@ wiz = wizardflow.init(
43
43
  ("retriever", "generator"), # branches rejoin
44
44
  ("generator", "final_response"),
45
45
  ],
46
+ # Optional one-line description per node (keyed by id), shown behind an
47
+ # info icon in the viewer when the node is selected. Plumbing nodes
48
+ # (user_input, final_response) are intentionally left out.
49
+ node_descriptions={
50
+ "router": "Classifies the request and picks a branch: planner or retriever.",
51
+ "planner": "Decomposes a tool-using request into concrete tool calls.",
52
+ "retriever": "Fetches the most relevant documents for the query.",
53
+ "tool_node": "Runs the planned tool calls against external APIs.",
54
+ "generator": "Writes the answer from the tool result or retrieved docs.",
55
+ },
46
56
  )
47
57
 
48
58
  # msg-1 — takes the planner / tool branch.
@@ -55,7 +65,14 @@ wizardflow.log("msg-1", "tool_node") # ran the tool, logged no payload
55
65
  wizardflow.log("msg-1", "generator", "llm_input", "Answer using the tool result...")
56
66
  wizardflow.log("msg-1", "generator", "llm_output", "It's 19C and partly cloudy in Berlin.")
57
67
  wizardflow.log("msg-1", "final_response", "Output", "It's 19C and partly cloudy in Berlin.")
58
- wizardflow.end_message("msg-1", title="Weather in Berlin")
68
+ # Optional meta: flat facts about the message as a whole (short scalars only),
69
+ # shown on the message's chip in the viewer. Here it records which branch ran
70
+ # and how the run turned out.
71
+ wizardflow.end_message(
72
+ "msg-1",
73
+ title="Weather in Berlin",
74
+ meta={"branch": "planner", "outcome": "answered", "latency_ms": 10210},
75
+ )
59
76
 
60
77
  # msg-2 — takes the retriever branch (skips planner/tool entirely).
61
78
  wizardflow.log("msg-2", "user_input", "Input", "Summarize the attached research paper.")
@@ -71,6 +88,10 @@ wizardflow.log("msg-2", "generator", "llm_output",
71
88
  "The paper proposes a sparse attention variant with near-linear cost.")
72
89
  wizardflow.log("msg-2", "final_response", "Output",
73
90
  "The paper proposes a sparse attention variant with near-linear cost.")
74
- wizardflow.end_message("msg-2", title="Summarize research paper")
91
+ wizardflow.end_message(
92
+ "msg-2",
93
+ title="Summarize research paper",
94
+ meta={"branch": "retriever", "outcome": "answered", "docs_used": 2, "latency_ms": 7130},
95
+ )
75
96
 
76
97
  print(f"wrote {wiz.current_path}")
@@ -18,6 +18,10 @@ blockquote {
18
18
  color: #8a8a8a; font-style: italic;
19
19
  }
20
20
  table.meta { border-collapse: collapse; font-size: .85rem; margin-bottom: 2rem; }
21
+ section.nodes { margin: -1rem 0 2rem; }
22
+ section.nodes h2 { font-size: 1.05rem; margin: 0 0 .2rem; }
23
+ section.nodes ul { margin: 0; padding-left: 1.2rem; font-size: .9rem; }
24
+ section.nodes li { margin: .15rem 0; }
21
25
  table.meta th, table.meta td {
22
26
  border: 1px solid #8884; padding: .25rem .6rem; text-align: left;
23
27
  }
@@ -27,6 +31,7 @@ section.message {
27
31
  padding: .25rem 1rem 1rem; margin-bottom: 1.5rem;
28
32
  }
29
33
  section.message > h2 { font-size: 1.15rem; margin: .8rem 0 .4rem; }
34
+ section.message > p.msg-meta { font-size: .85rem; color: #8a8a8a; margin: -.2rem 0 .6rem; }
30
35
  section.step { padding: .9rem 0; }
31
36
  section.step:first-of-type { padding-top: .2rem; }
32
37
  /* Divider between consecutive nodes in a message. */
@@ -57,33 +62,42 @@ pre code { background: none; padding: 0; }
57
62
  <table class="meta">
58
63
  <tr><th>version</th><td>0.2</td></tr>
59
64
  </table>
65
+ <section class="nodes">
66
+ <h2>Nodes</h2>
67
+ <ul>
68
+ <li><span class="label">router</span> — Classifies the request and picks the next node.</li>
69
+ <li><span class="label">planner</span> — Decomposes the request into concrete tool calls.</li>
70
+ <li><span class="label">tool_node</span> — Runs the planned tool calls against external APIs.</li>
71
+ </ul>
72
+ </section>
60
73
  <section class="message">
61
74
  <h2>msg-1</h2>
62
75
  <section class="step">
63
- <h3>user_input <span class="time">· 21:39:58</span></h3>
76
+ <h3>user_input <span class="time">· 13:13:58</span></h3>
64
77
  <p class="scalar"><span class="label">Input</span> <code>What&#x27;s the weather in Berlin?</code></p>
65
78
  </section>
66
79
  <section class="step">
67
- <h3>router <span class="time">· 21:39:58</span></h3>
80
+ <h3>router <span class="time">· 13:13:58</span></h3>
68
81
  <p class="scalar"><span class="label">llm_input</span> <code>Route this request...</code></p>
69
82
  <p class="scalar"><span class="label">llm_output</span> <code>{&quot;route&quot;: &quot;planner&quot;}</code></p>
70
83
  </section>
71
84
  <section class="step">
72
- <h3>tool_node <span class="time">· 21:39:58</span></h3>
85
+ <h3>tool_node <span class="time">· 13:13:58</span></h3>
73
86
  </section>
74
87
  <section class="step">
75
- <h3>final_response <span class="time">· 21:39:58</span></h3>
88
+ <h3>final_response <span class="time">· 13:13:58</span></h3>
76
89
  <p class="scalar"><span class="label">Output</span> <code>It&#x27;s 19C and partly cloudy in Berlin.</code></p>
77
90
  </section>
78
91
  </section>
79
92
  <section class="message">
80
93
  <h2>Summarize the paper</h2>
94
+ <p class="msg-meta"><span class="label">outcome</span>: answered · <span class="label">latency_ms</span>: 320</p>
81
95
  <section class="step">
82
- <h3>user_input <span class="time">· 21:39:58</span></h3>
96
+ <h3>user_input <span class="time">· 13:13:58</span></h3>
83
97
  <p class="scalar"><span class="label">Input</span> <code>Summarize the paper.</code></p>
84
98
  </section>
85
99
  <section class="step">
86
- <h3>router <span class="time">· 21:39:58</span></h3>
100
+ <h3>router <span class="time">· 13:13:58</span></h3>
87
101
  <p class="scalar"><span class="label">llm_output</span> <code>{&quot;route&quot;: &quot;planner&quot;}</code></p>
88
102
  </section>
89
103
  </section>
@@ -21,30 +21,36 @@ flowchart TD
21
21
  n3 --> n4
22
22
  ```
23
23
 
24
+ - **router** — Classifies the request and picks the next node.
25
+ - **planner** — Decomposes the request into concrete tool calls.
26
+ - **tool_node** — Runs the planned tool calls against external APIs.
27
+
24
28
  ## msg-1
25
29
 
26
- ### user_input · 21:39:58
30
+ ### user_input · 13:13:58
27
31
 
28
32
  **Input**: `What's the weather in Berlin?`
29
33
 
30
- ### router · 21:39:58
34
+ ### router · 13:13:58
31
35
 
32
36
  **llm_input**: `Route this request...`
33
37
 
34
38
  **llm_output**: `{"route": "planner"}`
35
39
 
36
- ### tool_node · 21:39:58
40
+ ### tool_node · 13:13:58
37
41
 
38
- ### final_response · 21:39:58
42
+ ### final_response · 13:13:58
39
43
 
40
44
  **Output**: `It's 19C and partly cloudy in Berlin.`
41
45
 
42
46
  ## Summarize the paper
43
47
 
44
- ### user_input · 21:39:58
48
+ **outcome**: answered · **latency_ms**: 320
49
+
50
+ ### user_input · 13:13:58
45
51
 
46
52
  **Input**: `Summarize the paper.`
47
53
 
48
- ### router · 21:39:58
54
+ ### router · 13:13:58
49
55
 
50
56
  **llm_output**: `{"route": "planner"}`
@@ -26,6 +26,15 @@ wiz = wizardflow.init(
26
26
  ("planner", "tool_node"),
27
27
  ("tool_node", "final_response"),
28
28
  ],
29
+ # Optional: a one-line description per node, keyed by node id. It shows
30
+ # behind an info icon in the viewer when the node is selected. Plumbing
31
+ # nodes (user_input, final_response) are left out on purpose — not every
32
+ # node needs one.
33
+ node_descriptions={
34
+ "router": "Classifies the request and picks the next node.",
35
+ "planner": "Decomposes the request into concrete tool calls.",
36
+ "tool_node": "Runs the planned tool calls against external APIs.",
37
+ },
29
38
  )
30
39
 
31
40
  # Message 1 — each log names its message ("msg-1") in the first argument.
@@ -36,10 +45,16 @@ wizardflow.log("msg-1", "tool_node") # visited, no payloads
36
45
  wizardflow.log("msg-1", "final_response", "Output", "It's 19C and partly cloudy in Berlin.")
37
46
  wizardflow.end_message("msg-1") # <- writes the file; msg-1 is now persisted
38
47
 
39
- # Message 2 — same shape; an optional title gives the message a human title.
48
+ # Message 2 — same shape; an optional title gives the message a human title,
49
+ # and optional meta attaches flat facts about the message as a whole (shown on
50
+ # the message's chip in the viewer). Keep meta values short scalars.
40
51
  wizardflow.log("msg-2", "user_input", "Input", "Summarize the paper.")
41
52
  wizardflow.log("msg-2", "router", "llm_output", '{"route": "planner"}')
42
- wizardflow.end_message("msg-2", title="Summarize the paper")
53
+ wizardflow.end_message(
54
+ "msg-2",
55
+ title="Summarize the paper",
56
+ meta={"outcome": "answered", "latency_ms": 320},
57
+ )
43
58
  # -> the part file now contains msg-1 and msg-2
44
59
 
45
60
  print(f"wrote {wiz.current_path}")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "wizardflow"
7
- version = "0.3.0"
7
+ version = "0.5.0"
8
8
  description = "Python SDK for recording agent flows into the WizardFlow / AgentTrace file format."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"