[{"data":1,"prerenderedAt":698},["ShallowReactive",2],{"page-\u002Fblog\u002Flaravel-ai-agents-architecture-2026":3,"\u002Fblog\u002Flaravel-ai-agents-architecture-2026-surround":690},{"id":4,"title":5,"author":6,"body":11,"category":671,"date":672,"description":673,"extension":674,"image":675,"meta":676,"navigation":175,"ogImage":682,"path":683,"published":175,"robots":682,"schemaOrg":682,"seo":684,"seoDescription":685,"seoTitle":686,"sitemap":687,"stem":688,"__hash__":689},"blog\u002Fblog\u002Flaravel-ai-agents-architecture-2026.md","Laravel + AI in 2026: A Practical Architecture for Adding Agents to Existing Apps",{"name":7,"role":8,"imageUrl":9,"href":10},"Bret Oreta","Admin","https:\u002F\u002Fbretoreta.me\u002Fimages\u002Fopt\u002Favatar-96.webp","https:\u002F\u002Fbretoreta.me\u002Fabout",{"type":12,"value":13,"toc":657},"minimark",[14,18,29,34,45,48,52,55,74,77,81,84,101,104,430,433,437,440,443,460,463,467,470,473,491,494,498,501,518,521,524,528,531,534,538,541,549,553,556,577,580,583,603,607,610,613,616,620,623,626,630,633,636,653],[15,16,17],"p",{},"You do not need to rewrite your Laravel application to add AI. You need a clean boundary between the model and the parts of your system that already work.",[15,19,20,21,28],{},"Laravel is well suited to this. It already has ",[22,23,27],"a",{"href":24,"rel":25},"https:\u002F\u002Flaravel.com\u002Fdocs\u002Fqueues",[26],"nofollow","queues",", events, HTTP clients, policies, validation, scheduling, and a mature testing story. The AI layer can be another application service, not a second application.",[30,31,33],"h2",{"id":32},"the-architecture-in-one-picture","The architecture in one picture",[35,36,42],"pre",{"className":37,"code":39,"language":40,"meta":41},[38],"language-text","HTTP \u002F API\n   ↓\nController → Agent service → Tool gateway → existing domain services\n                 ↓                ↓\n             model provider    policies + validation\n                 ↓\n          conversations, tool calls, usage, evals\n                 ↓\n          queue \u002F Horizon \u002F Redis\n","text","",[43,44,39],"code",{"__ignoreMap":41},[15,46,47],{},"The model never talks directly to the database. It asks for tools. The tool gateway checks the user, the tenant, the input, and the permission before calling the domain service.",[30,49,51],{"id":50},"start-with-a-use-case-not-a-framework","Start with a use case, not a framework",[15,53,54],{},"Pick one workflow with a clear outcome:",[56,57,58,62,65,68,71],"ul",{},[59,60,61],"li",{},"summarize an inbound support ticket and route it,",[59,63,64],{},"answer questions from approved documentation,",[59,66,67],{},"qualify a lead and draft a follow-up,",[59,69,70],{},"extract structured data from an uploaded document,",[59,72,73],{},"generate a first-pass report from existing metrics.",[15,75,76],{},"Then design the tools for that workflow. Avoid building a universal agent platform before you have one reliable use case.",[30,78,80],{"id":79},"the-tool-layer","The tool layer",[15,82,83],{},"A tool is a small, well-named capability. It should be:",[56,85,86,89,92,95,98],{},[59,87,88],{},"validated,",[59,90,91],{},"authorized,",[59,93,94],{},"logged,",[59,96,97],{},"idempotent where possible,",[59,99,100],{},"narrow enough to test.",[15,102,103],{},"For example:",[35,105,109],{"className":106,"code":107,"language":108,"meta":41,"style":41},"language-php shiki shiki-themes material-theme-lighter github-light github-dark","final class GetCustomerOrders\n{\n    public function __construct(private CustomerRepository $customers) {}\n\n    public function handle(int $customerId, int $limit = 5): array\n    {\n        $customer = $this->customers->findOrFail($customerId);\n        Gate::authorize('view', $customer);\n\n        return $customer->orders()\n            ->latest()\n            ->limit(min($limit, 10))\n            ->get(['id', 'status', 'total', 'created_at'])\n            ->toArray();\n    }\n}\n","php",[43,110,111,127,134,170,177,226,232,265,298,303,322,333,358,407,418,424],{"__ignoreMap":41},[112,113,116,120,123],"span",{"class":114,"line":115},"line",1,[112,117,119],{"class":118},"sbsja","final",[112,121,122],{"class":118}," class",[112,124,126],{"class":125},"sbgvK"," GetCustomerOrders\n",[112,128,130],{"class":114,"line":129},2,[112,131,133],{"class":132},"sP7_E","{\n",[112,135,137,140,143,147,150,153,157,160,164,167],{"class":114,"line":136},3,[112,138,139],{"class":118},"    public",[112,141,142],{"class":118}," function",[112,144,146],{"class":145},"sptTA"," __construct",[112,148,149],{"class":132},"(",[112,151,152],{"class":118},"private",[112,154,156],{"class":155},"sZMiF"," CustomerRepository",[112,158,159],{"class":132}," $",[112,161,163],{"class":162},"su5hD","customers",[112,165,166],{"class":132},")",[112,168,169],{"class":132}," {}\n",[112,171,173],{"class":114,"line":172},4,[112,174,176],{"emptyLinePlaceholder":175},true,"\n",[112,178,180,182,184,188,190,194,196,199,202,205,207,210,214,218,220,223],{"class":114,"line":179},5,[112,181,139],{"class":118},[112,183,142],{"class":118},[112,185,187],{"class":186},"sGLFI"," handle",[112,189,149],{"class":132},[112,191,193],{"class":192},"sw1J6","int",[112,195,159],{"class":132},[112,197,198],{"class":162},"customerId",[112,200,201],{"class":132},",",[112,203,204],{"class":192}," int",[112,206,159],{"class":132},[112,208,209],{"class":162},"limit ",[112,211,213],{"class":212},"smGrS","=",[112,215,217],{"class":216},"srdBf"," 5",[112,219,166],{"class":132},[112,221,222],{"class":212},":",[112,224,225],{"class":192}," array\n",[112,227,229],{"class":114,"line":228},6,[112,230,231],{"class":132},"    {\n",[112,233,235,238,241,243,247,250,252,254,257,260,262],{"class":114,"line":234},7,[112,236,237],{"class":132},"        $",[112,239,240],{"class":162},"customer ",[112,242,213],{"class":212},[112,244,246],{"class":245},"s39Yj"," $this",[112,248,249],{"class":212},"->",[112,251,163],{"class":162},[112,253,249],{"class":212},[112,255,256],{"class":186},"findOrFail",[112,258,259],{"class":132},"($",[112,261,198],{"class":162},[112,263,264],{"class":132},");\n",[112,266,268,271,274,277,279,283,287,289,291,293,296],{"class":114,"line":267},8,[112,269,270],{"class":155},"        Gate",[112,272,273],{"class":212},"::",[112,275,276],{"class":186},"authorize",[112,278,149],{"class":132},[112,280,282],{"class":281},"sjJ54","'",[112,284,286],{"class":285},"s_sjI","view",[112,288,282],{"class":281},[112,290,201],{"class":132},[112,292,159],{"class":132},[112,294,295],{"class":162},"customer",[112,297,264],{"class":132},[112,299,301],{"class":114,"line":300},9,[112,302,176],{"emptyLinePlaceholder":175},[112,304,306,310,312,314,316,319],{"class":114,"line":305},10,[112,307,309],{"class":308},"sVHd0","        return",[112,311,159],{"class":132},[112,313,295],{"class":162},[112,315,249],{"class":212},[112,317,318],{"class":186},"orders",[112,320,321],{"class":132},"()\n",[112,323,325,328,331],{"class":114,"line":324},11,[112,326,327],{"class":212},"            ->",[112,329,330],{"class":186},"latest",[112,332,321],{"class":132},[112,334,336,338,341,343,346,348,350,352,355],{"class":114,"line":335},12,[112,337,327],{"class":212},[112,339,340],{"class":186},"limit",[112,342,149],{"class":132},[112,344,345],{"class":145},"min",[112,347,259],{"class":132},[112,349,340],{"class":162},[112,351,201],{"class":132},[112,353,354],{"class":216}," 10",[112,356,357],{"class":132},"))\n",[112,359,361,363,366,369,371,374,376,378,381,384,386,388,390,393,395,397,399,402,404],{"class":114,"line":360},13,[112,362,327],{"class":212},[112,364,365],{"class":186},"get",[112,367,368],{"class":132},"([",[112,370,282],{"class":281},[112,372,373],{"class":285},"id",[112,375,282],{"class":281},[112,377,201],{"class":132},[112,379,380],{"class":281}," '",[112,382,383],{"class":285},"status",[112,385,282],{"class":281},[112,387,201],{"class":132},[112,389,380],{"class":281},[112,391,392],{"class":285},"total",[112,394,282],{"class":281},[112,396,201],{"class":132},[112,398,380],{"class":281},[112,400,401],{"class":285},"created_at",[112,403,282],{"class":281},[112,405,406],{"class":132},"])\n",[112,408,410,412,415],{"class":114,"line":409},14,[112,411,327],{"class":212},[112,413,414],{"class":186},"toArray",[112,416,417],{"class":132},"();\n",[112,419,421],{"class":114,"line":420},15,[112,422,423],{"class":132},"    }\n",[112,425,427],{"class":114,"line":426},16,[112,428,429],{"class":132},"}\n",[15,431,432],{},"The agent receives only the data it needs. It does not receive a database connection.",[30,434,436],{"id":435},"queues-for-long-running-work","Queues for long-running work",[15,438,439],{},"Model calls can be slow. Do not hold a web request open for a multi-step agent run unless the UX explicitly supports streaming.",[15,441,442],{},"Use queued jobs for:",[56,444,445,448,451,454,457],{},[59,446,447],{},"document ingestion and embedding,",[59,449,450],{},"agent runs that take more than a few seconds,",[59,452,453],{},"provider retries,",[59,455,456],{},"evaluation batches,",[59,458,459],{},"report generation.",[15,461,462],{},"Use broadcasting or server-sent events to stream progress back to the UI. Laravel Echo and a small event stream are often enough.",[30,464,466],{"id":465},"streaming-the-response","Streaming the response",[15,468,469],{},"For chat-style interfaces, stream tokens or status updates. A user should see that the system is working within a second, even if the full answer takes longer.",[15,471,472],{},"A simple approach:",[474,475,476,479,482,485,488],"ol",{},[59,477,478],{},"Create an agent run record.",[59,480,481],{},"Dispatch a job.",[59,483,484],{},"Stream status and output chunks.",[59,486,487],{},"Persist the final message and tool calls.",[59,489,490],{},"Emit a completion event.",[15,492,493],{},"If streaming is not possible, return a run ID and let the frontend poll a status endpoint.",[30,495,497],{"id":496},"vector-search-without-a-new-platform","Vector search without a new platform",[15,499,500],{},"For retrieval-augmented generation, you need:",[56,502,503,506,509,512,515],{},[59,504,505],{},"a chunking strategy,",[59,507,508],{},"embeddings,",[59,510,511],{},"a vector store,",[59,513,514],{},"metadata filters,",[59,516,517],{},"access control.",[15,519,520],{},"Options include PostgreSQL with pgvector, a managed vector database, or a search service. Start with the smallest option that fits your existing infrastructure.",[15,522,523],{},"Store the source document, chunk text, embedding, tenant ID, and access metadata. Filter by tenant and permission before the model ever sees the chunk.",[30,525,527],{"id":526},"permissions-and-tenancy","Permissions and tenancy",[15,529,530],{},"Every tool call must carry the authenticated user and tenant context. Do not rely on the model to provide a tenant ID from the prompt.",[15,532,533],{},"Use Laravel policies and scoped queries. If the current user cannot open the record in the UI, the agent cannot open it either.",[30,535,537],{"id":536},"prompt-injection-and-data-boundaries","Prompt injection and data boundaries",[15,539,540],{},"Retrieved documents are untrusted input. A PDF, email, or web page can contain instructions. Keep system instructions separate, validate tool arguments, and require approval for writes.",[15,542,543,544,548],{},"For a fuller checklist, read ",[22,545,547],{"href":546},"\u002Fblog\u002Fhow-to-secure-ai-agents-2026","How to Secure AI Agents",".",[30,550,552],{"id":551},"testing-and-evaluation","Testing and evaluation",[15,554,555],{},"You need three layers of tests:",[474,557,558,565,571],{},[59,559,560,564],{},[561,562,563],"strong",{},"Unit tests"," for tools and authorization.",[59,566,567,570],{},[561,568,569],{},"Integration tests"," with a fake model provider.",[59,572,573,576],{},[561,574,575],{},"Evaluation cases"," with real prompts and expected outcomes.",[15,578,579],{},"Laravel’s HTTP fake and queue fake make the first two straightforward. For evaluations, store a versioned set of cases and run them when prompts, models, or tools change.",[15,581,582],{},"Measure:",[56,584,585,588,591,594,597,600],{},[59,586,587],{},"task completion,",[59,589,590],{},"tool-call accuracy,",[59,592,593],{},"unauthorized access attempts,",[59,595,596],{},"latency,",[59,598,599],{},"cost per run,",[59,601,602],{},"human correction rate.",[30,604,606],{"id":605},"deployment-and-operations","Deployment and operations",[15,608,609],{},"Use Horizon or your queue of choice. Monitor queue depth, failed jobs, provider latency, token usage, and cost.",[15,611,612],{},"Add a kill switch: a config flag that disables agent runs or specific tools without a deploy.",[15,614,615],{},"Log enough to reconstruct a run, but redact secrets and personal data. Retention should be intentional.",[30,617,619],{"id":618},"when-to-use-a-package","When to use a package",[15,621,622],{},"A package can help with model clients, provider abstraction, or streaming. Use it if it reduces boilerplate without hiding authorization or tool execution.",[15,624,625],{},"Do not let a package own your permission model. Your application should still decide what a user can see and do.",[30,627,629],{"id":628},"the-bottom-line","The bottom line",[15,631,632],{},"Laravel already provides the architecture an AI feature needs: validation, policies, queues, events, and testing. The model should be a dependency inside that architecture, not a second system beside it.",[15,634,635],{},"Start with one workflow, build narrow tools, enforce permissions outside the model, and evaluate the result like any other production feature.",[15,637,638,639,643,644,648,649,548],{},"If you are adding AI to an existing Laravel app, ",[22,640,642],{"href":641},"\u002Fcontact","book a free call"," or ",[22,645,647],{"href":646},"\u002Fproject","see the kind of systems I build",". For the storage layer, read ",[22,650,652],{"href":651},"\u002Fblog\u002Fhow-to-use-cloudflare-r2-with-laravel-storage","Cloudflare R2 with Laravel",[654,655,656],"style",{},"html pre.shiki code .sbsja, html code.shiki .sbsja{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sptTA, html code.shiki .sptTA{--shiki-light:#6182B8;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZMiF, html code.shiki .sZMiF{--shiki-light:#E2931D;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sGLFI, html code.shiki .sGLFI{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sw1J6, html code.shiki .sw1J6{--shiki-light:#F76D47;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":41,"searchDepth":129,"depth":136,"links":658},[659,660,661,662,663,664,665,666,667,668,669,670],{"id":32,"depth":129,"text":33},{"id":50,"depth":129,"text":51},{"id":79,"depth":129,"text":80},{"id":435,"depth":129,"text":436},{"id":465,"depth":129,"text":466},{"id":496,"depth":129,"text":497},{"id":526,"depth":129,"text":527},{"id":536,"depth":129,"text":537},{"id":551,"depth":129,"text":552},{"id":605,"depth":129,"text":606},{"id":618,"depth":129,"text":619},{"id":628,"depth":129,"text":629},"Laravel","2026-10-10","How to add AI agents to an existing Laravel application with queues, HTTP clients, events, vector search, and guardrails — without rebuilding the stack.","md","\u002Fimages\u002Fblog\u002Flaravel-ai-agents-architecture-2026.png",{"tags":677},[678,679,108,680,27,681],"laravel","ai-agents","architecture","vector-search",null,"\u002Fblog\u002Flaravel-ai-agents-architecture-2026",{"title":5,"description":673},"A practical Laravel AI architecture: tool layers, queues, streaming, vector search, permissions, evaluation and production guardrails for existing apps.","Laravel AI Agents: A 2026 Architecture Guide",{"loc":683},"blog\u002Flaravel-ai-agents-architecture-2026","0kuBwpwWwYKsV7JgadP6t95hLfKxuaHdMHVIE2ISQDA",[691,695],{"title":692,"path":693,"stem":694,"children":-1},"AI Voice Agents for Lead Qualification in 2026: A Practical Playbook","\u002Fblog\u002Fai-voice-agents-lead-qualification-2026","blog\u002Fai-voice-agents-lead-qualification-2026",{"title":696,"path":651,"stem":697,"children":-1},"How to Use Cloudflare R2 with Laravel File Storage","blog\u002Fhow-to-use-cloudflare-r2-with-laravel-storage",1793181057998]