Skip to main content
A parent agent can fire off one or more subagents and continue working. Each background subagent returns a job_id immediately — results can be pulled on demand or delivered automatically back to the originating chat.
The user asks for parallel research; background subagents return job IDs while the parent keeps working.

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 inside BotOS, 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 by BackgroundJobManager. Job IDs are 8-character random strings. Results are kept in memory until retrieved.

How Delivery is Wired

When deliver is set, the job pushes its result when it reaches a terminal state — no polling needed. Core layer (praisonaiagents) — lightweight signal:
  • HookEvent.JOB_COMPLETED fires 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_subagent captures origin context into the job and wires the injected on_job_complete.
Wrapper layer (praisonai) — delivery orchestration:
  • BotOS.on_background_job_complete(job_info) resolves the deliver target and routes the success summary or failure notice through the same DeliveryRouter the scheduler uses.
  • With a BackgroundJobStore configured, on_complete may 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

Failure delivery: On failure, a short notice is delivered to the same target instead of the full result. HookEvent.JOB_COMPLETED fires for both terminal states. The internal on_complete callback and hook handlers are best-effort — a raised exception is swallowed and logged at DEBUG so the worker never crashes.

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

Use JOB_COMPLETED hook for observability — fires on both success and failure after the completion callback runs:
See Hook Events for the full reference.

Best Practices

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.
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.
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.
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.
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.
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.

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