Infrastructure Patterns for Agentic Applications
Architecting Production-Ready AI Agents: Infrastructure Patterns
Many developers initially approach AI agent development as they would any standard web feature: they wrap a Large Language Model (LLM) in a route handler, process the incoming request, and wait for the model to return a response.
This works for simple chatbots. However, for complex agents, this leads to disaster. When agents begin calling tools, interacting with external APIs, branching into sub-tasks, or hitting rate limits, they often crash mid-sequence.
The Core Risk: If your agent's lifecycle is bound to a single HTTP request, your system's reliability is tethered to the wall-clock time of a non-deterministic loop.
This guide explores three essential patterns to transition from fragile scripts to resilient, scalable production systems.
The Fundamental Nature of Agents
At its heart, an agent is a continuous loop. We can represent the logic of this loop as:
This loop introduces several properties that are inherently hostile to traditional web architectures:
- Extended Lifespans: Agents aren't always fast; some may take hours or days to reach a conclusion.
- Complex State: The "state" isn't just a user ID; it's a combination of the goal, the current plan, tool outputs, error logs, and intermediate decisions.
- Persistence Requirements: If a process crashes, the system must know exactly where it left off to avoid starting from scratch.
- Dynamic Execution: Unlike traditional code, the model decides the next step at runtime.
- Unpredictable Resource Use: Agents may fan out into parallel tasks, wait for human intervention, or generate massive artifacts.
Agent Logic vs. Traditional Logic
| Feature | Traditional Workflow | Agentic Workflow |
|---|---|---|
| Pathing | Pre-defined (If/Else) | Dynamic (Model-driven) |
| Recovery | Simple retry of the function | Complex state replay/recovery |
| Execution | Linear or predictable DAG | Non-deterministic loop |
Pattern 1: The Web-Queue-Worker Architecture
To solve the "timeout" problem, production systems decouple the request from the execution. Instead of running the agent inside the web request, the API should:
- Create a durable record of the "run."
- Enqueue the task.
- Return an immediate response to the user.
The user then monitors progress via status polling, WebSockets, or a callback webhook.
The Role of the Queue: It acts as a durable buffer, providing worker scaling, burst absorption, and basic retry logic.
The Limitation: While a queue knows a job exists, it is "blind" to the logical flow. It cannot natively manage a sequence of twenty dependent steps, complex branching, or pauses for human approval.
Ensuring Reliability: Idempotency and Compensation
Moving to a queue introduces a new problem: at-least-once delivery. This means a job might be executed more than once. To prevent this from causing chaos (e.g., charging a customer twice), you need two strategies:
1. Idempotency (Handling Duplicate Steps)
Idempotency ensures that performing an operation multiple times has the same effect as performing it once.
Bad Code: Assumes the worker never crashes between steps.
Good Code: Implements an idempotency boundary.
For every tool call, the agent should follow this logic:
def execute_tool_step(step_id, tool_func, args):
# 1. Check if this specific step was already completed
if db.check_completed(step_id):
return db.get_result(step_id)
# 2. Mark as 'running' to prevent concurrent duplicates
db.upsert_status(step_id, "RUNNING")
# 3. Execute the actual side-effect
result = tool_func(args)
# 4. Mark as complete and store result
db.mark_complete(step_id, result)
return result
2. Compensation (Handling Partial Failures)
What happens when an agent completes 3 out of 5 steps and then fails permanently? If the agent charged a credit card and provisioned a server, you cannot simply "roll back" a database transaction.
This is solved via the Saga Pattern: for every action, define a corresponding compensating action to reverse the effect.
Compensation Requirements:
- Must be idempotent.
- Must have a bounded retry limit.
- Must have a "dead-letter" path for failures.
- Must provide a manual escape hatch for human intervention.
Note: Compensation is not always the answer. If an agent is processing five parallel orders and one fails, it is often better to accept the four successes rather than undoing everything.
Pattern 2: Workflow Engines
Once you have solved the "where does this run" problem with queues, you face the "how do I track this" problem. This is where Workflow Engines replace simple workers.
A workflow engine does not just execute a job; it manages the entire lifecycle of the run. It tracks:
- Which steps started, finished, or failed.
- Which steps are currently timing out.
- Where the process is waiting for external events (e.g., a human clicking "Approve").
- The exact state required to replay a failed run from the point of failure.
By moving to a workflow-based architecture, you shift the burden of state management and reliability from your application code to the infrastructure layer.