Nodes/Kinburg-Nodes/Ouroboros (Self-Correcting Sampler) 🐍
ComfyUI Node

Ouroboros (Self-Correcting Sampler) 🐍

The sampler that critiques its own output until you say stop

By KinburgΒ·Created 3 months agoΒ·Updated 6 days agoΒ· 1
Ouroboros (Self-Correcting Sampler) 🐍
  • 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
β—„user_promptβ–Ί
β—„negativeβ–Ί
β—„target_score4.0β–Ί
β—„max_iterations6β–Ί
β—„low_vramtrueβ–Ί
β—„enhancer_history4β–Ί
β—„trigger_wordsβ€”β–Ί
β—„full_console_logtrueβ–Ί
β—„log_modestreamingβ–Ί

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, but fixed is the honest default.)
  • Negative terms accumulate. The critic's flaw terms get deduped and appended onto your base negative each 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 set denoise < 1 in Sampler Settings.
  • user_prompt - your intent. The enhancer improves it; the critic anchors its advice to it. negative is the base; flaw terms accumulate onto it.
  • enhancer_settings - a Local 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 - an Ouroboros Critic Settings bundle: the vision judge + scoring rules.
  • sampler_settings - a Sampler Settings bundle, or a chain of them. One = classic single pass. Two or more = refine mode: stage 1 drafts, later stages polish the previous latent (give them denoise < 1), and the critic judges only the final image.
  • target_score and max_iterations - the two stop conditions. The score scale comes from the critic's score_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.

CategoryKinburg-Nodes/Bestiary/Ouroboros

Inputs (17)

NameTypeDefaultDescription
modelMODELβ€”
clipCLIPβ€”
vaeVAEβ€”
latentLATENTStarting 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_promptSTRINGYour intent β€” what you want. The enhancer improves it; the critic anchors advice to it.
negativeSTRINGBase negative prompt; the critic's flaw terms accumulate onto it each iteration.
enhancer_settingsKINBURG_LLM_CONFIGA '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_settingsKINBURG_CRITICA 'Ouroboros Critic Settings 🐍' bundle β€” the vision judge + scoring rules.
sampler_settingsKINBURG_SAMPLER_CFGA '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_scoreFLOAT4.00–100The loop stops once the overall score reaches this (on the critic's score_min..score_max scale).
max_iterationsINT61–100Hard cap on refinement rounds.
low_vramBOOLEANtrueLow-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_negativeoptMODELSecond 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_historyoptINT40–50How 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_wordsoptSTRINGComma-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_logoptBOOLEANtrueConsole/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_modeoptCOMBOstreamingHow 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)

NameTypeDescription
imagesIMAGEβ€”
promptsSTRINGβ€”
judge_dataSTRINGβ€”
best_imageIMAGEβ€”
best_promptSTRINGβ€”
best_scoreFLOATβ€”
reportSTRINGβ€”
iterationsINTβ€”
captionsSTRINGβ€”
timesSTRINGβ€”
settings_dataGEN_SETTINGSβ€”