Skip to main content
The Subagent Tool lets a parent agent spawn specialised subagents for specific tasks — synchronously, or in the background so the parent keeps working and collects the result later.
from praisonaiagents import Agent
from praisonaiagents.tools.subagent_tool import create_subagent_tool

parent = Agent(
    name="Coordinator",
    instructions="Delegate exploration to subagents when needed.",
    tools=[create_subagent_tool(default_permission_mode="plan")],
)

parent.start("Explore the auth module read-only")
The user asks the coordinator; the parent spawns a subagent with the requested permissions.

Quick Start

1

Create Subagent Tool

from praisonaiagents.tools.subagent_tool import create_subagent_tool

tool = create_subagent_tool()
2

Spawn a Subagent

func = tool["function"]
result = func(task="Analyze the authentication module")

if result["success"]:
    print(result["output"])
3

With Model and Permission Mode

tool = create_subagent_tool(
    default_llm="gpt-4o-mini",
    default_permission_mode="plan"
)

func = tool["function"]
result = func(
    task="Explore the codebase",
    llm="gpt-4o",  # Override model
    permission_mode="plan"  # Read-only mode
)

Configuration Options

from praisonaiagents.tools.subagent_tool import create_subagent_tool

tool = create_subagent_tool(
    agent_factory=my_factory,      # Custom agent factory
    allowed_agents=["explorer"],   # Restrict agent types
    max_depth=3,                   # Maximum nesting depth
    default_llm="gpt-4o-mini",     # Default LLM model
    default_permission_mode="plan" # Default permission mode
)
ParameterTypeDefaultDescription
agent_factoryCallableNoneCustom function to create agents
allowed_agentsList[str]NoneRestrict which agent types can be spawned
max_depthint3Maximum subagent nesting depth
default_llmstrNoneDefault LLM model for subagents
default_permission_modestrNoneDefault permission mode

Spawn Parameters

When calling the subagent function:
func = tool["function"]
result = func(
    task="Your task description",
    agent_name="explorer",
    context="Additional context",
    tools=["read_file", "search"],
    llm="gpt-4o",
    permission_mode="plan"
)
ParameterTypeRequiredDescription
taskstr✅ YesTask description for the subagent
agent_namestrNoSpecific agent type to use
contextstrNoAdditional context for the task
toolsList[str]NoTools to give the subagent
llmstrNoLLM model override
permission_modestrNoPermission mode override
backgroundboolNoWhen True, enqueue on the background job runner and return immediately with a job_id
deliverstrNoDelivery target for a background job’s result — e.g. "origin", "all", or "telegram:12345". Only meaningful with background=True. Empty (default) keeps jobs pull-only.
platformstrNoOrigin platform (e.g. "telegram") — captured for deliver="origin". Usually supplied by the gateway context automatically.
chat_idstrNoOrigin chat/channel ID — captured for deliver="origin".
thread_idstrNoOptional origin thread ID.
session_idstrNoOptional origin session ID to preserve context.

Deliver Background Job Results Back to Chat

When a background job finishes, its result can be pushed back to the user’s chat automatically — no polling required.
from praisonaiagents.tools.subagent_tool import create_subagent_tool

tool = create_subagent_tool()
spawn = tool["function"]

handle = spawn(
    task="Run a full analysis of the repository",
    background=True,
    deliver="origin",
)

Delivery targets

TargetBehaviour
"" (empty, default)Pull-only — use subagent_result(job_id) to collect
"origin"Deliver to the chat that started the job (requires parent to have an origin)
"all"Deliver to all channels registered on the gateway
"platform:chat_id"Deliver to a specific platform/channel (e.g. "telegram:123456")
"platform:chat_id:thread_id"Deliver to a specific thread
deliver="origin" requires the parent to have an origin context — inside a BotOS chat it does. From a plain script with no active chat context, the pull-only default (deliver="") applies. Failures are also delivered as a failure notice, so the user is never left waiting silently.
The deliver vocabulary mirrors the scheduling tool’s deliver= parameter. See JOB_COMPLETED Hook for the hook that fires on completion. Backward compatibility: with no deliver target set, behaviour is byte-for-byte identical to before — pull-only via subagent_result.

Background Mode

Run a long subtask without blocking the parent agent. Use it for full test runs, broad codebase scans, or large refactors — anything where the parent agent should keep working instead of waiting.
1

Kick off in the background

from praisonaiagents.tools.subagent_tool import create_subagent_tool

tool = create_subagent_tool()
spawn = tool["function"]

handle = spawn(
    task="Run the full test suite",
    background=True,
)
job_id = handle["job_id"]
2

Keep working, then collect the result

collect = tool["result_tool"]["function"]

# Non-blocking poll — returns status="running" or status="completed"
status = collect(job_id)

# Or block until done
final = collect(job_id, wait=True)
if final["status"] == "completed":
    print(final["result"]["output"])
3

Handle failures cleanly

result = collect(job_id, wait=True)

if not result["success"]:
    print("Job failed:", result.get("error"))
elif result["result"]["success"] is False:
    print("Subagent error:", result["result"]["error"])
else:
    print(result["result"]["output"])

When to use background mode

Background return shapes

spawn_subagent(..., background=True) returns a handle (not a final result):
{
    "success": True,
    "job_id": "...",
    "status": "running",
    "agent_name": "subagent",
    "task": "...",
    "llm": "...",
    "permission_mode": "...",
}
subagent_result(job_id) polls without blocking:
# While running
{"success": True, "job_id": "...", "status": "running"}
subagent_result(job_id, wait=True) blocks until done:
# On completion — the subagent's normal result dict is under "result"
{
    "success": True,
    "job_id": "...",
    "status": "completed",
    "result": {"success": True, "output": "...", "agent_name": "...", ...},
}

# On failure
{"success": False, "job_id": "...", "status": "failed", "error": "..."}

# Unknown job_id
{"success": False, "job_id": "...", "error": "Job '...' not found"}
Depth (max_depth), allowed_agents, and per-call tools / llm / permission_mode apply to background subagents identically to synchronous ones. Background jobs run on a worker thread but inherit the same scoping the parent set up.

Model Selection

Model selection priority:
  1. Per-call llm parameter - Highest priority
  2. default_llm from tool creation - Fallback
  3. Agent’s default model - Final fallback
# Set default for all subagents
tool = create_subagent_tool(default_llm="gpt-4o-mini")

# Override for specific call
result = func(task="Complex analysis", llm="gpt-4o")

Permission Modes

ModeValueDescription
defaultStandardNormal permission checking
accept_editsAuto-acceptAuto-accept file edits
dont_askAuto-denyAuto-deny all prompts
bypass_permissionsBypassSkip all checks
planRead-onlyExploration mode only
# Read-only exploration
tool = create_subagent_tool(default_permission_mode="plan")

# Auto-accept for refactoring
result = func(
    task="Refactor the utils module",
    permission_mode="accept_edits"
)

Custom Agent Factory

Create agents with custom configurations:
from praisonaiagents import Agent

def my_agent_factory(name, tools=None, llm=None):
    return Agent(
        name=name,
        instructions=f"You are a {name} agent",
        tools=tools or [],
        llm=llm or "gpt-4o-mini"
    )

tool = create_subagent_tool(
    agent_factory=my_agent_factory,
    default_llm="gpt-4o-mini"
)

Depth Limiting

Prevent infinite subagent recursion:
# Limit to 2 levels of nesting
tool = create_subagent_tool(max_depth=2)

# First subagent can spawn another
# Second subagent cannot spawn more
The default max_depth=3 prevents runaway subagent spawning. Increase with caution.

Agent Restrictions

Limit which agent types can be spawned:
tool = create_subagent_tool(
    allowed_agents=["explorer", "reviewer"]
)

# This works
result = func(task="Explore code", agent_name="explorer")

# This fails
result = func(task="Write code", agent_name="coder")
# Returns: {"success": False, "error": "Agent 'coder' not in allowed list"}

Result Structure

Synchronous calls (background=False, the default) return the final result directly:
result = func(task="Analyze code")

# Success response
{
    "success": True,
    "output": "Analysis results...",
    "agent_name": "subagent",
    "task": "Analyze code",
    "llm": "gpt-4o-mini",
    "permission_mode": "plan"
}

# Error response
{
    "success": False,
    "error": "Error message",
    "output": None
}
Background calls return a handle — see Background Mode for subagent_result shapes.

Best Practices

Use smaller models like gpt-4o-mini for simple tasks and larger models for complex analysis.
Always set permission_mode="plan" for exploration tasks to prevent accidental modifications.
Restrict allowed_agents to only the agent types needed for your use case.
Always check result["success"] before accessing the output.
For test suites, broad scans, or refactors, use background=True so the parent agent can keep working and collect results with subagent_result.

Subagent Delegation

Advanced subagent management

Permission Modes

Permission mode details

JOB_COMPLETED Hook

Hook that fires when a background job finishes — success or failure