A single model call works well for a focused task. Complex applications need more structure.
One agent can classify a request. Another can retrieve context. A third can review the result. A final agent can format the response.
The Laravel AI SDK gives PHP developers a consistent way to compose these agents. You can chain prompts, route requests to specialists, run independent agents concurrently, and move long-running workflows onto Laravel queues.
This article shows how to combine those patterns in a Laravel application and expose the workflow through a REST API built with PHP.
Multi-agent workflows: the core idea
A Laravel AI SDK agent is a PHP class that owns its instructions, tools, model configuration, and output schema. You can create one with Artisan:
php artisan make:agent ResearchAgent
php artisan make:agent WriterAgent
php artisan make:agent ReviewAgent --structured
The generated classes live in app/Ai/Agents. Each class can be prompted directly:
$response = (new ResearchAgent)->prompt($request);
$text = $response->text;
This separation matters. Each agent has one responsibility. The orchestrator owns the workflow.
The four patterns covered here are:
- Prompt chaining: Run dependent steps in order.
- Routing: Classify a request and select a specialist.
- Parallelization: Run independent agents at the same time.
- Queue orchestration: Move long workflows out of the HTTP request.
The patterns often work together.
Prompt chaining: pass results between agents
Prompt chaining is the simplest multi-agent workflow. One agent’s output becomes the next agent’s input.
A content workflow might use this sequence:
Research → Draft → Review → Format
Each agent receives a focused instruction. The workflow code controls the order.
use App\Ai\Agents\DraftAgent;
use App\Ai\Agents\FormatAgent;
use App\Ai\Agents\ResearchAgent;
use App\Ai\Agents\ReviewAgent;
$research = (new ResearchAgent)->prompt($topic);
$draft = (new DraftAgent)->prompt([
'topic' => $topic,
'research' => $research->text,
]);
$review = (new ReviewAgent)->prompt([
'draft' => $draft->text,
]);
$final = (new FormatAgent)->prompt([
'draft' => $draft->text,
'review' => $review->text,
]);
return $final->text;
Use structured output when the next step needs predictable data. A review agent could return a score and a list of issues instead of unstructured prose:
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;
class ReviewAgent implements Agent, HasStructuredOutput
{
use Promptable;
public function instructions(): string
{
return 'Review the draft for accuracy, clarity, and missing information.';
}
public function schema(JsonSchema $schema): array
{
return [
'approved' => $schema->boolean()->required(),
'score' => $schema->integer()->min(1)->max(10)->required(),
'issues' => $schema->array()
->items($schema->string())
->required(),
];
}
}
The caller can then make a deterministic decision:
$review = (new ReviewAgent)->prompt($draft->text);
if (! $review['approved']) {
$draft = (new DraftAgent)->prompt([
'draft' => $draft->text,
'issues' => $review['issues'],
]);
}
Chaining works best when the sequence is known and each step depends on the previous one.

Routing: send each request to the right specialist
Routing separates classification from execution.
A lightweight router examines the request and returns a structured decision. Your PHP code then selects the appropriate specialist agent.
use Illuminate\Contracts\JsonSchema\JsonSchema;
use Laravel\Ai\Attributes\UseCheapestModel;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Contracts\HasStructuredOutput;
use Laravel\Ai\Promptable;
#[UseCheapestModel]
class RequestRouter implements Agent, HasStructuredOutput
{
use Promptable;
public function instructions(): string
{
return <<<'PROMPT'
Classify the request.
Choose one specialist:
- billing
- technical
- account
Return only the requested structured fields.
PROMPT;
}
public function schema(JsonSchema $schema): array
{
return [
'specialist' => $schema->string()
->enum(['billing', 'technical', 'account'])
->required(),
'complexity' => $schema->string()
->enum(['standard', 'complex'])
->required(),
];
}
}
The UseCheapestModel attribute is useful here. Classification is usually a smaller task than technical analysis, so it does not always need your most capable model.
The orchestration code stays explicit:
use App\Ai\Agents\AccountAgent;
use App\Ai\Agents\BillingAgent;
use App\Ai\Agents\RequestRouter;
use App\Ai\Agents\TechnicalAgent;
$route = (new RequestRouter)->prompt($message);
$agent = match ($route['specialist']) {
'billing' => new BillingAgent,
'technical' => new TechnicalAgent,
'account' => new AccountAgent,
};
$response = $agent->prompt($message);
return [
'specialist' => $route['specialist'],
'answer' => $response->text,
];
Give each specialist only the tools it needs. A billing agent might access invoice lookup tools. A technical agent might search documentation or inspect product configuration.
This keeps prompts smaller and tool selection more reliable.
Parallelization: run independent agents concurrently
Parallelization is useful when several agents can inspect the same input without depending on one another.
Consider a code review workflow. Security, performance, and readability reviews can run independently. A synthesis agent can combine the results afterward.
Laravel’s Concurrency facade provides this fan-out pattern:
use App\Ai\Agents\PerformanceReviewAgent;
use App\Ai\Agents\ReadabilityReviewAgent;
use App\Ai\Agents\SecurityReviewAgent;
use App\Ai\Agents\SynthesisAgent;
use Illuminate\Support\Facades\Concurrency;
[$security, $performance, $readability] = Concurrency::run([
fn () => (new SecurityReviewAgent)->prompt($code),
fn () => (new PerformanceReviewAgent)->prompt($code),
fn () => (new ReadabilityReviewAgent)->prompt($code),
]);
$summary = (new SynthesisAgent)->prompt([
'code' => $code,
'security' => $security->text,
'performance' => $performance->text,
'readability' => $readability->text,
]);
return $summary->text;
Without concurrency, the total response time is close to the sum of all three reviews. With concurrency, the independent work can overlap.
Do not parallelize dependent steps. A formatter cannot run before the writer produces a draft. That workflow should remain sequential.

Laravel queues: run workflows outside the request
AI requests can take longer than a typical HTTP request. Multi-agent workflows also introduce more external calls and more failure points.
The AI SDK supports queued agent invocations through queue():
(new ReportAgent)
->queue($payload)
->then(function ($response) {
Report::whereKey($payload['report_id'])->update([
'status' => 'complete',
'result' => $response->text,
]);
})
->catch(function (Throwable $exception) use ($payload) {
Report::whereKey($payload['report_id'])->update([
'status' => 'failed',
'error' => $exception->getMessage(),
]);
});
For larger workflows, create a normal Laravel job. The job can persist progress between stages and use Laravel’s retry, timeout, and failure handling.
namespace App\Jobs;
use App\Services\ReportWorkflow;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Queue\Queueable;
class RunReportWorkflow implements ShouldQueue
{
use Queueable;
public function __construct(
public int $reportId,
) {}
public function handle(ReportWorkflow $workflow): void
{
$workflow->run($this->reportId);
}
}
Dispatch it from a controller:
RunReportWorkflow::dispatch($report->id)
->onQueue('ai-workflows');
For distributed fan-out, Laravel job batches are another option. Each specialist review can become its own queued job. A final job runs after the batch completes and combines the stored results.
use Illuminate\Support\Facades\Bus;
$batch = Bus::batch([
new RunSecurityReview($report->id),
new RunPerformanceReview($report->id),
new RunReadabilityReview($report->id),
])->then(function () use ($report) {
RunSynthesisAgent::dispatch($report->id);
})->catch(function () use ($report) {
$report->update(['status' => 'failed']);
})->name('AI code review')->dispatch();
Use Concurrency::run() for a small number of independent calls inside one process. Use job batches when you need distributed workers, progress tracking, retries per step, or durable intermediate results.
Set queue timeouts longer than the expected agent call duration. Also configure provider HTTP timeouts. A queue worker timeout should remain shorter than the connection’s retry_after value.
Build a REST API with PHP for agent orchestration
The following example exposes a simple JSON endpoint. It routes a request, runs specialist agents, and returns a final response.
namespace App\Http\Controllers;
use App\Ai\Agents\AccountAgent;
use App\Ai\Agents\BillingAgent;
use App\Ai\Agents\RequestRouter;
use App\Ai\Agents\ResponseAgent;
use App\Ai\Agents\TechnicalAgent;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class AgentWorkflowController
{
public function __invoke(Request $request): JsonResponse
{
$validated = $request->validate([
'message' => ['required', 'string', 'max:10000'],
]);
$route = (new RequestRouter)->prompt($validated['message']);
$specialist = match ($route['specialist']) {
'billing' => new BillingAgent,
'technical' => new TechnicalAgent,
'account' => new AccountAgent,
};
$specialistResponse = $specialist->prompt($validated['message']);
$final = (new ResponseAgent)->prompt([
'original_message' => $validated['message'],
'specialist' => $route['specialist'],
'specialist_response' => $specialistResponse->text,
]);
return response()->json([
'data' => [
'specialist' => $route['specialist'],
'answer' => $final->text,
],
]);
}
}
Register the endpoint in routes/api.php:
use App\Http\Controllers\AgentWorkflowController;
use Illuminate\Support\Facades\Route;
Route::post('/agent-workflows', AgentWorkflowController::class);
A request now looks like this:
POST /api/agent-workflows
Content-Type: application/json
{
"message": "Why was my latest invoice charged twice?"
}
The API returns a stable JSON shape:
{
"data": {
"specialist": "billing",
"answer": "..."
}
}
Add authentication, authorization, rate limiting, and request validation before exposing this endpoint publicly. If the workflow is slow, return a workflow ID immediately and process the orchestration through a queued job.

Laravel Boost: a PHP developer tool for AI-assisted development
The Laravel AI SDK adds AI features to your application. Laravel Boost serves a different purpose.
Boost is a PHP developer tool for AI-assisted Laravel development. It gives coding agents project context through a Laravel-specific MCP server, versioned guidelines, and Laravel ecosystem documentation.
Install it as a development dependency:
composer require laravel/boost --dev
php artisan boost:install
Boost can help an AI coding agent inspect routes, read configuration, inspect database schemas, search versioned documentation, run Tinker, and generate tests that match your project.
This distinction is useful:
- Laravel AI SDK: Build AI features for application users.
- Laravel Boost: Help developers build Laravel applications with AI.
- Laravel MCP: Expose application capabilities to external AI clients.
Boost can help you scaffold the agents, jobs, API resources, and tests used by the workflow in this article. Review every generated change and run your test suite before deployment.
Production checklist
Before shipping a multi-agent workflow, verify the following:
- Use structured output between agents that exchange data.
- Keep each agent’s instructions and tools narrowly scoped.
- Use a cheap model for classification when appropriate.
- Set explicit model and timeout values for predictable behavior.
- Queue workflows that exceed normal request limits.
- Use job batches for distributed parallel work.
- Persist intermediate results when retries matter.
- Add rate limits for provider APIs and user-facing endpoints.
- Use Laravel AI SDK fakes and assertions in tests.
- Monitor queue depth, failures, latency, and token usage.
- Add human approval before irreversible tool actions.
Multi-agent systems become easier to maintain when the workflow remains ordinary Laravel code. Agents are PHP classes. Orchestration is a service. Long-running work belongs in queues. Results belong in validated, observable application boundaries.
Start with one focused agent. Add chaining, routing, or parallelization only when the task requires it.