What Changed in Laravel AI v0.11.0
Before v0.11.0, the Laravel AI SDK reported only two events for an entire agent run: PromptingAgent at the start and AgentPrompted at the end. A run that made five provider round-trips looked identical to one that made a single call, and a run that threw an exception midway reported nothing at all because AgentPrompted was never dispatched.
Seven pull requests from @pushpak1300, merged as #870–#876, change that completely. Every run now carries one stable ID, and every round-trip and tool call fires start and end events with wall timings.
One Invocation ID for the Whole Run
streamPrompt() already generated a run-level invocation ID, but prompt() did not. Synchronous middleware saw $prompt->invocationId === null while streaming middleware saw a real value. Worse, failover across three providers produced three unrelated IDs for what was logically one run.
prompt() now generates the ID up front, and the provider reuses whatever the caller supplied. Every event carries it as its first constructor argument:
public function __construct(
public string $invocationId,
public int $stepNumber,
// ...
) {}
AgentFailedOver also gained the ID as a required first argument. Any code constructing that event manually needs updating.
Step Events
StartingStep, StepCompleted, and StepFailed fire around every provider round-trip on both the synchronous and streaming paths.
StartingStep carries the full message history sent for that step, the resolved options, stepNumber, and isFinalStep. StepCompleted carries the whole StepResponse plus a float $time in milliseconds — the same unit as QueryExecuted::$time:
use Laravel\Ai\Events\StepCompleted;
Event::listen(StepCompleted::class, function (StepCompleted $event) {
Log::info('AI step completed', [
'invocation' => $event->invocationId,
'step' => $event->stepNumber,
'ms' => $event->time,
'prompt_tokens' => $event->response->usage->promptTokens,
'finish' => $event->response->finishReason->value,
]);
});
Per-step usage was available before through $response->steps, but only as a bulk payload on the terminal event with no timing attached. Now cost and duration are reported as the run progresses.
StepFailed covers steps that end without a response, carrying the Throwable and the time spent before it threw.
Tool Events
InvokingTool and ToolInvoked already existed, but a shared mutable property on the provider caused the outer ToolInvoked to report the inner call's ID when an agent was invoked as a tool. A RunContext now owns the run identity and dispatches events directly, and each invocation receives its own ID inside executeTool().
The new ToolFailed event closes the gap for tool exceptions:
use Laravel\Ai\Events\ToolFailed;
Event::listen(ToolFailed::class, function (ToolFailed $event) {
Log::error('AI tool failed', [
'invocation' => $event->invocationId,
'tool_invocation' => $event->toolInvocationId,
'tool' => class_basename($event->tool),
'ms' => $event->time,
'exception' => $event->exception->getMessage(),
]);
});
The exception is still rethrown, so existing behavior is unchanged. ToolInvoked also gained a required float $time; update any manual constructor calls.
Run Failure and Sub-Agent Linking
AgentFailed fires once per run after the entire chain is exhausted, carrying the invocation ID, the prompt, and the exception. AgentFailedOver no longer fires for the final provider in a chain — that attempt is now reported as the run's failure instead.
Sub-agents invoked as tools now receive parentInvocationId and parentToolInvocationId on their prompt, correlating nested runs to their parent. The link does not cross a queue boundary; a prompt dispatched with promptOnQueue() starts its own unparented run.
Key Takeaways
- Every agent run now has a single
invocationIdshared acrossprompt()andstreamPrompt(). StartingStep,StepCompleted, andStepFailedgive per-round-trip visibility with millisecond timings.ToolFailedcloses the silent-exception gap in tool execution.AgentFailedfires once per failed run;AgentFailedOverno longer fires on the final provider.- Sub-agent runs are linked to their parent via
parentInvocationIdandparentToolInvocationId. - If you only need timings and token counts, prefer
StepCompletedoverStartingStepto avoid serializing the full message history in queued listeners.
Source: Laravel News — Laravel AI: Trace Agent Runs With Lifecycle Events