Overview
This example extends the existing agent telemetry pattern with delegated work. The parent agent stays visible in the console, but the same event stream is also projected to Logfire so you can inspect the full parent and subagent trace tree. Key concepts:UseSubagents: lets the parent delegate work throughspawn_subagentAgentDefinitionRegistry: defines the available delegated workersRuntimeEventBridge: projects the full parent and child event stream into telemetryAgentsTelemetryProjector: maps agent execution, tool, and subagent lifecycle eventsUseBash: gives the delegated subagent a real tool boundary to emit nested telemetry
Example
Copy
<?php
require 'examples/boot.php';
require_once 'examples/_support/logfire.php';
use Cognesy\Agents\Builder\AgentBuilder;
use Cognesy\Agents\Capability\Bash\UseBash;
use Cognesy\Agents\Capability\Core\UseContextConfig;
use Cognesy\Agents\Capability\Core\UseGuards;
use Cognesy\Agents\Capability\Core\UseLLMConfig;
use Cognesy\Agents\Capability\Subagent\UseSubagents;
use Cognesy\Agents\Collections\NameList;
use Cognesy\Agents\Data\AgentState;
use Cognesy\Agents\Enums\ExecutionStatus;
use Cognesy\Agents\Events\SubagentCompleted;
use Cognesy\Agents\Events\SubagentSpawning;
use Cognesy\Agents\Events\Support\AgentEventConsoleObserver;
use Cognesy\Agents\Telemetry\AgentsTelemetryProjector;
use Cognesy\Agents\Template\AgentDefinitionRegistry;
use Cognesy\Agents\Template\Data\AgentDefinition;
use Cognesy\Events\Dispatchers\EventDispatcher;
use Cognesy\Http\Telemetry\HttpClientTelemetryProjector;
use Cognesy\Messages\Messages;
use Cognesy\Polyglot\Inference\LLMProvider;
use Cognesy\Polyglot\Telemetry\PolyglotTelemetryProjector;
use Cognesy\Telemetry\Application\Projector\CompositeTelemetryProjector;
use Cognesy\Telemetry\Application\Projector\RuntimeEventBridge;
$events = new EventDispatcher('examples.d05.subagent-telemetry-logfire');
$hub = exampleLogfireHub('examples.d05.subagent-telemetry-logfire');
(new RuntimeEventBridge(new CompositeTelemetryProjector([
new AgentsTelemetryProjector($hub),
new PolyglotTelemetryProjector($hub),
new HttpClientTelemetryProjector($hub),
])))->attachTo($events);
$logger = new AgentEventConsoleObserver(
useColors: true,
showTimestamps: true,
showContinuation: true,
showToolArgs: true,
);
$workDir = dirname(__DIR__, 3);
$registry = new AgentDefinitionRegistry();
$registry->register(new AgentDefinition(
name: 'repo_inspector',
description: 'Inspects repository paths with bash and reports concise evidence',
systemPrompt: <<<'SYSTEM'
You inspect repositories with the bash tool.
Every factual claim must come from bash output.
Use bash at least 3 separate times.
Never combine commands with && or ;.
SYSTEM,
tools: NameList::fromArray(['bash']),
));
$subagentSpawns = [];
$subagentCompletions = [];
$agent = AgentBuilder::base($events)
->withCapability(new UseContextConfig(systemPrompt: <<<'SYSTEM'
You are an orchestration agent.
When repository inspection is needed and a suitable subagent exists, delegate the inspection.
Do not use bash directly when `repo_inspector` can do the work.
Summarize delegated findings clearly and briefly.
SYSTEM))
->withCapability(new UseLLMConfig(llm: LLMProvider::using('openai')))
->withCapability(new UseBash(baseDir: $workDir))
->withCapability(new UseSubagents(provider: $registry))
->withCapability(new UseGuards(maxSteps: 8, maxTokens: 12288, maxExecutionTime: 60))
->build()
->wiretap($logger->wiretap())
->wiretap(static function (object $event) use (&$subagentSpawns, &$subagentCompletions): void {
if ($event instanceof SubagentSpawning) {
$subagentSpawns[] = $event;
}
if ($event instanceof SubagentCompleted) {
$subagentCompletions[] = $event;
}
});
$task = <<<'TASK'
Inspect this repository by delegating the inspection to the `repo_inspector` subagent.
Requirements for the delegated subagent work:
- Use bash at least 3 separate times.
- Do not combine commands with && or ;.
- Run these as separate bash calls:
1. pwd
2. ls examples/D05_AgentTroubleshooting
3. rg -n "UseSubagents|SpawnSubagentTool|SubagentSpawning" packages/agents/src/Capability/Subagent/*.php packages/agents/src/Events/Subagent*.php
Then answer in 3 short bullets:
1. What kind of repository this is
2. Where the agent telemetry examples live
3. What the subagent telemetry path records
TASK;
$state = AgentState::empty()->withMessages(Messages::fromString($task));
echo "=== Agent Execution Log ===\n\n";
$finalState = $agent->execute($state);
$hub->flush();
$collectToolNames = function (AgentState $state) use (&$collectToolNames): array {
return array_reduce(
$state->stepExecutions()->all(),
static function (array $names, $stepExecution) use (&$collectToolNames): array {
$stepNames = [];
foreach ($stepExecution->step()->toolExecutions()->all() as $toolExecution) {
$stepNames[] = $toolExecution->name();
$value = $toolExecution->value();
if ($value instanceof AgentState) {
$stepNames = [...$stepNames, ...$collectToolNames($value)];
}
}
return [...$names, ...$stepNames];
},
[],
);
};
$toolNames = $collectToolNames($finalState);
$toolCallCount = count($toolNames);
$bashCallCount = count(array_filter($toolNames, static fn(string $name): bool => $name === 'bash'));
echo "\n=== Result ===\n";
$response = $finalState->finalResponse()->toString() ?: 'No response';
echo "Answer: {$response}\n";
echo "Status: {$finalState->status()->value}\n";
echo "Steps: {$finalState->stepCount()}\n";
echo "Tools used: " . implode(' > ', $toolNames) . "\n";
echo "Total tool calls: {$toolCallCount}\n";
echo "Bash calls across parent/child runs: {$bashCallCount}\n";
echo "Subagents spawned: " . count($subagentSpawns) . "\n";
echo "Subagents completed: " . count($subagentCompletions) . "\n";
echo "Telemetry: flushed to Logfire\n";
assert($finalState->status() === ExecutionStatus::Completed);
assert($response !== '');
assert(count($subagentSpawns) >= 1);
assert(count($subagentCompletions) >= 1);
assert($bashCallCount >= 3);
?>