Skip to content

Krea 2 Image Creator V2.5.6 — Handbook

Put yourself into new scenes, at camera angles your photos never showed, on Krea 2. Train once, generate forever. For a RunPod pod running ComfyUI on an RTX PRO 6000 Blackwell (96 GB, sm_120).

VRAM. 29 GB minimum (estimated, ComfyUI swapping models between stages), 38 GB comfortable (everything resident).

file role goes on the pod
SoulCraft Image Creator (Krea 2).json the graph yes
image-creator-krea-2-script.sh the thin installer on ComfyUI Base: 26 packs, 28 files, the llama-cpp-python build and the import check yes
suite.py + pytest.ini the acceptance suite, 17 tiers, run by … test yes
models.py the model library contract — every filename, once; the installer’s MODELS rows are generated from it yes
image-creator-krea-2-runpod.zip the pod files, bundled (built by base/comfyui-base/_build/package.py) uploaded, then extracted
image-creator-krea-2-handbook.md this yes — four tests read it
_build/build_workflow.py generates the workflow — the JSON is an artifact, this is its source no
_build/graph.py the graph model the builder and the suite share no
_build/layout.py + _build/appmode.py the canvas geometry and the App Mode form, both run after the builder no
_build/appe2e.py + _build/pod_flows.py drive the real App Mode form on the pod, through the DOM no
_build/appview.py, probe_states.py, prompt_gate.py, object_info.py what the panel renders, the states probe, ComfyUI’s own /prompt validator, the cached schema no
_archive/ the ProGrade original the graph is transformed from — the build reads it, so it is not archive in the disposable sense no

0. Read this first

The one thing that makes this work

A reference photo cannot tell the model what your profile looks like. Reference-image conditioning interpolates over what the photos show; a side view from front-facing photos is extrapolation, and every model — Krea 2, FLUX.2’s ten slots, Qwen-Edit, LongCat — will render a confident, plausible, wrong jawline. The Krea 2 Identity Edit model card says so itself:

“strongly distinctive facial geometry (unusual nose, eye spacing, face length) regresses toward typical proportions … geometry-defined faces read as a ‘close relative.’”

So this package does not try. You train a LoRA of yourself once, and your geometry goes into the weights. After that, ① Create mode needs no reference image at all — you write a prompt and it is you, at any angle.

Corollary worth stating: generated character reference sheets are a trap for a real person. The model invents your profile, you feed the invention back as ground truth, and you get excellent consistency of a stranger who resembles you from the front.

So the character sheet (now in Krea 2 Character Creator) is for a character you invented — an AI influencer, a persona, a face that exists only in one reference render — where the invention is the ground truth, and for augmenting a thin real set with more angles. For yourself, train on your photographs (Krea 2 Character Creator takes a zip of them); the sheet would teach the model that stranger.

Design rules the graph and the installer rely on

  1. Mode and quality are numbers, not bypass panels (V1.4.0). They are PrimitiveInt widgets feeding core ComfyUI’s ComfySwitchNode, which is lazy on both branches so the branch you did not choose is never executed. This is not a preference: App Mode can expose a node’s widgets and inputs, and never group state, so the front door could not have been built on rgthree radios. The «MODE» and «TIER» markers are retired and a test asserts they are gone — two controls for one decision disagree the moment either is touched. V2.1.0 retired the 🧩 MODEL radio («PATH») the same way — the router picks the loader with lazy switches — so the 🎛️ SUBJECT LoRA switch («LORA») is the one panel left, and for it membership is decided by group bounds and moving a node across a group edge still changes behaviour. Nothing may be bypassed outside every group; test_nothing_bypassed_outside_every_group enforces it, and it measures zero against the ProGrade archive too.
  2. The model library is a contract. Every filename lives in models.py once. Loader widgets are derived from it, the installer’s MODELS rows are generated from it, and test_contract_installer_table_matches_models_py fails if either drifts.
  3. Shared pod: the base’s rules, not this file’s. One venv stamp for every package, the base owns comfyui_args.txt, and deletion is scoped by the base’s ledger so nothing another package installed is ever offered. test_contract_shared_pod_rules_hold now checks that this script contains none of that machinery itself.
  4. The three shared packs come from the base. rgthree-comfy, ComfyUI-KJNodes and cg-use-everywhere are pinned by ComfyUI Base and may not be redeclared here; the other 26 are this package’s, pinned by commit.
  5. Superseded names are Krea basenames only, offered inside Krea2/ folders after a y/N that defaults to N.
  6. The output size is set by you, never derived from a reference. This is the Flux 2 V2.0.0 defect, and Krea 2 is structurally safe from it — Krea2EditModelPatch fits the source to the target grid, so the arrow runs target→source. Guard-tested anyway.
  7. widgets_values_named and widgets_values must agree. See §5 — this caused four defects.
  8. One version everywhere. KR_VERSION, extra.kr_version, the script banner and line 1 of this handbook must agree.
  9. The base is pinned by BASE_MIN, and a bump is not optional. V1.4.0 needs ComfyUI Base 2.0.35 — Budgets.leaf_bloat_max and Budgets.coord_max are read at suite import, so a pod carrying an older base fails with “1 error during collection” and names nothing. BASE_MIN turns that into one clear line. (2.0.35 exists because 2.0.34’s zip was amended three times under the one version, which the base’s own handbook forbids for exactly this reason.)
  10. The session cycle: plan → implement → App Mode test → loop. A detailed plan first. Then implement the whole plan — every phase, every milestone — with no testing at all: not after each edit, not after each task, not “just this one tier”. Running a build tool (_build/build_workflow.py, layout.py, appmode.py, and the base’s snapshot.py, package.py) or measuring the graph for a number is not testing. Then the test phase begins and App Mode is what gets tested, on RunPod: open the workflow from the pod’s own workflow list, confirm it lands in the form rather than the canvas, type into the real fields, press the real Run button — never queuePrompt from the console, which passes with the panel completely broken — and get an image out. Then loop until App Mode works for every flow: ① Create, ② Restage, ③ +person 🔁 Head Swap and 🔄 Replace, across the tiers and the four model paths. The offline suite is necessary and not sufficient: V1.4.0 was green on 160 tests with ComfyUI’s own validator accepting all 32 states, while the panel rendered positive · value · value · image · image and silently dropped two of its seven fields. The repo-root CLAUDE.md carries this for every package.
  11. A run hands you a list — fix all of it before the next run. A suite pass, a render.py --review, a rebuild or a podctl install reports every failure it found. Work the whole list, plus anything you noticed while reading it, and only then pay for the next cycle. One fix per cycle is what turned a six-item canvas review into three full rebuilds during V1.4.0: height, Mpx-per-node, fill, a clipping note, eleven bloated frames and a stale NEST were all in the first report. Several failures usually share one root — stale constants after an architecture change, one table that needs re-measuring, one number in this file. The only exception is a blocking finding that hides the rest (an import error, a build that cannot produce the artefact): fix that alone, re-run to reveal the real list, then work it whole.

Quick start

Terminal window
# from the Mac — the one command (base 2.0.14+): upload, extract, --check, run, poll, fetch the logs, save the last-tested rows
uv run "base/comfyui-base/_build/pod/podctl.py" install <pod> --base --pkg "image-creator-krea-2"
# by hand on the pod — a package extracts into its own folder (base 2.0.17)
cd /workspace/packages
python3 -m zipfile -e "comfyui-base-runpod.zip" . && bash "comfyui-base/comfyui-base-script.sh" # step one, once per pod
mkdir -p "Krea 2 Image Creator" && python3 -m zipfile -e "image-creator-krea-2-runpod.zip" "Krea 2 Image Creator"
bash "image-creator-krea-2/image-creator-krea-2-script.sh" --check # look first, changes nothing
BASE_RESTART=1 bash "image-creator-krea-2/image-creator-krea-2-script.sh" # install, then restart onto the base's venv
bash "image-creator-krea-2-script.sh" train ~/my-photos astrx7 --check # read your photos
bash "image-creator-krea-2-script.sh" train ~/my-photos astrx7 # ~1 hour, under $2

Six command forms, the same as every package on the base — bare, --check, --latest, test [tier], rescue, help — plus train.

Requires ComfyUI ≥ 0.34.3 and NVIDIA driver ≥ 580. Both are hard gates — the run stops before anything is downloaded.


1. Using the workflow

From V1.4.0 the workflow opens as a form, not a canvas. extra.linearMode is set, so ComfyUI’s App view is the front door: five fields and a picture, with the 379-node graph behind it. Press the canvas toggle in the top bar when you want the engine; nothing about it changed.

the form what it does
📝 Prompt describe the picture. Name your character by its trigger word. Krea 2 wants plain sentences — weight syntax like (word:1.5) comes out as literal text
📷 Photo 1 optional. One photo and it edits that photo; with photo 2 this is the scene
📷 Photo 2 optional. The person to place, or the face to swap in. The order is fixed by training: scene first, person second
🧬 Character a LoRA from Krea 2 Character Creator, or upload one with the button. — none — makes pictures without one
🎚️ Quality ⚡ Iterate 8 steps · ◆ Quality 12 · ★ Master 40 at CFG 3.5. Removals need ★

Five fields, and that is the whole form. What used to be four more dropdowns — mode, model, style, subject strength — is decided for you by the router, from your words and what you uploaded. It tells you what it chose, spelled out under the picture in the output panel and written into the PNG’s own extra_params.

The file it saves is named for you, not for the machine. Until V2.0.0 the routing decision was the filename — Krea2_edit-master-raw_2026-09-09_00001.png — which is the bill, not the picture. Now it is three words from what you described — your own words, stop-words dropped — and the character, if you chose one, gets a folder of their own:

output/Krea2/masafee/night-market-portrait_2026-09-09_00001.png
output/Krea2/rocky-shore-surfer_2026-09-09_00002.png ← no character

Nothing diagnostic is lost. edit-master-raw is in the panel and in the PNG metadata, which is where you look when you are asking why, rather than which one was that.

A LoRA from anywhere works, and V2.1.0 is where that became true. The trigger word is never used to invoke your character — you type it yourself in 📝 Prompt. Its only job is to survive the rewrite, because Krea 2 has no concept of a trigger and the rewriter will happily turn sofia_face into “a woman”. So the router now collects every word the LoRA might answer to — from the safetensors header (ss_tag_frequency, the dataset folder, the output name), and from every underscore-run in the filename — and protects the one you actually typed.

That matters because our own naming rule only ever worked for our own files. Measured against three real Krea 2 LoRAs on Hugging Face, only one of three carries its trigger in its metadata at all; sofia_face_v4_blend.safetensors answers to sofia_face, which is in neither its header nor its whole filename. Before this, the rewriter carefully protected sofia_face_v4_blend — a token the LoRA had never seen — and dropped the one that mattered.

If you pick a character and type no trigger, the receipt says so rather than pretending: “no trigger of its own in your prompt, so nothing is protected.”

A third-party LoRA runs at this package’s strength, not its author’s. The router applies 1.0 when creating and 0.8 when editing a photo — the right numbers for a LoRA trained by Krea 2 Character Creator. Other authors tune differently: madmacs5/sofia-krea2-lora’s card recommends 0.3 (0.2–0.5) for its face LoRA. There is no strength field in the form — five fields is the product — so a LoRA that wants much less than 1.0 can look over-cooked here. Measured on the pod 2026-09-10, Sofia at 1.0 rendered clean; if yours does not, the 🧬 strength is on the canvas. The folder is a separate question with a separate answer — it comes from the filename and never moves, so a character’s pictures stay in one place whatever you type.

How it decides, in order, and mostly without a model at all:

what you gave it what it makes
no photo ① Create — your prompt alone
one photo ② Edit Image, or 📐 Depth if your words ask to keep the pose, composition or layout
two photos 🔁 Head Swap if you asked to move a face or head; otherwise it looks at photo 1 — a scene means ③ Person + Scene, a person means 👥 Two People, unless your words ask to replace or remove that person, which means 🔄 Replace

Only the last of those costs a model call, and it is one yes/no question about photo 1 (~0.7 s). Everything above it is the photo count and two phrase patterns. If it ever chooses wrongly, open the canvas: 🖼️ Mode, 🧩 Model and 🎨 Style are all still there, each defaulting to ✨ Auto.

The subject LoRA is a form field too, from V1.4.1. It used to ship switched off — which meant App Mode, which draws only a live node’s widgets, silently left both LoRA fields out of the form altogether. It ships live now, and the file it names is real: the installer writes Krea2/Subject/PUT-YOUR-TRAINED-LORA-HERE.safetensors, a safetensors file holding zero tensors, so it loads, patches nothing and changes no image. Pick your own file in 🧬 Your LoRA once you have trained one. Nothing to do on the canvas.

On the canvas, everything else you touch each queue is in the 🎛️ CONTROL PANEL — the same Mode and Quality numbers, the seed, and the 🧩 MODEL picker (✨ Auto · Raw · ⚡ Turbo · GGUF Q8 · INT8, §4; the form reaches the last two by name — type gguf or int8). The engine sprawls behind it and you should never need to open it.

Tidying ComfyUI’s input folder no longer breaks the app (V2.2.0). Both photo fields point at UPLOAD-YOUR-PHOTO-HERE.png whenever you give no photo, so with that file gone EVERY flow failed at LoadImage — even ① Create, which uses no photo (measured 2026-09-10). The node pack now writes it back whenever it is missing: at start-up, and before every Run is checked.

Which mode

Mode is a dropdown on the package’s own node (V1.5.0; a bare integer in V1.4.x), not a bypass panel, and that is the architecture change. Selection used to be group bypass, which App Mode cannot reach; it is a PrimitiveInt into core ComfyUI’s own ComfySwitchNode now. That node declares lazy: true on both branches, so the branch you did not choose is never executed — ① Create does not load the 1.8 GB Identity Edit LoRA and throw it away, which a non-lazy index switch would.

Use ① Create for almost everything. It is the mode that puts you somewhere new. It uses core ComfyUI nodes only — no edit pack, no ref_boost penalty, nothing off-distribution.

Use ② or ③ Restage when you have a specific photograph you want changed: a different outfit, a different background, this person placed into that room. The order in ③ is fixed by how the model was trained — scene first, person second. Swapping them sharply degrades the result.

🔁 Head Swap is ③ with four settings changed — no new pack, no extra VRAM in the other modes. It puts the face from photo 2 onto the body in photo 1: your instruction is blanked so it cannot fight the swap, a preset system prompt tells the model to take the whole head — face, hair and any beard — and keep photo 1’s body, clothing, pose, lighting and background exactly as they are (V2.2.0: naming the facial hair is what took the old man’s beard off — 2 of 2 seeds; the old wording left it on 4 of 4), and photo 2 is cut out of its background first, so the model takes the person and not the room they were standing in. That cutout runs through a lazy switch, so modes ① ② ③ never execute it. The fourth setting, ref_boost 4.0, is meant to make the second reference dominate — and on the pod it never reaches the model: the graph’s Sage Attention node drops the attention mask the boost is built from (measured bit-identical, HANDOFF §7). The swap works on the other three.

🔄 Replace swaps one person in a photo for another (V2.0.0; rebuilt in V2.1.0). Photo 1 holds the scene and the person leaving, photo 2 the one arriving: “replace the bearded man on the right with this person.” It is ③’s engine — the same settings, and ⚡ Iterate is enough — confined to one box:

  1. SAM 3.1 finds who you named. The router reads the noun phrase out of your sentence (“bearded man”) and a side word if you gave one (left, right, in the middle). SAM 3.1, which core ComfyUI loads as an ordinary checkpoint, finds every match; the side — or, without one, the largest match — picks who goes. With no side in your sentence the model is still told one — the side of the frame that match stands in — because told only to stand “in the empty space”, it placed nobody (measured through the form, 2026-09-10).
  2. A box around them is the only thing repainted — and nobody else is in it. SAM 3.1 also finds every person, woman and man in photo 1; anyone who is not mostly the one leaving is cut out of the box with a margin, so a neighbour standing close is never repainted. The sampler starts from photo 1’s own latent with what is left of the box as its noise mask, and the result is laid back onto photo 1 through it, feathered inward. Outside the box the picture is your photo — resized and finished with the rest of the frame, never repainted by the model (measured: a mean difference of 11/255 there, from the upscale and finishing, against 59/255 inside the box).
  3. Two outcomes, told apart by your words alone — there is no field for it:
you type what happens
“replace the bearded man with this person” he is painted out first — his outline filled smoothly from the picture around it — and photo 2’s person is placed in the gap, as themselves. The instruction goes to the model bare: Krea’s rewriter looks at the original photo and would describe him back in
“…exactly where he is” / “…same pose” / “…keep the composition” nothing is painted out; photo 2’s person takes over his pose and his clothes, and the instruction goes through the rewriter like every ③ prompt

Anything else you say about the newcomer is kept. “…with this person wearing a red dress” sends “put this person … standing in the empty space, wearing a red dress” (V2.2.0). Before, the sentence was rebuilt from who and where alone, and because the cleared variant goes to the model bare, the dress never reached it. Where they stand and whether they take the pose are read elsewhere, so those words are not repeated.

The outline is gone; what can remain is a lighter patch (V2.2.0, V2.2.1). Painted out with a smooth fill, the person who left used to linger as a faint outline behind the newcomer — on 3 of 6 fixed seeds on a one-wall photo — because the edit model copies whatever its reference shows, and the fill had his shape. Now the box’s background is regenerated first by the plain base model (no reference image to copy), laid onto your photo through a wide soft edge, and the insert happens on that: on the three seeds that had ghosted, no outline, 6 of 6. What it is told matters as much. Asked for “the same colour, texture and light as around it” it invented cracked plaster, pebbles, even a salmon-pink panel, on 3 of 3 fixed seeds; told what the wall IS it painted a calm one, 3 of 3. So the router now asks its vision model what photo 1’s background is — “a warm cream plaster wall with a fine stucco texture, in soft warm light” — and the fill paints that (V2.2.2; the receipt shows the words it used). What can remain is a rectangle of a slightly different texture or tone from the wall around it — the receipt says so, and names the two ways round it (a new seed; “exactly where they are”, which paints nothing out). Everything tried on the way is in HANDOFF §7.

Removals use the same machinery. One photo and “remove the lamp” is ② Edit Image confined to a box: SAM 3.1 finds the lamp, the whole box is filled smoothly from its surroundings (people in it are kept, as above), and the model is told, bare, “the same scene, with nothing where the lamp was”. Outside the box, your photo. Measured on a sunlit living room: the lamp gone — and the box visible as a patch of re-textured wall. The receipt says to run again if it shows.

Say which person. Nothing can guess whether you mean the man or the woman: name them by what they are or wear (“the woman in red”) or where they stand. A pronoun (“replace him”) makes SAM 3.1 look for any person and take the largest.

This is measured, not hoped. On the pod, 2026-09-10, the same two photos through every design (HANDOFF §7 has each run): asking the identity edit to replace left him standing on ⚡ and re-imagined the scene on ★; repainting only his silhouette removed him and put nobody in; a box, with ③ told “put this person on the right, standing in the empty space”, put photo 2’s person there as herself in 35 s on ⚡. Which variant takes the rewriter was measured too: in place, the bare instruction kept him and borrowed only her hair, and the rewritten one worked; cleared, the rewriter — which sees the ORIGINAL photo — described the man it was meant to replace, so that one goes bare.

📇 Character sheet moved out in V2.0.0. It makes training data, so it lives beside the trainer that eats it: Krea 2 Character Creator. There, you give it a name and one photo and it renders 24 captioned scenes, zips them and trains on them in the same Run — with no mode to pick, because a zip trains on the zip and no zip plus a photo makes the sheet first.

The one performance trap

In Restage modes, ref_boost is a two-position switch, not a slider. Any value other than 1.0 builds a float attention bias; Krea 2’s GQA (48 query heads, 12 KV) makes fused attention kernels refuse a float mask, so it falls back to SDPA. One user measured 1.4 → 3.0 s/it (krea2edit #6, open; hardware and resolution unstated). No attention backend fixes it — the maintainer has said so. Take the cost deliberately when you want likeness, not by accident on every render.

On the pod this trap is not sprung — because the boost is not applied at all. The graph’s Sage Attention node (PathchSageAttentionKJ, auto) wraps every mode, and SageAttention’s dispatcher calls its kernels without the mask it is handed. So ref_boost and ref_boost_a are dropped, and their cost with them: 1.0 and 0.7 render bit-identical from the same seed (HANDOFF §7). Everything above holds where sage is absent.

The post-processing chain, and why its order is what it is (V2.5.0)

The 🎨 Post processing lane is 24 nodes in a fixed order, and the order is the point. It came from Lonecat’s ProGrade v19.2.1, which documents a reason per position; this package had been carrying v15.0.1’s order, from the archive it builds on, until V2.5.0 ported it.

Denoise → Skin Beauty → Skin Upscale → (Relight) → Color Match → Sharpen Pro → Adjust
→ Vibrance → Lift/Gamma/Gain → White Balance → RGB → Lens FX → Lens Profile
→ Chromatic Aberration → Bloom → Vignette → Film Stock B&W → Film Stock Colour → Grain
→ Desaturate → LUT → Crop → Text Overlay → Watermark

The reasons worth knowing, because they are what breaks if you reorder it:

  • Denoise first — kill decode and tiled-upscale grit before any later CNN amplifies it.
  • Skin before sharpen. “Smooth / cool / lift before you add pores”, and sharpen only “after skin and light so it does not pre-sharpen what SkinContrast will hit again”. This is the one that was wrong here, and both nodes ship live, so every render before V2.5.0 smoothed away the sharpening on skin.
  • Colour Match after the light — so the match sees the new key, not the old one.
  • RGB last among the colour nodes — “global grade last, so you are not grading twice”.
  • Bloom after lighting — “or you glow a flat plate”.
  • Grain after the upscale — “or the CNN treats grain as pores”.
  • LUT last — “so it sits on the final look”.
  • Crop after everything — “so you do not lose edge treatments” like the vignette and the bloom.

Almost all of it ships bypassed. The toggles are on the LC Bypasser hub, which carries one labelled switch per node, and 🎨 Skin Upscale was added to that hub with the node so it cannot be switched off with no way back on.

The tiled upscale’s numbers are deliberately unchanged. denoise 0.15, mask_blur 0, tile_padding 32, seam fix None. Three other workflows in the intake pool use 96 to 304 padding and warn that thin padding invents detail (“a tile containing a sleeve doesn’t know there is an arm inside it”), but v19.2.1 ships the same numbers this package does, so moving them is tuning to be measured against a fixed seed, not a defect to fix blind.


2. The quality tiers

Precision is the same everywhere — bf16. A tier changes steps, CFG and the size.

Tier Model Steps CFG Budget With no photo Saved Extras
⚡ Iterate krea2_turbo_bf16 8 1.0 1.05 MP 1024² 1536² —
◆ Quality krea2_turbo_bf16 12 1.0 1.98 MP 1408² ~1992² detailers
★ Master krea2_turbo_bf16 ¹ 40 3.5 1.98 MP 1408² 1992² detailers (SeedVR2 ships bypassed) ¹

A tier buys a pixel BUDGET, and your photo decides its shape (V2.1.0). Upload a 2:1 photo at ⚡ Iterate and you get 1456 × 720 — the same 1.05 MP, in your photograph’s proportions. Before this, every render came back square whatever you uploaded, because the same number drove both sides. Nobody chose that; it was simply never derived from the upload.

Very wide or very tall photos are clamped at 2.5:1, past which the model’s training thins out.

Two different numbers, and conflating them started this. Budget is what the model actually generates — the number the identity card’s megapixel guidance is about. Saved is the file, after the detailer tail’s upscale. Both measured on the pod, 2026-09-09.

👥 Two People and 🔄 Replace are capped at 1216² (1.48 MP) on every tier. The card asks for ~1–1.5 MP whenever two identities share a frame, above which “the two identities blend” — so picking ★ Master for a two-face shot must not silently hand you the setting its own model card warns about. That is why the size comes from the router, which knows the mode, and not from a switch on the quality index, which cannot.

The size column was a lie until V2.0.0. The builder’s TIERS table had carried a megapixel column (1.0, 2.0, 2.0) since V1.4.0 that was unpacked by the tier pass and then never used: a tier changed steps and CFG and nothing else, every tier sampled at 1056² = 1.12 MP and saved 1584², and this table asserted the megapixels as if they were behaviour.

It is wired now. The router computes both sides and drives custom_width, custom_height and max_resolution on LCAspectRatioPipeOut [3089] — the node whose empty latent the sampler works at, because VAEEncode [2085] ships bypassed and the “Latent Switch” [1800] therefore always takes 3089’s slot 5. aspect_ratio is custom: a preset and a budget are two controls for one decision, and they disagree the moment either is touched.

Three things worth knowing, all learned the hard way:

  • max_resolution is a clamp on the LONG SIDE, not a budget. It can only make a size smaller. Point it at the tier’s side and a 2:1 render comes back at 1024 × 512 — correctly shaped and silently half the tier you paid for. It takes render_max now, so it can never bind.
  • The first wiring made two modes worse. Driving the size from an LCIndexSwitch on the quality index put ★ Master’s sampler at 1.98 MP — inside the card’s ≤2 MP ceiling, but above its ~1–1.5 MP two-person band, which is the band 👥 Two People and 🔄 Replace live in. A switch on quality cannot know the mode. The router can, so it owns the number and caps it.
  • The second wiring made every edit worse, and only the pod could see it. Telling the aspect node to “read the image” looked right and shipped a 1544 × 768 upload back as 768 × 768 — square and smaller — because [3089].image was fed by LoadImage [2105], the legacy ProGrade loader holding the placeholder, not the App Mode photo [3400]. V2.1.0 computes the answer in Python from photo 1’s own tensor instead, so the arithmetic is proved on a laptop and no wire has to be trusted to reach the right node. (It reaches the right node now as well.)

¹ What the tiers switch today (V1.4.0; the numbers proven on the pod on 2026-09-06 under V1.2.7): the tier carries steps and CFG into the pipe; the diffusion model stays krea2_turbo_bf16 for every tier, the size is the panel’s aspect-ratio setting, and the SeedVR2 nodes ship bypassed. From V1.4.0 the tier is a number — LCIndexSwitch picks row N’s steps and row N’s CFG — where it used to be an rgthree radio over three frames; the frames survive as a home for those nodes but no panel toggles them any more. Which model loads is a separate decision — the router’s: ⚡ Turbo, Raw for a removal or when you ask for the undistilled ceiling, GGUF Q8 or INT8 only when your words name them — and the 🧩 MODEL picker on the canvas overrides it. All four loaders ship live behind lazy switches, so only the chosen one loads. The intended pairings are ⚡ and ◆ on Turbo and ★ on Raw; wiring the tier to drive the model needs the pod’s answer first (§9, item 5). The SeedVR2 upscale for ★ is still open (§9, item 4); a docs test keeps this table honest until then. The acceptance’s ★ render ran turbo at 40 steps, CFG 3.5, in 61 s.

Sampler is euler / simple throughout — the res_2s family is reported to drift faces.

Master is the only tier that reliably removes things. Distilled Turbo at CFG 1 tends to re-render a subject you asked it to delete.

Why not a fast quantised tier? It was planned and then dropped on evidence. A LoRA applied at runtime over a quantised base forfeits the quantised fast path, and your subject LoRA is always on — so the tiering bought almost nothing. Separately, NVFP4 turned out to be only ~15 % faster than fp8 (not the ~2× folklore), and slower than fp8 without a cu130 build.

Generate at 2 MP or under. Above the trained range the source bleeds into the output and subjects duplicate.


3. Training your LoRA

Training moved to its own package on 2026-09-06 and this section with it: see ../character-creator-krea-2/character-creator-krea-2-handbook.md. The one-line version: make a 📇 Character sheet here (or zip your own photographs, with optional .txt captions and an optional holdout/ folder), open the Character Creator’s form, type the same name, press Run. The LoRA lands in models/loras/Krea2/Subject/<name>_<steps>steps.safetensors — refresh this page once and pick it in 🧬 Your trained likeness, then write the name in your prompt. The trigger rule is shared by both packages: lowercase letters, digits and underscore, 3–24 characters, starting with a letter.

The old ai-toolkit image-creator-krea-2-trainer.sh is gone (V1.6.0): it duplicated the sibling’s and had drifted from it.


4. Where the models live

<category>/<Family>/<Purpose>/<upstream filename> — so a path tells you what it is.

models/
├── diffusion_models/Krea2/Turbo/ krea2_turbo_bf16 26.3 GB (⚡ the shipped default)
│ krea2_turbo-Q8_0.gguf 13.7 GB (GGUF, low VRAM; vantagewithai)
│ krea2_turbo_int8_convrot 13.5 GB (INT8, low VRAM; core loads it natively)
│ /Raw/ krea2_raw_bf16 26.3 GB (the Raw path + the training base)
├── text_encoders/Krea2/Qwen3VL/ qwen3vl_4b_bf16 8.9 GB
├── vae/Krea2/Real/ krea2RealVae_v10 0.51 GB (default)
│ /Official/ qwen_image_vae 0.25 GB (toggle)
├── loras/Krea2/Edit/ krea2_identity_edit_v1_2 1.8 GB (Restage only)
├── checkpoints/SAM3/ sam3.1_multiplex_fp16 1.75 GB (🔄 Replace finds who leaves; Comfy-Org/sam3.1)
│ /Style/ style_reference + 9 styles 4.6 GB
│ /Subject/ ← your trained LoRA lands here
├── upscale_models/ESRGAN/ 1x-ITF-SkinDiffDetail-Lite-v1 20 MB (the hi-res fix pipe; from uwg/upscaler)
├── LLM/GGUF/ Qwen3-4B-abliterated-bf16.gguf 8.05 GB (the prompt enhancer; Mungert/Qwen3-4B-abliterated-GGUF)
├── LLM/Florence-2-base-PromptGen-v2.0/ snapshot 1.09 GB (the caption helper — live in every mode)
├── RMBG/RMBG-2.0/ snapshot 0.88 GB (rmbg3 — live in every mode)
├── sams/ sam3.pt 3.45 GB (◆ SmartInpainters; 1038lab/sam3, Meta's file)
│ sam_hq_vit_h.pth 2.57 GB (★ masks; HQ-SAM, lkeab/hq-sam)
├── grounding-dino/ groundingdino_swint_ogc.pth 0.69 GB (★ masks; ShilongLiu/GroundingDINO)
├── depthanything/ depth_anything_v2_vitl_fp32 1.34 GB (the ControlNet branch; Kijai)
└── SEEDVR2/ seedvr2_ema_7b_fp16 + ema_vae_fp16 17.0 GB (★ upscale; numz/SeedVR2_comfyUI)

≈ 125.5 GB, 29 rows, all from the Hub. V1.1.0 declared the GGUF LOCAL (“not on the Hub under that name”) — it is, public and byte-exact, and a LOCAL row not found on the pod fails the run even under --check; V1.2.0 downloads it. V1.2.0 also declares every model the ◆/★ tiers load, which the packs used to fetch at queue time (or, for SAM 3, not at all): the rows are filed exactly where each pack os.listdirs (the base’s flat categories sams, depthanything, SEEDVR2, RMBG, grounding-dino, LLM/<name>/), and the graph names them the way the packs expect — sam3.pt (the only checkpoint the SmartInpainter pack is tested with; the archive said sam3.safetensors, a name Meta never shipped) and HQ-SAM ViT-H instead of Meta’s ViT-H (a better SAM, Apache-2.0, on the Hub; Meta’s is on a CDN the base’s download list does not allow). facebook/sam3 is gated and needs Meta’s approval: the row points at 1038lab/sam3, the mirror ComfyUI-RMBG downloads from by default, with the same byte count; it can move to Meta’s repo once approved with nothing to re-download. Two things still come from the Hugging Face cache on the volume at first ★ use, because the base cannot file them (models/bert-base-uncased needs a Family the pack cannot read; ViTMatte is a transformers snapshot): GroundingDINO’s bert-base-uncased text encoder and hustvl/vitmatte-small-composition-1k, ≈ 0.54 GB together.

Downloads and privacy. Every fetch is a GET: the Hub (the token as authorisation, hf download with hf_xet), GitHub (pack clones) and PyPI (wheels and a few pure-Python sdists). No prompt, image or file leaves the pod; nothing here calls a third-party inference API.

On krea2RealVae: it has no model card, but its provenance is traceable — it is spacepxl’s Wan2.1-VAE-upscale2x decoder, the 12-channel sub-pixel head averaged back to 3 and merged in, encoder unchanged. spacepxl’s card claims it “improves skin details and hair very significantly”; it is weaker on text and lineart. The repackager is unidentified. It is decode-side only, so the swap is low-risk and reversible — both VAEs ship and it is one toggle.


5. What the build is, and what it caught

The workflow is not hand-authored. _build/build_workflow.py loads the ProGrade original from _archive/ and transforms it in eight passes, so node ids, links and groups survive and the build is re-runnable and reviewable as a diff.

python3 _build/build_workflow.py --report # build and report, write nothing
python3 _build/build_workflow.py # write the JSON

The V1.0.0 suite was green over a broken workflow

Worth recording plainly, because it is the most useful thing in this document. The first suite had 22 passing tests. The workflow it passed had five serious defects, because every test asked whether a node had the right properties and none asked whether the wiring did anything:

# defect why 22 tests missed it
1 Krea2EditModelPatch output connected to nothing tests checked it existed, not that it reached a sampler
2 both Krea2EditGroundedEncode nodes were dead ends same
3 all four tier Any Switch nodes were dead ends — the quality radio changed nothing a test asserted the panel said toggleRestriction: always one
4 the MODE radio matched 🖼️, and ProGrade already ships a group called 🖼️ Image Preprocessing — picking a mode would have bypassed 80 unrelated nodes nothing checked what a marker actually matches
5 the three mode rectangles were 20px-offset near-duplicates nothing checked group geometry

The rewrite adds dataflow, radio, e2e and layout tiers specifically to catch that class. The e2e tier walks all 5 modes × 3 tiers — from V1.4.0 by resolving the switches the way ComfyUI does, not by simulating a bypass pattern. That distinction matters: the old model asked “can some output still be reached”, which is trivially true because Create is the base graph, and it could not have caught mode ③ pointing at the wrong producer for the life of the package. The new one names the node that feeds the sampler and asserts which one it should be.

Three further defects surfaced during that rewrite:

  • 16 node packs were missing from the installer — ControlNet aux, SAM3, LayerStyle, SeedVR2, Florence2, Derfuu, essentials and more. The workflow would have opened as a field of red boxes. Caught by test_contract_every_node_declares_a_pack_the_installer_installs, which reads each node’s own cnr_id and checks the PACKS table covers it — offline, no ComfyUI needed.
  • The de-brand pass rewrote a combo value. "Instagram Portrait (4:5) - 1080x1350" matched “instagram” and became the credit line. A bad COMBO does not fail on load — it fails at queue time. Branding is now replaced only inside note text.
  • The subject LoRA sat 23,000 px from the control panel, because it was placed next to the UNETLoader in ProGrade’s far-off loader area. No graph test can see that; render.py --review found it in one line. The control surface is now anchored above ProGrade’s own settings block and the whole reading path spans 730 px.

Look at the canvas

python3 _build/render.py --review # draw it, number the reading order, assert the budgets
python3 _build/render.py --focus # just the control surface

It draws the graph to scale from the JSON, numbers the four frames a person touches in the order they touch them, and exits non-zero if a budget fails. Two defects in the sibling H3 package were invisible to its whole suite and obvious in one glance; this is that lesson, applied.

The original four defects

All one root cause worth knowing about:

ComfyUI’s newer frontend writes a widgets_values_named dict alongside the positional widgets_values list. Update one and not the other and the loader reads the stale half.

# what survived because
1 Krea 2\Your models\Krealism_V1.0.safetensors on the UNETLoader set_widget wrote only the list
2 seven personal LoRAs, including other people’s character LoRAs the rgthree Power Lora Loader stores rows as nested dicts, so a top-level pass missed them
3 two Civitai/Instagram-branded notes their text lives in the named dict
4 the abliterated Heretic text-encoder filename changing a node’s type left the old schema’s named dict behind

Two of the tests were also wrong and were fixed rather than deleted: one asserted on the whole 687 KB JSON and matched a node title containing a backslash (taking 32 seconds to render the failure); another would have stripped properties.aux_id, which is how ComfyUI Manager resolves a pack — that is correct attribution and removing it breaks node resolution.

The suite

bash "image-creator-krea-2-script.sh" test [tier] # on the pod (from its folder under /workspace/packages)
bash "image-creator-krea-2-script.sh" test # here: the repo's base, the shared testbed for the comfyui tier
tier tests what it proves
static 38 the JSON is well-formed, links resolve both ways, widgets_values_named agrees with the list, no Windows paths, no combo was rewritten, no branding survives in prose, no node needs CUDA merely to register — and, whole-pattern: every group title is unique, no GetNode is orphaned, every Set/Get channel pairs cleanly, and every seed widget shadowed by a live link holds the sentinel -1
dataflow 13 every node the build adds actually reaches a sampler. Set/Get channels are resolved first, or the reachability is meaningless
radio 10 the panels’ matchTitle selects only their own groups, the rectangles do not overlap, exactly one slot per family ships selected, and no group ships in mixed states
e2e 31 all 7 modes × 3 tiers resolved through the real switches; each mode makes something live; the two Restage paths stay structurally identical
flow 99 every way a person can use the front door: one row per flow routed with no GPU, the two 🔄 Replace options told apart by their words, who and which side read out of the sentence and the instruction built from them, and every flow’s prompt reduced to a filename a person could find
layout 11 nothing straddles a group edge, the workflow opens on its own control panel on three real screens, and the panel holds the prompt and seed it claims to. The three collision tests moved to the kit in V1.4.0, which checks every node with the title bar and the image-preview reserve rather than the 36 the build added
contract 88 loaders ⊆ manifest, installer table = models.py, every pack pinned to a commit SHA, every node’s declared pack is one the installer installs, every row a public Hub download, the ◆/★ tier models declared where their packs look, the live prompt enhancer fully provided, ① Create’s positive conditioning is the prompt alone (and it wins the last pipe edit), the style references ship off
schema 4 ComfyUI’s own GET /object_info, cached (decision 16). Every node type the graph uses is registered or explained; every widget value lands in its own slot, checked positionally the way the frontend restores them; and the cache came from the pinned frontend. This is what catches a pack that renamed or reordered its inputs — the defect that made node 1524’s third widget a boolean holding sdpa.
app 17 the App Mode form — the declaration, and what the real frontend actually renders. linearMode is what OPENS the form (linearData alone is invisible); every entry is a WidgetId on the ROOT graph, so a host folded into a subgraph would be dropped silently on load; slot 2 is the widget’s NAME, not prose; every host node is LIVE, because App Mode draws only a live node’s widgets; every field has a label on its input slot and help text in its config. Then test_app_live_* opens the real ComfyUI and reads the panel’s own text — the only check that could see either V1.4.0 defect
install 5 the thin installer run on the base against a throwaway tree: every Krea row fails loudly by name, a clean run installs all 26 and the Subject folder, --check writes nothing, superseded names are Krea basenames, a planted foreign file survives the deletion pass, and a fake or dry run says it skipped the llama-cpp build (the toolchain itself is proven by the base’s own install tier)
docs 6 the handbook’s own numbers — node counts, the tier table, steps and CFG — match the graph
package 4 the zip matches the working tree, both scripts are valid bash, executable in the checkout and stored executable in the zip (zipfile -e carries no modes; the pod runs them with bash)
build 2 rebuilding from _archive/ reproduces the shipped JSON byte for byte
comfyui 3 node types resolve against a real ComfyUI checkout (skips, saying so, on a stub tree such as the rehearsal’s fake pod)
config 10 the serverless tooling (V2.4.0): the shipped API prompt passes api_prompt.py --check and carries exactly the five form fields; the generated Dockerfile pins every pack once, clones only the base packs the prompt’s nodes need, installs no registry pack and downloads no model; the yaml names every category models.py uses; deploy.py configure is an idempotent PATCH that never shows the key (against a fake RunPod); the driver’s request builder and PNG receipt reader, against fixtures

335 cases in this table, 377 collected. The difference is ComfyUI Base’s canvas kit, adopted in full at V1.4.0: one CANVAS = CanvasSpec(...) declaration and from canvastest import * bring in 42 generic tests across three more tiers — uiux (collisions, spacing, grid, every node in a frame, notes and labels that fit, the level-of-detail cliff, canvas size, fill, group bloat, title length, palette size, link flow), snapshot (the REAL frontend through Playwright: the saved view, the opening screen, no node smaller than the frontend draws it, no page errors, the size cache current) and config (the zip is the shipped pair, versions agree, every tier test carries its gate, the handbook’s numbers, every pack pinned). They are the same 42 every package here is judged by, which is the point.

The suite collects 234 test functions across 17 tiers, expanding to 377 collected cases with the kit’s 42 and the ten serverless checks. Those numbers are asserted against the code, so they cannot quietly drift — which they had: until V2.1.0 the checker expanded @parametrize only when its argvalues were a literal list, so three flow tests parametrized on the bare name FLOWS counted as one each. The table said 21 where pytest collected 69, and the assertion passed because the handbook had been written from the checker’s own wrong arithmetic.

Off the testbed the three comfyui checks, the snapshot tier and two build/package checks skip; on the shared testbed nothing skips. On a pod the build tier, the schema tier, the snapshot tier, the zip tests and six of the thirteen app cases skip — the pod gets the shipped pair, never _build/, so anything reading object_info.json, appmode.py or the builder has nothing to read. The seven app cases that do run there are the ones that matter most on a pod: they read the shipped workflow itself and prove linearMode, the root-graph ids, the live hosts and the input slot labels. A tier that cannot apply must skip, never fail: a red suite is a red run, and V1.4.1’s first pod install was reported broken by a build test that simply had nothing to build.

Three layers of validation, all first-party

The suite is the first layer and it checks the house’s own rules. The other two ask ComfyUI.

_build/object_info.py caches ComfyUI’s own GET /object_info for every type this graph uses (131 of 141; the other 9 are frontend-only and the QwenVL enhancer needs a CUDA llama-cpp build a Mac cannot have). The schema tier then checks every widget value against that schema positionally, the way the frontend restores widgets_values — which is what catches a pack that renamed or reordered its inputs.

_build/prompt_gate.py POSTs each state to /prompt. That is execution.validate_prompt, the same code path that rejects at queue time, so it cannot drift from ComfyUI’s semantics; there is no validate-only route, so an accepted prompt is interrupted and cleared at once. The prompts come from the REAL frontend through ComfyUI Base’s probe.py, because graphToPrompt() is what resolves bypass, mute and Set/Get — rolling our own would test our model of ComfyUI rather than ComfyUI.

The states are 16, doubled to 32. Four modes × three tiers as widget values (V1.4.0: probe.py sets 3439.value and 3440.value before graphToPrompt), plus one per model path, which is still an rgthree radio and so still a mode map. Each is emitted twice because validate_prompt returns on the first node type the server does not know (execution.py:1144) — a prompt containing the QwenVL enhancer is rejected before a single link, widget or cycle is looked at, so the gate would pass every state while checking nothing. The second copy has the host-gated nodes bypassed, and the frontend does that resolution, not us.

What the Mac cannot prove: its model folders are empty, and ComfyUI raises no value_not_in_list against an empty option list — verified directly. So filenames are covered offline by test_contract_loaders_come_from_the_manifest, and on the pod by prompt_gate.py --strict, which allows nothing.

Bypass semantics matter here and the tests model them properly: a bypassed node forwards each output to its first same-typed input, so it stays traversable — only a muted node emits nothing. Modelling bypass as a cut made nine e2e cases fail against a perfectly good graph.


6. Installing

Step one on a fresh pod is ComfyUI Base: it brings up uv, the newest Python and torch, ComfyUI at its newest tag, the six node packs every package shares (rgthree-comfy, ComfyUI-KJNodes, ComfyUI-VideoHelperSuite, cg-use-everywhere, ComfyUI-Manager, Civicomfy), the launch-arg hygiene, the boot and the restart policy. This script is step two and installs only what is Krea’s: 26 packs pinned by commit, 28 files into the library, the Subject folder, the workflow’s loader paths, and one thing no requirements file declares — see below. It never builds a venv: the base does, and it did not exist before V1.1.0 (the old installer could only find one).

llama-cpp-python. ComfyUI-QwenVL-Mod, the prompt enhancer’s pack, does from llama_cpp import Llama at module top in both of its GGUF modules, declares it in no requirements file, and its __init__ swallows the ImportError — so node 3390, live in every mode, silently never registered (the testbed log: Error loading module AILab_QwenVL_GGUF_PromptEnhancer: No module named 'llama_cpp'), and on a pod the base’s smoke stage fails the run on the missing type. No cp314/cu130 wheel exists anywhere (PyPI, abetlen’s index), so pkg_post_venv builds the pinned sdist (LLAMA_CPP_VER) with GGML_CUDA=on for the pod’s own compute capability — --no-cache-dir (pip keys a built wheel by its sdist, so a cached CPU build would be reused) and --no-binary llama-cpp-python (scikit-build-core fetches cmake itself under build isolation) — and proves llama_supports_gpu_offload() with torch imported first, the way ComfyUI loads it: the build links torch’s CUDA 13 runtime, which a bare import cannot find on a container whose toolkit is 12.8 (measured 2026-09-08 on a migrated pod), but which is already in the process by the time any custom node imports. pkg_import_check proves it again after the packs’ pip stage, reads the quick-test log for a dropped QwenVL module, and confirms a sam3 package is vendored: the three SmartInpainters import sam3.model_builder, which ComfyUI-Easy-Sam3 supplies (it adds no node to this graph).

SageAttention. Node 1293 (Sage Attention, KJNodes) is live in every mode and imports sageattention when it runs; no requirements file installs it, and no smoke or combo check can see a run-time import — the first render on the pod died there at queue time. The hook calls the base’s base_build_sageattention: SageAttention 2 from source for the pod’s GPU (TORCH_CUDA_ARCH_LIST=12.0 on this one; no PyPI wheel carries sm_120 kernels and 1.0.6 crashes there), 10–20 minutes with a heartbeat, built from SageAttention’s newest commit and cached by it (base 3.0.0 retired SAGE_WHEEL and a commit SAGE_REF, which held it below its newest; both are ignored with a note); pkg_import_check proves the import afterwards.

ComfyUI_LC123_nodes supplies 43 of the graph’s 198 live compute nodes. It is in the Comfy Registry — publisher lonecatone23, MIT, 6,637 downloads, latest 1.2.9 of 2026-08-09 (checked 2026-09-06). An earlier version of this handbook said it was absent; that came from a registry search whose search parameter was being ignored, so the query returned the whole registry and the name was never actually looked for. The script still installs it by URL, at its repository’s HEAD, because the Registry publishes releases and HEAD is newer than any of them. It has no Python dependencies.

Every pack is installed from its git URL, never from the Manager, and that is not fussiness: Manager advertises comfyui-krea2edit 1.2.3 while the repo is at 1.2.5 (#21), and a 1.2.5 workflow meeting 1.2.3’s nodes throws patch() got an unexpected keyword argument 'vae' (#5, open). Since base 3.0.0 every run takes every pack to its remote HEAD, so --latest is a plain run; each row’s sha is the last-tested record, printed on every run and written back by podctl after a green install.


Krea 2’s weights and any LoRA you train from them fall under the Krea 2 Community License: a derivative model, commercial use permitted below the revenue threshold (§2.3, currently <$1M/yr), reasonable content moderation required (§4.2), AI-disclosure obligations where applicable (§4.3).

comfyui-krea2edit is Apache-2.0. ComfyUI_LC123_nodes is MIT. The graph is transformed from the ProGrade workflow, whose author’s node pack and design this package depends on and credits.

The tier models V1.2.0 declares, as published (this package’s use is non-commercial; nothing is recommended for swap):

model licence
SAM 3 (sam3.pt, Meta) SAM licence — obtained through the 1038lab/sam3 mirror while facebook/sam3 awaits approval
HQ-SAM ViT-H (lkeab/hq-sam) Apache-2.0
GroundingDINO SwinT (ShilongLiu/GroundingDINO) Apache-2.0
SeedVR2 7B + VAE (numz/SeedVR2_comfyUI) Apache-2.0
DepthAnything V2 Large (Kijai/DepthAnythingV2-safetensors) CC-BY-NC-4.0 — non-commercial (V2 Base is Apache if that ever matters)
RMBG-2.0 (1038lab/RMBG-2.0, BRIA) BRIA RMBG-2.0 licence — non-commercial without a paid licence; the pack labels it Apache, which it is not (BiRefNet is MIT if that ever matters)
Florence-2 base PromptGen v2.0 (MiaoshouAI) as published on its model card
Qwen3-4B abliterated GGUF (Mungert) Apache-2.0 (Qwen3)

The identity-edit weights are explicitly SFW-scope, and their author asks that the model not be used for non-consensual imagery of real people. A subject LoRA of a real person carries the same obligation — train only on people who have consented, and that includes you deciding where these images go.


8. Record

version date what
V2.5.4 2026-10-16 Every Hub row is pinned to a commit. ComfyUI Base 3.12.0 honours the revision in a row and checks each Hub file against the sha256 the Hub states for that commit, once, at download, so a repo that moves or renames a file upstream can no longer 404 a row or change what installs (the H3 latent upscaler broke that way on 2026-09-17). All 28 rows (27 files and the RMBG-2.0 snapshot, hf://1038lab/RMBG-2.0@1cd4787) name the commit base.sh gen-models <dir> --pin printed on 2026-10-16, from models.py’s new REV table, one commit per repository and the same as the Character Creator’s for the rows they share; each file’s size and Hub sha256 are unchanged. Files already on a volume are kept as they are: no download and no re-hash. BASE_MIN is 3.12.0, because an older base drops the revision and fetches main. No graph change but the version stamp.
V2.5.5 2026-10-16 BASE_MIN 3.17.1, the image-feed setting where the server reads it, coach_tour, and three tests that skip instead of branching. pysssss.ImageFeed.Location = hidden was written to $COMFY/user/default/comfy.settings.json; on a shared volume the server reads another user folder and never saw it (error log row 105). The script now takes the base’s _base_user_dir and writes through the base’s py/comfy_settings.py (atomic, every other key kept); krea2_install.py hide-feed is gone. The base marks the App Mode tour seen on install (3.17.1); _build/appview.py and _build/appe2e.py call its coach_tour instead of their own Skip/Close clicks and stop when a tour opens on a seeded server. test_versions_agree, test_package_scripts_are_executable and test_app_the_dropdowns_are_the_packs_vocabulary checked their _build/ part behind an if (one returned early), so a run from the extracted zip passed without it; each now SKIPS there, and CI’s VERIFY_SUITE=zip runs it again from the repo (error log row 106). No graph change but the version stamp.
V2.5.6 2026-10-17 GPU_MIN_GB for the site’s catalog. GPU_MIN_GB="29", estimated: the smallest card the default flow runs on, for the site’s catalog. It is the heaviest stage’s weights at the precision the loader picks, plus ComfyUI’s own memory_required activations and its 1.2 GiB inference reserve, with ComfyUI swapping models between stages; the script’s comment holds the arithmetic. Comfortable: 38 GB. A measured peak replaces the estimate at the next free GPU smoke. No graph change but the version stamp.
V2.5.3 2026-10-16 The pack names no other project. Two releases made in the monorepo on 2026-10-01 (its V2.5.2 and V2.5.3) that never reached it before the split, ported as one: comments, docstrings, test messages and the HANDOFF describe the router’s optional product input as a generic extension seam (unwired means SOULCRAFT, the SFW product) and name no sibling brand; policy.py’s verdict() docstring says only that nodes.py chooses which policy to pass; the suite’s absence test spells its first two words in smaller pieces so a brand-stem grep stays clean; _build/spike_s4_rewrite.py, a spike that measured adult-prompt rewriting, is deleted. No behaviour change: the workflow differs only in kr_version; the zip is rebuilt.
V2.5.2 2026-10-16 Its own repository. The package moved from the SoulCraft-Workflows monorepo to SoulCraft-Org/image-creator-krea-2, cloned beside its siblings in the SoulCraft-Org/workspace checkout, with ComfyUI Base read from $COMFY_BASE. The suite’s base-packs check (BASE_PACKS_SH) read <repo>/base/comfyui-base/lib/40-packs.sh, which a workspace does not have, and now reads the base the runner put on PYTHONPATH, in either layout. The script’s dead monorepo base lookup is gone; the _build/ tools find the base through $COMFY_BASE. No change to the workflow beyond its version.
V2.5.1 2026-09-25 Wording for ComfyUI Base 3.0.0, which is always the newest. §6 said SAGE_WHEEL=<wheel> installs a wheel through base_build_sageattention; 3.0.0 ignores it with a note, and the build follows SageAttention’s newest commit. The script header and §6 said every pack is pinned and --latest moves the pins to HEAD; every run now installs HEAD, a row’s sha is the last-tested record, and --latest is a plain run. The Quick start’s podctl install comment drops --latest. No graph or row change: the version moves because the script and this handbook ship.
V2.2.3 2026-09-10 One ask, and a receipt that says what shows. V2.2.2’s thinking-off first ask for the wall’s description returned the chat template on 8 of 8 on the pod; only its thinking-on retry ever answered. The router now asks once, thinking on, and logs the answer. The cleared Replace’s receipt said the repainted box could look “a shade lighter or smoother”; through the form it looked coarser (cycle 9), so it now says “a slightly different texture or tone”. The spike tools the HANDOFF names (render_probe.py, spike_mr.py, measure_counts.py, montage_tags.py) moved from a session scratchpad into _build/.
V2.2.2 2026-09-10 The wall’s description is asked the way the model answers. V2.2.1 on the pod asked with photo 1 alone and thinking off; the model returned the chat template itself (“user”) on 3 of 3, the cleaner took it for a description, and the fill painted a PERSON into the box. The question now attaches every photo and names the first, as the measured vision question does; an echo, a fragment or the question itself is not an answer; the model is asked once more with thinking on (the rewriter’s way); every raw answer is logged.
V2.2.1 2026-09-10 The regenerated box is told what the wall IS. V2.2.0’s first pass through the form on rq8w3sycj7wug6 placed photo 2’s person with no outline on every cleared Replace, and left an obvious re-textured rectangle behind her every time: told “the same colour, texture and light”, the base model invented cracked plaster, pebbles and a salmon-pink panel (S1f, 3 of 3 fixed seeds), while a described wall came back calm (3 of 3). The router now asks its resident vision model one fact, what is behind the people in photo 1, and the fill paints that: a new router output fill_words (slot 21), with a plain fallback when the model cannot answer. Also proven through the form in that pass: the red dress (extra words kept), the beard gone from a head swap, and a tidied input/ healing itself.
V2.2.0 2026-09-10 Every flow as a person expects — measured on the migrated pod. A cleared 🔄 Replace no longer leaves a faint outline of whoever left: the box’s background is regenerated first by the plain base model (no reference to copy) and the insert happens on that — no outline on 6 of 6 seeds that had ghosted (HANDOFF §7, S1–S1e). 🔁 Head Swap takes the beard with the face: the preset names the facial hair (the old one left it on 4 of 4 renders, and sage on or off made no difference). What you say about the newcomer is kept (“…wearing a red dress”). Tidying ComfyUI’s input folder no longer breaks every flow: the pack writes its photo placeholder back at start-up and before every Run.
V2.1.0 2026-09-10 Clean first, then the four things that did not work. Deleted: two shipped plan documents, _audit/, 629 tracked pod logs, a 249-line dead builder block, the model namer (it never named anything — it returned its own instructions), four router/picker outputs that went nowhere, the shadow constants. The shape follows your photo: the router computes both sides at the tier’s area, so a 2:1 photo at ⚡ comes back 1456×720, not 1024². A third-party LoRA keeps its trigger: read out of the file’s own metadata and matched against your words. 🔁 Head Swap hears plain words. GGUF Q8 and INT8 reach the sampler: they hung off an LCAnySwitch whose output went nowhere, so picking either handed the Sage patch a None model; the router now picks all four with lazy switches, the «PATH» radio that could only bypass its own choice is deleted, and gguf / int8 in your words select them. 🔄 Replace is rebuilt on what the pod measured: ③ confined to one box that SAM 3.1 finds (one models.py row, +1.75 GB), the rest of your photo pinned by a latent noise mask and restored by the blend — and every Replace-only knob of the old design (forced Raw, ref_boost 4, the 0.9 scene dial) deleted with it. Anyone else SAM 3.1 finds is kept out of the box, a one-photo removal runs on the same machinery, and the one limit a coherent photo showed — a faint outline of the person who left, on 3 of 6 seeds — is in the receipt with the two ways round it. A cleared Replace whose sentence names no side is told the side of the frame SAM 3.1 found them in — told only “the empty space”, the form placed nobody. Found on the way: the pod’s Sage Attention drops the attention mask ref_boost and ref_boost_a are built from, so both dials were inert on the pod all along — 🔁 Head Swap’s 4.0 included (HANDOFF §7). 🧬 uploads from the form: a button in the row, because a file dropped on it never reached the node on frontend 1.51.10.
V2.0.0 2026-09-09 The front door. The form is five fields — prompt, two photos, a character and a quality tier — and a router decides mode, model path, style, ref_boost and the prompt itself from what you typed and what you uploaded, showing its reasoning under the picture and in the PNG’s extra_params — the FILENAME is three words about what you asked for, under a folder named for your character, because a filename is read by a person scanning a folder. Three new modes: 📐 Depth, whose branch was in the graph but dead — every node bypassed, the loader’s filename empty, terminating in a SetNode nothing read — 👥 Two People, and 🔄 Replace, which swaps one person out of a crowd for the one in photo 2, in place or cleared out first, told apart by the words alone. Measured on the pod before it was built: a Krea 2 Turbo render is 3.1 s, so routing had to be nearly free — it is, because the photo count and two regexes settle every case except the two-photo split, which costs one 0.7 s yes/no question about photo 1. Asked to pick a mode from a list the same model scored 5/8; asked what it could see, 5/5. The prompt rewriter is Krea’s own docs/expansion.txt, which their prompting guide tells you to use as a system prompt, plus three amendments it cannot cover (never refuse, keep the trigger word, a photo is ground truth). Two things left: the 📇 Character sheet moved to Krea 2 Character Creator, where the trainer that eats its output lives; and the Qwen prompt enhancer went entirely, taking an 8 GB GGUF, a third-party pack and a five-minute llama-cpp CUDA build with it — it was the only node needing CUDA to register, so every state can now be validated on a Mac. 🧬 lists characters only (it was every LoRA on the machine, twelve of them not characters) and gained an upload button.
V1.6.4 2026-09-08 Two changes from the cold-install session, both measured on a pod this package had never seen. (1) ComfyUI-QwenVL-Mod moves to 55aa269, the commit their green --latest install validated; the pin was recorded as a measurement by the base’s 2.0.43 artefact and applied deliberately rather than written into this file behind anyone’s back. (2) _krea_llama_build refuses to start when the CUDA toolkit cannot link cuBLAS: their pod’s image ships nvcc and cudart only, so llama.cpp’s ggml-cuda failed at CMake’s generate step — after a 131 GB download. The guard is declare -f-conditional, so it is inert on a base older than 2.0.50 rather than an error. Both diagnosed, measured and written by that session; verified here from the repo and from the built zip.
V1.6.3 2026-09-08 BASE_MIN 2.0.35 → 2.0.40, measured rather than assumed. This package’s install on pod 5lgll2pfr93u3j went green through the import check for all 34 packs and then failed outright: restart FAILED, smoke and combos skipped, 41 minutes of pod time lost. The cause was the base’s flat 180 s restart window against a pod whose custom nodes import for over three minutes; the server was healthy a minute later. Base 2.0.40 waits while the process lives and gives up the moment it dies, so that is the base this package now requires.
V1.6.2 2026-09-08 The sheet covers what the auditor measures. The Character Photo Auditor measured a 📇 sheet’s 28 renders on the pod (HANDOFF §2): profile L 0 · ¾ L 7 · frontal 17 · ¾ R 3 · profile R 1, every frame pitch-level, none full-body — so every fresh sheet audited NOT READY on three blocking gaps, and “profile view” rendered as three-quarter at best. The scene list is re-worded for what the identity edit reads literally (“seen exactly from the left side, only the left ear and the near eye visible, the far eye hidden”) and re-balanced: 6 profile lines, 5 full-body (“the whole figure from head to shoes visible”, feet inside the frame), 4 that look up or down; the holdout list gains a profile and a full-body frame. Still 24 + 4, all SFW, all distinct. Paul’s call (root CLAUDE.md §4.8): the scene list changes, the auditor’s verdict stays honest. Record note: this version’s commit (7d688ef) contains 53 files, of which 42 are the MiniMax H3 Video Creator’s V8.0.0 and 2 are ComfyUI Base — that session had them staged when this commit was made, and git commit takes the whole index no matter which paths were just added. Nothing is lost or wrong, and shared history is not being rewritten; the fix is git commit -- <paths>, which commits only those paths whatever else is staged.
V1.6.1 2026-09-08 The pod pass’s list, fixed as one batch. (1) Krea2SheetSave.character is lazy: eager, it pulled Krea2SheetScenes into every mode, whose name check rejected the empty field — ① Create raised before it sampled (measured on pod y9iojohs7rs3b0). Now nothing in the sheet branch executes outside 📇 mode; the pack is 1.1.1 and a sheet test reads the lazy flags by ast. (2) The installer checks the photo placeholder every run — it was checked only on the branch that had just written the LoRA one, so a volume that had lost UPLOAD-YOUR-PHOTO-HERE.png passed four installs and failed every ① Create at LoadImage. (3) The llama_cpp check imports torch first, the way ComfyUI loads it: on a migrated pod the container toolkit was CUDA 12.8 while the venv’s build links torch’s 13 runtime, and a bare import could not find libcudart.so.13; a build that fails to load is now removed before the rebuild (pip called the old one “already satisfied”). (4) Two pod-side tests read the wrong place in the --check phase, which runs before the pack is copied in: the node-resolution test now reads the shipped pack, the name-field test reads extra.linearData. (5) Pins moved to the commits the green install ran (ComfyUI_LC123_nodes, ComfyUI-QwenVL-Mod). (6) appe2e.py gained --likeness for the consumer step and its textarea message no longer contradicts itself. (7) ① renders without a photo caption. Read from a ① render’s own metadata: the final prompt said the subject “stands centered in a simple black square with rounded corners” — Florence2’s caption of the PLACEHOLDER photo, joined into the prompt by [3010] — and Krea 2 painted that frame around every ① image. V1.5.0 stopped the pixels leaking; the text still did. pass_caption_gate (7u, ids 3472–3473): a lazy switch on the mode ≥ 1 gate feeds the join an empty string in ① and the caption otherwise; ShowText [2102] ships bypassed so the describer is never executed in ① at all.
V1.6.0 2026-09-07 📇 Character sheet — the fifth mode. One reference photo in, a captioned photo set out, zipped straight into input/ for Krea 2 Character Creator. The engine is the one this graph already proves as ② Restage; the mode picker (ComfyUI-Krea2-App-Controls 1.1.0) reports an engine (the sheet says 1) and an is_sheet gate, because every comparator in the graph is “index ≥ n” and a fifth index would have switched Head Swap on. Nine nodes after the section note (3463–3471): Krea2SheetScenes (the 🪪 Character name field, the two scene lists), a 1024² latent, two grounded encodes, two KSamplers and two decodes (the second chain renders the four holdout scenes), Krea2SheetSave (the folder, the zip, the contact-sheet preview, the report). Map-over-list does the ×24; the branch ends in its own saver so the render tail runs once. pass_form_labels moved after the sheet pass. The stale image-creator-krea-2-trainer.sh and its ten tests are gone; §3 points at the Character Creator. New sheet tier. Adopted from lonecatone23’s Krea2 ID Edit Character Dataset Creator (the idea, not the file: no Comfyroll, no LC123 post-processing, no RMBG, no Krealism, our own SFW prompts).
V1.5.0 2026-09-07 Dropdowns, and no cartoon. App Mode draws an INT as a bare number and a COMBO as a dropdown of its options, so the two decisions made on every run read Mode 0 / Quality 0 with a help line decoding the integers. They are COMBOs on the package’s own node pack now — ComfyUI-Krea2-App-Controls, two nodes, a widget with a vocabulary — and the options are the legend. The pack has no git URL yet, so it rides in the zip (ZIP_EXTRA) and pkg_post_venv copies it into custom_nodes before base_import_check, which is what proves it registers; Phase 5 lifts it out and pins it. The ids of the two control nodes did not move. And the two photo fields ship on UPLOAD-YOUR-PHOTO-HERE.png — a plain dark square the installer writes, whose name is the instruction — instead of ComfyUI’s child’s-drawing example.png, which App Mode drew large under both fields in ① Create where photos are not used at all. Four app tests. 137 own cases.
V1.4.1 2026-09-07 The form is a form now — proven by looking at it. V1.4.0’s App Mode shipped green on every check and rendered positive · value · value · image · image, with two of its seven fields missing entirely. Neither defect was visible in the JSON, to ComfyUI’s validator, or to the canvas budgets. A label is not part of linearData at all: LinearInput is [id, widgetName, {height?, description?}] — slot 2 is the widget’s NAME, used to resolve the entry, and the config has no label field. AppModeWidgetList.vue renders `widget.label
V1.4.0 2026-09-06 The front door, and the architecture change it forced. The workflow opens as a form: extra.linearMode with a seven-field extra.linearData — prompt, mode, quality, two photos, the LoRA file and its strength — verified against the real frontend, which prunes an unresolvable entry silently and with no error. That is what made MODE and QUALITY stop being rgthree radios: App Mode can expose a node’s widgets and inputs, never group state, so a front door could not have been built on bypass panels. They are PrimitiveInts now, feeding core ComfyUI’s own ComfySwitchNode — which declares lazy: true on both branches, so the branch you did not choose is never executed and ① Create no longer loads a 1.8 GB Identity Edit LoRA to throw it away. The tier picks steps and CFG through LCIndexSwitch. The frames survive as a home for their nodes; the «MODE» and «TIER» markers are retired and a test asserts they are gone, because two controls for one decision disagree the moment either is touched. 🔁 Head Swap is the fourth mode and cost no new node: it is ③ with ref_boost at 4.0, the instruction blanked and a preset system prompt. And a defect the form exposed: nodes 3406 and 3410 carried a hardcoded night-market placeholder as their instruction, so the one prompt box drove ① only and did nothing in ② or ③ — the moodboard’s resolved prompt feeds both now. The canvas kit is adopted in full (42 generic tests, three tiers), geometry moved out of the builder into _build/layout.py, and _build/sizes.json is the frontend’s own computeSize() for all 356 nodes. 120 own cases, 162 collected.
V1.3.3 2026-09-06 ComfyUI validates the graph itself, and the house rules reach every instance. _build/object_info.py caches ComfyUI’s own GET /object_info; a schema tier checks every widget against it positionally, the way the frontend restores them — and it immediately found that node [3402] pointed at example_person.png, a file that has never existed in any ComfyUI install. validate_prompt rejects that with value_not_in_list, so mode ③ could not be QUEUED even after V1.3.0 rewired the switch: a second, independent reason no ③ render has ever happened. _build/prompt_gate.py then POSTs thirteen radio states to /prompt — the same execution.validate_prompt that rejects at queue time — reads node_errors and interrupts immediately, because there is no validate-only route. The prompts come from the REAL frontend through the base’s probe.py, since graphToPrompt() is what resolves bypass, mute and Set/Get. All thirteen are accepted, 130-134 nodes each. What that does not prove: the testbed’s model folders are empty, and ComfyUI raises no value_not_in_list against an empty option list (verified directly), so filenames are covered by test_contract_loaders_come_from_the_manifest offline and by --strict on the pod. Two panel tests scoped to 2 of 11 panels were replaced by whole-pattern ones. 145 cases across 13 tiers.
V1.3.2 2026-09-06 The frontend is pinned where a pin can actually live. Decision 13 said “pinned exactly in the Docker image” and there is no Docker image — ComfyUI Base is an any-image installer that runs inside whatever RunPod image the pod already has, and base_update_comfyui checks out the newest ComfyUI release tag on every run, so the frontend arrives as a transitive dependency of whatever that day’s ComfyUI asks for. The pin is PIP_EXTRA, which lib/40-packs.sh re-applies on every run rather than only on a venv build — that is what makes it survive ComfyUI moving underneath it. FRONTEND_PIN=1.49.6 is what ComfyUI v0.34.5 requires and the frontend every snapshot here was taken against (confirmed live: /system_stats reports exactly that pair). FRONTEND_MIN=1.41.13 is where App Mode became supported, and pkg_import_check fails the run below it.
V1.3.1 2026-09-06 Everything you can switch off, you can switch back on. Ninety compute nodes ship switched off; eighty-six could be reached from a panel and four could not. Three were a live defect: the “Style bypass” panel toggled the transfer node [2653] and rmbg3 [3332] but not the three encoders feeding them, so a user who switched style transfer back ON from the panel got Krea2TwoStyleTransfer.patch() missing style_refs — the same crash V1.2.7 fixed, this time reachable from the UI. The whole chain is on the panel now and moves as one. The fourth was [3398], the subject LoRA: the node this product exists for, with no switch, and a handbook paragraph telling you to un-bypass it by hand. It has its own «LORA» panel — a switch, not a radio, because shipping with no LoRA is a valid state. The mixed-state rule was also rescoped: measuring the thirteen frames that ship mixed showed seven are parents whose children toggle independently and six are leaves where the spread IS the setting (🎨 Post Processing ships 24 effects, 17 off), so the rule now covers frames a panel toggles as a unit and the exception list is gone. 138 cases.
V1.3.0 2026-09-06 Four real model paths, and mode ③ can finally win. The Model selector was a three-position radio over two EMPTY frames — choosing GGUF or INT8 bypassed the only loader and enabled nothing, and the test that would have caught it was scoped to the MODE and TIER families. It has four positions now, carrying the «PATH» marker: Raw, ⚡ Turbo (shipped), GGUF Q8, INT8. ProGrade’s own LoaderGGUF [2539] is reused rather than deleted; the INT8 position is a plain core UNETLoader, because ComfyUI loads int8_convrot natively through comfy-kitchen and ProGrade’s OTUNetLoaderW8A8 came from an encryption pack that stays deleted. The GGUF pack is calcuis’ gguf, not city96’s: city96’s IMG_ARCH_LIST has no krea2 and raises on it (#464, open since 2026-06-24, no maintainer reply). The panel matched the bare word ‘Model’, which would sweep any future frame containing it; it matches «PATH» now, the way MODE and TIER already did. And the mode ③ defect, which no render could have found because ③ has never been rendered: LCAnySwitch returns its first NON-NULL input, and a bypassed Krea2EditModelPatch FORWARDS its model instead of emitting null — so ② on any_01 always answered and ③’s +person patch was unreachable. Resolving the shipped graph the way the frontend does handed mode ③ the raw UNETLoader. The switch is back upstream selecting loaders, where ProGrade had it, and the two edit paths run in series behind it; all 12 combinations of 4 paths x 3 modes now resolve to the right producer. Two models.py rows (+27.2 GB, 28 rows, ≈131 GB); krea2_turbo_int8_convrot leaves SUPERSEDED, where it would have been offered for deletion. 137 cases.
V1.2.7 2026-09-06 The first load of V1.2.6 in the pod’s frontend turned the two switched-off nodes back on: an LC Bypasser derives its toggles at load from the PrimitiveBoolean nodes wired to its enable inputs and re-applies them, and “Image 3 Style Transfer” (node 2682:0) and “Remove background 3” (3329) still shipped true — the render then died in Krea2TwoStyleTransfer (its packer bypassed, style_refs None). The builder now sets those two booleans false by following the panel’s own links, and the contract test asserts the booleans, not only the toggles and the modes. 99 cases.
V1.2.6 2026-09-06 V1.2.5’s fallback never reached the sampler, and the cartoon had a second route. (1) ProGrade’s OWN pipe edit (node 3027) sits downstream of the package’s (3015) and overwrites the positive with its “Conditioning Switch” (2377), whose first input was Krea2EncodeRebalance (3117) grounded on Load Image 1 = example.png, the second the bypassed “Base Prompt Encode” (1315). The package’s “grounded ▸ prompt” switch (3412) is now that switch’s FIRST input and the Rebalance its last: whatever the mode chooses wins — the prompt alone in ① Create, the edit-grounded encodes in Restage (which the same overwrite had been stripping too); bypass node 3414 to use the Image Reference Edit encoder on purpose. (2) The “Style bypass” panel (3022) shipped with “2 Style Transfer Image 3” ON: a live Krea2TwoStyleTransfer (2653) patched the model with two style references, both the stock image (the ◆ render showed the cartoon twice), and rmbg3 (3332) cut it out at the start of every render — the two reference encoders (2649, 2650), their packer (2652), the transfer node and rmbg3 ship bypassed and the panel agrees. Each route was proven live in the pod’s frontend before the builder was touched: style off alone → the model DRAWS the character (grounding); both off → a clean photograph of the prompt in 43 s. Two contract tests read the wiring and the modes. 99 cases.
V1.2.5 2026-09-06 The first renders on a clean GPU (pod b6chi54aw7mxcq, base 2.0.30) painted ComfyUI’s stock example.png cartoon into every ① Create image. Cause: the positive “grounded ▸ prompt” switch had its two edit-mode inputs (bypassed in Create, so null) and NO third, so the pipe kept ProGrade’s Krea2EncodeRebalance conditioning — grounded on Load Image 1, which ships as example.png; the negative switch had always had its fallback (node 3016). The builder’s comment (“the pipe falls back to its prompt-string path, which is exactly Create mode”) was true for the negative only. Fix: an LCPromptToConditioning (node 3414) from the same clip and the moodboard’s positive text on the positive switch’s third input, live; bypass that one node to use the Image Reference Edit encoder on purpose. Open item 2 of §9 is answered by the pod. A contract test reads the wiring. 98 cases. Superseded by V1.2.6: this node never reached the sampler.
V1.2.4 2026-09-06 BASE_MIN 2.0.25: the base that builds SageAttention on torch 2.14 (its setup.py patched to C++20 at build time) and sizes a snapshot without the __pycache__ a pack leaves in it — the two reds of the first Krea step on the new pod (hearmeman/comfyui-qwen-template:v15), both fixed in the base. Nothing else changed.
V1.2.3 2026-09-06 The Trainer could not have run. It downloaded krea/Krea-2-Raw, a gated repo, and the token on the pod belongs to an account that never accepted the licence (403 on Raw, 200 on Turbo; the Mac’s account is unauthorised too) — so every run would have cloned ai-toolkit, installed its dependencies and died at the download. The library’s own krea2_raw_bf16.safetensors is the same weights: 26,283,332,608 bytes, the tensor names ai-toolkit’s krea2 loader reads (blocks.N.attn.wq…, 28 blocks + txtfusion), and that loader takes a bare .safetensors; the text encoder (Qwen/Qwen3-VL-4B-Instruct) and VAE (Qwen/Qwen-Image) are public. The Trainer now reads the library file, checks it before any work, and needs no token. The LoKr paragraph was wrong and is rewritten: ComfyUI #10973 and #12638 are Z-Image and ACE-Step, ComfyUI maps Krea 2 LoRA keys generically and weight_adapter/lokr.py loads them; ai-toolkit #964 measures plain LoRAs on krea2 collapsing when stacked and full-rank LoKr not — plain LoRA stays the default, LoKr is the documented fix. mu 1.15 resolved. ai-toolkit is 0.13.5 at HEAD (2026-09-05); #947 (shift timesteps only), #1026 (layer offloading only) and #925 (Mac/MPS, 0.10.19) do not touch this config. 97 cases.
V1.2.2 2026-09-06 The second pod run was green at every stage (base 2.0.20, 08:24Z: smoke ok, combos ok, suite 91 passed, STEP RC=0, pins saved for LC123 and krea2edit) and the first render then died at queue time — node 1293’s Sage patch imports sageattention when it runs. The base gained base_build_sageattention (2.0.22); the hook calls it after the llama-cpp build, the import check proves it, a static test ties the live patch to the build. The console had already shown the enhancer running its GGUF on the GPU, Florence-2 and RMBG-2.0 loading from their rows. 97 cases.
V1.2.1 2026-09-06 The first pod run (base 2.0.17, podctl install, 2026-09-06 07:34Z) was green everywhere but two stages — 33 packs importing, llama-cpp-python built with CUDA in a minute, 25 files downloaded and verified, the suite 90 passed on the pod — and red at smoke and combos. Smoke: three frontend-only types (KJNodes’ Get/Set, LC123’s group bypasser) the base’s list did not know (base 2.0.20). Combos: node 1524’s third widget was ProGrade’s attention value sdpa; the pinned Florence2 pack reads convert_to_safetensors (a boolean) there — the builder sets it to False in both representations; a static test holds the pack’s three widgets. 96 cases.
V1.2.0 2026-09-06 The first version that can run on a pod, found in pre-flight before it did. (1) The prompt enhancer never registered anywhere: its pack imports llama-cpp-python and installs it nowhere — pkg_post_venv builds it with the CUDA backend for the GPU, pkg_import_check proves it. (2) The GGUF was declared LOCAL and existed on no machine; it is on the Hub under that exact name — a row. (3) The ◆/★ tiers loaded six models nothing declared, two of them live in every mode — eight rows (26 in all, ≈ 104 GB), the graph repointed to sam3.pt and HQ-SAM ViT-H, ComfyUI-Easy-Sam3 for the sam3 package (27 packs). (4) Two tests assumed the repo checkout (the builder file, the executable bit) and the comfyui tier ran against a stub tree: red on any pod and inside the rehearsal — guarded. BASE_MIN 2.0.17 (flat RMBG and grounding-dino, packages in their own folder). 95 cases.
V1.1.0 2026-09-04 Onto ComfyUI Base. The 662-line installer became an 86-line declaration: 26 pack rows, 18 model rows generated from models.py, two hooks. It can now build the venv it used to only look for, because the base does; the three shared-pod rules are the base’s structure; the Trainer takes ComfyUI, the venv and the torch pin from the base instead of re-deriving them. The install tier shrank from 19 to 6 (the toolchain half lives in the base’s suite); _build/ keeps only generators, and suite.py/pytest.ini/models.py live once, at the root. Previous installer: git show bc1e337017562fb2e572d4c866b1edc3895a660b:"image-creator-krea-2/image-creator-krea-2-script.sh".
V1.0.0 2026-09-04 First release. ProGrade v15.01 transformed in code: model chain repointed to the library contract, dead INT8/GGUF branches removed, subject-LoRA slot added, Krea2Edit identity engine grafted in for Restage, three quality tiers and two radio panels, ProGrade’s Windows paths and third-party LoRAs scrubbed. Trainer added. 104-test suite across 12 tiers.

What is NOT tested — stated so nobody assumes otherwise

Every test in this package is structural. Not one of them renders an image. Specifically:

  • no render is ever executed — no GPU, no weights, no queue. A graph that passes every test here can still fail at queue time on a bad combo value or a node whose signature changed. ComfyUI’s own validator is asked (prompt_gate.py, every state, interrupted before it runs); the renders are proven on the pod, through the form, and recorded in HANDOFF START HERE.
  • the images are not judged here. Whether a 🔄 Replace left an outline of the person who went, or a 🔁 Head Swap really swapped, is looked at by a person on the pod — no structural test can see it.
  • the models are never downloaded or hashed. Sizes in models.py were read from the Hugging Face API; nothing re-checks them. (A size match is not an identity: HANDOFF §6.x.)
  • the CUDA-only packs are never imported on the Mac. The pure-Python ones register on the local testbed, which is where the schema tier’s cached /object_info comes from.
  • ref_boost’s cost is not measured. Without sage attention it is inferred from source and one user report; on the pod, sage drops the bias altogether, and its cost with it (HANDOFF §7).
  • likeness is not evaluated. No test can tell you whether the LoRA looks like you.

9. On the pod — the first installs, 2026-09-06

Six one-command runs in one day, on two pods sharing volume dkxkiljlii (600 GB): uv run "base/comfyui-base/_build/pod/podctl.py" install <pod> --base --pkg "image-creator-krea-2". Logs: _build/podruns/2026-09-06/pod-logs/ (every install_krea_2_image_creator_*.console and krea2_*.log).

run pod · base · Krea verdict
1 · 07:34Z cl90brwiw1pm0x (HearmemanAI MiniMax) · 2.0.17 · V1.2.0 33 packs import, llama-cpp-python built with CUDA in 1 min, 25 files / 103.8 GB downloaded and verified, suite 90 passed on the pod — red at smoke (three frontend-only node types the base’s list did not know → base 2.0.20) and combos (node 1524’s third widget → V1.2.1)
2 · 08:24Z same · 2.0.20 · V1.2.1 green at every stage: models ok 26, import ok (33 packs), restart restarted, smoke ok, combos ok, suite 91 passed, STEP RC=0; pins saved (LC123, krea2edit). The first ⚡ Iterate render then died at queue time on No module named 'sageattention' (node 1293) — the console had already shown the enhancer loading its GGUF through llama-cpp on the GPU, Florence-2 and RMBG-2.0 from their rows
3 · 09:15Z same · 2.0.22 · V1.2.2 base green; the Krea step stopped on a zip another session had just made stale (V1.2.3, later committed)
4 · 16:40Z 6qk87qn7u7fiz3 (HearmemanAI qwen v15, same volume) · 2.0.22 · V1.2.3 the base’s shipped suite red on this image: a fake pod’s discovery walked the image’s own /opt/venv → base 2.0.24
5 · 17:17Z same · 2.0.24 · V1.2.3 Krea red twice, both in the base: the SageAttention build died at the first PyTorch include (torch 2.14 wants C++20, upstream hardcodes c++17) and the RMBG-2.0 snapshot’s byte total counted the __pycache__ the first render left in it → base 2.0.25
6 · 18:02Z same · 2.0.25 · V1.2.4 green at every stage: SageAttention 2.2.0 built for sm_120 through the patched checkout (2.5 min), sageattention imports, llama_cpp imports with GPU offload, the sam3 package vendored, models ok 26 (103.79 GB) with nothing re-fetched, import ok (33 packs), smoke ok, combos ok, suite 92 passed / 5 skipped, STEP RC=0, pins unchanged. The pod’s ledger: base 2.0.25, H3 6.0.5, Krea 2 1.2.4

What the new pod proved along the way: the base’s boot contract on a second image family — podctl ensure set the start override on hearmeman/comfyui-qwen-template:v15, and boot.sh became PID 1 with sshd, JupyterLab and ComfyUI from the volume’s venv, nothing reinstalled; the template’s own downloader never ran.

Renders. After run 6 the ⚡ Iterate render in ① Create failed at its first node with CUDA out of memory: 93.5 GB of the card were held by processes outside the container (0 % utilisation) — not the package, and not fixable from inside the pod. The base grew rule 11 for it (2.0.26: the GPU must be ours, measured before any upload), a stop/start put the pod back on the same machine, and podctl deploy --like (2.0.27) put the volume on a new pod, b6chi54aw7mxcq, machine bxjuys2ybmex, with the whole card free. There, base 2.0.30 went green and the renders ran through the tunnel in ① Create with no LoRA: ⚡ in 421 s (every first model load: Florence-2, the GGUF enhancer through llama-cpp on the GPU, SageAttention in auto mode, Krea 2 turbo), ◆ in 65 s with the models warm — both without an error in the console, and both with the stock cartoon of example.png painted into the scene. V1.2.5 (a prompt fallback on the package’s switch) changed nothing on the pod; two live experiments in the frontend found the two routes and V1.2.6 closes both (§8): the same prompt then rendered as a clean photograph in 43 s. V1.2.6’s first load re-enabled two nodes from their booleans (§8, V1.2.7).

The renders on V1.2.7 (2026-09-06 ~22:00-22:30Z, the shipped workflow loaded from the pod’s user data, nothing patched by hand, ① Create, no LoRA, one prompt — “candid editorial photograph of a woman with short dark hair in a mustard raincoat, standing under a cafe awning during light rain…”): ⚡ 45 s, ◆ 41 s, ★ 61 s, each a clean photograph of the prompt with no error in the console; the enhancer kept the scene on every run (its output is logged); the models were warm from the first run of the day. Two extra renders happened by accident and are worth their line: adding node 3414 shifted the ids of every node the build adds after it, so a tier switch addressed by node id landed on the MODE radio and queued ② Restage — which rendered the stock image as a person at a night market (the instruction box’s placeholder text), clean, at ◆ and at ★: the Restage path’s grounded conditioning reaches the sampler (V1.2.6’s second effect), and the ★ pipeline runs. Address the radios by group title, never by id. ★ ran on turbo at 40 steps, CFG 3.5 (§2’s footnote).

② Restage, proven the same evening (Paul’s choice: a stock photo now, his own photos later). The source was the ⚡ render above, copied on the pod into input/restage_source.png — a realistic person made on the pod, so nothing entered from outside and no one’s likeness was used. Instruction: “Create a photo of this person on a sunny beach boardwalk at golden hour, wearing a white linen shirt, smiling, sea in the background.” Result in 57 s at ⚡, no error: the same face and hair on a boardwalk at golden hour, smiling, white linen shirt — with the mustard raincoat kept as a layer over it, the one part of the instruction the model did not drop. The prompt graph carried the four Restage nodes, the photo and the instruction (read back from the history). Two driving lessons for scripted renders, both recorded in memory: right after a fresh load the groups report no members until the canvas has drawn once, so force a draw (or resolve members by title) before setting modes; and verify the mode and tier counts before every queue.

Still Paul’s: Restage renders and the trainer with his photos; the four items below.

Still to verify on the pod — stated rather than assumed:

  1. Does krea2_style_reference.safetensors apply cleanly to krea2_turbo_bf16? The official style-reference template ships on int8_convrot. If that LoRA is quantisation-coupled, it is the one place a quantised model earns a slot.
  2. In Create mode, positive conditioning is either CLIPTextEncode or Krea2EncodeRebalance — answered by the pod on 2026-09-06: it was Krea2EncodeRebalance, grounded on the stock example.png — and ProGrade’s last pipe edit (3027) chose it regardless of the package’s switch. V1.2.6 makes the package’s switch the first input of that edit’s Conditioning Switch and ships the style references off; ① Create renders the prompt alone (proven: a clean photograph).
  3. Benchmark Iterate with the Krea2Edit patch engaged versus bypassed. If the subject LoRA alone carries likeness, Iterate may not need the patch at all.
  4. The tiers switch steps and CFG only (proven 2026-09-06): the ★ row’s RAW model and SeedVR2 upscale are not wired. From V1.4.0 the tier is an index into two LCIndexSwitch nodes, so wiring the model would mean a third row of filenames into the diffusion loader and the SeedVR2 frame behind a boolean off that index. Design first — Raw at CFG 3.5 costs minutes and SeedVR2 7B is 16 GB — and item 5 has to answer first.
  5. Is ★’s CFG 3.5 a Raw setting? Two renders, same prompt and seed, ★ on 🧩 MODEL · Raw against ★ on 🧩 MODEL · ⚡ Turbo. Krea recommends ~52 steps at CFG 3.5 for Raw and Turbo is distilled to run without CFG, so the Turbo render should look worse. The Raw path exists so this can be asked.
  6. Does the GGUF path render, and does the INT8 path load on sm_120? The pod’s V1.3.3 install on 2026-09-07 raised one warning and it is about this: “gguf: no requirements file, and the venv cannot import bagel2, diao, gguf_comfy, gguf_cutter, gradio, soundfile — a node using it renders red.” The pack itself imported (import ok (34 packs import)), so LoaderGGUF registers, but calcuis’ gguf ships no requirements.txt and the base therefore installs nothing for it. Check LoaderGGUF is in /object_info on the pod before blaming the file. Provenance is good — calcuis’ pack lists krea2 in PIG_ARCH_LIST, and core loads int8_convrot through comfy-kitchen — but neither file has ever run here, and comfy-kitchen’s INT8 kernels are Triton, which a CPU testbed cannot answer. Watch the VRAM high-water mark too: if GGUF and INT8 are not meaningfully lighter than bf16, the file is being dequantised on load and the low-VRAM claim is not real.
  7. Does 🔁 Head Swap do what it says? Structurally it is ③ with ref_boost 4.0, a blank instruction and a preset system prompt, and all 32 gate states validate. Whether that combination actually moves the face from photo 2 onto the body in photo 1 is a render, not a graph property.
  8. Does mode ③ render at all? It never has. Two independent blockers were fixed — the model switch sitting downstream of two pass-through patches (V1.3.0) and node [3402] pointing at example_person.png, a file that has never existed in any ComfyUI install (V1.3.3) — and the second is exactly the kind that hides behind the first.