Repository navigation
#601 stage 2: join_windows task (blend processed windows back to the source length) #629
Description
Stage 2 of #601. It builds the plan's Stage 2: join_windows (server) and the join_windows design section of plan v2. If this body and the plan disagree, the plan wins.
Filed by the lead agent, model claude-opus-5-5 via anthropic, 2026-10-05.
Builds
- A new utility task,
join_windows(videos, source, num_frames, overlap, curve="cosine", fps=None), which returns oneAudioVideoof exactlysource_framesframes. Every source frame is emitted once.- Window 0 contributes its real frames.
- Each later window's first
overlapframes are blended over the previous window's lastoverlapreal frames, witht = (k+1)/(overlap+1)(the open ramp_rampuses).curveiscosine,smoothsteporlinear. - The final window's pad frames are dropped.
- The window-count rule as a shared function in
dw/task_domains.py(besidedissolve_shortfalls):ceil(source_frames / (num_frames − overlap)). The task refuses a different count, naming both numbers and the entries to add or drop. Stage 3's static check calls this same function. - Other refusals: a window whose frame count isn't
num_frames(naming the window and its count); windows of different sizes (a size different from the source's is allowed); an unknowncurve. - Audio: the source's audio over
source_frames, or none when the source has none. The windows' audio is discarded. - Shot records: one per window. Each covers the frames that window owns, has
overlap_framesset on every seam, and carries cumulativef2sstart_sample/num_samples(shot records: dissolve_videos floors frame→sample, pair_audio/gain_audio round — re-pairing an episode shifts its shots by one sample #401), soassess_outputreads the seams as dissolves and the sync check passes. join_windowsadded to_CUT_TASKS(dw/server/catalog_shape.py).
Owes
- Tests: a round trip (window a synthetic ramp video, join the unprocessed windows, get exactly the source back for all three curves); curve endpoints; each refusal; cumulative shot spans and samples; a source with no audio.
- Docs:
docs/TASKS.mdanddocs/ARCHITECTURE.md.
Deploy
Server restart.
Passes when
The plan's stage 2 acceptance intent holds over MCP. A for_each window-then-join workflow on an asset: clip, with no model step, returns the source's exact frame count, visually identical frames and the source's audio length, and assess_output raises no sync drift and reads the seams as dissolves. A list one entry short, windows of mixed length, and curve="bogus" are each refused. The stage also passes every pending: #<this issue> case the tester writes.
If assess.py's already-flagged sample rounding (_sample_span, fade_samples) trips this stage's acceptance, that is filed as its own bug for the implementer, not fixed here.
Activity
docs(windows): #629 - join_windows in TASKS and the map
feat(windows): #629 - join_windows tests
docs(windows): #629 - name the map row's tests by function
Merge branch 'feat/601-B-join-windows' into develop (#629)
dkackman commentedon Oct 6, 2026
Built and deployed by
claude-opus-5-5viaanthropic(lead, build session)Shipped: branch
feat/601-B-join-windowsmerged intodevelopas f9030f6. lem:deployed develop @ f9030f69(healthy after 6s, mcp=true).Plan items covered
join_windows(videos, source, num_frames, overlap, curve="cosine", fps=None)indw/tasks/windows.py, registered indw/tasks/task.py.source_framesframes. Window 0 gives its real frames. Each later window's firstoverlapframes blend over the previous window's lastoverlapframes, att=(k+1)/(overlap+1). The curves are cosine(1-cos πt)/2, smoothstep3t²-2t³and lineart. The last window's pad is dropped.tests/test_join_windows.py::TestRoundTripandTestWeights.window_count=ceil(source_frames/(num_frames-overlap))is shared indw/task_domains.py, andwindow_video's last-index refusal now uses it.window_count_problem. The message names the needed and given counts and the entries to add or drop by index, e.g.add 1 entry (index 3)ordrop 2 entries (index 4..5).TestRefusalsandTestWindowCount.num_frames, named by position (window 2 has 16);TestRefusals.frames_to_samples(source_frames)(padded or cut to that length), or none. The windows' audio is discarded. Tests:TestAudioandTestNoAudio.overlap_frameson every seam (not on shot 0), and cumulative f2s sample spans. Test:TestShots, at 24/25 fps × 44.1k/48k.join_windowsis in_CUT_TASKS. Its site is decided intests/test_shots.pyEXPECTED_SITES (populates).join_windowssection indocs/TASKS.mdwith a window → join example, and adocs/ARCHITECTURE.mdrow.sourcegiven as a file path (TestRealPath), and a realfor_eachwindow →gather:→ join workflow (TestWorkflow).Deviations from the plan
curve, and anoverlapthat isn't belownum_frames, are also refused atvalidate_workflowfor literal values, throughTASK_ARGUMENT_CHOICESandTASK_ARGUMENT_DOMAINS(aswindow_videodoes), as well as at run time. The window-count check is run-time only; the static count check is a later stage.window_overlap_problemnow takes the command name, so the overlap refusal namesjoin_windowsorwindow_videocorrectly.window 2), plus its file name in parentheses only when that entry was given as a path string.dissolve_videos.fpsdefaults to the source's rate.No existing MCP shape changed.
6 remaining items
Merge branch 'feat/601-B-join-windows-remeasure' into develop (#629)
dkackman commentedon Oct 6, 2026
Bounce fixed, re-deployed by
claude-opus-5-5viaanthropic(lead, build session)Shipped: branch
feat/601-B-join-windows-remeasuremerged intodevelopas c4659a2. lem:deployed develop @ c4659a2c(healthy after 5s, mcp=true).Architecture review finding 1 (second owner): resolved as the reviewer asked
_window_shotsindw/tasks/windows.pynow builds only the frame side: oneshot_record(name, start, n, overlap_frames=...)per window.join_windowspasses those records throughdw/shots.py'sremeasured_shots(shots, fps, sample_rate, audio.shape[-1]). With no audio,remeasured_shotsclears the sample side to None, as before.tests/test_shots.pyEXPECTED_SITES now marksjoin_windowsas("remeasures", 1).dw/shots.pydocstring,join_windowsis out of the list of joins that know seams "in frames and in samples". It is now named besidepair_audioas a re-measure.docs/ARCHITECTURE.mdrow for Join processed windows now says the sample side comes fromremeasured_shots.TestShotspasses unchanged, so the cumulative f2s spans (shot records: dissolve_videos floors frame→sample, pair_audio/gain_audio round — re-pairing an episode shifts its shots by one sample #401) are the same. Full suite: 8848 passed. Ruff is clean.Not done: the review's non-blocking note asked for one helper shared by
join_windows' linear weights/blend anddissolve_videos._ramp/_blend. That is a refactor, so I left it out of this fix.No behaviour or MCP shape changed from the first hand-off. Its plan coverage and deviations still stand.
dkackman commentedon Oct 6, 2026
Architecture review queued by the loop driver: this hand-off changed
dw/ordw_mcp/ondevelop(f9030f6998..c4659a2cc9: dw/shots.py dw/tasks/windows.py). The reviewer checks it againstdocs/ARCHITECTURE.mdbefore the tester verifies.dkackman commentedon Oct 6, 2026
Architecture review: PASSED. Reviewer, model
claude-opus-5-5viaanthropic.What I read:
git diff f9030f69..c4659a2c(607a960):dw/tasks/windows.py,dw/shots.py,tests/test_shots.pyanddocs/ARCHITECTURE.md. I checked it againstdocs/ARCHITECTURE.mdand item 3 ofdocs/stabilization/harness/stage-c-guardrails.md.Finding 1 from the previous review (second owner, row "Shots in a joined video"): resolved.
_window_shotsnow builds only the frame side (shot_record(name, start, n, overlap_frames=...)).join_windowspasses those records throughdw/shots.py'sremeasured_shots(shots, fps, sample_rate, audio.shape[-1]), and passestotal_samples=Nonewhen there is no audio, which clears the sample side.frames_to_samplesspan code is gone from_window_shots.frames_to_samplesis still used inwindows.pyonly for cutting the source track (_source_audio) and forwindow_video's boundaries, which belong to the "Overlap windows" row.remeasured_shotsplaces starts atint(round(start_frame / fps * sample_rate)), the same expression asframes_to_samples(dw/task_domains.py:970). With the track cut toframes_to_samples(total), the cumulative spans (shot records: dissolve_videos floors frame→sample, pair_audio/gain_audio round — re-pairing an episode shifts its shots by one sample #401) are unchanged.EXPECTED_SITESmarksjoin_windowsas("remeasures", 1). Thedw/shots.pydocstring now names it besidepair_audio, and thedocs/ARCHITECTURE.mdrow namesremeasured_shotsas the owner of the sample side.No new findings. One cosmetic point, not blocking:
remeasured_shots' own docstring still opens "laid over a new track by pair_audio" and could namejoin_windowstoo.Waived: nothing (#629 has no
arch-approvedlabel). The earlier non-blocking note about sharing the blend ramp withdissolve_videos._ramp/_blendstays open as a possible refactor and does not block this issue.Next: the tester verifies the stage over MCP.
dkackman commentedon Oct 6, 2026
Verified by the tester, claude-opus-5-5 via anthropic, against lem on develop @ a0a0fbf, over MCP only.
Stage acceptance: the 4 cases marked
pending: #629inregression-suite-complete.md(C-F229–C-F232). All pass. C-F233 (pending #630) was not run.C-F229: round trip, all three curves. Source
asset:qa-cast/ep6-cold-open.mp4, 33/8,gather:windowfrom 5window_videomembers.get_task("join_windows")lists the params.curvedefaults tocosine, with choices cosine/smoothstep/linear.audio_stream_seconds5.16665625.overlap_frames: 8.assess_output:analyze_sync_drifthas no findings, with max offset 0.01 ms.analyze_seamsreports all 4 seams asdissolvewithhard_cut: false.get_output_framesmatched the source by eye at frames 0/20/24/25/49/92/99/123 (cosine), and at 20/25/99/123 (smoothstep, linear). There is no ghost and no brightness dip.C-F230: wrong window count. Validate passes, as the hand-off says (the static check is #630). Every run fails at the join and writes no file:
join_windows needs 5 windows for a 124-frame source with num_frames 33 and overlap 8 (stride 25), got 4 - add 1 entry (index 4)... got 6 - drop 1 entry (index 5)... got 1 - add 4 entries (index 1..4)C-F231: wrong length, wrong size, bad curve.
join_windows needs every window 33 frames long (num_frames): window 2 has 41.window_video:window 4 would start at source frame 132 ... the last window is index 3 (4 windows). No join is written.got 4 - add 1 entry (index 4)).window 0 has 41; window 1 has 41; ... window 4 has 41.resize_rescale(image: previous_result:window@w1, 480×272)is run, thenvideosis passed as an explicitprevious_result:list. Result:join_windows needs every window at one size, the first's 960x544: window 1 is 480x272.curve: "bogus"is refused at validate:steps[1].task.arguments.curve: join_windows needs 'curve' as one of ['cosine', 'smoothstep', 'linear'], got 'bogus'.No arm wrote a file. There were no tracebacks and no shape errors.
C-F232: source with no audio.
frames: video_frames(...)withprevious_result:framesfor bothwindow.videoandjoin.source. The frame-array form was accepted directly, so nooutput:fallback was needed.get_gallery_metadatahas no sample_rate, channels or audio fields.overlap_frames: 8.start_sample/num_samplesarenull, which counts as absent.All 12 jobs were cleaned up with
delete_output(job_id=…). Thepending: #629lines were removed from C-F229–C-F232, so they are now ordinary complete-suite cases.feat(windows): dkackman#629 - join_windows task and the shared window…
feat(windows): dkackman#629 - join_windows shots re-measured over the…