FastAPI’s BackgroundTasks runs in the same process as your request handler. If the worker dies or restarts, the task disappears without a retry, a schedule, or a record of the run.
BackgroundTasks is built into FastAPI. It schedules a function to run after the response is sent, in the same process.
from fastapi import BackgroundTasks, FastAPI app = FastAPI() def send_report(user_id: str): rows = db.query( "SELECT * FROM orders WHERE user_id = %s", user_id, ) report = generate_pdf(rows) smtp.send(to=user_id, attachment=report) @app.post("/reports/{user_id}") async def request_report( user_id: str, tasks: BackgroundTasks, ): tasks.add_task(send_report, user_id) return {"status": "queued"}
BackgroundTasks is designed for trivial post-response work. Once the job needs to survive a restart or run on a schedule, the limits are concrete.
A Task is an AI automation written in Python, hosted on a managed runtime. A developer writes the script; anyone runs it from a form. Integrations, model calls, version control, and logging are built in.
Model and provider agnostic.
A qbash task runs on a managed runtime, triggered by a webhook, a cron schedule, or an API call. It survives restarts, retries on failure, and logs every run.
user = aisle.inputs.get("user_id") rows = aisle.integrations.postgres.execute_query( "SELECT * FROM orders WHERE user_id = %(id)s", params={"id": user}, ) report = aisle.ai.run_prompt( slug="order-summary", variables={"orders": rows}, ) aisle.integrations.gmail.send_email( to=user, subject="Your order report", body=report["summary"], )
Runs survive restarts, failures retry from where they left off, and every execution is logged.
aisle.parallel records the items that finished. A retry resumes at the failure, so it does not pay again for model calls that already returned and does not re-fire side effects that already happened.
result = aisle.parallel( process_order, orders, concurrency=5, max_per_minute=60, checkpoint="order-backfill", retry=3, )
aisle.run.last_run_at tells you when the task last ran, so each execution processes only what is new. No state table to maintain.
since = aisle.run.last_run_at new_orders = aisle.integrations.postgres.execute_query( "SELECT * FROM orders WHERE created_at > %(since)s", params={"since": since}, ) for order in new_orders: aisle.integrations.pipedrive.create_deal( title=order["name"], value=order["amount"], )
Send results to Slack, email, a CRM field, or a database. 45+ integrations called in one line, with credentials brokered server-side.
aisle.integrations.slack.create_message( "#reports", text=summary, ) url = aisle.files.download_url(report_file) aisle.integrations.gmail.send_email( to="[email protected]", subject="Daily report", body=f"Download: {url}", )
Celery works, but it adds Redis or RabbitMQ as a broker, workers to keep alive, and retry logic to hand-roll. If the background work involves model calls, integrations, or file processing, a qbash task handles the runtime, the retries, and the provider bindings in one place.
Yes. Each task has an API endpoint. Call it over HTTP from your request handler, the same way you would use BackgroundTasks, but the task runs on a separate, durable runtime.
No. qbash runs tasks, not web applications. Your FastAPI app stays where it is. Background work that needs to survive a restart, retry on failure, or run on a schedule moves to a task.
Up to 12 hours. Each run is logged with inputs, output, errors, and duration.
No server to provision, patch, or monitor. Saving the task deploys it. The runtime, the interpreter, and the upgrades are the platform’s responsibility.
Competitor details reviewed . Vendors change plans and features without notice, so check theirs before deciding.
Open the editor, write a script against your connected accounts, and put it on a trigger. Or describe it, and the builder drafts the task as code you edit.