Prompt Scheduler
The engine that turns wildcard folders into batched generations
- positive
- negative
- prompt_json
This is the engine room of the pack - the node that turns a prompt library into a batch. It stacks multiple prompt tracks, expands wildcards, and spits out ready-to-encode positive and negative strings plus a prompt_json that carries the resolved recipe downstream to the Image Saver. If you've ever hand-rolled prompt matrices with {a|b} syntax, this is that, but with a real library behind it and a sane way to control batch size.
The inputs
Four required, and they're worth understanding as a group:
config_json(STRING, multiline) - the whole scheduler config in one JSON blob, defaulting to{"start_index":0,"task_count":1,"negative":"","tracks":[]}. The frontend writes it when you build tracks; you can edit it directly if you like living dangerously.execution_index(INT) - which job in the batch this run is. Sequential tracks pick their candidate from this index.run_id(STRING) - for batch runs: it ties executions together so the pack can track and complete the batch's task list.seed(INT) - the selection seed for random tracks.
There are also dynamic STRING inputs the frontend creates per track (and one for negative) that override that track's text for a single execution - the "external socket" path. Note the subtle behavior: the override only applies to that execution and is never written back into config_json, so the UI stays clean.
The outputs
positive(STRING) andnegative(STRING) - the assembled prompts, wildcards expanded, ready for a CLIP Text Encode.prompt_json(STRING) - a compact record of the resolved tracks, positive, and negative. This is what the pack's Image Saver consumes so the exact prompt text and settings get written into the image metadata.
How the wildcard engine works
It speaks Impact-compatible syntax - the same __folder__ / __path/file__ and {a|b} language Impact Pack popularized - plus weights, multi-select, and folder/global name lookup. But the scheduler's real personality is its nested wildcards and sequential leaf space. The rules, in one breath:
- Nested branches under one key sum (each candidate's own expansion runs end to end);
- side-by-side
__a__, __b__in one track multiply (cartesian product); - multiple sequential tracks take the longest (shorter tracks wrap);
{a|b}choices stay random and never lengthen the cycle.
That distinction - __key__ alternates sequentially, {a|b} stays random - is the part people get wrong. A common pattern the docs push: outer cards hold LoRAs/presets you want to compare while inner __scenes__ hold shared prompts, and Count tasks (which uses the same leaf space) gives you the real job count, not just the top-level folder size. Nested expansion also carries a card's LoRAs, negatives, and sparse generation settings along with its text, and can auto-apply models/LoRA/params to matching nodes before queueing and restore the canvas after.
One implementation note that saves you a confusing hour: the node returns NaN from its IS_CHANGED method, which forces it to re-run every time instead of trusting the cache. That's deliberate - the library changes on disk between queues, so stale cached text would be wrong. The tradeoff is that this node pins a bit of the graph's caching, which is fine for a prompt node and not fine if you're trying to cache a huge expensive upstream subgraph.
Installing it
Ships with the pack:
cd ComfyUI/custom_nodes
git clone https://github.com/tsukino4a/ComfyUI-4A-Prompt-Manager.git
cd ComfyUI-4A-Prompt-Manager
python install.py # or: pip install -r requirements.txt
Restart after; ComfyUI Manager users search 4A Prompt Manager. No model downloads. Read docs/wildcard-nesting.md in the pack - it's the actual manual for the counting rules, with worked examples you can check against Count tasks.
Troubleshooting
- Batch runs fewer/more jobs than you expected. You're thinking top-level folder size; it's counting leaf space. Recheck nested sums vs products against the docs.
- Outputs look stale. Shouldn't happen (NaN IS_CHANGED), but if you're on an old version, update the pack.
- Tracks silently missing from the output. Only enabled tracks expand - check the track's enabled flag in the config. And a track whose text expands to nothing is dropped from
positive(and fromprompt_json), so an empty track isn't an error, it's just absent. - Random tracks repeat across a batch. They use the
seedinput; give different seeds per job (or letrun_idbatch handling manage selection) if you want variety.
Inputs (4)
| Name | Type | Default | Description |
|---|---|---|---|
| config_json | STRING | {"start_index":0,"task_count":1,"negative":"","tracks":[]} | — |
| execution_index | INT | 00–2147483647 | — |
| run_id | STRING | — | |
| seed | INT | 00–18446744073709550000 | — |
Outputs (3)
| Name | Type | Description |
|---|---|---|
| positive | STRING | — |
| negative | STRING | — |
| prompt_json | STRING | — |