Background work is reliable only when intent and outcome are durable, delivery can repeat safely, retries are bounded, poison messages are isolated, and operators can inspect or replay failures. A queue transports work; PostgreSQL job and AI-run records tell the product truth. Server-sent events can project progress but never replace that truth.
Move work when it exceeds request latency budgets, is CPU intensive, needs independent concurrency, can retry after client disconnect, or fans out. Tiny post-response tasks may use FastAPI background tasks, but they are process-local and should not own important business work.
Create a durable job record:
Record tenant, type, versioned payload reference, idempotency key, attempt, maximum attempts, schedule time, lease owner/expiry, progress, outcome, trace, and timestamps. Avoid placing secrets or large documents directly in queue messages.
Use an outbox if the database commit and queue publish must agree. A broker can be Redis-backed, PostgreSQL-backed, or managed; choose from guarantees and operations, not fashion.
Persist coarse progress events or sequence numbers. An SSE endpoint authenticates the requester, verifies job ownership, sends bounded events with IDs, heartbeats periodically, and resumes after Last-Event-ID. If the stream fails, GET /v1/jobs/{id} remains authoritative.
Document ingestion, batch evaluation, long generations, and tool workflows are natural jobs. Track monetary/token budgets and provider request IDs per attempt. Cancellation stops future application work, but a provider may still finish or charge; reconcile late outcomes without overwriting terminal policy decisions.
Design the knowledge-ingestion job and an SSE progress endpoint. Simulate a crash after provider success but before acknowledgment, duplicate delivery, cancellation, and a poison document.
Acceptance criteria: no duplicate published chunks or charges are accepted as new outcomes, terminal states are monotonic, poison work is isolated, and clients recover without an open stream.