Run agent tasks and recipes asynchronously in the background
Run agent tasks and recipes in the background without blocking your main thread.
from praisonaiagents import Agentagent = Agent( name="AsyncAssistant", instructions="Research in the background while the user keeps working.", background=True,)agent.start("Summarise today's news.")
The user submits long-running work; the background runner executes it concurrently while the main thread continues.
# List all tasksfor task in runner.tasks: print(f"{task.name}: {task.status.value}")# Get running tasksrunning = runner.running_tasks# Get pending taskspending = runner.pending_tasks# Clear completed tasksrunner.clear_completed()
For simpler use cases, use BackgroundJobManager for synchronous job management:
from praisonaiagents.background.job_manager import BackgroundJobManager, JobStatus# Create manager with auto-background thresholdmanager = BackgroundJobManager(auto_background_threshold=5.0)# Start a jobjob_id = manager.start_job(lambda: expensive_computation())# Check statusstatus = manager.get_status(job_id)if status == JobStatus.COMPLETED: result = manager.get_result(job_id)elif status == JobStatus.FAILED: error = manager.get_error(job_id)# List all jobsfor job_id, info in manager.list_jobs().items(): print(f"{job_id}: {info.status}")# Cancel a running jobmanager.cancel_job(job_id)
Need jobs to survive a process restart? Pass a store= and call reconcile_on_start() β see Durability below.
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)
from praisonaiagents.background.job_manager import BackgroundJobManager# Your app supplies a store that implements the BackgroundJobStore protocolmanager = BackgroundJobManager(store=my_store)
2
Reconcile once at startup
Call reconcile_on_start() once at boot, after wiring the deliver-back handler:
counts = manager.reconcile_on_start(redeliver=on_job_complete)# {"lost": 0, "redelivered": 0, "rehydrated": 0} on a clean start
Terminal state for RUNNING/PENDING jobs interrupted by a crash
JobInfo.delivered
field (bool, default False)
Whether the deliver-back has fired
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.
Submit an Agent task from synchronous code. Resolves the agentβs callable (start β chat β run) automatically.
from praisonaiagents import Agentfrom praisonaiagents.background.runner import BackgroundRunneragent = Agent(name="researcher", instructions="Research AI trends")runner = BackgroundRunner()task = runner.submit_agent_sync( agent=agent, prompt="What are the top AI trends in 2026?", name="research-task")# task.result will contain the agent's response when done
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.
from praisonaiagents import Agentfrom praisonaiagents.tools import schedule_add, schedule_list, schedule_removefrom praisonaiagents.scheduler import ScheduleLoopfrom praisonaiagents.background.runner import BackgroundRunneragent = Agent( name="assistant", instructions="You can set reminders and schedules.", tools=[schedule_add, schedule_list, schedule_remove],)runner = BackgroundRunner()def on_schedule_fire(job): task = runner.submit_agent_sync(agent, job.message, name=f"schedule-{job.name}") print(f"β Background task {task.id} started for '{job.name}'")loop = ScheduleLoop(on_trigger=on_schedule_fire, tick_seconds=30)loop.start()# Agent creates schedules that now actually fire!agent.start("Remind me to check email every morning at 7am")
Parameter
Type
Default
Description
on_trigger
Callable
required
Called with each due ScheduleJob
store
FileScheduleStore
Auto-created
Schedule store to poll
tick_seconds
float
30.0
Poll interval in seconds
Method
Description
loop.start()
Start polling (no-op if already running)
loop.stop(timeout=5.0)
Signal stop and wait for thread exit
loop.is_running
Whether the daemon thread is alive
Error handling: If on_trigger() raises an exception, itβs logged but not propagated β the loop continues with remaining jobs and future ticks.
The background module uses lazy loading β no overhead when not used:
# Only loads when accessedfrom praisonaiagents.background import BackgroundRunner# Sync loop thread only created on first submit_sync() callimport praisonaiagents.background.runner as brassert br._bg_loop is None # Not created until needed