Now make sure nothing goes around the boundary.
Callers get an agent from the container or construct one with the workspace, and that is the only way to reach a model:
$copy = SiteCopywriter::make(workspace: $workspace)
->prompt($brief);
A caller cannot see which provider answered, cannot build a provider with a different key, and cannot forget the timeout, because the timeout is on the class.
What it can still do, on a busy Friday, is call Http::post() against a provider URL from a controller. So write that down as a rule a test can enforce:
arch('only the AI layer builds providers')
->expect('Laravel\Ai\Ai')
->toOnlyBeUsedIn('App\Ai');
arch('nobody calls a provider by hand')
->expect('App')
->not->toUse(['OpenAI', 'Anthropic']);
Two architecture tests, a few milliseconds each. The first keeps the building of providers, and with it every credential, inside app/Ai. The second stops a vendor SDK being installed and used beside the boundary. Neither checks your judgement. Both check that there is still one place to look.
When You Still Write Your Own
There is one part of the running system that does not go through the SDK, and you should know why before you meet it.
The agent from the preface talks to Anthropic’s Messages API directly, through a hand-written client. It uses a provider feature, a connector that lets the provider call our MCP server itself, which sits outside what a provider-neutral SDK models. It also depends on stop reasons that a neutral abstraction has to fold together. Chapter 8 shows that loop and what it costs to own it.
The rule is simple. Use the SDK unless you need something it does not express. When you do, put the hand-written client behind the same kind of boundary: one class, one place for the credential, the timeout and the retry policy, and an architecture test that keeps it there. The boundary was always the point. The SDK just means you usually do not have to build it.
What This Bought
Take stock, because it is easy to look at a few small classes and conclude you have written ceremony.
You now have one place where each use of a model is described, with its cost and its time limit in the file. One place where credentials are resolved per tenant, with an error that says where to look. A retry policy that cannot bill you twice for a slow afternoon. A test that fails the day someone goes around all of it.
And you have somewhere to put the next lesson. That is the part that compounds. Everything the rest of this book teaches (structured output validation, cost ceilings, cancellation, observability) lands cleanly because there is a boundary to land on. Without it, each of those becomes a change to every call site.
What This Does Not Solve Yet
At this stage the endpoint is honest but naive. It trusts that the response has the structure the feature needs, which is not yet true. It has no ceiling on how many calls a tenant can make. It runs inside the HTTP request, which will not survive a long turn. And its prompt is a constant nobody has looked at, which is the subject of Chapter 3.
Every one of those is a chapter, and every one is a change inside this boundary rather than across your application.
Decisions This Chapter Made
- Use the SDK, and read its defaults as questions. Every default is a decision made for nobody in particular.
- One agent class per thing the application wants. Name it after the use, never after the model.
- Build the provider per tenant, from a stored credential, with no shared fallback.
- Fail at the boundary with your own message, and a status that says the dependency is missing.
- Never retry a timeout. The provider is still generating, and it will bill you for both.
- Keep one road in, and let an architecture test guard it.