Repository navigation
Conversation
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: make backfill-frames --dry-run leave the trajectory store unchanged.
I reviewed the full github/main...HEAD change and the relevant runtime, replay, trajectory, store, waterfall, and detail callers. I also checked the repository rules and canonical Runtime/Web UI terms, commit history, backward compatibility, the replacement tests for removed UI components, and the Runtime/Web UI RPC boundary. I found one concrete write-on-dry-run defect inline; no other blocker survived validation.
Verification:
uv run pytest tests/test_trajectory_frames_backfill.py tests/test_trajectory_frames_replay.py tests/test_trajectory_replay.py tests/test_trajectory_details.py tests/test_subagent_acp.py tests/test_cli_trajectory_commands.py -x- 604 passed, 1 pre-existing pytest-mark warning.npm test -- --run src/features/trajectory src/chrome/ChatTop.test.tsx src/chrome/behaviour/scrollbars.test.ts src/state/scrollbars.test.ts- 335 passed.npm run type-check- passed.scripts/check_large_files.py github/main...HEADandscripts/check_source_language.py github/main...HEAD- passed via the repository's uv commands.- A focused reproduction observed two
persist_artifactcalls fromrecord_steps(..., dry_run=True)for one prompt and answer.
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
I reviewed the complete delta from the previously reviewed revision and rechecked it against the full github/main...HEAD change, relevant callers, commit history, backward compatibility, replacement-test strength, repository rules and canonical terms, and the Runtime/Web UI architecture boundary. The reported dry-run defect and its second-pass orphan-artifact variant are fixed without changing real-write behavior. I replied to the author and resolved my original thread. No new finding survived validation.
Verification:
uv run pytest tests/test_trajectory_frames_backfill.py tests/test_trajectory_frames_replay.py tests/test_trajectory_replay.py tests/test_trajectory_details.py tests/test_subagent_acp.py tests/test_cli_trajectory_commands.py -x- 606 passed, with one unrelated existing pytest-mark warning.git diff --check github/main...HEAD- passed.
|
Windows compatibility review — PR #1147 @ Scope: the full base-to-head diff at this head (80 files, +6895/−5877): the ui-web trajectory redraw as an agent waterfall, external-agent run spans ( Findings, most severe first:
Positive observations, checked by reading and by the runs: Verification on native Windows 11, detached worktree
Coverage gaps: the ui-web half of this PR (the waterfall rewrite, its 20+ new test files) could not be executed — the shared |
eb53222 to
2b8bfa3
Compare
|
Windows compatibility re-review — PR #1147 @ Scope: the branch is rebased onto current main; the delta since the previously reviewed head Result: both findings are addressed and verified on this host.
Verification on native Windows 11, detached worktree
Remaining inherited Windows issue, not introduced here: three Coverage gaps: the ui-web half of this PR (the waterfall work) still cannot be executed — the shared |
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
I reviewed the nineteen follow-up commits after the previously clean revision and rechecked the rebased full github/main...HEAD change, relevant callers and history, backward compatibility, replacement-test strength, repository rules and canonical terms, and the Runtime/Web UI boundary. The follow-ups improve scope landing, failure and tree semantics, live-axis stability, cut-short ACP recording, backfill accounting and bounds, localized withheld reasoning, and Windows redaction behavior. No new finding survived validation, and my earlier thread remains resolved.
Verification:
uv run pytest tests/test_trajectory_frames_backfill.py tests/test_trajectory_frames_replay.py tests/test_trajectory_replay.py tests/test_trajectory_details.py tests/test_trajectory_entries.py tests/test_trajectory_redact.py tests/test_subagent_acp.py tests/test_acp_journal.py tests/test_cli_trajectory_commands.py -x- 790 passed, with one unrelated existing pytest-mark warning.npm test -- --run src/features/trajectory- 303 passed.npm run type-check- passed.git diff --check github/main...HEAD- passed.- Repository large-file and source-language checks - passed.
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the delta from 18e0c6e through 7736f0e. The only change translates two trajectory schema strings in the Chinese catalog and keeps the generated TypeScript catalog in sync; it does not alter callers, compatibility, test strength, or architecture. I also checked the repository rules, relevant history, and the previously resolved review thread.
Verification: uv run pytest tests/test_i18n_* -x (18 passed), git diff --check github/main...HEAD (passed), and the source-language gate (passed).
|
Windows compatibility re-review — PR #1147 @ Scope: the delta since the previously reviewed head Result: no Windows-specific issue. This is catalogue text; the generated TUI catalogue is the only derived artifact. Verification on native Windows 11, detached worktree
Coverage gaps: the ui-web half of the PR remains untestable here — the shared |
|
Not a blocker - non-blocking items from the T1 acceptance on Severity of this one finding, not a verdict on the pull request. Any blocking findings from this pass are review threads on the changed files; GitHub renders those collapsed, as a file name with no text.
Tests here on the merged tree: the changed Python test files via the board runner gave 749 passed. ui-web |
gloryfromca
left a comment
There was a problem hiding this comment.
Blocking: correct frame replay before backfill writes its output permanently.
The two new frame-replay findings change my stance on this unchanged revision. I confirmed the underlying code paths: frames_replay.events independently reduces updates with a hard-coded AcpDialect and filters only by session id, while the live path uses dialect_for(...), _TurnCollector's plan/steer/call-revision behavior, and attaches the collector only after session/load. That makes ordinary dialect-specific and resumed runs capable of producing different replay/backfill steps from their live steps; the backfill's idempotent skip then preserves the incorrect spans.
Verification: uv run pytest tests/test_trajectory_frames_replay.py tests/test_trajectory_frames_backfill.py tests/test_trajectory_replay.py tests/test_subagent_acp.py -x passes (414 passed, one existing warning), but those tests do not cover the reported replay divergences or resumed-session history. I am leaving the two open blocker threads to their author rather than duplicating or re-grading them.
7736f0e to
1950ad7
Compare
|
Thanks for the T1 list. Item by item, at 006c094:
|
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; this can merge as far as I am concerned.
Reviewed the full delta since 7736f0e and verified the fixes for all four open blocker threads: live collection and journal replay now share TurnEvents and select the recorded handshake's dialect; replay starts at this run's first prompt and excludes session/load history; the trajectory view keeps #sheetRack visible; and the waterfall uses complete light/dark/system-dark token sets. I also checked the additional backfill policy, failed-step status, timestamp, span typing/liveness, header-count, segment-sweep, narrow-shell, documentation, and generated-gate changes.
Coverage included AGENTS.md and the relevant runtime/Web UI context rules, the changed code and callers, history, backward compatibility, test strength, and the Turn Rows/UI architecture constraints. The new tests strengthen rather than weaken the prior coverage. My earlier thread is resolved; the four newer threads belong to another reviewer and remain for that reviewer to resolve.
Verification: 782 focused Python tests passed; all 4,494 UI tests passed; TypeScript type-check passed; ESLint passed with 0 errors and four pre-existing warnings on untouched files; git diff --check, source-language, and large-file gates passed. The new real-browser integration module collected no tests because its optional Playwright environment is unavailable here.
|
Windows compatibility re-review — PR #1147 @ Scope: the branch is rebased onto main Windows angle: the new timestamp is Verification on native Windows 11, detached worktree
Coverage gaps: the ui-web half of the PR still cannot be executed — the shared |
## Summary Three storage layers of the trace store, each with an idempotent, opt-in migration: 1. **Payloads.** Every captured artifact and every content-addressed message is written as a zstd frame under the path it already had. `raven tracing compress` migrates an existing store in place and moves each artifact hard link onto the one compressed blob; `--dry-run` compresses in memory to report the real saving and writes nothing. 2. **Cold logs.** A rotated span or event log is compressed one rotation later, never the log just rotated (another writer may still be appending its last line to that inode), and by `raven tracing compress --logs`, which leaves the newest rotated log of each kind for that next rotation; a compressed log keeps its mtime, which is how the viewer orders logs. The span-log readers decode by the frame and keep working in plaintext byte offsets, which is the only coordinate the session index cursor and the viewer shard sidecars record. Compression gives a log a new inode, so the session index recognises it by the sha1 of its first 64 plaintext bytes and carries the cursor over instead of reading it again. 3. **Message packs.** Content-addressed messages are packed into multi-megabyte zstd frames with an `sha1 -> (pack, plaintext offset, length)` index. `raven tracing pack-messages` writes the packs, commits the index atomically, and only then removes the message files; a reader that finds no file reads its slice. A fourth commit bounds what a stored frame may decode to, after the automated security review flagged decompression bombs. A later review round fixed six defects, each with a regression test that failed before its change: a frame without a content-size field was measured from the wrong offset (a span log then reset the session index on every other pass); a per-shell budget cut inside a multi-byte character raised instead of degrading; the viewer accepted Node 23.0-23.7, which have no zstd; the pack reader's streaming skip was unbounded and packs past the cache budget were decoded whole rather than streamed; the pack cleanup deleted a message file whose pack no longer decoded; and CONTEXT.md still said the message store had no reclaim path. A third round fixed four more the same way: compressing a rotated log replaced its inode, so the session index read it again from offset 0 (a record without ids was listed twice, a rotation mid-scan reset the index, as did ext4 handing the freed inode to the next log, and a spilled trace was counted as lost); compressing the log just rotated dropped a line another writer had opened the file to append, at rotation and in `raven tracing compress --logs` alike; the writer did not write a packed message again when its pack was gone or cut short, so a store copied incompletely never healed; and a PATH Node without zstd hid a zstd-capable one later in the search order, such as the runtime under `~/.raven/runtime`. The command table also stopped calling packs archives. Why: the live store on this machine was 1357.7 MB, most of it block slack and uncompressed JSON. On a clone of that store the archive logs fell from 94.0 MB to 8.2 MB, and 40,056 messages fell from 225 MB of disk to 12 packs (23.6 MB) plus a 3.9 MB index. Projected whole-store result: about 548 MB, roughly 60 percent saved. Key decisions: - **Paths never change, the frame decides.** A span records `artifact_path` when it is written and those records are the audit trail, so a migration cannot rename anything. Readers sniff the zstd magic, which is also what keeps a store written before this PR readable and a half-migrated one coherent. - **ACP frame journals stay plaintext on purpose.** A span records a plaintext byte range into its journal (`subagent.external.frames.start` / `end`), and #1147's frames replay reads exactly those offsets. Re-encoding a journal would silently invalidate that audit record, so neither the migration nor rotation touches it. - **Pack migration order is crash-safe.** Packs, then the index via atomic replace, then deletion, then an orphan sweep. A crash at any point leaves every address readable from one side or the other. The writer skips an address whose pack still decodes and covers its slice, so a resumed or repeated turn does not recreate files that were packed, while an address whose pack was lost or cut short is written again. The deletion pass keeps a file whose pack no longer decodes, so a store copied incompletely does not lose its last readable copy. - **Reader caches are bounded.** The pack index is cached per process until its size or mtime changes; at most 32 MB of decompressed packs are kept, and a pack larger than the whole budget is streamed per read. Relationship to #1147: the two branches overlap in four files (`CONTEXT.md`, `raven/trajectory/conversation.py`, `raven/trajectory/details.py`, `tests/test_subagent_acp.py`), none of them touched by the third-round fixes, and a merge-tree dry run against its head `1950ad79` has no conflict. #1147 does not touch `pyproject.toml` or `uv.lock`. This branch was rebased onto main; the only conflict was main's `openai` dependency from #1108, resolved by keeping both entries and re-locking. A second rebase, onto `7c12b93f` (#1141), had no conflict. Deployment: deploy this code before running either migration. The bundled viewer needs Node 22.15 or newer (23.8 on the 23.x line) for the zstd decoder, the same requirement its compressed-artifact path already has. Code older than this PR cannot read a compressed payload, and the write path compresses new artifacts by default, so the deployment is one-way once anything new is written; the migrations themselves are manual and opt-in. Measurements on a clone of the live store (2026-10-29): - Archive span/event logs: 94.0 MB -> 8.2 MB (11.4x); 74,098 spans read identically before and after; a second pass reports `compressed 0, already 33`. - Messages: 40,056 addresses, 225 MB of disk -> 12 packs (23.6 MB) + 3.9 MB index; all 40,056 read-backs match the stored plaintext; a real v2 artifact resolves identically from files and from packs. - Read overhead: +0.01 to 0.11 ms for small and medium payloads, +0.45 to 0.49 ms for a 2.1 MB payload read with a 512 KiB cap; 40,056 packed reads in 1.1 s. - Migration speed: 4,973 files/s, 38 MB/s; full dry run 24.3 s; 40,056 messages packed in 1.7 s. ## Type - [ ] Fix - [x] Feature - [ ] Docs - [ ] CI / tooling - [ ] Refactor - [ ] Other ## Verification - [x] Relevant tests pass locally - [x] Relevant lint / type checks pass locally - [x] User-facing docs or screenshots are updated when needed Commands and results: - `uv run pytest tests/test_tracing_store_compression.py tests/test_trajectory_index.py tests/test_cli_tracing_commands.py tests/test_tracing_api.py tests/test_subagent_acp.py tests/test_trajectory_conversation.py tests/test_trajectory_details.py tests/test_trajectory_selection.py tests/test_trajectory_bundle.py tests/test_trajectory_export.py tests/test_rpc_cli_dispatch.py tests/test_utils_mirrors.py tests/test_node_server.py tests/test_tracing_message_pack.py -q -n 4`: 1127 passed, 1 skipped. - Review-fix round, the affected tracing and trajectory suites plus the CLI, RPC and kernel-closure tests, before and after the rebase onto main: 978 passed, 1 skipped. Each of the six fixes has a regression test that failed at the reviewed head and passes now. - Third review round: each of the four fixes has a regression test that failed at `57447ef8` and passes now, and removing each fix again (nine mutants: no cursor adoption, adopting only for a key that vanished, no moved-key lookup in the rescan, compressing the just-rotated log, no mtime kept at rotation, the migration compressing the newest log, the migration not keeping mtimes, an index-only packed check, the TUI floor in the viewer's Node search) turns exactly its own tests red. `uv run pytest tests/test_trajectory_index.py tests/test_tracing_store_compression.py tests/test_tracing_compression_edges.py tests/test_tracing_message_pack.py tests/test_cli_tracing_commands.py -q`: 188 passed, and the same files pass on each of the six fix commits' own tree. - Fourth review round: the writer now verifies that a pack still decodes and covers a packed address's slice, so a pack cut short heals like a missing one. The new test in `tests/test_tracing_message_pack.py` failed before the change (the message files were not written again) and passes now; the decoded length is streamed and cached per pack, so a healthy store pays one decompression per pack rather than one per address. The affected tracing, trajectory, CLI and RPC set passed: 1050 tests. Full suite at the fixed head: 37157 passed, 8 failed - the same known local baselines as before. - `uv run pytest tests/integration/test_tracing_viewer_e2e.py -m integration -q`: 28 passed. - `uv run pytest tests/integration/test_kernel_wheel_smoke.py -q`: 1 passed. - Full suite at the fixed head: 37157 passed, 8 failed, 255 skipped, 2 xfailed in 7:29, after the rebase onto `7c12b93f`. The eight are the known local baselines on this box (five proxy-environment tests, the Excel parser, the install script, and the node runtime probe); the same eight IDs also fail at `57447ef8` here, and none of the new tests is among them. The new index tests also pass with the test temp directory on ext4, which reuses a freed inode at once, and on tmpfs, which does not. - `uv run ruff check` and `uv run ruff format --check` on the changed trees: clean. - `uv run --frozen --all-extras ty check raven/tracing raven/trajectory raven/cli/tracing_commands.py raven/cli/tui_commands.py raven/rpc/methods/cli_dispatch.py`: all checks passed. - `uv run --frozen --all-extras lint-imports`: 10 kept, 0 broken. - `scripts/check_commit_messages.py origin/main..HEAD`, `commitlint --from origin/main --to HEAD`, `scripts/check_source_language.py origin/main...HEAD` and `scripts/check_large_files.py origin/main...HEAD`: exit 0. - The three feature commits were each verified on their own tree before the next one was built; the fourth commit bounds decode output and is covered by the same batch above. ## Risk - [x] Security impact considered - [x] Backward compatibility considered - [x] Rollback path is clear for risky changes - Security: local files only; no new network surface, credentials, or permissions. A trace store can be imported from another machine, so decompression is bounded rather than trusted: every decode path stops at 256 MiB (including the pack reader's offset skip), far above the 32.9 MB largest captured payload, and treats an oversized or corrupt frame as unreadable, the viewer passes `maxOutputLength` to its zstd decode, and `resolve_payload` carries a per-shell total budget so a shell naming many addresses cannot pull in more than one bounded amount. The pack index is validated (40-hex addresses, integer offsets) before any path is built. - Backward compatibility: readers tell the encoding from the frame, so plain and half-migrated stores read. Older code cannot read a migrated payload, which is why both migrations are manual and the deployment order is stated above. ACP journals are not migrated at all. - Performance: measured read overhead is sub-millisecond; the pack cache is capped at 32 MB per process and the index is 3.9 MB for 40,000 messages. A rotation compresses at most one earlier log, and finding it among 2,191 archives took 9 ms. - Rollback: the write path stores every new artifact compressed, so a revert is only clean if no artifact has been written by this version. After any new write or a migration there is no decompress command in this PR; any current reader still returns the original plaintext, and a decompress command can be a follow-up if one is wanted. ## Related Issues N/A. The relationship to #1147 is described in Summary.
Span.identity() is public API for backends that write child spans after the fact, and was missing from the tracing standard's Span table. The ui-web glossary no longer calls the overview's rows lanes, a word Raven uses for something else, and says why an agent's mark is picked by its recorded name. The design doc says how a backfill reads a journal now.
perfectionist/sort-imports reads the blank line between node:fs and vitest in scripts/gates/trajectory-tokens-css.test.mjs as a group break and fails eslint on it.
A running row whose record carries its last snapshot's end runs to the present while that snapshot is fresh (entryEnd), but the incremental tree still re-cut only the turns of rows without an end on a clock tick. Such a turn kept the end it was built with while the one-shot tree moved it with the clock. setNow now marks dirty the turn of every row whose end reads the clock: one without an end, and a running one. Pinned by the incremental tree equalling the one-shot tree after a tick moves a running row that carries its snapshot's end.
Each figure says what it is made of: the session's two ends, the agents split into main, external and in-process ones, the median turn, the model's share of the turns' time, and the two tools called most. The error count leads to the earliest failure, opening what holds it. The figures are worked out in summary.ts from the tree the rows are drawn from, calls counted once per span; clock.ts writes instants in the reader's clock and the page's language.
…ndow The ruler marks round clock times (ticks.ts), from tenths of a second to a day, at the shortest step that leaves a label its room. None falls inside a folded idle stretch, none prints over a neighbour a fold pulled close, and the next unit up is major on the ruler and in the rows' grid. Labels stand centred over short ticks. The head names the window on screen -- its day, both ends, whole session or its length -- and carries it as data-start and data-end. The elapsed-time marks are gone.
Fit, 3h, 1h and 15m set a window of that length about the middle of the one on screen (presetWindow); a middle that falls in an idle fold moves to the nearer side of it, so the window opens on work. A preset is pressed while the window is that long, and a window the wheel made presses none. Collapse all folds every agent and closes every turn.
The duration that stood just past each bar moves to a BUSY column at the end of the label: a call's and a turn's own length, and an agent's runs of work merged (busyRuns) rather than its lifetime. The axis keeps 2% of trailing scale instead of 15%, now that no label needs it.
With several agents each starts folded, one row each: a capsule per run of work on a faint line from the first to the last, the main agent in gold and every other in one neutral, a diamond where a failure happened, an arrow at an edge its work runs past, and a chip naming when it works next, or last did, once none of it is in the window. A lone agent starts open. A row selected from elsewhere opens its agent and its turn in one pass.
Once the window is narrowed, the workers whose work lies wholly before
it, or wholly after it, step into one row for each side ("2 agents
finished earlier"), their marks stacked; a fold opens in place, and a
row selected from elsewhere opens the fold that hides it. The main
agent always keeps its own row.
A worker's row is named by its task, its agent left to the mark and the hover. Open, it adds a second line: the agent that ran it, the turn that sent it and how many turns it took. A worker that ran inside the process is tagged sub-agent. The main agent keeps its name and model.
…colour A call row opens on a small square in its kind's colour, the same ink as its bar and the turn's stretch of that kind: the model's violet and a tool's teal, saturated and checked against both themes; a failure in the error ink, anything else neutral. It is named by its tool, or by the part of a model call it is, beside what the call worked on.
A turn row under the pointer opens a card under it: the turn's span and length, its model and tool calls with their time and share, and the waiting (turnBreakdown, read off the turn's own bar). The row names the card as its description.
…strip The strip shows the whole session's calls per bucket of real time, the model's under the tools', a mark over a bucket holding a failure, and each folded idle stretch with how long it was -- on an axis of its own that gives every fold room to say so. The window is a brush: a drag takes a new one, moves it, or moves one edge. The per-agent lanes, their wheel handling and their two strings go.
Once a worker is open, a line runs from the instant it was sent, on its dispatcher's row, to where its work begins: gold from the main agent, the agents' neutral otherwise, with a dot where it leaves. Worked out when the rows or the window change, never per scroll frame.
The design spec's page shape, levels, axis and a new section on the window; the web UI page in both languages.
The light theme's glass goes from 70% to 85% white, so the rows read on a near-white sheet while the wash still shows at its edges; the details pane laid over it comes to about 91%. The dark theme is unchanged.
…nguage An external agent reports each call's kind from ACP's closed set -- execute, edit, read and the rest -- and the header's top tools and the call rows printed it as it came. toolLabel (palette.ts) gives each kind its word in the catalogue; a tool's own name, the identifier the model called, is shown as it is.
…ectory The header split the agents into main, external and in-process ones, and a row tagged an in-process worker as a sub-agent. An external agent's run is a sub-agent like any other: the header counts the main agent and its sub-agents, and no row is set apart, since a worker's mark already says who ran it.
The pane sat in the row beside the waterfall, so opening it narrowed the axis under the reader's eye. It is now a white card laid over the rows' right side, below the header -- whose window presets and fold button stay in reach -- and the waterfall keeps its whole width. The grip that resizes it is the card's own left edge; under 800px the card covers the area, with the back button, as the pane did. The glass pane's token, --traj-pane, goes with the rule that used it, and the design doc's sketch stops drawing the pane as a column beside the rows. A stylesheet gate pins what happy-dom cannot see: the pane floats, on the page's paper, with rounded corners, starting below the header by the same variable that sizes the header.
The details card now moves and sizes the way the desk palette does. A press on its head moves it once it has travelled 4px, so the head's buttons keep their clicks, and the click that ends a drag is swallowed. The corner sizes it from the edges that are free: the bottom-left where the card opens, the bottom-right once moved. Let go within 34px of where it opened, the card goes back there, filling the room under the header again unless the reader made it shorter. The store holds the reader's place and size inside the area, which the island now measures down as well as across, and keeps them across a conversation switch. Like the desk palette, the card sizes by its corner alone: the grip on its left edge goes, and with it the arrow-key resize and the grip's label. While the hand is down the card moves by a transform and is placed on release: the waterfall and the pane's views read the details store, and a write per pointer move would redraw them every frame. The pane's overlay bar is parked in a fixed layer and placed from the pane's box, so the card asks for it again whenever it moves or changes size. The island's body and the waterfall now read only whether the card is open and whether the area is narrow: read whole, the store redrew every row under the card on each move of the corner. Under 800px the card still covers the area and stays put. A stylesheet gate pins what happy-dom cannot see: the height cap under the header, the handle's corner, and touch-action on the head and the handle.
The frosted sheet under the details card is 85% white, so the card's own white paper read as more of the sheet: its border was 8% ink, and its shadow, pulled in by negative spreads, fell under the bottom edge only. The card now takes the desk palette's edge -- the page's line and the palette's shadow, on every side -- and its own line token goes. A stylesheet gate pins both: the border is the page's line, and no layer of the card's shadow has a negative spread.
A key-value list sized its key column between 96px and 160px, so a reader who widened the details card to read a long key, such as subagent.external.session_id, still saw it cut: the column stopped at 160px whatever the width. It is now a share of the card, 40% and at most 320px, the same share in every section so the values still line up, and capped so a short key in a very wide card stays near its value.
1950ad7 to
c70fd7f
Compare
|
Windows compatibility re-review — PR #1147 @ Scope: the branch is rebased onto current main (which now carries the merged #1125, #1141 and #1149); the PR's own delta since the previously reviewed head Windows angle: the new interaction is pointer-driven — a press has to travel 4 px before it counts as a drag so the head's buttons keep their clicks, the pointer is captured, move/up listeners hang off an Verification: no executable check was possible — the shared Coverage gaps: the visual result (card drag/snap, the clock ruler, the activity strip) was not rendered in a browser here; the review rests on code reading. |
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; suggestions only, and they are marked inline.
The rebased portion is patch-equivalent to the previously reviewed revision; I reviewed the 20 new trajectory UI/docs commits separately. I covered AGENTS.md and the updated ui-web/CONTEXT.md, the base-to-head diff and focused post-rebase delta, the affected stores/model/projection callers, commit history and range-diff, backward compatibility across the rebase, test changes for weakening/skips, and the documented waterfall architecture.
The only follow-up is the DST clock-ruler drift marked inline; it is a narrow display edge case and does not hold up this change.
Verification: npm test --prefix ui-web passed (272 files, 4591 tests); npm run type-check --prefix ui-web passed; npm run lint --prefix ui-web passed with 0 errors and 4 pre-existing warnings in untouched files; git diff --check github/main...HEAD, the source-language gate, and the large-file gate passed.
|
A heads-up on the push after 1950ad7: it redraws the waterfall to the owner's design, adds what the owner asked for while trying it on a live page, and touches nothing the four threads or the T1 list fixed, apart from one fix. The branch is rebased onto main 9d9bec9.
Each commit was built and checked on its own (trajectory tests, tsc, eslint, check-css). The full ui-web suite, the gates and the Python checks are in the description's Verification. |
The pane's overlay bar runs the length of the pane's own box, and once the card is moved its size handle sits in the bottom-right corner, so the bar's foot ran under the handle. The pane now stops a handle's height short of the card's foot -- the same --wf-corner that sizes the handle -- so the bar ends above the handle on either side. A stylesheet gate pins both to the one variable.
The strip drew every folded idle stretch 36px wide whenever all of them fitted, and fell back to a share of the working time when they did not. For a long session the first rule left the working time a few dozen pixels -- hour-long buckets, flat bars -- while a strip a little narrower switched to the second and drew the same session in half-hour buckets: opening the rail changed the strip's whole shape. A fold now keeps its 36px only while the folds together take no more than a third of the strip, and narrows past that, so the working time holds two thirds of the strip or more at any width and the shape follows the width smoothly. The rule moves into activity.ts as stripAxis, where it is tested on its own, and the strip's comment, CONTEXT.md and the design doc now say a fold has room for its length only while the folds keep to a third of the strip.
gloryfromca
left a comment
There was a problem hiding this comment.
No blockers; suggestions only, and they are marked inline.
The two-commit delta from c70fd7f44260 is sound: the details pane now reserves its resize corner from the overlay scrollbar, and the activity strip's axis formula preserves at least two thirds of the plot for working time while retaining 36px idle folds when they fit. I checked the base-to-head record through the previously reviewed head plus this delta, the affected CSS/axis callers and history, compatibility with existing viewport behavior, the changed tests for weakening or skips, AGENTS.md and the updated ui-web/CONTEXT.md, and the documented waterfall architecture.
The existing DST clock-ruler drift remains the only named follow-up; it is unchanged and nonblocking.
Verification: the focused Vitest selection passed (3 files, 49 tests), TypeScript type-check passed, and ESLint passed for the changed TypeScript files.
|
Windows compatibility re-review — PR #1147 @ Scope: the full base-to-head diff ( Windows angle: no platform-relevant surface was added. The change moves the activity strip's own axis into Verification: reviewed the complete delta — Coverage gaps: the new axis math and the scrollbar clearance are verified by reading only, not by running the vitest cases; the strip and details card were not rendered in a browser on Windows. No Windows-specific issue found at this head. |
Summary
The trajectory page could show what happened and in what order, but not
who was doing it: one cost bar, a row list, and one Sub-agent Scope at a
time behind a tab strip. A sub-agent that outlived its dispatching turn
was invisible next to it. This replaces the bar, the list and the strip
with an agent-first waterfall, and makes an external agent's own steps
visible in it.
The page:
by agent (the main session first, then each worker, a worker's runs
folded into one by instance, else by name), then by turn, then by
span. Bars are the span's real extent (operation_start to
operation_end); charged_ms stays in the details pane.
fixed, labelled width. Each row's busy time stands in a column of its
own; an agent's is its runs of work merged, never its lifetime.
ends, the main agent and its sub-agents, an external agent's run
counted as one, the median turn, the model's share of the turns' time,
the two tools called most; the error count leads to the earliest
failure). Under it an activity strip draws the whole session's calls
per bucket of real time, the model's under the tools', with each
folded idle stretch saying how long it was. A ruler in clock time
beside the window's name, windowed rows, and a snapshot walk that
grows the tree and the axis page by page instead of rebuilding them. A
turn's bar is swept once and kept while its rows and the clock stand
still, so a scroll frame recomputes none.
on a faint line from the first to the last (the main agent in gold), a
diamond where a failure happened, an arrow where its work runs past
the window, and a chip naming when it works next. A worker is named by
its task; open, a second line names the agent that ran it, the turn
that sent it and how many turns it took, and a line is drawn from the
instant it was sent to where its work begins. A call row is keyed by a
square in its kind's colour and named by its tool, an external agent's
kind of call (execute, edit) in the page's language, beside what it
worked on; hovering a turn says what its time went on. Otherwise what
crosses between agents is marked on rows: a dispatching row's door
arrow, and the worker's name on the host turn its report started.
a drag takes a window, moves it or moves one edge; Ctrl/Cmd + wheel
zooms about the pointer, Shift + wheel pans, a plain wheel scrolls.
With the window narrowed, workers wholly before or after it fold into
one row for each side. The window and a stretch picked on the ruler
are kept in real time, so a live session growing past its end leaves
them where they are; a zoom, a pan or another conversation drops the
pick.
is reached with Tab, each agent and turn folds from a button with
aria-expanded, and the selected row is marked on screen and announced
(aria-selected, the tree's active descendant). A failed operation is
marked on its own row and on the turn that holds it, and the header
counts each call and each failure once. A row the index reports
running is drawn running.
door arrow, land on the run they name: its external agent's entry,
which opens on what the agent recorded, else its first row. They
select across agents, so nothing is filtered and the selection is
never left empty.
(every entry the index returns now has a row), the toolbar and reset
button, the now line, the dispatch rules, and the Scope Tab strip; the
door arrow on a dispatching row finds the worker instead. The store
state, styles and 38 catalogue keys only those parts used are gone
with them. The per-agent overview lanes and the elapsed-time ruler
marks gave way to the activity strip and the clock ruler, with their
two strings.
themes, with the details pane floating over the rows' right side as a
white card below the header -- opening it leaves the waterfall its
whole width. The card carries the desk palette's edge, so it reads as
solid paper on the nearly white glass; it moves by its head and sizes
by its corner alone, as that palette does, and goes back where it
opened when let go near there. A key-value list's key column widens
with the card. Every ink in the waterfall comes from a theme token,
and the stylesheet's color gate now covers its sheet. The light
theme's wash is warmer and a step stronger, so the background behind
the transcript changes too. While the trajectory is up the composer
card, the task strip and the desk step aside, and a message is sent
from the chat view; an approval or a question the conversation raises
still shows over the trajectory.
The backend:
as llm.call / tool.call child spans of its subagent.external run, the
same shape the main loop writes, at most 400 per run. A step ends
where the next begins and every step lies inside its run. The steps
are written however the turn ends once its prompt went out: answered,
cancelled, past the prompt's deadline, or cut off by the connection.
A tool call that failed is a failed step, marked the way the main
loop marks one, and a model step is typed model.
live and from the journal alike (acp_client/turn_events.py), and the
journal is read under the dialect the connection's handshake chose.
The journal stamps each frame with its UTC offset, so a run read
back in another zone keeps its times.
before, from the wire journal still on disk: ids derived from the run,
so a second pass, like a dry run, writes nothing, neither a span nor
an artifact; it closes a step an earlier pass left running past its
run's end, and holds a step stamped outside its run inside it, so a
second pass converges; it leaves alone a run that wrote its steps
live (subagent.external.steps); and it reports a run whose recorded
stretch reads back empty as unread, not as already on record. It
reads a run from its own session/prompt on, so a resumed session's
replayed history is not counted as the run's. With tracing turned
off it writes nothing and says so; --force writes anyway.
journal, from its prompt on, and only from this store's acp-frames
directory.
subagent.external span: the tool that launched the agent is answered
from the recording, so the agent never runs again.
thinking row carries the call's time instead.
text, or, when the provider counted reasoning tokens and returned
none, a sentence saying so in the reader's language.
writes its copy back as bytes, so on Windows a CRLF file no longer
gains a blank line between records. The redaction predates this
change; this branch's cassette test is what met it there.
Docs: the ui-web glossary entry, the Timing Owner entry, the design doc,
and the public WebUI and trajectory-debugging pages (both languages) now
describe this. The charged_ms description in the RPC contract and both
generated clients no longer names the removed bar.
Checked and deliberately not changed:
(else meta.instance); the Raven mark goes to the word raven, alone
or followed by a separator, as in Raven-Research. An entry carries
no preset, so the preset key AgentMark is built on is not available
here. It is presentation only; nothing else derives from it.
(follow, anchor, saved). No strip shows it; select and the pane's
links still use it, and openScope, switchScope and closeScope are now
reached only from their tests. Removing it is a refactor of its own
and is left out.
the backfill reads ACP wire journals. A CLI-lane external agent still
appears as one bar.
Type
Verification
At the rebased head, on origin/main 9d9bec9. This push changes no
Python; git range-diff against the previous head shows every commit's
patch unchanged but one, whose import context moved with main's trace
compression.
Python, proxy variables unset, with zstandard (the dependency main
added) layered over the venv: the trajectory, tracing and ACP suites
(tests/test_trajectory_.py, test_tracing_.py, test_subagent_acp.py,
test_acp_journal.py, test_cli_trajectory_commands.py and
test_cli_tracing_commands.py), 2354 passed and 1 skipped. The full suite
was last run at the previous head: 3 failed, 37022 passed, 255 skipped,
2 xfailed, the same three failing on origin/main on this machine (a
LibreOffice conversion that writes no .xls, npm on PATH, running as
root).
The tests for the replay, cassette, live-lane and backfill fixes were
seen failing before their fix, and a mutant of each fix fails them:
replay keeping an external step, the cassette keeping one, the subtree
walk cut to direct children, a live step starting before its run, the
backfill ignoring the live count, the live lane not writing it, and a
dry run or a skipped step persisting its artifacts (checked by the real
backfill over a real store, which a dry run and a second pass must leave
byte-for-byte as they found it). The same holds for the later fixes,
each seen failing first and each mutant killed: a cancelled run, one
past its prompt deadline and one whose agent died mid-turn writing their
steps, an unsent prompt writing none, a thought-only call keeping its
time, a late step held inside its run on the first pass and left alone
by the second, an unreadable stretch counted as unread, and withheld
reasoning sent as a reason code.
The fixes for the last review round were each seen failing first too:
the same frames read live and from a journal compared event by event
(a plan, a steer, revised arguments, a codex command that exited 1, a
command recovered from a permission request), a resumed run replayed
from its own prompt, the handshake's dialect reaching the Replay block
and the backfill, a failed external call drawn failed, model steps
typed model, a journal stamp read back the same in another zone, the
backfill writing nothing with tracing off, the sheet rack kept in the
trajectory view, the color gate reading the waterfall's sheet, a
running row drawn running, calls counted by span, and a turn's bar
kept across frames. Mutants of those fixes all fail them: 8 on the
reduction and the replay, 10 on the page and its gates. The commits
that split one file between two of these were checked on their own:
each passes its files' tests.
Gates: ruff check and ruff format --check on every lint target,
lint-imports (10 contracts kept), deptry, ty check, check_large_files.py
and check_source_language.py over origin/main...HEAD,
check_commit_messages.py and commitlint over origin/main..HEAD: all
pass.
ui-web: gen:check, type-check, eslint (0 errors; 4 warnings in pages
this change does not touch), vitest 272 files / 4591 tests, and
vitest --no-file-parallelism scripts/gates 57 files / 272 tests: all
pass. The tests for the scope landing, the failure marks, the tree's
roles and keys, the zoom and pick kept across a growing session, the
pointer-anchored zoom, the scroll cost, the ruler's rounding and the
reveal of a row outside every turn were seen failing first, and a
mutant of each fix fails them. This push's twenty commits were each
checked on their own tree (the trajectory tests and stylesheet gates,
the i18n key gate, tsc, check-css, eslint); every behaviour they add
was seen failing first, with a mutant of each failing its tests, and
the card's drag, corner, snap back and overlay bar were measured in
Chromium with real mouse input.
ui-tui: eslint on the regenerated catalogue (0 errors), lint:rpc,
lint:i18n and lint:shared in sync.
Windows: the line-ending fix is pinned by a test that applies Windows
text-mode newline translation at the write, so it fails without the fix
on any platform. The test that replay will not follow a link out of the
journals now skips, with the reason, on an account that cannot create
symbolic links instead of erroring: run here with the link denied it
skips, and with links allowed it passes as before.
The page's later fixes were checked in happy-dom tests, not by eye in a
browser. By hand on a real store, before them: the page served from
this change; an export and re-import round trip kept every entry,
artifact and message; the
backfill filled 151 older external runs, and a dry run after it
reported nothing left to write ("0 of 151 external run(s); 151 already
on record").
Risk
Disk: each step writes a span record and its input/output artifacts
through the existing content-addressed artifact store, at most 400
steps a run. Measured on a real store after the backfill: 151 runs,
17,133 step records, 20.8 MB of span log (about a sixth of its 120 MB)
and 11.6 MB of unique artifact blobs. There is no new reclaim policy:
the steps live and go with the audit log they belong to.
Security: the Replay block resolves the journal path and refuses one
outside this store's acp-frames directory, symlinks included, so a
path inside an imported trajectory cannot make it read another file.
The backfill reads only journals the local store recorded; imported
trajectories are never scanned.
Compatibility: no RPC shape changes, only a field description. Bundles
and cassettes recorded before this hold no steps under an external run
and replay exactly as before. The wire journal's timestamps now carry
their UTC offset; the one reader parses both forms, and an older naive
stamp reads as it did. With tracing turned off, backfill-frames now
writes nothing unless --force is passed. In the trajectory view an
approval or a question the conversation raises now shows over the
trace instead of being hidden.
Rollback: revert the squash commit. Steps already written stay in
the audit log; an older build reads them as ordinary spans, and
replaying a bundle that holds them would be fed them, which is what
the replay change here prevents.
Security impact considered
Backward compatibility considered
Rollback path is clear for risky changes
Related Issues
N/A