Skip to content

Release 0.5.0 - #541

Merged
dkackman merged 91 commits into
masterfrom
develop
Sep 27, 2026
Merged

dkackman merged 91 commits into
masterfrom
develop

Conversation

@dkackman

Copy link
Copy Markdown
Owner

Release 0.5.0, cut by run-release.sh from develop @ c693980. Gates on the release issue #515.

Most of this range landed on develop without PRs, so the auto-generated
notes name only the Mac PR (#470) and a deploy-script PR (#476). Paste this
section into the GitHub release body once the tag has published (gh release edit v0.5.0 --notes-file ...).

Breaking and behaviour changes

New

Fixes

dkackman and others added 30 commits September 26, 2026 15:20
templates/ltx2/extend-clip always generated a fresh opening clip, with
no way to condition on a clip already on hand (a kept shot). Add a
`clip` variable (default previous_result:opening) that opening_frames
reads instead: when a caller supplies an asset:/output: reference, the
opening step becomes unreferenced and is elided via the existing
elision mechanism (the draw_singer precedent), costing nothing. Update
the dw:ltx-2.5 skill's "Longer" line to document the clip variable and
its width/height/clip_frames match requirement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The tester's bounce: with a clip supplied, opening is elided (correctly),
but extended's reused_components had no earlier step left to share
transformer/text_encoder/etc, so run_workflow failed at load with
"Cannot reuse component 'transformer' - no earlier step shared it."

Fix is template-only: extended now declares its own transformer/text_encoder
SDNQ quantization blocks, identical to opening's, and both shared_components
(on opening) and reused_components (on extended) are removed. Each step is
self-sufficient regardless of whether opening ran. Trade-off: the default
(no-clip) path now loads the model twice instead of sharing one load, in
exchange for the clip-supplied path working at all.

Updates README.md's summary table row (no longer claims shared loading) and
adds TestExtendClip::test_extended_resolves_components_with_opening_elided,
which builds the real Pipeline for 'extended' and calls
resolve_reused_components({}) - the actual method, the actual empty dict an
elided run hands it - rather than mocking the check away.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ared asset library

_remote_roots only read /api/server's directories (workspace, workflows,
assets, outputs, prompts), which never named the shared common/assets
library - so a legal, writable source there was refused as if it were
outside every root. Add each writable library from /api/assets's
libraries (workspace and common; a read-only examples library stays
excluded), keeping the traversal refusal unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_job_scoped_peak_rss_mb fell back to current rss whenever the
process-lifetime peak hadn't grown past the job's baseline, and that
fallback tracked rss down as well as up. Keep a running max across the
job's readings in job_baseline so the field never decreases once a
higher value has been reported, matching the running-max semantics
#272 intended.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…sset/delete_asset/list_assets

A stray workflow save could land in the wrong workspace and a following
delete_workflow, following the shared session pin, would then remove the
correct workspace's good copy. #298/#99 gave the output tools a per-call
workspace override; these five write/delete tools never got it. Each now
takes an optional workspace= that scopes just that call without moving the
session pin, and the reply from save_workflow/delete_workflow/delete_asset/
upload_asset names the workspace (delete_workflow/delete_asset also name the
path) it acted on.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e/path fields

The route already returned workspace and path (needed by upload_asset's
sibling fix), but this test's exact-dict assertion predated that and pinned
the old, smaller shape.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…raise

validate_workflow expanded a for_each item's bare object description
(a MiniMax-H3 reference named directly under a key, not in a list) whose
from_file/from_previous_result/from_arguments resolved null, and reported
it valid. The run's prepare step catches the same shape at run time, a few
seconds into the job, once the checkpoint is already loaded.

dw/null_media.py walks the expanded definition's pipeline arguments and
flags exactly the shape realize_args' dict branch would raise on - reusing
its own _names_no_media predicate - while leaving a null entry inside a
list alone, since that is realize_args' documented, intentional way of
making a reference optional (dialogue-short's shots rely on it being
silently dropped, not reported).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…run end

A finished for_each member could previously only be found via list_gallery
while the job kept running; manifest.json reported steps: [] until the
whole run ended, so a killed worker left no record of which members had
already saved. The manifest is now rewritten (best effort, same as the
run-open and final writes) right after each step's bookkeeping lands, with
status still "running" until the run actually finishes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…each pairing in guide text

upload_asset's refusals (path confined on a mounted server, content over
the inline size cap) and the guide's References section now point at
curl-ing POST /api/uploads directly, and cover a client-assembled
deliverable that never reaches the gallery. The for_each section now
states that item-level from_previous_result must spell the member
(slice@entry) rather than relying on the step-level auto-pairing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Six Ref2VA prompting habits (crowd props/age, lip-sync target, landmark
framing, reference-leak mitigation, 1344x768 on Ref2VA) go into the
Ref2VA README as detailed field notes, with a condensed pointer in the
minimax-h3 SKILL.md hard rules; the three-heading Music3 caption route
is confirmed as recommended in minimax-music3 SKILL.md. Made room in
the H3 skill's 12288-byte cap by trimming prose not pinned by any test.
Item 7 (per-section singer control) stays out of scope, tracked as #485;
item 5's automated first-frame probe stays out of scope, tracked as #487.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…rd wiring

per shot (50ms RMS windows), and a new shot_dead_air rule (>0.4s, warn) flags an
H3 dialogue gap inside a shot rather than at a seam. Suppressed when the shot's
own rms is at or below HOLE_VOICED_DBFS, so a shot that's quiet throughout isn't
flagged for holding a gap. Remedy is a room-tone bed: slice_audio -> loop_audio
-> mix_audio, documented in docs/TASKS.md, docs/WORKFLOW_GUIDE.md and the
minimax-h3/series-episodes plugin skills.

hard_cut: true - a cut meant as a cut. sort_findings keeps warn findings ahead
of info ones so seam_frame_jump's info severity never buries a real problem.

Tests: tests/test_assess.py adds TestShotDeadAir (fires at the right position,
suppressed below the floor, suppressed by the quiet-shot guard) and
TestConcatVideosMarksHardCut; tests/test_assessment_rules.py's generic
doc-pinning tests now also cover shot_dead_air.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e, match episodes downward

assemble-and-score hardcoded peak_dbfs=-3.0 on the 'balanced' normalize_audio
step and gave a caller no way to pass target_lufs, so peak-normalized
episodes with different crest factors (a studio-audience laugh setting one
episode's peak) could not be loudness-matched even though gaining a mix
downward to a shared LUFS always succeeds.

- Add an optional target_lufs variable (default null, unchanged behavior)
  and wire it into the balanced step; peak_dbfs stays a hard ceiling.
- series-episodes skill: replace "use target_lufs when they need to match"
  with match-downward guidance - pick one series-wide target_lufs the
  quietest episode's ceiling allows and pass it to every episode; a
  target_lufs_capped warning means lower the series target, not accept the
  mismatch. Reaching louder than a capped episode's ceiling needs a limiter
  (#474).
- tests/test_assemble_and_score_target_lufs.py exercises the real
  variable-substitution path so target_lufs reaching the balanced step,
  the unchanged peak_dbfs ceiling, and the omitted-variable case are all
  pinned against the real template.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…can't block SIGTERM

uvicorn.run() had no timeout_graceful_shutdown, so a streamable-HTTP MCP
client left connected held the server open indefinitely on SIGTERM,
which is what scripts/deploy.sh sends when no systemd unit is installed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…_air, fix gain-down wording

shot_dead_air's `says` now names the slice_audio -> loop_audio ->
mix_audio room-tone bed recipe directly, matching the docs, and a new
pinned test in test_assessment_rules.py checks the exact string.

"gain down always succeeds" was false whenever the mix itself already
peaks above the ceiling - reworded in the normalize_audio warning
(also making it name which side of the target the result landed on),
the assemble-and-score template description, and the series-episodes
skill's normalize bullet.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e-liner

Both refusals in dw_mcp/assets.py (file_path outside the server's roots,
and content over the 4MB inline cap) named POST /api/uploads but omitted
the workspace param, the bearer token, the host:port, and the response
shape - so a caller following the refusal verbatim would 401 or land in
the wrong workspace. They now give a real curl command built from the
client's own base_url, matching docs/WORKFLOW_GUIDE.md's References
section, and say the response is 201 with 'path' as the asset: reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Whisper already returns per-chunk start/end past the 30 s long-form
threshold; transcribe_audio kept only result["text"]. A `timestamps:
"segment"|"word"` argument now requests return_timestamps explicitly
(including under 30 s, where the long-form branch never fired) and
returns {text, chunks} instead of a bare string, so a music video can
be cut to the lyric or a dialogue shot checked against its line
without leaving dw. Default (unset) is unchanged plain text.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…fills workspace, states the 200MB ceiling

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The prior fix (c6ef345) pointed the served skill at
workflows/templates/minimax/README.md, which no MCP consumer can
reach, and dropped items 2-6 of the field report. Rewrite the skill's
Hard rules to state all six items inline (crowd action/prop fit,
landmark framing, lip-sync misattribution, crowd age, reference-sheet
backdrop bleed, Ref2VA at 1344x768) by trimming prose elsewhere in the
file to stay under the 12288-byte skill cap. The 1344x768 long-form
trade-off (frame count vs. canvas) moves to the recipes guide's
MiniMax-H3 section, which is genuinely MCP-reachable via
get_guide("recipes", section="MiniMax-H3"). Pin the new figures with a
literal-substring test, since no diffusers symbol enforces a measured
field report.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dkackman and others added 29 commits September 27, 2026 03:53
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…n error, not zero iterations

A modular step's result is the raw dict its pipeline returned, so
'previous_result:base.sample_rate' named a key the dict does not have
('sampling_rate'). The lookup skipped a dict lacking the key without
counting it as missing, returned [], and the documented mux step ran
zero iterations and saved nothing (M-F041). The guide's workflow now
reads base.sampling_rate, and the test parses that workflow and runs
its mux through pair_audio to an mp4.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ide a Hub repo, and a location refusal names no server directory

SE-F039: weight_name is checked for shape at validation and again at run
time before any download; the upscaler refuses a pickle format by name.
The model_name/glob refusals no longer list the server's media roots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…se.videos saves as an mp4

A modular step's 'videos' is the batch diffusers returns; pair_audio passed
it through and encode_video failed on the list at save. A batch of several is
refused. The guide shows how to save the base's own take beside the promotion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ansion, with a per-reference term

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rence H3 ceiling

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
concat_videos/dissolve_videos leave a nested input's own shot names
untouched when the input contributed more than one shot, so two
episodes joined by assemble-and-score whose inner for_each used the
same entry names (shot@accuse, shot@deflect) produced a shot map with
duplicate names and no warning - start_frame still disambiguated, but
a shots= argument or a probe finding addressing a shot by name could
not tell the two apart.

duplicate_shot_names (dw/shots.py) checks the final per-file shot map
for repeated names, and Workflow.run emits a shot_name_collision
warning naming them whenever a step's manifest entry carries any.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Follow-up to f447e8b: ruff format wraps the warning-event filter
comprehension the harness format check flagged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e identity

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hand-built workflow's validate warning comes from the catalog template
loading the same pipeline, and config may differ. Trimmed elsewhere to
stay under the client's 2048-character cap.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
concat_videos and dissolve_videos only unwrapped a bare string path in
their videos list, so the {"location": ...} idiom get_last_frame(video=...)
and a pipeline's image argument already accept validated clean but failed
at run time with "Cannot extract frames from a dict" - after an earlier,
possibly expensive, step had already run.

load_audio_video now unwraps a {"location": ...} dict itself, and a new
is_video_location() predicate (replacing the isinstance(v, str) checks in
both tasks) recognizes both forms. Docstrings for concat_videos and
load_audio_video (surfaced by get_task) now name the dict form. No new
validate rule, per the triage note - existing validation already lets the
form through; only the run-time load was missing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…s/concat_videos

A frame-size mismatch between dissolve_videos/concat_videos inputs decoded
clean past validate and failed only after every upstream step had already
generated. dw/video_size_errors.py mirrors dissolve_frame_errors.py (#400):
for every statically-resolvable (asset:/output:/literal path) video
argument, probe its size and refuse a disagreement at validate. A
previous_result:/variable:/item:/gather: reference, or a {"location": ...}
dict, has no size until run time and is left to the existing run-time
check.

Docs (dw/tasks/dissolve_videos.py, dw/tasks/concat_videos.py docstrings,
docs/TASKS.md) state the one-size rule. ltx2/two-stage.json already
documents its own size incompatibility with H3 shots as of 1cf752c.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dead_air_floor_dbfs is the quietest measured window in the run (null
for digital silence or no run), not the DEAD_AIR_FLOOR_DBFS threshold
(-65 dBFS) itself.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
extend-clip's default path leaves the opening step's LTX2Pipeline
resident (a different cache key than the extended step's
LTX2ConditionPipeline, so neither the step cache nor redefinition
sharing frees it), doubling the resident model stack on a 24 GB card.
release_pipeline is a no-op when opening is elided by a supplied clip.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Removes upscale_h3_latents and decode_h3_latents (7afd891, 9ab6ad1,
3b2d867) and the upscaler-only parts of a6f816e, since #499 is parked
after four failed verifications and its code must not ship unverified.
Keeps the general fixes that landed alongside it: a missing
previous_result property is an error (3ec965e), pair_audio unwraps a
batch of one video (465a61e), and validate_weight_name plus the
reworded location-refusal messages (a6f816e). #499 can reland this
stage by reverting this commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…with references

DELETE /api/workflows/{name} and DELETE /api/assets/{name} carried the
server's absolute filesystem path, which delete_workflow/delete_asset
passed through unchanged to MCP callers - the one place those tools
gave out a server path rather than the reference every other MCP
identifier uses. Workflow delete now reports the source's origin
alongside the name; asset delete reports the asset: reference.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
docs: 0.5.0 release notes (drafted by run-release.sh notes)
@dkackman
dkackman merged commit 5b8b216 into master Sep 27, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant