Agent as Tool

Use agents as callable tools for hierarchical agent composition

Agent as Tool turns any agent into a callable tool so a parent agent can invoke specialists and keep control of the result.

python
from praisonaiagents import Agent

researcher = Agent(name="Researcher", instructions="Research topics thoroughly")
writer = Agent(
    name="Writer",
    instructions="Write polished copy using specialist tools",
    tools=[researcher.as_tool()],
)
writer.start("Write a short post about quantum computing with cited facts")

The user asks for a polished article; the writer agent invokes the researcher as a tool and composes the answer.

Diagram

Agent as Tool

📋 User Request

🤖 Parent Agent

🔧 Specialist as Tool

✅ Composed Answer

Quick Start

  1. Simple Usage
    Basic Usage
    from praisonaiagents import Agent
    
    # Create specialist agents
    researcher = Agent(
        name="Researcher",
        instructions="Research topics thoroughly and return findings"
    )
    
    coder = Agent(
        name="Coder", 
        instructions="Write clean Python code"
    )
    
    # Parent agent uses specialists as tools
    writer = Agent(
        name="Writer",
        instructions="Write technical articles using your tools",
        tools=[
            researcher.as_tool("Research a topic and return findings"),
            coder.as_tool("Write Python code for a given task"),
        ]
    )
    
    # Writer invokes researcher and coder as needed
    result = writer.start("Write an article about async Python patterns")
    Custom Tool Names
    from praisonaiagents import Agent
    
    analyst = Agent(name="DataAnalyst", instructions="Analyze data")
    
    # Custom tool name and description
    tool = analyst.as_tool(
        description="Analyze dataset and return insights",
        tool_name="analyze_data"
    )
    
    coordinator = Agent(
        name="Coordinator",
        tools=[tool]
    )
  2. With Configuration

    Pass custom tool_name and description to as_tool() when the default labels are not clear enough for the parent LLM.

  3. Direct Invocation

    Call the generated tool yourself with execute_tool — the tool exposes an explicit task parameter:

    python
    from praisonaiagents import Agent
    
    coder = Agent(name="Coder", instructions="Write clean Python code")
    writer = Agent(
        name="Writer",
        instructions="Delegate coding to Coder",
        tools=[coder.as_tool(tool_name="invoke_coder")],
    )
    
    # New in PR #4232: the tool exposes an explicit `task` parameter
    writer.execute_tool("invoke_coder", {"task": "write a haiku about async I/O"})

    The generated tool now has (task: str) in its signature — the LLM sees it in the schema and fills it, and direct callers must pass it.

How It Works

Diagram
ResearcherWriterUserResearcherWriterUser“Write a post about quantum computing”invoke_researcher(“quantum computing”)Research findingsPolished article with cited facts
StepWhat happens
1. RequestUser asks the parent agent for a result
2. InvokeParent LLM decides to call the specialist as a tool
3. ExecuteSpecialist agent runs with a clean context
4. ReturnResult is returned to the parent agent
5. RespondParent composes the final response

as_tool() vs Handoffs

Featureas_tool()Handoffs
ControlParent retains controlControl transfers to target
ContextNo history passed (clean slate)Context passed based on policy
Tool naminginvoke_{name}transfer_to_{name}
Context policyContextPolicy.NONE (clean slate)Default ContextPolicy.SUMMARY
Use CaseHierarchical compositionTask delegation
ReturnResult returned to parentTarget continues conversation

If you previously “worked around” the broken as_tool() by switching to handoffs=, note the workaround wasn’t equivalent — invoke_{name} runs with ContextPolicy.NONE (a genuine clean slate, now that context policies take effect) while transfer_to_{name} shares context per its policy (SUMMARY by default). Now that as_tool() works in tools=[...], switch back if you want the clean-slate behaviour.

Diagram

Handoff Pattern

transfer

continues

Source

Target

User

as_tool() Pattern

invoke

result

Parent

Child Tool

API Reference

Agent.as_tool()

python
def as_tool(
    self,
    description: Optional[str] = None,
    tool_name: Optional[str] = None,
) -> Handoff
description str

Tool description for the LLM. Describes what this agent does. Default: "Invoke {agent_name} to complete a subtask and return the result"

tool_name str

Custom tool name. Default: invoke_{agent_name} (snake_case)

ResponseField Handoff

A Handoff configured with ContextPolicy.NONE (no history passed to child).

The return type is Handoff (not a callable) because to_tool_function() needs the parent agent that will own this tool — and at as_tool() call time, that parent doesn’t exist yet. Conversion happens automatically the moment you place the result inside a parent’s tools=[...].

Generated tool signature

Because as_tool() always pins ContextPolicy.NONE, the generated callable exposes a task: str keyword-only parameter. This is what the LLM tool schema advertises and what direct execute_tool calls must supply.

python
def invoke_<agent_name>(*, task: str) -> str
task str

The prompt passed verbatim to the sub-agent. If task is not provided, prompt is accepted as an alias.

Examples

Research + Writing Pipeline
python
from praisonaiagents import Agent

# Specialist agents
researcher = Agent(
    name="Researcher",
    instructions="Search the web and compile research findings"
)

fact_checker = Agent(
    name="FactChecker", 
    instructions="Verify facts and cite sources"
)

# Writer uses both as tools
writer = Agent(
    name="Writer",
    instructions="""Write articles using your tools:
    - Use invoke_researcher for initial research
    - Use invoke_factchecker to verify claims""",
    tools=[
        researcher.as_tool("Research a topic"),
        fact_checker.as_tool("Verify facts and claims"),
    ]
)

result = writer.start("Write about quantum computing breakthroughs in 2024")
Code Review Pipeline
python
from praisonaiagents import Agent

# Specialist agents
linter = Agent(
    name="Linter",
    instructions="Check code for style issues and bugs"
)

security_scanner = Agent(
    name="SecurityScanner",
    instructions="Scan code for security vulnerabilities"
)

# Reviewer orchestrates both
reviewer = Agent(
    name="CodeReviewer",
    instructions="Review code using your analysis tools",
    tools=[
        linter.as_tool("Lint code for issues"),
        security_scanner.as_tool("Scan for security vulnerabilities"),
    ]
)

code = """
def login(username, password):
    query = f"SELECT * FROM users WHERE name='{username}'"
    return db.execute(query)
"""

result = reviewer.start(f"Review this code:\n{code}")
Multi-Step Analysis
python
from praisonaiagents import Agent

# Create a chain of specialists
data_fetcher = Agent(name="DataFetcher", instructions="Fetch data from APIs")
analyzer = Agent(name="Analyzer", instructions="Analyze data patterns")
visualizer = Agent(name="Visualizer", instructions="Create visualizations")

# Orchestrator uses all three
orchestrator = Agent(
    name="Orchestrator",
    instructions="Coordinate data analysis workflow",
    tools=[
        data_fetcher.as_tool("Fetch data from a source"),
        analyzer.as_tool("Analyze data and find patterns"),
        visualizer.as_tool("Create charts and visualizations"),
    ]
)

result = orchestrator.start("Analyze sales trends for Q4 2024")

Best Practices

Clear Descriptions

Provide clear tool descriptions so the LLM knows when to invoke each specialist.

Single Responsibility

Each specialist agent should have one clear purpose.

Avoid Deep Nesting

Keep hierarchies shallow (2-3 levels max) for clarity.

Test Individually

Test each specialist agent independently before composing.

Cloning for channels rebinds handoffs

Each channel’s clone rebinds the as_tool() handoff to itself, not the source agent. This means Discord and Telegram clones each get their own invoke_researcher bound to their own chat_history / tools / memory — no cross-channel leakage. You do not need to rebuild tools=[...] per channel; clone_for_channel() handles it.

Troubleshooting

Related