Sub-agents
A sub-agent is an agent used as a tool by another agent. When the parent decides to call it, the sub-agent runs on its own — its own instructions, model, and tools — and returns its answer to the parent as the tool result.
Use this to delegate specialised work: a support agent that hands billing questions to a billing agent, a coordinator that delegates research, and so on. Each sub-agent keeps its own focused instructions, so the parent's prompt stays small.
Because a sub-agent is just a tool, it behaves like any other tool everywhere — it fires the same events, streams to your UI in real time, and is recorded in persistence the same way.
You could always hand-roll this — write a custom tool that runs another agent itself. The built-in sub-agent support does that wiring for you and adds context isolation, depth/cycle guards, and parent → child audit tracking automatically.
Declaring a sub-agent
List an agent class in another agent's tools(). Atlas exposes it to the parent model as a delegation tool automatically:
use Atlasphp\Atlas\Agent;
class SupportAgent extends Agent
{
public function tools(): array
{
return [
BillingAgent::class, // delegated to as a sub-agent
SearchKnowledgeBase::class, // a normal tool
];
}
}The tool's name and description come from the sub-agent's key() and description().
What the parent passes
The parent model sees a single string parameter, task, which it fills with a clear, self-contained instruction. That becomes the sub-agent's message, and the sub-agent's final text is returned as the tool result.
Context isolation
Each sub-agent invocation runs in isolation: its own instructions, model, and tools, starting from a fresh history. The parent's conversation history is not shared — pass everything the sub-agent needs in the task. Context you set with withMeta() (auth, tenant) is forwarded, so authorization still works.
Running sub-agents concurrently
By default delegations run one at a time. If the parent agent enables concurrent execution (via concurrent() or ->withConcurrent()) and the model delegates to several sub-agents in a single step, those sub-agents run at the same time — each in its own forked process:
class CoordinatorAgent extends Agent
{
public function concurrent(): bool
{
return true; // fan out to sub-agents in parallel
}
public function tools(): array
{
return [ResearchAgent::class, PricingAgent::class, LegalAgent::class];
}
}The parent's tool loop waits for all of them to finish, then continues with the complete set of responses — so the parent always reasons over every sub-agent's result, exactly as it would sequentially. Only wall-clock time changes: three sub-agents that each take ~3s finish in ~3s instead of ~9s.
Everything in this page still holds under concurrency. The full lineage tree, each sub-agent's own and rolled-up token usage, and the depth/cycle guards are all preserved across the fork boundary, and persistence tracking is automatically fork-safe (Atlas resets database connections before forking).
One nuance to know: a concurrently-delegated sub-agent's internal real-time events (its own step and tool-call events) fire inside its forked process and are not delivered to in-process listeners in the parent — the delegation still surfaces as a parent tool-call event, and the full tree is still persisted for auditing. Requirements, the CLI/queue-only nature of fork-based parallelism, and this event caveat are covered in Concurrent Tool Execution.
Guards
Delegation is bounded to prevent runaway nesting:
- Depth —
atlas.agents.max_delegation_depth(default5) caps how deep agents may delegate; exceeding it throwsMaxDelegationDepthException. - Cycles — delegating to an agent already in the chain (
A → B → A) throwsDelegationCycleException.
atlas.agents.delegation_errors controls sub-agent failures: throw (default) surfaces a failed tool call; return hands the error message back to the parent model so it can recover.
// config/atlas.php
'agents' => [
'max_delegation_depth' => 5,
'delegation_errors' => 'throw',
],Auditing the delegation tree
With persistence enabled, every delegation is recorded as an auditable tree. Each sub-agent run is its own Execution, linked to its parent by parent_execution_id, parent_tool_call_id (the delegating call, typed agent), and depth (0 for the root, incremented per level):
use Atlasphp\Atlas\Persistence\Models\Execution;
$run = Execution::find($id);
$run->parent; // the execution that delegated to this one (null for a root)
$run->children; // sub-agent executions it spawned
// Each agent's own token cost is stored on its own row.
$run->usage; // ['input_tokens' => …, 'output_tokens' => …]
// Total tokens for the whole chain (this run plus every sub-agent it called).
$run->totalUsage(); // ['input_tokens' => …, 'output_tokens' => …, 'total_tokens' => …]So you can report what each individual agent cost, as well as the cost of an entire delegation chain.