LTX 2.3 · audio-lipsync · iteration loop

Every value on the run card maps back to one node on the canvas — and nothing else.

The card the maintainer pins next to each render is generated by scripts/loop_audio.py from the saved workflow JSON. To keep what's shown honest, the card uses only two source families: the five tracked Text Multiline nodes (the per-render notes), and the active LTX Director node's own widget values + input wiring. The run notes also capture the SETUP variables block as audit context. No BasicScheduler, no KSamplerSelect, no git status, no _dev_build.json. If a value can't be traced to one of those two sources, it doesn't appear on the card.

The reading graph

Workflow JSON → extract → state dict → card sections

Top-down. Each arrow is a one-way data dependency.

Canvas — the source nodes

name

Run identifier. Prefers GLOBAL [ base name ]; fallback NAME.

Text Multiline

RELAY_OVERRIDES

JSON object of Prompt-Relay knobs. Wired into the Director's relay_overrides input.

Text Multiline

OVERLAY · INFO

Δ-this-render free-form note. Card renders this verbatim.

Text Multiline

OVERLAY · FEEDBACK

Post-render observations. motion:N/5 / sync:N/5 / sharp:N/5 lines auto-parsed into Outcome.

Text Multiline

working_folder

Working folder for renders. Prefers GLOBAL [ path ] - working folder; first reachable duplicate wins.

Text Multiline

SETUP variables

INPUT Path [ EXR ], GLOBAL [ version ], and GLOBAL [ run offset ] are captured into notes.md. BASE is a checklist; its [ai] rows are filled from derived facts.

Text Multiline / Primitive*

LTX Director

The active Director node: Koolook for modified runs, upstream original for A/B comparison.

LTXDirector__koolook / LTXDirector
The thin interface

loop-audio

Chat phrase → python scripts/loop_audio.py  ·  user-initiated only, never automatic  ·  config: scripts/loop_audio.config.json

Belly — extraction module
scripts/loop_audio.py Pure JSON-walking. No I/O outside the workflow file.

find_workflow()

Newest JSON in the ComfyUI workflows dir matching workflow_pattern; excludes filenames containing skip_filename_substring.

config-driven · stdlib only

extract_multilines()

Dict-of-lists keyed by semantic capture name. tracked_multilines maps each key to preferred title aliases, so the v02 GLOBAL labels can coexist with older names.

config.tracked_multilines alias map

extract_setup_variables()

Reads extra SETUP values for notes.md, including primitive version/run-offset nodes, while ignoring GetNode/SetNode plumbing and BASE placeholders.

config.tracked_setup_variables

extract_director()

Finds the Koolook Director, its legacy alias, or upstream LTXDirector. Koolook wins if both are on the canvas. Returns the whole node dict so callers can read widgets AND inputs[].link.

DIRECTOR_TYPES priority

parse_timeline()

Decodes the timeline_data JSON widget into segments + audioSegments arrays. Returns empties on any error.

director widget timeline_data

derive_audio_state()

Reduces audio_vae link + use_custom_audio + audioSegments count to one of five labels — including the "(no director)" split that prevents a missing-Director workflow from silently collapsing into "VAE unwired".

5-state machine

parse_feedback()

Splits OVERLAY · FEEDBACK into motion / sync / sharp scores and prose lines via per-line regex.

SCORE_PAT regex
Audio-src state machine — derived structurally, never from prompt text
(no director) No Koolook or upstream Director node on the canvas — Director structural state is unavailable.
off (no VAE) Director present · audio_vae not wired — no audio latent produced regardless of any other widget.
model-gen audio_vae wired · use_custom_audio = False · audioSegments ignored.
custom audio_vae wired · use_custom_audio = True · audioSegments non-empty.
custom (empty) audio_vae wired · use_custom_audio = True · audioSegments = [].
The state dict — single contract between loop and renderer

_build_state_for_card()

Returns a flat dict with the canonical field set the renderer consumes. Building it in one place — not inside the renderer — keeps the source rule auditable: every key here can be traced to one node on the canvas.

run_number · run_label · date · workflow_name · source_workflow_name
name · relay_overrides_raw · info_body · feedback_lines · scores · work_folder
output_folder · output_name · metadata
director_node · director_variant · director_flavor · director_pin_tag · audio_src
epsilon · frame_rate
segments · audio_segments · segment_prompt_mode
Card sections — which source feeds each
Section Accent Source family Exact source
HEADER multiline + loop bookkeeping name multiline + loop's own run number + date + module name
BASE / RUN sky workflow + multiline + setup copied workflow stem (no .json) + expected output folder/name/version
DIRECTOR READINGS sky director + live upstream package metadata Director flavor + pin tag + epsilon + derived audio_src + timeline_data segment coverage
KNOB STATE amber multiline relay_overrides multiline (verbatim; marked inert when upstream Director is active)
BASE NOTES amber multiline overlay - info multiline (verbatim, blank lines preserved)
POST-RENDER green multiline overlay - feedback multiline → feedback body + parsed motion/sync/sharp scores
Snapshot folder — what the loop writes per render
runNNN_workflow.json Run-tagged, path-redacted snapshot of the saved workflow at submission. runs/run-NNN_<label>/
card.png Rendered card matching this graph plus embedded koolook_audio_loop JSON metadata. runs/run-NNN_<label>/
metadata.json Structured run/setup/director/repo metadata, including copied workflow source and Director pin tag. runs/run-NNN_<label>/
notes.md OVERLAY-FEEDBACK + OVERLAY-INFO verbatim + captured SETUP variables + director's structural state. runs/run-NNN_<label>/
relay_overrides.txt RELAY_OVERRIDES body in plain text, diff-friendly. runs/run-NNN_<label>/
patch_state.txt MAIN sha + last dev-sync-audio sha + fork-dir clean/dirty; also represented in metadata. runs/run-NNN_<label>/
log.md row Appended to runs/log.md. Same column rule as the card (multilines + director only). runs/log.md
Forbidden sources

What is deliberately not read

Every removal here was a card-design fix.

BasicScheduler · KSamplerSelect · RandomNoise · CFGGuider widgets

The previous card surfaced these as a SAMPLER block. But this loop sweeps relay_overrides, not the sampler — surfacing them mixed Δ-this-run signal with frozen-across-the-sweep state and produced cards that contradicted the maintainer's curated OVERLAY-INFO.

_dev_build.json + git status of the fork dir

The previous card had a FORK STATE block reading the dev-sync footer and the working-tree status. Useful audit trail, but neither is a multiline nor a director value — so they're forbidden on the card and stay in patch_state.txt instead.

Substring matches on prompt text (e.g. "Audio:")

Per-segment audio coverage used to be inferred from a substring search on the prompt. That treats the maintainer's [Audio]: formatting convention as a wiring signal, which it isn't — the director reads audioSegments, not the prompt. Coverage now overlaps audioSegments against segment time ranges.

Hardcoded sampler / CFG / seed values

base-1step's card hardcodes Sampler = euler and CFG = 1.0 because they never change in that loop. For audio-lipsync we drop the rows entirely — the Koolook Director doesn't expose them, and hardcoding contradicts the automation principle.

Trigger surface

What the maintainer types and what runs

User-initiated only. Never on commit, never on session end.

Chat phrase Script What it does
loop-audio scripts/loop_audio.py This document. Snapshots the most recent render into runs/run-NNN_<label>/ and appends a row to runs/log.md.
dev-sync-audio scripts/sync_to_dev_audio.py Copies forks/whatdreamscost_koolook/, web/whatdreamscost_koolook/, and root __init__.py into the live ComfyUI install ($KOLOOK_COMFYUI_DEV_PATH) and removes stale pre-v1.3.9 web extension folders. Restart ComfyUI manually after Python changes. Not required for widget-only changes on the canvas.

Companion docs: the machine-readable schema next to this file (reading-graph.schema.yaml) lists every node, widget index, and card field in YAML so an agent can rebuild the pipeline without reading prose. The short cheat sheet (CHEATSHEET.md) is the minimum a maintainer needs to drive the loop on a fresh machine.