AI Agent Stack in 2026: LangGraph vs Custom vs DIY
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.
The three stacks I keep reaching for
When I start a new agent, I pick from three buckets. Everything else is a variation of these:
-
A DIY loop, meaning a plain
whileloop around an LLM call with tool execution and a message array. Roughly 150 lines of Python or TypeScript. - A framework, almost always LangGraph now, occasionally Pydantic AI or the OpenAI Agents SDK for narrow cases.
- Custom orchestration, meaning I write my own state machine, usually on top of a message queue or a durable executor like Temporal, Inngest, or AWS Step Functions.
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.
When a DIY loop is the correct answer
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:
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]<a href="**block.input">"fn"</a>
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": str(result),
})
except Exception as e:
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": f"ERROR: {e}",
"is_error": True,
})
messages.append({"role": "user", "content": tool_results})
raise RuntimeError("Agent exceeded max_steps")
Enter fullscreen mode Exit fullscreen mode
That is it. The advantages are real:
- You debug in your IDE. Stack traces point at your code, not at three layers of framework internals.
- Token usage is transparent. You can see and control every message that gets sent. When I cut LLM spend 40% on one project last quarter, most of it was pruning the message array in exactly this loop.
-
You own retries and error surfaces. Tool errors go back to the model as
is_error: trueso it can recover, and if the model loops on a broken tool, you catch it because you wrote the counter.
Where DIY breaks: parallel tool execution across long-running jobs, multi-agent handoffs, or anything with checkpointing that needs to survive a process restart. At that point you are either building a framework or reaching for one.
When LangGraph earns its complexity
LangGraph is the framework I keep coming back to in 2026, mostly because it treats agents as graphs of nodes with typed state, and that model actually matches how complex agents behave. I reach for it when the workflow has real branching (three or more meaningful paths), when I need durable execution across hours or days, or when I have multiple agents talking to each other.
The value shows up in three specific features:
-
Typed state as a first-class object. You define a
TypedDictor Pydantic model for state, and every node returns partial updates. This is worth more than it sounds because it forces you to think about state transitions instead of passing message arrays around. -
Checkpointing. With
SqliteSaverorPostgresSaver, the graph resumes from the last node if the process dies. For long-running research agents or human-in-the-loop workflows, this is non-negotiable. - Interrupts for human review. You can pause the graph at a node, wait for external input (a Slack approval, a form submission), and resume without re-running earlier nodes.
Where I get burned: LangGraph's abstraction over LLM calls hides token accounting unless you wire in callbacks explicitly. The docs move fast, and some patterns that worked six months ago are now discouraged. If you use it, pin your versions and write integration tests that snapshot the actual API calls being sent.
A rough rule of thumb from what I've shipped:
| Agent type | Nodes | Best fit |
|---|---|---|
| Single-purpose tool caller | 1-3 | DIY loop |
| Research + write + review | 4-8 | LangGraph |
| Multi-agent with handoffs | 8+ | LangGraph or custom |
| Long-running workflow (hours+) | any | Custom orchestration |
| Fan-out to 50+ parallel jobs | any | Custom orchestration |
When custom orchestration is worth the write
Custom orchestration means treating the agent as a distributed system: a durable executor (Temporal, Inngest, Step Functions, or a homegrown thing on SQS) drives the workflow, and each LLM call or tool execution is a task in that system. I use this when I need to survive machine failures, when a single "agent turn" might take an hour, or when I have to fan out to hundreds of parallel branches.
The serverless AWS + Zendesk integration I built for SLA compliance was custom orchestration. Not because it was an "agent" exactly, but because the same reasoning applies: EventBridge drove the workflow, Lambda functions were the tasks, and DynamoDB held the state. If a Lambda died, EventBridge retried. If a tool failed, the state machine knew where to resume. That pattern maps cleanly onto agentic workflows once you accept that an LLM call is just another idempotent task.
What custom orchestration buys you that a framework does not:
- True durability. LangGraph's checkpointer is fine for hours. Temporal or Step Functions is fine for months.
- Fan-out at scale. When I need an agent to review 500 documents in parallel and aggregate results, I want a real queue and worker pool, not a graph library trying to manage concurrency.
- Blast radius control. Each task runs in isolation. A poisoned tool response cannot corrupt the whole run because state is materialized between steps.
The cost is real. You write more code, you own the infrastructure, and you have to build your own visualization of what the agent did. For anything under an hour of runtime with fewer than 10 parallel branches, this is overkill. Above that, frameworks start to feel like toys.
The stack I actually pick, decision by decision
Here is the flowchart in my head when a new agent project starts:
- Is the workflow linear with fewer than five tools? DIY loop. Ship it in a week. Add structured logging, token counting, and a retry wrapper.
-
Does it have real branching, or need to pause for human input? LangGraph, with
PostgresSaverfor checkpointing and OpenTelemetry callbacks for observability. - Does a single run last hours, or fan out to hundreds of parallel branches, or need to survive infrastructure failures? Custom orchestration on Temporal or Step Functions. Treat each LLM call as an activity.
- Multi-agent with three or more distinct agents talking to each other? LangGraph if runs are short, custom if runs are long. Never DIY, because message routing between agents is where DIY loops turn into unmaintainable spaghetti.
I lean DIY more often than framework marketing suggests I should. Most agents in the wild do not need a graph. They need one good loop, careful prompt engineering, tight tool schemas, and honest evals. The framework becomes useful the moment the workflow diagram stops fitting on one screen.
The things nobody tells you until you ship
A few lessons that cost me real time:
Tool schemas matter more than model choice. I have seen the same task go from 60% to 95% success just by rewriting tool descriptions and parameter names. Before you switch models or add a framework, spend a day on your tool schemas. The model can only be as smart as your tools let it be.
Message array pruning is where the token savings live. In a 20-step agent, the message array grows quadratically in tokens if you keep all tool results. I keep the last 3-5 tool results verbatim and summarize the rest. On one content agent this cut cost by 40% with no measurable quality loss.
Error strings are prompts. When a tool fails, the error message goes back into the model's context and becomes part of the next prompt. "Database connection failed" is a useless prompt. "Database query returned no rows for user_id=42. Try a different user_id or check if the user exists." lets the model recover.
Checkpointing without idempotency is a lie. If your graph resumes from step 7 and step 7 called a payment API, you just charged the customer twice. Every tool that mutates state needs an idempotency key. This is true for any framework.
Evals are not optional. I run every agent I build against a fixed set of 20-50 test cases before every deploy. Not fancy LLM-as-judge stuff, just deterministic checks: did it call the right tools, did the final answer contain the required fields, did it stay under the token budget. This is the single highest-ROI engineering practice in agent work.
What I'd do if I were starting a new agent tomorrow
Start with the DIY loop. Get the tools, the system prompt, and the eval set right first. Instrument token usage and step count from day one. Ship it, watch it fail on real inputs, and fix it.
Only reach for LangGraph when the workflow diagram has real branches or when you need durable pause-and-resume. Only reach for custom orchestration when a single run is longer than an hour or fans out beyond what a single process should manage. Do not skip the DIY stage: it teaches you what your agent actually does, and that knowledge is what makes the framework choice obvious later.
The framework debate is mostly noise. The agents that ship and stay shipped are the ones with tight tool schemas, honest evals, and observability that lets you debug production failures in under 10 minutes. Everything else is scaffolding.
If you are building an agent right now and stuck between these stacks, I'm always happy to look at the workflow diagram and tell you what I'd pick. Reach out at lazar-milicevic.com/#contact, or read more on the blog where I've written about agent evaluation, context engineering, and cutting token spend in production.
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.