> ## Documentation Index
> Fetch the complete documentation index at: https://docs.instructorphp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Agent loop events

## Overview

The agent emits events throughout its lifecycle. Events are read-only observations —
they cannot modify agent behavior (use hooks for that).

Two ways to observe events:

* `wiretap(callable)`: Receives **every** event — `AgentEventConsoleObserver` uses this internally
* `onEvent(EventClass, callable)`: Subscribes to a **specific** event type for custom logic

Both can be used together. The logger provides general visibility while `onEvent()` lets
you collect metrics, trigger side effects, or react to specific events.

Key concepts:

* `AgentEventConsoleObserver`: Built-in wiretap that formats all events for console output
* `onEvent()`: Targeted listener for a single event class
* Events include: `AgentStepCompleted`, `ToolCallStarted`, `ToolCallCompleted`,
  `InferenceResponseReceived`, `AgentExecutionCompleted`, `ContinuationEvaluated`, and more

## Example

```php theme={null}
<?php
require 'examples/boot.php';

use Cognesy\Agents\AgentLoop;
use Cognesy\Agents\Capability\Bash\BashTool;
use Cognesy\Agents\Data\AgentState;
use Cognesy\Agents\Events\AgentExecutionCompleted;
use Cognesy\Agents\Events\InferenceResponseReceived;
use Cognesy\Agents\Events\Support\AgentEventConsoleObserver;

// AgentEventConsoleObserver uses wiretap() internally to show all lifecycle events
$logger = new AgentEventConsoleObserver(
    useColors: true,
    showTimestamps: true,
    showContinuation: true,
    showToolArgs: true,
);

$agent = AgentLoop::default()
    ->withTool(BashTool::inDirectory(getcwd()))
    ->wiretap($logger->wiretap());

// onEvent(): subscribe to specific event types for custom logic
// This runs alongside the logger — use it to collect metrics, trigger
// side effects, or react to specific events the logger doesn't cover.

$totalInferenceMs = 0;

$agent->onEvent(InferenceResponseReceived::class, function (InferenceResponseReceived $event) use (&$totalInferenceMs) {
    $ms = $event->receivedAt->getTimestamp() * 1000 + (int)($event->receivedAt->format('u') / 1000)
        - $event->requestStartedAt->getTimestamp() * 1000 - (int)($event->requestStartedAt->format('u') / 1000);
    $totalInferenceMs += $ms;
});

$agent->onEvent(AgentExecutionCompleted::class, function (AgentExecutionCompleted $event) use (&$totalInferenceMs) {
    echo "\n  [custom] Execution summary:\n";
    echo "    Steps: {$event->totalSteps}\n";
    echo "    Total tokens: {$event->totalUsage->total()}\n";
    echo "    LLM time: {$totalInferenceMs}ms\n";
});

// Run the agent
$state = AgentState::empty()->withUserMessage(
    'What is today\'s date? Use bash to find out. Be concise.'
);

echo "=== Agent Events Demo ===\n\n";
$finalState = $agent->execute($state);

echo "\n=== Result ===\n";
$response = $finalState->finalResponse()->toString() ?: 'No response';
echo "Answer: {$response}\n";

if ($finalState->status()->value !== 'completed') {
    echo "Skipping assertions because execution status is {$finalState->status()->value}.\n";
    exit(1);
}

// Assertions
assert(!empty($finalState->finalResponse()->toString()), 'Expected non-empty response');
assert($finalState->stepCount() >= 1, 'Expected at least 1 step');
assert($totalInferenceMs > 0, 'Expected inference time to be tracked');
?>
```
