← Visual SandboxCONTENT HISTORY
WHAT CHANGED
Update to Visual Sandbox
Snapshot Nov 5, 2026 · 01:19 UTC · version 1.0.8
Collection source: downloaded plugin package.
First saved snapshot
No earlier snapshot is available to establish a change.
Save Visual Sandbox and check future changes in Following.
Up to 10 plugins free · no card required.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Change an image more than once without it falling apart. Read this before the SECOND `vsb run` on the same picture — before chaining a sandbox node's `output_url` into the next run's `image_input`, before \"now make the shirt blue too\", and before any loop that feeds an output back in as an input. Covers generation loss (why every image model degrades on repeated edits), the four workflows that prevent it (prompt stacking, contact sheet, crop and composite, noise repair), and the `output_format` / `resolution` settings that decide the outcome. For writing the prompt itself, read [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) first.\n",
"included_files": [
{
"relative_path": "agents/openai.yaml",
"size_in_bytes": 280
}
],
"name": "vsb-image-iteration",
"skill_md_contents": "---\nname: vsb-image-iteration\ndescription: >\n Change an image more than once without it falling apart. Read this before the\n SECOND `vsb run` on the same picture — before chaining a sandbox node's\n `output_url` into the next run's `image_input`, before \"now make the shirt\n blue too\", and before any loop that feeds an output back in as an input.\n Covers generation loss (why every image model degrades on repeated edits), the\n four workflows that prevent it (prompt stacking, contact sheet, crop and\n composite, noise repair), and the `output_format` / `resolution` settings that\n decide the outcome. For writing the prompt itself, read\n [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) first.\nlicense: Proprietary. See LICENSE for the terms.\n---\n\n# Iterating on an image without degrading it\n\n## The one thing to know\n\n**No image model edits an image. It reads the image and draws a new one that\ncopies it.** Every \"edit\" is a full redraw from scratch.\n\nSo each pass adds error on top of the last pass. After two edits you see it.\nAfter ten, the parts you never touched are unusable. Users call it\n*generation loss*, or \"a photocopy of a photocopy\".\n\nThis is not a Nano Banana bug. It happens on `image/nano-banana*`,\n`image/gpt-image-2`, `image/seedream-5-pro`, and `image/flux-2-*`. It happens\nthrough the API, not only in a chat interface. It cannot be prompted away.\n\n**What it looks like:** grain and noise creep in, edges go soft, colors drift,\nJPEG blocks and banding appear, faces slowly stop being the same face.\n\n> **The trap in this CLI.** `vsb` critical rule 10\n> tells you to pass a selected node's `output_url` into the next run's\n> `image_input`. That is correct for the FIRST edit. Do it twice in a row and\n> you have built the photocopy loop. Read the decision table below before the\n> second one.\n\n## Pick a workflow\n\n| You want | Use | Quality cost |\n|----------|-----|--------------|\n| Several changes to one picture | **A — prompt stacking** | None. Always one pass. |\n| Many frames of the same subject / scene | **B — contact sheet** | None. One pass for all frames. |\n| One small change, rest must stay untouched | **C — crop and composite** | None outside the crop. |\n| An image that is already damaged | **D — noise repair** | Recovers some. Not all. |\n\nWorkflows A and C are the only two that actually solve the problem. B avoids it\nby never chaining. D is a rescue for work you cannot redo.\n\n---\n\n## Workflow A — prompt stacking (the default)\n\n**Never edit the edit. Grow the prompt and re-run from the original.**\n\nKeep two things in a scratch file: the original image (or the original\ntext-to-image prompt) and a master prompt. For each new change, append one\nsentence to the master prompt, then run again from the original source.\n\n```bash\n# Turn 1 — establish the original. Note the job_id it returns.\nvsb run image/nano-banana-2 --json --no-sandbox \\\n --output_format png --resolution 2K \\\n --prompt \"A man in a grey suit stands in a bright office.\"\n\n# Turn 2 — the user wants a green tie. Re-run the SAME inputs with one\n# sentence added. No reference image, so the picture is drawn fresh.\nvsb run image/nano-banana-2 --json \\\n --from-job <job_id_from_turn_1> \\\n --prompt-add \"He wears a dark green tie.\"\n\n# Turn 3 — and a red door behind him. Stack again, from turn 2's job.\nvsb run image/nano-banana-2 --json \\\n --from-job <job_id_from_turn_2> \\\n --prompt-add \"A red door stands behind him.\"\n```\n\n`--from-job` copies the past run's inputs and `--prompt-add` appends to its\nprompt. Explicit flags override anything inherited. Nothing is ever fed back in\nas `image_input`, so there is no chain and no decay.\n\nDoing it by hand works the same way: keep the master prompt in a file, append a\nline per change, and pass the whole file each time.\n\nWhen the original is a photo the user supplied, the same rule applies — the\n`image_input` always points at **their** file, never at your last output.\n\n**Cost:** a full generation per change, same as an edit. Nano Banana 2 Lite is\nabout $0.05. You pay the same either way, so there is no reason to chain.\n\n**Trade-off:** the composition moves between runs, because each run is a new\ndraw. When the user needs the exact same frame and only one element changed,\nuse workflow C instead.\n\n> ⚠️ This corrects rule 4 in [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md)\n> (\"Iterate, don't one-shot. One change per turn.\"). One change per turn is\n> right. Feeding the previous output back in is not. Iterate on the **prompt**,\n> not on the **pixels**.\n\n## Workflow B — contact sheet (one pass, many frames)\n\nWhen the user needs the same character or product in several shots, ask for a\ngrid in a single generation. One pass means one draw, so every frame shares\nexactly the same face, wardrobe, light, and grade. No chaining, no drift.\n\n```bash\nvsb run image/nano-banana-pro --json --output_format png --resolution 4K \\\n --aspect_ratio 3:2 \\\n --prompt \"One 2x3 contact sheet image, 6 frames.\nAll wardrobe, styling, hair, makeup, lighting, environment, and color grade\nmust remain 100% unchanged across all frames.\nEach frame shows a resting point after a camera move. Describe only the final\ncamera position and what the subject does, never the motion.\nFrame 1: eye-level medium shot, three-quarter turn.\nFrame 2: high-angle three-quarter frame, diagonal downward, reveals wardrobe from above.\nFrame 3: low-angle oblique full-body frame, elongates the silhouette.\nFrame 4: side-on compression frame, long lens.\nFrame 5: over-the-shoulder frame, subject in the near third.\nFrame 6: extreme detail frame of one wardrobe detail, from an unusual angle.\nKeep exact garment type, silhouette, material, color, texture, stitching,\naccessories, closures, jewelry, shoes, hair, and makeup in every frame.\"\n```\n\nThen split the grid. Two ways, both fine:\n\n- **Local crop** (free, exact) — splits a 2x3 sheet into six tiles:\n ```bash\n for r in 0 1; do for c in 0 1 2; do\n ffmpeg -y -i sheet.png -vf \"crop=iw/3:ih/2:iw/3*$c:ih/2*$r,format=rgb24\" \\\n -frames:v 1 -update 1 \"frame_${r}${c}.png\"\n done; done\n ```\n- **Model extract** (costs a run, upscales at the same time):\n `\"Isolate and amplify the key frame in row 1 column 1. Keep all details of\n the image in this keyframe exactly the same, do not change the pose or any\n details of the model.\"`\n\nUse the local crop by default. The model extract is a second pass, so it is a\nsecond chance to degrade.\n\n**Best fit:** keyframes for image-to-video, character sheets, product angles,\nstoryboards, ad variations.\n\n## Workflow C — `--mask` (surgical)\n\n**Change one region. Keep every other pixel bit-identical.**\n\nThis is what professional retouchers do. The model only ever sees the crop, so\nloss cannot spread into the rest of the picture.\n\n1. **Crop the region**, with padding around it. Padding matters — a tight crop\n produces an edit that does not know what it sits next to, and the seam shows.\n Add 15–20 px.\n2. **Run the model on the crop only.**\n3. **Paste the result back** onto the original with a feathered mask (2–5 px).\n\n```bash\nvsb run image/nano-banana-2 --json \\\n --image_input photo.png \\\n --mask \"375,415,360,325\" \\\n --prompt \"Change only the ceramic mug to a deep cobalt blue glazed ceramic.\nKeep everything else in the image exactly the same, preserving the original\nstyle, lighting, and composition.\"\n```\n\n`--mask` takes **a rectangle** (`\"x,y,w,h\"` in pixels, or percentages like\n`\"40%,30%,25%,25%\"`) **or a mask image**, where white marks the part to change.\nThat is the polarity every mask-taking model uses.\n\n**The CLI picks the route from the model's schema. You do not.**\n\n| The model | What happens | What you get back |\n|---|---|---|\n| Has a mask input | The mask goes straight to the model | The model's own output. It saw the whole picture for context and painted only inside the mask. |\n| Has none | The area is cropped, run alone, and pasted back | A composited picture. Pixels outside the crop were never sent, so they are bit-identical. |\n\n`image/flux-fill-pro` is the first kind. Everything else is the second — all\nfour Nano Banana variants, `gpt-image-2`, `seedream-5-pro`, `flux-2-klein-9b` —\nso the crop route is still what most edits take. **No Gemini image model takes\na mask on any provider**: not Replicate, not fal.ai, not Google's\nown API. Google offers only *semantic masking*, which is the prompt wording in\nthe template below, not a parameter. Check any model with:\n\n```bash\nvsb schema image/<slug> --json | jq '.inputs | keys'\n```\n\nThe crop route reports what it did. The number that matters is\n`pixels_changed_outside_region`, which must be `0`:\n\n```json\n\"mask\": {\n \"mode\": \"crop\",\n \"out_path\": \"photo-edited.png\",\n \"region\": { \"x\": 375, \"y\": 415, \"w\": 360, \"h\": 325 },\n \"padded_box\": { \"x\": 355, \"y\": 395, \"w\": 400, \"h\": 365 },\n \"feather_px\": 20,\n \"pixels_changed_outside_region\": 0\n}\n```\n\n**A painted mask on a model without a mask input collapses to a rectangle.**\nThe crop route can only cut a box, so it uses the box containing the painted\narea. Anything you masked *out* inside that box is redrawn too.\n\n**Flags**\n\n| Flag | Default | What it does |\n|---|---|---|\n| `--mask <spec>` | — | A mask image, or `\"x,y,w,h\"` in pixels or percentages. |\n| `--mask-out <path>` | `<source>-edited.png` | Where the composited picture lands. Crop route only. |\n\n`--pad` and `--feather` still work on the crop route but are deliberately\nundocumented: both derive from the size of the area now, and overriding them\nmostly breaks the blend. Reach for them only when a seam is visibly wrong.\n`--region` and `--region-out` keep working as aliases.\n\n\n`--image_input` may be a local file or a URL, so a sandbox node's `output_url`\nworks directly.\n\n**Reading `colour_shift`:** the CLI samples the padding ring — context the model\nonly saw so it could match — and corrects the whole patch by the difference. A\nshift of a few levels is normal and is what makes the seam disappear. A shift\nnear ±32 means the correction hit its clamp: the region is probably drawn\ntighter than the object, so the ring caught part of the subject. Widen the\nregion and run again.\n\n**Rules that make the seam invisible:**\n\n- Expand the selection by 15–20 px before you generate.\n- Feather the edge by 2–5 px on the way back.\n- Keep every attempt. The best result is often a blend of two tries.\n- **Prompt the change, not the scene.** \"Change the wall behind her to sage\n green\" beats a full re-description. A full description invites the model to\n reconsider everything.\n- Expect the crop to come back slightly off in color. Correct it with a curves\n adjustment locally. Do not re-prompt for a color shift — that is another pass.\n\n**When `--mask` is the wrong tool.** It assumes the surroundings stay put: it\nblends the new region against the padding ring, and the ring only works as a\nreference if that ring is the same content in both pictures. Removing or moving\na whole structure breaks the assumption — take out a closet and the wall plane,\nthe floor line and the ceiling all shift out to the frame edges, so there is no\nstable border left to blend against. Measured on exactly that edit: the colour\nmatch hit its ±32 clamp on all three channels and the result was an obvious\nrectangle. The CLI now warns when the clamp is hit.\n\nFor that kind of change, run **full-frame** with no `--mask`, and accept that\neverything is redrawn. Tier up to `image/nano-banana-pro` when the picture has\ndetail worth keeping — on a real bedroom photo it held the brick texture, the\ncap logos and the lamp filament that the cheaper models softened away.\n\n**Best fit:** a fix on a picture the user already approved, product retouching,\nremoving one object, a text or sign correction, any change the user calls small.\n\n## Workflow D — noise repair (rescue an image that is already damaged)\n\nUse this only when the damage exists and the original is gone.\n\nAdd monochrome Gaussian noise to the image **before** you upload it. The noise\nbreaks up the JPEG blocks and the color banding. The model must then clean the\nnoise, so it rebuilds real detail instead of copying the damage.\n\n```bash\n# Add grain locally. More damage needs more noise; do not bury fine detail.\nffmpeg -y -i damaged.png -vf \"noise=alls=18:allf=t+u\" noisy.png\n\nvsb run image/nano-banana-pro --json --output_format png --resolution 4K \\\n --image_input '[\"noisy.png\"]' \\\n --prompt \"Upscale the image, reconstruct it with no noise and a high amount\nof detail, maintain its style.\"\n```\n\nRun it again with a different noise level if the first result is wrong. This\nrecovers sharpness and gradients. It does not recover detail the model never\ndrew.\n\nAn alternative rescue: `image-enhance/upscale`. Check the catalog first with\n`vsb models --modality image --json`.\n\n---\n\n## Settings that decide the outcome\n\n| Setting | Default in Visual Sandbox | What to use when iterating |\n|---------|---------------------------|----------------------------|\n| `output_format` | `jpg` for text-to-image, **`png` automatically when `image_input` is set** | Visual Sandbox flips it for you on edit runs, because an edit's output is a likely input to the next edit. Pass `--output_format jpg` to override. |\n| `resolution` | `1K` on `nano-banana-2`, `2K` on `nano-banana-pro` | **`2K`.** It holds up across passes. Only `nano-banana-2`, `nano-banana-pro`, and `gpt-image-2` expose it. |\n| `aspect_ratio` | `match_input_image` | Leave it. Changing it forces a full re-frame. |\n| Model | `nano-banana-2-lite` | **Not Lite for chained edits.** Google states Nano Banana 2 Lite is \"not optimized for multiple reference inputs or multi-turn sequential editing.\" Use `image/nano-banana-2` or `image/nano-banana-pro`. |\n\nA text-to-image run stays JPEG, because its output is a leaf. The moment a\nreference image is attached the container switches to PNG on its own. Check the\n`inputs.output_format` on the response if you want to be sure.\n\n```bash\nvsb schema image/nano-banana-2 --json | jq '.inputs | {output_format, resolution}'\n```\n\n## Prompt templates\n\n**The preserve template** (Google's own semantic-mask wording — use it on every\nedit run):\n\n```\nUsing the provided image, change only the [specific element] to [new element].\nKeep everything else in the image exactly the same, preserving the original\nstyle, lighting, and composition.\n```\n\n**Name three things, never two.** An implied region causes a full redraw:\n\n1. Which region changes. Describe it as if you were drawing a mask.\n2. What the change is.\n3. What the result must look like.\n\n**Use positive phrasing.** \"An empty, deserted street with no signs of traffic\"\nworks. \"A street with no cars\" gives you cars. The model reads the noun, not\nthe \"not\". This is the same rule as\n[`vsb-image-prompting`](../vsb-image-prompting/SKILL.md) rule 2.\n\n## What does not work\n\n- **Quality tags.** \"8k\", \"high resolution\", \"keep the same quality\",\n \"maintain sharpness\" do nothing on this model class. Users test them\n constantly and report no effect.\n- **Asking the model to fix its own damage.** A fresh run that says \"fix the\n pixelation\" returns the same bad result. Use workflow D instead.\n- **Long negative commands.** Repeating \"do not change the background\" five\n ways does not help and often makes it worse.\n- **Feathering on its own.** It softens the boundary and does nothing about the\n interior. Measured on a real Nano Banana edit: the step at the seam was 1.2\n levels, the patch interior sat 8.9 levels bluer than the wall around it, and\n it was the interior the eye caught. The colour match is what fixes it — which\n is why the crop route does both.\n- **`ffmpeg overlay` for the paste-back.** It converts the frame to YUV 4:2:0\n and back, which damages the whole picture. Measured: 324,018 pixels changed\n outside a 552x552 paste on a 1024x1024 frame. Use `--mask`; if you must do\n it by hand, force `format=rgba` on both inputs and\n `overlay=...:format=rgb,format=rgb24` on the output.\n- **A fresh chat or a fresh session.** Some users believe conversation history\n causes the loss. The problem also appears through the API with no history at\n all, so this is unproven. Nothing is lost by starting fresh, but do not rely\n on it as the fix.\n\n## Pre-flight checklist\n\nBefore the **second** `vsb run` on the same picture:\n\n1. Do I still have the original image or the original prompt? (If no — this is\n already workflow D.)\n2. Does the user need the exact same frame? → `--mask` (workflow C). Otherwise → `--from-job` + `--prompt-add` (workflow A).\n3. Did I pass `--output_format png`? (Automatic when `image_input` is set — but confirm it in the response.)\n4. Am I on `nano-banana-2` or `nano-banana-pro`, not Lite?\n5. Am I about to pass my own last output as `image_input`? → stop, go to step 2.\n\nAll five ✓ → run. Any ✗ → not ready.\n\n## Cross-links\n\n- Prompt craft for every image model → [`vsb-image-prompting`](../vsb-image-prompting/SKILL.md)\n- Model picker → [`vsb-image`](../vsb-image/SKILL.md)\n- Nano Banana edit-mode quirks + variant table → [`vsb-nano-banana`](../vsb-nano-banana/SKILL.md)\n- Saving a recipe so the original inputs survive → `vsb-presets`\n- Sandbox selection chain (critical rule 10) → `vsb`\n"
}SHA-256 of public snapshot: e676e7ff0f59b859e7b536a514ab0974dac4b03fc117e5bf4ae0b8bca3998f93