Writing

The lifecycle of a media generation job

Long-running media generation is a distributed job that needs durable state, recovery, settlement, and delivery—not a single API call.

The first media model usually looks simple to integrate: submit parameters, wait for a response, and receive an image or video.

That abstraction breaks as models multiply, generation takes longer, providers return task identifiers, and the product must handle retries, billing, refunds, and file delivery. The interface may still resemble an API call. What actually happens is a distributed job that spans several systems and continues for minutes.

When we separated media generation into its own service, we initially focused on language, deployment, and provider adapters. The harder questions appeared later: who owns task state, what counts as success, and how the system can still make the right decision after the network has lost certainty.

Generation begins when the request ends

A synchronous interface makes a convenient assumption: when the request returns, the work is complete.

Media generation behaves in the opposite way. The upstream system may only acknowledge that a task has been accepted. The actual computation happens later. It may queue, run, fail, retry, and eventually produce something that can be downloaded.

Task creation and generation success must therefore be separate facts. The first says that the system received the work. The second says that the user has a result.

Collapsing them into one request makes timeouts dangerous. The caller sees a failure even though the generation service may have created the task successfully. Submitting again can create duplicate work. Refunding immediately can leave completed computation unsettled. Charging anyway can make a user pay for a result the product cannot find.

A network timeout is not a failure verdict. It means the caller does not know what happened.

One job needs one state authority

A media pipeline often contains a product backend, a generation service, and an external compute platform. Each holds part of the state, and each can offer its own answer to where the task is.

If every layer independently infers the final state, reality splits. One layer believes creation failed while another continues generating. One layer issues a refund before another reports success. A user closes the page, and the only process responsible for recording the result disappears with it.

A reliable design needs a stable job identity and a state record with final authority over the lifecycle. A task can move through accepted, running, succeeded, and failed, but those transitions must be traceable, repeatedly readable, and independent of whether a browser tab still exists.

Other systems may observe the job. They should not invent their own version of its facts.

Polling observes work; it does not perform it

Asynchronous generation is commonly observed through polling. Polling itself is not the problem. The mistake is allowing it to become the force that advances the task.

If the backend checks status, saves the result, or settles billing only while the frontend keeps asking, closing the page ends the product’s responsibility. The external computation may finish, but the product never learns that it did.

Polling should answer only one question: where is the work now? Progress, terminal reconciliation, artifact persistence, and settlement cannot depend on whether an observer remains online.

The service should not maintain an unconditional high-frequency polling loop while every caller repeats the same work either. One component must own reconciliation. Other queries should read the same state. More polling without authority only creates more races and cost.

Idempotency handles uncertainty

Idempotency is often described as protection against a user clicking twice. For long-running jobs, its more important purpose is to handle moments when the system cannot confirm the result of its previous action.

A request can disconnect after the upstream job was created. A callback can arrive more than once. Two instances can try to apply the same state transition. Before repeating any action, the system must determine whether the corresponding fact already exists.

A generation job therefore cannot depend only on a temporary request identifier. It needs a stable business identity so creation, status, artifact persistence, and settlement all refer to the same piece of work.

Retrying does not mean doing the action again. It means attempting again to bring the system to the same intended state.

The file is part of the terminal state

Result URLs returned by generation platforms are not always stable. They may expire or even change across repeated status queries. Passing the most recent URL straight to the user does not mean the product owns the deliverable.

After generation succeeds, the external result must become a product artifact: a stable location, content type, lineage, and owning job, with confirmation that the user can continue to access it.

Success should not mean that the upstream platform returned a URL. It should mean that the product has acquired and preserved a deliverable result.

This boundary also determines settlement. Computation ending, a file becoming available, and the user receiving a deliverable are related but distinct facts. The system must know which fact authorizes a charge or a refund.

Abstractions should unify meaning, not erase differences

A multi-provider service is often designed around one perfectly uniform interface: every model receives the same parameters and returns the same shape.

Different models do not share identical support for size, duration, reference material, or generation modes. Erasing those differences only hides constraints in the implementation until they reappear as confusing runtime failures.

A better abstraction unifies the job lifecycle, error semantics, and artifact outcome while expressing capability differences explicitly. The caller chooses a model and provider according to product policy. The generation service maps each implementation into the same set of task facts.

The system should agree on what the work means, not pretend that every capability is identical.

Success rate begins as a definition problem

Once the system measures success rate, another question appears: what counts as a service failure?

Invalid input, rejected content, temporary model saturation, network failure, and lost internal state can all prevent a user from seeing a result. They require entirely different engineering actions. Combining them into one failure number produces anxiety without directing a fix.

Observability must preserve where a failure occurred, whether it can be retried, whether responsibility belongs to the input or the service, and whether the job eventually recovered through another path. A useful success rate is not the most flattering number. It is a classification with a clear owner and next action.

Cost cannot remain only in an upstream bill either. A job must be traceable from creation to delivery, including where consumption occurred, whether user settlement completed, and whether a failure path left the two inconsistent.

Media generation is a job system

Separating media generation from the main application did not ultimately create a faster API. It created a clearer division of responsibility.

Model adapters submit work to compute platforms. The job system owns state, idempotency, and recovery. The artifact layer owns results. The product system owns policy and the user relationship. Observability proves whether those parts agree.

Language and deployment are implementation choices. The important decision is that work which cannot finish inside a request should stop pretending to be a request.

If generation takes minutes, it is not a response.

It is a job.

Article activity

readscomments

Discussion

Comments

Questions, disagreements, and useful additions are all welcome.

Leave a comment

Comments publish immediately and can be deleted from this browser.

A privacy-friendly human check runs when you publish.