Z-Image Turn Builder
Multi-turn character edits for Z-Image
- previous
- clip
- conversation
- conditioning
- formatted_prompt
- debug_output
Here's the clever bit of this whole pack. Because Z-Image's text encoder is a real language model (Qwen3-4B), you can treat prompting like a conversation instead of a one-shot string. Define a character in turn one, then in turn two say "add a flying sloth above him" - and the model still sees the full original definition plus every edit since. ZImageTurnBuilder is the node that appends each new turn onto an existing conversation and keeps that context intact.
This is the character-consistency play, done with prompt structure rather than a LoRA or an IP-Adapter. It won't hold a face across a whole workflow the way a trained LoRA does, but for iterative "keep this guy, now change one thing" edits inside a single Z-Image session, it's genuinely neat.
How it works
You start a conversation with ZImageTextEncoder, which emits a ZIMAGE_CONVERSATION object alongside its conditioning. ZImageTurnBuilder takes that object in, tacks on a new user turn, and gives you back an updated conversation plus fresh conditioning encoding the whole history. Chain several builders and you've got a multi-turn edit sequence - the README's Walter Finch example defines a 72-year-old British gentleman in turn one, then adds a floating sloth in turn two, and the model renders the edit while remembering the original character sheet.
The catch is inherent to how these LLM encoders work: everything you add accumulates tokens, and drift creeps in past roughly 75-100 effective tokens. Long chains of turns eventually blow past the model's comfortable attention window, and earlier detail starts to smear. Keep turns punchy.
The inputs and outputs that matter
previous(required,ZIMAGE_CONVERSATION) - the conversation so far. This is the whole point; it comes fromZImageTextEncoder'sconversationoutput or an upstreamZImageTurnBuilder.user_prompt(required) - the edit for this turn ("add a flying sloth above him").clip(optional) - connect it and this node encodes the turn directly, so you don't need a separate encoder at the end of the chain.thinking_content/assistant_content- the same<think>and assistant channels as the base encoder, per turn.is_final(default true) - marks whether this is the last turn, which affects how the conversation is closed off for encoding.
Outputs: conversation (the extended history - feed it to the next builder), conditioning (to your KSampler when this is the final turn), plus formatted_prompt and debug_output so you can see exactly what the accumulated conversation looks like.
How to install it
ComfyUI Manager: search ComfyUI-QwenImageWanBridge, install, restart. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/fblissjr/ComfyUI-QwenImageWanBridge
then restart. You need a Z-Image checkpoint and the Qwen3-4B encoder; there's an example multi-turn workflow in the repo (z-image_custom_nodes_multi-turn_builder_workflow.json) that's the fastest way to see the wiring.
Common issues & troubleshooting
Nothing chains - the node complains about previous. That input is required and must be a ZIMAGE_CONVERSATION, which only ZImageTextEncoder (full version, not Simple) or another turn builder produces. Start the chain with the full encoder, not the simple one.
Later turns forget the character. That's the token-drift ceiling biting. Trim each turn to essentials; a five-turn chain of verbose descriptions will overrun the attention window and blur the original definition.
Edits blend instead of replacing. The README saw this directly - swapping a class (sloth for cat) often produces a mix, because thinking/assistant content is weighted below the user prompt. Push the change harder in user_prompt rather than relying on the softer channels.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| previous | ZIMAGE_CONVERSATION | Previous conversation (from ZImageTextEncoder or another TurnBuilder) | |
| user_prompt | STRING | User's message for this turn | |
| clipopt | CLIP | Connect to encode and output conditioning directly (skip chaining back to encoder) | |
| thinking_contentopt | STRING | Assistant's thinking (only if conversation has enable_thinking=True) | |
| assistant_contentopt | STRING | Assistant's response after thinking | |
| is_finalopt | BOOLEAN | true | Is this the last turn? If True, last message has no <|im_end|> |
| strip_key_quotesopt | BOOLEAN | false | Remove all double quotes from JSON-style prompts. Keys ("subject":) and values ("text") both get quotes stripped. |
| filter_paddingopt | BOOLEAN | true | Filter padding tokens (matches diffusers/DiffSynth). Only applies when clip is connected. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| conversation | ZIMAGE_CONVERSATION | — |
| conditioning | CONDITIONING | — |
| formatted_prompt | STRING | — |
| debug_output | STRING | — |