The two tempting moves
Picture a brain in the middle of a conversation. Dad asked for a cover. The tool call fails, or it has been running for sixteen minutes. Two moves look helpful. The first is to go around: call Ember directly, or pick an image file out of an avatar folder and use that. The second is to try again: send the same request one more time. Both are forbidden, and the reasons are different.
The Avatars client opens with both rules in its module docstring: there is no direct-Ember fallback, and a caller timeout leaves the durable batch available in Avatars rather than silently creating a second generation.
Why going around loses the picture's identity
Track 1 laid out who owns what. Avatars owns references, lineage, protection and the record of what was asked; Ember owns providers and the generated original. A direct Ember call would still produce a picture. It would produce one with no Avatars asset, no lineage, no inherited protection and no package. Ember's history would hold a row that Avatars has never heard of. If a protected soul's reference was involved, nothing downstream would know to keep that picture away from the public CDN, because the record that says so was never written.
The architecture puts it in two sentences. Reading an avatar folder and generating directly in Ember is not a supported bypass. If Avatars is unavailable, avatar generation keeps its intent and reports a retryable failure, while conversation and the serving of already published avatars carry on. Dad loses a picture for a while. Nobody loses the truth about the pictures that exist.
The client enforces the folder half in code. It refuses to write an output into my avatar folders at all: those are published copies, and new work belongs in the studio. A reference path inside those folders is never read for its bytes. The client sends the path to /internal/published-reference, which maps it to the Avatars publication it came from, or answers 404 with a sentence saying this copy has no authoritative publication.
An error message addressed to the reader
The first code block is the client's shared transport. Both of its failure messages end with the same instruction: do not bypass it with a direct image generator. origin() says it when Avatars cannot be reached from this Mac. request() says it for any other failure, a refusal included, because urlopen raises for a 4xx too. One step skips that transport. When the task is done, the original is downloaded with a bare urlopen and written to disk, outside request(). A failure there reaches ImageGen as a raw urllib or file error, with neither the instruction nor the batch id. The picture already exists by then and Avatars holds it, so the right move is the same, look at the batch in the studio and do not resubmit, but the message no longer says so.
That sentence is written for a brain. ImageGen catches the exception and returns its text as the tool result, prefixed Error: ImageGen:, and on the Claude path the result is marked as an error. So the instruction arrives inside the very message that tempts the brain to improvise. A rule in a system prompt competes with everything else the model has read. A rule in the error competes with nothing.
Why trying again is a second bill
The second code block is the end of the polling loop. While the task is still in progress, the client waits 3 seconds and asks again. Two exits are not success:
- The task needs attention. For
failed,cancelled,uncertain,copy_failedorhistory_missing, it raises with the batch id, the state and the task's error, and says to review or reconcile that batch and not to submit it again. - Time ran out. After
timeout_seconds(960 by default), it raises with the batch id and the current state, and says the batch continues on the server: inspect this batch instead of resubmitting.
A timeout here is the client giving up on waiting. Avatars' worker has not given up on anything. The task may finish a minute later, saved, billed and recorded.
Now look at what a retry would send. ImageGen passes no request_id, so the client mints a fresh UUID for every call. Avatars recognises a repeated request only by its id: the same id with the same body returns the batch it already made. A second ImageGen call is therefore a new intent, a new batch and, on a paid tier, a second charge for a picture that was probably already on its way. VideoGen is different on purpose. It lets the caller pass back the request_id a refused plan returned, so confirming a clip after Dad agrees is the same request, not a new one.
What to do instead
The rule in the image-gen skill, in the third code block, says it in one line: report the existing batch ID and reconcile an uncertain outcome before retrying; do not bill a second generation. In practice:
- Tell Dad the batch id and the state the error named.
- Look before acting.
/internal/batches/{id}answers the actor that asked; VideoGen hasaction=statusfor clips.ImageGenhas no status action, so for an image the next look happens in the studio's batch view. - Reconcile there. Track 4's lesson Unknown Is Not Failed shows how the worker searches Ember's history by task id and why an uncertain task can be retried only after a reconciliation with no match and an explicit acknowledgement.
The rules are written in five more places: the ImageGen description (do not fall back to direct generation when Avatars is unavailable), the skill, the Sidekick rulebook (reconcile before a retry, never silently regenerate), VideoGen's description (never resubmit a clip that timed out or needs attention) and the architecture. The words are there to steer a brain. The worker's retry rules are what hold when a brain does not listen.
One more line in the skill keeps the rule honest. Outside cwkPippa and Beacon, a harness may use Codex's built-in image tool by default, and the skill says plainly that this is not a service-failure fallback. A different route chosen up front is a choice. The same route chosen because Avatars failed is a bypass.