AI Agent Stack in 2026: LangGraph vs Custom vs DIY A developer who ships AI agents weekly compares three approaches to building agent stacks in 2026: plain loops, LangGraph, and custom frameworks. For simple linear agents with fewer than five tools, a 150-line loop around an LLM call suffices for about 40% of shipped agents. LangGraph is preferred for complex branching, durable execution, and multi-agent handoffs, though it hides token accounting unless callbacks are explicitly wired. I ship agents into production most weeks, and the framework question keeps coming back in every kickoff call. The honest answer is that the "right" stack depends on how much branching, how much state, and how much blast radius you can tolerate. I've built the same agent three ways in the last year, and the trade-offs are sharper than the framework marketing lets on. Here is how I actually decide. When I start a new agent, I pick from three buckets. Everything else is a variation of these: while loop around an LLM call with tool execution and a message array. Roughly 150 lines of Python or TypeScript.The decision comes down to three questions: how many steps, how much concurrency, and what happens when it crashes at 3am. Everything else observability, evals, human-in-the-loop can be bolted onto any of the three. For any agent with fewer than about five tools and a linear think-act-observe loop, a plain loop wins. I use this for ~40% of the agents I ship, including most of the sub-agents inside BizFlowAI ContentStudio. Frameworks add abstraction cost that only pays back when you have real branching. Here is what the whole thing looks like, minus logging: python def run agent task: str, tools: dict, max steps: int = 12 : messages = {"role": "system", "content": SYSTEM PROMPT}, {"role": "user", "content": task}, for step in range max steps : resp = client.messages.create model="claude-sonnet-4-5", messages=messages, tools=list tools.values , max tokens=4096, messages.append {"role": "assistant", "content": resp.content} if resp.stop reason == "end turn": return resp.content -1 .text tool results = for block in resp.content: if block.type == "tool use": try: result = tools block.name