Model swap workflow: engineering handover
Exported 2026-10-20 from the model-swap skill at version 2.6.0 (skills/model-swap/, the canonical source; the copy in reference-implementation/ is a snapshot). This is the process that produced the swaps Kate Hewko approved on https://pixverse-kate-hewko-1020.pages.dev/. It is written for the team that will run it as a production workflow.
What the workflow does. Given a brand's product photo, a model's face and body references and a few words about the pose and the garment, it produces one edited copy of the photo in which the model is replaced down to the fingertips, the garment and everything else stay as photographed, an independent vision reviewer has judged the result against the original, and the deliverable has the photo's exact pixel size.
Contents of this folder:
| Path | What |
|---|---|
README.md |
this spec |
| prompts/ | every prompt template, rendered from the code with {PLACEHOLDERS}; review.rubric.json is the reviewer's criteria and pass rule |
| job.schema.json | the job request and result shapes |
examples/kate-hewko/ |
the real parameters, a rendered prompt per type, a generation receipt, two review records, two size charts, timings and costs |
reference-implementation/ |
the Python scripts and the skill's own documentation, as shipped |
1. Pipeline
model intake ──► reference set ──► size check ──► generate (one edit) ──► frame match ──► review ──► retry? ──► deliver
| Stage | Input | Output | Reference implementation |
|---|---|---|---|
| 1. Model intake | a comp card or photos (brand's own model) or a casting brief (fictional model) | a measurement card; for a fictional model, an approved six-panel sheet | model-creation.md; prompt prompts/model-sheet.six-panel.txt |
| 2. Reference set | the comp card plus an identity description in words | four views: front, three-quarter, profile, full length (about 40 s, four parallel generations) | ref-set.py; prompts prompts/reference-set.*.txt |
| 3. Size check | the product's size chart, the size the photo was shot in, the model's card | mode: identity, refit or no_swap |
SKILL.md section 2 |
| 4. Generate | product photo + face reference + body reference + the rendered prompt | one raw image | identity-swap.py / body-swap.py, prompts.py, image_model.py |
| 5. Frame match | the raw image and the product photo | the master at the photo's exact size | frame.py |
| 6. Review | product photo + master + three full-resolution crop pairs | a record with nine scores, evidence, the worst detail, a resubmit hint, a verdict | garment_review.py; prompts/review.*.txt, prompts/review.rubric.json |
| 7. Retry | a failed verdict | a second review to confirm; then one resubmission with the hint appended to the prompt; at most three attempts | the loop in identity-swap.py |
| 8. Deliver | the passing (or best) attempt | master PNG, raw PNG, before/after JPEG, receipts, review records, the claim |
2. Models and references
- Brand's own model. Rights first: a comp card is not a release; generation is refused without the brand's written confirmation of AI-likeness rights. Then
ref-set.pymakes four views from the comp card with one prompt per view (prompts/reference-set.*.txt) at 3:4 (portraits) and 2:3 (full length), all four in parallel.refset/front.pngbecomes the face reference andrefset/full-length.pngthe body reference. The profile comes out at about 80 degrees, not a strict silhouette; that is fine as a reference. - Fictional model. One six-panel sheet from one prompt (
prompts/model-sheet.six-panel.txt): portrait, laugh and street shot on top; front, side and back in a grey studio in a black base layer below. Every run makes a new face; the brand approves one and that sheet is locked. The portrait panel is the face reference, the studio front panel the body reference. Cast from the brief, never from a real person; accounts the brand admires are style cues only. - Measurement card. Height, bust, waist, hip and the brand's size, kept as data. It never enters a prompt: digits render as text in the image.
- Model look string (
lookin the schema,FACEin the scripts): what the face reference shows, skin tone included, e.g. "platinum blonde blunt bob with heavy straight bangs, pale Scandinavian skin tone, mid-forties with fine crow's feet, matte red lip, small earrings". It is used twice: in the generation prompt, and in the reviewer prompt so the model's own glasses and jewellery are not scored as added accessories (see the false failure in examples/kate-hewko/parameters.md).
3. Size check
For each product photo: read the size chart, note the size the photo was shot in (page text, the brand, or the variant image), and the size the model would buy from that chart.
| Model's size vs the photo | Mode | Prompt | Reviewer mode |
|---|---|---|---|
| same size, similar build | identity |
prompts/identity-swap.*.txt |
strict: the garment sits exactly as photographed |
| different, inside the chart | refit |
prompts/body-refit.*.txt (adds the {BUILD} and a refit KEEP block) |
refit: fit judged as "the same garment at her size" |
| outside the chart | no_swap |
none | ask the brand for a fit-model shoot or another product |
A swap never keeps the photographed body under a different model: that misrepresents her and makes a size range a lie. On a relaxed, boxy garment the identity and refit results look almost identical (Yvonne on the jacket); on a fitted piece they would not.
4. Generation
Call. pixverse create image (the PixVerse CLI; the API equivalent takes the same fields) with:
| Field | Value |
|---|---|
| model | gpt-image-2.5-sunburst, else gemini-nano-banana-2.1; if neither is available to the account, stop. No other model. |
| images, in order | product photo, face reference, body reference (the prompt refers to them as first, second and third image) |
| prompt | rendered from the four blocks below; no digits anywhere |
| quality | 1440p by default; 2160p only on an explicit request (Kate's runs were at 2160p) |
| aspect ratio | the nearest supported ratio that is at least as wide as the photo (3:4 for a 5:7 photo); never narrower, see section 5 |
| detail level | high, GPT Image models only |
| count | 1: one job per photo |
| idempotency key | <job>-a<attempt>-<model>; the backend dedupes by key |
| timeout | 1200 s polling |
The prompt is four blocks, built by reference-implementation/prompts.py and exported verbatim in prompts/:
- CHANGE ONLY THE MODEL. Who becomes the model, part by part: face, hair, neck, shoulders, both arms from shoulder to fingertips, both hands, one consistent skin tone, no trace of the original model. For a back view, the face is not visible and the back of the head is named. Identity mode says her build matches the photographed model's; refit mode says her whole body follows the body reference and the outfit is refitted "neither slimmer nor larger than she is", with
{BUILD}. - KEEP THE GARMENT AS PHOTOGRAPHED. The garment description, then print at the same scale and direction, colours, fabric, seams, trims, hardware; folds and hem where they are (identity) or moving only as much as her body makes them (refit); the neckline binding and every edge crisp and uncovered; hair covering no more than the original's; other garments with their exact cut, wash and weave texture, nothing smoothed.
- LIGHT AND SKIN. Same studio light direction, highlights and shadows on the new skin; natural, unretouched skin, no beauty smoothing.
- FRAME AND POSE. Same pose (
{POSE}), body and head position, head size, shoulder line, camera angle, focal length, background; nothing re-cropped or zoomed; extra canvas width is plain backdrop at the sides only.
Then the tail: no readable text or logos, and (fictional models only) "she is a fictional person and must not resemble any real or famous person".
Each block exists because an earlier, shorter prompt left a specific soft spot that the reviewer kept naming: softened collar bindings, hair wisps over the neckline, arms lit differently from the studio, smoothed denim, a frame that crept up. Keep the blocks when you port this; do not compress them back into one sentence.
Rules the generator enforces. The rendered prompt must contain no digits (the scripts refuse to run otherwise). The words "semi-sheer" and "bralette" have failed moderation; describe layering neutrally ("layered over a black top").
5. Frame match
The CLI offers fixed aspect ratios only, so a 5:7 photo cannot be produced directly. The rule (frame.py): generate at the nearest supported ratio that is at least as wide as the photo, then centre-trim the extra width and resize to the photo's exact pixel size. Only backdrop at the sides is trimmed; nothing is cut from the top or bottom, so hair and hems survive. A narrower ratio would have to lose height and is never chosen. A 2K generation that is smaller than the photo keeps its own pixels at the photo's aspect; it is never upscaled. Supported ratios used: 9:16, 2:3, 3:4, 1:1, 4:3, 3:2, 16:9.
6. Review
garment_review.py sends the reviewer the original, the master, and three full-resolution crop pairs (upper, middle, lower garment: the central band of the frame split in three), with the prompt in prompts/review.identity.txt or prompts/review.refit.txt. The reviewer returns JSON: nine criteria scored 1 to 5 with evidence, the single most visible difference, a one-sentence resubmit hint, and notes. Temperature 0.1, JSON response mode.
| Criterion | Critical |
|---|---|
| print_pattern | yes |
| colour | yes |
| construction | yes |
| trims_hardware | yes |
| other_garments | yes |
| fit_drape | no |
| pose_framing | no |
| model_replacement | yes |
| artifacts | yes |
Pass = every critical criterion scores 4 or 5 and no criterion scores below 3. Anchors: 5 identical in every inspected place; 4 a difference only a pixel comparison would find; 3 a small visible difference; 2 a clear difference a shopper would notice; 1 changed into something else.
Reviewer models, in order: gemini-4-argon, then gemini-3.8-flash. Route: the OYDN gateway first, the Gemini API as fallback; an overloaded model is retried; if none answers the job stops as "unreviewed" rather than shipping unjudged. The reviewer is not perfectly stable near the threshold, so a fail is confirmed by a second independent review before a generation is spent on a resubmission.
7. Retry loop
attempt = 1
loop:
generate (prompt + hint if any) → raw → frame match → master
review → record
if pass: master is the deliverable; stop
if fail: review again; if that passes, treat as pass; stop
if attempt == max_attempts (3): keep the best-scoring attempt, mark NOT PASSED; stop
hint = record.resubmit_hint with any digits stripped; attempt += 1
A resubmission is the only case in which a photo is generated twice. A photo that already has a passing result is never regenerated, and a test image that passed counts as a result.
8. Deliverables and claims
Per photo: the master PNG at the photo's exact size (<stem>-swap.png), the raw output and master for every attempt, a before/after JPEG per attempt, the generation receipt (image id, model, size, cost) and the review record per attempt. Full-resolution files go to the brand as files; the PixVerse media host downscales to 1920 px on the long side, so the page shows 1920 px copies and the masters travel separately.
The claim for every swap is "kept as photographed and checked against your photo" (plus "refitted to her" after a refit). Never "pixel-identical", "untouched" or "unchanged pixels": the frame is re-rendered, and the review plus the brand's own comparison are the proof.
9. Observed performance (Kate Hewko, Sunburst at 2160p)
| Step | Time | Credits |
|---|---|---|
| Reference set, four views in parallel | 37 s | about 50 per view |
| Identity swap, generation only | 52 to 60 s | 50 |
| Review verdict | 2 to 3 min | none (reviewer cost separate) |
| Body refit including review | 3 to 4.5 min | 50 |
| Six-panel model sheet | about 60 s | 70 |
19 of 22 page swaps passed on the first attempt; two needed one or two resubmissions; one failure was the reviewer's, fixed by passing it the model's look. Details in examples/kate-hewko/parameters.md.
10. For production
- Secrets and routes. PixVerse credentials (CLI login or API key), the reviewer route (OYDN gateway key or a Gemini API key with quota for Gemini 4). The reference implementation shells out to the
pixverseandoydnCLIs; replace with API calls and keep the same fields. - Concurrency. Three photos per model in parallel was fine; the reviewer is the slow step, so queue reviews separately from generations.
- State per attempt. Keep every attempt's receipt, raw, master and review record; the review record's
modelfield names the judge and route, which matters when a verdict is disputed. - Size data. Store size charts per product (
examples/kate-hewko/size-chart-*.json), the photographed size per photo, and the measurement card per model; the mode decision should be data, not a judgment call at run time. - Rights. Block generation for a brand's own model until a rights confirmation is on file; block any use of accounts the brand admires as image references.
- Moderation. Keep a list of garment words that have failed (
semi-sheer,bralette) and rewrite them before submission. - Known limits to surface to the user. The frame is re-rendered; a shorter-haired model exposes garment the camera never saw, which is rendered; only photographed angles can be swapped, a new angle is a new picture (
prompts/four-view.new-angle.txt); a refit changes fit by design and needs the brand's eye; real age and skin texture are kept, never smoothed.
Prompt files
- body-refit.back.txt
- body-refit.front.txt
- four-view.front.txt
- four-view.full-length.txt
- four-view.new-angle.txt
- four-view.profile.txt
- four-view.three-quarter.txt
- identity-swap.back.txt
- identity-swap.front.txt
- identity-swap.own-model.front.txt
- model-sheet.six-panel.txt
- reference-set.front.txt
- reference-set.full-length.txt
- reference-set.profile.txt
- reference-set.three-quarter.txt
- review.identity.txt
- review.refit.txt
- review.rubric.json