Background tasks Run heavy or slow work outside the request/response cycle: a request returns immediately and the work happens later, in the background, with retries and de-duplication. Use it for anything that would blow the request budget or that should survive the response: processing an upload, calling an external API, an ingestion pipeline, sending a batch of emails. A task runs in an ephemeral isolate: the platform loads your app's code, runs one handler, and tears it down. The app does not have to be serving HTTP for a task to run. Declare handlers with withTasks Wrap your default HTTP handler with withTasks and pass a map of named task handlers. The default export stays your request handler; onvibe reads the task handlers from it. import { enqueueTask, withTasks } from "./.onvibe/helpers.ts"; async function handler(req: Request): Promise { const url = new URL(req.url); if (req.method === "POST" && url.pathname === "/import") { const { fileId } = await req.json(); const { taskId } = await enqueueTask("process_import", { fileId }, { dedupKey: import:${fileId}, }); return Response.json({ taskId }, { status: 202 }); } return new Response("hello"); } export default withTasks(handler, { // Each handler gets the payload you passed to enqueueTask. Keep it IDEMPOTENT: // a task can run more than once on retry; the source of truth is your database. process_import: async ({ fileId }) => { const res = await fetch(https://api.example.com/files/${fileId}); // external fetch is allowed // ...heavy work, write results to your Postgres... return { processed: true }; }, }); Enqueue work with enqueueTask const { taskId, deduplicated } = await enqueueTask("process_import", { fileId }, { dedupKey: "import:42", // optional: won't enqueue a second task while one with this key is active maxAttempts: 3, // optional: retries on failure (exponential backoff) }); - enqueueTask(name, data?, opts?) returns { taskId, deduplicated }. name must match a handler registered in withTasks. - De-dup is scoped to your app: dedupKey never collides with other apps. - Enqueue from anywhere in your handler — a request, a cron, or even another task. Check status with checkTask const status = await checkTask(taskId); // null if no such task, else: // { status: "pending" "running" "done" "failed", result?: unknown, error?: string } if (status?.status === "done") { // status.result is whatever the task handler returned } Notes - Idempotency is your job: a task may run more than once (retries, timeouts). Make handlers safe to re-run; keep the real state in your database. - Retries: failures retry with exponential backoff up to maxAttempts (default 3), then the task is failed (its error is in checkTask().error). - External network: task handlers can fetch the public internet (RSS, third-party APIs, LLMs). - Timeouts: a task that runs too long is considered stuck and retried. - Tasks vs cron: cron fires your normal handler on a schedule (see scheduled tasks); tasks run a withTasks handler you enqueue on demand, off the request path.