A YC company launched a cloud coding agent platform this morning. Buried in the launch post is the architecture note that interests me more than the product:
Everything is hosted on AWS, with the exception of: Temporal for durable workflows, Modal for sandboxes, and Planetscale for our database. Our infra decisions were driven by a strong belief that agents are becoming a tier 0 piece of infrastructure, and they need the reliability and security to match that.
That belief is correct. The conclusion they drew from it is also correct: if agents are tier 0, they need the reliability properties of tier 0 systems. What is worth noticing is which dependency they added to get there. Not a model. Not an agent framework. A durable workflow engine.
This keeps happening. Teams start with the model, discover the model was the easy part, and end up assembling a distributed systems stack around it.
I want to argue something that sounds parochial and is not:
The agent loop is a
whileloop. Everything that makes it survivable in production is ordinary backend infrastructure, and a mature application framework is a reasonable place to already have it.
I build in Laravel. That is a disclosure, not a thesis. The thesis is about which layer the difficulty actually lives in.
The loop is not the hard part
Strip an agent to its mechanism and you get: send context to a model, receive a response, if the response requests a tool then execute it, append the result, repeat until done or out of budget.
That is not hard to write. It is hard to operate. The operational questions arrive immediately and none of them are about intelligence:
- The process died forty minutes into a run that has already spent real money. Does it resume, or start over?
- A tool call succeeded and then the run crashed before recording it. Does the retry issue the refund twice?
- The run is looping. What stops it, and at what threshold, and who gets told?
- Six months later, someone asks why the system took an action. Can you reconstruct it?
- The model wants to do something consequential. Can a person intervene before it happens rather than after?
Every one of those is a problem the backend world solved before large language models existed. Queues, retries with backoff, idempotency keys, dead letter handling, distributed locks, batch tracking, event logs, approval workflows. The agentic wave did not create these problems. It made them mandatory for a category of application that previously did not have them.
This is why the durable execution vendors have had such a good year. Teams reach for Temporal or an equivalent because they have discovered that the thing they need is not a smarter agent. It is a run contract that survives a process restart.
What Laravel had before it needed it
Laravel's queue system was not designed for agents. It was designed for sending email and processing uploads. The properties it grew for those jobs turn out to be the properties an agent run needs, which is a fairly common pattern in infrastructure: the boring layer built for boring reasons ends up load bearing for something nobody anticipated.
The relevant primitives, all of which predate the current cycle:
Retries with backoff, and failure that is a first class outcome. A failed job is a record, not a lost process. It has an exception, an attempt count, and a retry path.
Batches. A group of related jobs with progress, cancellation, and completion callbacks. An agent run that fans out to six subtasks is a batch.
Unique jobs and distributed locks. The mechanism that stops two workers doing the same work.
Scheduling. Unattended, recurring, timed. The overnight run is a cron entry, not a bespoke daemon.
Three things landed at Laracon US last week that read differently once you are thinking about agents rather than email:
Refreshable locks. Long running work has always forced a bad choice between a lock too short to cover the work and a lock too long to release cleanly on failure. Now you take a short lock, do one unit of work, and call $lock->refresh() to extend it. If the job dies, the lock expires in seconds. An agent run is exactly the shape of work that breaks fixed duration locks: unpredictable length, frequent failure, expensive duplication.
Debounced jobs. Repeated dispatches inside a window collapse into one run. Laravel's example is reindexing a product edited five times in quick succession. The agentic version is a file watcher, a webhook, or an upstream agent triggering the same downstream analysis repeatedly.
Managed queues that scale to zero. Workers on isolated compute, scaling on queue depth, dropping to zero when empty, with failed jobs and their reasons visible in a dashboard with one click retry. Laravel reports idle workers waking in under a second and jobs of up to an hour. That is a managed agent worker pool that happens not to be marketed as one.
None of this is exotic. That is the point. The reason it matters is that an agent platform assembled from a model provider, a sandbox vendor, and a workflow engine has three vendors, three failure modes, and three billing relationships in the layer where a Laravel application has php artisan queue:work.
The tell: where they put the MCP client
Framework marketing is unreliable evidence. Framework architecture decisions are better evidence, because they reveal what the team thinks the shape of the problem is.
When Laravel shipped MCP client support, the obvious move was to put it inside the AI SDK, next to the agents. They did not. They put the client in laravel/mcp and left a thin integration in laravel/ai. Their stated reason:
A Laravel app might want to talk to an MCP server from a queued job or a console command, with no agent in sight.
The team building the AI SDK assumed the interesting caller is a queued job, not a chat interface. They designed the protocol layer to be usable with no agent present at all.
The same instinct shows up in the human in the loop API that shipped at Laracon. Approval is not a global setting, it is a predicate on the arguments:
protected function needsApproval(Request $request): Approval|bool
{
return $request['amount'] <= 2000
? false
: Approval::required('Refunds over $20 need a manager.');
}
That is policy as code at the tool boundary: a function that receives the actual arguments of the actual call and returns a decision. Under it sits a new approval_state column on conversation messages and a storeApprovalResults() method that custom conversation stores must implement, which is to say the approval is persisted state, not an in memory pause. The paused run can be resumed by a different process on a different machine on a different day.
And then there is the detail I find most telling, from the release documentation:
Pauses are per call, not per step. Tools in the same step that don't require approval run immediately, so keep external side effects idempotent using
$request->toolCallId().
That is an idempotency key. In an AI SDK. Documented with the same matter of fact tone you would use for a payments integration, because it is the same problem. Somebody on that team has operated a distributed system before.
Where this argument is weak
I would rather state the objections than have them stated back to me.
A queue is not a workflow engine. This is the real one. Temporal gives you durable execution of arbitrary application code: the workflow function itself is replayable, its local variables survive a crash, and you can version workflows that are already in flight. Laravel's queue gives you durable jobs. If a job dies halfway, it retries from the top of the job, not from the line it died on. You get durability at the boundary you chose when you decided where one job ends and the next begins. For agent work that is often good enough, because the natural boundary is the tool call and you want to reconsider from there anyway. But it is a weaker guarantee, it puts the decomposition burden on you, and anyone who tells you the two are equivalent is selling something.
The model tooling ecosystem is Python. Research code, new inference libraries, evaluation harnesses, and most reference implementations land in Python first. If your work sits close to model internals, this argument does not apply to you. It applies to people building applications that call models, which is most people.
The SDK is young. laravel/ai was at v0.10.0 as of 21 July. Version zero means the API will move. Some of what I have described is weeks old and has not been run in anger by very many teams, including the human in the loop API. Treat the primitives as real and the surface as unsettled.
PHP concurrency is not a strength. If your architecture needs thousands of concurrently in flight agent conversations inside one process, this is the wrong runtime. Laravel's answer is worker processes and queue depth, which is a fine answer for factory work and a poor one for a high fanout streaming product.
I have a stake in this. I run a Laravel practice. Read the argument, not the author.
What this changes in my factory
The practical consequence of taking this seriously is that I stopped looking for an agent framework and started treating the agent as a job.
The unit of durability is the tool call, not the run. Because a queued job retries from the top, the job boundary is a design decision rather than an implementation detail. I draw the boundary where I would want to resume from, which for consequential work means one tool call per job, with the conversation persisted between them.
Side effects carry idempotency keys, always. Not when convenient. The tool call ID exists for this, and the failure it prevents is the expensive kind: the refund issued twice, the email sent twice, the migration applied twice.
The spend fuse lives in the queue, not the prompt. Budget enforcement in the system prompt is a request. Budget enforcement in the worker is a control. Failed budget checks stop the line, they do not warn.
Approval is a predicate, not a checkpoint. "Ask me before doing anything dangerous" is not implementable. needsApproval($request) receiving actual arguments is. The interesting design work is writing those predicates, and it is the same work as writing authorization policies, which the framework has opinions about already.
Health checks are agent facing. artisan doctor runs environment and configuration checks and fixes what it safely can. Laravel's own framing is that this is a natural last step for a coding agent before it considers a task done. A machine readable, framework maintained "is this application sane" check is a cheap verification gate that I did not have to write.
What to ask before adding an agent framework
If you are about to add an orchestration dependency, five questions first:
- What happens to a run that is interrupted forty minutes in, and who pays for the restart?
- Which of your tool calls are safe to execute twice, and how do you know?
- What stops a run: a token limit, a wall clock, a spend threshold, or nothing?
- When something goes wrong in six months, what will you read to reconstruct why the system acted?
- Which of these does your existing framework already do, and which are you actually missing?
If the answer to question five is "most of them, already," you may need a model client and a queue rather than a platform.
Agent infrastructure is converging on properties that mature web frameworks have had for a decade. Check what you already own before you buy it again.
Research and source trail for this area live in the Orchestration, state, concurrency & recovery and Execution environments, identity & secrets subsystems. If your team is moving from AI-assisted coding toward autonomous production, Agency Intentional works on the harness, verification, and operating boundaries that make that transition safe enough to use.