Appearance
Agent as Tool
Wrap a specialised agent as a tool that another agent can call. This requires no orchestrator class — just agent.as_tool() passed to the parent agent's tools list.
Agent delegation
- Wrap — Call
specialist.as_tool()to create anAgentTool. - Register — Pass the tool to the orchestrator agent's
toolsparameter. - Delegate — The orchestrator's model decides when to invoke the sub-agent.
- Execute — The sub-agent runs its own agent loop (with its own tools and hooks) and returns a result.
- Continue — The orchestrator incorporates the result and continues until done.
Usage
python
from elsai import Agent
from elsai.agent import AgentConfig
researcher = Agent(
system_prompt="You are a research specialist. Find accurate, up-to-date information.",
config=AgentConfig(
name="researcher",
description="Searches the web and returns summarised findings on any topic.",
),
)
orchestrator = Agent(
system_prompt="You are a helpful assistant. Use the researcher tool when you need information.",
config=AgentConfig(name="orchestrator"),
tools=[researcher.as_tool()],
)
result = orchestrator("What are the key differences between React and Vue?")
print(result)Preserve context — keep conversation history across sub-agent calls:
python
memory_agent = Agent(
config=AgentConfig(
name="memory",
description="Stores and retrieves information from our working memory.",
),
)
orchestrator = Agent(
tools=[memory_agent.as_tool(preserve_context=True)],
)Nested agents:
python
from elsai.agent import AgentConfig
data_agent = Agent(config=AgentConfig(name="data", description="Retrieves raw data from databases"))
analyst = Agent(
config=AgentConfig(name="analyst", description="Analyses data and produces insights"),
tools=[data_agent.as_tool()],
)
orchestrator = Agent(
config=AgentConfig(name="orchestrator"),
tools=[analyst.as_tool()],
)
result = orchestrator("Analyse our top customers and explain the key trends")Sub-agents with their own tools:
python
from elsai import Agent, tool
from elsai.agent import AgentConfig
@tool
def evaluate_expression(expression: str) -> str:
"""Evaluate a mathematical expression."""
return str(eval(expression))
coder = Agent(
config=AgentConfig(
name="coder",
description="Writes and executes Python code to solve problems",
),
tools=[evaluate_expression],
)
manager = Agent(
config=AgentConfig(name="manager"),
tools=[coder.as_tool()],
)
result = manager("Calculate the compound interest on $10,000 at 5% for 10 years")Configuration
| Parameter | Default | Description |
|---|---|---|
name | agent.name | Tool name the model sees |
description | agent.description | How the model decides when to call this tool |
preserve_context | False | True = keep history across calls; False = fresh start each time |
mode | "reject" | Concurrency when the same tool is invoked in parallel — see below |
python
from elsai.agent import AgentAsToolMode
researcher.as_tool(
name="research",
description="Searches and summarises information on any topic.",
preserve_context=False,
mode=AgentAsToolMode.SPAWN, # or "spawn", "queue", "reject"
)Concurrent invocation (mode)
When a parent agent uses the default ConcurrentToolExecutor, the model may issue two tool calls to the same sub-agent in one turn. Each sub-agent is wrapped with a shared runtime and a lock. The mode parameter controls what happens on the second concurrent call.
Registry default
Passing tools=[researcher] auto-wraps the agent with as_tool() using mode="reject". To use queue or spawn, wrap explicitly: tools=[researcher.as_tool(mode="spawn")].
Modes
| Mode | Runtime | Second concurrent call |
|---|---|---|
reject (default) | Shared template agent | Error: Agent '…' is already processing a request |
queue | Shared template agent | Waits until the first call finishes |
spawn | New isolated worker per call | Runs in parallel |
Different specialists (tools=[researcher, writer]) run in parallel without any mode configuration.
When to use which
python
from elsai import Agent
from elsai.agent import AgentConfig
researcher = Agent(config=AgentConfig(name="researcher", description="Research specialist"))
writer = Agent(config=AgentConfig(name="writer", description="Writing specialist"))
# Different sub-agents — default mode is fine
manager = Agent(tools=[researcher, writer])
# Same specialist called twice in one turn — parallel
manager = Agent(tools=[researcher.as_tool(mode="spawn")])
# Same specialist — OK to wait on the shared agent
manager = Agent(tools=[researcher.as_tool(mode="queue")])
# Stateful shared specialist (preserve history, serialize access)
researcher.as_tool(preserve_context=True, mode="queue")Shared modes (reject, queue)
- Use the template agent referenced by the tool wrapper.
- With
preserve_context=False, messages and state reset to the snapshot taken at wrap time before each call. queueacquires a lock so the second call blocks until the first completes.
Spawn mode
Each invocation creates an isolated Agent worker:
- Worker
agent_idis{template_agent_id}:spawn:{tool_use_id}for distinct traces. - Template
event_loop_metricsare not updated — workers run in isolation. - MCP clients are shared with reference-counted lifecycle;
worker.cleanup()runs after each call. - On interrupt, the worker is retained until resume completes.
Built-in conversation managers (SlidingWindowConversationManager, SummarizingConversationManager, NullConversationManager) and spawn-ready plugins clone onto each worker. See Plugins — Spawn compatibility and Conversation Management — Spawn cloning.
Memory pipelines
as_tool(mode="spawn") is not compatible with elsai memory integration conversation managers (ElsaiConversationManager, CompositeConversationManager) today. Use mode="queue", or implement clone_for_spawn() on a custom conversation manager and supports_spawn() / clone_for_spawn() on any custom plugins.
Construction validation
| Config | Result |
|---|---|
mode="reject" or mode="queue" | OK |
mode="spawn" + preserve_context=True | ValueError |
mode="spawn" + template has session_manager | ValueError |
mode="spawn" + non-spawnable plugin | ValueError (names the plugin) |
mode="spawn" + non-cloneable conversation manager | ValueError |
| Invalid mode string | ValueError |
Import the enum from elsai.agent:
python
from elsai.agent import AgentAsToolModeRelation to ConcurrentInvocationMode
AgentConfig.concurrent_invocation_mode controls direct agent() / agent.stream_async() calls on a single agent. AgentAsToolMode is a separate layer for the tool wrapper when a parent agent invokes a sub-agent as a tool.
Full agent loop
A sub-agent invoked via as_tool() runs a complete agent loop — its own tools, hooks, plugins, and session manager all apply independently of the orchestrator.