{"slug": "why-we-stopped-using-llm-agents-to-control-llm-agents-deterministic-multi-agent", "title": "Why We Stopped Using LLM Agents to Control LLM Agents (Deterministic Multi-Agent FSM)", "summary": "Parvej Shah, lead full-stack engineer at Minions.AI, detailed in a blog post why the company replaced an LLM orchestration agent with a deterministic finite state machine (FSM) for its automated content pipeline. The FSM, written in TypeScript, achieved a 99.2% automated completion rate and reduced inference cost variance from ±120% to ±8%, eliminating loop oscillations and context poisoning.", "body_md": "Originally published at[parvejshah.com/blog/deterministic-multi-agent-systems-production]by[Parvej Shah].\n\nThe standard architecture pattern for multi-agent systems right now is an orchestration agent: a central LLM that receives a goal, decides which specialized agents to invoke, passes messages between them, and decides when the task is complete.\n\nIn early 2025, we built this exact pattern for **Minions.AI**'s automated technical content pipeline. An orchestrator LLM coordinated a research agent, a draft writer, a critic agent, and a formatting specialist.\n\nIt worked in 70% of runs. In the other 30%, it failed in creative, unpredictable ways.\n\nThe failure modes weren't bugs in the traditional sense. The individual prompts were well-engineered. The tool definitions were clean. The failures came from the non-deterministic nature of the control plane.\n\n**Loop oscillation.** The critic agent would reject a draft for lacking specific technical details. The writer agent would add details, but slightly change the tone. The critic agent would then reject the new draft for tone issues, causing the writer to revert the details. The orchestrator would watch this tennis match loop until hitting the maximum turn limit.\n\n**Context poisoning.** As agents passed conversational turns back and forth, the shared context window accumulated conversational residue — conversational filler, apologies, retry explanations. By turn 6, 40% of the token budget was spent on orchestration chatter rather than the actual task domain.\n\n**Non-deterministic convergence.** The same topic with the same source signals would sometimes produce a crisp, 1,200-word technical deep dive in 4 turns, and other times produce a rambling 3,000-word overview in 14 turns costing 4x the inference budget.\n\nThe fix was conceptually simple: **remove all control flow decisions from LLMs and put them in typed code.**\n\nLLMs are exceptional at content transformation, extraction, synthesis, and evaluation against specific criteria. They are terrible at state machine transitions, termination detection, and error routing.\n\nWe redesigned the multi-agent pipeline as a formal Finite State Machine (FSM) written in TypeScript:\n\n```\ntype PipelineState =\n  | \"HARVEST_SIGNALS\"\n  | \"DRAFT_CONTENT\"\n  | \"CRITIC_REVIEW\"\n  | \"REVISE_DRAFT\"\n  | \"STAGE_CMS\"\n  | \"FAILED\";\n\ninterface PipelineContext {\n  topicId: string;\n  signals: IndustrySignal[];\n  draftMarkdown?: string;\n  critique?: CritiqueResult;\n  revisionCount: number;\n  maxRevisions: 2; // Strict deterministic limit\n}\n\nexport async function runContentPipeline(\n  ctx: PipelineContext\n): Promise<PipelineState> {\n  let state: PipelineState = \"HARVEST_SIGNALS\";\n\n  while (state !== \"STAGE_CMS\" && state !== \"FAILED\") {\n    switch (state) {\n      case \"HARVEST_SIGNALS\":\n        ctx.signals = await signalHarvesterAgent(ctx.topicId);\n        state = ctx.signals.length > 0 ? \"DRAFT_CONTENT\" : \"FAILED\";\n        break;\n\n      case \"DRAFT_CONTENT\":\n        ctx.draftMarkdown = await draftingAgent(ctx.signals);\n        state = \"CRITIC_REVIEW\";\n        break;\n\n      case \"CRITIC_REVIEW\":\n        ctx.critique = await criticAgent(ctx.draftMarkdown!);\n        if (ctx.critique.score >= 85) {\n          state = \"STAGE_CMS\";\n        } else if (ctx.revisionCount < ctx.maxRevisions) {\n          ctx.revisionCount++;\n          state = \"REVISE_DRAFT\";\n        } else {\n          // Hard exit: escalate to human editor rather than infinite loop\n          state = \"FAILED\";\n        }\n        break;\n\n      case \"REVISE_DRAFT\":\n        ctx.draftMarkdown = await revisionAgent(\n          ctx.draftMarkdown!,\n          ctx.critique!.actionableFixes\n        );\n        state = \"CRITIC_REVIEW\";\n        break;\n    }\n  }\n\n  return state;\n}\n```\n\nThe deterministic FSM pipeline has processed hundreds of scheduled content runs for Minions.AI with a **99.2% automated completion rate** and zero loop oscillations. Inference cost variance dropped from ±120% to ±8%.\n\n*Parvej Shah is a Lead Full-Stack Web Developer & Platform Architect based in Dhaka, Bangladesh. Explore full architecture case studies and production code at parvejshah.com.*", "url": "https://wpnews.pro/news/why-we-stopped-using-llm-agents-to-control-llm-agents-deterministic-multi-agent", "canonical_source": "https://dev.to/parvejshah/why-we-stopped-using-llm-agents-to-control-llm-agents-deterministic-multi-agent-fsm-4jpj", "published_at": "2026-08-26 20:20:06+00:00", "updated_at": "2026-08-26 20:50:20.646157+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools", "mlops"], "entities": ["Minions.AI", "Parvej Shah"], "alternates": {"html": "https://wpnews.pro/news/why-we-stopped-using-llm-agents-to-control-llm-agents-deterministic-multi-agent", "markdown": "https://wpnews.pro/news/why-we-stopped-using-llm-agents-to-control-llm-agents-deterministic-multi-agent.md", "text": "https://wpnews.pro/news/why-we-stopped-using-llm-agents-to-control-llm-agents-deterministic-multi-agent.txt", "jsonld": "https://wpnews.pro/news/why-we-stopped-using-llm-agents-to-control-llm-agents-deterministic-multi-agent.jsonld"}}