Ouroboros (Self-Correcting Sampler) π
The sampler that critiques its own output until you say stop
- model
- clip
- vae
- latent
- enhancer_settings
- critic_settings
- sampler_settings
- model_negative
- images
- prompts
- judge_data
- best_image
- best_prompt
- best_score
- report
- iterations
- captions
- times
- settings_data
Ouroboros is the self-correcting sampler: a closed loop where an LLM expands your prompt, an image gets sampled, a vision critic scores it and tells you (in the form of advice) how to improve the prompt, and it repeats until the score hits a target or you run out of iterations. It's one node that replaces the "generate, squint, retype the prompt, generate again" ritual you've been doing by hand - and it keeps the best image across the whole run, not just the last one.
How the loop works
Each iteration is: LLM rewrites/expands the prompt β an image is sampled β a vision critic scores it and returns concrete advice plus negative-prompt terms for the flaws it sees β the advice feeds the next revision. Two details make the loop meaningful rather than random:
- Fixed seed, fixed start latent. Only the prompt varies, so the score differences are a clean signal about the writing, not about seed luck. (Sampler Settings can opt out of this with its
seed_mode, butfixedis the honest default.) - Negative terms accumulate. The critic's flaw terms get deduped and appended onto your base
negativeeach iteration, so the model is progressively told what not to do.
The loop is a plain Python while inside the node - no graph expansion - and generation runs through ComfyUI's own custom-sampler API (comfy.sample.sample_custom), which is what lets Sampler Settings expose per-sampler knobs like eta. Single-stage results match the classic KSampler path, so it's not some exotic sampler; it's a KSampler with a critic standing over its shoulder.
Wiring it up
The required inputs read like a shopping list of the pack's bundle idiom:
model/clip/vae- your checkpoint.latent- an Empty Latent matching your model sets the image dimensions. For img2img or hires feedback, feed a real latent and setdenoise < 1in Sampler Settings.user_prompt- your intent. The enhancer improves it; the critic anchors its advice to it.negativeis the base; flaw terms accumulate onto it.enhancer_settings- aLocal LLM Settings (GGUF)for the prompt LLM. Its system prompt defines how the prompt is expanded. On low VRAM, point it at the same model file as the critic.critic_settings- anOuroboros Critic Settingsbundle: the vision judge + scoring rules.sampler_settings- aSampler Settingsbundle, or a chain of them. One = classic single pass. Two or more = refine mode: stage 1 drafts, later stages polish the previous latent (give themdenoise < 1), and the critic judges only the final image.target_scoreandmax_iterations- the two stop conditions. The score scale comes from the critic'sscore_min..score_max(default target 4 out of 5-ish on that node's default rubric), so set it on the Critic Settings, not here.low_vram- on by default, and leave it on unless you have VRAM to spare. It keeps strictly one model in VRAM at a time: frees ComfyUI models before each LLM call, unloads the worker after, and auto-sizes each LLM's context to what the request needs.
Useful optional: trigger_words (comma-separated, e.g. LoRA triggers from Lora Unlim Accumulator) are always appended to the enhanced prompt so an LLM rewrite can't drop them. enhancer_history recaps the last N prompt+score pairs as "already tried, don't repeat." model_negative is for checkpoints shipped as a model + uncond-model pair (Ideogram) - note it does nothing at cfg 1.0, because the guider skips the uncond pass there.
Outputs
images, prompts, judge_data, captions, times and settings_data all feed Image Compare directly - wire the whole trajectory in and flip through iterations. best_image / best_prompt / best_score are the winners. report has the full trace. Wire best_score into a Settings Save and your measured results start building a library of what actually works.
Installing it
Same pack: ComfyUI Manager β "Kinburg-Nodes", or git clone https://github.com/Kinburg/Kinburg-Nodes into ComfyUI/custom_nodes, restart. You need two model downloads of your own: a GGUF LLM for the enhancer and a vision (mmproj) model for the critic, plus llama-cpp-python - installed automatically by the pack's install.py. First-time confusion is almost always the critic: a vision model without an mmproj isn't a critic, it's a chat model looking at nothing.
Inputs (17)
| Name | Type | Default | Description |
|---|---|---|---|
| model | MODEL | β | |
| clip | CLIP | β | |
| vae | VAE | β | |
| latent | LATENT | Starting latent β sets the image dimensions (wire an Empty Latent, matching your model: SD/SDXL vs SD3/Flux). For img2img/hires-feedback, feed a real latent and set denoise<1 in Sampler Settings. Reused each iteration so only the prompt varies. | |
| user_prompt | STRING | Your intent β what you want. The enhancer improves it; the critic anchors advice to it. | |
| negative | STRING | Base negative prompt; the critic's flaw terms accumulate onto it each iteration. | |
| enhancer_settings | KINBURG_LLM_CONFIG | A 'Local LLM Settings (GGUF)' for the prompt-enhancer LLM β its model/temperature AND its system_prompt define how the prompt is expanded. On low VRAM, point it at the SAME model file as the critic. | |
| critic_settings | KINBURG_CRITIC | A 'Ouroboros Critic Settings π' bundle β the vision judge + scoring rules. | |
| sampler_settings | KINBURG_SAMPLER_CFG | A 'Sampler Settings' bundle β OR a chain of them (wire Sampler Settings nodes in series). One = classic single pass. Two or more = REFINE mode: each iteration runs the stages in order (stage 1 drafts, later stages polish the previous latent with their own denoise / sampler / eta); the critic judges only the FINAL image. | |
| target_score | FLOAT | 4.00β100 | The loop stops once the overall score reaches this (on the critic's score_min..score_max scale). |
| max_iterations | INT | 61β100 | Hard cap on refinement rounds. |
| low_vram | BOOLEAN | true | Low-VRAM mode (default ON). ON: strictly one model in VRAM at a time β frees ComfyUI models before each LLM call, unloads the LLM worker after it, and AUTO-SIZES each LLM's context (n_ctx) to what the request actually needs, so the n_ctx set on the LLM Settings nodes becomes the MAXIMUM. Best when the GPU can't hold the diffusion model + the LLM at once. OFF: keep everything resident (fastest, no reloads) and use the full n_ctx from the Settings nodes β for GPUs with VRAM to spare. NOTE: inside the loop this overrides the unload_* toggles on the LLM Settings nodes. |
| model_negativeopt | MODEL | Second model for the UNCONDITIONAL (negative) pass β for checkpoints shipped as a model + uncond-model pair, such as Ideogram. The loop builds the dual-model guider itself on every iteration, so the freshly rewritten prompt is still what gets encoded (wiring a ready-made 'Dual Model CFG Guider' node here instead would freeze the conditioning and defeat the whole loop). NOTE: at cfg 1.0 the guider skips the uncond pass entirely and this model does nothing β give the Sampler Settings a cfg above 1. | |
| enhancer_historyopt | INT | 40β50 | How many of the most recent iterations (prompt + score) are recapped to the enhancer LLM as 'already tried, do not repeat' context. Higher = more memory of what failed (longer prompt, more tokens); 0 = no history (each rewrite sees only the intent, current prompt and the latest critic advice). The latest advice is always sent regardless. |
| trigger_wordsopt | STRING | Comma-separated words ALWAYS appended to the enhanced prompt (e.g. LoRA triggers from Lora Unlim Accumulator's 'triggers' output), so the LLM rewrite can't drop them. | |
| full_console_logopt | BOOLEAN | true | Console/terminal verbosity only (not the Live Log node or 'report'). On: per iteration also prints the enhanced prompt, advice and negative additions. Off: just a one-line score per iteration. The full trace is always in the 'report' output and the Live Log node. |
| log_modeopt | COMBO | streaming | How the Ouroboros Live Log node updates: 'streaming' is like 'per step' but the enhancer's prompt types out token by token as it's written (the critic's verdict still arrives whole β it simply isn't wired to the log that way); 'per step' posts each stage the moment it finishes (enhanced prompt β generated image β critic verdict), each timestamped; 'per iteration' posts one combined entry after the whole iteration (the older behaviour). |
Outputs (11)
| Name | Type | Description |
|---|---|---|
| images | IMAGE | β |
| prompts | STRING | β |
| judge_data | STRING | β |
| best_image | IMAGE | β |
| best_prompt | STRING | β |
| best_score | FLOAT | β |
| report | STRING | β |
| iterations | INT | β |
| captions | STRING | β |
| times | STRING | β |
| settings_data | GEN_SETTINGS | β |