One order, one owner
Most image requests in the family name no generator. Pippa's tool and the image-gen skill default to provider=auto, and so does the studio. Behind that word is a cost order Dad set on 2026-09-30: first the shared Codex subscription, then ElevenLabs' prepaid credits, then a local instruction model on Ember's machine, and only then a metered API key. That order lives in one service, Ember's generator catalog, and inside it in one function: auto_route() builds the route by appending the four tiers in exactly that sequence, and each entry's position is the rule. The module also declares AUTO_TIERS = ("codex", "elevenlabs", "local", "api"), but nothing reads that tuple. It is a second statement of the order, true only while someone keeps it in step by hand.
This lesson shows what Ember's code says today so you can see the mechanism. The image-gen skill tells every brain never to restate that order in its own words, for a good reason: the day Ember changes it, every restated copy becomes a quiet lie. Avatars follows that rule in code. It keeps no list of tiers to decide with. Each batch does store a snapshot of the catalog it was planned against, Ember's route included, as a record; nothing reads it back to choose anything.
| Tier | Row Ember resolves to | Billing | Reference limit |
|---|---|---|---|
| codex | Codex Image-Gen Tool (codex_image_gen) | subscription | none declared |
| elevenlabs | the ElevenLabs image model Ember is configured with | credits | per model |
| local | an installed instruction model on Ember | included | per model family |
| api | OpenAI Image 2.5 Flare (openai_flare) | metered | 16 |
One small mismatch inside Ember itself: its module docstring speaks of a metered Google/OpenAI key, while the route's last tier is the OpenAI row only.
How Ember decides
resolve_auto walks the tiers in order and skips a tier for one of four named reasons: it is unavailable (Ember says why), it does not offer the requested output size, the request carries more references than the tier declares it accepts, or it does not offer the aspect ratio. The first tier that survives wins. The function returns the provider and a decision record: what was requested, what it resolved to, which tier, and every tier it passed over with the reason. If nothing survives, it raises No automatic tier can serve this request, lists each tier's reason, and ends with Choose a provider explicitly.
Two details matter. A size of auto, or no size, never disqualifies a tier; it means the chosen tier's own default output. And ElevenLabs with low or unknown credits is skipped by the route while its row stays usable when someone picks it by name.
The route is the first row
Ember publishes the route twice. The tiers, with readiness and limits, go out as auto_route. The route also becomes the first row of the generator catalog, labelled Automatic, and the catalog's default provider becomes auto. Any client that just renders the catalog inherits Dad's order with no code of its own. Avatars' studio renders that row like any other, and under it prints Ember's cost order with unavailable tiers marked, plus each unavailable tier's reason.
Ask now, record it, submit the answer
When a request names the automatic generator, plan() does not send auto to Ember and hope. It calls Ember's dry decision, POST /api/v1/api-generators/route, with the request's size, reference count, aspect ratio and quality. Ember's own docstring for that route: which tier an automatic request would take right now, and why the others were skipped, so a client can show it before spending anything. Avatars then swaps in the resolved provider and stores the decision on every task as route. The plan summarises the distinct decisions as routes, and the review step prints one line per decision: Automatic → the chosen row's label and its tier, then skipped with each passed-over row and its reason.
The original choice is not lost. The task keeps provider_options.id == "auto" while provider holds the tier that was chosen, and a test checks both on the stored task. The worker later submits that resolved provider explicitly, so Ember never routes a submitted task a second time. Nothing switches provider after submission, and Ember's resolver says the same from its side: a paid or credited call is never repeated on another provider silently.
Because Generate images plans again, the decision stored is the one made at submission time, which can differ from the one shown minutes earlier. If Codex went down in between, the stored route says so.
The copy that lived 65 minutes
The first version of this was a copy. At 14:32 on 2026-09-30 the studio gained its own Automatic entry, built in the web UI with its own list of sizes. At 15:37 the same day it was gone: Ember now listed the route as its first catalog row and default, so the studio stopped building its own. The commit's subject is the lesson: render Ember's automatic generator from the catalog instead of a local copy. What survives in Avatars is one guarded line for an Ember that publishes no auto_route at all, which picks Ember's static default, or its OpenAI default when 2K or 4K is asked for. It is a fallback for an older engine, and the route is Ember's.
One number to read carefully
Avatars sends the route reference_count=min(inputs, 10). The ten looks like a residue of the ten-reference ceiling removed on 2026-10-06, and the architecture doc says the route gets the full count, soul references included. Nothing records whether the clamp is intended. Its effect is bounded: the per-task check after routing still refuses a tier whose declared limit is too small, by name, before anything is queued, so it cannot overspend. When the Codex tier is up it changes nothing, because Codex declares no limit. With Codex down and 12 references, though, the route can land on a tier that accepts 10 and the plan then refuses, where the metered tier (16) could have taken it, and the skipped text reports 10 where there were 12.