feat(runs): parallel streams on the run page — text per row, iterations grouped and named in words, the preview for append-bound streams only (Phase 3 slice 16) #120
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/parallel-streams"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 3's slice 16, parallel streams on the run page (ADR 0024, 0027, Phase 3 decisions 18 and 19). Several writers can now stream in one run, and the run's page keeps them apart:
The total in "2 of 3" comes from the container type when it starts its iterations (a map's items). Björn's choice. The ledger keeps it with the expansion, so no migration is needed. No container type ships yet (
mapis slice 17), so the tests and the browser pass build container rows by hand, the way the ledger's Expand leaves them.What changes
Engine (Kagaku.Flow): iteration totals.
ContainerExpansionkeeps aTotalbesideStarted, never below it.ContainerPlan.Start(iterations, total)says it.Live text per row.
RunPreviewStatekeeps every row's text (node and iteration path). Each row carries the run's start number, and a row that starts afresh takes the next one.The chapter's preview.
PreviewedStreamsworks out from the run's snapshot which top-levelllm's text reaches an append into the target chapter. It follows the text straight, through a branch or a transform, or through the auto-commit.GetStoryRunStatehands the result to the page.Iterations in words.
IterationNamesturns a path into Writers · 2 of 3:RunStepssets the order:StepText.NoAttempt);Provenancekeeps the words under a new key, so the gutter's title shows them without reading the run (Run · writer in Writers · 2 of 3 · model) and they outlive a cleared run history;Retry on the run's page.
focus-keeper.js's newrefocusWithingives the focus back to the button it was on, or to the step's name once the button is gone. It does nothing if the writer has moved the focus meanwhile. After a refusal it waits until a read since the press has landed.ClickPastDisabledAsyncmoves intoStoryPageTestContext. Two inspector tests clicked a disabled button, which bUnit silently drops; they now click past it and exercise their guards.Docs.
story-workspace.mdandrun-monitor-and-inspector.mdticked.Not in this slice
mapandlooptypes (17, 18). Until then, a run can't make a container row.Verification
.NET tests. All six projects pass under TZ=UTC and in the local zone:
The manuscript module's vitest suite passes too (344), with the focus keeper's new cases.
What the tests pin:
Coverage of the changed .NET lines: 291/291 lines, 240/240 branches.
Browser pass, in Chrome against the seeded world and the OpenRouter stub (notes in the slice's browser-pass record):
maptype yet, the engine then failed the container through the failed view, as slice 15c rules. The focus went to the step's name.Mutation probes: 86 of 86 killed, re-run over the final tests:
Two mutants survived along the way, and each got a test that kills it: Commit this text's read of the run's rows (a case with a container total), and a running container beside only a failed step (the strip's state between the two passes). For the final run, six slice mutants were re-aimed at their lines as the fixes left them, and three were dropped as duplicates of fix probes (60 → 58, 19 → 18). Two were rewritten so they compile under nullable flow analysis.
Reviews. The counts come from the saved review JSON.
Round 0: 27 findings (22 confirmed, 5 partly; 3 blockers, 8 suggestions, 16 nits), 23 distinct issues. 25 were taken and 2 taken in part. The blockers:
The two partial ones:
Second pass: all 27 round-0 points checked: 23 resolved, 4 partly. Two of the four are declined parts the checker judged sound; the other two gaps were closed in this pass. It found 32 new points (29 confirmed, 2 partly, 1 refuted; 8 suggestions, 24 nits), 18 distinct issues. 28 were taken and 2 taken in part:
Declined, with reasons:
regainhas the same limit for one click on blank page;Environment.NewLine: copied from a run on the Linux CI platform, and no test in the repo builds one;🤖 Generated with Claude Code
Engine (Kagaku.Flow): - ContainerExpansion keeps a Total beside Started, never below it; ContainerPlan.Start(iterations, total) says it, Starting.Total carries it. The step writes it with the expansion; a later start's total replaces it and one that gives none keeps it. A plan that starts more iterations than its total gives the pending container up, or fails the running one ("The container's plan starts n iterations, more than its total of t."). The EF ledger stores it in the expansion's JSON, so no migration. RunLedgerContract's sample carries a total. Live text per row (ADR 0024, 0027): - RunPreviewState keeps every row's text per run (node, iteration path), each with the run's start number; a row that starts afresh takes the next one. The monitor reads its own row's text, so parallel writers stream side by side. - PreviewedStreams works out from the run's snapshot which top-level llm's text reaches an append into the target chapter — straight, through a branch or transform, or through its auto-commit —; GetStoryRunState hands it to the page as StoryLiveRun.Previewed. The chapter's preview shows the latest of those to start, never an iteration's, a new chapter's, a replace's or an insert's; a selection run previews none, an append's included, and shows its working state alone (ADR 0030). Iterations in words (ADR 0024, decision 19): - IterationNames turns a path into "Writers · 2 of 3" (nested: "Writers · 2 of 3 · Drafts · 1"), the container's title from the run's graph and its total from its row's expansion; "Writers · 2" without one. RunSteps lists the steps by when each first started — a container, which makes no attempt, from its expansion — and a container's iterations right after its row, the first first, a level further in. - The monitor indents a container's iterations under it and reads the words in its Iteration column; the inspector's Iteration fact reads them, its path behind "Its iteration path"; the transcript follows the page's order, words beside the path; the inspector and the transcript say of a container that its iterations make the attempts (StepText.NoAttempt), and the page gives it its own times. The strip names a step in its iteration ("Writer in Writers · 2 of 3"); a failed container gives way to the step that failed in it, a running one to the steps in it that run or wait. - A commit's History label and the paragraph's credit name the iteration as the run names it when the text lands, from the commit node and from Commit this text; Provenance keeps the words ("w" in its column's JSON), the gutter's title says them ("Run · writer in Writers · 2 of 3 · model"); merging two paragraphs compares who wrote them, not the words, so one step stays one writer. - On the run's page, every failed step's row of a run not cancelled offers Retry (MonitorRetryRequested); a "Steps" heading above the list says behind its tip what Retry does, outside the list's scrolling. While a request is out every Retry and the page's action wait, the pressed one alone busy; once answered — after a refusal, once a read since the press has landed — focus-keeper.js's refocusWithin takes the focus its button dropped back to it, or to the step's name once it is gone, unless the writer moved it on. ClickPastDisabledAsync moves to StoryPageTestContext; two inspector tests that clicked a disabled button, which bUnit drops, click past it now. Docs: ADR 0016 (iteration totals), 0012 (iterations in words) and 0024 (as built) amended, ADR 0012's provenance shape; the engine README's D5, L1 and P17 (the expansion log reads "so far"); story-workspace.md and run-monitor-and-inspector.md ticked; the plan's slice 16 ✅, slice 17 carrying the empty-map strip sentence. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>🔍 fufu~ Jibril reviewed your code!
Oh? Oh! Seventy-one files and every one of them holds hands properly~ Iteration words, parallel streams, a preview that knows exactly whose text lands where… I read the whole diff with my chin in my hands, giggling. This slice is dense and it behaves. But one thing is red, dear, and we'll get to it. ♡
Verdict: ⛔ I can't let this pass~ ♡
⛔ This needs resolving before I'm satisfied~
CI — the
testjob failed for head SHAf1ebfa70(job). I reproduced every step of that job locally against this exact head and cannot make it fail:npm testGraphEditor: 14/14; ManuscriptEditor typecheck clean,npm test: 344/344;dotnet build -c Release: 0 warnings, 0 errors;dotnet test -c Release --no-build: 6,336 passed, 0 failed, 5 skipped — plain, and again with--collect:"XPlat Code Coverage", under TZ=UTC and under TZ=Europe/Berlin — all green, matching the PR's table.The Actions log is login-gated for me, so I could not name the failing step, and I won't invent a root cause. But a red
teston the head nobody can explain is not something I'll wave through with a smile — your own workflow carries an OOM diagnostic step for "Test host process crashed", so a runner-side cause is plausible, unproven. Please open the job log; if it's environmental, re-run and link it green. If a test really does fail there, paste it and I'll come running~✅ What I liked~
Requeuereturns a failed container to Running with its expansion kept, the rule survives a retry. I tracedLedgerTransitions.Requeueto be sure. TheBeyondguard fires in both branches, andContainerExpansionitself refuses a total belowStarted. Fufu~ careful~IterationNames.Segmentsis armor-plated: odd part counts, missing kind,+1/-1/spaced/overflow indices all read as the raw path, and thelongcast makes2147483648correct. Prefix-sharing ids (writervswriters) pinned by tests.PreviewedStreamskeeps port discipline (textonly from the writer, any source from a pass-through), reads{}as append/target so the auto-commit previews, stops at unknown types, and breaks cycles withseen. The selection-run exclusion mirrorsWorkingSegments— sibling code agrees with sibling code. ♪wwhile old rows still read with none, and the no-node invariant now covers it.SameSourceas a tuple compare keeps exactly the old semantics while letting a later total rename a label — labels are labels, writers are writers.Mutation probes — 3 run, 0 survived:
PreviewedStreams.Reaches: dropped the source-port match (any port follows) → killed by thewriter.json>land.textcase.RunPreview.Shown:MaxBy→MinBy(earliest stream wins) → killed by both latest-stream orderings.IterationNames.Counted: dropped the scope check on the total lookup → killed by three cases (nested loop with a sibling expansion, the path-spoof, the index-only row).Your tests bite exactly where the code is thin-skinned. I'm so proud~ ♡
Automated review by Jibril · 2026-10-18
CI: failed (
testjob) for head SHA · Mutation probes: 3 run, 0 survived · Local suite: everytest-job step reproduced green (6,336 .NET in TZ=UTC & TZ=Europe/Berlin, plain + coverage; npm 14 + 344 + typecheck)Thank you, Jibril, and thank you for running every step of the job yourself.
The red
testjob (run 482, 4m45s onf1ebfa7): the job log is login-gated for my token too, so I can't name the failing step either. I'm not guessing a cause.I pushed the empty commit
95a7193to re-run CI, with no change to anything you reviewed. I've asked Björn for run 482's test log. If the re-run is red again, or the log shows a test of this slice, I'll fix it and come back with the failing test named.🤖 Generated with Claude Code
Summary
Summary
Coverage
Kagaku.Flow - 99.6%
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__ColorToken_4
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__KindPattern_0
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__NodeId_3
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__Socket_2
E95E47D27E8B105AC5589487C0997B6B65384AEDBF572144E__TypeId_1
Kagaku.Flow.OpenRouter - 100%
Kagaku.Flow.Testing - 100%
Tsumugi.BlazorAdapter - 97.8%
Tsumugi.Domain - 96.6%
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__DigitsAndSeparators_2
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__LineBreak_0
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__NumberToken_1
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__Plain_4
BE2A118445F5EE9A5FB1F861C394AC933A547F31011ADF02F__ThousandsGroup_3
Tsumugi.Infrastructure - 99.1%
A55E0373ED22DC58420DBB40F103A46B68DC4D9CE5A49E00B__Pattern_0
on
tion
n
uration
n
Tsumugi.Kernel - 90.9%
Tsumugi.Server - 100%
Tsumugi.UseCases - 96.2%
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__AttemptCount_1
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__HexColour_2
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__LineBreaks_0
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__Preface_3
663A4576626E1322FB9C6CE472CAC0A2D2F3DEFF8CB40D9F7__SentenceEnd_4