job_id immediately — results can be pulled on demand or delivered automatically back to the originating chat.
Quick Start
Spawn a background subagent
Call
spawn_subagent with background=True. The parent gets a job_id and keeps running: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).Check status without blocking
Poll a job without waiting — useful when you want to check periodically:
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 is lost if the process restarts mid-job. Wire a
BackgroundJobStore and call reconcile_on_start() at boot to survive restarts — see Background Tasks → Durability.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.- With a
BackgroundJobStoreconfigured,on_completemay be 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. Keep the handler idempotent. See Durable Background Jobs.
Delivery Tokens
| Token | Meaning |
|---|---|
"" (empty) | Pull-only via subagent_result (unchanged legacy behaviour) |
"origin" | Deliver back to the platform/chat id captured at spawn |
"all" | Broadcast to all registered channels |
"platform:chat_id" | Deliver to a specific channel (e.g. "telegram:12345") |
"platform:chat_id:thread_id" | Deliver to a specific thread or reply context |
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
| Parameter | Type | Default | Description |
|---|---|---|---|
task | str | required | The task or prompt for the subagent |
agent_name | str | "subagent" | Name for the spawned agent |
llm | str | None | LLM model override |
permission_mode | str | None | Permission mode for the subagent |
tools | list | [] | Tools to give the subagent |
background | bool | False | When True, return immediately with a job_id |
deliver | str | "" | Delivery token: "origin", "all", "platform:chat_id[:thread_id]", or "" (pull-only) |
platform | str | "" | Origin platform (e.g. "telegram", "slack"). Injected automatically by BotOS; required for deliver="origin" outside BotOS. |
chat_id | str | "" | Origin chat/channel id. Required for deliver="origin" outside BotOS. |
thread_id | str | "" | Optional origin thread id. |
session_id | str | "" | Optional origin session id to preserve context. |
output, success, error.
Returns (background): {"success": True, "job_id": "...", "status": "running"}.
subagent_result
| Parameter | Type | Default | Description |
|---|---|---|---|
job_id | str | required | The job_id returned by spawn_subagent(background=True) |
wait | bool | False | When True, block until the job completes |
- 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
| Token | Meaning |
|---|---|
"" (empty) | Pull-only via subagent_result (unchanged legacy behaviour) |
"origin" | Deliver back to the platform/chat_id captured at spawn |
"all" | Broadcast to all registered channels |
"platform:chat_id" | Deliver to a specific channel (e.g. "telegram:12345") |
"platform:chat_id:thread_id" | Deliver to a specific thread/reply-context |
Subscribe to Job Completion
UseJOB_COMPLETED hook for observability — fires on both success and failure after the completion callback runs:
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.Related
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

