OutlabsTaskq
Concepts

Fencing and settles

Attempt IDs, typed settle races, and handler return values.

Every claim creates a server-generated attempt_id. Heartbeat and settle verbs CAS on (job_id, attempt_id).

Settle results

ResultMeaning
okTransition applied
already_settledSame verb already finished this attempt — treat as success on retry
settle_conflictDifferent verb against an already-settled attempt
lostFence lost (another attempt owns the job)
retry_scheduledFailure accepted; job re-queued with backoff
deadTerminal failure

A dropped HTTP response after a successful settle is no longer indistinguishable from theft: retrying the same verb returns already_settled.

Handler return values

Handlers return typed results; the worker maps them to SQL:

ReturnFieldsBurns attempts?
Completeresult: dict, optional followupsno
Snoozedelay_seconds: int, optional progress, reasonno
Cancelreason: strno
Retryoptional after_seconds, error, progressyes
NonRetryableerror: str, optional progressterminal
Non-empty Complete.followups are rejected by the 0.1 worker/SQL (followup_rejected). The field is reserved for 0.2.
from taskq import Complete, Snooze

async def scrape(payload: Input) -> Complete | Snooze:
    if quota_hit():
        return Snooze(delay_seconds=3600, reason="provider_quota")
    data = await do_scrape(payload)
    return Complete(result={"n": len(data)})