[{"data":1,"prerenderedAt":7452},["ShallowReactive",2],{"profile-data":3,"blog-post-\u002Fblog\u002F42-laravel-ai-agent-hybrid-backends":88,"series-Hybrid Backend Architecture":2092,"related-posts-\u002Fblog\u002F42-laravel-ai-agent-hybrid-backends":3568},{"id":4,"title":5,"availability":6,"avatar":20,"clientSatisfaction":21,"currentFocus":22,"description":26,"experience":27,"extension":28,"footer":29,"heroHeadline":32,"meta":33,"name":34,"pricingRanges":35,"projectsDelivered":46,"social":48,"stem":61,"tagline":62,"whoIWorkWith":63,"workApproach":83,"__hash__":87},"profile\u002Fprofile.yml","Senior Software Engineer | Full-Stack Developer | DevOps Enthusiast",{"status":7,"statusText":8,"startDate":9,"startDateContext":10,"description":11,"responseTime":12,"timezone":13,"slotsAvailable":14,"paymentTerms":15,"cta":16,"note":19},"available","Available for new projects","April 2025","Next opening","Open to freelance, consulting, and collaborative projects. Flexible with remote, async, and agile workflows. Comfortable working across time zones and with distributed teams.","3h","GMT+5",3,"20% upfront, rest on milestones",{"text":17,"url":18},"Email Now To Discuss Your Project Or Idea","mailto:mubaidr@gmail.com","I aim to reply as quickly as possible with the attention your message deserves.","\u002Fmubaidr.png",100,[23,24,25],"Infrastructure as Code (IaC)","Cloud-native tooling and observability","AI-assisted development workflows","Delivering robust, scalable, and user-focused software solutions that drive business success.",13,"yml",{"message":30,"lastUpdated":31},"Thank you for your interest. I look forward to collaborating and building something exceptional together.","2025-06-28T12:00:00.000Z","Senior Software Engineer building scalable systems and developer tools. Open source maintainer",{},"Muhammad Ubaid R.",{"mvp":36,"architectureAudit":41,"hourly":45},{"min":37,"max":38,"currency":39,"description":40},5000,15000,"USD","Full-stack MVP development",{"min":42,"max":43,"currency":39,"description":44},1500,3000,"System architecture review and recommendations",{"min":21,"max":46,"currency":39,"description":47},125,"Hourly consulting and development",[49,53,57],{"name":50,"url":51,"icon":52},"GitHub","https:\u002F\u002Fgithub.com\u002Fmubaidr","i-ph-github-logo",{"name":54,"url":55,"icon":56},"LinkedIn","https:\u002F\u002Fwww.linkedin.com\u002Fin\u002Fmubaidr","i-ph-linkedin-logo",{"name":58,"url":59,"icon":60},"X","https:\u002F\u002Fx.com\u002Fmubaidr","i-ph-x-logo","profile","Delivered 125+ projects with 100% client satisfaction. 13 years building scalable systems.",[64,68,72,75,79],{"name":65,"icon":66,"description":67},"Startups","i-ph-rocket-launch","Early-stage companies building MVPs and scaling products",{"name":69,"icon":70,"description":71},"SMEs","i-ph-buildings","Small to medium enterprises optimizing and modernizing systems",{"name":73,"icon":70,"description":74},"Enterprise","Large organizations requiring architecture and performance expertise",{"name":76,"icon":77,"description":78},"Agencies","i-ph-users-three","Digital agencies needing technical leadership and delivery support",{"name":80,"icon":81,"description":82},"Individual Founders","i-ph-user","Solo founders turning ideas into production-ready applications",[84,85,86],"Architecting solutions and managing product lifecycles - optimizing performance, scalability, and security","Leading agile teams and collaborating with clients - Ensuring clarity, timeliness, and adaptability in all project phases","Automating workflows and ensuring code quality","kah6FarLEWIb1aJERf-A2v09d44jRxlAI-CWvfBY268",{"id":89,"title":90,"abstract":91,"author":92,"authorUrl":93,"body":94,"date":2068,"dateUpdated":2068,"description":2069,"excerpt":2070,"extension":2071,"featured":215,"headline":90,"image":2070,"meta":2072,"navigation":215,"ogImage":2070,"path":2074,"seo":2075,"series":2076,"seriesDescription":2077,"seriesOrder":206,"socialImage":2078,"stem":2084,"tags":2085,"__hash__":2091},"blog\u002Fblog\u002F42-laravel-ai-agent-hybrid-backends.md","Laravel + AI Agent Systems: Building Hybrid Backends in 2026","Laravel is an excellent orchestration backend for AI agents. Queue-driven execution, structured API gateways for LLM systems, and proven SaaS patterns for hybrid architectures.","mubaidr","https:\u002F\u002Fmubaidr.js.org",{"type":95,"value":96,"toc":2055},"minimark",[97,101,105,108,112,115,123,158,161,165,168,179,182,185,189,192,195,657,660,686,690,693,718,721,725,728,871,878,882,885,1530,1533,1557,1561,1564,1567,1616,1619,1626,1716,1719,1844,1847,1851,1854,1999,2002,2006,2009,2035,2038,2042,2045,2048,2051],[98,99,90],"h2",{"id":100},"laravel-ai-agent-systems-building-hybrid-backends-in-2026",[102,103,104],"p",{},"When I started building AI agent systems in early 2025, my first instinct was to reach for Python. Everyone was using Python for AI work — LangChain, LlamaIndex, FastAPI. But as I moved from prototypes to production SaaS products, I kept running into the same problems: no built-in queue system, weak job persistence, and a fragmented ecosystem for background workers.",[102,106,107],{},"I chose Laravel. Not because PHP is better at ML inference, but because Laravel is better at the orchestration layer that surrounds AI agents. This post explains the architecture I settled on after a year of iteration, and why I believe Laravel is the best choice for hybrid backends that connect traditional web applications with AI agent systems.",[98,109,111],{"id":110},"why-laravel-for-ai-orchestration","Why Laravel for AI Orchestration?",[102,113,114],{},"The misconception that AI backends require Python ignores a critical distinction: running ML models and orchestrating AI agents are two different problems. Model training and inference benefit from Python's scientific computing ecosystem. Agent orchestration benefits from battle-tested job queues, database abstractions, and API management — all areas where Laravel excels out of the box.",[102,116,117,118,122],{},"My production setup uses Laravel for everything that happens ",[119,120,121],"em",{},"around"," AI agents:",[124,125,126,134,140,146,152],"ul",{},[127,128,129,133],"li",{},[130,131,132],"strong",{},"Queue management",": Laravel Horizon with Redis for dispatching and monitoring agent tasks",[127,135,136,139],{},[130,137,138],{},"State persistence",": MySQL for agent session state, task history, and result storage",[127,141,142,145],{},[130,143,144],{},"API gateway",": Rate limiting, token budgeting, and response caching for LLM calls",[127,147,148,151],{},[130,149,150],{},"User management",": Authentication, team accounts, billing — the standard SaaS stack",[127,153,154,157],{},[130,155,156],{},"Event system",": Broadcasting agent status updates via Laravel Reverb for real-time UI",[102,159,160],{},"The AI agents themselves run as isolated PHP worker processes, with the heavy inference work delegated to external LLM APIs via the gateway layer. This separation means I can scale the orchestration and inference layers independently.",[98,162,164],{"id":163},"architecture-overview","Architecture Overview",[102,166,167],{},"The system breaks down into four layers that communicate through well-defined interfaces:",[169,170,176],"pre",{"className":171,"code":173,"language":174,"meta":175},[172],"language-text","User Request (HTTP)        API Gateway (REST)\n       |                        |\n       v                        v\n  Laravel App \u003C--> Queue Dispatcher (Redis\u002FHorizon)\n       |                        |\n       |                        v\n       |                   Agent Workers (PHP)\n       |                        |\n       |                        v\n       |                   LLM Gateway (Rate-limited, Cached)\n       |                        |\n       |                        v\n       v                   External LLM APIs\n  Database\u002FRedis         (OpenAI, Claude, etc.)\n","text","",[177,178,173],"code",{"__ignoreMap":175},[102,180,181],{},"The flow works like this: a user submits a request through the Laravel app. The app validates the input, creates a task record in the database, and dispatches a job to the queue. An agent worker picks up the job, processes it through a series of orchestrated LLM calls (all routed through the API gateway), and writes the result back to the database. An event fires, and the user's UI updates via server-sent events or WebSockets.",[102,183,184],{},"This async, queue-driven pattern is the core architectural decision. Every agent task goes through a queue, which gives me retry logic, failure handling, and horizontal scaling without modifying application code.",[98,186,188],{"id":187},"queue-driven-agent-execution","Queue-Driven Agent Execution",[102,190,191],{},"The heart of the system is a Laravel job that orchestrates multi-step AI agent tasks. I use a pattern where a single \"orchestrator\" job dispatches sub-tasks as separate jobs, giving me granular control over failures and retries.",[102,193,194],{},"Here is the core job class that processes a document analysis task:",[169,196,200],{"className":197,"code":198,"language":199,"meta":175,"style":175},"language-php shiki shiki-themes material-theme-lighter github-light github-dark monokai","\u003C?php\n\nnamespace App\\Jobs\\Agent;\n\nuse App\\Models\\AgentTask;\nuse App\\Services\\LlmGateway;\nuse Illuminate\\Contracts\\Queue\\ShouldQueue;\nuse Illuminate\\Foundation\\Bus\\Dispatchable;\nuse Illuminate\\Foundation\\Bus\\PendingDispatch;\nuse Illuminate\\Queue\\InteractsWithQueue;\nuse Illuminate\\Queue\\SerializesModels;\nuse Illuminate\\Support\\Facades\\Event;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass ProcessAgentTask implements ShouldQueue\n{\n    use Dispatchable, InteractsWithQueue, SerializesModels;\n\n    public int $timeout = 300;\n    public int $tries = 3;\n    public array $backoff = [10, 30, 60];\n\n    public function __construct(\n        public AgentTask $task\n    ) {}\n\n    public function handle(LlmGateway $gateway): void\n    {\n        $this->task->update(['status' => 'processing']);\n\n        try {\n            \u002F\u002F Step 1: Analyze the document\n            $analysis = $gateway->chat(\n                systemPrompt: 'You are a document analysis agent...',\n                messages: [\n                    ['role' => 'user', 'content' => $this->task->input['document_text']],\n                ],\n                options: ['model' => 'gpt-4o', 'max_tokens' => 2000]\n            );\n\n            \u002F\u002F Step 2: Extract structured data\n            $extraction = $gateway->chat(\n                systemPrompt: 'Extract structured data from the analysis...',\n                messages: [\n                    ['role' => 'assistant', 'content' => $analysis],\n                    ['role' => 'user', 'content' => 'Return JSON with fields: summary, key_points, entities'],\n                ],\n                options: ['model' => 'gpt-4o', 'response_format' => 'json']\n            );\n\n            \u002F\u002F Step 3: Store results and dispatch follow-up jobs\n            $this->task->update([\n                'status' => 'completed',\n                'result' => json_decode($extraction, true),\n                'completed_at' => now(),\n            ]);\n\n            \u002F\u002F Dispatch post-processing as a separate job chain\n            PostProcessAgentResult::dispatch($this->task)\n                ->onQueue('post-processing');\n\n            Event::dispatch('agent.task.completed', [$this->task]);\n\n        } catch (\\Throwable $e) {\n            Log::error('Agent task failed', [\n                'task_id' => $this->task->id,\n                'error' => $e->getMessage(),\n            ]);\n\n            $this->task->update([\n                'status' => 'failed',\n                'error' => $e->getMessage(),\n                'attempts' => $this->attempts(),\n            ]);\n\n            throw $e;\n        }\n    }\n}\n","php",[177,201,202,210,217,222,227,233,239,245,251,257,263,269,275,280,285,291,297,303,308,314,320,326,331,337,343,349,354,360,366,372,377,383,389,395,401,407,413,419,425,431,436,442,448,454,459,465,471,476,482,487,492,498,504,510,516,522,528,533,539,545,551,556,562,567,573,579,585,591,596,601,606,612,617,623,628,633,639,645,651],{"__ignoreMap":175},[203,204,207],"span",{"class":205,"line":206},"line",1,[203,208,209],{},"\u003C?php\n",[203,211,213],{"class":205,"line":212},2,[203,214,216],{"emptyLinePlaceholder":215},true,"\n",[203,218,219],{"class":205,"line":14},[203,220,221],{},"namespace App\\Jobs\\Agent;\n",[203,223,225],{"class":205,"line":224},4,[203,226,216],{"emptyLinePlaceholder":215},[203,228,230],{"class":205,"line":229},5,[203,231,232],{},"use App\\Models\\AgentTask;\n",[203,234,236],{"class":205,"line":235},6,[203,237,238],{},"use App\\Services\\LlmGateway;\n",[203,240,242],{"class":205,"line":241},7,[203,243,244],{},"use Illuminate\\Contracts\\Queue\\ShouldQueue;\n",[203,246,248],{"class":205,"line":247},8,[203,249,250],{},"use Illuminate\\Foundation\\Bus\\Dispatchable;\n",[203,252,254],{"class":205,"line":253},9,[203,255,256],{},"use Illuminate\\Foundation\\Bus\\PendingDispatch;\n",[203,258,260],{"class":205,"line":259},10,[203,261,262],{},"use Illuminate\\Queue\\InteractsWithQueue;\n",[203,264,266],{"class":205,"line":265},11,[203,267,268],{},"use Illuminate\\Queue\\SerializesModels;\n",[203,270,272],{"class":205,"line":271},12,[203,273,274],{},"use Illuminate\\Support\\Facades\\Event;\n",[203,276,277],{"class":205,"line":27},[203,278,279],{},"use Illuminate\\Support\\Facades\\Log;\n",[203,281,283],{"class":205,"line":282},14,[203,284,216],{"emptyLinePlaceholder":215},[203,286,288],{"class":205,"line":287},15,[203,289,290],{},"class ProcessAgentTask implements ShouldQueue\n",[203,292,294],{"class":205,"line":293},16,[203,295,296],{},"{\n",[203,298,300],{"class":205,"line":299},17,[203,301,302],{},"    use Dispatchable, InteractsWithQueue, SerializesModels;\n",[203,304,306],{"class":205,"line":305},18,[203,307,216],{"emptyLinePlaceholder":215},[203,309,311],{"class":205,"line":310},19,[203,312,313],{},"    public int $timeout = 300;\n",[203,315,317],{"class":205,"line":316},20,[203,318,319],{},"    public int $tries = 3;\n",[203,321,323],{"class":205,"line":322},21,[203,324,325],{},"    public array $backoff = [10, 30, 60];\n",[203,327,329],{"class":205,"line":328},22,[203,330,216],{"emptyLinePlaceholder":215},[203,332,334],{"class":205,"line":333},23,[203,335,336],{},"    public function __construct(\n",[203,338,340],{"class":205,"line":339},24,[203,341,342],{},"        public AgentTask $task\n",[203,344,346],{"class":205,"line":345},25,[203,347,348],{},"    ) {}\n",[203,350,352],{"class":205,"line":351},26,[203,353,216],{"emptyLinePlaceholder":215},[203,355,357],{"class":205,"line":356},27,[203,358,359],{},"    public function handle(LlmGateway $gateway): void\n",[203,361,363],{"class":205,"line":362},28,[203,364,365],{},"    {\n",[203,367,369],{"class":205,"line":368},29,[203,370,371],{},"        $this->task->update(['status' => 'processing']);\n",[203,373,375],{"class":205,"line":374},30,[203,376,216],{"emptyLinePlaceholder":215},[203,378,380],{"class":205,"line":379},31,[203,381,382],{},"        try {\n",[203,384,386],{"class":205,"line":385},32,[203,387,388],{},"            \u002F\u002F Step 1: Analyze the document\n",[203,390,392],{"class":205,"line":391},33,[203,393,394],{},"            $analysis = $gateway->chat(\n",[203,396,398],{"class":205,"line":397},34,[203,399,400],{},"                systemPrompt: 'You are a document analysis agent...',\n",[203,402,404],{"class":205,"line":403},35,[203,405,406],{},"                messages: [\n",[203,408,410],{"class":205,"line":409},36,[203,411,412],{},"                    ['role' => 'user', 'content' => $this->task->input['document_text']],\n",[203,414,416],{"class":205,"line":415},37,[203,417,418],{},"                ],\n",[203,420,422],{"class":205,"line":421},38,[203,423,424],{},"                options: ['model' => 'gpt-4o', 'max_tokens' => 2000]\n",[203,426,428],{"class":205,"line":427},39,[203,429,430],{},"            );\n",[203,432,434],{"class":205,"line":433},40,[203,435,216],{"emptyLinePlaceholder":215},[203,437,439],{"class":205,"line":438},41,[203,440,441],{},"            \u002F\u002F Step 2: Extract structured data\n",[203,443,445],{"class":205,"line":444},42,[203,446,447],{},"            $extraction = $gateway->chat(\n",[203,449,451],{"class":205,"line":450},43,[203,452,453],{},"                systemPrompt: 'Extract structured data from the analysis...',\n",[203,455,457],{"class":205,"line":456},44,[203,458,406],{},[203,460,462],{"class":205,"line":461},45,[203,463,464],{},"                    ['role' => 'assistant', 'content' => $analysis],\n",[203,466,468],{"class":205,"line":467},46,[203,469,470],{},"                    ['role' => 'user', 'content' => 'Return JSON with fields: summary, key_points, entities'],\n",[203,472,474],{"class":205,"line":473},47,[203,475,418],{},[203,477,479],{"class":205,"line":478},48,[203,480,481],{},"                options: ['model' => 'gpt-4o', 'response_format' => 'json']\n",[203,483,485],{"class":205,"line":484},49,[203,486,430],{},[203,488,490],{"class":205,"line":489},50,[203,491,216],{"emptyLinePlaceholder":215},[203,493,495],{"class":205,"line":494},51,[203,496,497],{},"            \u002F\u002F Step 3: Store results and dispatch follow-up jobs\n",[203,499,501],{"class":205,"line":500},52,[203,502,503],{},"            $this->task->update([\n",[203,505,507],{"class":205,"line":506},53,[203,508,509],{},"                'status' => 'completed',\n",[203,511,513],{"class":205,"line":512},54,[203,514,515],{},"                'result' => json_decode($extraction, true),\n",[203,517,519],{"class":205,"line":518},55,[203,520,521],{},"                'completed_at' => now(),\n",[203,523,525],{"class":205,"line":524},56,[203,526,527],{},"            ]);\n",[203,529,531],{"class":205,"line":530},57,[203,532,216],{"emptyLinePlaceholder":215},[203,534,536],{"class":205,"line":535},58,[203,537,538],{},"            \u002F\u002F Dispatch post-processing as a separate job chain\n",[203,540,542],{"class":205,"line":541},59,[203,543,544],{},"            PostProcessAgentResult::dispatch($this->task)\n",[203,546,548],{"class":205,"line":547},60,[203,549,550],{},"                ->onQueue('post-processing');\n",[203,552,554],{"class":205,"line":553},61,[203,555,216],{"emptyLinePlaceholder":215},[203,557,559],{"class":205,"line":558},62,[203,560,561],{},"            Event::dispatch('agent.task.completed', [$this->task]);\n",[203,563,565],{"class":205,"line":564},63,[203,566,216],{"emptyLinePlaceholder":215},[203,568,570],{"class":205,"line":569},64,[203,571,572],{},"        } catch (\\Throwable $e) {\n",[203,574,576],{"class":205,"line":575},65,[203,577,578],{},"            Log::error('Agent task failed', [\n",[203,580,582],{"class":205,"line":581},66,[203,583,584],{},"                'task_id' => $this->task->id,\n",[203,586,588],{"class":205,"line":587},67,[203,589,590],{},"                'error' => $e->getMessage(),\n",[203,592,594],{"class":205,"line":593},68,[203,595,527],{},[203,597,599],{"class":205,"line":598},69,[203,600,216],{"emptyLinePlaceholder":215},[203,602,604],{"class":205,"line":603},70,[203,605,503],{},[203,607,609],{"class":205,"line":608},71,[203,610,611],{},"                'status' => 'failed',\n",[203,613,615],{"class":205,"line":614},72,[203,616,590],{},[203,618,620],{"class":205,"line":619},73,[203,621,622],{},"                'attempts' => $this->attempts(),\n",[203,624,626],{"class":205,"line":625},74,[203,627,527],{},[203,629,631],{"class":205,"line":630},75,[203,632,216],{"emptyLinePlaceholder":215},[203,634,636],{"class":205,"line":635},76,[203,637,638],{},"            throw $e;\n",[203,640,642],{"class":205,"line":641},77,[203,643,644],{},"        }\n",[203,646,648],{"class":205,"line":647},78,[203,649,650],{},"    }\n",[203,652,654],{"class":205,"line":653},79,[203,655,656],{},"}\n",[102,658,659],{},"Key design decisions in this job:",[124,661,662,668,674,680],{},[127,663,664,667],{},[130,665,666],{},"SerializesModels"," ensures the task model is re-retrieved from the database when the job executes, preventing stale data issues on retry.",[127,669,670,673],{},[130,671,672],{},"The backoff array"," implements progressive delay: 10 seconds, then 30, then 60. Most transient LLM API failures resolve within 60 seconds.",[127,675,676,679],{},[130,677,678],{},"Timeout of 300 seconds"," accounts for LLM API latency. GPT-4o responses can take 15-45 seconds for complex prompts, and multi-step chains multiply that.",[127,681,682,685],{},[130,683,684],{},"Post-processing is a separate job"," on a different queue, isolating the main agent pipeline from side effects like indexing, notifications, or webhook delivery.",[98,687,689],{"id":688},"event-driven-result-handling","Event-Driven Result Handling",[102,691,692],{},"Jobs complete asynchronously, so the user needs a way to get results without polling. I use Laravel's broadcasting system with Reverb to push status updates:",[169,694,696],{"className":197,"code":695,"language":199,"meta":175,"style":175},"\u002F\u002F In a service provider boot method\nEvent::listen('agent.task.completed', function (AgentTask $task) {\n    broadcast(new AgentTaskCompleted($task))->toOthers();\n});\n",[177,697,698,703,708,713],{"__ignoreMap":175},[203,699,700],{"class":205,"line":206},[203,701,702],{},"\u002F\u002F In a service provider boot method\n",[203,704,705],{"class":205,"line":212},[203,706,707],{},"Event::listen('agent.task.completed', function (AgentTask $task) {\n",[203,709,710],{"class":205,"line":14},[203,711,712],{},"    broadcast(new AgentTaskCompleted($task))->toOthers();\n",[203,714,715],{"class":205,"line":224},[203,716,717],{},"});\n",[102,719,720],{},"The frontend receives these events and updates the UI in real time. For users who close their browser, the system sends a notification once the task completes — handled by a separate listener that dispatches a notification job.",[98,722,724],{"id":723},"hybrid-architecture-diagram","Hybrid Architecture Diagram",[102,726,727],{},"The following diagram shows the complete request flow through the hybrid backend:",[169,729,733],{"className":730,"code":731,"language":732,"meta":175,"style":175},"language-mermaid shiki shiki-themes material-theme-lighter github-light github-dark monokai","sequenceDiagram\n    participant User as User\u002FClient\n    participant Laravel as Laravel App\n    participant Gateway as LLM API Gateway\n    participant Horizon as Queue (Redis\u002FHorizon)\n    participant Worker as Agent Worker\n    participant LLM as External LLM API\n\n    User->>Laravel: Submit document for analysis\n    Laravel->>Laravel: Validate input, create AgentTask\n    Laravel->>Horizon: Dispatch ProcessAgentTask job\n    Laravel-->>User: Return task ID (202 Accepted)\n\n    Horizon->>Worker: Pop job from queue\n    Worker->>Gateway: Request LLM analysis\n    Gateway->>Gateway: Check rate limit & token budget\n    Gateway->>LLM: Forward prompt\n    LLM-->>Gateway: Return analysis response\n    Gateway->>Gateway: Cache response, deduct tokens\n    Gateway-->>Worker: Return structured result\n\n    Worker->>Worker: Process & extract data\n    Worker->>Laravel: Update task status (completed)\n    Worker->>Horizon: Dispatch PostProcessAgentResult\n\n    Laravel-->>User: SSE\u002FWebSocket: task.completed\n    User->>Laravel: Fetch processed result by task ID\n    Laravel-->>User: Return full analysis\n","mermaid",[177,734,735,740,745,750,755,760,765,770,774,779,784,789,794,798,803,808,813,818,823,828,833,837,842,847,852,856,861,866],{"__ignoreMap":175},[203,736,737],{"class":205,"line":206},[203,738,739],{},"sequenceDiagram\n",[203,741,742],{"class":205,"line":212},[203,743,744],{},"    participant User as User\u002FClient\n",[203,746,747],{"class":205,"line":14},[203,748,749],{},"    participant Laravel as Laravel App\n",[203,751,752],{"class":205,"line":224},[203,753,754],{},"    participant Gateway as LLM API Gateway\n",[203,756,757],{"class":205,"line":229},[203,758,759],{},"    participant Horizon as Queue (Redis\u002FHorizon)\n",[203,761,762],{"class":205,"line":235},[203,763,764],{},"    participant Worker as Agent Worker\n",[203,766,767],{"class":205,"line":241},[203,768,769],{},"    participant LLM as External LLM API\n",[203,771,772],{"class":205,"line":247},[203,773,216],{"emptyLinePlaceholder":215},[203,775,776],{"class":205,"line":253},[203,777,778],{},"    User->>Laravel: Submit document for analysis\n",[203,780,781],{"class":205,"line":259},[203,782,783],{},"    Laravel->>Laravel: Validate input, create AgentTask\n",[203,785,786],{"class":205,"line":265},[203,787,788],{},"    Laravel->>Horizon: Dispatch ProcessAgentTask job\n",[203,790,791],{"class":205,"line":271},[203,792,793],{},"    Laravel-->>User: Return task ID (202 Accepted)\n",[203,795,796],{"class":205,"line":27},[203,797,216],{"emptyLinePlaceholder":215},[203,799,800],{"class":205,"line":282},[203,801,802],{},"    Horizon->>Worker: Pop job from queue\n",[203,804,805],{"class":205,"line":287},[203,806,807],{},"    Worker->>Gateway: Request LLM analysis\n",[203,809,810],{"class":205,"line":293},[203,811,812],{},"    Gateway->>Gateway: Check rate limit & token budget\n",[203,814,815],{"class":205,"line":299},[203,816,817],{},"    Gateway->>LLM: Forward prompt\n",[203,819,820],{"class":205,"line":305},[203,821,822],{},"    LLM-->>Gateway: Return analysis response\n",[203,824,825],{"class":205,"line":310},[203,826,827],{},"    Gateway->>Gateway: Cache response, deduct tokens\n",[203,829,830],{"class":205,"line":316},[203,831,832],{},"    Gateway-->>Worker: Return structured result\n",[203,834,835],{"class":205,"line":322},[203,836,216],{"emptyLinePlaceholder":215},[203,838,839],{"class":205,"line":328},[203,840,841],{},"    Worker->>Worker: Process & extract data\n",[203,843,844],{"class":205,"line":333},[203,845,846],{},"    Worker->>Laravel: Update task status (completed)\n",[203,848,849],{"class":205,"line":339},[203,850,851],{},"    Worker->>Horizon: Dispatch PostProcessAgentResult\n",[203,853,854],{"class":205,"line":345},[203,855,216],{"emptyLinePlaceholder":215},[203,857,858],{"class":205,"line":351},[203,859,860],{},"    Laravel-->>User: SSE\u002FWebSocket: task.completed\n",[203,862,863],{"class":205,"line":356},[203,864,865],{},"    User->>Laravel: Fetch processed result by task ID\n",[203,867,868],{"class":205,"line":362},[203,869,870],{},"    Laravel-->>User: Return full analysis\n",[102,872,873,874,877],{},"The critical insight in this architecture is the ",[130,875,876],{},"LLM API Gateway",". Every request to an external language model passes through this middleware layer, which handles concerns that would otherwise be scattered across every job class.",[98,879,881],{"id":880},"building-the-llm-api-gateway","Building the LLM API Gateway",[102,883,884],{},"The gateway is a dedicated service class that wraps all external LLM calls. I built it because I discovered early that direct API calls from job classes lead to duplicate rate-limiting logic, inconsistent error handling, and no centralized observability.",[169,886,888],{"className":197,"code":887,"language":199,"meta":175,"style":175},"\u003C?php\n\nnamespace App\\Services;\n\nuse App\\Models\\ApiTokenUsage;\nuse Illuminate\\Support\\Facades\\Cache;\nuse Illuminate\\Support\\Facades\\Http;\nuse Illuminate\\Support\\Facades\\Log;\n\nclass LlmGateway\n{\n    private const CACHE_TTL = 3600; \u002F\u002F 1 hour for identical prompts\n    private const RATE_LIMIT_KEY = 'llm_rate_limit:';\n    private const TOKEN_BUDGET_KEY = 'llm_token_budget:';\n    private const MAX_TOKENS_PER_MINUTE = 100000;\n    private const MAX_TOKENS_PER_DAY = 10000000;\n\n    public function chat(\n        string $systemPrompt,\n        array $messages,\n        array $options = []\n    ): string {\n        $cacheKey = $this->buildCacheKey($systemPrompt, $messages, $options);\n\n        \u002F\u002F Check cache first for identical requests\n        if ($cached = Cache::get($cacheKey)) {\n            Log::debug('LLM cache hit', ['cache_key' => $cacheKey]);\n            return $cached;\n        }\n\n        \u002F\u002F Enforce rate limits\n        $this->checkRateLimit();\n        $this->checkTokenBudget($options['max_tokens'] ?? 1000);\n\n        \u002F\u002F Make the API call\n        $response = Http::timeout(120)\n            ->withToken(config('services.openai.api_key'))\n            ->post('https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions', [\n                'model' => $options['model'] ?? 'gpt-4o',\n                'messages' => array_merge(\n                    [['role' => 'system', 'content' => $systemPrompt]],\n                    $messages\n                ),\n                'max_tokens' => $options['max_tokens'] ?? 1000,\n                'temperature' => $options['temperature'] ?? 0.3,\n                'response_format' => $options['response_format'] ?? null,\n            ]);\n\n        if ($response->failed()) {\n            Log::error('LLM API call failed', [\n                'status' => $response->status(),\n                'body' => $response->body(),\n            ]);\n            throw new \\RuntimeException(\n                'LLM API error: ' . $response->body()\n            );\n        }\n\n        $result = $response->json('choices.0.message.content');\n        $tokensUsed = $response->json('usage.total_tokens');\n\n        \u002F\u002F Track usage\n        $this->trackTokenUsage($tokensUsed);\n\n        \u002F\u002F Cache identical requests\n        if ($tokensUsed > 50) {\n            Cache::put($cacheKey, $result, self::CACHE_TTL);\n        }\n\n        return $result;\n    }\n\n    private function checkRateLimit(): void\n    {\n        $key = self::RATE_LIMIT_KEY . (string) time();\n        $count = Cache::increment($key, 1, 60);\n\n        if ($count > 100) {\n            throw new \\RuntimeException('LLM rate limit exceeded');\n        }\n    }\n\n    private function checkTokenBudget(int $requestedTokens): void\n    {\n        $dailyKey = self::TOKEN_BUDGET_KEY . now()->format('Y-m-d');\n        $dailyUsage = Cache::get($dailyKey, 0);\n\n        if ($dailyUsage + $requestedTokens > self::MAX_TOKENS_PER_DAY) {\n            throw new \\RuntimeException('Daily token budget exhausted');\n        }\n\n        $minuteKey = self::TOKEN_BUDGET_KEY . now()->format('Y-m-d-H-i');\n        $minuteUsage = Cache::get($minuteKey, 0);\n\n        if ($minuteUsage + $requestedTokens > self::MAX_TOKENS_PER_MINUTE) {\n            throw new \\RuntimeException('Minute token budget exceeded');\n        }\n    }\n\n    private function trackTokenUsage(int $tokens): void\n    {\n        ApiTokenUsage::create([\n            'tokens' => $tokens,\n            'model' => 'gpt-4o',\n            'recorded_at' => now(),\n        ]);\n\n        Cache::increment(\n            self::TOKEN_BUDGET_KEY . now()->format('Y-m-d'),\n            $tokens\n        );\n        Cache::increment(\n            self::TOKEN_BUDGET_KEY . now()->format('Y-m-d-H-i'),\n            $tokens,\n            120\n        );\n    }\n\n    private function buildCacheKey(\n        string $system,\n        array $messages,\n        array $options\n    ): string {\n        return 'llm_response:' . md5(\n            serialize([$system, $messages, $options])\n        );\n    }\n}\n",[177,889,890,894,898,903,907,912,917,922,926,930,935,939,944,949,954,959,964,968,973,978,983,988,993,998,1002,1007,1012,1017,1022,1026,1030,1035,1040,1045,1049,1054,1059,1064,1069,1074,1079,1084,1089,1094,1099,1104,1109,1113,1117,1122,1127,1132,1137,1141,1146,1151,1155,1159,1163,1168,1173,1177,1182,1187,1191,1196,1201,1206,1210,1214,1219,1223,1227,1232,1236,1241,1246,1250,1255,1260,1265,1270,1275,1281,1286,1292,1298,1303,1309,1315,1320,1325,1331,1337,1342,1348,1354,1359,1364,1369,1374,1379,1385,1391,1397,1403,1409,1414,1420,1426,1432,1438,1443,1449,1455,1461,1466,1471,1476,1482,1488,1493,1499,1504,1510,1515,1520,1525],{"__ignoreMap":175},[203,891,892],{"class":205,"line":206},[203,893,209],{},[203,895,896],{"class":205,"line":212},[203,897,216],{"emptyLinePlaceholder":215},[203,899,900],{"class":205,"line":14},[203,901,902],{},"namespace App\\Services;\n",[203,904,905],{"class":205,"line":224},[203,906,216],{"emptyLinePlaceholder":215},[203,908,909],{"class":205,"line":229},[203,910,911],{},"use App\\Models\\ApiTokenUsage;\n",[203,913,914],{"class":205,"line":235},[203,915,916],{},"use Illuminate\\Support\\Facades\\Cache;\n",[203,918,919],{"class":205,"line":241},[203,920,921],{},"use Illuminate\\Support\\Facades\\Http;\n",[203,923,924],{"class":205,"line":247},[203,925,279],{},[203,927,928],{"class":205,"line":253},[203,929,216],{"emptyLinePlaceholder":215},[203,931,932],{"class":205,"line":259},[203,933,934],{},"class LlmGateway\n",[203,936,937],{"class":205,"line":265},[203,938,296],{},[203,940,941],{"class":205,"line":271},[203,942,943],{},"    private const CACHE_TTL = 3600; \u002F\u002F 1 hour for identical prompts\n",[203,945,946],{"class":205,"line":27},[203,947,948],{},"    private const RATE_LIMIT_KEY = 'llm_rate_limit:';\n",[203,950,951],{"class":205,"line":282},[203,952,953],{},"    private const TOKEN_BUDGET_KEY = 'llm_token_budget:';\n",[203,955,956],{"class":205,"line":287},[203,957,958],{},"    private const MAX_TOKENS_PER_MINUTE = 100000;\n",[203,960,961],{"class":205,"line":293},[203,962,963],{},"    private const MAX_TOKENS_PER_DAY = 10000000;\n",[203,965,966],{"class":205,"line":299},[203,967,216],{"emptyLinePlaceholder":215},[203,969,970],{"class":205,"line":305},[203,971,972],{},"    public function chat(\n",[203,974,975],{"class":205,"line":310},[203,976,977],{},"        string $systemPrompt,\n",[203,979,980],{"class":205,"line":316},[203,981,982],{},"        array $messages,\n",[203,984,985],{"class":205,"line":322},[203,986,987],{},"        array $options = []\n",[203,989,990],{"class":205,"line":328},[203,991,992],{},"    ): string {\n",[203,994,995],{"class":205,"line":333},[203,996,997],{},"        $cacheKey = $this->buildCacheKey($systemPrompt, $messages, $options);\n",[203,999,1000],{"class":205,"line":339},[203,1001,216],{"emptyLinePlaceholder":215},[203,1003,1004],{"class":205,"line":345},[203,1005,1006],{},"        \u002F\u002F Check cache first for identical requests\n",[203,1008,1009],{"class":205,"line":351},[203,1010,1011],{},"        if ($cached = Cache::get($cacheKey)) {\n",[203,1013,1014],{"class":205,"line":356},[203,1015,1016],{},"            Log::debug('LLM cache hit', ['cache_key' => $cacheKey]);\n",[203,1018,1019],{"class":205,"line":362},[203,1020,1021],{},"            return $cached;\n",[203,1023,1024],{"class":205,"line":368},[203,1025,644],{},[203,1027,1028],{"class":205,"line":374},[203,1029,216],{"emptyLinePlaceholder":215},[203,1031,1032],{"class":205,"line":379},[203,1033,1034],{},"        \u002F\u002F Enforce rate limits\n",[203,1036,1037],{"class":205,"line":385},[203,1038,1039],{},"        $this->checkRateLimit();\n",[203,1041,1042],{"class":205,"line":391},[203,1043,1044],{},"        $this->checkTokenBudget($options['max_tokens'] ?? 1000);\n",[203,1046,1047],{"class":205,"line":397},[203,1048,216],{"emptyLinePlaceholder":215},[203,1050,1051],{"class":205,"line":403},[203,1052,1053],{},"        \u002F\u002F Make the API call\n",[203,1055,1056],{"class":205,"line":409},[203,1057,1058],{},"        $response = Http::timeout(120)\n",[203,1060,1061],{"class":205,"line":415},[203,1062,1063],{},"            ->withToken(config('services.openai.api_key'))\n",[203,1065,1066],{"class":205,"line":421},[203,1067,1068],{},"            ->post('https:\u002F\u002Fapi.openai.com\u002Fv1\u002Fchat\u002Fcompletions', [\n",[203,1070,1071],{"class":205,"line":427},[203,1072,1073],{},"                'model' => $options['model'] ?? 'gpt-4o',\n",[203,1075,1076],{"class":205,"line":433},[203,1077,1078],{},"                'messages' => array_merge(\n",[203,1080,1081],{"class":205,"line":438},[203,1082,1083],{},"                    [['role' => 'system', 'content' => $systemPrompt]],\n",[203,1085,1086],{"class":205,"line":444},[203,1087,1088],{},"                    $messages\n",[203,1090,1091],{"class":205,"line":450},[203,1092,1093],{},"                ),\n",[203,1095,1096],{"class":205,"line":456},[203,1097,1098],{},"                'max_tokens' => $options['max_tokens'] ?? 1000,\n",[203,1100,1101],{"class":205,"line":461},[203,1102,1103],{},"                'temperature' => $options['temperature'] ?? 0.3,\n",[203,1105,1106],{"class":205,"line":467},[203,1107,1108],{},"                'response_format' => $options['response_format'] ?? null,\n",[203,1110,1111],{"class":205,"line":473},[203,1112,527],{},[203,1114,1115],{"class":205,"line":478},[203,1116,216],{"emptyLinePlaceholder":215},[203,1118,1119],{"class":205,"line":484},[203,1120,1121],{},"        if ($response->failed()) {\n",[203,1123,1124],{"class":205,"line":489},[203,1125,1126],{},"            Log::error('LLM API call failed', [\n",[203,1128,1129],{"class":205,"line":494},[203,1130,1131],{},"                'status' => $response->status(),\n",[203,1133,1134],{"class":205,"line":500},[203,1135,1136],{},"                'body' => $response->body(),\n",[203,1138,1139],{"class":205,"line":506},[203,1140,527],{},[203,1142,1143],{"class":205,"line":512},[203,1144,1145],{},"            throw new \\RuntimeException(\n",[203,1147,1148],{"class":205,"line":518},[203,1149,1150],{},"                'LLM API error: ' . $response->body()\n",[203,1152,1153],{"class":205,"line":524},[203,1154,430],{},[203,1156,1157],{"class":205,"line":530},[203,1158,644],{},[203,1160,1161],{"class":205,"line":535},[203,1162,216],{"emptyLinePlaceholder":215},[203,1164,1165],{"class":205,"line":541},[203,1166,1167],{},"        $result = $response->json('choices.0.message.content');\n",[203,1169,1170],{"class":205,"line":547},[203,1171,1172],{},"        $tokensUsed = $response->json('usage.total_tokens');\n",[203,1174,1175],{"class":205,"line":553},[203,1176,216],{"emptyLinePlaceholder":215},[203,1178,1179],{"class":205,"line":558},[203,1180,1181],{},"        \u002F\u002F Track usage\n",[203,1183,1184],{"class":205,"line":564},[203,1185,1186],{},"        $this->trackTokenUsage($tokensUsed);\n",[203,1188,1189],{"class":205,"line":569},[203,1190,216],{"emptyLinePlaceholder":215},[203,1192,1193],{"class":205,"line":575},[203,1194,1195],{},"        \u002F\u002F Cache identical requests\n",[203,1197,1198],{"class":205,"line":581},[203,1199,1200],{},"        if ($tokensUsed > 50) {\n",[203,1202,1203],{"class":205,"line":587},[203,1204,1205],{},"            Cache::put($cacheKey, $result, self::CACHE_TTL);\n",[203,1207,1208],{"class":205,"line":593},[203,1209,644],{},[203,1211,1212],{"class":205,"line":598},[203,1213,216],{"emptyLinePlaceholder":215},[203,1215,1216],{"class":205,"line":603},[203,1217,1218],{},"        return $result;\n",[203,1220,1221],{"class":205,"line":608},[203,1222,650],{},[203,1224,1225],{"class":205,"line":614},[203,1226,216],{"emptyLinePlaceholder":215},[203,1228,1229],{"class":205,"line":619},[203,1230,1231],{},"    private function checkRateLimit(): void\n",[203,1233,1234],{"class":205,"line":625},[203,1235,365],{},[203,1237,1238],{"class":205,"line":630},[203,1239,1240],{},"        $key = self::RATE_LIMIT_KEY . (string) time();\n",[203,1242,1243],{"class":205,"line":635},[203,1244,1245],{},"        $count = Cache::increment($key, 1, 60);\n",[203,1247,1248],{"class":205,"line":641},[203,1249,216],{"emptyLinePlaceholder":215},[203,1251,1252],{"class":205,"line":647},[203,1253,1254],{},"        if ($count > 100) {\n",[203,1256,1257],{"class":205,"line":653},[203,1258,1259],{},"            throw new \\RuntimeException('LLM rate limit exceeded');\n",[203,1261,1263],{"class":205,"line":1262},80,[203,1264,644],{},[203,1266,1268],{"class":205,"line":1267},81,[203,1269,650],{},[203,1271,1273],{"class":205,"line":1272},82,[203,1274,216],{"emptyLinePlaceholder":215},[203,1276,1278],{"class":205,"line":1277},83,[203,1279,1280],{},"    private function checkTokenBudget(int $requestedTokens): void\n",[203,1282,1284],{"class":205,"line":1283},84,[203,1285,365],{},[203,1287,1289],{"class":205,"line":1288},85,[203,1290,1291],{},"        $dailyKey = self::TOKEN_BUDGET_KEY . now()->format('Y-m-d');\n",[203,1293,1295],{"class":205,"line":1294},86,[203,1296,1297],{},"        $dailyUsage = Cache::get($dailyKey, 0);\n",[203,1299,1301],{"class":205,"line":1300},87,[203,1302,216],{"emptyLinePlaceholder":215},[203,1304,1306],{"class":205,"line":1305},88,[203,1307,1308],{},"        if ($dailyUsage + $requestedTokens > self::MAX_TOKENS_PER_DAY) {\n",[203,1310,1312],{"class":205,"line":1311},89,[203,1313,1314],{},"            throw new \\RuntimeException('Daily token budget exhausted');\n",[203,1316,1318],{"class":205,"line":1317},90,[203,1319,644],{},[203,1321,1323],{"class":205,"line":1322},91,[203,1324,216],{"emptyLinePlaceholder":215},[203,1326,1328],{"class":205,"line":1327},92,[203,1329,1330],{},"        $minuteKey = self::TOKEN_BUDGET_KEY . now()->format('Y-m-d-H-i');\n",[203,1332,1334],{"class":205,"line":1333},93,[203,1335,1336],{},"        $minuteUsage = Cache::get($minuteKey, 0);\n",[203,1338,1340],{"class":205,"line":1339},94,[203,1341,216],{"emptyLinePlaceholder":215},[203,1343,1345],{"class":205,"line":1344},95,[203,1346,1347],{},"        if ($minuteUsage + $requestedTokens > self::MAX_TOKENS_PER_MINUTE) {\n",[203,1349,1351],{"class":205,"line":1350},96,[203,1352,1353],{},"            throw new \\RuntimeException('Minute token budget exceeded');\n",[203,1355,1357],{"class":205,"line":1356},97,[203,1358,644],{},[203,1360,1362],{"class":205,"line":1361},98,[203,1363,650],{},[203,1365,1367],{"class":205,"line":1366},99,[203,1368,216],{"emptyLinePlaceholder":215},[203,1370,1371],{"class":205,"line":21},[203,1372,1373],{},"    private function trackTokenUsage(int $tokens): void\n",[203,1375,1377],{"class":205,"line":1376},101,[203,1378,365],{},[203,1380,1382],{"class":205,"line":1381},102,[203,1383,1384],{},"        ApiTokenUsage::create([\n",[203,1386,1388],{"class":205,"line":1387},103,[203,1389,1390],{},"            'tokens' => $tokens,\n",[203,1392,1394],{"class":205,"line":1393},104,[203,1395,1396],{},"            'model' => 'gpt-4o',\n",[203,1398,1400],{"class":205,"line":1399},105,[203,1401,1402],{},"            'recorded_at' => now(),\n",[203,1404,1406],{"class":205,"line":1405},106,[203,1407,1408],{},"        ]);\n",[203,1410,1412],{"class":205,"line":1411},107,[203,1413,216],{"emptyLinePlaceholder":215},[203,1415,1417],{"class":205,"line":1416},108,[203,1418,1419],{},"        Cache::increment(\n",[203,1421,1423],{"class":205,"line":1422},109,[203,1424,1425],{},"            self::TOKEN_BUDGET_KEY . now()->format('Y-m-d'),\n",[203,1427,1429],{"class":205,"line":1428},110,[203,1430,1431],{},"            $tokens\n",[203,1433,1435],{"class":205,"line":1434},111,[203,1436,1437],{},"        );\n",[203,1439,1441],{"class":205,"line":1440},112,[203,1442,1419],{},[203,1444,1446],{"class":205,"line":1445},113,[203,1447,1448],{},"            self::TOKEN_BUDGET_KEY . now()->format('Y-m-d-H-i'),\n",[203,1450,1452],{"class":205,"line":1451},114,[203,1453,1454],{},"            $tokens,\n",[203,1456,1458],{"class":205,"line":1457},115,[203,1459,1460],{},"            120\n",[203,1462,1464],{"class":205,"line":1463},116,[203,1465,1437],{},[203,1467,1469],{"class":205,"line":1468},117,[203,1470,650],{},[203,1472,1474],{"class":205,"line":1473},118,[203,1475,216],{"emptyLinePlaceholder":215},[203,1477,1479],{"class":205,"line":1478},119,[203,1480,1481],{},"    private function buildCacheKey(\n",[203,1483,1485],{"class":205,"line":1484},120,[203,1486,1487],{},"        string $system,\n",[203,1489,1491],{"class":205,"line":1490},121,[203,1492,982],{},[203,1494,1496],{"class":205,"line":1495},122,[203,1497,1498],{},"        array $options\n",[203,1500,1502],{"class":205,"line":1501},123,[203,1503,992],{},[203,1505,1507],{"class":205,"line":1506},124,[203,1508,1509],{},"        return 'llm_response:' . md5(\n",[203,1511,1512],{"class":205,"line":46},[203,1513,1514],{},"            serialize([$system, $messages, $options])\n",[203,1516,1518],{"class":205,"line":1517},126,[203,1519,1437],{},[203,1521,1523],{"class":205,"line":1522},127,[203,1524,650],{},[203,1526,1528],{"class":205,"line":1527},128,[203,1529,656],{},[102,1531,1532],{},"The gateway solves three problems that every production AI system faces:",[124,1534,1535,1541,1547],{},[127,1536,1537,1540],{},[130,1538,1539],{},"Rate limiting",": Prevents accidental burst requests from hitting the LLM API simultaneously. I use Redis atomic counters with sliding windows — one for per-minute, one for per-day. When the budget exhausts, jobs fail gracefully and retry via the queue's backoff mechanism.",[127,1542,1543,1546],{},[130,1544,1545],{},"Response caching",": Many agent workflows repeatedly ask the same questions (analyzing similar documents, checking the same policies). The cache key uses a hash of the full prompt, and I cache responses for one hour. In my production data, this gives a 22% cache hit rate, saving roughly $400 per month on LLM API costs.",[127,1548,1549,1552,1553,1556],{},[130,1550,1551],{},"Token tracking",": Every call logs usage to an ",[177,1554,1555],{},"api_token_usage"," table, which feeds into billing dashboards and cost forecasting. Cache increments on Redis keys let me reject requests before they hit the API, rather than discovering the overage in a monthly bill.",[98,1558,1560],{"id":1559},"real-world-example-saas-document-processing-pipeline","Real-World Example: SaaS Document Processing Pipeline",[102,1562,1563],{},"I built a SaaS product that processes legal documents using AI agents. Users upload contracts, and the system extracts clauses, identifies risks, and generates summaries. The architecture follows the pattern described above with one addition: a multi-stage pipeline.",[102,1565,1566],{},"The pipeline has five stages, each as a separate Laravel job on a dedicated queue:",[1568,1569,1570,1580,1589,1598,1607],"ol",{},[127,1571,1572,1575,1576,1579],{},[130,1573,1574],{},"Document ingestion"," (",[177,1577,1578],{},"queue:ingestion","): Validates file types, extracts text via OCR if needed, stores in S3",[127,1581,1582,1575,1585,1588],{},[130,1583,1584],{},"Classification",[177,1586,1587],{},"queue:classification","): Identifies document type (NDA, employment contract, lease) using a lightweight classification prompt",[127,1590,1591,1575,1594,1597],{},[130,1592,1593],{},"Analysis",[177,1595,1596],{},"queue:analysis","): Full clause extraction and risk assessment using GPT-4o — this is the most expensive stage",[127,1599,1600,1575,1603,1606],{},[130,1601,1602],{},"Review",[177,1604,1605],{},"queue:review","): Cross-references extracted clauses against a database of known legal terms and company policies",[127,1608,1609,1575,1612,1615],{},[130,1610,1611],{},"Reporting",[177,1613,1614],{},"queue:reporting","): Generates the PDF report and sends notification",[102,1617,1618],{},"Each stage dispatches the next stage only on success. If analysis fails, the pipeline stops and notifies the user with partial results.",[102,1620,1621,1622,1625],{},"The queue configuration for this pipeline in ",[177,1623,1624],{},"config\u002Fqueue.php",":",[169,1627,1629],{"className":197,"code":1628,"language":199,"meta":175,"style":175},"'connections' => [\n    'redis' => [\n        'driver' => 'redis',\n        'connection' => 'default',\n        'queue' => [\n            'default',\n            'ingestion',\n            'classification',\n            'analysis',\n            'review',\n            'reporting',\n            'post-processing',\n        ],\n        'retry_after' => 360,\n        'block_for' => null,\n    ],\n],\n",[177,1630,1631,1636,1641,1646,1651,1656,1661,1666,1671,1676,1681,1686,1691,1696,1701,1706,1711],{"__ignoreMap":175},[203,1632,1633],{"class":205,"line":206},[203,1634,1635],{},"'connections' => [\n",[203,1637,1638],{"class":205,"line":212},[203,1639,1640],{},"    'redis' => [\n",[203,1642,1643],{"class":205,"line":14},[203,1644,1645],{},"        'driver' => 'redis',\n",[203,1647,1648],{"class":205,"line":224},[203,1649,1650],{},"        'connection' => 'default',\n",[203,1652,1653],{"class":205,"line":229},[203,1654,1655],{},"        'queue' => [\n",[203,1657,1658],{"class":205,"line":235},[203,1659,1660],{},"            'default',\n",[203,1662,1663],{"class":205,"line":241},[203,1664,1665],{},"            'ingestion',\n",[203,1667,1668],{"class":205,"line":247},[203,1669,1670],{},"            'classification',\n",[203,1672,1673],{"class":205,"line":253},[203,1674,1675],{},"            'analysis',\n",[203,1677,1678],{"class":205,"line":259},[203,1679,1680],{},"            'review',\n",[203,1682,1683],{"class":205,"line":265},[203,1684,1685],{},"            'reporting',\n",[203,1687,1688],{"class":205,"line":271},[203,1689,1690],{},"            'post-processing',\n",[203,1692,1693],{"class":205,"line":27},[203,1694,1695],{},"        ],\n",[203,1697,1698],{"class":205,"line":282},[203,1699,1700],{},"        'retry_after' => 360,\n",[203,1702,1703],{"class":205,"line":287},[203,1704,1705],{},"        'block_for' => null,\n",[203,1707,1708],{"class":205,"line":293},[203,1709,1710],{},"    ],\n",[203,1712,1713],{"class":205,"line":299},[203,1714,1715],{},"],\n",[102,1717,1718],{},"And the Horizon configuration for scaling:",[169,1720,1722],{"className":197,"code":1721,"language":199,"meta":175,"style":175},"'defaults' => [\n    'supervisor-1' => [\n        'connection' => 'redis',\n        'queue' => ['default'],\n        'balance' => 'auto',\n        'minProcesses' => 1,\n        'maxProcesses' => 5,\n        'tries' => 3,\n    ],\n    'supervisor-2' => [\n        'connection' => 'redis',\n        'queue' => ['analysis', 'classification'],\n        'balance' => 'auto',\n        'minProcesses' => 2,\n        'maxProcesses' => 10,\n        'tries' => 3,\n    ],\n    'supervisor-3' => [\n        'connection' => 'redis',\n        'queue' => ['ingestion', 'review', 'reporting'],\n        'balance' => 'auto',\n        'minProcesses' => 1,\n        'maxProcesses' => 3,\n        'tries' => 5,\n    ],\n],\n",[177,1723,1724,1729,1734,1739,1744,1749,1754,1759,1764,1768,1773,1777,1782,1786,1791,1796,1800,1804,1809,1813,1818,1822,1826,1831,1836,1840],{"__ignoreMap":175},[203,1725,1726],{"class":205,"line":206},[203,1727,1728],{},"'defaults' => [\n",[203,1730,1731],{"class":205,"line":212},[203,1732,1733],{},"    'supervisor-1' => [\n",[203,1735,1736],{"class":205,"line":14},[203,1737,1738],{},"        'connection' => 'redis',\n",[203,1740,1741],{"class":205,"line":224},[203,1742,1743],{},"        'queue' => ['default'],\n",[203,1745,1746],{"class":205,"line":229},[203,1747,1748],{},"        'balance' => 'auto',\n",[203,1750,1751],{"class":205,"line":235},[203,1752,1753],{},"        'minProcesses' => 1,\n",[203,1755,1756],{"class":205,"line":241},[203,1757,1758],{},"        'maxProcesses' => 5,\n",[203,1760,1761],{"class":205,"line":247},[203,1762,1763],{},"        'tries' => 3,\n",[203,1765,1766],{"class":205,"line":253},[203,1767,1710],{},[203,1769,1770],{"class":205,"line":259},[203,1771,1772],{},"    'supervisor-2' => [\n",[203,1774,1775],{"class":205,"line":265},[203,1776,1738],{},[203,1778,1779],{"class":205,"line":271},[203,1780,1781],{},"        'queue' => ['analysis', 'classification'],\n",[203,1783,1784],{"class":205,"line":27},[203,1785,1748],{},[203,1787,1788],{"class":205,"line":282},[203,1789,1790],{},"        'minProcesses' => 2,\n",[203,1792,1793],{"class":205,"line":287},[203,1794,1795],{},"        'maxProcesses' => 10,\n",[203,1797,1798],{"class":205,"line":293},[203,1799,1763],{},[203,1801,1802],{"class":205,"line":299},[203,1803,1710],{},[203,1805,1806],{"class":205,"line":305},[203,1807,1808],{},"    'supervisor-3' => [\n",[203,1810,1811],{"class":205,"line":310},[203,1812,1738],{},[203,1814,1815],{"class":205,"line":316},[203,1816,1817],{},"        'queue' => ['ingestion', 'review', 'reporting'],\n",[203,1819,1820],{"class":205,"line":322},[203,1821,1748],{},[203,1823,1824],{"class":205,"line":328},[203,1825,1753],{},[203,1827,1828],{"class":205,"line":333},[203,1829,1830],{},"        'maxProcesses' => 3,\n",[203,1832,1833],{"class":205,"line":339},[203,1834,1835],{},"        'tries' => 5,\n",[203,1837,1838],{"class":205,"line":345},[203,1839,1710],{},[203,1841,1842],{"class":205,"line":351},[203,1843,1715],{},[102,1845,1846],{},"Key insight: the analysis queue has the most workers because it makes the slowest LLM calls. The ingestion and reporting queues are lightweight and need fewer workers. Separating them means a backlog in ingestion never blocks analysis results from being processed.",[98,1848,1850],{"id":1849},"laravel-vs-python-for-ai-backends","Laravel vs Python for AI Backends",[102,1852,1853],{},"After a year of building hybrid systems in both ecosystems, here is my honest comparison:",[1855,1856,1857,1874],"table",{},[1858,1859,1860],"thead",{},[1861,1862,1863,1868,1871],"tr",{},[1864,1865,1867],"th",{"align":1866},"left","Criteria",[1864,1869,1870],{"align":1866},"Laravel",[1864,1872,1873],{"align":1866},"Python (FastAPI + Celery)",[1875,1876,1877,1889,1900,1911,1922,1933,1944,1955,1966,1977,1988],"tbody",{},[1861,1878,1879,1883,1886],{},[1880,1881,1882],"td",{"align":1866},"Queue system",[1880,1884,1885],{"align":1866},"Built-in with Horizon dashboard",[1880,1887,1888],{"align":1866},"Celery + Redis\u002FRabbitMQ, manual monitoring",[1861,1890,1891,1894,1897],{},[1880,1892,1893],{"align":1866},"Job persistence",[1880,1895,1896],{"align":1866},"MySQL\u002FPostgres out of the box",[1880,1898,1899],{"align":1866},"Requires result backend configuration",[1861,1901,1902,1905,1908],{},[1880,1903,1904],{"align":1866},"API gateway features",[1880,1906,1907],{"align":1866},"Native rate limiting & caching middleware",[1880,1909,1910],{"align":1866},"Custom implementation or third-party lib",[1861,1912,1913,1916,1919],{},[1880,1914,1915],{"align":1866},"LLM SDK ecosystem",[1880,1917,1918],{"align":1866},"Limited, community-maintained packages",[1880,1920,1921],{"align":1866},"Rich (OpenAI, Anthropic, LangChain)",[1861,1923,1924,1927,1930],{},[1880,1925,1926],{"align":1866},"Developer productivity",[1880,1928,1929],{"align":1866},"High — conventions, ORM, artisan commands",[1880,1931,1932],{"align":1866},"Medium — more boilerplate for same patterns",[1861,1934,1935,1938,1941],{},[1880,1936,1937],{"align":1866},"Runtime performance",[1880,1939,1940],{"align":1866},"Good for I\u002FO-bound orchestration",[1880,1942,1943],{"align":1866},"Better for CPU-bound computation",[1861,1945,1946,1949,1952],{},[1880,1947,1948],{"align":1866},"Team skill availability",[1880,1950,1951],{"align":1866},"Large pool of Laravel\u002FPHP developers",[1880,1953,1954],{"align":1866},"Large pool, but fewer with queue expertise",[1861,1956,1957,1960,1963],{},[1880,1958,1959],{"align":1866},"Deployment simplicity",[1880,1961,1962],{"align":1866},"Single server, Forge\u002FEnvoyer ecosystem",[1880,1964,1965],{"align":1866},"Multiple services, more moving parts",[1861,1967,1968,1971,1974],{},[1880,1969,1970],{"align":1866},"Cost for typical SaaS",[1880,1972,1973],{"align":1866},"Lower — one server for app + queue",[1880,1975,1976],{"align":1866},"Higher — separate worker infrastructure",[1861,1978,1979,1982,1985],{},[1880,1980,1981],{"align":1866},"Real-time capabilities",[1880,1983,1984],{"align":1866},"Laravel Reverb (WebSockets)",[1880,1986,1987],{"align":1866},"WebSocket libraries, no built-in solution",[1861,1989,1990,1993,1996],{},[1880,1991,1992],{"align":1866},"ML inference on same server",[1880,1994,1995],{"align":1866},"Not practical",[1880,1997,1998],{"align":1866},"Possible with ONNX, TensorFlow Lite",[102,2000,2001],{},"This table highlights the tradeoff that defines the hybrid approach: Laravel wins on developer experience and operational simplicity for the orchestration layer; Python wins for anything involving actual model computation. The pragmatic solution is to use both where each excels, connected through the queue or API gateway layer.",[98,2003,2005],{"id":2004},"when-laravel-is-the-wrong-choice","When Laravel Is the Wrong Choice",[102,2007,2008],{},"I want to be clear about the limitations. There are scenarios where Laravel is not the right tool for an AI backend:",[124,2010,2011,2017,2023,2029],{},[127,2012,2013,2016],{},[130,2014,2015],{},"Heavy ML training or fine-tuning",": If your system needs to train models on user data, use Python with PyTorch or TensorFlow. Laravel has no role here.",[127,2018,2019,2022],{},[130,2020,2021],{},"Real-time streaming inference",": Applications that require sub-100ms response times for every token (like interactive chatbots) benefit from Python's asyncio ecosystem with FastAPI streaming responses. Laravel's request lifecycle adds overhead.",[127,2024,2025,2028],{},[130,2026,2027],{},"On-device GPU workloads",": If you need to run local models with GPU acceleration, Python (or Rust) is the only practical choice. PHP's FFI layer is not suitable for this.",[127,2030,2031,2034],{},[130,2032,2033],{},"Large-scale embeddings pipelines",": Processing millions of documents through embedding models is better served by Python batch processing systems or dedicated vector database pipelines.",[102,2036,2037],{},"In all these cases, Laravel can still serve as the management layer — handling user authentication, billing, and job dispatching — while Python workers handle the computation. That is the hybrid approach in practice.",[98,2039,2041],{"id":2040},"the-2026-sweet-spot","The 2026 Sweet Spot",[102,2043,2044],{},"Hybrid architectures are the pragmatic middle ground between the hype of \"AI-native\" stacks and the reliability of traditional web frameworks. After building multiple production systems, I have settled on a stack that uses Laravel for the orchestration surface and delegates actual model work to specialized services — whether external LLM APIs, Python microservices, or serverless functions.",[102,2046,2047],{},"This approach gives me the best of both worlds. I get Laravel's mature queue system, excellent developer tooling, and large ecosystem for the parts of the application that handle user management, billing, and workflow orchestration. I get access to the latest AI models through a clean API gateway that manages costs, rate limits, and caching. And I avoid the operational complexity of maintaining a pure Python stack for what is fundamentally a CRUD application with AI features bolted on.",[102,2049,2050],{},"If you are building a SaaS product in 2026 that includes AI agent features, consider this architecture. Start with Laravel for the application layer, add queue-driven agent jobs, build a proper API gateway for LLM calls, and only reach for Python when you need actual model computation. Your deployment will be simpler, your costs lower, and your team will thank you.",[2052,2053,2054],"style",{},"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);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":175,"searchDepth":212,"depth":212,"links":2056},[2057,2058,2059,2060,2061,2062,2063,2064,2065,2066,2067],{"id":100,"depth":212,"text":90},{"id":110,"depth":212,"text":111},{"id":163,"depth":212,"text":164},{"id":187,"depth":212,"text":188},{"id":688,"depth":212,"text":689},{"id":723,"depth":212,"text":724},{"id":880,"depth":212,"text":881},{"id":1559,"depth":212,"text":1560},{"id":1849,"depth":212,"text":1850},{"id":2004,"depth":212,"text":2005},{"id":2040,"depth":212,"text":2041},"2026-05-26","Laravel as the orchestration backend for AI agents — queue-driven execution, API gateway for LLM systems, and real-world SaaS architecture patterns.",null,"md",{"readingTime":2073},"12 min read","\u002Fblog\u002F42-laravel-ai-agent-hybrid-backends",{"title":90,"description":2069},"Hybrid Backend Architecture","Building production backends that bridge traditional frameworks with AI systems",{"src":2079,"mime":2080,"alt":2081,"width":2082,"height":2083},"\u002Fimg\u002Fblog\u002F42-laravel-ai-agent-hybrid-backends\u002Fbanner.svg","svg","Hybrid backend architecture showing Laravel orchestration layer connecting to AI agent systems via queues and API gateway",1200,680,"blog\u002F42-laravel-ai-agent-hybrid-backends",[1870,2086,2087,2088,2089,2090],"AI","Agents","Backend Architecture","Hybrid Systems","PHP","OgbvWYVgnZO7O5bgKEjq8URB2lodlALjguQvvkw8GZs",[2093],{"id":89,"title":90,"abstract":91,"author":92,"authorUrl":93,"body":2094,"date":2068,"dateUpdated":2068,"description":2069,"excerpt":2070,"extension":2071,"featured":215,"headline":90,"image":2070,"meta":3564,"navigation":215,"ogImage":2070,"path":2074,"seo":3565,"series":2076,"seriesDescription":2077,"seriesOrder":206,"socialImage":3566,"stem":2084,"tags":3567,"__hash__":2091},{"type":95,"value":2095,"toc":3551},[2096,2098,2100,2102,2104,2106,2110,2132,2134,2136,2138,2143,2145,2147,2149,2151,2153,2473,2475,2493,2495,2497,2517,2519,2521,2523,2639,2643,2645,2647,3163,3165,3181,3183,3185,3187,3219,3221,3225,3297,3299,3407,3409,3411,3413,3515,3517,3519,3521,3539,3541,3543,3545,3547,3549],[98,2097,90],{"id":100},[102,2099,104],{},[102,2101,107],{},[98,2103,111],{"id":110},[102,2105,114],{},[102,2107,117,2108,122],{},[119,2109,121],{},[124,2111,2112,2116,2120,2124,2128],{},[127,2113,2114,133],{},[130,2115,132],{},[127,2117,2118,139],{},[130,2119,138],{},[127,2121,2122,145],{},[130,2123,144],{},[127,2125,2126,151],{},[130,2127,150],{},[127,2129,2130,157],{},[130,2131,156],{},[102,2133,160],{},[98,2135,164],{"id":163},[102,2137,167],{},[169,2139,2141],{"className":2140,"code":173,"language":174,"meta":175},[172],[177,2142,173],{"__ignoreMap":175},[102,2144,181],{},[102,2146,184],{},[98,2148,188],{"id":187},[102,2150,191],{},[102,2152,194],{},[169,2154,2155],{"className":197,"code":198,"language":199,"meta":175,"style":175},[177,2156,2157,2161,2165,2169,2173,2177,2181,2185,2189,2193,2197,2201,2205,2209,2213,2217,2221,2225,2229,2233,2237,2241,2245,2249,2253,2257,2261,2265,2269,2273,2277,2281,2285,2289,2293,2297,2301,2305,2309,2313,2317,2321,2325,2329,2333,2337,2341,2345,2349,2353,2357,2361,2365,2369,2373,2377,2381,2385,2389,2393,2397,2401,2405,2409,2413,2417,2421,2425,2429,2433,2437,2441,2445,2449,2453,2457,2461,2465,2469],{"__ignoreMap":175},[203,2158,2159],{"class":205,"line":206},[203,2160,209],{},[203,2162,2163],{"class":205,"line":212},[203,2164,216],{"emptyLinePlaceholder":215},[203,2166,2167],{"class":205,"line":14},[203,2168,221],{},[203,2170,2171],{"class":205,"line":224},[203,2172,216],{"emptyLinePlaceholder":215},[203,2174,2175],{"class":205,"line":229},[203,2176,232],{},[203,2178,2179],{"class":205,"line":235},[203,2180,238],{},[203,2182,2183],{"class":205,"line":241},[203,2184,244],{},[203,2186,2187],{"class":205,"line":247},[203,2188,250],{},[203,2190,2191],{"class":205,"line":253},[203,2192,256],{},[203,2194,2195],{"class":205,"line":259},[203,2196,262],{},[203,2198,2199],{"class":205,"line":265},[203,2200,268],{},[203,2202,2203],{"class":205,"line":271},[203,2204,274],{},[203,2206,2207],{"class":205,"line":27},[203,2208,279],{},[203,2210,2211],{"class":205,"line":282},[203,2212,216],{"emptyLinePlaceholder":215},[203,2214,2215],{"class":205,"line":287},[203,2216,290],{},[203,2218,2219],{"class":205,"line":293},[203,2220,296],{},[203,2222,2223],{"class":205,"line":299},[203,2224,302],{},[203,2226,2227],{"class":205,"line":305},[203,2228,216],{"emptyLinePlaceholder":215},[203,2230,2231],{"class":205,"line":310},[203,2232,313],{},[203,2234,2235],{"class":205,"line":316},[203,2236,319],{},[203,2238,2239],{"class":205,"line":322},[203,2240,325],{},[203,2242,2243],{"class":205,"line":328},[203,2244,216],{"emptyLinePlaceholder":215},[203,2246,2247],{"class":205,"line":333},[203,2248,336],{},[203,2250,2251],{"class":205,"line":339},[203,2252,342],{},[203,2254,2255],{"class":205,"line":345},[203,2256,348],{},[203,2258,2259],{"class":205,"line":351},[203,2260,216],{"emptyLinePlaceholder":215},[203,2262,2263],{"class":205,"line":356},[203,2264,359],{},[203,2266,2267],{"class":205,"line":362},[203,2268,365],{},[203,2270,2271],{"class":205,"line":368},[203,2272,371],{},[203,2274,2275],{"class":205,"line":374},[203,2276,216],{"emptyLinePlaceholder":215},[203,2278,2279],{"class":205,"line":379},[203,2280,382],{},[203,2282,2283],{"class":205,"line":385},[203,2284,388],{},[203,2286,2287],{"class":205,"line":391},[203,2288,394],{},[203,2290,2291],{"class":205,"line":397},[203,2292,400],{},[203,2294,2295],{"class":205,"line":403},[203,2296,406],{},[203,2298,2299],{"class":205,"line":409},[203,2300,412],{},[203,2302,2303],{"class":205,"line":415},[203,2304,418],{},[203,2306,2307],{"class":205,"line":421},[203,2308,424],{},[203,2310,2311],{"class":205,"line":427},[203,2312,430],{},[203,2314,2315],{"class":205,"line":433},[203,2316,216],{"emptyLinePlaceholder":215},[203,2318,2319],{"class":205,"line":438},[203,2320,441],{},[203,2322,2323],{"class":205,"line":444},[203,2324,447],{},[203,2326,2327],{"class":205,"line":450},[203,2328,453],{},[203,2330,2331],{"class":205,"line":456},[203,2332,406],{},[203,2334,2335],{"class":205,"line":461},[203,2336,464],{},[203,2338,2339],{"class":205,"line":467},[203,2340,470],{},[203,2342,2343],{"class":205,"line":473},[203,2344,418],{},[203,2346,2347],{"class":205,"line":478},[203,2348,481],{},[203,2350,2351],{"class":205,"line":484},[203,2352,430],{},[203,2354,2355],{"class":205,"line":489},[203,2356,216],{"emptyLinePlaceholder":215},[203,2358,2359],{"class":205,"line":494},[203,2360,497],{},[203,2362,2363],{"class":205,"line":500},[203,2364,503],{},[203,2366,2367],{"class":205,"line":506},[203,2368,509],{},[203,2370,2371],{"class":205,"line":512},[203,2372,515],{},[203,2374,2375],{"class":205,"line":518},[203,2376,521],{},[203,2378,2379],{"class":205,"line":524},[203,2380,527],{},[203,2382,2383],{"class":205,"line":530},[203,2384,216],{"emptyLinePlaceholder":215},[203,2386,2387],{"class":205,"line":535},[203,2388,538],{},[203,2390,2391],{"class":205,"line":541},[203,2392,544],{},[203,2394,2395],{"class":205,"line":547},[203,2396,550],{},[203,2398,2399],{"class":205,"line":553},[203,2400,216],{"emptyLinePlaceholder":215},[203,2402,2403],{"class":205,"line":558},[203,2404,561],{},[203,2406,2407],{"class":205,"line":564},[203,2408,216],{"emptyLinePlaceholder":215},[203,2410,2411],{"class":205,"line":569},[203,2412,572],{},[203,2414,2415],{"class":205,"line":575},[203,2416,578],{},[203,2418,2419],{"class":205,"line":581},[203,2420,584],{},[203,2422,2423],{"class":205,"line":587},[203,2424,590],{},[203,2426,2427],{"class":205,"line":593},[203,2428,527],{},[203,2430,2431],{"class":205,"line":598},[203,2432,216],{"emptyLinePlaceholder":215},[203,2434,2435],{"class":205,"line":603},[203,2436,503],{},[203,2438,2439],{"class":205,"line":608},[203,2440,611],{},[203,2442,2443],{"class":205,"line":614},[203,2444,590],{},[203,2446,2447],{"class":205,"line":619},[203,2448,622],{},[203,2450,2451],{"class":205,"line":625},[203,2452,527],{},[203,2454,2455],{"class":205,"line":630},[203,2456,216],{"emptyLinePlaceholder":215},[203,2458,2459],{"class":205,"line":635},[203,2460,638],{},[203,2462,2463],{"class":205,"line":641},[203,2464,644],{},[203,2466,2467],{"class":205,"line":647},[203,2468,650],{},[203,2470,2471],{"class":205,"line":653},[203,2472,656],{},[102,2474,659],{},[124,2476,2477,2481,2485,2489],{},[127,2478,2479,667],{},[130,2480,666],{},[127,2482,2483,673],{},[130,2484,672],{},[127,2486,2487,679],{},[130,2488,678],{},[127,2490,2491,685],{},[130,2492,684],{},[98,2494,689],{"id":688},[102,2496,692],{},[169,2498,2499],{"className":197,"code":695,"language":199,"meta":175,"style":175},[177,2500,2501,2505,2509,2513],{"__ignoreMap":175},[203,2502,2503],{"class":205,"line":206},[203,2504,702],{},[203,2506,2507],{"class":205,"line":212},[203,2508,707],{},[203,2510,2511],{"class":205,"line":14},[203,2512,712],{},[203,2514,2515],{"class":205,"line":224},[203,2516,717],{},[102,2518,720],{},[98,2520,724],{"id":723},[102,2522,727],{},[169,2524,2525],{"className":730,"code":731,"language":732,"meta":175,"style":175},[177,2526,2527,2531,2535,2539,2543,2547,2551,2555,2559,2563,2567,2571,2575,2579,2583,2587,2591,2595,2599,2603,2607,2611,2615,2619,2623,2627,2631,2635],{"__ignoreMap":175},[203,2528,2529],{"class":205,"line":206},[203,2530,739],{},[203,2532,2533],{"class":205,"line":212},[203,2534,744],{},[203,2536,2537],{"class":205,"line":14},[203,2538,749],{},[203,2540,2541],{"class":205,"line":224},[203,2542,754],{},[203,2544,2545],{"class":205,"line":229},[203,2546,759],{},[203,2548,2549],{"class":205,"line":235},[203,2550,764],{},[203,2552,2553],{"class":205,"line":241},[203,2554,769],{},[203,2556,2557],{"class":205,"line":247},[203,2558,216],{"emptyLinePlaceholder":215},[203,2560,2561],{"class":205,"line":253},[203,2562,778],{},[203,2564,2565],{"class":205,"line":259},[203,2566,783],{},[203,2568,2569],{"class":205,"line":265},[203,2570,788],{},[203,2572,2573],{"class":205,"line":271},[203,2574,793],{},[203,2576,2577],{"class":205,"line":27},[203,2578,216],{"emptyLinePlaceholder":215},[203,2580,2581],{"class":205,"line":282},[203,2582,802],{},[203,2584,2585],{"class":205,"line":287},[203,2586,807],{},[203,2588,2589],{"class":205,"line":293},[203,2590,812],{},[203,2592,2593],{"class":205,"line":299},[203,2594,817],{},[203,2596,2597],{"class":205,"line":305},[203,2598,822],{},[203,2600,2601],{"class":205,"line":310},[203,2602,827],{},[203,2604,2605],{"class":205,"line":316},[203,2606,832],{},[203,2608,2609],{"class":205,"line":322},[203,2610,216],{"emptyLinePlaceholder":215},[203,2612,2613],{"class":205,"line":328},[203,2614,841],{},[203,2616,2617],{"class":205,"line":333},[203,2618,846],{},[203,2620,2621],{"class":205,"line":339},[203,2622,851],{},[203,2624,2625],{"class":205,"line":345},[203,2626,216],{"emptyLinePlaceholder":215},[203,2628,2629],{"class":205,"line":351},[203,2630,860],{},[203,2632,2633],{"class":205,"line":356},[203,2634,865],{},[203,2636,2637],{"class":205,"line":362},[203,2638,870],{},[102,2640,873,2641,877],{},[130,2642,876],{},[98,2644,881],{"id":880},[102,2646,884],{},[169,2648,2649],{"className":197,"code":887,"language":199,"meta":175,"style":175},[177,2650,2651,2655,2659,2663,2667,2671,2675,2679,2683,2687,2691,2695,2699,2703,2707,2711,2715,2719,2723,2727,2731,2735,2739,2743,2747,2751,2755,2759,2763,2767,2771,2775,2779,2783,2787,2791,2795,2799,2803,2807,2811,2815,2819,2823,2827,2831,2835,2839,2843,2847,2851,2855,2859,2863,2867,2871,2875,2879,2883,2887,2891,2895,2899,2903,2907,2911,2915,2919,2923,2927,2931,2935,2939,2943,2947,2951,2955,2959,2963,2967,2971,2975,2979,2983,2987,2991,2995,2999,3003,3007,3011,3015,3019,3023,3027,3031,3035,3039,3043,3047,3051,3055,3059,3063,3067,3071,3075,3079,3083,3087,3091,3095,3099,3103,3107,3111,3115,3119,3123,3127,3131,3135,3139,3143,3147,3151,3155,3159],{"__ignoreMap":175},[203,2652,2653],{"class":205,"line":206},[203,2654,209],{},[203,2656,2657],{"class":205,"line":212},[203,2658,216],{"emptyLinePlaceholder":215},[203,2660,2661],{"class":205,"line":14},[203,2662,902],{},[203,2664,2665],{"class":205,"line":224},[203,2666,216],{"emptyLinePlaceholder":215},[203,2668,2669],{"class":205,"line":229},[203,2670,911],{},[203,2672,2673],{"class":205,"line":235},[203,2674,916],{},[203,2676,2677],{"class":205,"line":241},[203,2678,921],{},[203,2680,2681],{"class":205,"line":247},[203,2682,279],{},[203,2684,2685],{"class":205,"line":253},[203,2686,216],{"emptyLinePlaceholder":215},[203,2688,2689],{"class":205,"line":259},[203,2690,934],{},[203,2692,2693],{"class":205,"line":265},[203,2694,296],{},[203,2696,2697],{"class":205,"line":271},[203,2698,943],{},[203,2700,2701],{"class":205,"line":27},[203,2702,948],{},[203,2704,2705],{"class":205,"line":282},[203,2706,953],{},[203,2708,2709],{"class":205,"line":287},[203,2710,958],{},[203,2712,2713],{"class":205,"line":293},[203,2714,963],{},[203,2716,2717],{"class":205,"line":299},[203,2718,216],{"emptyLinePlaceholder":215},[203,2720,2721],{"class":205,"line":305},[203,2722,972],{},[203,2724,2725],{"class":205,"line":310},[203,2726,977],{},[203,2728,2729],{"class":205,"line":316},[203,2730,982],{},[203,2732,2733],{"class":205,"line":322},[203,2734,987],{},[203,2736,2737],{"class":205,"line":328},[203,2738,992],{},[203,2740,2741],{"class":205,"line":333},[203,2742,997],{},[203,2744,2745],{"class":205,"line":339},[203,2746,216],{"emptyLinePlaceholder":215},[203,2748,2749],{"class":205,"line":345},[203,2750,1006],{},[203,2752,2753],{"class":205,"line":351},[203,2754,1011],{},[203,2756,2757],{"class":205,"line":356},[203,2758,1016],{},[203,2760,2761],{"class":205,"line":362},[203,2762,1021],{},[203,2764,2765],{"class":205,"line":368},[203,2766,644],{},[203,2768,2769],{"class":205,"line":374},[203,2770,216],{"emptyLinePlaceholder":215},[203,2772,2773],{"class":205,"line":379},[203,2774,1034],{},[203,2776,2777],{"class":205,"line":385},[203,2778,1039],{},[203,2780,2781],{"class":205,"line":391},[203,2782,1044],{},[203,2784,2785],{"class":205,"line":397},[203,2786,216],{"emptyLinePlaceholder":215},[203,2788,2789],{"class":205,"line":403},[203,2790,1053],{},[203,2792,2793],{"class":205,"line":409},[203,2794,1058],{},[203,2796,2797],{"class":205,"line":415},[203,2798,1063],{},[203,2800,2801],{"class":205,"line":421},[203,2802,1068],{},[203,2804,2805],{"class":205,"line":427},[203,2806,1073],{},[203,2808,2809],{"class":205,"line":433},[203,2810,1078],{},[203,2812,2813],{"class":205,"line":438},[203,2814,1083],{},[203,2816,2817],{"class":205,"line":444},[203,2818,1088],{},[203,2820,2821],{"class":205,"line":450},[203,2822,1093],{},[203,2824,2825],{"class":205,"line":456},[203,2826,1098],{},[203,2828,2829],{"class":205,"line":461},[203,2830,1103],{},[203,2832,2833],{"class":205,"line":467},[203,2834,1108],{},[203,2836,2837],{"class":205,"line":473},[203,2838,527],{},[203,2840,2841],{"class":205,"line":478},[203,2842,216],{"emptyLinePlaceholder":215},[203,2844,2845],{"class":205,"line":484},[203,2846,1121],{},[203,2848,2849],{"class":205,"line":489},[203,2850,1126],{},[203,2852,2853],{"class":205,"line":494},[203,2854,1131],{},[203,2856,2857],{"class":205,"line":500},[203,2858,1136],{},[203,2860,2861],{"class":205,"line":506},[203,2862,527],{},[203,2864,2865],{"class":205,"line":512},[203,2866,1145],{},[203,2868,2869],{"class":205,"line":518},[203,2870,1150],{},[203,2872,2873],{"class":205,"line":524},[203,2874,430],{},[203,2876,2877],{"class":205,"line":530},[203,2878,644],{},[203,2880,2881],{"class":205,"line":535},[203,2882,216],{"emptyLinePlaceholder":215},[203,2884,2885],{"class":205,"line":541},[203,2886,1167],{},[203,2888,2889],{"class":205,"line":547},[203,2890,1172],{},[203,2892,2893],{"class":205,"line":553},[203,2894,216],{"emptyLinePlaceholder":215},[203,2896,2897],{"class":205,"line":558},[203,2898,1181],{},[203,2900,2901],{"class":205,"line":564},[203,2902,1186],{},[203,2904,2905],{"class":205,"line":569},[203,2906,216],{"emptyLinePlaceholder":215},[203,2908,2909],{"class":205,"line":575},[203,2910,1195],{},[203,2912,2913],{"class":205,"line":581},[203,2914,1200],{},[203,2916,2917],{"class":205,"line":587},[203,2918,1205],{},[203,2920,2921],{"class":205,"line":593},[203,2922,644],{},[203,2924,2925],{"class":205,"line":598},[203,2926,216],{"emptyLinePlaceholder":215},[203,2928,2929],{"class":205,"line":603},[203,2930,1218],{},[203,2932,2933],{"class":205,"line":608},[203,2934,650],{},[203,2936,2937],{"class":205,"line":614},[203,2938,216],{"emptyLinePlaceholder":215},[203,2940,2941],{"class":205,"line":619},[203,2942,1231],{},[203,2944,2945],{"class":205,"line":625},[203,2946,365],{},[203,2948,2949],{"class":205,"line":630},[203,2950,1240],{},[203,2952,2953],{"class":205,"line":635},[203,2954,1245],{},[203,2956,2957],{"class":205,"line":641},[203,2958,216],{"emptyLinePlaceholder":215},[203,2960,2961],{"class":205,"line":647},[203,2962,1254],{},[203,2964,2965],{"class":205,"line":653},[203,2966,1259],{},[203,2968,2969],{"class":205,"line":1262},[203,2970,644],{},[203,2972,2973],{"class":205,"line":1267},[203,2974,650],{},[203,2976,2977],{"class":205,"line":1272},[203,2978,216],{"emptyLinePlaceholder":215},[203,2980,2981],{"class":205,"line":1277},[203,2982,1280],{},[203,2984,2985],{"class":205,"line":1283},[203,2986,365],{},[203,2988,2989],{"class":205,"line":1288},[203,2990,1291],{},[203,2992,2993],{"class":205,"line":1294},[203,2994,1297],{},[203,2996,2997],{"class":205,"line":1300},[203,2998,216],{"emptyLinePlaceholder":215},[203,3000,3001],{"class":205,"line":1305},[203,3002,1308],{},[203,3004,3005],{"class":205,"line":1311},[203,3006,1314],{},[203,3008,3009],{"class":205,"line":1317},[203,3010,644],{},[203,3012,3013],{"class":205,"line":1322},[203,3014,216],{"emptyLinePlaceholder":215},[203,3016,3017],{"class":205,"line":1327},[203,3018,1330],{},[203,3020,3021],{"class":205,"line":1333},[203,3022,1336],{},[203,3024,3025],{"class":205,"line":1339},[203,3026,216],{"emptyLinePlaceholder":215},[203,3028,3029],{"class":205,"line":1344},[203,3030,1347],{},[203,3032,3033],{"class":205,"line":1350},[203,3034,1353],{},[203,3036,3037],{"class":205,"line":1356},[203,3038,644],{},[203,3040,3041],{"class":205,"line":1361},[203,3042,650],{},[203,3044,3045],{"class":205,"line":1366},[203,3046,216],{"emptyLinePlaceholder":215},[203,3048,3049],{"class":205,"line":21},[203,3050,1373],{},[203,3052,3053],{"class":205,"line":1376},[203,3054,365],{},[203,3056,3057],{"class":205,"line":1381},[203,3058,1384],{},[203,3060,3061],{"class":205,"line":1387},[203,3062,1390],{},[203,3064,3065],{"class":205,"line":1393},[203,3066,1396],{},[203,3068,3069],{"class":205,"line":1399},[203,3070,1402],{},[203,3072,3073],{"class":205,"line":1405},[203,3074,1408],{},[203,3076,3077],{"class":205,"line":1411},[203,3078,216],{"emptyLinePlaceholder":215},[203,3080,3081],{"class":205,"line":1416},[203,3082,1419],{},[203,3084,3085],{"class":205,"line":1422},[203,3086,1425],{},[203,3088,3089],{"class":205,"line":1428},[203,3090,1431],{},[203,3092,3093],{"class":205,"line":1434},[203,3094,1437],{},[203,3096,3097],{"class":205,"line":1440},[203,3098,1419],{},[203,3100,3101],{"class":205,"line":1445},[203,3102,1448],{},[203,3104,3105],{"class":205,"line":1451},[203,3106,1454],{},[203,3108,3109],{"class":205,"line":1457},[203,3110,1460],{},[203,3112,3113],{"class":205,"line":1463},[203,3114,1437],{},[203,3116,3117],{"class":205,"line":1468},[203,3118,650],{},[203,3120,3121],{"class":205,"line":1473},[203,3122,216],{"emptyLinePlaceholder":215},[203,3124,3125],{"class":205,"line":1478},[203,3126,1481],{},[203,3128,3129],{"class":205,"line":1484},[203,3130,1487],{},[203,3132,3133],{"class":205,"line":1490},[203,3134,982],{},[203,3136,3137],{"class":205,"line":1495},[203,3138,1498],{},[203,3140,3141],{"class":205,"line":1501},[203,3142,992],{},[203,3144,3145],{"class":205,"line":1506},[203,3146,1509],{},[203,3148,3149],{"class":205,"line":46},[203,3150,1514],{},[203,3152,3153],{"class":205,"line":1517},[203,3154,1437],{},[203,3156,3157],{"class":205,"line":1522},[203,3158,650],{},[203,3160,3161],{"class":205,"line":1527},[203,3162,656],{},[102,3164,1532],{},[124,3166,3167,3171,3175],{},[127,3168,3169,1540],{},[130,3170,1539],{},[127,3172,3173,1546],{},[130,3174,1545],{},[127,3176,3177,1552,3179,1556],{},[130,3178,1551],{},[177,3180,1555],{},[98,3182,1560],{"id":1559},[102,3184,1563],{},[102,3186,1566],{},[1568,3188,3189,3195,3201,3207,3213],{},[127,3190,3191,1575,3193,1579],{},[130,3192,1574],{},[177,3194,1578],{},[127,3196,3197,1575,3199,1588],{},[130,3198,1584],{},[177,3200,1587],{},[127,3202,3203,1575,3205,1597],{},[130,3204,1593],{},[177,3206,1596],{},[127,3208,3209,1575,3211,1606],{},[130,3210,1602],{},[177,3212,1605],{},[127,3214,3215,1575,3217,1615],{},[130,3216,1611],{},[177,3218,1614],{},[102,3220,1618],{},[102,3222,1621,3223,1625],{},[177,3224,1624],{},[169,3226,3227],{"className":197,"code":1628,"language":199,"meta":175,"style":175},[177,3228,3229,3233,3237,3241,3245,3249,3253,3257,3261,3265,3269,3273,3277,3281,3285,3289,3293],{"__ignoreMap":175},[203,3230,3231],{"class":205,"line":206},[203,3232,1635],{},[203,3234,3235],{"class":205,"line":212},[203,3236,1640],{},[203,3238,3239],{"class":205,"line":14},[203,3240,1645],{},[203,3242,3243],{"class":205,"line":224},[203,3244,1650],{},[203,3246,3247],{"class":205,"line":229},[203,3248,1655],{},[203,3250,3251],{"class":205,"line":235},[203,3252,1660],{},[203,3254,3255],{"class":205,"line":241},[203,3256,1665],{},[203,3258,3259],{"class":205,"line":247},[203,3260,1670],{},[203,3262,3263],{"class":205,"line":253},[203,3264,1675],{},[203,3266,3267],{"class":205,"line":259},[203,3268,1680],{},[203,3270,3271],{"class":205,"line":265},[203,3272,1685],{},[203,3274,3275],{"class":205,"line":271},[203,3276,1690],{},[203,3278,3279],{"class":205,"line":27},[203,3280,1695],{},[203,3282,3283],{"class":205,"line":282},[203,3284,1700],{},[203,3286,3287],{"class":205,"line":287},[203,3288,1705],{},[203,3290,3291],{"class":205,"line":293},[203,3292,1710],{},[203,3294,3295],{"class":205,"line":299},[203,3296,1715],{},[102,3298,1718],{},[169,3300,3301],{"className":197,"code":1721,"language":199,"meta":175,"style":175},[177,3302,3303,3307,3311,3315,3319,3323,3327,3331,3335,3339,3343,3347,3351,3355,3359,3363,3367,3371,3375,3379,3383,3387,3391,3395,3399,3403],{"__ignoreMap":175},[203,3304,3305],{"class":205,"line":206},[203,3306,1728],{},[203,3308,3309],{"class":205,"line":212},[203,3310,1733],{},[203,3312,3313],{"class":205,"line":14},[203,3314,1738],{},[203,3316,3317],{"class":205,"line":224},[203,3318,1743],{},[203,3320,3321],{"class":205,"line":229},[203,3322,1748],{},[203,3324,3325],{"class":205,"line":235},[203,3326,1753],{},[203,3328,3329],{"class":205,"line":241},[203,3330,1758],{},[203,3332,3333],{"class":205,"line":247},[203,3334,1763],{},[203,3336,3337],{"class":205,"line":253},[203,3338,1710],{},[203,3340,3341],{"class":205,"line":259},[203,3342,1772],{},[203,3344,3345],{"class":205,"line":265},[203,3346,1738],{},[203,3348,3349],{"class":205,"line":271},[203,3350,1781],{},[203,3352,3353],{"class":205,"line":27},[203,3354,1748],{},[203,3356,3357],{"class":205,"line":282},[203,3358,1790],{},[203,3360,3361],{"class":205,"line":287},[203,3362,1795],{},[203,3364,3365],{"class":205,"line":293},[203,3366,1763],{},[203,3368,3369],{"class":205,"line":299},[203,3370,1710],{},[203,3372,3373],{"class":205,"line":305},[203,3374,1808],{},[203,3376,3377],{"class":205,"line":310},[203,3378,1738],{},[203,3380,3381],{"class":205,"line":316},[203,3382,1817],{},[203,3384,3385],{"class":205,"line":322},[203,3386,1748],{},[203,3388,3389],{"class":205,"line":328},[203,3390,1753],{},[203,3392,3393],{"class":205,"line":333},[203,3394,1830],{},[203,3396,3397],{"class":205,"line":339},[203,3398,1835],{},[203,3400,3401],{"class":205,"line":345},[203,3402,1710],{},[203,3404,3405],{"class":205,"line":351},[203,3406,1715],{},[102,3408,1846],{},[98,3410,1850],{"id":1849},[102,3412,1853],{},[1855,3414,3415,3425],{},[1858,3416,3417],{},[1861,3418,3419,3421,3423],{},[1864,3420,1867],{"align":1866},[1864,3422,1870],{"align":1866},[1864,3424,1873],{"align":1866},[1875,3426,3427,3435,3443,3451,3459,3467,3475,3483,3491,3499,3507],{},[1861,3428,3429,3431,3433],{},[1880,3430,1882],{"align":1866},[1880,3432,1885],{"align":1866},[1880,3434,1888],{"align":1866},[1861,3436,3437,3439,3441],{},[1880,3438,1893],{"align":1866},[1880,3440,1896],{"align":1866},[1880,3442,1899],{"align":1866},[1861,3444,3445,3447,3449],{},[1880,3446,1904],{"align":1866},[1880,3448,1907],{"align":1866},[1880,3450,1910],{"align":1866},[1861,3452,3453,3455,3457],{},[1880,3454,1915],{"align":1866},[1880,3456,1918],{"align":1866},[1880,3458,1921],{"align":1866},[1861,3460,3461,3463,3465],{},[1880,3462,1926],{"align":1866},[1880,3464,1929],{"align":1866},[1880,3466,1932],{"align":1866},[1861,3468,3469,3471,3473],{},[1880,3470,1937],{"align":1866},[1880,3472,1940],{"align":1866},[1880,3474,1943],{"align":1866},[1861,3476,3477,3479,3481],{},[1880,3478,1948],{"align":1866},[1880,3480,1951],{"align":1866},[1880,3482,1954],{"align":1866},[1861,3484,3485,3487,3489],{},[1880,3486,1959],{"align":1866},[1880,3488,1962],{"align":1866},[1880,3490,1965],{"align":1866},[1861,3492,3493,3495,3497],{},[1880,3494,1970],{"align":1866},[1880,3496,1973],{"align":1866},[1880,3498,1976],{"align":1866},[1861,3500,3501,3503,3505],{},[1880,3502,1981],{"align":1866},[1880,3504,1984],{"align":1866},[1880,3506,1987],{"align":1866},[1861,3508,3509,3511,3513],{},[1880,3510,1992],{"align":1866},[1880,3512,1995],{"align":1866},[1880,3514,1998],{"align":1866},[102,3516,2001],{},[98,3518,2005],{"id":2004},[102,3520,2008],{},[124,3522,3523,3527,3531,3535],{},[127,3524,3525,2016],{},[130,3526,2015],{},[127,3528,3529,2022],{},[130,3530,2021],{},[127,3532,3533,2028],{},[130,3534,2027],{},[127,3536,3537,2034],{},[130,3538,2033],{},[102,3540,2037],{},[98,3542,2041],{"id":2040},[102,3544,2044],{},[102,3546,2047],{},[102,3548,2050],{},[2052,3550,2054],{},{"title":175,"searchDepth":212,"depth":212,"links":3552},[3553,3554,3555,3556,3557,3558,3559,3560,3561,3562,3563],{"id":100,"depth":212,"text":90},{"id":110,"depth":212,"text":111},{"id":163,"depth":212,"text":164},{"id":187,"depth":212,"text":188},{"id":688,"depth":212,"text":689},{"id":723,"depth":212,"text":724},{"id":880,"depth":212,"text":881},{"id":1559,"depth":212,"text":1560},{"id":1849,"depth":212,"text":1850},{"id":2004,"depth":212,"text":2005},{"id":2040,"depth":212,"text":2041},{"readingTime":2073},{"title":90,"description":2069},{"src":2079,"mime":2080,"alt":2081,"width":2082,"height":2083},[1870,2086,2087,2088,2089,2090],[3569,5233,5918],{"id":3570,"title":3571,"abstract":3572,"author":92,"authorUrl":93,"body":3573,"date":5215,"dateUpdated":5215,"description":5216,"excerpt":2070,"extension":2071,"featured":215,"headline":3571,"image":2070,"meta":5217,"navigation":215,"ogImage":2070,"path":5219,"seo":5220,"series":5221,"seriesDescription":5222,"seriesOrder":229,"socialImage":5223,"stem":5226,"tags":5227,"__hash__":5232},"blog\u002Fblog\u002F41-multi-agent-memory-systems-production.md","Multi-Agent Memory Systems: What Actually Works in Production","Multi-agent memory is the hardest engineering challenge in production AI systems. Short-term context, persistent memory, vector retrieval — and how to avoid context drift and hallucinated memory.",{"type":95,"value":3574,"toc":5183},[3575,3578,3584,3587,3590,3593,3596,3600,3603,3608,3611,3614,3618,3621,3624,3628,3631,3634,3638,3641,3645,3648,3651,3654,3658,3661,3664,4768,4771,4775,4778,4781,4784,4788,4791,4795,4798,4801,4804,4808,4811,4814,4817,4821,4824,4827,4830,4834,4837,4840,4843,4847,4850,4854,4857,4860,4863,4867,4870,4873,4876,4880,4883,4886,4889,4893,4896,5027,5030,5034,5037,5127,5130,5134,5137,5141,5144,5147,5151,5154,5157,5161,5164,5167,5171,5174,5177,5180],[98,3576,3571],{"id":3577},"multi-agent-memory-systems-what-actually-works-in-production",[102,3579,3580,3583],{},[130,3581,3582],{},"Last updated:"," May 2026",[102,3585,3586],{},"The fifth turn of the conversation was where it broke.",[102,3588,3589],{},"I was running a multi-agent pipeline where a coordinator agent decomposed a feature request, dispatched tasks to three specialist agents, and collected their outputs for synthesis. On iterations one through four, the system performed flawlessly. By iteration five, the specialist agents started referencing data that no longer existed in the shared context. The coordinator began repeating instructions that had already been executed. One agent proposed a solution identical to one it had produced two turns earlier, apparently unaware it had already completed that work.",[102,3591,3592],{},"What broke was not the reasoning model. What broke was memory.",[102,3594,3595],{},"Memory is the hardest engineering problem in production AI systems. Reasoning capabilities have improved dramatically across every major model provider, but the mechanisms for maintaining, retrieving, and managing state across multiple agents remain primitive by comparison. After building and operating multi-agent systems using the Gem-Team architecture for the past year, I have developed a set of patterns that survive production pressure. This post covers what those patterns are, why they work, and where they fall short.",[98,3597,3599],{"id":3598},"the-memory-hierarchy-problem","The Memory Hierarchy Problem",[102,3601,3602],{},"Every multi-agent system confronts the same fundamental question: what should an agent remember, when should it remember it, and for how long? The answer determines whether the system operates coherently or descends into contradiction and repetition.",[3604,3605,3607],"h3",{"id":3606},"what-to-remember","What to Remember",[102,3609,3610],{},"Not everything an agent encounters deserves preservation. The chat history of a code-generation agent contains dozens of intermediate reasoning steps, failed hypotheses, and irrelevant tool outputs. Preserving all of it bloats the context window and degrades response quality. Preserving too little forces the agent to re-derive context that it should carry forward.",[102,3612,3613],{},"The correct approach is to define a memory schema for each agent role. A code-generation agent remembers the file it is editing, the current diff, and the linting errors it encountered. It does not remember the five failed regex attempts it made before arriving at the correct pattern. A testing agent remembers the test suite structure and the last execution result. It does not remember the conversation history with the developer who requested the tests.",[3604,3615,3617],{"id":3616},"when-to-remember","When to Remember",[102,3619,3620],{},"Memory operations carry cost. Writing to a persistent store adds latency to every agent turn. Reading from a vector store adds retrieval time. The timing of these operations determines whether the system feels responsive or sluggish.",[102,3622,3623],{},"In the Gem-Team architecture, memory writes occur at natural boundaries: when an agent completes a subtask, when a checkpoint is explicitly requested, or when the system detects a state transition. Memory reads occur at the start of a new agent invocation and on explicit retrieval requests. This cadence prevents the system from performing useless I\u002FO on every micro-step while ensuring that critical state is preserved.",[3604,3625,3627],{"id":3626},"for-how-long","For How Long",[102,3629,3630],{},"Retention policies must be explicit. Short-term context (the current conversation window) persists for the duration of a single agent session. Persistent structured state (checkpoints, file diffs, execution results) persists across sessions but has a defined time-to-live. Vector-embedded knowledge persists indefinitely but requires active curation to prevent stale references.",[102,3632,3633],{},"The mistake I see most frequently is treating all memory as permanent. A decision made in one session becomes stale when the codebase changes, but the vector store still returns it as relevant. An agent retrieves an outdated architectural decision and builds on top of it, compounding the error across subsequent turns.",[98,3635,3637],{"id":3636},"three-memory-types","Three Memory Types",[102,3639,3640],{},"Every production multi-agent system needs three distinct memory mechanisms. Each serves a different purpose and carries different tradeoffs.",[3604,3642,3644],{"id":3643},"short-term-context-window","Short-Term Context Window",[102,3646,3647],{},"The context window is the agent's working memory. It contains the current task, recent conversation history, and relevant tool outputs. This is the fastest memory to access and the most expensive to scale.",[102,3649,3650],{},"In practice, the context window works well for tasks that complete within a bounded number of turns. A code review agent that examines a single file and produces comments can operate entirely within its context window. An agent that must coordinate across five files, three API calls, and two database migrations will overflow.",[102,3652,3653],{},"The solution is to treat the context window as a cache, not a database. Keep the most recent and most relevant information there. Archive completed work to persistent storage. Prune redundant or superseded content aggressively.",[3604,3655,3657],{"id":3656},"persistent-structured-state","Persistent Structured State",[102,3659,3660],{},"Persistent state is where the system stores completed work, accumulated results, and agent decisions that must survive beyond the current session. This is the hardest memory type to get right because it requires a schema.",[102,3662,3663],{},"The Gem-Team approach uses typed checkpoints. Each agent writes its state to a structured checkpoint that includes a schema version, a timestamp, the agent role, and the actual state payload. Downstream agents can query checkpoint history to understand what has already been done and what decisions were made.",[169,3665,3669],{"className":3666,"code":3667,"language":3668,"meta":175,"style":175},"language-typescript shiki shiki-themes material-theme-lighter github-light github-dark monokai","\u002F\u002F Structured checkpoint schema for agent state persistence\n\u002F\u002F Used by Gem-Team agents to write and read typed checkpoints\n\ninterface CheckpointMetadata {\n  schemaVersion: number\n  agentId: string\n  role: string\n  sessionId: string\n  parentSessionId: string | null\n  timestamp: string\n  turnNumber: number\n}\n\ninterface CodeGenerationState {\n  metadata: CheckpointMetadata\n  payload: {\n    targetFile: string\n    originalContent: string\n    currentDiff: string\n    lintsPassed: string[]\n    lintsFailed: string[]\n    dependentFiles: string[]\n    completedTasks: string[]\n    pendingTasks: string[]\n    decisions: Array\u003C{\n      id: string\n      description: string\n      rationale: string\n      alternativesConsidered: string[]\n      timestamp: string\n    }>\n  }\n}\n\nclass StructuredStateManager {\n  private store: Map\u003Cstring, CodeGenerationState>\n  private retentionMs: number\n\n  constructor(retentionMinutes: number = 1440) {\n    this.store = new Map()\n    this.retentionMs = retentionMinutes * 60 * 1000\n  }\n\n  async writeCheckpoint(state: CodeGenerationState): Promise\u003Cvoid> {\n    this.store.set(state.metadata.sessionId, state)\n    \u002F\u002F Archive to persistent store here\n    await this.archive(state)\n  }\n\n  async readCheckpoint(sessionId: string): Promise\u003CCodeGenerationState | null> {\n    const state = this.store.get(sessionId)\n    if (!state) return null\n    if (this.isExpired(state)) {\n      this.store.delete(sessionId)\n      return null\n    }\n    return state\n  }\n\n  async getAgentHistory(agentId: string): Promise\u003CCodeGenerationState[]> {\n    const results: CodeGenerationState[] = []\n    for (const state of this.store.values()) {\n      if (state.metadata.agentId === agentId && !this.isExpired(state)) {\n        results.push(state)\n      }\n    }\n    return results.sort(\n      (a, b) =>\n        new Date(a.metadata.timestamp).getTime() -\n        new Date(b.metadata.timestamp).getTime(),\n    )\n  }\n\n  private isExpired(state: CodeGenerationState): boolean {\n    const age = Date.now() - new Date(state.metadata.timestamp).getTime()\n    return age > this.retentionMs\n  }\n\n  private async archive(state: CodeGenerationState): Promise\u003Cvoid> {\n    \u002F\u002F Write to durable storage (PostgreSQL, S3, etc.)\n  }\n}\n","typescript",[177,3670,3671,3677,3682,3686,3700,3713,3723,3732,3741,3757,3766,3775,3779,3783,3792,3802,3811,3820,3829,3838,3851,3862,3873,3884,3895,3908,3917,3926,3935,3946,3955,3960,3965,3969,3973,3983,4011,4022,4026,4055,4079,4104,4108,4112,4147,4182,4187,4207,4211,4215,4250,4277,4298,4321,4341,4348,4352,4360,4364,4368,4401,4421,4452,4495,4511,4516,4520,4534,4552,4586,4617,4622,4626,4630,4654,4699,4715,4719,4723,4755,4760,4764],{"__ignoreMap":175},[203,3672,3673],{"class":205,"line":206},[203,3674,3676],{"class":3675},"ss7Ak","\u002F\u002F Structured checkpoint schema for agent state persistence\n",[203,3678,3679],{"class":205,"line":212},[203,3680,3681],{"class":3675},"\u002F\u002F Used by Gem-Team agents to write and read typed checkpoints\n",[203,3683,3684],{"class":205,"line":14},[203,3685,216],{"emptyLinePlaceholder":215},[203,3687,3688,3692,3696],{"class":205,"line":224},[203,3689,3691],{"class":3690},"srJo8","interface",[203,3693,3695],{"class":3694},"sKvfc"," CheckpointMetadata",[203,3697,3699],{"class":3698},"swvn1"," {\n",[203,3701,3702,3706,3709],{"class":205,"line":229},[203,3703,3705],{"class":3704},"sIDdj","  schemaVersion",[203,3707,1625],{"class":3708},"sGXK2",[203,3710,3712],{"class":3711},"s_MOj"," number\n",[203,3714,3715,3718,3720],{"class":205,"line":235},[203,3716,3717],{"class":3704},"  agentId",[203,3719,1625],{"class":3708},[203,3721,3722],{"class":3711}," string\n",[203,3724,3725,3728,3730],{"class":205,"line":241},[203,3726,3727],{"class":3704},"  role",[203,3729,1625],{"class":3708},[203,3731,3722],{"class":3711},[203,3733,3734,3737,3739],{"class":205,"line":247},[203,3735,3736],{"class":3704},"  sessionId",[203,3738,1625],{"class":3708},[203,3740,3722],{"class":3711},[203,3742,3743,3746,3748,3751,3754],{"class":205,"line":253},[203,3744,3745],{"class":3704},"  parentSessionId",[203,3747,1625],{"class":3708},[203,3749,3750],{"class":3711}," string",[203,3752,3753],{"class":3708}," |",[203,3755,3756],{"class":3711}," null\n",[203,3758,3759,3762,3764],{"class":205,"line":259},[203,3760,3761],{"class":3704},"  timestamp",[203,3763,1625],{"class":3708},[203,3765,3722],{"class":3711},[203,3767,3768,3771,3773],{"class":205,"line":265},[203,3769,3770],{"class":3704},"  turnNumber",[203,3772,1625],{"class":3708},[203,3774,3712],{"class":3711},[203,3776,3777],{"class":205,"line":271},[203,3778,656],{"class":3698},[203,3780,3781],{"class":205,"line":27},[203,3782,216],{"emptyLinePlaceholder":215},[203,3784,3785,3787,3790],{"class":205,"line":282},[203,3786,3691],{"class":3690},[203,3788,3789],{"class":3694}," CodeGenerationState",[203,3791,3699],{"class":3698},[203,3793,3794,3797,3799],{"class":205,"line":287},[203,3795,3796],{"class":3704},"  metadata",[203,3798,1625],{"class":3708},[203,3800,3801],{"class":3694}," CheckpointMetadata\n",[203,3803,3804,3807,3809],{"class":205,"line":293},[203,3805,3806],{"class":3704},"  payload",[203,3808,1625],{"class":3708},[203,3810,3699],{"class":3698},[203,3812,3813,3816,3818],{"class":205,"line":299},[203,3814,3815],{"class":3704},"    targetFile",[203,3817,1625],{"class":3708},[203,3819,3722],{"class":3711},[203,3821,3822,3825,3827],{"class":205,"line":305},[203,3823,3824],{"class":3704},"    originalContent",[203,3826,1625],{"class":3708},[203,3828,3722],{"class":3711},[203,3830,3831,3834,3836],{"class":205,"line":310},[203,3832,3833],{"class":3704},"    currentDiff",[203,3835,1625],{"class":3708},[203,3837,3722],{"class":3711},[203,3839,3840,3843,3845,3847],{"class":205,"line":316},[203,3841,3842],{"class":3704},"    lintsPassed",[203,3844,1625],{"class":3708},[203,3846,3750],{"class":3711},[203,3848,3850],{"class":3849},"ss--_","[]\n",[203,3852,3853,3856,3858,3860],{"class":205,"line":322},[203,3854,3855],{"class":3704},"    lintsFailed",[203,3857,1625],{"class":3708},[203,3859,3750],{"class":3711},[203,3861,3850],{"class":3849},[203,3863,3864,3867,3869,3871],{"class":205,"line":328},[203,3865,3866],{"class":3704},"    dependentFiles",[203,3868,1625],{"class":3708},[203,3870,3750],{"class":3711},[203,3872,3850],{"class":3849},[203,3874,3875,3878,3880,3882],{"class":205,"line":333},[203,3876,3877],{"class":3704},"    completedTasks",[203,3879,1625],{"class":3708},[203,3881,3750],{"class":3711},[203,3883,3850],{"class":3849},[203,3885,3886,3889,3891,3893],{"class":205,"line":339},[203,3887,3888],{"class":3704},"    pendingTasks",[203,3890,1625],{"class":3708},[203,3892,3750],{"class":3711},[203,3894,3850],{"class":3849},[203,3896,3897,3900,3902,3905],{"class":205,"line":345},[203,3898,3899],{"class":3704},"    decisions",[203,3901,1625],{"class":3708},[203,3903,3904],{"class":3694}," Array",[203,3906,3907],{"class":3698},"\u003C{\n",[203,3909,3910,3913,3915],{"class":205,"line":351},[203,3911,3912],{"class":3704},"      id",[203,3914,1625],{"class":3708},[203,3916,3722],{"class":3711},[203,3918,3919,3922,3924],{"class":205,"line":356},[203,3920,3921],{"class":3704},"      description",[203,3923,1625],{"class":3708},[203,3925,3722],{"class":3711},[203,3927,3928,3931,3933],{"class":205,"line":362},[203,3929,3930],{"class":3704},"      rationale",[203,3932,1625],{"class":3708},[203,3934,3722],{"class":3711},[203,3936,3937,3940,3942,3944],{"class":205,"line":368},[203,3938,3939],{"class":3704},"      alternativesConsidered",[203,3941,1625],{"class":3708},[203,3943,3750],{"class":3711},[203,3945,3850],{"class":3849},[203,3947,3948,3951,3953],{"class":205,"line":374},[203,3949,3950],{"class":3704},"      timestamp",[203,3952,1625],{"class":3708},[203,3954,3722],{"class":3711},[203,3956,3957],{"class":205,"line":379},[203,3958,3959],{"class":3698},"    }>\n",[203,3961,3962],{"class":205,"line":385},[203,3963,3964],{"class":3698},"  }\n",[203,3966,3967],{"class":205,"line":391},[203,3968,656],{"class":3698},[203,3970,3971],{"class":205,"line":397},[203,3972,216],{"emptyLinePlaceholder":215},[203,3974,3975,3978,3981],{"class":205,"line":403},[203,3976,3977],{"class":3690},"class",[203,3979,3980],{"class":3694}," StructuredStateManager",[203,3982,3699],{"class":3698},[203,3984,3985,3989,3992,3994,3997,4000,4003,4006,4008],{"class":205,"line":409},[203,3986,3988],{"class":3987},"sTNss","  private",[203,3990,3991],{"class":3704}," store",[203,3993,1625],{"class":3708},[203,3995,3996],{"class":3694}," Map",[203,3998,3999],{"class":3698},"\u003C",[203,4001,4002],{"class":3711},"string",[203,4004,4005],{"class":3698},",",[203,4007,3789],{"class":3694},[203,4009,4010],{"class":3698},">\n",[203,4012,4013,4015,4018,4020],{"class":205,"line":415},[203,4014,3988],{"class":3987},[203,4016,4017],{"class":3704}," retentionMs",[203,4019,1625],{"class":3708},[203,4021,3712],{"class":3711},[203,4023,4024],{"class":205,"line":421},[203,4025,216],{"emptyLinePlaceholder":215},[203,4027,4028,4031,4034,4038,4040,4043,4046,4050,4053],{"class":205,"line":427},[203,4029,4030],{"class":3690},"  constructor",[203,4032,4033],{"class":3698},"(",[203,4035,4037],{"class":4036},"sQgqH","retentionMinutes",[203,4039,1625],{"class":3708},[203,4041,4042],{"class":3711}," number",[203,4044,4045],{"class":3708}," =",[203,4047,4049],{"class":4048},"sYThS"," 1440",[203,4051,4052],{"class":3698},")",[203,4054,3699],{"class":3698},[203,4056,4057,4061,4064,4067,4069,4072,4075],{"class":205,"line":433},[203,4058,4060],{"class":4059},"sSBr1","    this",[203,4062,4063],{"class":3698},".",[203,4065,4066],{"class":3849},"store",[203,4068,4045],{"class":3708},[203,4070,4071],{"class":3708}," new",[203,4073,3996],{"class":4074},"sD0ED",[203,4076,4078],{"class":4077},"squCx","()\n",[203,4080,4081,4083,4085,4088,4090,4093,4096,4099,4101],{"class":205,"line":438},[203,4082,4060],{"class":4059},[203,4084,4063],{"class":3698},[203,4086,4087],{"class":3849},"retentionMs",[203,4089,4045],{"class":3708},[203,4091,4092],{"class":3849}," retentionMinutes",[203,4094,4095],{"class":3708}," *",[203,4097,4098],{"class":4048}," 60",[203,4100,4095],{"class":3708},[203,4102,4103],{"class":4048}," 1000\n",[203,4105,4106],{"class":205,"line":444},[203,4107,3964],{"class":3698},[203,4109,4110],{"class":205,"line":450},[203,4111,216],{"emptyLinePlaceholder":215},[203,4113,4114,4117,4121,4123,4126,4128,4130,4132,4134,4137,4139,4142,4145],{"class":205,"line":456},[203,4115,4116],{"class":3987},"  async",[203,4118,4120],{"class":4119},"sY_X6"," writeCheckpoint",[203,4122,4033],{"class":3698},[203,4124,4125],{"class":4036},"state",[203,4127,1625],{"class":3708},[203,4129,3789],{"class":3694},[203,4131,4052],{"class":3698},[203,4133,1625],{"class":3708},[203,4135,4136],{"class":3694}," Promise",[203,4138,3999],{"class":3698},[203,4140,4141],{"class":3711},"void",[203,4143,4144],{"class":3698},">",[203,4146,3699],{"class":3698},[203,4148,4149,4151,4153,4155,4157,4160,4162,4164,4166,4169,4171,4174,4176,4179],{"class":205,"line":461},[203,4150,4060],{"class":4059},[203,4152,4063],{"class":3698},[203,4154,4066],{"class":3849},[203,4156,4063],{"class":3698},[203,4158,4159],{"class":4074},"set",[203,4161,4033],{"class":4077},[203,4163,4125],{"class":3849},[203,4165,4063],{"class":3698},[203,4167,4168],{"class":3849},"metadata",[203,4170,4063],{"class":3698},[203,4172,4173],{"class":3849},"sessionId",[203,4175,4005],{"class":3698},[203,4177,4178],{"class":3849}," state",[203,4180,4181],{"class":4077},")\n",[203,4183,4184],{"class":205,"line":467},[203,4185,4186],{"class":3675},"    \u002F\u002F Archive to persistent store here\n",[203,4188,4189,4193,4196,4198,4201,4203,4205],{"class":205,"line":473},[203,4190,4192],{"class":4191},"sRxSC","    await",[203,4194,4195],{"class":4059}," this",[203,4197,4063],{"class":3698},[203,4199,4200],{"class":4074},"archive",[203,4202,4033],{"class":4077},[203,4204,4125],{"class":3849},[203,4206,4181],{"class":4077},[203,4208,4209],{"class":205,"line":478},[203,4210,3964],{"class":3698},[203,4212,4213],{"class":205,"line":484},[203,4214,216],{"emptyLinePlaceholder":215},[203,4216,4217,4219,4222,4224,4226,4228,4230,4232,4234,4236,4238,4241,4243,4246,4248],{"class":205,"line":489},[203,4218,4116],{"class":3987},[203,4220,4221],{"class":4119}," readCheckpoint",[203,4223,4033],{"class":3698},[203,4225,4173],{"class":4036},[203,4227,1625],{"class":3708},[203,4229,3750],{"class":3711},[203,4231,4052],{"class":3698},[203,4233,1625],{"class":3708},[203,4235,4136],{"class":3694},[203,4237,3999],{"class":3698},[203,4239,4240],{"class":3694},"CodeGenerationState",[203,4242,3753],{"class":3708},[203,4244,4245],{"class":3711}," null",[203,4247,4144],{"class":3698},[203,4249,3699],{"class":3698},[203,4251,4252,4255,4258,4260,4262,4264,4266,4268,4271,4273,4275],{"class":205,"line":494},[203,4253,4254],{"class":3690},"    const",[203,4256,4178],{"class":4257},"s91G_",[203,4259,4045],{"class":3708},[203,4261,4195],{"class":4059},[203,4263,4063],{"class":3698},[203,4265,4066],{"class":3849},[203,4267,4063],{"class":3698},[203,4269,4270],{"class":4074},"get",[203,4272,4033],{"class":4077},[203,4274,4173],{"class":3849},[203,4276,4181],{"class":4077},[203,4278,4279,4282,4284,4287,4289,4292,4295],{"class":205,"line":500},[203,4280,4281],{"class":4191},"    if",[203,4283,1575],{"class":4077},[203,4285,4286],{"class":3708},"!",[203,4288,4125],{"class":3849},[203,4290,4291],{"class":4077},") ",[203,4293,4294],{"class":4191},"return",[203,4296,3756],{"class":4297},"sMTiH",[203,4299,4300,4302,4304,4307,4309,4312,4314,4316,4319],{"class":205,"line":506},[203,4301,4281],{"class":4191},[203,4303,1575],{"class":4077},[203,4305,4306],{"class":4059},"this",[203,4308,4063],{"class":3698},[203,4310,4311],{"class":4074},"isExpired",[203,4313,4033],{"class":4077},[203,4315,4125],{"class":3849},[203,4317,4318],{"class":4077},")) ",[203,4320,296],{"class":3698},[203,4322,4323,4326,4328,4330,4332,4335,4337,4339],{"class":205,"line":512},[203,4324,4325],{"class":4059},"      this",[203,4327,4063],{"class":3698},[203,4329,4066],{"class":3849},[203,4331,4063],{"class":3698},[203,4333,4334],{"class":4074},"delete",[203,4336,4033],{"class":4077},[203,4338,4173],{"class":3849},[203,4340,4181],{"class":4077},[203,4342,4343,4346],{"class":205,"line":518},[203,4344,4345],{"class":4191},"      return",[203,4347,3756],{"class":4297},[203,4349,4350],{"class":205,"line":524},[203,4351,650],{"class":3698},[203,4353,4354,4357],{"class":205,"line":530},[203,4355,4356],{"class":4191},"    return",[203,4358,4359],{"class":3849}," state\n",[203,4361,4362],{"class":205,"line":535},[203,4363,3964],{"class":3698},[203,4365,4366],{"class":205,"line":541},[203,4367,216],{"emptyLinePlaceholder":215},[203,4369,4370,4372,4375,4377,4380,4382,4384,4386,4388,4390,4392,4394,4397,4399],{"class":205,"line":547},[203,4371,4116],{"class":3987},[203,4373,4374],{"class":4119}," getAgentHistory",[203,4376,4033],{"class":3698},[203,4378,4379],{"class":4036},"agentId",[203,4381,1625],{"class":3708},[203,4383,3750],{"class":3711},[203,4385,4052],{"class":3698},[203,4387,1625],{"class":3708},[203,4389,4136],{"class":3694},[203,4391,3999],{"class":3698},[203,4393,4240],{"class":3694},[203,4395,4396],{"class":3849},"[]",[203,4398,4144],{"class":3698},[203,4400,3699],{"class":3698},[203,4402,4403,4405,4408,4410,4412,4415,4418],{"class":205,"line":553},[203,4404,4254],{"class":3690},[203,4406,4407],{"class":4257}," results",[203,4409,1625],{"class":3708},[203,4411,3789],{"class":3694},[203,4413,4414],{"class":4077},"[] ",[203,4416,4417],{"class":3708},"=",[203,4419,4420],{"class":4077}," []\n",[203,4422,4423,4426,4428,4431,4433,4436,4438,4440,4442,4444,4447,4450],{"class":205,"line":558},[203,4424,4425],{"class":4191},"    for",[203,4427,1575],{"class":4077},[203,4429,4430],{"class":3690},"const",[203,4432,4178],{"class":4257},[203,4434,4435],{"class":3708}," of",[203,4437,4195],{"class":4059},[203,4439,4063],{"class":3698},[203,4441,4066],{"class":3849},[203,4443,4063],{"class":3698},[203,4445,4446],{"class":4074},"values",[203,4448,4449],{"class":4077},"()) ",[203,4451,296],{"class":3698},[203,4453,4454,4457,4459,4461,4463,4465,4467,4469,4472,4475,4478,4481,4483,4485,4487,4489,4491,4493],{"class":205,"line":564},[203,4455,4456],{"class":4191},"      if",[203,4458,1575],{"class":4077},[203,4460,4125],{"class":3849},[203,4462,4063],{"class":3698},[203,4464,4168],{"class":3849},[203,4466,4063],{"class":3698},[203,4468,4379],{"class":3849},[203,4470,4471],{"class":3708}," ===",[203,4473,4474],{"class":3849}," agentId",[203,4476,4477],{"class":3708}," &&",[203,4479,4480],{"class":3708}," !",[203,4482,4306],{"class":4059},[203,4484,4063],{"class":3698},[203,4486,4311],{"class":4074},[203,4488,4033],{"class":4077},[203,4490,4125],{"class":3849},[203,4492,4318],{"class":4077},[203,4494,296],{"class":3698},[203,4496,4497,4500,4502,4505,4507,4509],{"class":205,"line":569},[203,4498,4499],{"class":3849},"        results",[203,4501,4063],{"class":3698},[203,4503,4504],{"class":4074},"push",[203,4506,4033],{"class":4077},[203,4508,4125],{"class":3849},[203,4510,4181],{"class":4077},[203,4512,4513],{"class":205,"line":575},[203,4514,4515],{"class":3698},"      }\n",[203,4517,4518],{"class":205,"line":581},[203,4519,650],{"class":3698},[203,4521,4522,4524,4526,4528,4531],{"class":205,"line":587},[203,4523,4356],{"class":4191},[203,4525,4407],{"class":3849},[203,4527,4063],{"class":3698},[203,4529,4530],{"class":4074},"sort",[203,4532,4533],{"class":4077},"(\n",[203,4535,4536,4539,4542,4544,4547,4549],{"class":205,"line":593},[203,4537,4538],{"class":3698},"      (",[203,4540,4541],{"class":4036},"a",[203,4543,4005],{"class":3698},[203,4545,4546],{"class":4036}," b",[203,4548,4052],{"class":3698},[203,4550,4551],{"class":3690}," =>\n",[203,4553,4554,4557,4560,4562,4564,4566,4568,4570,4573,4575,4577,4580,4583],{"class":205,"line":598},[203,4555,4556],{"class":3708},"        new",[203,4558,4559],{"class":4074}," Date",[203,4561,4033],{"class":4077},[203,4563,4541],{"class":3849},[203,4565,4063],{"class":3698},[203,4567,4168],{"class":3849},[203,4569,4063],{"class":3698},[203,4571,4572],{"class":3849},"timestamp",[203,4574,4052],{"class":4077},[203,4576,4063],{"class":3698},[203,4578,4579],{"class":4074},"getTime",[203,4581,4582],{"class":4077},"() ",[203,4584,4585],{"class":3708},"-\n",[203,4587,4588,4590,4592,4594,4597,4599,4601,4603,4605,4607,4609,4611,4614],{"class":205,"line":603},[203,4589,4556],{"class":3708},[203,4591,4559],{"class":4074},[203,4593,4033],{"class":4077},[203,4595,4596],{"class":3849},"b",[203,4598,4063],{"class":3698},[203,4600,4168],{"class":3849},[203,4602,4063],{"class":3698},[203,4604,4572],{"class":3849},[203,4606,4052],{"class":4077},[203,4608,4063],{"class":3698},[203,4610,4579],{"class":4074},[203,4612,4613],{"class":4077},"()",[203,4615,4616],{"class":3698},",\n",[203,4618,4619],{"class":205,"line":608},[203,4620,4621],{"class":4077},"    )\n",[203,4623,4624],{"class":205,"line":614},[203,4625,3964],{"class":3698},[203,4627,4628],{"class":205,"line":619},[203,4629,216],{"emptyLinePlaceholder":215},[203,4631,4632,4634,4637,4639,4641,4643,4645,4647,4649,4652],{"class":205,"line":625},[203,4633,3988],{"class":3987},[203,4635,4636],{"class":4119}," isExpired",[203,4638,4033],{"class":3698},[203,4640,4125],{"class":4036},[203,4642,1625],{"class":3708},[203,4644,3789],{"class":3694},[203,4646,4052],{"class":3698},[203,4648,1625],{"class":3708},[203,4650,4651],{"class":3711}," boolean",[203,4653,3699],{"class":3698},[203,4655,4656,4658,4661,4663,4665,4667,4670,4672,4675,4677,4679,4681,4683,4685,4687,4689,4691,4693,4695,4697],{"class":205,"line":630},[203,4657,4254],{"class":3690},[203,4659,4660],{"class":4257}," age",[203,4662,4045],{"class":3708},[203,4664,4559],{"class":3849},[203,4666,4063],{"class":3698},[203,4668,4669],{"class":4074},"now",[203,4671,4582],{"class":4077},[203,4673,4674],{"class":3708},"-",[203,4676,4071],{"class":3708},[203,4678,4559],{"class":4074},[203,4680,4033],{"class":4077},[203,4682,4125],{"class":3849},[203,4684,4063],{"class":3698},[203,4686,4168],{"class":3849},[203,4688,4063],{"class":3698},[203,4690,4572],{"class":3849},[203,4692,4052],{"class":4077},[203,4694,4063],{"class":3698},[203,4696,4579],{"class":4074},[203,4698,4078],{"class":4077},[203,4700,4701,4703,4705,4708,4710,4712],{"class":205,"line":635},[203,4702,4356],{"class":4191},[203,4704,4660],{"class":3849},[203,4706,4707],{"class":3708}," >",[203,4709,4195],{"class":4059},[203,4711,4063],{"class":3698},[203,4713,4714],{"class":3849},"retentionMs\n",[203,4716,4717],{"class":205,"line":641},[203,4718,3964],{"class":3698},[203,4720,4721],{"class":205,"line":647},[203,4722,216],{"emptyLinePlaceholder":215},[203,4724,4725,4727,4730,4733,4735,4737,4739,4741,4743,4745,4747,4749,4751,4753],{"class":205,"line":653},[203,4726,3988],{"class":3987},[203,4728,4729],{"class":3987}," async",[203,4731,4732],{"class":4119}," archive",[203,4734,4033],{"class":3698},[203,4736,4125],{"class":4036},[203,4738,1625],{"class":3708},[203,4740,3789],{"class":3694},[203,4742,4052],{"class":3698},[203,4744,1625],{"class":3708},[203,4746,4136],{"class":3694},[203,4748,3999],{"class":3698},[203,4750,4141],{"class":3711},[203,4752,4144],{"class":3698},[203,4754,3699],{"class":3698},[203,4756,4757],{"class":205,"line":1262},[203,4758,4759],{"class":3675},"    \u002F\u002F Write to durable storage (PostgreSQL, S3, etc.)\n",[203,4761,4762],{"class":205,"line":1267},[203,4763,3964],{"class":3698},[203,4765,4766],{"class":205,"line":1272},[203,4767,656],{"class":3698},[102,4769,4770],{},"The key insight is that typed schemas prevent hallucinated memory. When an agent reads a checkpoint, it receives exactly the fields defined in the schema, no more and no less. The agent cannot fabricate a file diff because the diff field either exists or it does not. This constraint is the single most effective defense against memory corruption.",[3604,4772,4774],{"id":4773},"vector-based-retrieval","Vector-Based Retrieval",[102,4776,4777],{},"Vector memory serves a different purpose: recalling relevant context from past sessions that is not directly related to the current task. An agent building a new authentication system might benefit from reviewing how a previous agent handled token expiration, even though the two tasks are not in the same session.",[102,4779,4780],{},"Vector retrieval works well for similarity search across unstructured or semi-structured content. The challenge is ensuring that retrieved content is relevant and current. A vector store that returns six-month-old architectural decisions alongside fresh code changes creates confusion.",[102,4782,4783],{},"The pattern that works in production is to segment the vector store by time range and agent role. A retrieval query specifies not only the similarity threshold but also the acceptable age range and the originating agent role. This filtering dramatically reduces irrelevant results.",[98,4785,4787],{"id":4786},"common-failure-modes","Common Failure Modes",[102,4789,4790],{},"Building multi-agent memory systems means encountering failure modes that do not exist in single-agent architectures. Here are the four that caused the most damage in my production systems.",[3604,4792,4794],{"id":4793},"context-drift","Context Drift",[102,4796,4797],{},"Context drift occurs when an agent's understanding of the current state diverges from reality. The agent believes it is editing version 3 of a file when the file is actually on version 7. The agent references a decision that was reversed two turns ago.",[102,4799,4800],{},"Context drift is insidious because it accumulates gradually. Each turn introduces a small imprecision. By turn ten, the agent operates on a fundamentally incorrect model of the world. The symptoms are baffling: the agent produces code that looks reasonable but references functions that no longer exist or assumes data structures that were refactored earlier.",[102,4802,4803],{},"The fix is to inject ground truth at every agent invocation. Before an agent starts work, provide it with the current state of every resource it might touch. File contents, environment variables, database schemas — whatever the agent needs, inject it as structured data before the agent generates its first token. This adds latency to each invocation but eliminates accumulated drift.",[3604,4805,4807],{"id":4806},"hallucinated-memory","Hallucinated Memory",[102,4809,4810],{},"Hallucinated memory is different from hallucinated output. The agent does not generate false facts about the world — it generates false facts about what it previously did. It claims to have written a function that it never wrote. It states that it tested a scenario that it never evaluated.",[102,4812,4813],{},"This happens because language models are not databases. When asked \"What did you do in the previous turn?\", the model does not query a log — it generates a plausible completion based on the conversation history. If the history contains gaps or ambiguities, the model fills them with fabricated content.",[102,4815,4816],{},"The defense against hallucinated memory is to never ask the agent to recall its own history. Instead, provide explicit memory from the checkpoint store. When the agent needs to know what it did, inject the relevant checkpoint data into its context window. The agent reads facts rather than generating them.",[3604,4818,4820],{"id":4819},"memory-bloat","Memory Bloat",[102,4822,4823],{},"Memory bloat is the accumulation of irrelevant or redundant state. Every turn adds content to the context window. Every checkpoint writes data to the persistent store. Over time, the signal-to-noise ratio degrades until the agent cannot find relevant information.",[102,4825,4826],{},"The cause is almost always an overly permissive memory policy. The system stores everything because the developer cannot predict what might be useful later. This approach works in small systems but collapses in production.",[102,4828,4829],{},"The solution is explicit retention policies with automated pruning. Checkpoints expire after a configurable window. Context window content is summarized and archived when it exceeds a threshold. Vector embeddings are re-indexed on a schedule, with stale entries removed. These policies require upfront design but eliminate the gradual degradation that plagues ad-hoc systems.",[3604,4831,4833],{"id":4832},"stale-references","Stale References",[102,4835,4836],{},"Stale references occur when memory points to resources that no longer exist or have changed. An agent checkpoint references a file path that was renamed. A decision record cites a dependency version that was upgraded.",[102,4838,4839],{},"This failure mode is particularly common in systems that combine multiple agents working on the same codebase. Agent A writes a checkpoint referencing file X. Agent B reads the checkpoint and depends on file X's structure. Meanwhile, Agent C refactors file X. Agent B now operates on stale data.",[102,4841,4842],{},"The mitigation is to include version metadata in every checkpoint and to validate references before injection. Before an agent reads a checkpoint, verify that the referenced resources still match the checkpoint's expectations. If they do not, flag the checkpoint as stale and exclude it from the agent's context.",[98,4844,4846],{"id":4845},"the-gem-team-approach","The Gem-Team Approach",[102,4848,4849],{},"The Gem-Team repository codifies the patterns I have described into reusable components. Three patterns have proven most effective in production.",[3604,4851,4853],{"id":4852},"wave-based-checkpointing","Wave-Based Checkpointing",[102,4855,4856],{},"Rather than writing state on every turn, the Gem-Team architecture uses wave-based checkpointing. A wave is a logical unit of work — completing a code review, generating a test suite, implementing a feature. The agent writes a checkpoint only at wave boundaries.",[102,4858,4859],{},"This approach reduces write volume by an order of magnitude compared to per-turn checkpointing while preserving all semantically meaningful state. If a wave fails partway through, the checkpoint from the previous successful wave provides a clean recovery point.",[102,4861,4862],{},"Wave boundaries are defined by the agent's task definition. A code-generation agent creates a wave boundary when it finishes editing a file. A testing agent creates a wave boundary when it completes a test run. The system does not need to know the details — it only needs to detect when a wave starts and ends.",[3604,4864,4866],{"id":4865},"typed-state-schemas","Typed State Schemas",[102,4868,4869],{},"Every agent in the Gem-Team system writes state using a typed schema. The schema defines exactly what the agent remembers and in what format. There is no free-form text field for \"notes\" or \"summary.\" If the information is important enough to preserve, it gets a field in the schema.",[102,4871,4872],{},"Typed schemas serve two purposes. First, they constrain what the agent can write, preventing hallucinated fields from entering the persistent store. Second, they constrain what downstream agents can read, providing a contract that other agents can depend on.",[102,4874,4875],{},"When a new agent role is added to the system, the first deliverable is not the agent logic — it is the state schema. The schema is reviewed and tested before any code is written. This discipline prevents a class of integration bugs that would otherwise surface only in production.",[3604,4877,4879],{"id":4878},"controlled-injection","Controlled Injection",[102,4881,4882],{},"Controlled injection is the practice of explicitly deciding what memory to place into an agent's context window at invocation time. The system does not dump the entire checkpoint history into the context. It selects the most relevant checkpoints based on the current task, the agent role, and the time window.",[102,4884,4885],{},"The injection logic is itself a small module that takes the task description and returns an ordered list of memory items to inject. Each item includes a source label so the agent can distinguish checkpoint data from conversation history from tool output. This labeling helps the agent prioritize competing sources of information.",[102,4887,4888],{},"Controlled injection eliminated context drift in my systems. The agent receives exactly the memory it needs, explicitly formatted, with no ambiguity about what is true and what is generated.",[98,4890,4892],{"id":4891},"memory-type-tradeoffs","Memory Type Tradeoffs",[102,4894,4895],{},"Choosing the right memory mechanism requires understanding their engineering characteristics. The table below summarizes the tradeoffs based on production measurements from my Gem-Team deployments.",[1855,4897,4898,4914],{},[1858,4899,4900],{},[1861,4901,4902,4905,4908,4911],{},[1864,4903,4904],{},"Characteristic",[1864,4906,4907],{},"Short-Term Context",[1864,4909,4910],{},"Persistent Structured",[1864,4912,4913],{},"Vector Retrieval",[1875,4915,4916,4930,4944,4958,4971,4985,4999,5013],{},[1861,4917,4918,4921,4924,4927],{},[1880,4919,4920],{},"Access latency",[1880,4922,4923],{},"\u003C10ms",[1880,4925,4926],{},"10-50ms",[1880,4928,4929],{},"50-200ms",[1861,4931,4932,4935,4938,4941],{},[1880,4933,4934],{},"Storage cost",[1880,4936,4937],{},"Free (context-limited)",[1880,4939,4940],{},"Low (key-value)",[1880,4942,4943],{},"Medium (index + storage)",[1861,4945,4946,4949,4952,4955],{},[1880,4947,4948],{},"Accuracy",[1880,4950,4951],{},"High (current turn)",[1880,4953,4954],{},"High (typed schema)",[1880,4956,4957],{},"Medium (similarity-based)",[1861,4959,4960,4963,4966,4968],{},[1880,4961,4962],{},"Recall precision",[1880,4964,4965],{},"Exact",[1880,4967,4965],{},[1880,4969,4970],{},"Approximate",[1861,4972,4973,4976,4979,4982],{},[1880,4974,4975],{},"Max useful scale",[1880,4977,4978],{},"~100K tokens",[1880,4980,4981],{},"Millions of checkpoints",[1880,4983,4984],{},"Billions of embeddings",[1861,4986,4987,4990,4993,4996],{},[1880,4988,4989],{},"Maintenance burden",[1880,4991,4992],{},"None",[1880,4994,4995],{},"Schema migrations",[1880,4997,4998],{},"Re-indexing, tuning",[1861,5000,5001,5004,5007,5010],{},[1880,5002,5003],{},"Failure mode",[1880,5005,5006],{},"Window overflow",[1880,5008,5009],{},"Schema drift",[1880,5011,5012],{},"Relevance degradation",[1861,5014,5015,5018,5021,5024],{},[1880,5016,5017],{},"Best for",[1880,5019,5020],{},"Active task state",[1880,5022,5023],{},"Completed work",[1880,5025,5026],{},"Cross-session recall",[102,5028,5029],{},"The critical takeaway is that these mechanisms are complementary, not competing. A production system uses all three. The context window holds active state. The structured store holds completed work. The vector store holds searchable knowledge. Each serves a role that the others cannot fill.",[98,5031,5033],{"id":5032},"memory-lifecycle","Memory Lifecycle",[102,5035,5036],{},"The following diagram shows how memory flows through the system from creation to pruning.",[169,5038,5040],{"className":730,"code":5039,"language":732,"meta":175,"style":175},"flowchart LR\n    A[Agent produces output] --> B{Is this a wave boundary?}\n    B -- No --> C[Keep in context window]\n    C --> D[Continue current wave]\n    B -- Yes --> E[Create structured checkpoint]\n    E --> F[Write to persistent store]\n    F --> G{Should this be indexed?}\n    G -- Yes --> H[Generate embedding]\n    H --> I[Store in vector index]\n    G -- No --> J[Retain in structured store]\n    J --> K[Set TTL]\n    I --> K\n    K --> L[Prune expired checkpoints]\n    L --> M[Re-index vector entries]\n    M --> N[Validate references]\n    N --> O[Mark stale entries]\n    O --> P[Remove on schedule]\n",[177,5041,5042,5047,5052,5057,5062,5067,5072,5077,5082,5087,5092,5097,5102,5107,5112,5117,5122],{"__ignoreMap":175},[203,5043,5044],{"class":205,"line":206},[203,5045,5046],{},"flowchart LR\n",[203,5048,5049],{"class":205,"line":212},[203,5050,5051],{},"    A[Agent produces output] --> B{Is this a wave boundary?}\n",[203,5053,5054],{"class":205,"line":14},[203,5055,5056],{},"    B -- No --> C[Keep in context window]\n",[203,5058,5059],{"class":205,"line":224},[203,5060,5061],{},"    C --> D[Continue current wave]\n",[203,5063,5064],{"class":205,"line":229},[203,5065,5066],{},"    B -- Yes --> E[Create structured checkpoint]\n",[203,5068,5069],{"class":205,"line":235},[203,5070,5071],{},"    E --> F[Write to persistent store]\n",[203,5073,5074],{"class":205,"line":241},[203,5075,5076],{},"    F --> G{Should this be indexed?}\n",[203,5078,5079],{"class":205,"line":247},[203,5080,5081],{},"    G -- Yes --> H[Generate embedding]\n",[203,5083,5084],{"class":205,"line":253},[203,5085,5086],{},"    H --> I[Store in vector index]\n",[203,5088,5089],{"class":205,"line":259},[203,5090,5091],{},"    G -- No --> J[Retain in structured store]\n",[203,5093,5094],{"class":205,"line":265},[203,5095,5096],{},"    J --> K[Set TTL]\n",[203,5098,5099],{"class":205,"line":271},[203,5100,5101],{},"    I --> K\n",[203,5103,5104],{"class":205,"line":27},[203,5105,5106],{},"    K --> L[Prune expired checkpoints]\n",[203,5108,5109],{"class":205,"line":282},[203,5110,5111],{},"    L --> M[Re-index vector entries]\n",[203,5113,5114],{"class":205,"line":287},[203,5115,5116],{},"    M --> N[Validate references]\n",[203,5118,5119],{"class":205,"line":293},[203,5120,5121],{},"    N --> O[Mark stale entries]\n",[203,5123,5124],{"class":205,"line":299},[203,5125,5126],{},"    O --> P[Remove on schedule]\n",[102,5128,5129],{},"The lifecycle enforces that memory follows a path from ephemeral context to durable storage to pruned archive. Every checkpoint enters the system at a wave boundary, receives a TTL, and is either pruned or re-indexed on a schedule. No memory persists indefinitely without review.",[98,5131,5133],{"id":5132},"when-not-to-use-memory","When NOT to Use Memory",[102,5135,5136],{},"Memory is not free. Every memory operation adds latency, complexity, and surface area for bugs. There are situations where the correct design choice is to use no memory at all.",[3604,5138,5140],{"id":5139},"stateless-agents","Stateless Agents",[102,5142,5143],{},"An agent that performs pure transformations does not need memory. A translation agent that takes text and returns translated text operates correctly with no context beyond the current input. Adding memory to such an agent introduces risk of contamination between unrelated requests.",[102,5145,5146],{},"The Gem-Team architecture explicitly marks agents as stateful or stateless at definition time. Stateless agents receive no checkpoint history, no session context, and no vector retrieval results. This constraint prevents accidental coupling between independent operations.",[3604,5148,5150],{"id":5149},"one-shot-tasks","One-Shot Tasks",[102,5152,5153],{},"Some tasks complete in a single agent invocation. Generating a commit message from a diff. Formatting a code block. Converting data between formats. These tasks do not benefit from memory because there is no subsequent invocation that needs the context.",[102,5155,5156],{},"The mistake is adding memory to a one-shot task because \"it might be useful later.\" It will not be useful later. It will consume storage and add retrieval noise. One-shot tasks should be stateless by default, with memory added only when a concrete need is identified.",[3604,5158,5160],{"id":5159},"pure-transformations","Pure Transformations",[102,5162,5163],{},"Any operation that is a pure function of its inputs — same inputs always produce same outputs — does not require memory. A lint fixer that applies deterministic rules. A code formatter. A type annotator.",[102,5165,5166],{},"These tasks benefit from stateless design because stateless systems are trivially testable, trivially parallelizable, and trivially reproducible. Adding memory to a pure transformation makes it harder to debug, harder to test, and harder to reason about.",[98,5168,5170],{"id":5169},"conclusion","Conclusion",[102,5172,5173],{},"Memory is the differentiator between a multi-agent system that works in a demo and one that works in production. Reasoning models improve on a quarterly cadence. Memory architectures must be designed from the ground up and refined through production operation.",[102,5175,5176],{},"My experience building with the Gem-Team patterns has taught me three enduring lessons. First, typed schemas are the most effective defense against memory corruption — they constrain what agents can write and what they can read. Second, explicit memory injection at agent boundaries eliminates context drift better than any amount of prompt engineering. Third, memory requires maintenance — retention policies, pruning schedules, and reference validation are not optional.",[102,5178,5179],{},"The systems that survive production pressure are the ones that treat memory as a first-class architectural concern, not an afterthought. Design your memory before you design your agents. The reasoning will take care of itself.",[2052,5181,5182],{},"html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sKvfc, html code.shiki .sKvfc{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-text-decoration:underline}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sIDdj, html code.shiki .sIDdj{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#F8F8F2}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .s_MOj, html code.shiki .s_MOj{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}html pre.shiki code .sYThS, html code.shiki .sYThS{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sSBr1, html code.shiki .sSBr1{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#FD971F}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sY_X6, html code.shiki .sY_X6{--shiki-light:#E53935;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-font-style:inherit}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .sMTiH, html code.shiki .sMTiH{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}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);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}",{"title":175,"searchDepth":212,"depth":212,"links":5184},[5185,5186,5191,5196,5202,5207,5208,5209,5214],{"id":3577,"depth":212,"text":3571},{"id":3598,"depth":212,"text":3599,"children":5187},[5188,5189,5190],{"id":3606,"depth":14,"text":3607},{"id":3616,"depth":14,"text":3617},{"id":3626,"depth":14,"text":3627},{"id":3636,"depth":212,"text":3637,"children":5192},[5193,5194,5195],{"id":3643,"depth":14,"text":3644},{"id":3656,"depth":14,"text":3657},{"id":4773,"depth":14,"text":4774},{"id":4786,"depth":212,"text":4787,"children":5197},[5198,5199,5200,5201],{"id":4793,"depth":14,"text":4794},{"id":4806,"depth":14,"text":4807},{"id":4819,"depth":14,"text":4820},{"id":4832,"depth":14,"text":4833},{"id":4845,"depth":212,"text":4846,"children":5203},[5204,5205,5206],{"id":4852,"depth":14,"text":4853},{"id":4865,"depth":14,"text":4866},{"id":4878,"depth":14,"text":4879},{"id":4891,"depth":212,"text":4892},{"id":5032,"depth":212,"text":5033},{"id":5132,"depth":212,"text":5133,"children":5210},[5211,5212,5213],{"id":5139,"depth":14,"text":5140},{"id":5149,"depth":14,"text":5150},{"id":5159,"depth":14,"text":5160},{"id":5169,"depth":212,"text":5170},"2026-05-21","Memory is the hardest part of AI systems — not reasoning. Here's what actually works for multi-agent memory in production: structured state, controlled injection, lifecycle management.",{"readingTime":5218},"15 min read","\u002Fblog\u002F41-multi-agent-memory-systems-production",{"title":3571,"description":5216},"AI Systems Engineering","Building production-grade AI systems that scale beyond demos",{"src":5224,"mime":2080,"alt":5225,"width":2082,"height":2083},"\u002Fimg\u002Fblog\u002F41-multi-agent-memory-systems-production\u002Fbanner.svg","Multi-agent memory architecture showing short-term context, persistent state store, and vector retrieval layers","blog\u002F41-multi-agent-memory-systems-production",[2086,5228,2087,5229,5230,5231],"Memory Systems","Architecture","Gem Team","Production Systems","H7ZvVuf2RYq6NjCYNLZV5X9N8bqezx_HLWM3eYEkrUY",{"id":5234,"title":5235,"abstract":2070,"author":92,"authorUrl":2070,"body":5236,"date":5900,"dateUpdated":5900,"description":5901,"excerpt":2070,"extension":2071,"featured":215,"headline":2070,"image":2070,"meta":5902,"navigation":215,"ogImage":2070,"path":5904,"seo":5905,"series":5906,"seriesDescription":5907,"seriesOrder":224,"socialImage":5908,"stem":5911,"tags":5912,"__hash__":5917},"blog\u002Fblog\u002F35-gem-team-v1.20-whats-new.md","Gem Team v1.20.0: Marketplace Integration, APM Structure, and Enhanced Install Experience",{"type":95,"value":5237,"toc":5882},[5238,5241,5244,5248,5252,5270,5321,5324,5330,5334,5341,5371,5378,5550,5554,5557,5577,5580,5584,5591,5679,5682,5686,5693,5697,5711,5715,5719,5730,5734,5737,5741,5748,5752,5755,5781,5785,5788,5822,5825,5829,5832,5845,5848,5859,5862,5865,5879],[102,5239,5240],{},"Since introducing gem-orchestrator last month, the Gem Team project has evolved rapidly. Version 1.20.0 brings major improvements in distribution, installation, and project structure — making it easier than ever to integrate AI-powered workflows into your development environment.",[102,5242,5243],{},"In this post, I'll walk you through what's new, why these changes matter, and how they improve the developer experience.",[98,5245,5247],{"id":5246},"whats-new-in-v1200","What's New in v1.20.0",[3604,5249,5251],{"id":5250},"marketplace-integration-for-all-major-ai-tools","Marketplace Integration for All Major AI Tools",[102,5253,5254,5255,5258,5259,5258,5262,5265,5266,5269],{},"The biggest change: Gem Team is now available as a marketplace package for ",[130,5256,5257],{},"GitHub Copilot",", ",[130,5260,5261],{},"Claude Code",[130,5263,5264],{},"Cursor",", and ",[130,5267,5268],{},"OpenCode",". No more manual file copying or symlinking — just one command to install across all your AI coding tools.",[169,5271,5275],{"className":5272,"code":5273,"language":5274,"meta":175,"style":175},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark monokai","# Install APM (AI Package Manager) first\ncurl -fsSL https:\u002F\u002Fmicrosoft.github.io\u002Fapm\u002Finstall.sh | sh\n\n# Then install Gem Team\napm install mubaidr\u002Fgem-team\n","bash",[177,5276,5277,5282,5301,5305,5310],{"__ignoreMap":175},[203,5278,5279],{"class":205,"line":206},[203,5280,5281],{"class":3675},"# Install APM (AI Package Manager) first\n",[203,5283,5284,5288,5292,5296,5298],{"class":205,"line":212},[203,5285,5287],{"class":5286},"sR7ES","curl",[203,5289,5291],{"class":5290},"sFhLe"," -fsSL",[203,5293,5295],{"class":5294},"sLACW"," https:\u002F\u002Fmicrosoft.github.io\u002Fapm\u002Finstall.sh",[203,5297,3753],{"class":3708},[203,5299,5300],{"class":5286}," sh\n",[203,5302,5303],{"class":205,"line":14},[203,5304,216],{"emptyLinePlaceholder":215},[203,5306,5307],{"class":205,"line":224},[203,5308,5309],{"class":3675},"# Then install Gem Team\n",[203,5311,5312,5315,5318],{"class":205,"line":229},[203,5313,5314],{"class":5286},"apm",[203,5316,5317],{"class":5294}," install",[203,5319,5320],{"class":5294}," mubaidr\u002Fgem-team\n",[102,5322,5323],{},"APM handles the rest — detecting your installed AI tools and deploying the agents to the correct locations automatically.",[102,5325,5326,5329],{},[130,5327,5328],{},"Why this matters:"," Lower barrier to entry means faster adoption. You can try Gem Team in minutes, not hours.",[3604,5331,5333],{"id":5332},"apm-compatible-project-structure","APM-Compatible Project Structure",[102,5335,5336,5337,5340],{},"The agent definitions have been moved to ",[177,5338,5339],{},".apm\u002Fagents\u002F"," to align with the AI Package Manager standard. This change brings several benefits:",[124,5342,5343,5349,5355,5365],{},[127,5344,5345,5348],{},[130,5346,5347],{},"Cleaner project root"," — No more clutter from agent definitions",[127,5350,5351,5354],{},[130,5352,5353],{},"Better compatibility"," — Works seamlessly with APM's dependency management",[127,5356,5357,5360,5361,5364],{},[130,5358,5359],{},"Hidden from source control"," — ",[177,5362,5363],{},".apm\u002F"," is typically gitignored, reducing repo size",[127,5366,5367,5370],{},[130,5368,5369],{},"Multi-version support"," — Different projects can use different Gem Team versions",[102,5372,5373,5374,5377],{},"The plugin manifest (",[177,5375,5376],{},"plugin.json",") at the project root now references the APM location, making the package self-contained and portable.",[169,5379,5383],{"className":5380,"code":5381,"language":5382,"meta":175,"style":175},"language-json shiki shiki-themes material-theme-lighter github-light github-dark monokai","{\n  \"name\": \"gem-team\",\n  \"version\": \"1.20.0\",\n  \"agents\": \".apm\u002Fagents\u002F\",\n  \"permissions\": [\"memory\", \"file-system\", \"subagent\"],\n  \"capabilities\": [\"orchestration\", \"planning\", \"testing\", \"review\"]\n}\n","json",[177,5384,5385,5389,5416,5436,5455,5496,5546],{"__ignoreMap":175},[203,5386,5387],{"class":205,"line":206},[203,5388,296],{"class":3698},[203,5390,5391,5395,5399,5402,5404,5408,5412,5414],{"class":205,"line":212},[203,5392,5394],{"class":5393},"saDeg","  \"",[203,5396,5398],{"class":5397},"sEff5","name",[203,5400,5401],{"class":5393},"\"",[203,5403,1625],{"class":3698},[203,5405,5407],{"class":5406},"sh1VR"," \"",[203,5409,5411],{"class":5410},"sINAO","gem-team",[203,5413,5401],{"class":5406},[203,5415,4616],{"class":3698},[203,5417,5418,5420,5423,5425,5427,5429,5432,5434],{"class":205,"line":14},[203,5419,5394],{"class":5393},[203,5421,5422],{"class":5397},"version",[203,5424,5401],{"class":5393},[203,5426,1625],{"class":3698},[203,5428,5407],{"class":5406},[203,5430,5431],{"class":5410},"1.20.0",[203,5433,5401],{"class":5406},[203,5435,4616],{"class":3698},[203,5437,5438,5440,5443,5445,5447,5449,5451,5453],{"class":205,"line":224},[203,5439,5394],{"class":5393},[203,5441,5442],{"class":5397},"agents",[203,5444,5401],{"class":5393},[203,5446,1625],{"class":3698},[203,5448,5407],{"class":5406},[203,5450,5339],{"class":5410},[203,5452,5401],{"class":5406},[203,5454,4616],{"class":3698},[203,5456,5457,5459,5462,5464,5466,5469,5471,5474,5476,5478,5480,5483,5485,5487,5489,5492,5494],{"class":205,"line":229},[203,5458,5394],{"class":5393},[203,5460,5461],{"class":5397},"permissions",[203,5463,5401],{"class":5393},[203,5465,1625],{"class":3698},[203,5467,5468],{"class":3698}," [",[203,5470,5401],{"class":5406},[203,5472,5473],{"class":5410},"memory",[203,5475,5401],{"class":5406},[203,5477,4005],{"class":3698},[203,5479,5407],{"class":5406},[203,5481,5482],{"class":5410},"file-system",[203,5484,5401],{"class":5406},[203,5486,4005],{"class":3698},[203,5488,5407],{"class":5406},[203,5490,5491],{"class":5410},"subagent",[203,5493,5401],{"class":5406},[203,5495,1715],{"class":3698},[203,5497,5498,5500,5503,5505,5507,5509,5511,5514,5516,5518,5520,5523,5525,5527,5529,5532,5534,5536,5538,5541,5543],{"class":205,"line":235},[203,5499,5394],{"class":5393},[203,5501,5502],{"class":5397},"capabilities",[203,5504,5401],{"class":5393},[203,5506,1625],{"class":3698},[203,5508,5468],{"class":3698},[203,5510,5401],{"class":5406},[203,5512,5513],{"class":5410},"orchestration",[203,5515,5401],{"class":5406},[203,5517,4005],{"class":3698},[203,5519,5407],{"class":5406},[203,5521,5522],{"class":5410},"planning",[203,5524,5401],{"class":5406},[203,5526,4005],{"class":3698},[203,5528,5407],{"class":5406},[203,5530,5531],{"class":5410},"testing",[203,5533,5401],{"class":5406},[203,5535,4005],{"class":3698},[203,5537,5407],{"class":5406},[203,5539,5540],{"class":5410},"review",[203,5542,5401],{"class":5406},[203,5544,5545],{"class":3698},"]\n",[203,5547,5548],{"class":205,"line":241},[203,5549,656],{"class":3698},[3604,5551,5553],{"id":5552},"enhanced-installation-documentation","Enhanced Installation Documentation",[102,5555,5556],{},"The README and installation guide have been completely rewritten to reflect the new multi-method approach:",[1568,5558,5559,5565,5571],{},[127,5560,5561,5564],{},[130,5562,5563],{},"APM install"," (recommended) — one command for all tools",[127,5566,5567,5570],{},[130,5568,5569],{},"Marketplace registration"," — install via your tool's marketplace",[127,5572,5573,5576],{},[130,5574,5575],{},"Manual install"," — still supported for advanced users",[102,5578,5579],{},"The docs now include clear, step-by-step instructions for each AI tool, with troubleshooting tips and verification steps.",[3604,5581,5583],{"id":5582},"editorconfig-for-consistent-formatting","EditorConfig for Consistent Formatting",[102,5585,5586,5587,5590],{},"To ensure all contributors follow consistent code style, an ",[177,5588,5589],{},".editorconfig"," file has been added:",[169,5592,5596],{"className":5593,"code":5594,"language":5595,"meta":175,"style":175},"language-ini shiki shiki-themes material-theme-lighter github-light github-dark monokai","# EditorConfig is awesome: https:\u002F\u002FEditorConfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n\n[*.{sh,py}]\nindent_size = 4\n\n[*.md]\ntrim_trailing_whitespace = false\n","ini",[177,5597,5598,5603,5607,5612,5616,5621,5626,5631,5636,5641,5646,5651,5655,5660,5665,5669,5674],{"__ignoreMap":175},[203,5599,5600],{"class":205,"line":206},[203,5601,5602],{},"# EditorConfig is awesome: https:\u002F\u002FEditorConfig.org\n",[203,5604,5605],{"class":205,"line":212},[203,5606,216],{"emptyLinePlaceholder":215},[203,5608,5609],{"class":205,"line":14},[203,5610,5611],{},"root = true\n",[203,5613,5614],{"class":205,"line":224},[203,5615,216],{"emptyLinePlaceholder":215},[203,5617,5618],{"class":205,"line":229},[203,5619,5620],{},"[*]\n",[203,5622,5623],{"class":205,"line":235},[203,5624,5625],{},"indent_style = space\n",[203,5627,5628],{"class":205,"line":241},[203,5629,5630],{},"indent_size = 2\n",[203,5632,5633],{"class":205,"line":247},[203,5634,5635],{},"end_of_line = lf\n",[203,5637,5638],{"class":205,"line":253},[203,5639,5640],{},"charset = utf-8\n",[203,5642,5643],{"class":205,"line":259},[203,5644,5645],{},"trim_trailing_whitespace = true\n",[203,5647,5648],{"class":205,"line":265},[203,5649,5650],{},"insert_final_newline = true\n",[203,5652,5653],{"class":205,"line":271},[203,5654,216],{"emptyLinePlaceholder":215},[203,5656,5657],{"class":205,"line":27},[203,5658,5659],{},"[*.{sh,py}]\n",[203,5661,5662],{"class":205,"line":282},[203,5663,5664],{},"indent_size = 4\n",[203,5666,5667],{"class":205,"line":287},[203,5668,216],{"emptyLinePlaceholder":215},[203,5670,5671],{"class":205,"line":293},[203,5672,5673],{},"[*.md]\n",[203,5675,5676],{"class":205,"line":299},[203,5677,5678],{},"trim_trailing_whitespace = false\n",[102,5680,5681],{},"This helps maintain clean diffs and consistent formatting across different editors and IDEs.",[3604,5683,5685],{"id":5684},"context7-configuration","Context7 Configuration",[102,5687,5688,5689,5692],{},"A ",[177,5690,5691],{},"context7.json"," file has been added to provide rich metadata for Context7 integration. This improves documentation lookup and code example discovery when using Gem Team with tools that support Context7.",[3604,5694,5696],{"id":5695},"custom-domain-setup","Custom Domain Setup",[102,5698,5699,5700,5706,5707,5710],{},"The project now has a custom domain: ",[4541,5701,5705],{"href":5702,"rel":5703},"https:\u002F\u002Fgem-team.js.org",[5704],"nofollow","gem-team.js.org"," — easier to share and remember than the GitHub Pages URL. The ",[177,5708,5709],{},"CNAME"," file configures this for GitHub Pages.",[98,5712,5714],{"id":5713},"under-the-hood-improvements","Under-the-Hood Improvements",[3604,5716,5718],{"id":5717},"agent-metadata-refinements","Agent Metadata Refinements",[102,5720,5721,5722,5725,5726,5729],{},"All agent definitions have been updated with proper ",[177,5723,5724],{},"mode"," and ",[177,5727,5728],{},"hidden"," flags. This clarifies which agents are subagents (invoked by the orchestrator) and which are top-level entry points. The documentation now reflects these distinctions more accurately.",[3604,5731,5733],{"id":5732},"license-update","License Update",[102,5735,5736],{},"The copyright year has been updated to 2026, and ownership is clearly attributed.",[3604,5738,5740],{"id":5739},"better-gitignore","Better .gitignore",[102,5742,5743,5744,5747],{},"The ",[177,5745,5746],{},".gitignore"," has been expanded to cover all common build artifacts, logs, coverage reports, diagnostics, caches, and generated files. This keeps the repository clean and reduces noise in PRs.",[98,5749,5751],{"id":5750},"whats-next","What's Next",[102,5753,5754],{},"The roadmap for Gem Team includes:",[124,5756,5757,5763,5769,5775],{},[127,5758,5759,5762],{},[130,5760,5761],{},"Phase 8 Final Review automation"," — deeper architecture validation before completion",[127,5764,5765,5768],{},[130,5766,5767],{},"Enhanced mobile testing"," — Detox and Maestro integration for React Native\u002FFlutter",[127,5770,5771,5774],{},[130,5772,5773],{},"Performance profiling"," — automatic bottleneck detection and optimization suggestions",[127,5776,5777,5780],{},[130,5778,5779],{},"More design agents"," — specialized UI\u002FUX patterns for different industries",[98,5782,5784],{"id":5783},"how-to-upgrade","How to Upgrade",[102,5786,5787],{},"If you installed Gem Team before v1.20.0:",[169,5789,5791],{"className":5272,"code":5790,"language":5274,"meta":175,"style":175},"# Update via APM\napm update gem-team\n\n# Or reinstall via marketplace\n# Search for \"gem-team\" in your AI tool's marketplace\n",[177,5792,5793,5798,5808,5812,5817],{"__ignoreMap":175},[203,5794,5795],{"class":205,"line":206},[203,5796,5797],{"class":3675},"# Update via APM\n",[203,5799,5800,5802,5805],{"class":205,"line":212},[203,5801,5314],{"class":5286},[203,5803,5804],{"class":5294}," update",[203,5806,5807],{"class":5294}," gem-team\n",[203,5809,5810],{"class":205,"line":14},[203,5811,216],{"emptyLinePlaceholder":215},[203,5813,5814],{"class":205,"line":224},[203,5815,5816],{"class":3675},"# Or reinstall via marketplace\n",[203,5818,5819],{"class":205,"line":229},[203,5820,5821],{"class":3675},"# Search for \"gem-team\" in your AI tool's marketplace\n",[102,5823,5824],{},"Your existing memory files and customizations will be preserved. The upgrade is fully backward compatible.",[98,5826,5828],{"id":5827},"try-it-today","Try It Today",[102,5830,5831],{},"Gem Team v1.20.0 is available now. Whether you're building web apps, mobile apps, or complex backend systems, the multi-agent orchestration harness can help you ship higher-quality code faster.",[169,5833,5835],{"className":5272,"code":5834,"language":5274,"meta":175,"style":175},"apm install mubaidr\u002Fgem-team\n",[177,5836,5837],{"__ignoreMap":175},[203,5838,5839,5841,5843],{"class":205,"line":206},[203,5840,5314],{"class":5286},[203,5842,5317],{"class":5294},[203,5844,5320],{"class":5294},[102,5846,5847],{},"Then describe your next development goal and watch the team work:",[169,5849,5853],{"className":5850,"code":5851,"language":5852,"meta":175,"style":175},"language-txt shiki shiki-themes material-theme-lighter github-light github-dark monokai","> Implement OAuth2 authentication with refresh token rotation and secure cookie storage\n","txt",[177,5854,5855],{"__ignoreMap":175},[203,5856,5857],{"class":205,"line":206},[203,5858,5851],{},[102,5860,5861],{},"The orchestrator will detect the complexity, route through research and planning, and execute with wave-based parallelism — all while maintaining traceability and quality gates.",[5863,5864],"hr",{},[102,5866,5867],{},[119,5868,5869,5870,5874,5875,4063],{},"This post is part of the ",[4541,5871,5873],{"href":5872},"\u002Fblog\u002Fseries\u002Fai-powered-development","AI-Powered Development series",". Previous: ",[4541,5876,5878],{"href":5877},"\u002Fblog\u002F34-gem-team-orchestrator","Introducing gem-orchestrator",[2052,5880,5881],{},"html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .sR7ES, html code.shiki .sR7ES{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sFhLe, html code.shiki .sFhLe{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}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);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .saDeg, html code.shiki .saDeg{--shiki-light:#39ADB5;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sEff5, html code.shiki .sEff5{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sh1VR, html code.shiki .sh1VR{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#CFCFC2}html pre.shiki code .sINAO, html code.shiki .sINAO{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#CFCFC2}",{"title":175,"searchDepth":212,"depth":212,"links":5883},[5884,5892,5897,5898,5899],{"id":5246,"depth":212,"text":5247,"children":5885},[5886,5887,5888,5889,5890,5891],{"id":5250,"depth":14,"text":5251},{"id":5332,"depth":14,"text":5333},{"id":5552,"depth":14,"text":5553},{"id":5582,"depth":14,"text":5583},{"id":5684,"depth":14,"text":5685},{"id":5695,"depth":14,"text":5696},{"id":5713,"depth":212,"text":5714,"children":5893},[5894,5895,5896],{"id":5717,"depth":14,"text":5718},{"id":5732,"depth":14,"text":5733},{"id":5739,"depth":14,"text":5740},{"id":5750,"depth":212,"text":5751},{"id":5783,"depth":212,"text":5784},{"id":5827,"depth":212,"text":5828},"2026-05-09","A deep dive into the latest Gem Team release featuring marketplace support for multiple AI tools, APM-compatible structure, simplified installation, and improved developer experience.",{"readingTime":5903},"6 min read","\u002Fblog\u002F35-gem-team-v1.20-whats-new",{"title":5235,"description":5901},"AI-Powered Development","Exploring how AI is transforming software development",{"src":5909,"mime":2080,"alt":5910,"width":2082,"height":2083},"\u002Fimg\u002Fblog\u002F35-gem-team-v1.20\u002Fbanner.svg","Gem Team v1.20.0 - marketplace integration, APM structure, and enhanced installation experience","blog\u002F35-gem-team-v1.20-whats-new",[2086,5230,5913,5914,2087,5915,5916],"Automation","Development Workflow","Open Source","Release","bJdb_0z8icDBrczUz5Fq1sDT6BtIhAi4Ff4UR92ysPE",{"id":5919,"title":5920,"abstract":5921,"author":92,"authorUrl":93,"body":5922,"date":7439,"dateUpdated":7439,"description":7440,"excerpt":2070,"extension":2071,"featured":215,"headline":5920,"image":2070,"meta":7441,"navigation":215,"ogImage":2070,"path":7443,"seo":7444,"series":5221,"seriesDescription":5222,"seriesOrder":206,"socialImage":7445,"stem":7448,"tags":7449,"__hash__":7451},"blog\u002Fblog\u002F37-production-grade-ai-agents-not-prompts.md","Production-Grade AI Agents Are Not Prompts — They Are Systems","A deep dive into why prompt-only AI agents fail in production, the architecture of orchestrated multi-agent systems, and practical patterns from the Gem-Team framework for building reliable AI workflows.",{"type":95,"value":5923,"toc":7421},[5924,5927,5930,5933,5936,5940,5943,5949,5955,5961,5967,5970,5974,5977,6061,6065,6068,6071,6075,6078,6081,6084,6088,6091,6094,6097,6101,6104,6108,6111,6466,6472,6476,6479,6574,6577,6581,6584,7167,7173,7177,7180,7183,7186,7309,7312,7315,7319,7322,7327,7344,7349,7366,7369,7373,7376,7379,7386,7389,7392,7395,7399,7402,7405,7408,7410,7418],[98,5925,5920],{"id":5926},"production-grade-ai-agents-are-not-prompts-they-are-systems",[102,5928,5929],{},"I learned this lesson the hard way. In early 2025, I built what I thought was a clever AI agent for a Laravel application. It was a single prompt chain: receive a bug report, analyze the stack trace, search the codebase, generate a fix, apply it. Five steps chained together with a template that read \"Based on the following context, generate...\" at each hop. It worked beautifully in the demo. Then we put it on a real repository with real issues, and it fell apart within 24 hours.",[102,5931,5932],{},"The prompt chain hallucinated a fix for an authentication bug by modifying the wrong file. It invented a database column that did not exist. It left the codebase in an inconsistent state, and because there was no rollback mechanism and no observability, it took me two hours to undo the damage. That was the moment I stopped thinking of AI agents as prompts and started thinking of them as systems.",[102,5934,5935],{},"This post is about what I learned since then: why prompt-only agents fail, what distinguishes the three tiers of agent sophistication, and how the Gem-Team architecture solves the reliability problem with directed acyclic graph execution, deterministic routing, and wave-based parallelism. If you are building AI agents for production in 2026, this is the architecture I wish I had from day one.",[98,5937,5939],{"id":5938},"why-prompt-only-agents-fail-in-production","Why Prompt-Only Agents Fail in Production",[102,5941,5942],{},"The failure modes of prompt-chained agents are not random. They follow predictable patterns that I have now seen across multiple projects, including the Laravel stack I maintain and the Nuxt applications I deploy.",[102,5944,5945,5948],{},[130,5946,5947],{},"Context drift accumulates with every hop."," A prompt chain passes context from one step to the next. Each step reformats, summarizes, or filters that context. After four or five hops, the original problem description has degraded into something the LLM barely recognizes. I watched a five-step bug-fixing chain turn \"user login returns 500 error\" into \"modify the UserController to add a new method\" by step three, because the intermediate representation lost the constraint that we were fixing a bug, not adding a feature. The chain produced plausible code that solved the wrong problem.",[102,5950,5951,5954],{},[130,5952,5953],{},"There is no recovery path."," When a prompt chain produces an incorrect intermediate result, every subsequent step compounds the error. A typical chain does not check its own output. It does not verify that a generated SQL migration matches the schema. It does not validate that a code change compiles. It just passes the output to the next prompt template. In the Laravel project I mentioned, the fix-for-auth-bug chain never verified that the User model had the column it was referencing. The LLM assumed the column existed because it looked plausible. The chain had no guard.",[102,5956,5957,5960],{},[130,5958,5959],{},"Observability is nonexistent."," When a prompt chain fails, you get the final output. You do not get step-level traces, confidence scores, or decision logs. You cannot answer the question \"why did the agent choose to modify this file?\" because the chain does not record the reasoning. Debugging becomes guesswork. I spent those two hours not fixing the bug but reconstructing what the agent had done and why.",[102,5962,5963,5966],{},[130,5964,5965],{},"No deterministic routing."," A prompt chain follows a fixed sequence regardless of the input. A simple typo fix goes through the same five steps as a database migration. There is no phase detection, no complexity assessment, no routing to specialized handlers. The chain is rigid, and rigidity in AI systems guarantees brittleness.",[102,5968,5969],{},"These failure modes are not fixable with better prompts. They are architectural. The chain structure itself is the problem.",[98,5971,5973],{"id":5972},"three-tiers-of-agent-sophistication","Three Tiers of Agent Sophistication",[102,5975,5976],{},"After the Laravel failure, I categorized every agent framework I could find into three tiers. Understanding where your system falls on this spectrum is the first step toward building something production-grade.",[1855,5978,5979,6001],{},[1858,5980,5981],{},[1861,5982,5983,5986,5989,5992,5995,5998],{},[1864,5984,5985],{},"Tier",[1864,5987,5988],{},"Name",[1864,5990,5991],{},"Routing",[1864,5993,5994],{},"State",[1864,5996,5997],{},"Recovery",[1864,5999,6000],{},"Example",[1875,6002,6003,6021,6041],{},[1861,6004,6005,6008,6011,6014,6016,6018],{},[1880,6006,6007],{},"1",[1880,6009,6010],{},"Prompt Chain",[1880,6012,6013],{},"Fixed sequential",[1880,6015,4992],{},[1880,6017,4992],{},[1880,6019,6020],{},"Single-LLM script with template steps",[1861,6022,6023,6026,6029,6032,6035,6038],{},[1880,6024,6025],{},"2",[1880,6027,6028],{},"Tool-Using Agent",[1880,6030,6031],{},"Model-decided routing",[1880,6033,6034],{},"Ephemeral",[1880,6036,6037],{},"Retry with same prompt",[1880,6039,6040],{},"ReAct-style agents, ChatGPT with plugins",[1861,6042,6043,6046,6049,6052,6055,6058],{},[1880,6044,6045],{},"3",[1880,6047,6048],{},"Orchestrated Multi-Agent",[1880,6050,6051],{},"Deterministic DAG with specialist routing",[1880,6053,6054],{},"Persistent across waves",[1880,6056,6057],{},"Diagnose-then-fix loop with confidence scoring",[1880,6059,6060],{},"Gem-Team, custom orchestrators",[3604,6062,6064],{"id":6063},"tier-1-prompt-chains","Tier 1: Prompt Chains",[102,6066,6067],{},"This is where most hobby projects and early prototypes live. A prompt chain is a script that calls an LLM multiple times, passing the output of one call as context to the next. The sequence is hardcoded. There is no conditional branching based on output quality. There is no state management beyond the text passed between steps.",[102,6069,6070],{},"Prompt chains are useful for one thing: prototyping. If you need to prove that an LLM can perform a multi-step task, a prompt chain is the fastest way to test the hypothesis. But shipping a prompt chain to production is like shipping a prototype without error handling. I did it once. I will not do it again.",[3604,6072,6074],{"id":6073},"tier-2-tool-using-agents","Tier 2: Tool-Using Agents",[102,6076,6077],{},"Tool-using agents improve on chains by letting the model decide which tool to call next. The ReAct pattern (Reasoning + Acting) is the most common example. The model receives a list of available functions, reasons about which to call, and iterates until it decides the task is complete.",[102,6079,6080],{},"These agents appear flexible, but they introduce a new failure mode: the model makes routing decisions without understanding the full context. I watched a ReAct-style agent call a \"search codebase\" tool seven times in a loop, each time with slightly different phrasing, because it lacked the confidence to stop. The state is ephemeral -- when the context window fills, earlier reasoning is lost. Recovery is limited to retrying the same action, which produces the same result.",[102,6082,6083],{},"Tier 2 works for narrow, well-scoped tasks with clear termination criteria. It does not work for multi-file changes, cross-system integrations, or tasks where sequence matters.",[3604,6085,6087],{"id":6086},"tier-3-orchestrated-multi-agent-systems","Tier 3: Orchestrated Multi-Agent Systems",[102,6089,6090],{},"This is the tier where agents become production systems. An orchestrator sits above the LLM calls and makes three critical decisions that prompt chains and tool-using agents leave to chance: which phase of work we are in, which specialist agent should handle it, and whether the output meets quality thresholds before proceeding.",[102,6092,6093],{},"The orchestrator does not just pass text between steps. It maintains a persistent state across waves of execution. It constructs a directed acyclic graph (DAG) of tasks, where edges represent dependencies and nodes represent work units assigned to specialist agents. It runs tasks in parallel when dependencies allow. It checks integration quality between waves. And when something fails, it runs a structured diagnostic loop instead of blindly retrying.",[102,6095,6096],{},"I built the Gem-Team orchestrator to encode these patterns explicitly. The rest of this post walks through the architecture that makes Tier 3 work.",[98,6098,6100],{"id":6099},"gem-team-architecture-dag-execution-and-deterministic-routing","Gem-Team Architecture: DAG Execution and Deterministic Routing",[102,6102,6103],{},"The Gem-Team orchestrator follows a five-phase workflow, but the core architectural difference is how it represents and executes work. Instead of a linear chain or a model-driven loop, it uses a directed acyclic graph of tasks, grouped into execution waves.",[3604,6105,6107],{"id":6106},"phase-detection","Phase Detection",[102,6109,6110],{},"The orchestrator starts by assessing the incoming goal. It does not assume every request needs the same workflow. A one-line bug fix skips straight to execution. A feature request that touches authentication, database schema, and frontend components triggers the full pipeline.",[169,6112,6114],{"className":3666,"code":6113,"language":3668,"meta":175,"style":175},"\u002F\u002F Phase detection logic from gem-orchestrator\ntype GoalComplexity = \"simple\" | \"medium\" | \"complex\"\ntype WorkflowPhase =\n  | \"discuss\"\n  | \"prd\"\n  | \"research\"\n  | \"planning\"\n  | \"execution\"\n  | \"summary\"\n\ninterface GoalAssessment {\n  complexity: GoalComplexity\n  phases: WorkflowPhase[]\n  riskFactors: string[]\n  affectedAreas: string[]\n}\n\nfunction assessGoal(\n  description: string,\n  context: CodebaseContext,\n): GoalAssessment {\n  const complexity = detectComplexity(description, context)\n  const phases = determinePhases(complexity, context)\n\n  return {\n    complexity,\n    phases,\n    riskFactors: identifyRisks(description, context),\n    affectedAreas: identifyAffectedAreas(description, context),\n  }\n}\n",[177,6115,6116,6121,6158,6168,6180,6191,6202,6212,6223,6234,6238,6247,6257,6268,6279,6290,6294,6298,6308,6319,6331,6341,6366,6389,6393,6400,6407,6414,6436,6458,6462],{"__ignoreMap":175},[203,6117,6118],{"class":205,"line":206},[203,6119,6120],{"class":3675},"\u002F\u002F Phase detection logic from gem-orchestrator\n",[203,6122,6123,6126,6129,6131,6134,6137,6139,6141,6143,6146,6148,6150,6152,6155],{"class":205,"line":212},[203,6124,6125],{"class":3690},"type",[203,6127,6128],{"class":3694}," GoalComplexity",[203,6130,4045],{"class":3708},[203,6132,5407],{"class":6133},"siCPE",[203,6135,6136],{"class":5294},"simple",[203,6138,5401],{"class":6133},[203,6140,3753],{"class":3708},[203,6142,5407],{"class":6133},[203,6144,6145],{"class":5294},"medium",[203,6147,5401],{"class":6133},[203,6149,3753],{"class":3708},[203,6151,5407],{"class":6133},[203,6153,6154],{"class":5294},"complex",[203,6156,6157],{"class":6133},"\"\n",[203,6159,6160,6162,6165],{"class":205,"line":14},[203,6161,6125],{"class":3690},[203,6163,6164],{"class":3694}," WorkflowPhase",[203,6166,6167],{"class":3708}," =\n",[203,6169,6170,6173,6175,6178],{"class":205,"line":224},[203,6171,6172],{"class":3708},"  |",[203,6174,5407],{"class":6133},[203,6176,6177],{"class":5294},"discuss",[203,6179,6157],{"class":6133},[203,6181,6182,6184,6186,6189],{"class":205,"line":229},[203,6183,6172],{"class":3708},[203,6185,5407],{"class":6133},[203,6187,6188],{"class":5294},"prd",[203,6190,6157],{"class":6133},[203,6192,6193,6195,6197,6200],{"class":205,"line":235},[203,6194,6172],{"class":3708},[203,6196,5407],{"class":6133},[203,6198,6199],{"class":5294},"research",[203,6201,6157],{"class":6133},[203,6203,6204,6206,6208,6210],{"class":205,"line":241},[203,6205,6172],{"class":3708},[203,6207,5407],{"class":6133},[203,6209,5522],{"class":5294},[203,6211,6157],{"class":6133},[203,6213,6214,6216,6218,6221],{"class":205,"line":247},[203,6215,6172],{"class":3708},[203,6217,5407],{"class":6133},[203,6219,6220],{"class":5294},"execution",[203,6222,6157],{"class":6133},[203,6224,6225,6227,6229,6232],{"class":205,"line":253},[203,6226,6172],{"class":3708},[203,6228,5407],{"class":6133},[203,6230,6231],{"class":5294},"summary",[203,6233,6157],{"class":6133},[203,6235,6236],{"class":205,"line":259},[203,6237,216],{"emptyLinePlaceholder":215},[203,6239,6240,6242,6245],{"class":205,"line":265},[203,6241,3691],{"class":3690},[203,6243,6244],{"class":3694}," GoalAssessment",[203,6246,3699],{"class":3698},[203,6248,6249,6252,6254],{"class":205,"line":271},[203,6250,6251],{"class":3704},"  complexity",[203,6253,1625],{"class":3708},[203,6255,6256],{"class":3694}," GoalComplexity\n",[203,6258,6259,6262,6264,6266],{"class":205,"line":27},[203,6260,6261],{"class":3704},"  phases",[203,6263,1625],{"class":3708},[203,6265,6164],{"class":3694},[203,6267,3850],{"class":3849},[203,6269,6270,6273,6275,6277],{"class":205,"line":282},[203,6271,6272],{"class":3704},"  riskFactors",[203,6274,1625],{"class":3708},[203,6276,3750],{"class":3711},[203,6278,3850],{"class":3849},[203,6280,6281,6284,6286,6288],{"class":205,"line":287},[203,6282,6283],{"class":3704},"  affectedAreas",[203,6285,1625],{"class":3708},[203,6287,3750],{"class":3711},[203,6289,3850],{"class":3849},[203,6291,6292],{"class":205,"line":293},[203,6293,656],{"class":3698},[203,6295,6296],{"class":205,"line":299},[203,6297,216],{"emptyLinePlaceholder":215},[203,6299,6300,6303,6306],{"class":205,"line":305},[203,6301,6302],{"class":3690},"function",[203,6304,6305],{"class":4074}," assessGoal",[203,6307,4533],{"class":3698},[203,6309,6310,6313,6315,6317],{"class":205,"line":310},[203,6311,6312],{"class":4036},"  description",[203,6314,1625],{"class":3708},[203,6316,3750],{"class":3711},[203,6318,4616],{"class":3698},[203,6320,6321,6324,6326,6329],{"class":205,"line":316},[203,6322,6323],{"class":4036},"  context",[203,6325,1625],{"class":3708},[203,6327,6328],{"class":3694}," CodebaseContext",[203,6330,4616],{"class":3698},[203,6332,6333,6335,6337,6339],{"class":205,"line":322},[203,6334,4052],{"class":3698},[203,6336,1625],{"class":3708},[203,6338,6244],{"class":3694},[203,6340,3699],{"class":3698},[203,6342,6343,6346,6349,6351,6354,6356,6359,6361,6364],{"class":205,"line":328},[203,6344,6345],{"class":3690},"  const",[203,6347,6348],{"class":4257}," complexity",[203,6350,4045],{"class":3708},[203,6352,6353],{"class":4074}," detectComplexity",[203,6355,4033],{"class":4077},[203,6357,6358],{"class":3849},"description",[203,6360,4005],{"class":3698},[203,6362,6363],{"class":3849}," context",[203,6365,4181],{"class":4077},[203,6367,6368,6370,6373,6375,6378,6380,6383,6385,6387],{"class":205,"line":333},[203,6369,6345],{"class":3690},[203,6371,6372],{"class":4257}," phases",[203,6374,4045],{"class":3708},[203,6376,6377],{"class":4074}," determinePhases",[203,6379,4033],{"class":4077},[203,6381,6382],{"class":3849},"complexity",[203,6384,4005],{"class":3698},[203,6386,6363],{"class":3849},[203,6388,4181],{"class":4077},[203,6390,6391],{"class":205,"line":339},[203,6392,216],{"emptyLinePlaceholder":215},[203,6394,6395,6398],{"class":205,"line":345},[203,6396,6397],{"class":4191},"  return",[203,6399,3699],{"class":3698},[203,6401,6402,6405],{"class":205,"line":351},[203,6403,6404],{"class":3849},"    complexity",[203,6406,4616],{"class":3698},[203,6408,6409,6412],{"class":205,"line":356},[203,6410,6411],{"class":3849},"    phases",[203,6413,4616],{"class":3698},[203,6415,6416,6419,6421,6424,6426,6428,6430,6432,6434],{"class":205,"line":362},[203,6417,6418],{"class":4077},"    riskFactors",[203,6420,1625],{"class":3698},[203,6422,6423],{"class":4074}," identifyRisks",[203,6425,4033],{"class":4077},[203,6427,6358],{"class":3849},[203,6429,4005],{"class":3698},[203,6431,6363],{"class":3849},[203,6433,4052],{"class":4077},[203,6435,4616],{"class":3698},[203,6437,6438,6441,6443,6446,6448,6450,6452,6454,6456],{"class":205,"line":368},[203,6439,6440],{"class":4077},"    affectedAreas",[203,6442,1625],{"class":3698},[203,6444,6445],{"class":4074}," identifyAffectedAreas",[203,6447,4033],{"class":4077},[203,6449,6358],{"class":3849},[203,6451,4005],{"class":3698},[203,6453,6363],{"class":3849},[203,6455,4052],{"class":4077},[203,6457,4616],{"class":3698},[203,6459,6460],{"class":205,"line":374},[203,6461,3964],{"class":3698},[203,6463,6464],{"class":205,"line":379},[203,6465,656],{"class":3698},[102,6467,5743,6468,6471],{},[177,6469,6470],{},"determinePhases"," function encodes the routing rules. Simple changes go directly to planning then execution. Changes that touch security, data migration, or public APIs trigger the discuss and PRD phases. The orchestrator does not ask the LLM which phase to use. It applies deterministic rules based on signals from the codebase analysis.",[3604,6473,6475],{"id":6474},"dag-construction-and-wave-based-execution","DAG Construction and Wave-Based Execution",[102,6477,6478],{},"Once the planning phase completes, the orchestrator has a list of tasks with dependencies. It constructs a DAG and groups tasks into waves. Tasks in the same wave have no dependencies on each other and can run in parallel. Waves execute sequentially, with an integration gate between them.",[169,6480,6482],{"className":730,"code":6481,"language":732,"meta":175,"style":175},"graph TD\n    A[Goal Assessment] --> B[Phase Detection]\n    B --> C[Planning: DAG Construction]\n    C --> D[Wave 1: Parallel Tasks]\n    D --> E1[Task A: Schema Migration]\n    D --> E2[Task B: Model Update]\n    D --> E3[Task C: Controller Stub]\n    E1 --> F[Integration Gate 1]\n    E2 --> F\n    E3 --> F\n    F --> G[Wave 2: Parallel Tasks]\n    G --> H1[Task D: Service Layer]\n    G --> H2[Task E: Validation Rules]\n    H1 --> I[Integration Gate 2]\n    H2 --> I\n    I --> J[Wave 3: E2E Test]\n    J --> K[Final Gate: Review]\n    K --> L[Summary]\n",[177,6483,6484,6489,6494,6499,6504,6509,6514,6519,6524,6529,6534,6539,6544,6549,6554,6559,6564,6569],{"__ignoreMap":175},[203,6485,6486],{"class":205,"line":206},[203,6487,6488],{},"graph TD\n",[203,6490,6491],{"class":205,"line":212},[203,6492,6493],{},"    A[Goal Assessment] --> B[Phase Detection]\n",[203,6495,6496],{"class":205,"line":14},[203,6497,6498],{},"    B --> C[Planning: DAG Construction]\n",[203,6500,6501],{"class":205,"line":224},[203,6502,6503],{},"    C --> D[Wave 1: Parallel Tasks]\n",[203,6505,6506],{"class":205,"line":229},[203,6507,6508],{},"    D --> E1[Task A: Schema Migration]\n",[203,6510,6511],{"class":205,"line":235},[203,6512,6513],{},"    D --> E2[Task B: Model Update]\n",[203,6515,6516],{"class":205,"line":241},[203,6517,6518],{},"    D --> E3[Task C: Controller Stub]\n",[203,6520,6521],{"class":205,"line":247},[203,6522,6523],{},"    E1 --> F[Integration Gate 1]\n",[203,6525,6526],{"class":205,"line":253},[203,6527,6528],{},"    E2 --> F\n",[203,6530,6531],{"class":205,"line":259},[203,6532,6533],{},"    E3 --> F\n",[203,6535,6536],{"class":205,"line":265},[203,6537,6538],{},"    F --> G[Wave 2: Parallel Tasks]\n",[203,6540,6541],{"class":205,"line":271},[203,6542,6543],{},"    G --> H1[Task D: Service Layer]\n",[203,6545,6546],{"class":205,"line":27},[203,6547,6548],{},"    G --> H2[Task E: Validation Rules]\n",[203,6550,6551],{"class":205,"line":282},[203,6552,6553],{},"    H1 --> I[Integration Gate 2]\n",[203,6555,6556],{"class":205,"line":287},[203,6557,6558],{},"    H2 --> I\n",[203,6560,6561],{"class":205,"line":293},[203,6562,6563],{},"    I --> J[Wave 3: E2E Test]\n",[203,6565,6566],{"class":205,"line":299},[203,6567,6568],{},"    J --> K[Final Gate: Review]\n",[203,6570,6571],{"class":205,"line":305},[203,6572,6573],{},"    K --> L[Summary]\n",[102,6575,6576],{},"Each integration gate runs gem-reviewer against the current state of the codebase. The reviewer checks that the build compiles, existing tests pass, and new code follows project conventions. If the gate fails, gem-debugger enters the diagnose-then-fix loop.",[3604,6578,6580],{"id":6579},"the-wave-executor","The Wave Executor",[102,6582,6583],{},"The wave executor is the runtime component that manages parallel agent execution. It spins up to four specialist agents concurrently, each handling one task from the wave. Each agent operates independently, with its own context window and its own completion criteria.",[169,6585,6587],{"className":3666,"code":6586,"language":3668,"meta":175,"style":175},"\u002F\u002F Simplified wave executor from gem-orchestrator\ninterface Wave {\n  id: string\n  tasks: Task[]\n  gate: IntegrationGate\n}\n\ninterface Task {\n  id: string\n  agent: string\n  specification: string\n  dependencies: string[]\n  status: \"pending\" | \"running\" | \"completed\" | \"failed\"\n}\n\nasync function executeWave(\n  wave: Wave,\n  state: ExecutionState,\n): Promise\u003CWaveResult> {\n  const results = await Promise.allSettled(\n    wave.tasks.map((task) => executeTask(task, state)),\n  )\n\n  const failures = results.filter(\n    (r) => r.status === \"rejected\",\n  ) as PromiseRejectedResult[]\n  if (failures.length > 0) {\n    return { status: \"wave_failed\", failures }\n  }\n\n  \u002F\u002F Integration gate\n  const gateResult = await runIntegrationGate(wave.gate, state)\n  if (!gateResult.passed) {\n    const diagnosis = await diagnoseFailure(gateResult, state)\n    const fixResult = await applyFix(diagnosis, state)\n    if (!fixResult.applied) {\n      return { status: \"gate_failed\", diagnosis, fixResult }\n    }\n  }\n\n  return { status: \"wave_completed\" }\n}\n",[177,6588,6589,6594,6603,6612,6624,6634,6638,6642,6650,6658,6667,6676,6687,6728,6732,6736,6749,6760,6772,6789,6809,6852,6857,6861,6879,6910,6923,6947,6973,6977,6981,6986,7016,7036,7060,7085,7105,7132,7136,7140,7144,7163],{"__ignoreMap":175},[203,6590,6591],{"class":205,"line":206},[203,6592,6593],{"class":3675},"\u002F\u002F Simplified wave executor from gem-orchestrator\n",[203,6595,6596,6598,6601],{"class":205,"line":212},[203,6597,3691],{"class":3690},[203,6599,6600],{"class":3694}," Wave",[203,6602,3699],{"class":3698},[203,6604,6605,6608,6610],{"class":205,"line":14},[203,6606,6607],{"class":3704},"  id",[203,6609,1625],{"class":3708},[203,6611,3722],{"class":3711},[203,6613,6614,6617,6619,6622],{"class":205,"line":224},[203,6615,6616],{"class":3704},"  tasks",[203,6618,1625],{"class":3708},[203,6620,6621],{"class":3694}," Task",[203,6623,3850],{"class":3849},[203,6625,6626,6629,6631],{"class":205,"line":229},[203,6627,6628],{"class":3704},"  gate",[203,6630,1625],{"class":3708},[203,6632,6633],{"class":3694}," IntegrationGate\n",[203,6635,6636],{"class":205,"line":235},[203,6637,656],{"class":3698},[203,6639,6640],{"class":205,"line":241},[203,6641,216],{"emptyLinePlaceholder":215},[203,6643,6644,6646,6648],{"class":205,"line":247},[203,6645,3691],{"class":3690},[203,6647,6621],{"class":3694},[203,6649,3699],{"class":3698},[203,6651,6652,6654,6656],{"class":205,"line":253},[203,6653,6607],{"class":3704},[203,6655,1625],{"class":3708},[203,6657,3722],{"class":3711},[203,6659,6660,6663,6665],{"class":205,"line":259},[203,6661,6662],{"class":3704},"  agent",[203,6664,1625],{"class":3708},[203,6666,3722],{"class":3711},[203,6668,6669,6672,6674],{"class":205,"line":265},[203,6670,6671],{"class":3704},"  specification",[203,6673,1625],{"class":3708},[203,6675,3722],{"class":3711},[203,6677,6678,6681,6683,6685],{"class":205,"line":271},[203,6679,6680],{"class":3704},"  dependencies",[203,6682,1625],{"class":3708},[203,6684,3750],{"class":3711},[203,6686,3850],{"class":3849},[203,6688,6689,6692,6694,6696,6699,6701,6703,6705,6708,6710,6712,6714,6717,6719,6721,6723,6726],{"class":205,"line":27},[203,6690,6691],{"class":3704},"  status",[203,6693,1625],{"class":3708},[203,6695,5407],{"class":6133},[203,6697,6698],{"class":5294},"pending",[203,6700,5401],{"class":6133},[203,6702,3753],{"class":3708},[203,6704,5407],{"class":6133},[203,6706,6707],{"class":5294},"running",[203,6709,5401],{"class":6133},[203,6711,3753],{"class":3708},[203,6713,5407],{"class":6133},[203,6715,6716],{"class":5294},"completed",[203,6718,5401],{"class":6133},[203,6720,3753],{"class":3708},[203,6722,5407],{"class":6133},[203,6724,6725],{"class":5294},"failed",[203,6727,6157],{"class":6133},[203,6729,6730],{"class":205,"line":282},[203,6731,656],{"class":3698},[203,6733,6734],{"class":205,"line":287},[203,6735,216],{"emptyLinePlaceholder":215},[203,6737,6738,6741,6744,6747],{"class":205,"line":293},[203,6739,6740],{"class":3987},"async",[203,6742,6743],{"class":3690}," function",[203,6745,6746],{"class":4074}," executeWave",[203,6748,4533],{"class":3698},[203,6750,6751,6754,6756,6758],{"class":205,"line":299},[203,6752,6753],{"class":4036},"  wave",[203,6755,1625],{"class":3708},[203,6757,6600],{"class":3694},[203,6759,4616],{"class":3698},[203,6761,6762,6765,6767,6770],{"class":205,"line":305},[203,6763,6764],{"class":4036},"  state",[203,6766,1625],{"class":3708},[203,6768,6769],{"class":3694}," ExecutionState",[203,6771,4616],{"class":3698},[203,6773,6774,6776,6778,6780,6782,6785,6787],{"class":205,"line":310},[203,6775,4052],{"class":3698},[203,6777,1625],{"class":3708},[203,6779,4136],{"class":3694},[203,6781,3999],{"class":3698},[203,6783,6784],{"class":3694},"WaveResult",[203,6786,4144],{"class":3698},[203,6788,3699],{"class":3698},[203,6790,6791,6793,6795,6797,6800,6802,6804,6807],{"class":205,"line":316},[203,6792,6345],{"class":3690},[203,6794,4407],{"class":4257},[203,6796,4045],{"class":3708},[203,6798,6799],{"class":4191}," await",[203,6801,4136],{"class":3711},[203,6803,4063],{"class":3698},[203,6805,6806],{"class":4074},"allSettled",[203,6808,4533],{"class":4077},[203,6810,6811,6814,6816,6819,6821,6824,6826,6828,6831,6833,6836,6839,6841,6843,6845,6847,6850],{"class":205,"line":322},[203,6812,6813],{"class":3849},"    wave",[203,6815,4063],{"class":3698},[203,6817,6818],{"class":3849},"tasks",[203,6820,4063],{"class":3698},[203,6822,6823],{"class":4074},"map",[203,6825,4033],{"class":4077},[203,6827,4033],{"class":3698},[203,6829,6830],{"class":4036},"task",[203,6832,4052],{"class":3698},[203,6834,6835],{"class":3690}," =>",[203,6837,6838],{"class":4074}," executeTask",[203,6840,4033],{"class":4077},[203,6842,6830],{"class":3849},[203,6844,4005],{"class":3698},[203,6846,4178],{"class":3849},[203,6848,6849],{"class":4077},"))",[203,6851,4616],{"class":3698},[203,6853,6854],{"class":205,"line":328},[203,6855,6856],{"class":4077},"  )\n",[203,6858,6859],{"class":205,"line":333},[203,6860,216],{"emptyLinePlaceholder":215},[203,6862,6863,6865,6868,6870,6872,6874,6877],{"class":205,"line":339},[203,6864,6345],{"class":3690},[203,6866,6867],{"class":4257}," failures",[203,6869,4045],{"class":3708},[203,6871,4407],{"class":3849},[203,6873,4063],{"class":3698},[203,6875,6876],{"class":4074},"filter",[203,6878,4533],{"class":4077},[203,6880,6881,6884,6887,6889,6891,6894,6896,6899,6901,6903,6906,6908],{"class":205,"line":345},[203,6882,6883],{"class":3698},"    (",[203,6885,6886],{"class":4036},"r",[203,6888,4052],{"class":3698},[203,6890,6835],{"class":3690},[203,6892,6893],{"class":3849}," r",[203,6895,4063],{"class":3698},[203,6897,6898],{"class":3849},"status",[203,6900,4471],{"class":3708},[203,6902,5407],{"class":6133},[203,6904,6905],{"class":5294},"rejected",[203,6907,5401],{"class":6133},[203,6909,4616],{"class":3698},[203,6911,6912,6915,6918,6921],{"class":205,"line":351},[203,6913,6914],{"class":4077},"  ) ",[203,6916,6917],{"class":4191},"as",[203,6919,6920],{"class":3694}," PromiseRejectedResult",[203,6922,3850],{"class":4077},[203,6924,6925,6928,6930,6933,6935,6938,6940,6943,6945],{"class":205,"line":356},[203,6926,6927],{"class":4191},"  if",[203,6929,1575],{"class":4077},[203,6931,6932],{"class":3849},"failures",[203,6934,4063],{"class":3698},[203,6936,6937],{"class":4257},"length",[203,6939,4707],{"class":3708},[203,6941,6942],{"class":4048}," 0",[203,6944,4291],{"class":4077},[203,6946,296],{"class":3698},[203,6948,6949,6951,6954,6957,6959,6961,6964,6966,6968,6970],{"class":205,"line":362},[203,6950,4356],{"class":4191},[203,6952,6953],{"class":3698}," {",[203,6955,6956],{"class":4077}," status",[203,6958,1625],{"class":3698},[203,6960,5407],{"class":6133},[203,6962,6963],{"class":5294},"wave_failed",[203,6965,5401],{"class":6133},[203,6967,4005],{"class":3698},[203,6969,6867],{"class":3849},[203,6971,6972],{"class":3698}," }\n",[203,6974,6975],{"class":205,"line":368},[203,6976,3964],{"class":3698},[203,6978,6979],{"class":205,"line":374},[203,6980,216],{"emptyLinePlaceholder":215},[203,6982,6983],{"class":205,"line":379},[203,6984,6985],{"class":3675},"  \u002F\u002F Integration gate\n",[203,6987,6988,6990,6993,6995,6997,7000,7002,7005,7007,7010,7012,7014],{"class":205,"line":385},[203,6989,6345],{"class":3690},[203,6991,6992],{"class":4257}," gateResult",[203,6994,4045],{"class":3708},[203,6996,6799],{"class":4191},[203,6998,6999],{"class":4074}," runIntegrationGate",[203,7001,4033],{"class":4077},[203,7003,7004],{"class":3849},"wave",[203,7006,4063],{"class":3698},[203,7008,7009],{"class":3849},"gate",[203,7011,4005],{"class":3698},[203,7013,4178],{"class":3849},[203,7015,4181],{"class":4077},[203,7017,7018,7020,7022,7024,7027,7029,7032,7034],{"class":205,"line":391},[203,7019,6927],{"class":4191},[203,7021,1575],{"class":4077},[203,7023,4286],{"class":3708},[203,7025,7026],{"class":3849},"gateResult",[203,7028,4063],{"class":3698},[203,7030,7031],{"class":3849},"passed",[203,7033,4291],{"class":4077},[203,7035,296],{"class":3698},[203,7037,7038,7040,7043,7045,7047,7050,7052,7054,7056,7058],{"class":205,"line":397},[203,7039,4254],{"class":3690},[203,7041,7042],{"class":4257}," diagnosis",[203,7044,4045],{"class":3708},[203,7046,6799],{"class":4191},[203,7048,7049],{"class":4074}," diagnoseFailure",[203,7051,4033],{"class":4077},[203,7053,7026],{"class":3849},[203,7055,4005],{"class":3698},[203,7057,4178],{"class":3849},[203,7059,4181],{"class":4077},[203,7061,7062,7064,7067,7069,7071,7074,7076,7079,7081,7083],{"class":205,"line":403},[203,7063,4254],{"class":3690},[203,7065,7066],{"class":4257}," fixResult",[203,7068,4045],{"class":3708},[203,7070,6799],{"class":4191},[203,7072,7073],{"class":4074}," applyFix",[203,7075,4033],{"class":4077},[203,7077,7078],{"class":3849},"diagnosis",[203,7080,4005],{"class":3698},[203,7082,4178],{"class":3849},[203,7084,4181],{"class":4077},[203,7086,7087,7089,7091,7093,7096,7098,7101,7103],{"class":205,"line":409},[203,7088,4281],{"class":4191},[203,7090,1575],{"class":4077},[203,7092,4286],{"class":3708},[203,7094,7095],{"class":3849},"fixResult",[203,7097,4063],{"class":3698},[203,7099,7100],{"class":3849},"applied",[203,7102,4291],{"class":4077},[203,7104,296],{"class":3698},[203,7106,7107,7109,7111,7113,7115,7117,7120,7122,7124,7126,7128,7130],{"class":205,"line":415},[203,7108,4345],{"class":4191},[203,7110,6953],{"class":3698},[203,7112,6956],{"class":4077},[203,7114,1625],{"class":3698},[203,7116,5407],{"class":6133},[203,7118,7119],{"class":5294},"gate_failed",[203,7121,5401],{"class":6133},[203,7123,4005],{"class":3698},[203,7125,7042],{"class":3849},[203,7127,4005],{"class":3698},[203,7129,7066],{"class":3849},[203,7131,6972],{"class":3698},[203,7133,7134],{"class":205,"line":421},[203,7135,650],{"class":3698},[203,7137,7138],{"class":205,"line":427},[203,7139,3964],{"class":3698},[203,7141,7142],{"class":205,"line":433},[203,7143,216],{"emptyLinePlaceholder":215},[203,7145,7146,7148,7150,7152,7154,7156,7159,7161],{"class":205,"line":438},[203,7147,6397],{"class":4191},[203,7149,6953],{"class":3698},[203,7151,6956],{"class":4077},[203,7153,1625],{"class":3698},[203,7155,5407],{"class":6133},[203,7157,7158],{"class":5294},"wave_completed",[203,7160,5401],{"class":6133},[203,7162,6972],{"class":3698},[203,7164,7165],{"class":205,"line":444},[203,7166,656],{"class":3698},[102,7168,5743,7169,7172],{},[177,7170,7171],{},"Promise.allSettled"," call is deliberate. It lets every task in the wave run to completion, even if some fail. This gives the diagnostic loop the full picture before attempting recovery. A task that succeeds provides context for fixing a related failure.",[98,7174,7176],{"id":7175},"failure-handling-patterns","Failure Handling Patterns",[102,7178,7179],{},"The diagnose-then-fix loop is the most important pattern in the Gem-Team architecture. It separates the concerns of detection, diagnosis, and remediation into distinct steps, each handled by a specialist agent.",[102,7181,7182],{},"Detection happens at the integration gate. The gate runs automated checks -- build, lint, type-check, test -- and collects any failures into a structured error report. This report goes to gem-debugger, not back to the original task agent.",[102,7184,7185],{},"Diagnosis follows a structured protocol. gem-debugger receives the error report, reads the relevant source files, and produces a scored root cause analysis. The confidence score must reach a threshold of 0.7 before any fix is attempted.",[169,7187,7189],{"className":3666,"code":7188,"language":3668,"meta":175,"style":175},"interface Diagnosis {\n  rootCause: string\n  targetFiles: string[]\n  confidence: number \u002F\u002F 0.0 to 1.0, threshold 0.7\n  fixRecommendations: FixRecommendation[]\n}\n\ninterface FixRecommendation {\n  file: string\n  lineStart: number\n  lineEnd: number\n  suggestedChange: string\n  rationale: string\n}\n",[177,7190,7191,7200,7209,7220,7232,7244,7248,7252,7260,7269,7278,7287,7296,7305],{"__ignoreMap":175},[203,7192,7193,7195,7198],{"class":205,"line":206},[203,7194,3691],{"class":3690},[203,7196,7197],{"class":3694}," Diagnosis",[203,7199,3699],{"class":3698},[203,7201,7202,7205,7207],{"class":205,"line":212},[203,7203,7204],{"class":3704},"  rootCause",[203,7206,1625],{"class":3708},[203,7208,3722],{"class":3711},[203,7210,7211,7214,7216,7218],{"class":205,"line":14},[203,7212,7213],{"class":3704},"  targetFiles",[203,7215,1625],{"class":3708},[203,7217,3750],{"class":3711},[203,7219,3850],{"class":3849},[203,7221,7222,7225,7227,7229],{"class":205,"line":224},[203,7223,7224],{"class":3704},"  confidence",[203,7226,1625],{"class":3708},[203,7228,4042],{"class":3711},[203,7230,7231],{"class":3675}," \u002F\u002F 0.0 to 1.0, threshold 0.7\n",[203,7233,7234,7237,7239,7242],{"class":205,"line":229},[203,7235,7236],{"class":3704},"  fixRecommendations",[203,7238,1625],{"class":3708},[203,7240,7241],{"class":3694}," FixRecommendation",[203,7243,3850],{"class":3849},[203,7245,7246],{"class":205,"line":235},[203,7247,656],{"class":3698},[203,7249,7250],{"class":205,"line":241},[203,7251,216],{"emptyLinePlaceholder":215},[203,7253,7254,7256,7258],{"class":205,"line":247},[203,7255,3691],{"class":3690},[203,7257,7241],{"class":3694},[203,7259,3699],{"class":3698},[203,7261,7262,7265,7267],{"class":205,"line":253},[203,7263,7264],{"class":3704},"  file",[203,7266,1625],{"class":3708},[203,7268,3722],{"class":3711},[203,7270,7271,7274,7276],{"class":205,"line":259},[203,7272,7273],{"class":3704},"  lineStart",[203,7275,1625],{"class":3708},[203,7277,3712],{"class":3711},[203,7279,7280,7283,7285],{"class":205,"line":265},[203,7281,7282],{"class":3704},"  lineEnd",[203,7284,1625],{"class":3708},[203,7286,3712],{"class":3711},[203,7288,7289,7292,7294],{"class":205,"line":271},[203,7290,7291],{"class":3704},"  suggestedChange",[203,7293,1625],{"class":3708},[203,7295,3722],{"class":3711},[203,7297,7298,7301,7303],{"class":205,"line":27},[203,7299,7300],{"class":3704},"  rationale",[203,7302,1625],{"class":3708},[203,7304,3722],{"class":3711},[203,7306,7307],{"class":205,"line":282},[203,7308,656],{"class":3698},[102,7310,7311],{},"If confidence is below 0.7, the orchestrator escalates to the developer. This prevents the system from making destructive changes based on an uncertain diagnosis. I added this threshold after watching an agent \"fix\" a test failure by deleting the test file. It was technically correct -- the test passed after deletion -- but it was not the right answer.",[102,7313,7314],{},"Remediation applies the fix recommendation and re-runs the gate. If the gate passes, execution proceeds. If it fails again, the loop cycles with the new error context. The orchestrator limits the retry count to three iterations before escalating.",[98,7316,7318],{"id":7317},"green-flags-and-red-flags-for-agent-systems","Green Flags and Red Flags for Agent Systems",[102,7320,7321],{},"After building and maintaining the Gem-Team framework, I have developed a rubric for evaluating agent architectures. Here are the signals I look for.",[102,7323,7324],{},[130,7325,7326],{},"Green flags:",[124,7328,7329,7332,7335,7338,7341],{},[127,7330,7331],{},"Tasks are represented as a structured graph with explicit dependencies, not as steps in a prompt template.",[127,7333,7334],{},"The system has a state model that persists across execution waves. If the agent cannot answer \"what tasks are running, which completed, and what failed,\" it does not have real state.",[127,7336,7337],{},"Specialist agents are routed by the system, not chosen by the model. Routing decisions are deterministic rules, not LLM guesses.",[127,7339,7340],{},"Failure handling has a diagnostic phase that captures error context and source code before attempting a fix.",[127,7342,7343],{},"There are quality gates between execution stages, and those gates run real verification (build, test, lint), not LLM-based evaluation.",[102,7345,7346],{},[130,7347,7348],{},"Red flags:",[124,7350,7351,7354,7357,7360,7363],{},[127,7352,7353],{},"The system is a single script that calls an LLM multiple times and passes text between calls. No state, no graph, no gate.",[127,7355,7356],{},"The model decides which tools to call and in what order, with no system-level constraints on looping or termination.",[127,7358,7359],{},"There is no difference between \"the plan\" and \"the prompt.\" If the task list is embedded in a text string inside the system prompt, it is not a plan.",[127,7361,7362],{},"Error handling consists of \"retry with the same prompt\" or \"append 'please try again' to the context.\"",[127,7364,7365],{},"There is no record of what decisions the agent made or why. If a stakeholder asks \"why did the agent modify this file?\" and you cannot answer, the system is not production-grade.",[102,7367,7368],{},"I have been guilty of every red flag on this list. The Laravel chain hit all of them. Moving from red to green required rewriting the architecture, not improving the prompts.",[98,7370,7372],{"id":7371},"migration-path-from-prompt-chains","Migration Path from Prompt Chains",[102,7374,7375],{},"If you have a Tier 1 or Tier 2 system today, you do not need to throw it away. The migration path is incremental.",[102,7377,7378],{},"First, extract the state. Instead of passing text between steps, build an explicit state object that tracks which phase the system is in, what tasks have completed, and what artifacts each task produced. This is the smallest change that makes a difference. Once you have explicit state, you can add conditional branching -- skip steps when the input does not require them.",[102,7380,7381,7382,7385],{},"Second, add gates. After each step, run a verification check. For code generation tasks, check that the code compiles. For data tasks, validate the output against your schema. The gate does not need to be sophisticated. A shell command that runs ",[177,7383,7384],{},"tsc --noEmit"," is infinitely better than no gate at all.",[102,7387,7388],{},"Third, introduce specialist routing. Identify the distinct capabilities your system needs -- research, planning, implementation, review, testing -- and assign each to a dedicated prompt or agent definition. The orchestrator routes tasks based on type, not based on what the model thinks it should do next.",[102,7390,7391],{},"Fourth, replace the linear chain with a DAG. This is the hardest step because it requires thinking about parallelism and dependency ordering. But it is also the step that provides the most value. Once tasks can run in parallel, total execution time drops, and the system can handle larger, more complex goals.",[102,7393,7394],{},"The Gem-Team framework went through this exact migration. The earliest version was a prompt chain with four steps. Version 1.6.0 introduced specialist mobile agents. Version 1.20.0 added the orchestrator that routes to those agents using the DAG model. Each step made the system more reliable without completely discarding the previous work.",[98,7396,7398],{"id":7397},"the-consulting-signal","The Consulting Signal",[102,7400,7401],{},"I now refuse to build agent systems that use Tier 1 architectures for production workloads. When a client asks me to add \"AI features\" to their Laravel or Nuxt application, I start with the architecture conversation, not the prompt conversation. I ask how the system will handle failures, how it will maintain state across multi-step operations, and how we will know when it produces incorrect output. If the answer to all three is \"the LLM will figure it out,\" the project needs an architecture review before it needs a code review.",[102,7403,7404],{},"The consulting work I do around agent systems has shifted from prompt engineering to system design. The prompts matter, but they matter in the same way that function signatures matter in a distributed system -- they define the contract at each boundary, but they do not determine whether the system as a whole is reliable. Reliability comes from the orchestration layer: the state model, the dependency graph, the quality gates, and the failure recovery loops.",[102,7406,7407],{},"Production-grade AI agents are not prompts. They are systems. The sooner the industry internalizes this, the fewer demos we will see that work perfectly in isolation and fail catastrophically in production. I learned this by breaking my own application. You can learn it from my mistake instead of making your own.",[5863,7409],{},[102,7411,7412],{},[119,7413,5869,7414,7417],{},[4541,7415,5221],{"href":7416},"\u002Fblog\u002Fseries\u002Fai-systems-engineering"," series. Next: Observability patterns for multi-agent systems -- what to log, how to trace decisions, and how to debug failures across distributed agent workflows.",[2052,7419,7420],{},"html pre.shiki code .ss7Ak, html code.shiki .ss7Ak{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit;--shiki-sepia:#88846F;--shiki-sepia-font-style:inherit}html pre.shiki code .srJo8, html code.shiki .srJo8{--shiki-light:#9C3EDA;--shiki-light-font-style:inherit;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sKvfc, html code.shiki .sKvfc{--shiki-light:#E2931D;--shiki-light-text-decoration:inherit;--shiki-default:#6F42C1;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-text-decoration:underline}html pre.shiki code .sGXK2, html code.shiki .sGXK2{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .siCPE, html code.shiki .siCPE{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .sLACW, html code.shiki .sLACW{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .swvn1, html code.shiki .swvn1{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sIDdj, html code.shiki .sIDdj{--shiki-light:#E53935;--shiki-default:#E36209;--shiki-dark:#FFAB70;--shiki-sepia:#F8F8F2}html pre.shiki code .ss--_, html code.shiki .ss--_{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .s_MOj, html code.shiki .s_MOj{--shiki-light:#E2931D;--shiki-light-font-style:inherit;--shiki-default:#005CC5;--shiki-default-font-style:inherit;--shiki-dark:#79B8FF;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sD0ED, html code.shiki .sD0ED{--shiki-light:#6182B8;--shiki-default:#6F42C1;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html pre.shiki code .sQgqH, html code.shiki .sQgqH{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}html pre.shiki code .s91G_, html code.shiki .s91G_{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#F8F8F2}html pre.shiki code .squCx, html code.shiki .squCx{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sRxSC, html code.shiki .sRxSC{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#F92672;--shiki-sepia-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);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sTNss, html code.shiki .sTNss{--shiki-light:#9C3EDA;--shiki-default:#D73A49;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sYThS, html code.shiki .sYThS{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}",{"title":175,"searchDepth":212,"depth":212,"links":7422},[7423,7424,7425,7430,7435,7436,7437,7438],{"id":5926,"depth":212,"text":5920},{"id":5938,"depth":212,"text":5939},{"id":5972,"depth":212,"text":5973,"children":7426},[7427,7428,7429],{"id":6063,"depth":14,"text":6064},{"id":6073,"depth":14,"text":6074},{"id":6086,"depth":14,"text":6087},{"id":6099,"depth":212,"text":6100,"children":7431},[7432,7433,7434],{"id":6106,"depth":14,"text":6107},{"id":6474,"depth":14,"text":6475},{"id":6579,"depth":14,"text":6580},{"id":7175,"depth":212,"text":7176},{"id":7317,"depth":212,"text":7318},{"id":7371,"depth":212,"text":7372},{"id":7397,"depth":212,"text":7398},"2026-05-01","Most 2026 agent frameworks are still demo-level. Real production agents require orchestration layers, not prompt chains. Here's what separates toy agents from systems that ship.",{"readingTime":7442},"14 min read","\u002Fblog\u002F37-production-grade-ai-agents-not-prompts",{"title":5920,"description":7440},{"src":7446,"mime":2080,"alt":7447,"width":2082,"height":2083},"\u002Fimg\u002Fblog\u002F37-production-grade-ai-agents-not-prompts\u002Fbanner.svg","Production AI agent architecture - orchestration layer with DAG execution, routing, and specialist agents","blog\u002F37-production-grade-ai-agents-not-prompts",[2086,2087,5229,5230,5231,7450],"Orchestration","ChTL1Kg0m3W5SF1QunScU5dZ264Um9yx9loAIDDop7w",1782250477749]