mech.app

The mech.app newsletter

Agentic AI, minus the noise.

Get practical field notes on AI agents, automation, developer tools and security delivered to your inbox.

No spam. Unsubscribe anytime.

AI Agents

Building Agentic Workflows That Don't Fall Apart in Production: What I've Learned the Hard Way

Production reliability patterns for multi-step agent workflows: state checkpoints, timeout strategies, graceful degradation, and observability primitives.

Source: dev.to
Building Agentic Workflows That Don't Fall Apart in Production: What I've Learned the Hard Way

The gap between an agent that works in a demo and one that survives production is wider than most teams expect. RAG systems have bounded failure modes: retrieve context, generate response, done. Agents have unbounded failure potential. They take actions, update records, trigger processes, and spawn other agents. When they fail, the blast radius extends beyond a single user interaction.

This is not a tutorial on building agents. This is a catalog of the specific things that break in production and the architectural decisions that prevent or mitigate those failures.

Why Agents Fail Differently

A retrieval error produces a wrong answer. An agent error produces a wrong action. Wrong actions in enterprise systems often have consequences that are difficult or impossible to reverse.

The difference:

  • RAG failure: User gets incorrect information, loses trust in one answer
  • Agent failure: System sends 500 emails, deletes production records, or triggers a cascade of downstream processes

This fundamental difference in failure surface requires a different set of architectural precautions. Most RAG-first teams are surprised by the problems they encounter when they add action capabilities.

The State Management Problem

The most common production failure is state loss. The agent loses track of where it is in a multi-step task and either starts over, gets stuck, or makes decisions based on an incorrect model of what has already happened.

State Checkpoint Architecture

You need explicit checkpoints between workflow steps. Not logs. Not traces. Durable state records that survive process restarts.

class WorkflowState:
    def __init__(self, workflow_id: str, store: StateStore):
        self.workflow_id = workflow_id
        self.store = store
        self.state = self.store.load(workflow_id) or {}
    
    def checkpoint(self, step: str, data: dict):
        self.state[step] = {
            "completed_at": datetime.utcnow().isoformat(),
            "data": data,
            "status": "completed"
        }
        self.store.save(self.workflow_id, self.state)
    
    def get_last_completed_step(self) -> str:
        completed = [
            step for step, meta in self.state.items()
            if meta.get("status") == "completed"
        ]
        return completed[-1] if completed else None
    
    def resume_from_checkpoint(self) -> dict:
        last_step = self.get_last_completed_step()
        if last_step:
            return self.state[last_step]["data"]
        return {}

The state store can be Redis, DynamoDB, or Postgres. The key requirement is atomic writes and the ability to query by workflow ID. You need to be able to answer “what was the last successfully completed step?” without replaying logs.

Idempotency Keys

Every action an agent takes needs an idempotency key. If the agent crashes after sending an API request but before recording the result, it will retry that request on restart. Without idempotency, you get duplicate actions.

def execute_action(action_id: str, action_fn, *args):
    result = result_cache.get(action_id)
    if result:
        return result
    
    result = action_fn(*args)
    result_cache.set(action_id, result, ttl=86400)
    return result

The action ID should be deterministic based on workflow ID and step number. Not a random UUID. You need to be able to reconstruct the same action ID on retry.

Timeout and Retry Strategies

Agents call external APIs with unpredictable latency. LLM inference can take 30 seconds. Database queries can hang. Third-party APIs can go down.

Timeout Hierarchy

LayerTimeoutRetry StrategyFailure Action
LLM inference60s3 attempts, exponential backoffDegrade to simpler model or cached response
Tool API call30s2 attempts, linear backoffSkip tool, mark step as degraded
Database query10s1 attemptFail fast, checkpoint before retry
Workflow step5 minutesResume from last checkpointAlert operator, enter manual review

The key insight: timeouts cascade. If your workflow step timeout is 5 minutes but your LLM timeout is 60 seconds with 3 retries, you can hit 3 minutes of LLM time alone. Budget your timeouts from the bottom up.

Circuit Breaker Pattern

When a tool fails repeatedly, stop calling it. Track failure rates per tool and open a circuit breaker after a threshold.

class CircuitBreaker:
    def __init__(self, failure_threshold: int = 5, timeout: int = 60):
        self.failure_count = 0
        self.failure_threshold = failure_threshold
        self.timeout = timeout
        self.opened_at = None
    
    def call(self, fn, *args):
        if self.is_open():
            raise CircuitOpenError("Circuit breaker is open")
        
        try:
            result = fn(*args)
            self.on_success()
            return result
        except Exception as e:
            self.on_failure()
            raise e
    
    def is_open(self) -> bool:
        if self.opened_at is None:
            return False
        elapsed = time.time() - self.opened_at
        if elapsed > self.timeout:
            self.reset()
            return False
        return True
    
    def on_failure(self):
        self.failure_count += 1
        if self.failure_count >= self.failure_threshold:
            self.opened_at = time.time()
    
    def on_success(self):
        self.failure_count = 0
    
    def reset(self):
        self.failure_count = 0
        self.opened_at = None

When the circuit is open, the agent needs a fallback path. This is where graceful degradation comes in.

Graceful Degradation

When a critical tool becomes unavailable, the agent has three options:

  1. Fail the workflow: Stop execution, alert operator
  2. Degrade functionality: Complete the workflow with reduced capability
  3. Queue for retry: Park the workflow and retry later

The choice depends on the business impact of partial completion versus delay.

Degradation Decision Matrix

  • Critical path, no fallback: Fail immediately (example: payment processing)
  • Critical path, fallback available: Degrade to fallback (example: use cached data instead of live API)
  • Non-critical path: Skip step, mark as degraded (example: optional notification)
  • Retriable: Queue for retry (example: data sync that can happen later)

Implement this as a tool registry with degradation policies:

tool_registry = {
    "payment_api": {
        "critical": True,
        "fallback": None,
        "degradation_policy": "fail"
    },
    "user_data_api": {
        "critical": True,
        "fallback": "cached_user_data",
        "degradation_policy": "degrade"
    },
    "notification_service": {
        "critical": False,
        "fallback": None,
        "degradation_policy": "skip"
    }
}

Observability Primitives

You cannot debug agent failures without structured observability. Logs are not enough. You need traces that show the full execution path, spans that measure timing, and structured events that capture state transitions.

Essential Observability Data

  1. Workflow trace: Every workflow execution gets a trace ID that follows it through all steps
  2. Step spans: Each step is a span with start time, end time, status, and metadata
  3. Tool call events: Every tool invocation is an event with input, output, latency, and error
  4. State transition events: Every checkpoint is an event with before/after state
  5. Degradation events: Every fallback or skip is an event with reason and impact

Use OpenTelemetry. Do not build your own tracing system.

from opentelemetry import trace

tracer = trace.get_tracer(__name__)

def execute_workflow(workflow_id: str):
    with tracer.start_as_current_span(
        "workflow.execute",
        attributes={"workflow.id": workflow_id}
    ) as span:
        state = WorkflowState(workflow_id, state_store)
        
        for step in workflow_steps:
            with tracer.start_as_current_span(
                f"workflow.step.{step.name}",
                attributes={"step.name": step.name}
            ) as step_span:
                try:
                    result = step.execute(state)
                    state.checkpoint(step.name, result)
                    step_span.set_status(trace.Status(trace.StatusCode.OK))
                except Exception as e:
                    step_span.set_status(
                        trace.Status(trace.StatusCode.ERROR, str(e))
                    )
                    step_span.record_exception(e)
                    raise

Ship traces to Jaeger, Tempo, or Honeycomb. You need to be able to query by workflow ID and see the full execution timeline.

Deployment Shape

Agents need different infrastructure than stateless APIs.

Infrastructure Requirements

  • State store: Redis or DynamoDB for workflow state
  • Message queue: SQS or RabbitMQ for async task execution
  • Worker pool: Horizontal scaling for parallel workflow execution
  • Cron scheduler: For retry and cleanup jobs
  • Trace collector: OpenTelemetry collector for observability

Do not run agents in serverless functions unless you have a robust external state store and can handle cold start latency. The state management overhead makes serverless a poor fit for most agentic workflows.

Scaling Considerations

Agents do not scale like web APIs. Each workflow execution is long-lived and stateful. You scale by adding workers, not by increasing request throughput.

Monitor:

  • Active workflow count
  • Average workflow duration
  • Worker utilization
  • State store latency
  • Tool API error rates

Scale workers when utilization exceeds 70%. Do not wait for queues to back up.

Failure Modes You Will Encounter

  1. Partial execution: Agent completes 3 of 5 steps, crashes, restarts from step 1
  2. Duplicate actions: Agent sends the same email twice because it did not record the first send
  3. State corruption: Two workers update the same workflow state simultaneously
  4. Timeout cascade: LLM timeout triggers workflow timeout triggers orchestrator timeout
  5. Tool unavailability: Critical API goes down mid-workflow
  6. Rate limiting: Agent hits API rate limit, fails all subsequent requests
  7. Memory leak: Long-running workflow accumulates state in memory, crashes worker

All of these are preventable with the patterns described above. None of them are obvious during development.

Technical Verdict

Use this approach when:

  • You are building multi-step workflows that take actions in external systems
  • You need to survive process restarts without losing progress
  • You need to debug failures that happened hours or days ago
  • You are deploying to production and need operational confidence

Avoid this approach when:

  • You are building a simple RAG system with no actions
  • Your workflows are stateless and complete in under 10 seconds
  • You are prototyping and do not need production reliability yet

The overhead of state management, checkpointing, and observability is not worth it for simple agents. But if you are building workflows that matter, you will encounter every failure mode described here. Build the guardrails early.

Tags

agentic-ai orchestration infrastructure

Primary Source

dev.to