Skip to content

Conversational image edit

View on GitHub

A four-seat noodle stall wedged under a concrete overpass at 4am … Empty, no people.

add a grandma with a cyborg arm serving inside

add a humanoid alien as well as a human, and an orange cat lying on the ground next to the shop

One session, in order. Each caption is the line that produced the image beside it.

Getting the picture you actually want takes a few goes. But rewriting the prompt each time starts over, and the model hands you a different picture.

Say what to change. Keep the picture.

Terminal window
GOOGLE_API_KEY=... pnpm tsx recipes/conversational-image-edit/run.ts
you A four-seat noodle stall wedged under a concrete overpass at 4am … Empty, no people.
ready output/conversational-image-edit/2026-09-05T19-38-21/turn-01.jpg in 11.3s
you add a grandma with a cyborg arm serving inside
ready …/turn-02.jpg in 9.3s
you add a humanoid alien as well as a human, and an orange cat on the ground
ready …/turn-03.jpg in 10.9s

The stall is still the same stall. Counter, stools, curtain and strung bulbs all survive three rewrites; only what you asked for changes.

Why it stops drifting

Chained edits fall apart. Each one is a copy of a copy, and by about the sixth your character is someone else: AnchorEdit finds artifacts “can be recursively amplified”, and BFL warns of visible drift after roughly six turns.

So every edit here carries two pictures: the one you just changed, and the first one, which is never dropped. That single anchor takes identity retention from 33.5% to 52.9% over ten-plus turns. It costs one array entry.

Watching it render

An image call is usually a minute of nothing. Some providers send preview frames as the picture resolves, and where your terminal can draw images they appear inline, no image library involved. Elsewhere they land in previews/ as they arrive.

Providers that cannot preview simply draw the whole image, and nothing else about the session changes. Inline drawing works in iTerm2, kitty, WezTerm and Ghostty, and in VS Code once terminal.integrated.enableImages is on.

How it fits together

Lines in, events out, both ordinary Streams:

session(requests, draw): Stream<SessionEvent, AiError, ImageGenerator>
  • Turn-taking is back-pressure, not a state machine. The next edit needs the last image, so a line is read only once the previous turn finishes.
  • draw is a parameter, so whether you get previews is decided once, next to the Layer, instead of branching through the code.
  • The recipe never names a provider. --model does, and swapping it is the only change.
Flag
--modelprovider:model, e.g. google:gemini-3.1-flash-image
--resolution1K, 2K, 4K. Start at 1K
--previewsPreview frames per turn, 0 to 3. 0 forces whole images
--base-urlA gateway the registry has no name for

Ctrl-C ends the session. Every finished frame is written to output/conversational-image-edit/<timestamp>/.