Originally published at hafiz.dev
Most Laravel developers building AI features make the same mistake. They read about multi-agent patterns, get excited, and wire up an orchestrator-workers system on their very first feature. A week later they're debugging dynamic planning logic, API costs are unpredictable, and the feature still hasn't shipped.
The Laravel AI SDK makes spinning up agents so easy that it lowers the barrier to overengineering. That's not a criticism of the SDK. It's just a trap worth naming before you fall into it.
Anthropic's original research identified five multi-agent patterns. The official Laravel blog covered all five on March 13 with clean code examples. What it didn't say is which ones you should actually reach for first, and which ones will cost you more than they're worth in a typical SaaS context.
This is that post.
A quick note on approach: all the code examples below use proper Agent classes generated with php artisan make:agent, not the agent() helper shorthand you'll see in most tutorials. The helper is great for prototyping. But in production, you want Agent classes. They're testable with the SDK's built-in fakes, the instructions live in one place, and when a prompt breaks in production you know exactly where to find it.
What Multi-Agent Patterns Actually Are
A single agent is one AI call with a system prompt. You send a prompt, you get a response. Simple. Multi-agent patterns are structured ways to chain, route, or run multiple AI calls together so complex tasks get broken into focused steps.
The five patterns Anthropic identified: prompt chaining, routing, parallelization, orchestrator-workers, and evaluator-optimizer. Each solves a different problem. Three of them are practical for most Laravel SaaS apps today. Two of them are expensive and difficult to debug unless you've got a very specific use case.
Let's go through the three worth shipping first.
Pattern 1: Prompt Chaining
This is the assembly line. Agent A does step 1 and passes its output to Agent B, which does step 2 and passes to Agent C. Each agent has one job and does it well.
Laravel's built-in Pipeline handles this naturally. Each step wraps a dedicated Agent class and enriches the payload before passing it forward.
Here's a real-world example: a lead enrichment pipeline for a CRM. A salesperson drops in a company name, and three agents run in sequence to produce a ready-to-send outreach email.
php artisan make:agent LeadResearcher
php artisan make:agent LeadScorer
php artisan make:agent OutreachDrafter
Each Agent class defines focused instructions:
<?php
namespace App\Ai\Agents;
use Laravel\Ai\Contracts\Agent;
use Laravel\Ai\Promptable;
class LeadResearcher implements Agent
{
use Promptable;
public function instructions(): string
{
return 'You are a B2B lead researcher. Given a company name, write a 3-sentence brief: what they do, their likely tech stack, and their growth stage. Be concise and factual.';
}
}
The pipeline steps wrap each agent and pass the enriched payload forward:
<?php
namespace App\Ai\Pipeline;
use App\Ai\Agents\LeadResearcher;
use Closure;
class ResearchStep
{
public function handle(array $payload, Closure $next): array
{
$brief = (string) (new LeadResearcher)->prompt($payload['company']);
return $next([...$payload, 'research' => $brief]);
}
}
Wire it together in a controller or job:
use Illuminate\Support\Facades\Pipeline;
$result = Pipeline::send(['company' => 'Acme Corp'])
->through([
ResearchStep::class,
ScoringStep::class,
OutreachStep::class,
])
->thenReturn();
That's it. Three agents, three focused jobs, one clean result. You can test each step independently, which is genuinely valuable when something breaks in production. The instructions live in their own class, so updating the researcher prompt doesn't touch the scorer or the drafter.
Chaining also scales well with complexity. Need to add a "tone check" step between scoring and drafting? Add one pipeline class. Need to skip the scoring step for returning customers? Add a conditional in that step. The structure absorbs changes cleanly, which is more than you can say for a single 800-word system prompt trying to do five things at once.
Use chaining when: the task has a clear sequence where each step depends on the previous one. Draft, validate, refine. Extract, classify, format. Research, score, write.
Pattern 2: Routing
Routing means classifying the input first, then sending it to the right specialist. One agent reads the request and decides which agent should handle it. Different input types get different instructions. Different complexity levels can get different models and different costs.
This is the pattern that makes the most economic sense for support bots and anything where inputs vary widely.
php artisan make:agent TicketClassifier
php artisan make:agent BillingAgent
php artisan make:agent TechnicalAgent
php artisan make:agent GeneralSupportAgent
The classifier returns a single category word, nothing else:
class TicketClassifier implements Agent
{
use Promptable;
public function instructions(): string
{
return 'Classify the support ticket into exactly one of: billing, technical, general. Respond with just the category word, nothing else.';
}
}
The router reads that category and dispatches to the right specialist:
<?php
namespace App\Support;
use App\Ai\Agents\BillingAgent;
use App\Ai\Agents\GeneralSupportAgent;
use App\Ai\Agents\TechnicalAgent;
use App\Ai\Agents\TicketClassifier;
class SupportRouter
{
public function route(string $ticket): string
{
$category = (string) (new TicketClassifier)->prompt($ticket);
return match (trim(strtolower($category))) {
'billing' => (string) (new BillingAgent)->prompt($ticket),
'technical' => (string) (new TechnicalAgent)->prompt($ticket),
default => (string) (new GeneralSupportAgent)->prompt($ticket),
};
}
}
You can take this further by specifying different providers per agent in the prompt() call. Simple billing questions can go to a cheaper, faster model. Complex technical issues go to a more capable one. The classifier call pays for itself quickly once you're handling real volume.
Here's what that looks like for the technical agent specifically:
use Laravel\Ai\Enums\Lab;
// In TechnicalAgent's caller, or via the prompt() override:
(string) (new TechnicalAgent)->prompt(
$ticket,
provider: Lab::Anthropic,
model: 'claude-opus-4-5-20251101'
);
// Billing uses the default cheaper model from config/ai.php
(string) (new BillingAgent)->prompt($ticket);
The total cost for a billing ticket drops significantly while technical tickets still get the full model. That's a cost profile that actually makes sense in production, especially at scale.
Here's what the routing flow looks like:
Use routing when: inputs vary significantly in type or complexity, and a single prompt can't handle all cases cleanly without turning into a mess of conditional instructions.
Pattern 3: Parallelization
When multiple agents need to look at the same input independently, there's no reason to run them one by one. Laravel's Concurrency::run() lets you kick all of them off simultaneously and collect results when they're done.
The time difference is real. Three agents in parallel takes roughly the same wall-clock time as one. Three agents in sequence takes three times as long.
Here's a document analysis example. You've got a contract and want legal, financial, and risk assessments all at once:
php artisan make:agent LegalReviewer
php artisan make:agent FinancialAnalyzer
php artisan make:agent RiskAssessor
php artisan make:agent ContractSummaryAgent
Run the three specialists in parallel, then feed their outputs to a summary agent:
use App\Ai\Agents\ContractSummaryAgent;
use App\Ai\Agents\FinancialAnalyzer;
use App\Ai\Agents\LegalReviewer;
use App\Ai\Agents\RiskAssessor;
use Illuminate\Support\Facades\Concurrency;
[$legal, $financial, $risk] = Concurrency::run([
fn () => (string) (new LegalReviewer)->prompt($contract),
fn () => (string) (new FinancialAnalyzer)->prompt($contract),
fn () => (string) (new RiskAssessor)->prompt($contract),
]);
$summary = (string) (new ContractSummaryAgent)->prompt(
"Legal review:\n{$legal}\n\nFinancial analysis:\n{$financial}\n\nRisk assessment:\n{$risk}"
);
The ContractSummaryAgent only runs after all three are done, so it has the full picture. This pattern also pairs naturally with queued jobs when you're processing documents asynchronously. If you're not already confident with Laravel queues at scale, this breakdown on processing large job volumes covers the fundamentals worth knowing before you lean into async agent pipelines.
Note: Concurrency::run() was introduced in Laravel 11. If you're still on Laravel 10, you'll need to handle parallelism through process pools or queued jobs instead.
One thing worth handling in production: if one of the parallel agents fails, Concurrency::run() will throw. Wrap it in a try/catch and decide whether you want to fail the whole request or fall back to running the failed agent synchronously. For document analysis, falling back gracefully is usually better than failing the whole operation over one specialist's timeout.
Use parallelization when: multiple independent specialists need to look at the same input, or when you need several separate analyses that don't depend on each other's results.
The Two Patterns to Skip (For Now)
Orchestrator-Workers
This one sounds compelling. A manager agent receives a complex task, breaks it into subtasks dynamically, delegates to worker agents, and assembles the result.
The problem in practice: the orchestrator runs an internal agentic loop. It needs to reason about what steps are required, call worker tools in an order it determines at runtime, and figure out when it's done. That means you can't predict token usage, you can't easily trace what happened when something breaks, and testing becomes genuinely hard.
For most SaaS features, the required steps aren't actually unknown at runtime. You just think they are. Nine times out of ten, you can replace a dynamic orchestrator with a well-structured chaining pipeline and end up with something cheaper, faster, and debuggable. A three-step pipeline that you wrote is easier to maintain than a planning loop you're hoping the model executes correctly.
The orchestrator pattern earns its place when the task genuinely varies in structure. A code generation agent that might need to create five files or fifteen depending on the feature request. A research agent that needs to decide how many sources to query. If your task has a predictable shape, use chaining. You'll know when orchestration is actually necessary because a fixed pipeline genuinely can't handle the variability.
Evaluator-Optimizer
This pattern loops: generate output, score it, rewrite if it doesn't meet the bar, repeat up to N times. Sounds great for quality. It's brutal on cost.
Think about what "3 refinement iterations" actually means in production. That's up to four API calls per user action: one write, one evaluate, one rewrite, one final evaluate. If you're generating content at any real volume, that multiplier compounds fast. A feature that processes 1,000 requests per day and costs $0.01 per single-agent call becomes $0.04 with a 3-iteration evaluator loop. Doesn't sound like much until it's running for a month.
There's also the latency problem. Each iteration adds a full round-trip to an AI provider. If you're doing this synchronously in a web request, you're looking at 10-20 seconds of wait time by the third iteration. That's not a user experience you want to ship.
The evaluator pattern genuinely earns its place when output quality is business-critical and a human would otherwise review every result. Legal document drafting. Medical content. Anything where a bad output has real consequences. For most SaaS AI features, a single well-prompted agent with a clear output schema and structured output validation does the job at a fraction of the cost.
If you're building a RAG-powered support system and want better response quality without retry loops, the Part 2 tutorial on tools and memory covers how to get more out of a single agent before reaching for the evaluator.
A Simple Decision Framework
Before picking a pattern, ask two questions: does the task have a predictable structure, and do the steps depend on each other?
| Situation | Pattern |
|---|---|
| Clear sequence, each step depends on the previous | Chaining |
| Inputs vary in type or complexity | Routing |
| Multiple independent analyses of the same input | Parallelization |
| Steps are genuinely unknown until runtime | Orchestrator-workers |
| Output quality needs iterative refinement | Evaluator-optimizer |
Start with a single well-prompted Agent class. If one agent can't do the job cleanly, reach for chaining first. Then routing or parallelization if the shape fits. The orchestrator and evaluator are there when you actually need them, and you'll know when you do.
FAQ
Do I need to pick one pattern, or can I mix them?
You can mix them freely. A routing pattern that dispatches tickets to specialist agents could use chaining inside the technical agent for complex multi-step resolution. The patterns compose. Just start with the simplest thing that works and layer from there.
What's the actual cost difference between chaining and parallelization?
Same number of API calls, different wall-clock time. Chaining runs them in sequence so total time is the sum of all agent response times. Parallelization runs them simultaneously so total time is roughly the slowest single agent. Costs are identical. Pick based on whether the steps depend on each other, not on cost.
Does this work with Laravel 11, 12, and 13?
Yes. The Laravel AI SDK supports all three. Concurrency::run() for parallelization requires Laravel 11 or later. For Laravel 10 you'll need a different approach to parallel execution. The Agent class patterns for chaining and routing work on any supported version.
How do I test multi-agent workflows?
The AI SDK includes built-in fakes. You can fake each Agent class in tests and assert it was called with the right prompt in the right order. The 30-minute SDK tutorial covers the testing utilities in detail before you start wiring up multi-step pipelines.
When should I use php artisan make:agent vs. the agent() helper?
The agent() helper is fine for one-off calls and prototyping. For production multi-agent workflows, use proper Agent classes. They're testable, reusable, and the instructions live in one place. When you need to update a system prompt, you know exactly where to look. The complete Claude Code and Laravel ecosystem guide covers how Agent classes fit into a larger agentic dev setup if you want the full picture.
Wrapping Up
Multi-agent patterns are a real leap in what you can build with AI in Laravel. But the best one to start with is almost always the simplest one that solves the actual problem. Get a single agent working first. Then use chaining to add structure, routing to handle variety, and parallelization to cut wait time. Save the orchestrator and evaluator for when the task genuinely demands them.
The Laravel AI SDK makes all of this feel like writing normal Laravel code. The Pipeline is already there. Concurrency is already there. You're just pointing agents at it. And because each Agent class is a plain PHP class with a well-defined interface, your tests stay clean and your prompts stay maintainable as the feature evolves.
The patterns you skip today aren't gone forever. Once you've shipped chaining in production and have a handle on your real API costs and latency profile, you'll have a much clearer sense of whether an orchestrator or evaluator is worth reaching for. Most of the time you'll find the simpler patterns got you further than you expected.
If you're building multi-agent workflows for a client project or a SaaS and want a second set of eyes on the architecture, get in touch.
Top comments (0)