Laravel Daily's

Agent Run Observability in Laravel AI SDK 0.11: Trace Every Step, Tool Call, and Failure

hero image

Laravel AI SDK v0.11.0 shipped on August 19, 2026. This release adds a complete execution timeline for agent runs.

You can now correlate every provider request, tool invocation, failover attempt, and terminal error with one invocation ID. New lifecycle events expose wall timings for each stage. Hosted tool search helps reduce prompt overhead when agents have large tool catalogues.

The result is a stronger production foundation for teams building AI features with Laravel, a productive php web framework.

One invocation ID for the entire run

Before v0.11.0, a multi-step agent run could be difficult to reconstruct. Each provider request or failover attempt might appear as an isolated operation.

That changes in v0.11.0.

The SDK now creates one invocation ID at the start of an agent run. It threads that ID through every step, including requests sent to fallback providers. A run that starts with OpenAI and fails over to Anthropic remains one trace.

The same context also links tool calls to their parent run. If a tool prompts another agent, the nested agent receives parent invocation information. This makes delegated work visible without custom correlation code.

A practical log entry might look like this:

[
    'invocation_id' => 'run_01J...',
    'provider' => 'openai',
    'event' => 'step.completed',
    'time_ms' => 842.31,
]

When the first provider fails, the next event can use the same invocation_id:

[
    'invocation_id' => 'run_01J...',
    'provider' => 'anthropic',
    'event' => 'step.completed',
    'time_ms' => 611.08,
]

That distinction matters. A provider attempt is not the same thing as an agent run.

Lifecycle events: StartingStep, StepCompleted, and StepFailed

Laravel AI SDK v0.11.0 reports every provider round-trip through three step events:

  • StartingStep
  • StepCompleted
  • StepFailed

These events fire for synchronous and streaming generation.

StartingStep fires before the provider request. It includes the messages and resolved options sent for that step. It also includes the run’s complete message history, including attachments. That makes the event suitable for queued observability listeners.

StepCompleted fires after a successful provider response. It includes the complete step response and the wall time spent on the request.

StepFailed fires when a provider round-trip fails. It includes the failure and the elapsed wall time before the exception occurred.

The timings use milliseconds, similar to QueryExecuted::$time. You can compare model latency with database queries, outbound HTTP requests, and queue activity in the same request trace.

Registering listeners

You can listen to the events in a service provider:

namespace App\Providers;

use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Laravel\Ai\Events\AgentFailed;
use Laravel\Ai\Events\StartingStep;
use Laravel\Ai\Events\StepCompleted;
use Laravel\Ai\Events\StepFailed;
use Laravel\Ai\Events\ToolFailed;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Event::listen(StartingStep::class, function (StartingStep $event): void {
            Log::info('AI step starting', [
                'invocation_id' => $event->invocationId,
                'message_count' => count($event->messages),
                'options' => $event->options,
            ]);
        });

        Event::listen(StepCompleted::class, function (StepCompleted $event): void {
            Log::info('AI step completed', [
                'invocation_id' => $event->invocationId,
                'time_ms' => $event->time,
            ]);
        });

        Event::listen(StepFailed::class, function (StepFailed $event): void {
            Log::warning('AI step failed', [
                'invocation_id' => $event->invocationId,
                'time_ms' => $event->time,
                'exception' => $event->exception::class,
            ]);
        });
    }
}

Avoid logging raw prompts by default. Message history can contain customer data, credentials, or internal documents. Store identifiers, token counts, provider names, and timing data unless you have a specific redaction policy.

You can also create dedicated event listener classes. That is useful when you send records to an observability platform or persist run metadata for later analysis.

Laravel Nightwatch can provide the surrounding application trace. Its request timeline connects exceptions, database queries, queued jobs, and outbound calls. The AI SDK lifecycle events add the model-specific detail inside that timeline.

Bright Laravel AI step timeline with wall-time checkpoints and failure markers

Tool failures and terminal agent failures

Tool execution now has its own failure event: ToolFailed.

Previously, an exception from a tool handler could escape the generation loop without identifying the tool that caused it. The new event reports the failure, elapsed wall time, run invocation ID, and tool invocation ID. The original exception is still rethrown.

use Illuminate\Support\Facades\Event;
use Illuminate\Support\Facades\Log;
use Laravel\Ai\Events\ToolFailed;

Event::listen(ToolFailed::class, function (ToolFailed $event): void {
    Log::error('AI tool failed', [
        'invocation_id' => $event->invocationId,
        'tool_invocation_id' => $event->toolInvocationId,
        'time_ms' => $event->time,
        'exception' => $event->exception::class,
    ]);
});

AgentFailed reports the terminal failure of the overall run. It fires once the run has ended, including after the configured failover chain is exhausted.

use Laravel\Ai\Events\AgentFailed;

Event::listen(AgentFailed::class, function (AgentFailed $event): void {
    Log::critical('AI agent run failed', [
        'invocation_id' => $event->invocationId,
        'time_ms' => $event->time,
        'exception' => $event->exception::class,
    ]);
});

This gives you two useful failure views:

  • ToolFailed answers which tool failed and how long it ran.
  • AgentFailed answers whether the entire run produced a terminal failure.

That distinction helps separate a recoverable tool error from a user-visible outage.

Hosted tool search with ToolSearch

Large tool catalogues create two problems. They increase prompt size, and they force the model to evaluate tools that are irrelevant to the current request.

Version 0.11.0 adds the ToolSearch wrapper for OpenAI and Anthropic. Tools inside the wrapper are deferred. The provider searches and loads them when needed instead of receiving the entire catalogue on every request.

use App\Ai\Tools\RefundOrder;
use App\Ai\Tools\SearchInvoices;
use Laravel\Ai\Tools\ToolSearch;

public function tools(): iterable
{
    return [
        new ToolSearch(
            tools: [
                new SearchInvoices,
                new RefundOrder,
            ],
        ),
    ];
}

The tool implementations do not need to change. They still implement the normal Laravel AI tool contract.

Anthropic also supports a search strategy:

new ToolSearch(
    tools: [
        new SearchInvoices,
        new RefundOrder,
    ],
    strategy: 'bm25',
);

The supported strategies are regex and bm25.

Hosted search is most useful when an agent serves several workflows. For example, a support agent may have tools for invoices, subscriptions, account security, shipping, and refunds. A customer asking about an invoice should not require all those schemas in the initial request.

Hosted search support is provider-specific. Unsupported providers fail clearly instead of silently dropping the wrapped tools. OpenAI hosted search also requires stored responses, so check the provider configuration before deploying this pattern.

Laravel AI hosted tool search with a colorful tool catalogue and token reduction visual

Failover covers more real provider failures

Failover now handles connection failures through ProviderConnectionException. It also recognizes more transient upstream statuses:

  • 502
  • 503
  • 504
  • 520
  • 522
  • 524

These statuses commonly represent gateway problems, upstream timeouts, or infrastructure incidents. Anthropic usage-cap errors now trigger failover as well.

A bare 500 remains deliberately excluded. A 500 can represent a deterministic application or request error. Retrying against another provider could hide that defect and produce inconsistent behavior.

You can configure a provider chain as usual:

use Laravel\Ai\Enums\Lab;

$response = $agent->prompt(
    'Summarize this report.',
    provider: [
        Lab::OpenAI,
        Lab::Anthropic,
    ],
);

The invocation ID remains stable across both attempts. That lets you measure the cost of recovery and identify which provider served the final response.

More provider capabilities

The release expands provider coverage in several practical areas:

  • xAI now supports web search and file search.
  • Groq supports transcription.
  • OpenAI-compatible providers support transcription.
  • OpenRouter supports the web fetch server tool.
  • Anthropic web fetch citations are available through $response->meta->citations.
  • Gemini’s default text model is now gemini-3.7-flash.

These additions make it easier to choose a provider based on workload. A transcription-heavy application can use Groq. An agent that needs web access can use xAI or OpenRouter. Teams using an OpenAI-compatible gateway can add speech input without writing a separate integration.

Testing prompt counts with assertPromptedTimes

The new assertPromptedTimes() helper makes prompt-count assertions straightforward:

use App\Ai\Agents\SalesCoach;

SalesCoach::fake([
    'First response',
    'Second response',
    'Third response',
]);

$agent = new SalesCoach;

$agent->prompt('First prompt');
$agent->prompt('Second prompt');
$agent->prompt('Third prompt');

SalesCoach::assertPromptedTimes(3);

This is useful for detecting accidental loops, unexpected retries, and duplicate prompts in jobs or controllers. It follows the same style as Laravel’s other assertion helpers.

Queued fakes also execute their real job flow in v0.11.0. As a result, then(...) callbacks run during tests for queued transcriptions, images, audio, and embeddings.

Upgrade notes for 0.11.0

Update the package with Composer:

composer update laravel/ai

Review the official upgrade guide before deploying.

Stream errors now throw StreamErrorException. Previously, a provider error inside an otherwise successful HTTP stream could end silently with partial text and no terminal stream event:

use Laravel\Ai\Exceptions\StreamErrorException;

try {
    foreach ($agent->stream('Summarize this report.') as $event) {
        // Process streamed events...
    }
} catch (StreamErrorException $exception) {
    $providerError = $exception->error;
}

Code that catches Illuminate\Http\Client\ConnectionException should now catch ProviderConnectionException when handling provider connection failures.

Two event constructors also changed:

  • AgentFailedOver requires a new invocation ID argument.
  • ToolInvoked requires a wall-time argument.

Listeners do not need changes. Update only code that constructs these events directly, including custom test fixtures.

Finally, applications relying on Gemini’s previous default model should pin gemini-3.6-flash explicitly. Otherwise, the default moves to gemini-3.7-flash.

For developers building AI features alongside APIs, queues, and dashboards, these changes provide the missing execution trail. Whether you are using Laravel to build a REST API with PHP, running agents in jobs, or shipping a full AI product, you can now follow the run instead of guessing at it.

Observability is becoming table stakes for production AI apps. Laravel AI SDK 0.11.0 makes that standard easier to reach with the same event-driven tools PHP developers already use to ship reliable software.

Read the Laravel AI SDK documentation or review the v0.11.0 release to get started.

Previous
The Inertia 3.x Data Playbook: Defer, Poll, or Fetch in Your Laravel + Vue SPA
Next
Content Generation in Laravel: From Prompt to Published Post with the AI SDK