Skip to main content

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 Langfuse so you can inspect the full parent and subagent trace tree. Key concepts:
  • UseSubagents: lets the parent delegate work through spawn_subagent
  • AgentDefinitionRegistry: defines the available delegated workers
  • RuntimeEventBridge: projects the full parent and child event stream into telemetry
  • AgentsTelemetryProjector: maps agent execution, tool, and subagent lifecycle events
  • UseBash: gives the delegated subagent a real tool boundary to emit nested telemetry

Example

<?php
require 'examples/boot.php';
require_once 'examples/_support/langfuse.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-langfuse');
$hub = exampleLangfuseHub();

(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.
        If `repo_inspector` is available, you must delegate repository inspection to it.
        Your first tool call must be `spawn_subagent`.
        Do not use bash directly when `repo_inspector` can do the work.
        If you skip delegation, the task is incomplete.
        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'
You must call `spawn_subagent` as your first tool call.
Use subagent `repo_inspector`.
Do not call bash yourself.

Pass this task to the subagent:

Inspect this repository with bash.

Requirements:
- 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

Return exactly 3 short bullets:
1. What kind of repository this is
2. Where the agent telemetry examples live
3. What the subagent telemetry path records

After the subagent returns, provide only its 3 bullets as the final answer.
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'));
$spawnSubagentCount = count(array_filter($toolNames, static fn(string $name): bool => $name === 'spawn_subagent'));

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 "spawn_subagent calls: {$spawnSubagentCount}\n";
echo "Subagents spawned: " . count($subagentSpawns) . "\n";
echo "Subagents completed: " . count($subagentCompletions) . "\n";
echo "Telemetry: flushed to Langfuse\n";

assert($finalState->status() === ExecutionStatus::Completed);
assert($response !== '');
assert($spawnSubagentCount >= 1);
assert(count($subagentSpawns) >= 1);
assert(count($subagentCompletions) >= 1);
assert($bashCallCount >= 3);
?>