Skip to main content
Run agent tasks and recipes in the background without blocking your main thread.
The user submits long-running work; the background runner executes it concurrently while the main thread continues.

Quick Start

1

Agent with background runner

2

Recipe in the background

3

Async callers (FastAPI / Jupyter)

Inside a running event loop (a FastAPI handler, a Jupyter cell, or any async def), use arun_background — the sync run_background raises RuntimeError there instead of deadlocking:
Do not call recipe.run_background() from within a running event loop — it raises RuntimeError pointing you to arun_background. Use the sync form only from plain synchronous code.

Features

  • Async Execution: Run tasks without blocking
  • Concurrency Control: Limit concurrent tasks
  • Progress Tracking: Monitor task status
  • Timeout Support: Set execution time limits
  • Cancellation: Cancel running tasks

Configuration

Task Status

In-session /tasks command

Inspect and cancel background tasks from inside a praisonai code session or a bot chat — no need to Ctrl-C and run praisonai background list. Type /tasks after kicking off a background=True run to check on it without leaving the conversation:
The REPL, bots, and CLI all read the same shared runner, so a task appears wherever you look for it.
In bot chats, /tasks is per-user: a caller can only see and cancel tasks whose metadata["user_id"] matches their bot user id. Tasks submitted from the CLI or REPL without a user_id are not exposed to bot users — fail-closed by design.
The same command works in Telegram, Slack, and Discord — see Bot Chat Commands.

Common Pattern: check on a background run from anywhere

1

Kick off a background task in praisonai code

Ask the agent for a long research task with background=True. The agent replies with a task id and keeps chatting.
2

Peek a few minutes later

Type /tasks — the research shows as running at 60%. Type /tasks <id> for the current progress detail.
3

Switch to your Telegram bot

Type /tasks there. Because bot /tasks is per-user scoped, you see only the tasks you submitted from Telegram — the REPL ones stay hidden.
4

Cancel when no longer needed

/tasks cancel <id> — the task actually stops (its underlying future is cancelled, not just the record marked).

Shared runner accessor

Every inspection surface resolves the same process-wide runner through get_background_runner().
/tasks sees the same tasks regardless of which surface submitted them. BackgroundRunner is still available directly for advanced users who need a private runner.

CLI Usage

Safe Defaults


Low-level API Reference

BackgroundRunner Direct Usage

Submitting Tasks

Task Management

Synchronous Job Manager

For simpler use cases, use BackgroundJobManager for synchronous job management:
Need jobs to survive a process restart? Pass a store= and call reconcile_on_start() — see Durability below.

Durability — survive a restart

An in-memory-only runner drops every in-flight job on a restart, including the promised deliver-back — pass a store= to persist jobs and recover them on boot. Durability is opt-in: omit store= and behaviour is byte-for-byte as before (pure in-memory, zero overhead); supply a store and every state transition is persisted.
1

Enable persistence (opt-in)

2

Reconcile once at startup

Call reconcile_on_start() once at boot, after wiring the deliver-back handler:

New API surface

reconcile_on_start(redeliver) passes the persisted JobInfo to your redeliver callback — make it idempotent, since a raised exception means “retry on the next restart” (the job is left undelivered, never lost).
LOST jobs are age-evictable by cleanup_completed(max_age=...), so reconciled orphans don’t leak across restarts.

Architecture

The sync wrappers and ScheduleLoop bridge the gap between disconnected modules:
  • submit_sync() / submit_agent_sync() — let sync code submit background tasks without asyncio boilerplate
  • ScheduleLoop — polls for due jobs on a daemon thread and fires your callback

Sync Wrappers

For sync code (scripts, bot handlers, Agent.start() callbacks), use the sync-friendly methods that handle asyncio automatically:

submit_sync()

Submit any callable from synchronous code. A daemon event loop thread is created lazily on first call.

submit_agent_sync()

Submit an Agent task from synchronous code. Resolves the agent’s callable (startchatrun) automatically.

cancel_task_sync()

Cancel a task from sync code or an unrelated event loop. It hops threads via run_coroutine_threadsafe, so it’s safe to call from within a running event loop and actually stops the underlying future.
Terminal-branch guarantee (PR #3883): on_complete(task) fires on every terminal branch — success, generic exception, asyncio.TimeoutError, and asyncio.CancelledError. Before #3883 it did not fire on timeout or cancel, so callers writing subscribers had to poll .status to detect those. That workaround is no longer needed. Inspect task.status, task.error, and task.result inside your callback to distinguish outcomes.
Cancellation still re-raises asyncio.CancelledError for cooperative shutdown — but only after on_complete fires.

ScheduleLoop

ScheduleLoop bridges scheduled jobs to actual execution. It runs a daemon thread that polls get_due_jobs() and fires your callback. To skip ticks cheaply before the model turn runs, see pre_run in Schedule Tools.

Combined Example: Scheduler + Background

ScheduleLoop is the default SchedulerProviderProtocol — the in-process poll thread, also exported as InProcessScheduleProvider. For event-driven / serverless firing (webhook, systemd timer, cron, K8s CronJob), see Scheduler Providers.
Error handling: If on_trigger() raises an exception, it’s logged but not propagated — the loop continues with remaining jobs and future ticks.

Zero Performance Impact

The background module uses lazy loading — no overhead when not used:

Best Practices

Set BackgroundConfig(max_concurrent_tasks=...) to match CPU and API rate limits — unbounded parallelism can exhaust tokens or file handles.
Background work can hang on tool calls; pass timeout= so your main loop can cancel or retry instead of blocking forever.
Fire scheduled jobs into BackgroundRunner.submit_agent_sync so reminders run without blocking the scheduler thread.
When callers are external services, prefer the Async Jobs server instead of embedding BackgroundRunner in app code.

Async Jobs

HTTP API for submitting and polling long-running agent jobs.

Schedule Tools

Let agents create reminders that trigger background runs.

Bot /tasks Command

Inspect and cancel background tasks from Telegram, Slack, Discord — per-user scoped.