AI Agent Architecture: Patterns That Actually Work in Production
Agents fail at the seams, not the steps.
Building an AI agent that works in a demo is straightforward. Building one that works reliably for hundreds of users across thousands of tasks is a different problem entirely.
After spending significant time building and debugging agent systems, the patterns below are the ones I keep returning to. Not because they're theoretically elegant, but because they reduce the failure modes that actually kill production agents.
The core problem: agents fail at the seams
Individual steps in an agent pipeline tend to be easy to get right. The LLM call works. The tool call works. The retrieval works. What breaks is the handoff between them.
- The step before produced output in an unexpected format
- The agent looped on ambiguous state without a termination condition
- A tool call failed silently and the agent continued with a false assumption
- The user's intent was captured at step 1 but drifted by step 5
Good agent architecture is mostly about designing seams: how steps communicate, how errors propagate, how state is represented, and how you recover when something unexpected happens.
Pattern 1: Explicit state machine over implicit reasoning
The most common agent mistake is letting the model figure out "where we are" at each step from the conversation history. This works until it doesn't — and it fails in subtle ways that are hard to debug.
A better approach: model the agent as an explicit state machine. Define the states, transitions, and what data lives at each state.
States: GATHERING_INFO → PLANNING → EXECUTING → VERIFYING → DONE | FAILED
Transitions: explicit conditions on structured output from each step
This makes behavior predictable and debuggable. When an agent fails, you know exactly which state transition broke and what data was in scope.
The tradeoff: more upfront design work. Worth it for any agent handling diverse inputs in production.
Pattern 2: Structured output at every boundary
Raw text between agent steps is a reliability trap. One prompt change, one unexpected input, and your string parsing breaks.
Use structured output (JSON mode or function calling) at every step boundary where you need to extract information for the next step:
{
"intent": "search_product",
"parameters": { "query": "noise canceling headphones", "budget_max": 300 },
"confidence": "high",
"clarification_needed": false
}
Explicit schema forces the model to commit to a structure, and lets you validate output before passing it downstream. Low confidence or missing required fields are caught at the boundary, not propagated as silent errors.
Pattern 3: Skeptical execution — verify before acting
For any agent that takes actions with real-world consequences (database writes, API calls, file changes, emails), add a verification step before execution.
The pattern:
- Plan step: model produces a structured action plan
- Verify step: separate prompt (or rule-based check) validates: is this plan safe? Does it match the user's stated intent? Are there destructive operations?
- Execute step: only runs if verification passes
This adds latency but catches the class of failures where the model misunderstood the instruction or planned an action broader than intended. For consequential operations, the latency is worth it.
Pattern 4: Bounded retry with fallback, not infinite retry
When a step fails, the instinct is to retry. But unbounded retry with the same input produces the same failure. Design retry logic with escalating variation:
- Retry 1: identical input (transient errors)
- Retry 2: simplified/decomposed input (complexity errors)
- Retry 3: route to fallback (human, simpler heuristic, or explicit "cannot complete")
Never retry more than 3 times without changing something. Define the fallback before you ship. "Error — please try again" is a product failure.
Pattern 5: Tools are contracts, not suggestions
Tool definitions (function calling specs) are the contract between the agent and the outside world. Treat them like API contracts:
- Describe preconditions: what state must be true for this tool to succeed
- Describe postconditions: what the tool guarantees on success
- Describe failure modes: what error types it can return and why
- Type everything: input and output schemas with precise types and constraints
Vague tool descriptions produce vague tool usage. The model uses the description to decide when and how to call the tool — a sloppy description means sloppy usage.
Pattern 6: Short context windows with retrievable memory
Long conversation context is a reliability problem. As context grows:
- The model loses track of early constraints
- Latency increases
- Costs compound
- The model starts "forgetting" instructions
The better architecture: keep the active context small and explicit. Move historical context into retrievable memory that gets injected only when relevant.
Practical format for memory:
[CONTEXT WINDOW]
- Current task: {current_task}
- User preferences: {relevant_preferences}
- Last N actions: {recent_actions}
- Retrieved: {retrieval_results_for_current_step}
What NOT to include: full conversation history, old reasoning traces, resolved questions.
Pattern 7: Observability as a first-class design concern
You will not understand why your agent fails unless you instrument it. Before shipping any agent:
- Assign a unique trace ID to each agent run
- Log every step: input, output, tool calls, latency, cost
- Log state transitions
- Capture failures with full context
Then build a way to replay any failed run with the same inputs. Replay is the superpower of agent debugging.
The agents that work in production aren't the most sophisticated — they're the most predictable. Explicit state, structured boundaries, bounded retries, and good observability beat clever reasoning every time.
If you're designing an agent system and want a systematic review, the AI Production Reliability Checklist has a dedicated agent reliability section.
Building in public
Follow the journey
as I build AI tools, products, and a serious founder life.
No spam. Unsubscribe anytime.