A release is a v<semver> tag on master. CI builds and publishes it.
Before merging develop into master, get scripts/preflight.sh passing. It runs the real-model integration tests (pytest -m integration), which CI cannot run, and the UI's full npm run preflight (see TESTING.md).
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 developscripts/release.sh, run on master with the venv active:
- Refuses a malformed version, a branch other than
master, an existing tag, or staged changes. Unstaged changes elsewhere are fine; the release commit is path-limited. - Runs
pytest -m integration -n0against the working tree, and refuses when they fail or when the machine has no CUDA or MPS device (there they would skip and pass having run nothing). - Sets the version in
pyproject.toml(the single source;dw.__version__reads it at run time) and inplugins/dw/.claude-plugin/plugin.json(tests/test_plugin_skills.pyholds them equal), commits those two files asrelease <version>, and pushesmaster. - Tags the bump commit
v<version>(annotated with the message, defaultrelease <version>) and pushes the tag. - With
--next <version>: in a temporary detached worktree, mergesmasterintodevelop(a fast-forward when nothing landed there since the release PR), sets<version>in the same two files, commitschore: open <version> on developand pushesdevelop. It works whiledevelopis checked out elsewhere. Without--next, do this by hand, ordevelopkeeps reporting the previous pre-release.
By hand, the equivalent of steps 3-4 is:
# Set version = "0.38.0" in pyproject.toml and plugins/dw/.claude-plugin/plugin.json
git commit -m "release 0.38.0" -- pyproject.toml plugins/dw/.claude-plugin/plugin.json
git tag -a v0.38.0 -m "release 0.38.0"
git push origin master v0.38.0.github/workflows/ci.yml runs on v<semver> tags:
- backend and ui: the same checks as on every push.
- wheel (after both):
scripts/build_dist.shbuilds the SPA, copies it intodw/server/ui, copiesdocs/*.mdintodw/docs(the guides an installed server serves), and runspython -m build. It uploadsdist/*as an artifact. - release (after wheel): fails unless the tag is
v+ semver and equals thepyproject.tomlversion, then creates a GitHub release named after the tag, with auto-generated notes and the wheel and sdist attached. A tag with a pre-release part (v0.38.0-rc1) is marked as a pre-release. - pypi (after release): publishes the same artifacts to PyPI by trusted publishing (OIDC; no token is stored). One-time setup on pypi.org under Publishing: a trusted publisher for project
diffusers-workflow, ownerdkackman, repositorydiffusers-workflow, workflowci.yml, environmentpypi(a pending publisher before the first release). A pre-release needspip install --pre. - docker (after release): builds
docker/Dockerfile, starts the image on the CPU runner and waits for/api/health, then pushesdkackman/diffusers-workflow:<version>to Docker Hub. A release also moveslatest; a pre-release does not. One-time setup: the public Docker Hub repositorydkackman/diffusers-workflow, a read/write access token, and a GitHub environmentdockerhubholding it as secretDOCKERHUB_TOKEN. A failed push leaves the GitHub release and PyPI as they are; re-run the job.
Python packaging normalizes a semver pre-release, so 0.38.0-alpha.1 builds a wheel named 0.38.0a1. The tag, pyproject.toml and the GitHub release keep the semver form.
To build artifacts without releasing, run CI by hand (workflow_dispatch); the wheel job uploads dist/*.
A PyPI install gets released diffusers, which may lag the newest pipelines; install git+https://github.com/huggingface/diffusers on top.
GitHub generates notes from commits at tag time. The sections below are the hand-written notes: the newest is drafted at release prep and pasted into the GitHub release body once the tag has published (gh release edit v<version> --notes-file ...). Items a branch's author wants the next note to name go in a new section at the top.
This release is long-form media: one playbook for a piece longer than one clip, with every lever measured on a bench before it became a default (#1199). An H3 chain now carries motion and voice across its seams by default, picks its drift counters from the kind of shot, can be asked for by length, checks each segment and re-rolls a bad one, and can have one segment repaired in place afterwards. Long narration and long music join from takes and sections, a finished picture can be given its sound, and a joined piece exports as an editor's timeline. A draft can be promoted, a chain that died can be resumed, a join can be levelled in LUFS, a narration can be checked against its script, and the job page draws the timeline. A chain can check that each segment spoke its line, and a take of one speaker can be held to a narration made first.
Breaking changes
templates/minimax/chained-segmentsdefaults tocontinuity: guide(waslast_frame): each segment's last 22 frames and their audio are laid into the next as an H3 guide and trimmed off again. Every guide-vs- last_frame read on the bench was mixed or favoured guide, and the last_frame counters froze or held back a moving camera. Passcontinuity: last_framefor the old behaviour.templates/ltx2/chained-segmentssetslook_hold: trueby default. Seam look improved on both seeds and an LTX orbit was not held back. Passlook_hold: falseto turn it off.templates/minimax/chained-segmentssetssave_segments: true: each finished segment is written beside the take while the chain runs and removed at the end unlesskeep_segments, so a chain that dies leaves somethingresume_fromcan continue. The final output is unchanged.clear_memorykeeps the worker process alive; its result andget_healthsay so (#1240).
The long-form playbook (#1199, #1213)
- A
long-formskill in thedwplugin: it splits a piece into scenes, picks per scene a single clip, cuts or a chain, sets the chain's shot preset and per-segment check, repairs one bad segment in place, joins long narration and music, and exports the cut. Every rule cites the bench record it came from (bench/records/). Static takes past about 30 s are cut into shots unlessshot: static_long; any take drifts past about 35 s. - The family skills point at it, and
cinematographyclasses each camera movestaticormoving(#1200, #1255), so the playbook picks the shot preset from the move.
H3 chains (#1202, #1203, #1204, #1205, #1206, #1207)
shotpresets onminimax/chained-segments:moving(default) anddialogueare guide with no counters;staticaddsanchor: segment_1at 0.25;static_longaddskeystone: segment_1atkeystone_slot: last, which held a backdrop and a face for two minutes on two seeds wherestaticcrusts from about 42 s. Apresetsblock is how a template declares one; any variable the caller passes still wins.chain.duration_secondsasks for a chain by its length. The plan derives the segment count, snaps the last segment to the pipeline's frame grid, and trims the stitched video and audio to exactly that length. A chain with noframe_snapderives it from thenum_framesvariable's constraint; one that cannot gets a validate warning.chain.checktests each segment after the first against the previous seam (jump, look, in-segment cut ratio) and segment 1's texture before keeping it;chain.retries(0-3) re-renders a failed one with an offset seed. On exhaustion the chain keeps the closest attempt and warnschain_check_failednaming the measure. The estimate quotes the worst case, 1 + (segments - 1)(1 + retries) renders (#1252).chain.keep_segmentssaves each segment as a playable mp4 beside the take and names it on the take's shots.templates/minimax/repair-segmentre-renders one segment with a head guide from its predecessor and a tail guide from its successor and splices it into the take; only the repaired shots and those after it change (chain_repair_cut,chain_repair_splice).- Counter keys, all measured and off unless a preset sets them:
anchor_strengthandlatent_anchor_strength(#1190),keystoneandkeystone_slot(#1187),guide_backoff(#1189),carry_pick: {sharpest: N}(#1032), andlook_hold(pickup, creep, texture, precompensate). The seam log line carries look_hold's measured and applied gains (#1251). chain.resume_fromnames one segment file of an earlier run of the same chain; the run reuses that run's finished segments (copied into the new take, named on its shots) and renders only the rest, underlast_frameorguidecontinuity. A resume never changes the plan: a kept segment of another length is refused, as is a set that mixes silent and voiced files. The estimate prices only the segments left to render. A chain that fails after a segment was saved warns with theoutput:referenceresume_fromwould continue from.chain.stop_after: Nrenders a probe: the plan stays the full take's and only its first N segments render. A second run withstop_aftercleared andresume_fromnaming the probe's last segment continues it. The estimate prices the segments a probe or a resume renders.resume_fromfollow-ups: a fan-out resumes per iteration and a finished iteration renders in full; the variable that carries the reference may have any name; a kept file the load refuses leaves the copy; the resume hint names accepted files only (#1268, #1269, #1270, #1304, #1307).- The spikes that lost on the bench ship dark behind experimental flags:
latent carry between segments (plain, and
"levelled", #1322), the latent keystone,anchor_every, the late capstone, SelfLift, TST and the windowed refine seam pass (#1217).
The words check (#1262, #1297, #1298, #1299, #1300, #1308)
chain.checktakeswords_similarity_min(null, off, by default): Whisper transcribes each segment's kept audio and compares it with the line the segment was to speak -chain.lines, one per segment, else the<d>dialogue of its prompt. A segment under the floor is re-rolled like any other failed check, and the warning names what was heard. Number words and digits compare equal.- A line that opens inside the trimmed head is reported as
line_clipped_at_head; a segment that passes with its opening words missing re-rolls, then reports. A carried dialogue line is cued past the guide span. chain.promptsandchain.linesmay each be oneprevious_result:list. The estimate adds the transcription's cost when the check is on.
The narrated take (#1308, docs/proposals/complete/narrated-long-take-complete.md)
templates/narration-retime(theretime_narrationtask) sets every pause of a spoken narration by the punctuation it follows and brings a word junction onto each segment boundary of the chain that will carry it, then checks the result against the script. It saves the retimed track and a report of words, pauses and boundaries. A pause is at most 60 seconds.templates/minimax/narrated-takeis an H3 guide chain matched to that narration and holding it as the soundtrack: the speech heard is the narration itself, the guide carries picture only, andnarration_linescuts the script into one line per segment and fillsprompt_templatewith it. It is run as a probe (stop_after) and then continued.- What it holds, measured at about 86 s (
bench/records/narrated.md):shot: dialogueto about 30 s;standing(keystone, no keyframes) to about 85 s on one seed, and she stands;walk_keyframedholds the face to 85 s on two seeds and moves the camera, at the price of a hand-built keyframe per segment (keyframes, the chain'skeystone_images). No preset cleared face, lip sync and motion together, and nothing is measured past 86 s. A longer piece is split into passages joined as cuts. - Each shot's
checkrecordsface_drop, the fall in face similarity to segment 1;face_drop_maxfails a segment on it.measure_lip_syncmeasures a finished take.templates/minimax/repair-segmentrepairs a narrated take, its last segment included, at the take's own tier. - The
long-formskill routes a speaking subject in one place to it.
Sound for a finished picture, long narration, long music (#1208, #1209, #1210)
templates/minimax/add-soundis an H3 audio pass: every video row holds the source clip's clean latent and only the audio is generated. The source keeps its own frame rate. Atlisten_scale1 the picture is 42 dB PSNR from the source and every line is spoken.split_takescuts narration text at sentence boundaries into takes of a measured word-complete length (37 s at 3 words a second);narration-longtakes text in and speaks it in one call;narration-from-takesspeaks a given list.templates/check-narration(thecheck_narrationtask) transcribes a finished narration and reports, per take, the words the speech dropped or added against the script, with anarration_take_mismatchwarning naming each take to re-speak.templates/minimax/music-longrenders sections of{name, caption, lyrics, seconds}, trims each to its length with an 8 s end snap to a quiet point, and crossfades them.slice_audiogainedpast_end: "stop"andend_snap_seconds.concat_videosand the other joins takematch_levels: "lufs": each shot gained to one integrated loudness (match_levels_lufs, default -16 LUFS) and held at -1 dBTP.match_levels: rmsnow holds at -3 dBFS so the AAC mux has headroom (#1248).relevel-clipexposeslimit(#1261).
Export and the job page (#1211, #1254)
export_timelinewrites a joined piece as an OpenTimelineIO.otiozbundle: one clip per shot, gaps filled, an audio track when the file has one;sourcesmode bundles each shot's own file at its own rate.get_gallery_metadatasummarizes an.otioz(tracks, clips with durations, members), and the web UI's job page draws it as a timeline strip with theoutput:reference of each clip's source one click away.
Promote a draft
rerun_jobandPOST /api/jobs/{id}/reruntakearguments, variable overrides merged onto the stored job before admission:rerun_job(id, arguments={"tier": "standard"})promotes a draft, keeping its seed and so its staging, even a seed the template drew at run time (bestre-stages, so it is a render of its own). An override changes the plan, so validate first; a bound acknowledgement is checked against the merged plan.
Quality tiers and cost (#1218, #1222, #1224, #1236, #1239)
tier: draft | standard | beston six H3 templates moves canvas, steps and weights together throughpresets.draftis 4 steps on the standard canvas, about 30% less wall, same staging at the same seed;bestis 1344x768 on int8, about 1.7x standard.- Estimates: a tier with no history of its own scales the default tier's observed figure; a child whose per-entry list comes from an earlier step says it is priced at the default count; a parent whose own steps are text-glue tasks is not partial; the fixed minutes of an entry are read from the entry its price came from. The MPS Music 3 row is 9.2 min (#1223).
- A matched chain's observed history is priced per segment, and an inherited chain figure scales by segments (#1305, #1306). A rerun with a null override of the seed variable draws a fresh seed, and resumed runs are left out of the observed basis (#1273). A run whose memory never grew records no job peak instead of the worker's resident size (#1288).
Server and worker (#1214, #1227, #1238, #1245, #1219, #1265)
- The host-RAM gate counts committed memory (anonymous + shared + the
driver's device-mapped pages), not mapped checkpoint pages; two H3 jobs
ran together at 42-51 GiB each on a 126 GiB host. Each tier declares its
host RAM, a host too small is refused at validate, and servers on one
host publish their claims to a shared folder under a file lock so they do
not co-admit what the RAM cannot hold. The default folder must be the
user's own and private; a shared one is named with
DW_HOST_CLAIMS_DIR. - The worker applies dw's PyTorch settings;
cudnn_benchmark: falseandcudnn_deterministic: trueare the defaults, after same-seed runs on a two-card server diverged from autotuning while the other card was busy. - A server restart records a tombstone for each job it dropped, so
get_jobsays what happened instead of "unknown job". - A CUDA out-of-memory that diffusers swallows while loading components
fails the job naming the component, instead of "Adapter 'turbo' not
present". A
release_modelsstep frees a warm worker's idle pipelines before its own model loads.
Validation and variables (#1215, #1228, #1247, #1249, #1250, #1253, #1259, #1271, #1302, #1303)
- Validation refuses what submit and run used to refuse late: an argument
the server refuses at submit, an empty Music 3 lyric, an animatic past the
4 GiB frame budget, a
result.file_base_nameanoutput:reference would refuse, a chain with variable segments besidematch_audio. - A braced
*_dtypeargument keeps its escape through a composed child;torch_dtypemay be a dict of per-component dtypes. - A bool-typed variable refuses a dict or list instead of folding it to
true; a variable may be declared required with no default (the voice
templates'
reference_audio). get_job_workflow's realized workflow dropspresets, so a re-run keeps the folded values (#1234).- Validate warns when
match_levels_lufsis set where nothing reads it, and refuses anexport_timelineresult that is notapplication/x-otioz.
MCP and guides (#1237, #1240, #1257, #1264)
get_guideserves the server, security and remote guides, so every link in a served guide resolves, and a section match ignores a heading's trailing parenthesised suffix. The workflow guide nameslist_workflowsas the catalog index and links only feature templates.
Security
- A task's
filename(upscale,restore_faces,interpolate_frames) is refused at validate and at run unless it is a plain name inside the model's repo (GHSA-ch56-2p9f-95cg). - A step that wrote several files named each shot's
fileby the server's absolute path inget_job,wait_for_jobandstep_end; it is now named asfilesare (GHSA-5m72-m9g5-q8vp). - A transcription
model_namethat arrives byprevious_result:is checked against the location policy at run time, not only at validate.
Compatibility
- diffusers 0.44.0.dev0 refuses explicit sigmas on the H3 scheduler unless
its config says how to read them. Every
refine_strengthandrefine_sigmasstep failed there; dw now states that its schedule is finished and restores the scheduler's setting afterwards.
Fixes from the release review
- A
duration_secondschain on the LTX template planned its last segment off LTX's 8n+1 grid and came up short; host-RAM claims were refused on every Windows folder; a chain repair with extra files beside the take said the take had no shots; a frame reader read wrong frames afterclose(); and a server test raced the claims folder. - From the second review (2026-11-13):
narration_linescounted frames by rounding up where the chain rounds, so about one narration in 300 planned a line more than the chain and was refused; an emptyscriptcleared every pause; akeystone_imageslist longer than the plan fetched its surplus.
This release is about long H3 chains: two off-by-default ways to hold a chain's look across its segments, a measurement that says whether the look drifted, a LoRA that was probed against burn, and the skill lines that came out of the probes. Alongside it, FLUX 3 Klein no longer loads a model it never runs, and the web UI draws a chained step as its segments.
H3 chain drift (#1166, #1173, #1174, #1181)
latent_anchor, an H3t2va/fl2vaargument, matches each segment's per-channel latent statistics to a reference clip before decode.chain.anchor: "segment_1"uses the chain's own first segment. It is off unless set, and the run logs one line per anchored call naming the source. The argument meets the media location policy at validate: a bare server path,..,file://or a loopback URL is refused there.guide_noise, a number in [0, 1), loosens every video guide on the step: cosine-tapered Gaussian noise from the guide's first frame to clean at its last, encoded once under the guide's seed. A seeded run repeats.- A ten-segment, two-scene probe found the anchor best on a static scene and
worst on a moving-camera arc, and guide noise alone the only arm that did
not hurt the arc. No template or skill sets either yet; the H3 skill's
chains.mdsays what the community reports agree on (#1167). - Imagineer13's anti-burn LoRA is in the catalog as
minimax-h3/h3-antiburn, markedtrial(#1151, #1159). On a five-segment chain it held segment 1's skin, knit and wall where the unaided chain had burnt by segment 5.
Measuring drift (#1152, #1160, #1139, #1158)
compare_outputs(names, texture_bands=true)reports the RMS texture energy per output per moment in three wavelength bands, fine (2-8 px), mid (8-32 px) and coarse (32-128 px), on luma and both chroma differences, over one shared crop, with ratios to the baseline. One name is allowed, so a chain's own segments can be read across. It is a measurement, not a score; the guide's Texture bands section says how to read it.get_gallery_metadatacarriespcm_sha256, a SHA-256 of the decoded samples, withsample_formatandframes, for any file with a soundtrack: an identity check for audio over MCP.
Assessment fixes
- A
stills_to_videoclip's silent track no longer raisesnear_silentor mono-duplication warnings,assess_outputno longer reportsshot_dead_airon its shots, and a cut whose shots are all silent by design no longer warns near-silent (#1161, #1162, #1163). One predicate decides it everywhere. shot_span_overrunfindings carry severitywarn;seam_holeleavesrules_appliedwhen it was skipped on every seam (#1179).face_driftmeasurements and findings name the shot's index, frames and times (#1165), and thescript-to-videoskill says what to do on one (#1178).check-scriptno longer counts a compound Whisper closed that the script wrote open ('candle crumbs' / 'candlecrumbs') against line similarity (#1172).
FLUX 3 Klein (#1168)
- Loading Klein on an empty 24 GB card no longer runs out of memory: the
pipeline loads only its four components, not the repository's 18.8 GB
prompt upsampler, and
configuration.remove_blockspops the upsampler block off the modular graph. The step cache keys follow the names. - The
controlnettemplate passes Canny's thresholds besideimage, where the detector reads them.
Fixes found on the templates (#1201)
validate_workflowrefuses amodel_namethe hub says does not exist, atmodel_name, instead of passing it to fail mid-run. An offline or timed-out lookup stays unknown, andsizes=falseskips the hub.gather_imagesloads anasset:oroutput:entry inurlsfrom disk, the waygather_videosalready did; a URL with a scheme still goes through the outbound fetcher, sofile://stays refused.- Marigold depth and normals predictions save as images (depth under its
Spectral colour map, normals as RGB), and the step progress wrapper
passes a pipeline's own
progress_barkeywords through.templates/surface-normalsnamesprs-eth/marigold-normals-v1-1; the repo it named never existed. - The MMAudio sound-effects decoder no longer fails to build with "Tensor on device cpu is not on the expected device meta!" when transformers' meta-init patch is live.
Skills
minimax-h3: splitting a short line into two<d>blocks removes the lead-in; action prose between blocks is not a pacing rule (#1180, #1184, #1185).check-scripttakes each block's contents, never the prose between.cinematography: one light phrase per value in the LTX-2.5, H3 and Klein compile tables (#1142); six named moves measured per video family (#1148).series-episodeskeeps cast portraits with an extensionless asset name (#1154); the sitcom multicam master setsmove: static(#1157).
Web UI and docs
- A chained step draws its segments: the count when the definition gives it, what each seam carries, and the running segment while the job runs.
- The job page shows a model download's progress while a load pulls a repo: the bytes so far and the rate, or how long no bytes have come.
- A workspace named in the URL that the cached listing lacks is refetched once before the UI decides it is gone.
- Every guide had an accuracy and concision pass, with page screenshots in
the README and SERVER guide. The workflow schema's
$idnames this repository, and the HTTP API's OpenAPI document and the schema are linked from the docs.
This release has two halves. One is directing: a shared vocabulary for the camera and the look, compiled into each video family's own caption format. Alongside it come storyboards and a timed animatic before any video is paid for, and a check that a recurring face stays the same person across shots. The other is packaging: dw now runs from a Docker image, the first step toward making it something you install rather than build.
Docker (docs/DOCKER.md)
docker/compose.yamlruns the server, web UI and MCP endpoint on a Linux box with an NVIDIA GPU (driver 580 or newer; CUDA 13). The image carries Python, torch and dw. From this release on, each release publishes it to Docker Hub asdkackman/diffusers-workflow:<version>andlatest. SetDW_IMAGEindocker/.envto run that instead of building locally.- diffusers keeps following GitHub main. It lives in a small venv on a volume, layered over the image, so the Models page's update button works as it does on a native install. A restart never moves it, a new image reinstalls the commit you had, and a broken HEAD falls back to the copy of diffusers built into the image so the server still starts.
bash docker/init-env.shpoints the container at the directories a native install on the same box already uses: the workspace, the Hugging Face cache (quantized cache included), the compile caches and settings. Each is mounted at its own path, so runs, downloads and compiled kernels are shared both ways.docker/switch.sh docker|nativeswaps between the container and the systemd unit.- Inside the container the server always has a token:
DW_API_TOKEN, or one generated on first start and kept. The token never goes in the log;entrypoint.py login-urlprints a/?token=...link, which the web UI now accepts. It saves the token, then clears it from the address bar.
Camera and look, per family (#1142)
- The
cinematographyskill is a closed vocabulary of six axes: shot size, move, lens, light, era and tempo. Each value has the one sentence it becomes in H3's, LTX-2.5's, Kandinsky 6's and FLUX 3 Klein's own caption formats. A shot list can saymove: dolly-inand mean the same thing on every family. An unset axis is left to the model. - Named grades applied after generation, on the CPU:
noir,teal-orange,70s-stock,vhsandbleach-bypass. Each is anapply_lutpalette followed byfilm_grain.
Storyboards and animatics (#1144, #1145)
- The
storyboardskill draws boards on FLUX 3 Klein from a character reference, covers one moment from six camera positions, and hands boards to H3, LTX-2.5 or Kandinsky 6 as first frames. stills_to_videoholds each still for a duration (one for all, or one per still) and returns a silent clip with a shot record per still.templates/animaticlays a scratch voice or music bed under the boards. It loads no model, so pacing and order can be checked before any video is rendered. Sizes are checked up front: over 600 s, 36000 frames, or a clip that would need more than 4 GiB is refused before it allocates.
Shot documents, the fix ladder, and face drift (#1143, #1146, #1147)
script-to-videokeeps its shot list in the prompt library (<project>/shots, one compiled<project>/shot-NNper shot). A revision recompiles only the shots it changed, and a later session picks the project up where it stopped.- A wrong shot is fixed in a fixed order, cheapest first, each step with
a cap: edit the region or repair the face, re-prompt with the seed kept,
reseed, and then stop and ask, showing the attempts and what each one
changed. H3 prompts get a lint checklist before a
render (a missing
overall_soundscape, an overfull beat, and so on). analyze_face_drift(templates/face-drift-check) scores sampled frames of each shot against a cast portrait with OpenCV's SFace. A shot whose median falls below the published same-person threshold is flagged asface_drift, and one with no face found asno_face. These are places to look before regenerating, not verdicts: a turned head scores low too.series-episodesgains a sitcom multicam grammar: a wide master held for the whole scene, a single per speaker, and cuts on line boundaries, never mid-word.plan_cutsplaces them from the transcript's line starts.
Web UI
- On the job page, clicking a step in the data-flow graph scrolls to what that step produced. A failed run draws the step it stopped in, red. The workflow's JSON is behind a toggle under the graph.
A release about fixing a good take instead of re-rolling it, and about sound that is not the model's own. MiniMax H3 edits one region of a take behind a mask, redraws a soft face and takes an explicit refine schedule; LTX-2.5 pulls an alpha matte and colourises black-and-white footage; a picture-synced sound-effects task (MMAudio) and a zero-shot voice clone (Audio8-TTS) join the catalog as vendored ports that run no Hub code; and every one-for-one video task now writes at its source's frame rate, exactly.
H3 regional edit, face repair and refine schedule (#1062, #1077, #1078, #1082, #1087, #1092, #1098, #1099)
templates/minimax/regional-edit: an H3 step takessource_videoand amask_imageormask_video(white regenerates) withrefine_strength, re-denoises the masked rows and writes every other row back to the source at each step, soundtrack held. The guide's H3: regional edit with source_video and a mask has the measured strength bands (0.9 replaces an object, 0.6 redraws a face); theminimax-h3skill routes "fix one region of a good take" to it (references/regional-edit.md). Validate and the call refuse the same things: a mask with no source, a source on a chained step, a size or frame-count mismatch, an all-black mask, a source whose frame count is off the17n + 5grid (the pipeline would snap it up over a padded source and the mask would drift).templates/minimax/face-repair:crop_face_trackcuts the face out on H3's grid carrying the take's padded soundtrack, a t2va refine at 0.6 redraws it withhold_audioso the mouth stays on the speech, andpaste_face_trackputs it back at the source's frame rate. The crop refuses a window outside 124-345 frames or a rate other than 24 before H3 loads.refine_sigmason an H3 refine step gives the schedule as the sigma points themselves (first =refine_strength, last = 0, strictly decreasing as float32, finite,num_inference_steps= the point count), the guide's Explicit refine sigmas. A full-frame refine whosewidth/heightdisagree with the upscaled latents is refused at validate (#1087), and a single-file LoRA is priced as its file, not its repo (#1088).- The
minimax-h3skill gains the style pointer (references/style.md, #1070), the AISkills and odd-then-bridge probes recorded as "probed, not a rule" (#1072, #1103), the re-measured lead-in (#1089), and routes for the past-768p latent refine and for replacing a shot's own sound.
Sound effects and voice cloning (#1112, #1120, #1121, #1124, #1131-#1133, #1134)
generate_sound_effects(MMAudio, vendored at a pinned commit with its Synchformer and DFN5B CLIP conditioning, decoder weights read from Kandinsky 6's safetensors) makes picture-synced effects for a clip and/or a prompt:templates/video-to-sound-effectsandtemplates/text-to-sound-effect. Duration is the clip's (0.64-20 s), the model is trained around 8 s, noise-like output is warned about, and the output runs 7-25 dB hot:match_levelsbefore mixing. The weights are CC-BY-NC and Apple research-only;docs/ATTRIBUTIONS.mdlists them.generate_speechwithmodel_name: Edge0/Audio8-TTS-Realistic-0.6bclones a voice from a shortreference_audio(a path, anasset:, or an earlier audio step's output) and itsreference_text;templates/voice-cloneruns it. The model is a vendored port pinned to one revision; its Hub repo's code never runs.attribute_voicestakes an audio step's output as a voice reference too (#1134).- The vendored ports live in
dw/vendored/, outside the architecture ratchet likedw/community_pipelines/.
LTX-2.5: alpha matte, colourisation, and the source's frame rate (#1064, #1079, #1080, #1090, #1106-#1109, #1114)
templates/ltx2/alpha-matte(Alpha-Gen IC-LoRA) pulls a grey matte from a clip, one shot at a time (a hard cut leaks), with the decoders kept off the card;templates/ltx2/restore-colorizecolourises black-and-white footage from a prompt that describes the clip, with its colour limit on low-rate and portrait sources documented.- Every one-for-one template (
upscale-clip,refine-clip,refine-in-place,restore-deblur,restore-decompression,restore-long,face-repair,alpha-matte,restore-colorize) writes at the source's frame rate (frame_rate: "source"), never relabelled to 24, and a fractional rate (29.97, 23.976) is written as its exact rational (#1109).restore-deblurandrestore-decompressionfit the clip to the model, then restore to the source's size.
Cuts, phrases and the script check (#1095, #1104, #1105, #1115-#1119, #1129, #1130, #1135-#1138)
analyze_phrasestimes sung phrases from centre-weighted voiced energy, with no transcript, andplan_cutstakes its result: lyric lines pair with phrases one to one when the counts match (a disproportionate pairing is warned about) and are spread evenly when they do not, with the warning naming which way to movemin_gap_secondsorfloor_db. The music-video templates' README shows the Whisper-free chain.transcribe_audiomarks Whisper's hallucinations: chunks of a near-silent clip (silent), its stock outro lines (stock_phrase), 5-6 word loops and duplicate overlapping chunks (repetition);plan_cutsandcheck_scriptleave suspect chunks out and say so.check_script: an untranscribed block crossing a shot seam is credited to each shot it covers (a shot too short to stand alone is folded into its neighbour, never dropped); a line's timing stays inside its shot; a take with no shots is one implicit shot forunscripted_speech; a looped room-tone bed is not untranscribed speech; speech outside a partial shot map is named inrules_skipped; an unheard line's clip is checked against its shot's end.separate_stemsno longer shift-averages a clip shorter than the htdemucs segment, which clipped the stems 9-17 dB hot (#1130).
Skills and composition (#1093, #1100, #1122, #1125, #1123, #1126-#1128, #1091, #1096)
script-to-video: cutaway and lead-in joins (references/joins.md), beat authoring and the beat review (references/beat-review.md), shared withseries-episodes;series-episodesgains the dialogue/rest stem recipe viaseparate_stems, kept balanced for headroom.- The story-forge lessons that held on lem (framing, no invented camera, no
all-caps) landed as guidance in the
ltx-2.5skill; the ones that did not are recorded as probed.
Validation, server and security (#1086, #1101, #1111, #1116, #1119, GHSA-hhfj-p9xj-xjmr, GHSA-75fh-g499-53cf, GHSA-9597-5qpq-55x6, GHSA-w9qv-2pg5-j79m, GHSA-75cg-7964-pjwx)
- A task step's
trust_remote_code,custom_pipelineorcustom_generateis refused at validate and again atTask.run, for every task; avideoslist and the active result types are gated at validate; a reference error no longer names a server path. for_eachrefuses a literal cross-entry reference inside one group;list_jobsrefuses a workspace that does not exist; a cached pipeline's minutes-long decode and a slow first pass are warned about as measurements, with no cause asserted.- Python 3.12 or newer is required (#1084); the install scripts and classifiers say so.
Architecture
dw/media.py,dw/tasks/cuts.py,dw/tasks/script_check.pyanddw/pipeline_processors/pipeline.pyeach hand a concern to a companion module (media_readers,cuts_lines,script_findings,pipeline_outputs), and the regional edit's mask arithmetic moves toh3_mask_cellsso the blocks never import their importer. The ratchet's module count rises 226 -> 236 for these and the new tasks; every other metric holds.
A release about reaching past a 24 GB card and about removing things from a picture. MiniMax H3 refines to 1920x1088 in tiles, the plan prices every weight a task step will fetch before a run is accepted, and the cost quotes and VRAM ceilings for LTX-2.5 and Z-Image are calibrated against real runs. A prompt-free object-removal task joins the catalog with a small inpaint model beside it, and check-script is sturdier on looped, clipped and repeated takes.
H3 tiled refine to 1920x1088 (#1043, #1048, #1056)
- An H3 refine step takes
refine_tiles: {width, height, overlap}and denoises an upscaled plate in feathered tiles on the 32 px grid, so a 1920x1088 refine fits a 24 GB card. The tiles blend in latent space and the canvas decodes once; the soundtrack held from the base take is encoded once per refine, not once per tile. The guide's Refining above 768p in tiles section is the recipe: 544p base,upscale_h3_latents, tiled refine on the 768p turbo LoRA, one decode (about 12 min on an RTX 3090 for 124 frames). upscale_h3_latentscaps at 1920x1088 (or 1088x1920) and validate refuses past it; references, guides, keyframes and<Picture N>are refused in tile mode; validate prices tile mode by the tile and the decode, not as a full-canvas denoise.- Every pipeline step whose pipeline reports its own frame rate now hands
its
videoson carrying it, sopair_audioon an H3 base take fits the track at 24 fps instead of the 8 fps fallback. The guide's keep-the-544p recipe no longer asks forresult.fps, which runs after the task. analyze_seamsreports look measures (lightness, contrast, saturation, detail) at each seam beside the motion fields, so a re-roll can say which side stepped (#1031, #1042). Thescript-to-videoskill says when to re-roll a seam (#1034).- H3 dialogue shots drop the turbo LoRA, the enhancer writes spoken and
sung lines as
<d>blocks, and locked-frame lip sync is FL2VA withhold_audio(#985, #989, #992).slice_audiowithstart_secondsandnum_frameskeeps the frame length (#990). A one-segment H3 chain is bit-identical to a native run (#1046).
Object removal (#956, #973, #974, #946)
remove_objecterases a masked region with LaMa, prompt-free, andtemplates/remove-objectandtemplates/segment-and-remove(SAM2 mask from a box or a label, then erase) run it.segmenttakes each box's best-rated SAM2 mask rather than its first.models/z-image-inpaintconfigurestemplates/inpaintwith Z-Image Turbo for masked add-or-replace, a fraction of FLUX.1 Fill's 54 GB. The guide's Erasing versus repainting a masked region section routes between the two.
Cost, downloads and admission (#975, #977, #978, #981, #1037, #1038, #948, #954)
- A validate's
downloads_requiredlists the Hugging Face repos and single files task steps fetch (segment,remove_background,diffusion_upscale,transcribe_audio,check_script,generate_speech,attribute_voices,text_generation,image_to_text,judge, the image processors,upscale,restore_faces,remove_object,upscale_h3_latents,crop_face_track,interpolate_frames, and facexlib's two weights), plusload_componentsrepo overrides. Entries gain optionalfilenameandrevision; the OpenAPI document and the UI's types carry them. - The validate plan passed back verbatim is a bound acknowledgement. A
bound
acknowledged_costtaken from a validate made before this deploy is answered 409downloadswhen the workflow has one of these tasks and its repo is not cached: re-validate and resend the newacknowledge. The 409's rebind plan carries the observed estimate validate quotes. - The observed estimate is the median of the newest runs, not the whole
history, and an inherited quote pays the model load once across scaled
steps. A task-only workflow's unknown estimate names why it is unpriced
(
unpriced_reason), and validate notesvram_config_differswhen a step's offload or quantization differs from the calibrated template. - Validate reports one VRAM error per projection. LTX-2.5's
upscale-clip,image-to-video,keyframes,two-stage,refine-clipandreference-sheetdeclare calibratedvram_estimates, so a 1080p or 481-frame request past a 24 GB card is refused at validate instead of running out of memory late in the denoise; the image-to-video default and theltx-2.5skill move to 401 frames (#1050, #1052, #1053, #1054).models/z-imagedeclares one over batch, width and height (#945).
check-script and audio (#953, #955, #959-#971, #1039, #1041)
- A looped cut is re-transcribed shot by shot when shots are known; a
scripted loop keeps its first take and flags
transcript_loop; a chunk repeating the previous span is discarded asrepeated_span; a second copy of a line inside its matched words isunscripted_speech; audible untranscribed sound between two lines, and after the last one, is reported and placed in the shot holding most of it; a clipped tail is judged against the line's loudest window, so a score bed is not a clip. separate_stemswarnsstacked_vocalsover layered-stereo spans of the vocal stem (#947).normalize_audio(limit)warns when the limiter works mostly on one shot of a cut, and reads a saved cut's manifest shots when given its path.
Server, worker and validation (#928, #944, #949, #950, #951, #952, #957, #972, #976, #993, #995, #996)
- A worker whose CUDA context a device-side assert poisoned is recycled
instead of answering every later job with the same error. A pulled card
in a
--deviceslist no longer stops the server: it starts on the cards present and warns.scripts/dw-serve.servicesetsRestartPreventExitStatus=2so a refused configuration stops the unit rather than looping; reinstall the unit to pick it up. - A file of a run with no recorded role lists as intermediate when in
intermediate/or read by a later run; manifest roles are stamped on the job at finish so history serves what a live job did; a deleted workspace's job rows stay resolvable by id. - Validate refuses a bare relative media name that resolves nowhere,
naming
output:<name>for an own output; an unknown argument is reported as itself; a document error no longer hides an unknown argument in the same call; no undertrained-LoRA warning on a null LoRA whose steps were raised. - A redirect hop the outbound guard refuses now names the target and the URL asked for, so a refused hop reads apart from an upstream error (#1055).
Web UI
- The flow view routes column-skipping edges in lanes and merges same-pair labels. The jobs page filters follow the URL hash. Jobs and models list the day (with the year only when it is not this one) and give the exact timestamp where it matters. undici 8.12.3.
Engine structure
- The task-weights hook path is acyclic (
validate_weight_nameindw/security.py, processor weights registered into the task registry), andtransformersno longer loads into the server's validate path.dw/plan_downloads.py,dw/tasks/script_take.pyanddw/pipeline_processors/mag_calibration.pysplit out of their parents;docs/ARCHITECTURE.mdnames each seam.
Dependencies and deploy
- No dependency floors changed. The engine changes in this release need a server restart (the persistent worker) after deploy.
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), withsparse_tauas 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_workflowwarnssparse_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-h3skill'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/healthlists the attention backends the server can run, and the web UI'sattention_backendfields 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-kleincomposition skill. text_generationgainschat_template_kwargs, arevision, a list of images, and asystem_promptread from a Hub repo file at a pinned commit (anonymous, public files only, size-capped). Itsgenerate_kwargsare filtered, andcustom_generate/trust_remote_codeneed trusted workflows.
Security
speech_generationpassed a workflow'sgenerate_kwargsandforward_paramsto the model'sgenerate()unfiltered, so an untrusted workflow could setcustom_generate/trust_remote_codeand 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.shreinstalls on the pyproject change.
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 setsresult.role: "final"stays final. Thescript-to-videoandseries-episodesskills have ingredient workflows declare"role": "intermediate"(#928). - Runs, files, assets and workspaces take an optional note:
noteonrun_workflow,upload_assetandkeep_output, and MCPset_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_workspacecopies 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",scalednames the axes). Any other driver that moves keeps the figure and setslow_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
queuedwithwaiting_forand aqueue_waitevent. 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). Afirst_pass_slowwarning 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,
fullgraphwith a cache, a CUDA-only attention backend, and an unbraced type name under a*_dtypekey (#887, #904, #926). - The plan fingerprint and step cache key only on what changes the output,
so metadata edits and
12.0vs12no longer miss the cache (#905, #911). - Every H3 template exposes
attention_backendandcache(#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-h3skill has measured draft -> final rungs. TV: L1 (~5.4 min) -> L7 (~7.2 min), changing onlyweights_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_outputsputs 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_speechandspeech_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_downloadstops queued files and showscancel_requested(#899); a model download recordsrefs/main, so it reads complete.cancel_jobtakes 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_metadatameasures an image, andinclude_workflow=falseomits 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.servicesetsOOMPolicy=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_keycolumn, migrated on start.
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, soasset:prifindscast/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:anditem:<field>in a for_each step, andgather:<step>andprevious_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 asbuiltin:names (newGET /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
latestform, 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_byonGET /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
shiftopens in full view. Every H3 template's step was stuck in the compact view. plan_cutstakes 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.0exclusion 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 dropshuggingface-cli. deploy.shreinstalls 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.
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=1keeps each SDNQ on-the-fly-quantized component on disk (safetensors plus JSON, under<HF_HOME>/dw-quantizedorDW_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 throughfrom_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'sreturn_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 aquantized_cachejob 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/cacheslists dw's caches of derived data -inductor(torch.compilekernels),triton(SDNQ's jit and autotune kernels) andquantized- 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 andDELETE /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_infocarries the list for an agent.
Transcription and cuts (#822, #823)
music-video-cutstranscribes 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_cutsleaves them out and warns once per run.get_gallery_metadata's envelope takesinterval_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) andlow_confidence(#851). assess_outputshot findings carryindexandstart_frame, so a repeated shot name is unambiguous (#835).dialogue-shortandmusic-videoexposematch_levels_dbfs(#836).- A default cache root that appears after the server starts is marked,
so
tritonstays clearable after the first run (#837). deploy.shrestarts adw.serverun as a loaded launchd daemon on macOS.- A component's
attention_backendpin stays on that component. diffusers also made it the process-wide default, so after an H3 run withattention_backend=sage_hubthe worker sent every later model through sage, and Music3 failed withUnsupported head_dim: 256(LTX's attention mask is refused the same way). Music3 and LTX templates need no pin of their own.
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-kreastream the transformer block by block from pinned host memory (group_offload) instead ofoffload: "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 batchesoffload: "model"runs out of memory on. It holds ~22 GB of pinned host RAM per loaded FLUX pipeline. - Z-Image Turbo (
models/z-imageand the MiniMax portrait steps) takesoffload: "model": four 960x544 images in 41 s instead of 107 s. - The four MiniMax-H3 family baselines expose
attention_backend(null by default;sage_hubmeasured about 30% faster per step on a 3090; it needs thekernelspackage andDIFFUSERS_TRUST_REMOTE_KERNELS=true, see docs/ACCELERATION.md). LTX-2.5 cannot use sage attention (its attention mask is refused). - A LoRA
scale/alphachange or a schedulershiftchange re-applies on the warm pipeline instead of reloading it. Removing an alpha or shift still reloads, and apipeline_referenceruns 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_facesbuilds 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_blockcache, and minimax/shots-batch drops itsrelease_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.
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_steps5, 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-videoholds 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_audiois declared (null) on it and thematch_audiochains (#795).for_eachitems take an optional field with a default, so a music-video entry may carry its own singer (#801, #809).plan_cutsdivides a split sung scene's lyric across its pieces by aligned word times (#805).- New
measure_synctask cross-correlates a shot's audio against its source slice (#802); newrelevel-cliputility template, named by the clip warning (#794). assess_outputruns the whole-file level rules (full_scale,near_silent) (#807); nolevel_spreadwarning when a laterpair_audioreplaces 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_imageargument fedprevious_result:of an earlier video step (#813).
MCP and server
list_assetstakeslimit(newest first),prefix, and reportstotal(#798).save_promptaccepts a plain string (#806);wait_for_jobfalls back cleanly on a client timeout and a running job reportsfinished_steps(#792, #799).
Acceleration (docs/ACCELERATION.md)
TORCHINDUCTOR_CACHE_DIRdefaults to~/.cache/dw/torchinductor, so compiled kernels survive a reboot instead of living in/tmp..env.exampledocuments 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.pysplit by family, the H3 block classes moved to module level, task argument rules moved todw/task_problems.py, and oneiter_stepswalk 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-jsandpostcss-selector-parserDependabot alerts.
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)
--devicesstarts a persistent worker per card; a VRAM-aware dispatcher with backfill and cache affinity routes each job, and a declaredvram_estimateis admitted against the pool's largest card (#675, #676, #677, #685).get_healthandget_memoryname the workers per card;get_memoryandclear_memorytake adevice; a job records its device ordinal and card inget_job,list_jobsand 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_memorysplits host RSS into anonymous and file-mapped pages (#709).
New tasks (docs/TASKS.md)
- Finishing:
gradetonal controls,sharpen,film_grain,apply_lutfrom a strictly parsed.cube(uploadable, listed as kindlut) or an in-memory palette, with alpha kept through every one (#633, #634, #635, #636, #775). - Faces:
crop_face_trackfollows one face and records its track;paste_face_trackpastes a repaired crop back, feathered (#622, #623). - Long video:
window_videoandjoin_windowsprocess a clip in overlapping fixed-length windows, range-read with a uint8 join;fit_to_modelandrestore_to_sourcetake 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) andingredients_grid(#604, #607, #625, #626, #627). check_scriptcompares a take's transcript to its expected lines, shot-aware, discarding words heard over silence and Whisper repetition loops;templates/check-scriptretires the by-eye check (#643, #644, #645).transcribe_audioresumes after a mid-clip stop and trims word bounds to waveform energy;attribute_voiceshandles zero-length and split lines (#617, #661, #672).
MiniMax H3
hold_audiogenerates video to a held soundtrack; the music-video andmatch_audiochains hold their track instead of referencing it (#618, #619).guides: clips held as condition rows on t2va/fl2va, with"audio": trueand 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
LTX2RefinePipelineandtemplates/ltx2/refine-in-placewith lem-tuned strength ladders;face-repairandrestore-longtemplates; 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), thekandinsky-6plugin skill. Needs diffusers from gitmain(Kandinsky6*is in no release yet); not on Apple Silicon (the VAE decode keeps MPS memory per tile).update_diffusers/POST /api/system/diffusers/updatenow 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_commanddeclares a task's argument domains, choices, static checks and media arguments;whole_numberandreal_numberare the one numeric coercion indw/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_cutsandcheck_scriptarguments are refused at validate at their argument path (#639, #646, #657, #785). validate_workflownames an unpriced composition's children, and aper_entrylist'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-workflowskeeps the old behaviour. Afile://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_assetfor larger media. - No response carries the server's absolute paths: job warnings, log lines
and phase details name an input by its
asset:oroutput:reference,get_jobno longer returns a history row'sspec, and the validate plan'soutput_diris gone -plan.workspacenames 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_eachmember'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_levelsdraws no headroom warning,resample_waveformreturns exactly the rounded count,video_frameskeeps 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_workflowfolds recorded arguments on an unrealized job (#647); lazy diffusers type resolution is serialized across threads (#787).
Plugin
kandinsky-6skill;minimax-h3gainsreferences/dialogue.md, the guides and music-video render rules;series-episodestakes an optional look step (palette,apply_lut,film_grain) (#637, #642, #665, #762).
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 (
.diffkeys), RAVEN (unrecognised prefix, loads nothing silently) and TaoMate (lowercaselora_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-Turboonly; 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_devicefigures are re-priced forfor_eachcounts 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-videoand Music 3 (#590).
Fixes
assemble-and-scorethreadssample_rateinto its edit join, and mixed-rate shots resampled to a pinnedsample_rateno longer draw a warning advising you to pass it (#594).export_jobreports the zip's real gating:/exports/*.zipis ungated like/outputs, soauth_requiredis false whether or not the server has a token. The MCPnexttext and the skills now say to fetchopen_urland unpack intoexports/to bring a project home (#595, #592).
Plugin
- The
ltx-2.5,minimax-h3andminimax-music3skills moved request-specific detail (the LTX caption spec, H3 checkpoint and LoRA combinations and cuts recipes, Music 3 loudness) intoreferences/beside eachSKILL.md, which are read when the skill points there. The 12 KiB cap applies toSKILL.mdalone; a new test fails on an unlinked reference or a dead link. - The H3 and LTX skills point at
list_loras.
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-mcpneeds adw.serveat least as new as itself: upgrade the server first. A mounted MCP (dw.serve --mcp) is always the same version. lorasis now a reserved workspace name. A workspace already calledlorasis 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 asoutput:<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, andget_jobreturns the traceback. - GHSA-crqf-hw9p-r739:
create_workspace's MCP result isname,default,currentandnextonly;list_workspaces(detail=true)is the opt-in for folder paths.POST /api/workspacesis unchanged. GET /api/loras/recommendandrecommend_loras:hub_errornames 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,trialorrejectedwith the evidence. The writableloras/at the server root is shared by every workspace and is read ahead of the shipped read-only one. An entry'smodel_name,weight_name,revisionandscale.defaultdrop straight into a step'slorasentry. 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-inrecommend_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 plushub_error, and the query is capped at 200 characters and 4 search terms.
New
upscale_h3_latentsanddecode_h3_latentstake 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. Itsweight_namemust be a bare file name, andmodel_namemust 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}/imagereturns an image output orasset:cropped, fitted tomax_dimensionand halved until it fitsmax_bytes.max_total_byteson/framesshrinks 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}/rundeletes a finished job's run directory.delete_output(job_id=)calls it, and a job still queued or running is a 409.findingson gallery metadata reports measured level problems (full_scale,near_silent) with the fix.acknowledgein a cost 409 is the{fingerprint, minutes, downloads}to resend.output_kindson a job, and on eachstep_endevent, maps each file toimage,video,audio,textor null.
- Every JSON route declares its response model, and the UI's types are generated from the server's OpenAPI document.
dw-mcpno 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-h3skill names the 4-step draft and the stacked Realism People LoRA, and rules out loader-only LoRAs (#585).ltx-2.5names the wait reply'stimeout_applied_secondsandtimeout_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/jobscan no longer queue a job twice. - A scheduler parameter defaulting to
-infreads"-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 advisessample_rate, which relabels the rate rather than resampling (#586).find_loop_bed'sno_loop_bednames the in-shot rule that actually ruled windows out (#587).- Deleting a job's run twice says the run is already gone.
- MCP:
upload_assetconfines a source to the workspace the call names.get_output_frames' audio excerpts share the response byte budget. A badDW_MCP_MAX_WAIT_SECONDSkeeps 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 inreferences.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 routeapi.tscalls 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 takesacknowledged_costis checked to refuse without it.- CI runs the e2e suite before
developmoves, once per push while the release PR is open.
- 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.jsonno longer loadsx.json: only the leading prefix is stripped, so the namebuiltin:x.jsonis looked up and reported asSubWorkflowNotFound.- At run time a missing
builtin:workflow now raisesSubWorkflowNotFound(naming the packaged root) instead ofvalidate_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.jsonwhen the same workflow instance is reused:Workflow.runresets 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_templateschema 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_audiorounds a frame-addressed region's end once, asslice_audiodoes, so a region's end can no longer be one sample off the matching slice's.concat_videosrefuses 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_videosgives the same message in place of the resample error. Save that step withaudio_sample_ratein its result and join the saved file through anoutput:reference. An unpinneddissolve_videoswith such a track now raises thisValueErrorrather than aTypeError.- 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_warningsandcache_hits. Callvalidation.workflow_context(workflow, ...),validation.run_warning_check(workflow, "<name>", ...)andworkflow_run.cache_hits(workflow, arguments).- Reference prefixes (
variable:,asset:,output:, ...) are read and built only throughdw.references; a hand-written prefix check elsewhere fails theprefix_handlingratchet. scripts/arch_metrics.py --check docs/stabilization/baseline.jsonruns in CI andscripts/preflight.sh, so a PR that raises any architecture metric goes red. The module-size metric ismodules_over_size_ceiling(fail over 1,100 lines, warn over 1,000), renamed frommodules_over_1000_lines.- Agent context: CLAUDE.md files went from 957 to 129 lines;
docs/ARCHITECTURE.mdis the seam map (concept, owning module, rule, enforced-by).
- The stabilization freeze is lifted (
stabilization-gate-4).
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_dirsandorigins(prompts). The writable root is now thelibrariesentry withwritable: trueandorigin: "workspace". Renamed: assetslibraries[].dirisroot, and the MCP compact workflow listing'ssourcesislibraries. Added to all three:libraries: [{origin, root, writable}]in search order,shadowed: [{name, origin, shadowed_by}], andorigin/writableon every entry. Item keys,details,folders, sort order and the single-item reads are unchanged. - The
teacachepipelineconfigurationkey is removed. A workflow that sets it fails validation ('teacache' was unexpected). Usecache(first_block,mag,taylorseer; docs/ACCELERATION.md). The guide's "TeaCache" sections are gone, soget_guide(section="TeaCache")no longer resolves. POST /api/jobs,POST /api/jobs/{id}/rerunand a workflow save whose validation itself crashes answer 500internal error - the server log has the detailwhen 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 onecheck '<name>' failed (<ExcType>) - the server log has the detailfinding and every other check still reports; a crashing warning check is a warning and never refuses. A null-path error no longer starts withNone:.- The REPL is removed (
dw-repl,python -m dw.repl; the guide is now docs/WORKER_GUIDE.md).python -m dw.runis an HTTP client of a runningdw.serveand takes--server,--workspace(a server workspace name, no longer a directory) and--token.-o/--output_dir,--prompt-dir,--asset-dir,--output-layout,--trust-workflowsand-l/--log_levelare gone; set them ondw.serve.httpxis a base dependency. save_workflow,save_prompt,upload_assetandkeep_outputno longer return the server's absolute path. They return the name, workspace, origin andreference(#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 anasset: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 intooutputs. 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 emptyprompts/.prompt:name.jsonnow resolves. - More is caught at validate: a
vram_estimateis checked even when the workflow has nocostblock (#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-shortdissolve_videosinput is refused; a literal numericsample_rateonslice_audiois honoured as a relabel; a literalnullthresholdorindexonselectis refused;audio_bleed_mswith only one input warns (#565). get_output_framesrefusesnamesorboundarieswithoutseams. They used to be dropped silently (#554).concat_videosfills 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, warningaudio_frame_driftfor one that runs long (#562). A step's declaredresult.fpsis handed on to later steps, andjoin_into_songrefuses a contradictingfps(#513). A frame-addressedslice_audioend is rounded once, which can move it by a sample (#557).- A composed
for_eachmember 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
generatorstring, keepslorasandip_adapter(so "open as workflow" works for adapter steps), and a composed child's steps no longer carryargument_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-clipandltx2/two-stagemiss 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 tonum_framesfirst (#549).- The
join_into_songtask joins dialogue shots into a song, placing the song from the measured dialogue length at run time (#513). Theminimax-h3skill and the workflows guide carry the recipe (#514). - The
find_loop_bedtask 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, theminimax-h3andseries-episodesskills point at it (#544/#545). keep_outputrecords the source job, run, version and workspace, soget_gallery_metadataon a kept asset reports its job (#556).- A composed sub-workflow's manifest entries carry
parent_step(#560). pair_audiowarnsshot_position_regriddedwhen it moves a measured shot start by 5 ms or more (#563), and loads a stringvideopath.- A job records the full warning set
/api/validatereports, and validate's argument-error 400 carries warnings.workflow.jsonrecords the folded variables: realized constants, resolved list entries and snapped values. get_job_workflowand the job export answer with the admitted definition when the workflow file has since moved or stopped parsing.dw.runprints 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-stagewith the same seed went from 78.8 s to 0.77 s on an RTX 3090. - Clearer error text: the
image_crferror names the video passed where an image was expected (#511); an image task given a video, and a join size mismatch, name the route throughvideo_framesandresize_rescale(#550/#551); the frame-size error names every mismatched video by its index;dissolve_videosnames every too-short input in one error. Audio shaping steps log seam fades, score offset and fade-out (#564), andaudio_bleedlogs each bled seam (#566). - The plugin skills tell an agent to pass
plan.estimateplus a margin aswait_for_job'stimeout_seconds(#546).
Fixes
- Validation and
workflow.jsonsee 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.jsonis 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_audiono longer truncates a multi-line clip in plain-text mode (#559). mix_audio's dB-typo warning ignores non-integer gains such asfind_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_namerefusals 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_sourcesisdw.library(WorkflowSourceisLibraryRoot, plusLibraryPath).dw.loudness,dw.media_audio,dw.media_infoanddw.teacacheare gone; their contents are indw.dspanddw.media.normalize_audioand the other dynamics tasks are indw.tasks.audio_dynamics, the join helpers indw.tasks.joins.result.pysplit intodw.writers,dw.audio_qcanddw.output_extraction.pipeline_processors/pipeline.pysplit intoplacement,components,adaptersandprogress.Workflow.run's phases are indw.workflow_run, pipeline ownership indw.pipeline_ownership, validation indw.validation(which absorbeddw.result_fps,dw.null_mediaanddw.select_validation), the trust gate indw.trust.pipeline_cache_keyandstep_pipeline_keysare indw.step_cache;dw.planno longer re-exportsunseeded_cache_warnings.create_appis a factory overdw/server/routes/*, andapp.dependency_overridesdoes not reach their routes. Worker messages are typed indw/worker_protocol.py(probe_idisrequest_id;ping/pongremoved). MCP tools are indw_mcp/tools_*.py.- An object passed to
Workflow.runis 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.
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-stageno longer hasfull_width/full_height. They never changed the output, which is always exactly 2xwidth/height. A call that still passes them is refused as an unknown variable (#506).- Every LTX-2.5 template refuses a
widthorheightthat isn't a multiple of 32 at validate and atrun_workflow, before the pipeline loads (#505). - The MiniMax-H3 Ref2VA VRAM ceiling is now projected per step, after
for_eachexpansion, 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. Afor_eachrefusal names the member (Member 'shot@...'). Eight more Ref2VA templates declare the ceiling (#479/#501). - A workflow with no
vram_estimategets avram_projection_inheritedwarning, 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:andoutput: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_workflowrefuses afor_eachitem's reference that resolves to null, which the run already refused (#478). It also refuses a frame-size mismatch between theasset:/output:/path inputs ofconcat_videosanddissolve_videos(#504).- A
transcribe_audiostep withtimestampsmust setcontent_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-768pruns 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_LOADINGstill wins (#470). - A
for_eachwhose members produce the same shot name now warnsshot_name_collision(#508).assess_outputhas a newshot_dead_airfinding for silent gaps inside a shot, andconcat_videosmarks its joins as hard cuts, soseam_frame_jumpno longer fires on a deliberate cut (#465/#466).
New
- Apple Silicon (MPS): the CUDA templates, LTX-2.5 and
minimax/musicincluded, run unchanged. Memory figures and the chip name are real, and torch's CPU-fallback warning is shown (#470). - The
attribute_voicestask names which reference voice sings or speaks each line or window, by timbre. It uses demucs separation and ECAPA embeddings, anddemucsis now a dependency.list_tasksmarks the read-only probes with anassessmentflag (#485/#494). Theminimax-music3skill points to it for multi-singer songs (#495). normalize_audio(limit=true): a true-peak look-ahead limiter that reachestarget_lufspast a loud transient. It warnslimiter_heavypast 6 dB of reduction andtarget_lufs_capped(limited: true) past 12 dB (#474/#496).assemble-and-scoretakestarget_lufs(#467) andlimit(#497, still open). Theseries-episodesskill says to match episodes downward to one series target. Unfinished (#497): withlimit: truethe 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-cliptakes aclipto extend an existing clip. The opening isn't generated when one is given (#446).save_workflow,delete_workflow,upload_asset,delete_assetandlist_assetstakeworkspace=for one call. Their replies name the workspace they acted on (#463).- A LoRA with
model_name: nullis switched off and warnslora_disabled. It used to validate clean and then fail after the model load (#469). get_guide(section=...)reaches a###subsection and returns itsparent_section(#503).upload_asset's refusals give a workingcurltoPOST /api/uploadsand state the 200 MB limit. The guide explains thatfor_eachitem-levelfrom_previous_resultmust name the member, asslice@<entry>(#481/#482).- A running job's
manifest.jsonis rewritten after each step, so finishedfor_eachmembers show up before the run ends (#480). SECURITY.md: report vulnerabilities privately, not as issues.
Fixes
templates/ltx2/extend-clipfrees 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_videosload a{"location": ...}entry invideos(#510).- A modular step whose outputs include latents no longer crashes at save
(#507).
pair_audiounwraps a pipeline's batch of one video. transcribe_audioworks on clips over 30 s, and a non-Whisper model on a long clip still returns text.segmentworks again: the SAM2KeyErroris 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_mbis a running max (#457).dw.serveexits on SIGTERM with an MCP client still connected (#477).- The
shot_dead_airfinding names the room-tone remedy. Thenormalize_audiowarning no longer says that gaining down always succeeds (#491/#492). The docs describedead_air_floor_dbfscorrectly (#519). - The
minimax-h3skill carries the 24-shot field report's prompting rules (#484).
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_outputover adw.serve --mcpendpoint refuses a call with nodestination. It used to write into the server's own directory (#353). -
Untrusted workflows are refused in more cases (#409-#413):
- a
*_typethat 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 plaintorchclass such astorch.nn.Linearis now refused; constant:walks through_names or out of the allowed packages;- URLs with backslashes;
text/htmlandtext/xmlresult 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-workflowslifts all of these. - a
-
run_workflowvalidates the caller'sargumentswhen 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/musicnormalizes to -3 dBFS instead of -1, so its output is quieter (#362). -
Every response carries
X-Content-Type-Options: nosniffandX-Frame-Options: DENY. Active document types under/outputsand/inputsare served withContent-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/keyframestakesfirst_image/last_imageas plain strings, not{"location": ...}(#431/#433). -
loop_framesreturns float32 frames in [0, 1] instead of uint8, the shapeLTX2ReferenceConditionneeds; a keyframe condition still wantsframes_as_array.ltx2/reference-sheet's default asset is nowasset:reference_sheet.jpg(#444). -
validate_workflowrefuses acomponentsname the pipeline doesn't register;duration_headis 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_videosanddissolve_videospad 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 asjoined_audio_short_after_muxfrom a frame up.media.shotsis measured against the file as written (#426/#435/#454). Neither warns about resampling inputs that agree to a pinnedsample_rate(#453). -
New warnings:
match_levels_near_silent(#434), andshot_span_overrunfrom 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_outputtool andGET /api/gallery/{name}/assess, plus the probe tasksanalyze_shots,analyze_seamsandanalyze_sync_drift(#387/#388). - A joined video records its shot boundaries (
media.shots).get_output_frames(seams=true)uses them, so it no longer needsboundaries(#385). - Run versions (
v<N>):list_galleryreturnsrun_id/versionand filters byfolderandversion;output:<wf>/v<N>/<file>references;wait_for_jobreturnsrun_version;- export zips download as
<wf>-vN-<job>.zip.
list_gallery(media=true)adds durations, andoutput:names work in gallery reads (#356).DW_PUBLIC_URLadds absolute URLs to gallery and export responses.export_jobalso returnsauth_requiredandopen_url(#353).- A
gradetask for images and video: exposure, contrast, saturation and temperature/tint (#349). - The
templates/minimax/shots-batchH3 template (#352). - Every generative template takes a
seedargument (#351). normalize_audio(target_lufs), andintegrated_lufsplus true peak in media metadata (#361).gain_audiowith no region gains the whole track (#395).world_fade_out_msonassemble-and-score(#339).- Download progress shows in
phase_detail(#343).phase_stallevents now read as informational (#357). workflow,inline_workflowandpromptalso 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'seventor itskind, so["phase_stall"]selects one warning type (#436).get_memoryreports the step cache'sentriesandretained_bytes(#418).get_output_imageand/outputsresolveasset:references (#445), andget_output_frames(seams=true)works on linked assets (#430).- Compact
assess_outputlists 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) andreference-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).