Agent Run Observability in Laravel AI SDK 0.11
Laravel AI #Laravel AI #AI SDK #Observability #PHP #Agent

Agent Run Observability in Laravel AI SDK 0.11

4 min read Mohamed Said Mohamed Said

What Changed in Laravel AI SDK 0.11

Released on August 19, 2026, Laravel AI v0.11.0 ships 36 merged pull requests from twelve first-time contributors. The headline feature is end-to-end observability for agent runs: every run now carries a single correlation ID, and a set of lifecycle events fires around every provider round-trip and every tool call.

Per-Run Invocation IDs

Before this release, prompt() never minted a run-level invocation ID, so synchronous middleware always saw $prompt->invocationId === null. Streaming middleware saw a real value, and a three-provider failover produced three unrelated IDs for the same logical run. prompt() now mints the ID up front, and every provider reuses whatever the caller supplied.

Nested tool calls had a related bug: an agent invoked as a tool overwrote the shared mutable ID before the outer ToolInvoked event fired, so the outer event carried the inner call's ID. The ID is now minted inside executeTool(), and tools can read it with Request::toolInvocationId().

Lifecycle Events

Five events now fire across both the synchronous and streaming paths:

  • StartingStep — carries the messages, resolved options, and the run's full message history sent to the provider.
  • StepCompleted — carries the complete step response.
  • StepFailed — fires when a step fails; both end events include wall time in milliseconds, matching QueryExecuted::$time.
  • ToolFailed — reports which tool threw and carries the same tool invocation ID as the InvokingTool that opened it; the exception is still rethrown.
  • AgentFailed — fires exactly once per run, after failover has exhausted the provider chain.

A tool call also publishes its run and tool invocation IDs for its own duration, so any agent prompted while it runs picks them up as parentInvocationId and parentToolInvocationId. This links sub-agents back to the run that delegated to them without requiring AgentTool.

Every tool was previously sent to the provider on every request. The new ToolSearch wrapper defers tools so OpenAI and Anthropic load them on demand through their own hosted search:

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

Anthropic's search strategy is a constructor argument validated against regex and bm25:

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

Providers that do not support hosted search throw a clear exception before the request is sent. Only one wrapper may be registered per request, and OpenAI hosted search requires stored responses, so pairing ToolSearch with store=false throws.

Broader Failover Coverage

Three production failure modes now trigger failover where they previously did not:

  1. Connection failuresConnectionException (unreachable host, refused connection, local Ollama not running) is rethrown as ProviderConnectionException implementing FailoverableException.
  2. Gateway error codes — the overloaded-provider status list grew from 503 alone to 502, 503, 504, 520, 522, and 524. A bare 500 is deliberately excluded.
  3. Anthropic spend caps — Anthropic returns HTTP 400 with a usage limit message when an organisation hits its spend cap. Adding that pattern to the failover handler prevents ~990 unhandled exceptions per month in affected setups.

Provider Additions

  • xAI: web search and file search
  • Groq and OpenAI-compatible providers: transcription
  • OpenRouter: web fetch server tool
  • Anthropic: web fetch citations now appear on $response->meta->citations
  • Gemini: default text model moved to gemini-3.7-flash

Testing

assertPromptedTimes() works like Bus::assertDispatchedTimes():

SalesCoach::assertPromptedTimes(3);

Faked queued generation of transcriptions, images, audio, and embeddings now runs the then(...) callback, so tests can assert on callback behaviour instead of stopping at the dispatch.

Upgrade Notes

  • Stream errors now throw StreamErrorException instead of silently ending the step.
  • AgentFailedOver requires a new string $invocationId argument; ToolInvoked requires a new float $time argument. Only code constructing these events by hand needs updating.
  • AgentFailedOver no longer fires for the final provider in a chain; AgentFailed handles that case.
  • Applications relying on Gemini's default text model will move to gemini-3.7-flash; set the model explicitly to keep the old one.

Key takeaways:

  • Every agent run now has a single correlation ID across failover attempts.
  • Five lifecycle events with wall timings make multi-step runs fully traceable.
  • ToolSearch reduces token usage by deferring tool loading to the provider.
  • Failover now handles connection errors, gateway timeouts, and Anthropic spend caps.
  • assertPromptedTimes() simplifies agent test assertions.

Upgrade with composer update laravel/ai. Full details at the Laravel News source article.

Found this useful?

Frequently Asked Questions

3 questions
Q01 What is the invocation ID in Laravel AI SDK 0.11 and why does it matter?
Every agent run now receives a single invocation ID minted at the start of `prompt()` or `streamPrompt()`. That ID is threaded through all provider round-trips and failover attempts, so middleware, listeners, and logs can correlate every event in a multi-step run back to one originating call.
Q02 How does ToolSearch reduce token usage in Laravel AI?
Wrapping tools in `new ToolSearch(tools: [...])` defers them so OpenAI and Anthropic load them on demand through their own hosted search, rather than sending the full tool catalogue to the provider on every request. The tools themselves need no changes.
Q03 What breaking changes does Laravel AI SDK 0.11 introduce?
Stream errors now throw `StreamErrorException` instead of silently ending the step. `AgentFailedOver` requires a new `string $invocationId` argument, and `ToolInvoked` requires a new `float $time` argument — only code constructing these events by hand needs updating. Gemini's default text model also moves to `gemini-3.7-flash`.

Continue reading

More Articles

View all