Skip to main content
Durable background jobs persist every state transition so a crash mid-job no longer silently drops the work or the promised deliver-back — orphans become a queryable LOST state and undelivered results are replayed on the next boot. Durability is on by default: the shared manager returned by get_job_manager() — the same one the chat /tasks command and background subagent deliver-backs use — automatically persists to <runs_dir>/background_jobs.db and reconciles on boot. Opt out with PRAISONAI_BACKGROUND_JOB_STORE=0 for the prior pure in-memory behaviour. Three ways to reach durability — pick the row that matches your setup.

Quick Start

1

You already have durability

The shared manager returned by get_job_manager() is already backed by SqliteBackgroundJobStore and has already reconciled orphaned jobs on first construction — zero code, zero setup:
2

Enable persistence on a custom manager

Building your own BackgroundJobManager? Pass store=SqliteBackgroundJobStore() (or any store implementing the BackgroundJobStore protocol). Every job transition is now persisted:
3

Reconcile on startup

get_job_manager() already ran this once. On a custom manager, call reconcile_on_start() once at boot, after wiring the deliver-back handler:
4

Query a job after a restart

Status lookups work after a restart — even before reconcile_on_start() runs, get_status falls through to the store:
5

Handle LOST jobs

Inspect orphaned jobs and decide whether to retry or surface them to the user — abandoned work is never auto-re-run:

How It Works

reconcile_on_start() reads store.list_unreconciled() and follows two paths: orphaned jobs become LOST; completed-but-undelivered jobs are replayed. Every persisted job is re-hydrated into the in-memory map so get_status(job_id) keeps working after a restart. redeliver=None is safe — undelivered jobs are re-hydrated but not delivered.

State machine

LOST is a new terminal state reached only via restart reconciliation.

The BackgroundJobStore Protocol

Implement this @runtime_checkable protocol to plug in your own durable store. A concrete SQLite implementation — SqliteBackgroundJobStore — now ships in core (praisonaiagents.background) and backs get_job_manager() by default; you only implement the protocol yourself for a non-SQLite backend.
The runner calls upsert() from its worker threads — your store implementation must be thread-safe.

Built-in SqliteBackgroundJobStore

SqliteBackgroundJobStore is the concrete, stdlib-only store that backs get_job_manager() by default. Import it, or omit db_path to use the default location:

Schema

A single background_jobs table with an index on status: Index: idx_background_jobs_status on status.

Thread-safety & durability posture

A single re-entrant lock guards one shared connection opened check_same_thread=False — safe to share across the runner’s worker threads. Durability matches its siblings SQLiteRunLedger and SqliteEventLog: WAL journal mode, busy_timeout=5000, synchronous=NORMAL.

Configuration

Environment variable

Reconciliation counts

reconcile_on_start() returns a counts dict — use it for a single startup log line:

Best Practices

Run it once at boot, after all deliver-back handlers are wired — mirroring how OutboundQueue.drain_pending() is wired. Calling it later risks a partially-wired redeliver.
redeliver receives the persisted JobInfo. A transient failure inside it means “retry on the next boot” — the job is left undelivered, never silently lost. Design the handler so a double-fire is harmless.
LOST is age-evictable by cleanup_completed(max_age=...). Run a periodic sweep so reconciled orphans don’t accumulate across restarts.
upsert() is called from BackgroundJobManager’s worker threads. Guard shared state (or use a per-thread connection) so concurrent transitions don’t corrupt the store.
For the shared get_job_manager() the escape hatch is the environment variable, not the constructor kwarg — set PRAISONAI_BACKGROUND_JOB_STORE=0 to get pure in-memory behaviour (no store, no reconcile). When you construct BackgroundJobManager yourself, omitting store= keeps that manager in-memory with zero overhead.

Background Tasks

The synchronous job manager and background runner patterns.

Background Subagents

Spawn subagents that deliver results back to chat when done.

Durable Delivery

Persist outbound bot messages with retry and crash-safe drain.

Hook Events

Subscribe to JOB_COMPLETED and other lifecycle events.