Skip to content

Latest commit

 

History

History
1299 lines (1123 loc) · 69.1 KB

RELEASING.md

File metadata and controls

1299 lines (1123 loc) · 69.1 KB

Releasing

Unreleased

This project has no standing release-notes file - GitHub auto-generates notes from commits at tag time (see below). This section is a scratch pad for items a branch's author wants the next release note to name; clear it when a release ships.

0.15.0

A release about speed for MiniMax H3 and a new image family. H3 now runs sparse attention by default, 2-4x faster per step than plain attention and 6-20% faster than sage_hub, with no extra install and equal quality on Don's judging. FLUX 3 Image Klein joins the catalog with its own templates and skill. It also closes a remote-code hole in speech_generation.

Sparse attention for MiniMax H3 (#931, #932)

  • A new attention_backend: "sparse" runs H3's generated-video attention through comfy-kitchen's Sol-Attn kernel. Text, references and audio stay exact. It is the default on every H3 template, routed by top-k 0.15 (sparse_topk), with sparse_tau as the alternative routing.
    • Against plain attention it is 2.2-4.4x faster per step on an RTX 3090.
    • Against sage_hub, on the H3 quality-ramp rungs, it is 6-20% faster. The L5 cinema final runs in 23.9-25.5 min, against 29.7 on sage.
    • Don judged faces, skin and draft-to-final staging equal or better on three seeds.
  • It needs CUDA with compute capability 8.0+. Anywhere else (mps, cpu, an older card) H3 runs dense instead of failing. On mps and cpu validate_workflow warns sparse_attention_fallback; on an older card or a CUDA box missing the kernels, the run says why it went dense.
  • Sparse moves details against dense and sage (same shot, different set dressing), so the minimax-h3 skill's quality ramp keeps a draft and its final on one backend. The ramp's TV and cinema paths now run sparse; the sage B3 -> P1 pair stays as the fastest cached cinema final.
  • /api/health lists the attention backends the server can run, and the web UI's attention_backend fields are dropdowns of that list.
  • A component that pins a backend keeps it under a pipeline-level sparse, with a warning, and the run log names sparse's routing.
  • lightx2v's Turbo-SLA LoRA was evaluated and not adopted: top-k routing gives the same result on the existing turbo LoRA (#932).

FLUX 3 Image Klein (#930)

  • Templates for text-to-image, upsample-then-generate, image edit, multi-reference and layout-with-boxes, plus the model entry and a flux3-klein composition skill.
  • text_generation gains chat_template_kwargs, a revision, a list of images, and a system_prompt read from a Hub repo file at a pinned commit (anonymous, public files only, size-capped). Its generate_kwargs are filtered, and custom_generate / trust_remote_code need trusted workflows.

Security

  • speech_generation passed a workflow's generate_kwargs and forward_params to the model's generate() unfiltered, so an untrusted workflow could set custom_generate / trust_remote_code and run code from the Hub. Both are now refused unless workflows are trusted, before the model loads. Every release through 0.14.0 is affected.

Dependencies and deploy

  • New core dependency: comfy-kitchen>=0.2.38,<0.3 (Apache-2.0). Linux and Windows get the compiled CUDA/HIP kernels; macOS gets a pure-Python wheel and runs H3 dense. scripts/deploy.sh reinstalls on the pyproject change.

0.14.0

A release about what a run is and what it will cost. Gallery files say whether they are a final or an intermediate, anything can carry a note, and runs copy between workspaces. The cost estimate scales with steps, canvas and frames instead of quoting a flat figure that was up to 12x off, and a queued job waits for host RAM rather than being OOM-killed.

Roles and notes (#855-#858)

  • Gallery files are tagged final or intermediate (inferred when a workflow doesn't say), and the gallery opens on finals. A run lists what it was Made from.
  • A file a later run in the workspace read, through output: or an asset kept from it, lists as intermediate, so a project's shots, narration and score step aside once the cut that uses them exists. A step that sets result.role: "final" stays final. The script-to-video and series-episodes skills have ingredient workflows declare "role": "intermediate" (#928).
  • Runs, files, assets and workspaces take an optional note: note on run_workflow, upload_asset and keep_output, and MCP set_note. Every listing carries notes; the web UI edits them in place as Markdown.
  • Starring a file keeps it. A rerun records the job it reran (rerun_of).
  • copy_to_workspace copies whole runs, with manifests, and asset-library entries into another workspace (#853, #859, #860).

Cost and host RAM (#866, #869, #881)

  • The estimate scales as steps x canvas x frames against the template's defaults (basis: "derived", scaled names the axes). Any other driver that moves keeps the figure and sets low_confidence; H3 adds weights, LoRA, attention and cache as drivers. On the H3 matrix it lands within 30% (B3->L5: 32.3 min quoted, 29.5 run), and an inline copy prices within 2% of its template (#906-#908, #915, #916, #921).
  • A queued job is held at dispatch while its projected host-RAM peak won't fit, and stays queued with waiting_for and a queue_wait event. It is never held with nothing else running. On a 125 GB, 2x 3090 host a second int8 H3 job waited ("needs ~87 GB, ~57 GB free") while two int4 jobs ran at once (#909, #910).
  • An OOM kill now takes the newest dispatch, not the earlier job (#881).

Timing and caches (#867, #868, #905)

  • Manifest entries carry per-step timing (first_pass_s, pass_s) and step-cache skip counts (cache_stats). A first_pass_slow warning names a warm pipeline's slow first pass (#924: 202 s against 37 s/pass).
  • Validate warns on acceleration that can't pay off or won't run: a cache on a short schedule or with no transformer to attach to, fullgraph with a cache, a CUDA-only attention backend, and an unbraced type name under a *_dtype key (#887, #904, #926).
  • The plan fingerprint and step cache key only on what changes the output, so metadata edits and 12.0 vs 12 no longer miss the cache (#905, #911).
  • Every H3 template exposes attention_backend and cache (#883, #884). MagCache calibration now reports its ratios on H3 (#927); with them, MagCache 0.06 ran the cinema final in 18.1 min, 39% under L5 (#875).

H3 quality ramp (#861, #862, #882)

  • The minimax-h3 skill has measured draft -> final rungs. TV: L1 (~5.4 min) -> L7 (~7.2 min), changing only weights_dtype. Cinema: B3 (~12.9 min) -> L5 (~29.5 min), changing only steps. Staging carries at the same seed; the skill says what to approve at each tier.
  • A proven LoRA catalog entry decides which H3 step an adapter fits, ahead of its filename (#870, #888).

Compare, sweeps and check-script (#871, #872, #919-#925)

  • compare_outputs puts 2-4 outputs side by side with PSNR/SSIM; the web UI's Compare page plays them in sync and lists the differing arguments. The workspaces guide has a Running a sweep section.
  • check-script refuses a wrong shot name at validate, adds unscripted_speech and speech_not_transcribed, and levels its input so the verdict no longer depends on loudness.

Web UI

  • A running-jobs chip, a tab title that tracks the running job, measured run times on cards, drag-and-drop upload, and keyboard culling in the gallery.

Fixes

  • cancel_download stops queued files and shows cancel_requested (#899); a model download records refs/main, so it reads complete. cancel_job takes effect during a load (#892).
  • A kept asset keeps its lineage and its job survives the workspace's deletion (#902, #913, #917, #918).
  • get_gallery_metadata measures an image, and include_workflow=false omits the embedded workflow (#922).
  • An inline copy of a list-driven template is no longer quoted the template's default-length price unscaled.
  • The host-RAM gate no longer under-projects a list-driven run whose peak grows with the list.
  • Whole-module compile: {fullgraph: true} no longer fails at the first denoise step after the timing hook.
  • FasterCache runs on FLUX.1-dev (#897); denoise time on mps is no longer billed to decode (#896).
  • The gallery's sort no longer stalls each keystroke on a large library.

Dependencies and deploy

  • No dependency floors changed.
  • scripts/dw-serve.service sets OOMPolicy=continue, so one worker's OOM kill no longer stops the server. Reinstall the unit to pick it up.
  • The jobs history gains a memory_key column, migrated on start.

0.13.0

A web UI release. Typing a reference opens a searchable picker over what it can name: assets, prompts, earlier runs' files, for_each items and packaged sub-workflows. A browse dialog picks a file by its thumbnail. A workspace's pages now show the workspace, not every example, and the gallery stops hanging on a folder of videos.

Reference picker (web UI)

  • asset: in any field that takes a reference lists the asset library, with thumbnails and the library each file comes from. prompt: lists the prompt library, searched through text, tags and description. output: lists what earlier runs wrote, newest first. Search matches every word anywhere in the name, so asset:pri finds cast/priya.png.
  • An empty field lists the reference prefixes on ↓. Two letters that start a prefix offer it, and picking one carries on into that library.
  • Inside a step, the list also offers item: and item:<field> in a for_each step, and gather:<step> and previous_result:<step>@<entry> for earlier for_each steps. These come from the editor's own dangling- reference rules, so nothing it offers is then flagged. A sub-workflow step's path offers the packaged fragments as builtin: names (new GET /api/builtin-workflows).
  • A browse button on media inputs and asset or output fields opens a thumbnail picker with two tabs, Assets and Earlier runs. It can pick an output's latest form, so a later stage follows the newest run.
  • The gallery thumbnail route accepts asset: names too.

Workspaces (web UI)

  • A workspace's Workflows page lists its own workflows. The read-only examples are a link to the shared Examples page.
  • A workspace's Assets page lists its own assets plus the shared assets its workflows use. Each asset names the workflows that use it (used_by on GET /api/assets).
  • A new workflow picks which workspace it saves into, and starts blank or from any example. New from lands on the same form.

Gallery and media

  • Grids show a video as its first frame, a lazy thumbnail with a play badge, not a <video> per tile. With 310 clips the gallery used to hang.
  • MP4 outputs are remuxed to faststart, so a browser shows a frame without fetching the file's tail. Existing outputs are untouched.

Caches (#838-#842, #844, #847)

  • Clearing a cache holds the job queue rather than racing a job that starts. Orphaned staging directories are swept. The scan is memoized and rescans log quietly.
  • The quantized cache key carries a KEY_VERSION, and the startup sweep deletes entries from any other version. This frees entries that could never be hit again (about 166 GB of H3 entries on one server). Entries record their component name.
  • get_server_info(cache_entries=true) lists the quantized cache's entries. The caches table shows "0 bytes" for an empty cache.

Fixes

  • A step whose scheduler sets only a shift opens in full view. Every H3 template's step was stuck in the compact view.
  • plan_cuts takes the fallback song length from non-suspect chunks, so a hallucination over the fade-out no longer stretches the plan (#843).
  • The MiniMax H3 4-step Ref2VA v0.1 turbo LoRA is recorded in the catalog as a trial (#824).

Dependencies and deploy

  • Floors raised to current releases: torch 2.14.1, transformers 5.19.0 (the !=5.17.0 exclusion goes with it), accelerate 1.15.0, peft 0.21.2, fastapi 0.143.0, mcp 2.3.0 and the rest. diffusers stays >=0.40.0. huggingface-hub stays >=1.33.0, because every PyPI diffusers caps it below 2.0. dw itself works on hub 2.x.
  • The gated-model login hint is now hf auth login. huggingface_hub 2.0 drops huggingface-cli.
  • deploy.sh reinstalls until an install succeeds, refreshing diffusers from git first. It also carries on with the pulled copy of itself when the pull changed it, so one deploy is enough.

0.12.0

A cold-start release. Opt in and a quantized model's second cold load skips the bf16 read and the quantize: MiniMax H3 loads in 19 s instead of 113 s on an RTX 3090. The caches that build up on a server's disk now have a page, and the music-video cut planner stops treating Whisper's hallucinations as lyrics.

Quantized-component cache (#815, #825, #826, #827)

  • DW_QUANTIZED_CACHE=1 keeps each SDNQ on-the-fly-quantized component on disk (safetensors plus JSON, under <HF_HOME>/dw-quantized or DW_QUANTIZED_CACHE_DIR) and reads it back on the next load of the same key. Measured on an RTX 3090: H3 cold 112.7 s -> 18.7 s, warm 67.5 s -> 4.0 s. An H3 configuration holds about 43 GB.
  • It covers a modular pipeline's load_components (H3) and a component loaded on its own through from_pretrained (LTX-2.5's transformer and its Gemma text encoder, saved under their own tensor names so they load back). A hit is placed on the config's return_device.
  • The key pins the snapshot commit, the quantizer config and the sdnq, diffusers, transformers and torch versions; a save is refused when it would leave less than min(50 GiB, 10%) free, and a quantized_cache job event names every hit, miss, save and skip with its reason. The cache never fails a job. Off by default; docs/QUANTIZATION.md has the details.

Disk caches (#828, #832, #833, #834, #837)

  • GET /api/caches lists dw's caches of derived data - inductor (torch.compile kernels), triton (SDNQ's jit and autotune kernels) and quantized - with size, files, last use and the volume's free space; DELETE /api/caches/{name} clears one, refused while a job runs or a download is active. The quantized cache lists its entries and DELETE /api/caches/quantized/entries/{id} removes one.
  • The web UI's Server page has a Caches panel for the same, asking before a clear and quoting what the clear costs. get_server_info carries the list for an agent.

Transcription and cuts (#822, #823)

  • music-video-cuts transcribes the vocal stem rather than the full mix, so an instrumental intro no longer folds into the first word. Chunks that are silent, overlong, wordless or a repetition loop are marked suspect; plan_cuts leaves them out and warns once per run.
  • get_gallery_metadata's envelope takes interval_seconds (0.05 to 3600, default 1.0) so an agent can place a cut to a frame. More than 2400 bins is refused unless the default interval would give more.

Fixes

  • The inherited cost estimate is re-priced linearly when an inline workflow runs more steps or frames than the template it came from, instead of quoting the template's number (#821: a 7x under-quote); the estimate carries scaled (why) and low_confidence (#851).
  • assess_output shot findings carry index and start_frame, so a repeated shot name is unambiguous (#835).
  • dialogue-short and music-video expose match_levels_dbfs (#836).
  • A default cache root that appears after the server starts is marked, so triton stays clearable after the first run (#837).
  • deploy.sh restarts a dw.serve run as a loaded launchd daemon on macOS.
  • A component's attention_backend pin stays on that component. diffusers also made it the process-wide default, so after an H3 run with attention_backend=sage_hub the worker sent every later model through sage, and Music3 failed with Unsupported head_dim: 256 (LTX's attention mask is refused the same way). Music3 and LTX templates need no pin of their own.

0.11.1

A speed release. The unquantized FLUX templates run about 3.5x faster on a 24 GB card, Z-Image Turbo about 2.6x, a LoRA scale or scheduler shift change no longer reloads the model, and switching workflows keeps the models the next one shares. Every number below was measured on an RTX 3090; the placement tables are in docs/RECIPES_24GB.md.

Workflow speed

  • The 14 unquantized FLUX templates and models/flux-krea stream the transformer block by block from pinned host memory (group_offload) instead of offload: "sequential": a 25-step 1024x1024 image takes 78 s instead of 273 s (1.5 s/step against 10.8), with 6 GB of VRAM to spare - enough for the 4-image batches offload: "model" runs out of memory on. It holds ~22 GB of pinned host RAM per loaded FLUX pipeline.
  • Z-Image Turbo (models/z-image and the MiniMax portrait steps) takes offload: "model": four 960x544 images in 41 s instead of 107 s.
  • The four MiniMax-H3 family baselines expose attention_backend (null by default; sage_hub measured about 30% faster per step on a 3090; it needs the kernels package and DIFFUSERS_TRUST_REMOTE_KERNELS=true, see docs/ACCELERATION.md). LTX-2.5 cannot use sage attention (its attention mask is refused).
  • A LoRA scale/alpha change or a scheduler shift change re-applies on the warm pipeline instead of reloading it. Removing an alpha or shift still reloads, and a pipeline_reference runs at the referenced step's own values.
  • Switching workflows in the worker keeps the warm pipelines the next workflow loads anyway, instead of releasing everything first.
  • restore_faces builds its face helper once per run instead of once per frame, and picture frames decode with libav threading, so video inputs process faster.
  • Catalog defaults corrected: compose-workflows video steps run 5 steps (was 20) and the prompt-weighting schnell workflow runs 4 steps at guidance 0 (was 20).
  • The six 9-step minimax ref2va templates drop a dead first_block cache, and minimax/shots-batch drops its release_pipeline: a no-op inside the run, but it unloaded H3 at job end, so H3 now stays resident after a shots-batch job, which is the win.

0.11.0

A short range. MiniMax H3's keyframe templates move to a 4-step turbo LoRA that halves a long take's render time, music videos hold each shot's audio slice by default and can give each entry its own singer, cost estimates reach inline workflows, and torch.compile's kernel cache now survives a reboot. Under the hood, the #790 refactor brought every module and function back under the architecture ratchet.

MiniMax H3 (plugins/dw/skills/minimax-h3, workflows/templates)

  • The nine T2VA/FL2VA templates default to lightx2v's 4-step v1.2 turbo (num_inference_steps 5, shift 6/3) at 960x544 and 1344x768: on a 345-frame take, ~7.3 min against 14.6 min for the 8-step file it replaces. Ref2VA templates stay on 8 steps; there is no tested 4-step Ref2VA file.

Music video and cuts (docs/TASKS.md)

  • music-video holds each shot's audio slice by default; measured 0.00 s sync offset on 8 of 8 shots against up to 1.37 s with the reference alone (#808). hold_audio is declared (null) on it and the match_audio chains (#795).
  • for_each items take an optional field with a default, so a music-video entry may carry its own singer (#801, #809).
  • plan_cuts divides a split sung scene's lyric across its pieces by aligned word times (#805).
  • New measure_sync task cross-correlates a shot's audio against its source slice (#802); new relevel-clip utility template, named by the clip warning (#794).
  • assess_output runs the whole-file level rules (full_scale, near_silent) (#807); no level_spread warning when a later pair_audio replaces the track (#800).

Planning and validation

  • An inline workflow is priced by pipeline identity, inheriting the catalog basis and warning where offload, quantization or frames differ (#797); the field-shift check covers derived and per-entry estimates (#796).
  • Validation refuses an image/last_image argument fed previous_result: of an earlier video step (#813).

MCP and server

  • list_assets takes limit (newest first), prefix, and reports total (#798).
  • save_prompt accepts a plain string (#806); wait_for_job falls back cleanly on a client timeout and a running job reports finished_steps (#792, #799).

Acceleration (docs/ACCELERATION.md)

  • TORCHINDUCTOR_CACHE_DIR defaults to ~/.cache/dw/torchinductor, so compiled kernels survive a reboot instead of living in /tmp.
  • .env.example documents the environment knobs dw reads; copy it to .env (loaded at import, before torch) and restart the server after editing.

Web UI

  • Per-card VRAM bars in the header, status popover and server page.
  • Step and job durations on the job page, the log and the jobs list.
  • The sidebar marks the open workspace; each workspace has its own hue.

Maintenance

  • #790: task.py split by family, the H3 block classes moved to module level, task argument rules moved to dw/task_problems.py, and one iter_steps walk for every validation pass. CI runs the ratchet first so a test failure can no longer hide it.
  • UI dependencies: vitest 5, monaco-editor 0.57; the lockfile update clears the source-map-js and postcss-selector-parser Dependabot alerts.

0.10.0

A large range: the server runs one worker per GPU, the task library grows by a dozen finishing, face, window and music-video commands, MiniMax H3 gains held audio, guide clips and refinement, Kandinsky 6.0 is onboarded, and the task registry owns argument domains and coercion. One upgrade-order note: restart the server after update_diffusers, and Kandinsky 6 needs diffusers from git main.

Multi-GPU servers (docs/WORKER_GUIDE.md, docs/SERVER.md)

  • --devices starts a persistent worker per card; a VRAM-aware dispatcher with backfill and cache affinity routes each job, and a declared vram_estimate is admitted against the pool's largest card (#675, #676, #677, #685).
  • get_health and get_memory name the workers per card; get_memory and clear_memory take a device; a job records its device ordinal and card in get_job, list_jobs and the UI (#677, #678, #682, #777).
  • A cancel sent between dispatch and the worker's start lands, and a cancelled queued job leaves no bookkeeping behind.
  • get_memory splits host RSS into anonymous and file-mapped pages (#709).

New tasks (docs/TASKS.md)

  • Finishing: grade tonal controls, sharpen, film_grain, apply_lut from a strictly parsed .cube (uploadable, listed as kind lut) or an in-memory palette, with alpha kept through every one (#633, #634, #635, #636, #775).
  • Faces: crop_face_track follows one face and records its track; paste_face_track pastes a repaired crop back, feathered (#622, #623).
  • Long video: window_video and join_windows process a clip in overlapping fixed-length windows, range-read with a uint8 join; fit_to_model and restore_to_source take a clip to a model's grid and back (#628, #629, #631, #632, #780).
  • Music video: analyze_beats (onset/DP tracking with anchor calibration), plan_cuts (a cut list from lyrics and beats on the render grid), trim_video, separate_stems (htdemucs) and ingredients_grid (#604, #607, #625, #626, #627).
  • check_script compares a take's transcript to its expected lines, shot-aware, discarding words heard over silence and Whisper repetition loops; templates/check-script retires the by-eye check (#643, #644, #645).
  • transcribe_audio resumes after a mid-clip stop and trims word bounds to waveform energy; attribute_voices handles zero-length and split lines (#617, #661, #672).

MiniMax H3

  • hold_audio generates video to a held soundtrack; the music-video and match_audio chains hold their track instead of referencing it (#618, #619).
  • guides: clips held as condition rows on t2va/fl2va, with "audio": true and guide continuity across chains; guides are charged in the VRAM projection (#648, #649, #650, #779).
  • A refine schedule block with refine_strength, its sigma grid and VAE chunk sizes pinned to diffusers (#620, #770). Width and height declare their multiple-of-32 constraint (#789).
  • The dialogue-probe audit (docs/audit) refuted the five H3 dialogue rules it tested (#640).

LTX-2.5

  • LTX2RefinePipeline and templates/ltx2/refine-in-place with lem-tuned strength ladders; face-repair and restore-long templates; both 2x templates fit and restore (#624, #630, #632, #638, #639).

Kandinsky 6.0 (#663)

  • templates/kandinsky6/ text-to-video, image-to-video and generate-and-upscale (Lite-distill + tiled SR, 24GB), the kandinsky-6 plugin skill. Needs diffusers from git main (Kandinsky6* is in no release yet); not on Apple Silicon (the VAE decode keeps MPS memory per tile).
  • update_diffusers / POST /api/system/diffusers/update now really replaces a git install whose dev version string is unchanged; install.sh and install.ps1 do the same. Restart the server after an update: admission still checks class names against the server process's old import.
  • A completed model download on huggingface_hub 1.33 is no longer reported failed.

Validation and the task registry

  • @register_command declares a task's argument domains, choices, static checks and media arguments; whole_number and real_number are the one numeric coercion in dw/tasks/, so a value is refused at validate and at run or at neither (#773, #774, #775).
  • Literal ingredients_grid, select, film_grain, join_windows, apply_lut, plan_cuts and check_script arguments are refused at validate at their argument path (#639, #646, #657, #785).
  • validate_workflow names an unpriced composition's children, and a per_entry list's index fields no longer make its estimate unknown (#655, #772).

Security

  • Media and remote-encoder requests from a workflow are dialed at the address the host policy checked (DNS rebinding closed), every redirect is re-validated, a POST follows the same path as a GET, and the HuggingFace token is dropped on a redirect that leaves the host or downgrades to http (#683, #686, #690).
  • Untrusted (the default), a URL whose host does not resolve or is percent-encoded is refused at validation rather than left to the fetch; --trust-workflows keeps the old behaviour. A file:// URL is refused as a media location at validate and at the loader (#618).
  • A fetched body is capped at 1 GiB and a fetch at 10 minutes end to end; a chunked upload is counted as it streams (#689). Use upload_asset for larger media.
  • No response carries the server's absolute paths: job warnings, log lines and phase details name an input by its asset: or output: reference, get_job no longer returns a history row's spec, and the validate plan's output_dir is gone - plan.workspace names the same place.

Fixes

  • Chains: seams record their trim and realized crossfade, a saved segment-backed video hands downstream its frames, a bare string for chain prompts is refused, and the segment label clears on a failed run (#651, #653, #660, #662, #667).
  • Shots: a for_each member's inner shot names are qualified, a cut inside a shot drops the seam attributes that opened it, and a join that saves nothing still names its shots (#670, #674, #680).
  • Audio: a shot consumed by match_levels draws no headroom warning, resample_waveform returns exactly the rounded count, video_frames keeps the source fps, and the music-video soundtrack is built from the song pieces under each cut (#671, #673, #716, #788).
  • uint8 frames are no longer colour-inverted by export_to_video (#679); get_job_workflow folds recorded arguments on an unrealized job (#647); lazy diffusers type resolution is serialized across threads (#787).

Plugin

  • kandinsky-6 skill; minimax-h3 gains references/dialogue.md, the guides and music-video render rules; series-episodes takes an optional look step (palette, apply_lut, film_grain) (#637, #642, #665, #762).

0.9.0

A smaller range than 0.8.0: the LoRA catalog grows to cover three more bases, the cost planner is corrected for list-driven and for_each steps, and export_job reports its zip's real auth gating. No upgrade-order changes.

LoRA catalog (docs/LORAS.md)

  • 11 LTX-2.5 entries added: IC-LoRAs for alpha generation, clean plate, colorization, day-to-night, layout-to-render, detail refinement, restoration, SDR-to-HDR and water simulation, plus cinemagraph and slow-motion control.
  • MiniMax-H3 goes to 13 trial and 3 rejected entries beyond the 0.8.0 set: styles, speech, orbits, action and motion, and a diffusers-native 4-step turbo. FastVideo FastH3 (.diff keys), RAVEN (unrecognised prefix, loads nothing silently) and TaoMate (lowercase lora_a/lora_b) are rejected.
  • New bases: Qwen-Image-2.1 (10 trial, including three few-step distills that need their scheduler and sigma overrides, plus edit LoRAs; Fun-Acc rejected) and Z-Image-Turbo (13 trial). The Z-Image entries match Tongyi-MAI/Z-Image-Turbo only; whether they apply to the SDNQ checkpoint is untested.
  • Every new entry pins the repo's current sha, and its header was read against the installed diffusers converter.

Cost planning

  • other_device figures are re-priced for for_each counts and shifted list drivers (#589, #590).
  • A shifted per-entry field in a list driver resets to unknown, and a summed child figure takes the children's basis (#593). A numeric string in a list-driver entry compares as its number (#593).
  • Measured MPS cost entries for templates/ltx2/text-to-video and Music 3 (#590).

Fixes

  • assemble-and-score threads sample_rate into its edit join, and mixed-rate shots resampled to a pinned sample_rate no longer draw a warning advising you to pass it (#594).
  • export_job reports the zip's real gating: /exports/*.zip is ungated like /outputs, so auth_required is false whether or not the server has a token. The MCP next text and the skills now say to fetch open_url and unpack into exports/ to bring a project home (#595, #592).

Plugin

  • The ltx-2.5, minimax-h3 and minimax-music3 skills moved request-specific detail (the LTX caption spec, H3 checkpoint and LoRA combinations and cuts recipes, Music 3 loudness) into references/ beside each SKILL.md, which are read when the skill points there. The 12 KiB cap applies to SKILL.md alone; a new test fails on an unlinked reference or a dead link.
  • The H3 and LTX skills point at list_loras.

0.8.0

Most of this range landed on develop without PRs, so the auto-generated notes are close to empty. Most of it is the UI stabilization (gates 0-4) and the second dw_mcp pass; the per-phase detail is in docs/stabilization/ui/. The rest is the LoRA catalog, the H3 latent upscaler and two security fixes.

Upgrade order

  • The MCP tools now call new server routes, so a stdio dw-mcp needs a dw.serve at least as new as itself: upgrade the server first. A mounted MCP (dw.serve --mcp) is always the same version.
  • loras is now a reserved workspace name. A workspace already called loras is no longer listed, and the server logs a warning at start; rename its directory.

Security

  • GHSA-fwg5-jfjg-fxpf: a job error no longer carries absolute server paths. A file under the job's asset search path is reported as asset:<name>, one under its output directory as output:<name>, and asset not-found messages name the libraries searched by origin (workspace, common, examples) instead of by directory. The server log keeps the paths. Still open: a traceback's frame lines name the server's source and site-packages paths, and get_job returns the traceback.
  • GHSA-crqf-hw9p-r739: create_workspace's MCP result is name, default, current and next only; list_workspaces(detail=true) is the opt-in for folder paths. POST /api/workspaces is unchanged.
  • GET /api/loras/recommend and recommend_loras: hub_error names the exception type, or the HTTP status, never the exception's text, which could name the server's HF cache directory. The log keeps the full error.
  • UI lockfile bumps for open Dependabot alerts (devalue, dompurify, brace-expansion, undici).

LoRA catalog (docs/LORAS.md)

  • A library of the LoRAs tried on a base model, one JSON file each, marked proven, trial or rejected with the evidence. The writable loras/ at the server root is shared by every workspace and is read ahead of the shipped read-only one. An entry's model_name, weight_name, revision and scale.default drop straight into a step's loras entry. Base models match exactly.
  • Shipped entries: 7 for MiniMax-H3 (Realism People and the turbo keyframe and reference adapters as proven; Acc-PDD, HyperFlow and FastH3 as rejected, because they need loaders dw doesn't have), 4 LTX-2.5 IC-LoRAs and 2 for FLUX.
  • MCP: list_loras, save_lora, and the opt-in recommend_loras. It is the only call that searches the Hugging Face Hub. It returns the catalog's entries ranked against the request first, then Hub adapters whose card declares that exact base. Nothing is downloaded; a single-weight repo's header is read to check its layout. Hub rows are candidates to trial and carry warnings (will_not_load, unknown_format, gated, stale, ...).
  • HTTP: GET /api/loras, GET/PUT/DELETE /api/loras/{name}, GET /api/loras/recommend, GET /api/lora-schema. One Hub search runs at a time per server. A concurrent call gets the catalog rows plus hub_error, and the query is capped at 200 characters and 4 search terms.

New

  • upscale_h3_latents and decode_h3_latents take a 960x544 MiniMax-H3 take to 768p in latent space (#499). This is the build withdrawn before 0.7.0, relanded with the ComfyUI node's normalization wrapper. Its weight_name must be a bare file name, and model_name must be a Hub repo id. docs/WORKFLOW_GUIDE.md has the recipe; the #500 A/B found it softer than a native 768p render.
  • Server routes the MCP tools now use:
    • GET /api/gallery/{name}/image returns an image output or asset: cropped, fitted to max_dimension and halved until it fits max_bytes.
    • max_total_bytes on /frames shrinks every tile to one shared size.
    • PATCH /api/workflows/{name} applies a JSON merge patch under the save lock. save_workflow's patch mode calls it.
    • DELETE /api/jobs/{id}/run deletes a finished job's run directory. delete_output(job_id=) calls it, and a job still queued or running is a 409.
    • findings on gallery metadata reports measured level problems (full_scale, near_silent) with the fix.
    • acknowledge in a cost 409 is the {fingerprint, minutes, downloads} to resend.
    • output_kinds on a job, and on each step_end event, maps each file to image, video, audio, text or null.
  • Every JSON route declares its response model, and the UI's types are generated from the server's OpenAPI document.
  • dw-mcp no longer imports Pillow; images and frame tiles are fitted on the server.
  • Web UI: dialogs and popovers run on Bits UI, with keyboard help on the modal. Every datalist is now a suggestion combobox that keeps typed text. The job page renders outputs by the server's media kinds, audio and text included.
  • Plugin: the minimax-h3 skill names the 4-step draft and the stacked Realism People LoRA, and rules out loader-only LoRAs (#585). ltx-2.5 names the wait reply's timeout_applied_seconds and timeout_capped (#546).

Fixes

  • Validating a workflow that needs an uncached gated repo (e.g. flux-dev on a fresh box) no longer answers 500. A response that fails its model is logged and sent rather than 500ing, so a retried POST /api/jobs can no longer queue a job twice.
  • A scheduler parameter defaulting to -inf reads "-inf", not null.
  • The phase watchdog measures silence from the last event, so it no longer reports false stalls.
  • The unquantized FLUX templates use sequential offload. Model offload left the 22 GiB transformer no room on a 24 GB card (#580).
  • mix_audio's rate-mismatch warning no longer advises sample_rate, which relabels the rate rather than resampling (#586). find_loop_bed's no_loop_bed names the in-shot rule that actually ruled windows out (#587).
  • Deleting a job's run twice says the run is already gone.
  • MCP: upload_asset confines a source to the workspace the call names. get_output_frames' audio excerpts share the response byte budget. A bad DW_MCP_MAX_WAIT_SECONDS keeps the default with a warning instead of failing the import. The startup probe reads a 401 from the status code.
  • UI: workspace, folder and prompt names follow the engine's one name-segment rule. The dtype select keeps a dtype it doesn't list. Text outputs render as text, and audio and text stay out of workflow card proofs (#573).

For developers

  • UI architecture ratchet over ui/src, in CI alongside the engine's. Reference prefixes are spelled only in references.ts, and the UI's copies of engine vocabularies are pinned to their owners.
  • The response contract is generated under the FastAPI and Pydantic pinned in constraints-openapi.txt. Every route api.ts calls must declare its response model.
  • dw_mcp's copied constants are pinned to their engine owners (tests/test_mcp_twins.py), and every tool that takes acknowledged_cost is checked to refuse without it.
  • CI runs the e2e suite before develop moves, once per push while the release PR is open.

0.7.0

  • A sub-workflow path is resolved by one function (library.resolve_sub_workflow_reference) at every site, so a path a run can open is one validation, the realized workflow's digest and the observed-cost lookup can open too.
    • builtin:builtin:x.json no longer loads x.json: only the leading prefix is stripped, so the name builtin:x.json is looked up and reported as SubWorkflowNotFound.
    • At run time a missing builtin: workflow now raises SubWorkflowNotFound (naming the packaged root) instead of validate_workflow_path's missing-file error.
    • The realized workflow's sub-workflow digest and a composed child's observed cost now fall back to the same catalog root, so a catalog sub-workflow a run could open is also digested and costed.
  • A run that fails before it opens its run directory no longer rewrites the previous run's manifest.json when the same workflow instance is reused: Workflow.run resets the directory and version it carried.
  • The per-variant lines of a kernels "Cannot find a build variant" error are sorted by dw (kernel_availability.stable_message), so the message no longer varies by process.
  • The argument_template schema description now says what the code does: handed arguments are held on the child at run time, never written into the definition, and an authored value is the fallback.
  • gain_audio rounds a frame-addressed region's end once, as slice_audio does, so a region's end can no longer be one sample off the matching slice's.
  • concat_videos refuses a track with no sample rate (concat_videos: '<name>' has audio with no sample rate) instead of joining it unresampled at the wrong speed and pitch; dissolve_videos gives the same message in place of the resample error. Save that step with audio_sample_rate in its result and join the saved file through an output: reference. An unpinned dissolve_videos with such a track now raises this ValueError rather than a TypeError.
  • For developers (Python paths only; nothing on the API or MCP reaches them):
    • Workflow's one-line check methods are gone: validation_context, sub_workflow_warnings, adapter_warnings, inherited_vram_warnings, slice_past_end_warnings, shot_span_warnings, null_variable_argument_warnings and cache_hits. Call validation.workflow_context(workflow, ...), validation.run_warning_check(workflow, "<name>", ...) and workflow_run.cache_hits(workflow, arguments).
    • Reference prefixes (variable:, asset:, output:, ...) are read and built only through dw.references; a hand-written prefix check elsewhere fails the prefix_handling ratchet.
    • scripts/arch_metrics.py --check docs/stabilization/baseline.json runs in CI and scripts/preflight.sh, so a PR that raises any architecture metric goes red. The module-size metric is modules_over_size_ceiling (fail over 1,100 lines, warn over 1,000), renamed from modules_over_1000_lines.
    • Agent context: CLAUDE.md files went from 957 to 129 lines; docs/ARCHITECTURE.md is the seam map (concept, owning module, rule, enforced-by).
  • The stabilization freeze is lifted (stabilization-gate-4).

0.6.0

This range landed on develop without PRs, so the auto-generated notes are close to empty. Most of it is the architecture stabilization (2026-09-28 to 2026-10-01); the per-stage detail is in docs/stabilization/ROADMAP.md. Paste this section into the GitHub release body once the tag has published (gh release edit v0.6.0 --notes-file ...).

Breaking and behaviour changes

  • The three library listings (GET /api/workflows, /api/prompts, /api/assets) share one envelope. Removed: workflow_dir, prompt_dir, asset_dir, sources (workflows), prompt_dirs, asset_dirs and origins (prompts). The writable root is now the libraries entry with writable: true and origin: "workspace". Renamed: assets libraries[].dir is root, and the MCP compact workflow listing's sources is libraries. Added to all three: libraries: [{origin, root, writable}] in search order, shadowed: [{name, origin, shadowed_by}], and origin/writable on every entry. Item keys, details, folders, sort order and the single-item reads are unchanged.
  • The teacache pipeline configuration key is removed. A workflow that sets it fails validation ('teacache' was unexpected). Use cache (first_block, mag, taylorseer; docs/ACCELERATION.md). The guide's "TeaCache" sections are gone, so get_guide(section="TeaCache") no longer resolves.
  • POST /api/jobs, POST /api/jobs/{id}/rerun and a workflow save whose validation itself crashes answer 500 internal error - the server log has the detail when something fails after the request was admitted. They used to answer 400 for any exception. A refused request is still 400. A crashing validation check is one check '<name>' failed (<ExcType>) - the server log has the detail finding and every other check still reports; a crashing warning check is a warning and never refuses. A null-path error no longer starts with None: .
  • The REPL is removed (dw-repl, python -m dw.repl; the guide is now docs/WORKER_GUIDE.md). python -m dw.run is an HTTP client of a running dw.serve and takes --server, --workspace (a server workspace name, no longer a directory) and --token. -o/--output_dir, --prompt-dir, --asset-dir, --output-layout, --trust-workflows and -l/--log_level are gone; set them on dw.serve. httpx is a base dependency.
  • save_workflow, save_prompt, upload_asset and keep_output no longer return the server's absolute path. They return the name, workspace, origin and reference (#527).
  • A job runs the definition admission checked. Editing or deleting the file while the job waits does not change it; a rerun admits the file afresh. An asset, output, prompt or sub-workflow that changes or disappears while a job waits now fails at the step that reads it, not at job start.
  • A rerun rechecks its asset:/prompt:/output: references and answers 400 when one no longer resolves. Admission refuses an asset: whose workspace copy is a symlink out of the library.
  • Upload, keep and delete with no asset library all answer 409 This workspace has no asset library; upload used to write into outputs. Deleting a read-only library entry is one 403 message for all three libraries.
  • In a named workspace, prompt: resolves against the server's prompt library everywhere, including plan building; creating a workspace no longer makes an empty prompts/. prompt:name.json now resolves.
  • More is caught at validate: a vram_estimate is checked even when the workflow has no cost block (#552), and only against the pipeline it was measured for (#516); a {"location": ...} media entry is size-checked and probed like a plain path (#518), so a too-short dissolve_videos input is refused; a literal numeric sample_rate on slice_audio is honoured as a relabel; a literal null threshold or index on select is refused; audio_bleed_ms with only one input warns (#565).
  • get_output_frames refuses names or boundaries without seams. They used to be dropped silently (#554).
  • concat_videos fills a silent input with silence for its length instead of shifting later shots' audio early (#553), and fits each input's audio to its own frame count, warning audio_frame_drift for one that runs long (#562). A step's declared result.fps is handed on to later steps, and join_into_song refuses a contradicting fps (#513). A frame-addressed slice_audio end is rounded once, which can move it by a sample (#557).
  • A composed for_each member whose parent saves the result now gets its template's fps and audio fit. LTX-2.5 image-to-video members were written at 8 fps (#561).
  • Embedded image metadata no longer carries a generator string, keeps loras and ip_adapter (so "open as workflow" works for adapter steps), and a composed child's steps no longer carry argument_template.
  • After upgrading: every plan fingerprint changes once, so a bound acknowledgement made before the deploy gets one 409. base-and-refiner, ltx2/generative-upscale, ltx2/refine-clip and ltx2/two-stage miss the step cache once.

New

  • templates/ltx2/upscale-clip: generative 2x upscale of the caller's clip, with its soundtrack paired back on (#548). templates/ltx2/refine-clip: 2x latent refine of the caller's clip, about 2.9 min cold on an RTX 3090; the source is trimmed to num_frames first (#549).
  • The join_into_song task joins dialogue shots into a song, placing the song from the measured dialogue length at run time (#513). The minimax-h3 skill and the workflows guide carry the recipe (#514).
  • The find_loop_bed task ranks room-tone loop windows as they sound looped (near-programme material, lap-rate modulation, ticks). It is shot-aware: no candidate crosses a shot boundary. shot_dead_air, the minimax-h3 and series-episodes skills point at it (#544/#545).
  • keep_output records the source job, run, version and workspace, so get_gallery_metadata on a kept asset reports its job (#556).
  • A composed sub-workflow's manifest entries carry parent_step (#560).
  • pair_audio warns shot_position_regridded when it moves a measured shot start by 5 ms or more (#563), and loads a string video path.
  • A job records the full warning set /api/validate reports, and validate's argument-error 400 carries warnings. workflow.json records the folded variables: realized constants, resolved list entries and snapped values.
  • get_job_workflow and the job export answer with the admitted definition when the workflow file has since moved or stopped parsing.
  • dw.run prints a job's whole event tail and its warnings, exits 130 on Ctrl-C before a job id, and reports a timeout as one error line.
  • A fully cached rerun no longer loads released pipelines: ltx2/two-stage with the same seed went from 78.8 s to 0.77 s on an RTX 3090.
  • Clearer error text: the image_crf error names the video passed where an image was expected (#511); an image task given a video, and a join size mismatch, name the route through video_frames and resize_rescale (#550/#551); the frame-size error names every mismatched video by its index; dissolve_videos names every too-short input in one error. Audio shaping steps log seam fades, score offset and fade-out (#564), and audio_bleed logs each bled seam (#566).
  • The plugin skills tell an agent to pass plan.estimate plus a margin as wait_for_job's timeout_seconds (#546).

Fixes

  • Validation and workflow.json see constraint-snapped values.
  • Sub-workflow warnings are strings at the author's step and use the caller's arguments; the web UI showed [object Object].
  • Validation, submit and the worker check asset: references against the job's own workspace, not the default one.
  • A run's directory and version are claimed atomically under a lock in dw's settings directory, so two runs of one workflow cannot share a number, and a gallery delete no longer races an opening run.
  • A prompt: name that climbs out of the prompt library is refused without probing the disk outside it. A missing file there used to be reported as not found.
  • manifest.json is written atomically, so a killed worker leaves the previous manifest (#517).
  • Step cache: a step that borrows a pipeline (pipeline_reference, reused_components) misses when anything up its borrow chain changes, and a stale resident pipeline is no longer reused.
  • Whisper transcribe_audio no longer truncates a multi-line clip in plain-text mode (#559).
  • mix_audio's dB-typo warning ignores non-integer gains such as find_loop_bed's (#555).
  • The shot-name collision warning drops its doubled step prefix and no longer repeats on pair_audio/interpolate_frames (#568).
  • Detail-cache pruning is safe under concurrent requests; a warning helper that raises never refuses a job; model_name refusals use one wording (#529).

Internal (for developers using dw as a Python library)

None of this reaches the HTTP API or MCP.

  • Import cycles are gone, no module is over 1,000 lines and no function over 150. Reference prefixes are spelled only in dw/references.py.
  • dw.workflow_sources is dw.library (WorkflowSource is LibraryRoot, plus LibraryPath).
  • dw.loudness, dw.media_audio, dw.media_info and dw.teacache are gone; their contents are in dw.dsp and dw.media. normalize_audio and the other dynamics tasks are in dw.tasks.audio_dynamics, the join helpers in dw.tasks.joins.
  • result.py split into dw.writers, dw.audio_qc and dw.output_extraction. pipeline_processors/pipeline.py split into placement, components, adapters and progress. Workflow.run's phases are in dw.workflow_run, pipeline ownership in dw.pipeline_ownership, validation in dw.validation (which absorbed dw.result_fps, dw.null_media and dw.select_validation), the trust gate in dw.trust.
  • pipeline_cache_key and step_pipeline_keys are in dw.step_cache; dw.plan no longer re-exports unseeded_cache_warnings.
  • create_app is a factory over dw/server/routes/*, and app.dependency_overrides does not reach their routes. Worker messages are typed in dw/worker_protocol.py (probe_id is request_id; ping/pong removed). MCP tools are in dw_mcp/tools_*.py.
  • An object passed to Workflow.run is the object the steps use, so an in-place write is visible to the caller. A composed child no longer deep-copies its handed arguments.

0.5.0

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

  • templates/ltx2/two-stage no longer has full_width/full_height. They never changed the output, which is always exactly 2x width/height. A call that still passes them is refused as an unknown variable (#506).
  • Every LTX-2.5 template refuses a width or height that isn't a multiple of 32 at validate and at run_workflow, before the pipeline loads (#505).
  • The MiniMax-H3 Ref2VA VRAM ceiling is now projected per step, after for_each expansion, and adds 1 GB for each non-null reference. At 1344x768 on a 24 GB card the limits are 243/209/175/141 frames with 1/2/3/4 references. Calls that used to pass are now refused, for example 209 frames with 3 references. A for_each refusal names the member (Member 'shot@...'). Eight more Ref2VA templates declare the ceiling (#479/#501).
  • A workflow with no vram_estimate gets a vram_projection_inherited warning, not a refusal, when the catalog template that loads the same pipeline would be over its ceiling (#502).
  • The VRAM ceiling is checked against the serving device's own cost entries (or its measured capacity), not every CUDA card in the catalog. A 64 GB Mac is no longer refused against the RTX 3090 entry (#470).
  • asset:, prompt: and output: references written directly into a step are resolved at validate and at submission. A stored workflow with a literal reference to a missing file now fails validation (#494).
  • validate_workflow refuses a for_each item's reference that resolves to null, which the run already refused (#478). It also refuses a frame-size mismatch between the asset:/output:/path inputs of concat_videos and dissolve_videos (#504).
  • A transcribe_audio step with timestamps must set content_type: application/json (#498).
  • A weight_name (a LoRA's, an IP adapter's) must be a relative file inside the model repo. An absolute path, a backslash, a drive or a ./.. segment is refused at validate. Media path and glob refusals no longer list the server's directories.
  • previous_result:<step>.<key> for a key that no result carries is an error. It used to run zero iterations and save nothing.
  • templates/minimax/video-with-audio-768p runs its turbo LoRA at its trained strength. It had been running at 16x, so its output changes (#468).
  • On macOS, parallel checkpoint loading is off by default, and so is SDNQ quantized matmul on MPS. Group-offload CUDA streams are dropped, with a warning, when the onload device isn't CUDA or XPU. An explicit HF_ENABLE_PARALLEL_LOADING still wins (#470).
  • A for_each whose members produce the same shot name now warns shot_name_collision (#508). assess_output has a new shot_dead_air finding for silent gaps inside a shot, and concat_videos marks its joins as hard cuts, so seam_frame_jump no longer fires on a deliberate cut (#465/#466).

New

  • Apple Silicon (MPS): the CUDA templates, LTX-2.5 and minimax/music included, run unchanged. Memory figures and the chip name are real, and torch's CPU-fallback warning is shown (#470).
  • The attribute_voices task names which reference voice sings or speaks each line or window, by timbre. It uses demucs separation and ECAPA embeddings, and demucs is now a dependency. list_tasks marks the read-only probes with an assessment flag (#485/#494). The minimax-music3 skill points to it for multi-singer songs (#495).
  • normalize_audio(limit=true): a true-peak look-ahead limiter that reaches target_lufs past a loud transient. It warns limiter_heavy past 6 dB of reduction and target_lufs_capped (limited: true) past 12 dB (#474/#496).
  • assemble-and-score takes target_lufs (#467) and limit (#497, still open). The series-episodes skill says to match episodes downward to one series target. Unfinished (#497): with limit: true the mix holds -3 dBTP, but the AAC-muxed film can land up to about 1 dB above it.
  • transcribe_audio(timestamps="segment"|"word") returns {text, chunks}. Unset, it still returns plain text (#483).
  • templates/ltx2/extend-clip takes a clip to extend an existing clip. The opening isn't generated when one is given (#446).
  • save_workflow, delete_workflow, upload_asset, delete_asset and list_assets take workspace= for one call. Their replies name the workspace they acted on (#463).
  • A LoRA with model_name: null is switched off and warns lora_disabled. It used to validate clean and then fail after the model load (#469).
  • get_guide(section=...) reaches a ### subsection and returns its parent_section (#503).
  • upload_asset's refusals give a working curl to POST /api/uploads and state the 200 MB limit. The guide explains that for_each item-level from_previous_result must name the member, as slice@<entry> (#481/#482).
  • A running job's manifest.json is rewritten after each step, so finished for_each members show up before the run ends (#480).
  • SECURITY.md: report vulnerabilities privately, not as issues.

Fixes

  • templates/ltx2/extend-clip frees the opening's pipeline before the extension loads, so it no longer holds two LTX-2.5 stacks on the GPU (#523).
  • concat_videos/dissolve_videos load a {"location": ...} entry in videos (#510).
  • A modular step whose outputs include latents no longer crashes at save (#507). pair_audio unwraps a pipeline's batch of one video.
  • transcribe_audio works on clips over 30 s, and a non-Whisper model on a long clip still returns text.
  • segment works again: the SAM2 KeyError is fixed, and the GroundingDINO query is written the way it scores. Dead default input URLs in several templates are replaced (#470).
  • upload_asset(file_path=...) accepts the writable shared asset library (#448).
  • host_memory_job_peak_rss_mb is a running max (#457).
  • dw.serve exits on SIGTERM with an MCP client still connected (#477).
  • The shot_dead_air finding names the room-tone remedy. The normalize_audio warning no longer says that gaining down always succeeds (#491/#492). The docs describe dead_air_floor_dbfs correctly (#519).
  • The minimax-h3 skill carries the 24-shot field report's prompting rules (#484).

0.4.0

The auto-generated notes for this range are a single merge line, since the work landed on develop without PRs. Paste this section into the GitHub release body once the tag has published (gh release edit v0.4.0 --notes-file ...).

Breaking and behaviour changes

  • download_output over a dw.serve --mcp endpoint refuses a call with no destination. It used to write into the server's own directory (#353).

  • Untrusted workflows are refused in more cases (#409-#413):

    • a *_type that doesn't resolve to a class, or that isn't a kind a workflow constructs: a diffusers or transformers model, pipeline, scheduler, tokenizer or processor, a quantization config, an auto factory, a diffusers reference/condition type or an attention processor. A plain torch class such as torch.nn.Linear is now refused;
    • constant: walks through _ names or out of the allowed packages;
    • URLs with backslashes;
    • text/html and text/xml result types;
    • media hosts that aren't globally routable, including 100.64/10 (CGNAT, and so Tailscale);
    • more than 5 redirects;
    • images over 50M pixels.

    Listings and export zips drop symlinks that escape their root. --trust-workflows lifts all of these.

  • run_workflow validates the caller's arguments when it queues the job (#414/#415). validate_workflow(arguments={}) checks a run with no values supplied, not just the document (#364).

  • A fractional value for an int variable is refused (#338), and so is a still image passed as a video argument (#347).

  • templates/minimax/music normalizes to -3 dBFS instead of -1, so its output is quieter (#362).

  • Every response carries X-Content-Type-Options: nosniff and X-Frame-Options: DENY. Active document types under /outputs and /inputs are served with Content-Security-Policy: sandbox.

  • A validate-time probe reads only a literal media path that the run itself would be allowed to read.

  • A dict or list passed to a string-typed variable is refused (#433). templates/ltx2/keyframes takes first_image/last_image as plain strings, not {"location": ...} (#431/#433).

  • loop_frames returns float32 frames in [0, 1] instead of uint8, the shape LTX2ReferenceCondition needs; a keyframe condition still wants frames_as_array. ltx2/reference-sheet's default asset is now asset:reference_sheet.jpg (#444).

  • validate_workflow refuses a components name the pipeline doesn't register; duration_head is gone from the in-context LTX-2 templates (#442).

  • A {"media_type": "image"} reference on a video argument loads as a one-frame still (#443).

  • pair_audio fit: "video" always fits, and warns on any nonzero gap (#428/#429). concat_videos and dissolve_videos pad a short joined track to the frame grid, warning (joined_audio_padded_to_frames) only when the pad is a frame or more; a residual the AAC mux trims off is logged, or warned as joined_audio_short_after_mux from a frame up. media.shots is measured against the file as written (#426/#435/#454). Neither warns about resampling inputs that agree to a pinned sample_rate (#453).

  • New warnings: match_levels_near_silent (#434), and shot_span_overrun from the probes plus a validate-time check (#425).

  • Error text changed: delete_workspace (#437/#438), the sub-workflow path refusal names the places it looked (#422), and /outputs/asset:... misses name the asset without server paths.

New

  • The assess_output tool and GET /api/gallery/{name}/assess, plus the probe tasks analyze_shots, analyze_seams and analyze_sync_drift (#387/#388).
  • A joined video records its shot boundaries (media.shots). get_output_frames(seams=true) uses them, so it no longer needs boundaries (#385).
  • Run versions (v<N>):
    • list_gallery returns run_id/version and filters by folder and version;
    • output:<wf>/v<N>/<file> references;
    • wait_for_job returns run_version;
    • export zips download as <wf>-vN-<job>.zip.
  • list_gallery(media=true) adds durations, and output: names work in gallery reads (#356).
  • DW_PUBLIC_URL adds absolute URLs to gallery and export responses. export_job also returns auth_required and open_url (#353).
  • A grade task for images and video: exposure, contrast, saturation and temperature/tint (#349).
  • The templates/minimax/shots-batch H3 template (#352).
  • Every generative template takes a seed argument (#351).
  • normalize_audio(target_lufs), and integrated_lufs plus true peak in media metadata (#361).
  • gain_audio with no region gains the whole track (#395).
  • world_fade_out_ms on assemble-and-score (#339).
  • Download progress shows in phase_detail (#343). phase_stall events now read as informational (#357).
  • workflow, inline_workflow and prompt also accept a JSON string. A mistyped workflow name gets suggestions from the catalog (#397).
  • Host caches are released when each job ends (#368), and the skills point at clear_memory.
  • get_job_events(kinds=...) and ?kinds= on the event-log route; a kind matches an event's event or its kind, so ["phase_stall"] selects one warning type (#436).
  • get_memory reports the step cache's entries and retained_bytes (#418).
  • get_output_image and /outputs resolve asset: references (#445), and get_output_frames(seams=true) works on linked assets (#430).
  • Compact assess_output lists each finding once (#427). Shots are named by their source when joined inputs already carry shots (#432).
  • A task-only workflow's run history counts, so its estimate can quote basis: observed (#439). The Music 3 hint no longer shows on video (#441).

Fixes

  • The step cache's retained-byte count no longer only grows (#418).
  • templates/ltx2/keyframes (#431), restore-decompression (#442) and reference-sheet (#444) run with their own defaults again.
  • Joined audio and shot maps stay on the frame grid through repeated joins (#423, #426, #428, #435).

Releases are cut by pushing a v<semver> tag. CI does the rest.

Before merging develop into master, run scripts/preflight.sh and get it passing. It covers more than CI: ruff over the whole repo rather than dw dw_mcp tests and the real-model integration tests (pytest -m integration), neither of which CI runs. (CI runs the UI's Playwright e2e tests on every develop push and on PRs into develop; the release PR from develop relies on the push runs.)

scripts/release.sh 0.38.0
scripts/release.sh 0.38.0-alpha.1 "UI front end"   # optional tag message
scripts/release.sh 0.38.0 --next 0.39.0-alpha.1     # and reopen develop

The script bumps pyproject.toml (the single source of the version — dw.__version__ reads it at runtime) and sets the same version in plugins/dw/.claude-plugin/plugin.json, so an installed plugin names the engine it was written against; it commits just those two files, pushes master, tags the bump commit v0.38.0, and pushes the tag. It refuses a malformed version, a branch other than master, an existing tag, or a dirty index (unstaged changes elsewhere are fine — the release commit is path-limited to those two files).

Before it bumps anything it runs the integration tests, the gate CI's accelerator-less runners cannot, and refuses to release when they fail or when the machine has no CUDA or MPS device - there they would skip and pass having run nothing. Cut a release from the Mac or lem, with the venv active. The tests run against the working tree, so unstaged changes are part of what they check.

--next <version> finishes the release on the other branch: it merges master back into develop (a fast-forward when nothing landed there since the release PR), sets <version> in the same two files, commits chore: open <version> on develop and pushes develop, all in a temporary worktree, so it works while develop is checked out elsewhere. Without it, do that by hand, or develop goes on reporting the previous pre-release.

CI runs on every push to develop as well as master - the agent loop pushes develop directly, with no PR - so a failure shows up against the commit that caused it, not first on the release PR.

By hand, the equivalent is:

# 1. Bump the version in pyproject.toml:
#    version = "0.38.0"
# 2. Set the same version in plugins/dw/.claude-plugin/plugin.json
git commit -m "release 0.38.0" -- pyproject.toml plugins/dw/.claude-plugin/plugin.json

# 3. Tag the bump commit and push
git tag -a v0.38.0 -m "release 0.38.0"
git push origin master v0.38.0

The tag must point at a commit whose pyproject already declares the same version — the release job checks and refuses a mismatch.

The tag triggers the full CI chain: backend tests, UI lint/type-check/ unit tests, then the wheel build (SPA compiled into the package via scripts/build_dist.sh). Only if all of that passes does the release job run — it verifies the tag matches the pyproject version, then creates a GitHub release named after the tag with auto-generated notes and the wheel + sdist attached.

Note on pre-release numbering: Python packaging normalizes semver-style pre-releases, so a 0.38.0-alpha.1 version builds a wheel named 0.38.0a1. The tag, pyproject, and release stay in the semver form; only the wheel filename and pip metadata show the normalized one.

A pre-release tag like v0.38.0-rc1 is marked as a pre-release on GitHub. Tags that aren't v + semver (or that don't match the declared versions) fail the release job before anything is published.

After the GitHub release, the pypi job publishes the same artifacts to PyPI via trusted publishing (OIDC — no token stored anywhere). One-time setup on pypi.org under Publishing: add a trusted publisher for project diffusers-workflow with owner dkackman, repository diffusers-workflow, workflow ci.yml, environment pypi (use "add a pending publisher" before the first release, since the project won't exist yet). Pre-release versions are hidden from plain pip install; they need pip install --pre.

Note: released diffusers from PyPI may lag the newest model pipelines this project targets — a PyPI install can need pip install git+https://github.com/huggingface/diffusers on top.

To rebuild artifacts without releasing, run the CI workflow manually (workflow_dispatch) — the wheel job uploads dist/* as a workflow artifact.