hermosodocs

Jobs & idempotency

Two things take long enough to need care: rendering video, and publishing to someone else's platform. Both have machinery you should understand before you write a retry loop.

Renders are jobs

A video render takes one to several minutes. Rather than hold a connection open, generating tools submit to a durable server-side queue and poll it. The MCP tools do this for you — render_ad and generate_video return the finished URL — so most callers never touch the queue directly. list_jobs and get_job exist for resuming and inspecting.

Job statuses

There are exactly four, and they mean what they say:

StatusMeaning
queuedAdmitted, not yet dispatched — or scheduled for a future time.
runningA worker owns it. progress is a 0→1 float.
doneFinished. result is populated before the status flips, so a poll can never see done with an empty result.
errorTerminal. error carries a human-readable message.

Polling

The reference client polls every 3 seconds with a 10-minute deadline and no backoff. A job that completes on the final tick still resolves — the deadline is checked after the status branch, not before.

A job's result is wrapped. Workers return { data: … }, so the payload you want is job.result.data when that key is present. Unwrap it rather than assuming a shape.

Concurrency and admission

Restarts, and why a failed render is not retried for free

Jobs are leased. A worker stamps ownership when it picks a job up and renews it while working; if a process dies, another adopts the job only once the lease has genuinely expired. That is what stops two instances rendering — and billing — the same job twice.

Recovery is deliberately selective. Pure render jobs are re-queued after an interrupted restart. Publishing jobs are not, ever: replaying a publish would post twice, and a duplicate post is not something an automatic retry may decide to risk.

Publishing idempotency

A publish can succeed on the platform and still fail to reach you — a transport timeout, a dropped connection, a crashed agent. A caller that retries then double-posts; a caller that trusts the error tells the user their live post failed. Neither is acceptable, so publishing runs through an idempotency layer.

The claim is written before the vendor call

Not after. The window where this goes wrong is the upload, so a claim written afterwards protects nothing.

Two ways to be covered

HowWindowUse it when
You pass an idempotencyKey24 hoursYou control the retry. The key is your promise that these two calls are the same publish, so no time limit is needed.
Nothing — a fingerprint is derived10 minutesAutomatic. Two identical posts 90 seconds apart are a retry; two identical posts a day apart are a legitimate re-post, and the window is what tells them apart.

Four outcomes

VerdictWhat happens
proceedNothing matched. The publish runs.
replayThis already succeeded. You get the original result and nothing is posted again.
inflightThe first attempt is still running. 409, nothing posted — ask again shortly.
unknownAn earlier attempt's outcome could not be determined. 409, reported as neither success nor failure.

What releases a claim, and what keeps it

The asymmetry is the whole design: when we cannot tell, we assume the irreversible thing happened.

If you have checked the platform yourself and know the post did not go out, allowDuplicate is the stated escape hatch. It is the only one, on purpose.

Long publishes

post_to_meta takes async: true, which routes the publish through the durable queue so a large video upload has no transport timeout to hit. Raising a timeout is not a fix — it moves the cliff.