job_id immediately — results can be pulled on demand or delivered automatically back to the originating chat.
Every background subagent still emits
HookEvent.SUBAGENT_STOP on completion (success or failure) — same as a synchronous spawn. See Observability below.Quick Start
1
Spawn a background subagent
Call
spawn_subagent with background=True. The parent gets a job_id and keeps running:2
Deliver result back to chat automatically
Pass
deliver="origin" when running behind BotOS — the finished result is sent back to the same chat without polling:deliver="origin" only works when the agent is running behind BotOS — the wrapper injects platform/chat_id/thread_id/session_id automatically. In a bare Agent, use deliver="telegram:12345" or leave it empty (pull-only).3
Fan out and collect all results
Spawn multiple subagents in parallel and wait for all of them:
4
Check status without blocking
Poll a job without waiting — useful when you want to check periodically:
5
Deliver result to chat when done
Set
deliver='origin' to push the result back to the originating chat automatically — no polling needed:Chat-Back Delivery Flow
When running insideBotOS, a background job can deliver itself back to the originating chat when it finishes — no polling required.
Results are delivered using the same DeliveryRouter the scheduler uses for scheduled messages — no extra configuration needed beyond the deliver parameter.
By default the deliver-back survives a process restart. The shared
get_job_manager() persists jobs to <runs_dir>/background_jobs.db and replays any undelivered deliver-backs on the next boot — no setup needed. For custom managers or non-SQLite backends see Durable Background Jobs.deliver="origin" only works when the agent is running behind BotOS — the wrapper injects platform, chat_id, thread_id, and session_id automatically. In a bare Agent, use deliver="telegram:12345" or leave it empty (pull-only).How It Works (Pull Mode)
Background subagents run on a shared thread pool managed byBackgroundJobManager. Job IDs are 8-character random strings. Results are kept in memory until retrieved.
How Delivery is Wired
Whendeliver is set, the job pushes its result when it reaches a terminal state — no polling needed.
Core layer (praisonaiagents) — lightweight signal:
HookEvent.JOB_COMPLETEDfires when a job reaches a terminal state (completed or failed).BackgroundJobManager.start_job(..., on_complete=None, origin=None)— accepts an optional completion callback; a raising callback never crashes the worker.spawn_subagentcaptures origin context into the job and wires the injectedon_job_complete.
praisonai) — delivery orchestration:
BotOS.on_background_job_complete(job_info)resolves thedelivertarget and routes the success summary or failure notice through the sameDeliveryRouterthe scheduler uses.- By default (via the shared
get_job_manager()),on_completeis re-fired once on the next boot for any job that completed-but-was-not-delivered before a crash —reconcile_on_start()replays the interrupted deliver-back automatically. Keep the handler idempotent. See Durable Background Jobs.
Delivery Tokens
Subscribe to JOB_COMPLETED
For observability or custom side effects when a background job finishes, register on HookEvent.JOB_COMPLETED (see Hook Events):
Tool Parameters
spawn_subagent
Returns (foreground): Full result dict with
output, success, error.
Returns (background): {"success": True, "job_id": "...", "status": "running"}.
subagent_result
Returns:
- Job still running (
wait=False):{"success": True, "job_id": "...", "status": "running"} - Job done:
{"success": True, "job_id": "...", "status": "completed", "result": {...}} - Job failed:
{"success": False, "job_id": "...", "error": "..."}
Backward compatibility: With no
deliver target, behaviour is byte-for-byte as before — pull-only via subagent_result. The delivery path is purely additive.Delivery Tokens
Subscribe to Job Completion
UseJOB_COMPLETED hook for observability — fires on both success and failure after the completion callback runs:
Observability
TheSUBAGENT_STOP hook fires from the same completion chokepoint for background jobs as for synchronous spawns, so you do not need to poll subagent_result just to observe completion. Register a hook and let the runtime call you.
Best Practices
Don't background trivially fast tasks
Don't background trivially fast tasks
Background subagents have overhead (thread scheduling, job management). For tasks that complete in under a second, use foreground subagents (
background=False, the default) — they’re simpler and return results immediately.Always capture job IDs
Always capture job IDs
The parent agent should record job IDs in its working memory or output immediately after spawning. If a job ID is lost, there is no way to retrieve the result.
Set a concurrency budget
Set a concurrency budget
The
BackgroundJobManager runs on a thread pool. Avoid spawning hundreds of concurrent subagents without throttling — each subagent holds a connection and context window while running.Use wait=True for final collection
Use wait=True for final collection
When you need a result to proceed, always use
subagent_result(job_id, wait=True) rather than polling in a loop. The wait=True mode blocks the calling thread efficiently without spinning.Use deliver='origin' instead of polling for chat bots
Use deliver='origin' instead of polling for chat bots
In a bot context,
deliver='origin' is simpler than polling: the parent can close its turn immediately and the result arrives as a new message when ready — no held connections.Failure delivery behaviour
Failure delivery behaviour
On failure, a short notice is delivered to the same target instead of the result. The
JOB_COMPLETED hook fires for both terminal states (COMPLETED and FAILED). Any exception raised inside on_complete is swallowed and logged at DEBUG level so the worker is never crashed by a handler error.Observability
TheSUBAGENT_STOP hook fires from the same completion chokepoint for background jobs as for synchronous spawns, so you do not need to poll subagent_result just to observe completion. Register a hook and let the runtime call you.
Related
Named Subagents
Delegate to your own named agents by name mid-run — no Python
Hook Events
Subscribe to JOB_COMPLETED and other lifecycle events
Background Tasks
Other background task patterns for long-running work
Proactive Delivery
Push messages to users outside of a turn
Channels Gateway
Configure BotOS for multi-platform bot deployments
Gateway Reliability
Production reliability presets for the gateway

