Skip to content

Events

Observe the full lifecycle of providers, agents, tools, streams, and queued executions with Laravel events.

Events vs Middleware

Middleware modifies behavior — it wraps execution and can change inputs/outputs. Events observe behavior — they fire at specific lifecycle points and are purely informational. Use middleware when you need to intercept or transform; use events when you need to log, broadcast, or react.

Overview

Atlas fires events across seven groups. Events fire automatically — no configuration needed to enable them. Every event is a standard Laravel event, so you can listen with Event::listen, an EventServiceProvider, or Laravel's event discovery.

All events live in the Atlasphp\Atlas\Events namespace.

Provider Events

Fired by the shared HttpClient on every API call to a provider.

EventProperties
ProviderRequestStarted
Before the HTTP request is sent
string $url, array $body, string $method
ProviderRequestCompleted
After a successful response is parsed
string $url, array $data, int $statusCode
ProviderRequestFailed
When the HTTP request fails
string $url, Response $response
ProviderRequestRetrying
Before retrying a failed request
string $url, Throwable $exception, int $attempt, int $waitMicroseconds

Agent Executor Events

Fired by the executor during the agent tool loop.

EventProperties
AgentStarted
Agent execution begins
?string $agentKey, ?int $maxSteps, bool $concurrent
AgentStepStarted
Before each step in the tool loop
int $stepNumber, ?string $agentKey
AgentStepCompleted
After each step completes
int $stepNumber, FinishReason $finishReason, Usage $usage, ?string $agentKey
AgentToolCallStarted
Before a tool is invoked
ToolCall $toolCall, ?string $agentKey, ?int $stepNumber
AgentToolCallCompleted
After a tool returns a result
ToolCall $toolCall, ToolResult $result, ?string $agentKey, ?int $stepNumber
AgentToolCallFailed
When a tool throws an exception
ToolCall $toolCall, Throwable $exception, ?string $agentKey, ?int $stepNumber
AgentCompleted
Agent execution finishes (always fires, even on error)
array $steps, Usage $usage, ?string $agentKey
AgentMaxStepsExceeded
Agent hit the max steps limit
int $limit, array $steps, ?string $agentKey

Stream Events

Fired during streaming responses. All stream events implement ShouldBroadcastNow for real-time WebSocket delivery.

EventProperties
StreamStarted
Stream iteration begins
?Channel $channel
StreamChunkReceived
A text chunk arrives
Channel $channel, string $text
StreamToolCallReceived
Tool calls arrive in the stream
Channel $channel, array $toolCalls
StreamThinkingReceived
A thinking/reasoning chunk arrives
Channel $channel, string $text
StreamCompleted
Stream finishes
Channel $channel, string $text, ?array $usage, ?FinishReason, ?string $error

Modality Events

Fired when a modality handler (text, object, image, audio, embedding) processes a request.

EventProperties
ModalityStarted
Before the modality handler runs
Modality $modality, string $provider, string $model
ModalityCompleted
After the modality handler returns
Modality $modality, string $provider, string $model, ?Usage $usage

Execution Events

Fired during queued agent execution. All execution events implement ShouldBroadcastNow for real-time WebSocket delivery.

EventProperties
ExecutionQueued
Job is dispatched to the queue
?int $executionId, ?Channel $channel
ExecutionProcessing
Job starts processing in the worker
?int $executionId, ?Channel $channel
ExecutionCompleted
Job finishes successfully
?int $executionId, ?Channel $channel
ExecutionFailed
Job fails after all retries
?int $executionId, string $error, ?Channel $channel

Persistence Events

Fired when conversation messages are stored to the database.

EventProperties
ConversationMessageStored
After a message is persisted to a conversation
int $conversationId, int $messageId, Role $role, ?string $agentKey

Listening to Events

EventServiceProvider

Register listeners in your EventServiceProvider:

php
use Atlasphp\Atlas\Events\AgentCompleted;
use Atlasphp\Atlas\Events\AgentToolCallFailed;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        AgentCompleted::class => [
            LogAgentCompletion::class,
            TrackTokenUsage::class,
        ],
        AgentToolCallFailed::class => [
            AlertToolFailure::class,
        ],
    ];
}

Inline Listeners

Use Event::listen for quick one-off listeners:

php
use Atlasphp\Atlas\Events\ProviderRequestCompleted;
use Illuminate\Support\Facades\Event;

Event::listen(ProviderRequestCompleted::class, function (ProviderRequestCompleted $event) {
    Log::info('Provider call completed', ['url' => $event->url]);
});

Event Subscriber

Handle multiple events in a single class:

php
use Atlasphp\Atlas\Events\AgentStarted;
use Atlasphp\Atlas\Events\AgentCompleted;
use Atlasphp\Atlas\Events\AgentMaxStepsExceeded;
use Illuminate\Events\Dispatcher;

class AgentEventSubscriber
{
    public function handleStarted(AgentStarted $event): void
    {
        Log::debug('Agent starting', ['agent' => $event->agentKey]);
    }

    public function handleCompleted(AgentCompleted $event): void
    {
        Log::info('Agent completed', [
            'agent' => $event->agentKey,
            'steps' => count($event->steps),
            'tokens' => $event->usage->inputTokens + $event->usage->outputTokens,
        ]);
    }

    public function handleMaxSteps(AgentMaxStepsExceeded $event): void
    {
        Log::warning('Agent exceeded max steps', [
            'agent' => $event->agentKey,
            'limit' => $event->limit,
        ]);
    }

    public function subscribe(Dispatcher $events): void
    {
        $events->listen(AgentStarted::class, [self::class, 'handleStarted']);
        $events->listen(AgentCompleted::class, [self::class, 'handleCompleted']);
        $events->listen(AgentMaxStepsExceeded::class, [self::class, 'handleMaxSteps']);
    }
}

Register the subscriber in your EventServiceProvider:

php
protected $subscribe = [
    AgentEventSubscriber::class,
];

Practical Examples

Cost Tracking

Track token usage across all agent executions:

php
use Atlasphp\Atlas\Events\AgentStepCompleted;

class TrackCosts
{
    public function handle(AgentStepCompleted $event): void
    {
        DB::table('usage_logs')->insert([
            'agent' => $event->agentKey,
            'step' => $event->stepNumber,
            'input_tokens' => $event->usage->inputTokens,
            'output_tokens' => $event->usage->outputTokens,
            'created_at' => now(),
        ]);
    }
}

Provider Request Metrics

Monitor latency and failures at the HTTP layer:

php
use Atlasphp\Atlas\Events\ProviderRequestStarted;
use Atlasphp\Atlas\Events\ProviderRequestCompleted;
use Atlasphp\Atlas\Events\ProviderRequestFailed;

class ProviderMetrics
{
    private static array $timers = [];

    public function handleStarted(ProviderRequestStarted $event): void
    {
        self::$timers[$event->url] = microtime(true);
    }

    public function handleCompleted(ProviderRequestCompleted $event): void
    {
        $duration = microtime(true) - (self::$timers[$event->url] ?? microtime(true));
        unset(self::$timers[$event->url]);

        Log::info('Provider request', [
            'url' => $event->url,
            'duration_ms' => round($duration * 1000, 2),
        ]);
    }

    public function handleFailed(ProviderRequestFailed $event): void
    {
        unset(self::$timers[$event->url]);

        Log::error('Provider request failed', ['url' => $event->url]);
    }
}

Alerting on Tool Failures

php
use Atlasphp\Atlas\Events\AgentToolCallFailed;
use Illuminate\Support\Facades\Notification;

class AlertToolFailure
{
    public function handle(AgentToolCallFailed $event): void
    {
        if ($this->isCriticalTool($event->toolCall->name)) {
            Notification::route('slack', config('services.slack.alerts'))
                ->notify(new ToolFailureAlert(
                    tool: $event->toolCall->name,
                    error: $event->exception->getMessage(),
                    agent: $event->agentKey,
                ));
        }
    }

    private function isCriticalTool(string $name): bool
    {
        return in_array($name, ['process_payment', 'send_email', 'update_order']);
    }
}

Broadcasting Events

Stream events and execution events implement ShouldBroadcastNow, making them available for real-time WebSocket delivery through Laravel Broadcasting (Pusher, Ably, Reverb, etc.).

Stream events broadcast chunks, tool calls, and thinking content as they arrive — ideal for building real-time chat interfaces.

Execution events broadcast queue job lifecycle — useful for showing progress indicators when agents run in the background.

Both use the Channel object to target the correct broadcast channel. See the Streaming Guide for setup details including frontend integration examples.

Event Lifecycle

A typical agent execution with tool calls fires events in this order:

1. ModalityStarted           ← modality wraps the entire execution
2. AgentStarted
3. AgentStepStarted
4.   ProviderRequestStarted
5.   ProviderRequestCompleted
6. AgentStepCompleted
7. AgentToolCallStarted
8. AgentToolCallCompleted
9. AgentStepStarted          ← next step begins
10.  ProviderRequestStarted
11.  ProviderRequestCompleted
12. AgentStepCompleted       ← no more tool calls
13. AgentCompleted
14. ModalityCompleted         ← modality closes with usage

For queued executions, the lifecycle is wrapped:

1. ExecutionQueued
2. ExecutionProcessing
3. AgentStarted
   ... (agent lifecycle as above) ...
4. AgentCompleted
5. ConversationMessageStored  ← if persistence is enabled
6. ExecutionCompleted

If the agent fails, ExecutionFailed fires instead of ExecutionCompleted.

API Reference

All 33 events at a glance:

#EventGroupBroadcasts
1ProviderRequestStartedProviderNo
2ProviderRequestCompletedProviderNo
3ProviderRequestFailedProviderNo
4ProviderRequestRetryingProviderNo
5AgentStartedExecutorNo
6AgentStepStartedExecutorNo
7AgentStepCompletedExecutorNo
8AgentToolCallStartedExecutorNo
9AgentToolCallCompletedExecutorNo
10AgentToolCallFailedExecutorNo
11AgentCompletedExecutorNo
12AgentMaxStepsExceededExecutorNo
13StreamStartedStreamYes
14StreamChunkReceivedStreamYes
15StreamToolCallReceivedStreamYes
16StreamThinkingReceivedStreamYes
17StreamCompletedStreamYes
18ModalityStartedModalityNo
19ModalityCompletedModalityNo
20ExecutionQueuedExecutionYes
21ExecutionProcessingExecutionYes
22ExecutionCompletedExecutionYes
23ExecutionFailedExecutionYes
24ConversationMessageStoredPersistenceNo
25VoiceSessionCreatedVoiceNo
26VoiceSessionEndedVoiceNo
27VoiceCallStartedVoiceNo
28VoiceCallCompletedVoiceNo
29VoiceToolCallStartedVoiceNo
30VoiceToolCallCompletedVoiceNo
31VoiceToolCallFailedVoiceNo
32VoiceAudioDeltaReceivedVoiceYes
33VoiceTranscriptDeltaReceivedVoiceYes

Voice Events

Events fired during voice call lifecycle.

VoiceCallStarted

Fired when a voice call record is created and the session is ready for connection.

PropertyTypeDescription
voiceCallIdintVoiceCall record ID
conversationId?intLinked conversation (nullable)
sessionIdstringProvider session ID
providerstringProvider name
agentKey?stringAgent key

VoiceCallCompleted

Fired when a voice call completes with the full transcript. This is the primary event for consumers to post-process voice calls — generate summaries, create conversation messages, embed into memory.

PropertyTypeDescription
voiceCallIdintVoiceCall record ID
conversationId?intLinked conversation
sessionIdstringProvider session ID
transcriptarrayComplete transcript [{role, content}]
durationMs?intWall-clock duration

Atlas fires this event and stores the transcript. Post-processing is entirely consumer-owned — you decide whether to summarize, create messages, embed into memory, or do nothing.

php
use Atlasphp\Atlas\Events\VoiceCallCompleted;
use Atlasphp\Atlas\Atlas;
use Atlasphp\Atlas\Messages\SystemMessage;
use Atlasphp\Atlas\Persistence\Models\VoiceCall;
use Atlasphp\Atlas\Persistence\Services\ConversationService;

Event::listen(VoiceCallCompleted::class, function ($event) {
    if ($event->transcript === []) return;

    $voiceCall = VoiceCall::find($event->voiceCallId);

    $formatted = collect($event->transcript)
        ->map(fn ($t) => ucfirst($t['role']).': '.$t['content'])
        ->implode("\n");

    try {
        // Summarize with a cheap, fast model
        $response = Atlas::text('xai', 'grok-3-mini-fast')
            ->instructions('Summarize this voice call in 2-3 sentences.')
            ->message($formatted)
            ->asText();

        // Store summary on the call record
        $voiceCall->update(['summary' => $response->text]);

        // Optionally inject into conversation so the text agent sees it
        if ($event->conversationId) {
            $conversations = app(ConversationService::class);
            $conversation = $conversations->find($event->conversationId);
            $conversations->addMessage($conversation, new SystemMessage(
                content: "[Voice call summary]\n{$response->text}"
            ));
        }
    } catch (\Throwable $e) {
        logger()->error('Voice summarization failed', ['id' => $event->voiceCallId]);
    }
});

See Voice — Post-Processing Patterns for more options.

Next Steps

  • Middleware — Modify behavior with middleware
  • Streaming — Real-time streaming with broadcast events
  • Testing — Test agent execution

Released under the MIT License.