DEV Community

Samcorp
Samcorp

Posted on

Building an Agent That Actually Handles Failure

Building an Agent That Actually Handles Failure
The first version of our AI agent had a surprisingly simple error-handling strategy:

Something failed
      ↓
Retry
Enter fullscreen mode Exit fullscreen mode

It looked reasonable.

APIs fail.

Networks time out.

Models occasionally return malformed output.

Just retry the operation and continue.

Then we started thinking about what happens when the agent performs real actions.

Imagine the agent calls:

send_email()
create_ticket()
update_customer()
issue_refund()
schedule_meeting()
Enter fullscreen mode Exit fullscreen mode

It calls issue_refund().

The request times out.

What does that mean?

A. The refund never happened.

B. The refund happened, but the response was lost.

C. The refund is still processing.

D. The service received the request but failed midway.
Enter fullscreen mode Exit fullscreen mode

If the agent automatically retries, option B becomes particularly interesting.

We may have just issued the refund twice.

That was when AI agent error handling stopped looking like:

try:
    run_agent()
except:
    retry()
Enter fullscreen mode Exit fullscreen mode

and started looking much more like distributed systems engineering.

A production agent doesn't merely need to recognize failure.

It needs to answer a harder question:

What state is the world in after that failure, and what is the safest thing to do next?

That distinction changed how we designed the entire agent runtime.


The Happy-Path Agent Is Easy

Most agent demos follow roughly this loop:

User Goal
    ↓
LLM Reasons
    ↓
Select Tool
    ↓
Execute Tool
    ↓
Observe Result
    ↓
Continue Reasoning
    ↓
Final Answer
Enter fullscreen mode Exit fullscreen mode

For example:

User:
"Find the customer's unpaid invoice
and email them a reminder."

Agent:
1. Find customer
2. Query invoices
3. Find unpaid invoice
4. Generate reminder
5. Send email
6. Report success
Enter fullscreen mode Exit fullscreen mode

Beautiful.

Until step 2 returns 503.

Or step 3 returns malformed JSON.

Or step 5 succeeds but times out before acknowledging it.

Or the model invents an invalid email parameter.

Or the agent sends the message and then loses its state.

The real architecture is closer to:

                ┌── Success ──────────────┐
                │                         ↓
Agent → Tool → Result → Validate → Continue
                │
                ├── Timeout
                ├── Rate Limit
                ├── Invalid Input
                ├── Permission Denied
                ├── Partial Success
                ├── Unknown State
                └── Permanent Failure
Enter fullscreen mode Exit fullscreen mode

Those failures should not all produce the same response.

That was our first important design decision.


Failure Is Data

The worst tool interface for an agent looks like this:

{
  "success": false,
  "message": "Something went wrong"
}
Enter fullscreen mode Exit fullscreen mode

What exactly should the agent do with that?

Retry?

Change the parameters?

Use another tool?

Ask the user?

Stop?

The error contains almost no actionable information.

We moved toward treating failures as structured observations.

For example:

{
  "status": "error",
  "code": "RATE_LIMITED",
  "retryable": true,
  "retry_after_ms": 3000,
  "message": "API request limit exceeded"
}
Enter fullscreen mode Exit fullscreen mode

Compare that with:

{
  "status": "error",
  "code": "INVALID_EMAIL",
  "retryable": false,
  "field": "email",
  "message": "Email address is invalid"
}
Enter fullscreen mode Exit fullscreen mode

Now the runtime can make different decisions.

RATE_LIMITED
    ↓
Wait + Retry

INVALID_EMAIL
    ↓
Correct Input / Ask User
Enter fullscreen mode Exit fullscreen mode

This is the foundation of reliable agent recovery:

Classify before recovering.


Our Error Taxonomy

We eventually stopped thinking about “tool errors” as one category.

We divided failures into several classes.

1. Transient failures

Examples:

HTTP 429
HTTP 502
HTTP 503
connection reset
temporary DNS failure
Enter fullscreen mode Exit fullscreen mode

These may succeed later.

Possible strategy:

Retry
+
Exponential Backoff
+
Jitter
Enter fullscreen mode Exit fullscreen mode

2. Validation failures

Example:

{
  "tool": "create_invoice",
  "arguments": {
    "customer_id": "C123",
    "amount": "a lot"
  }
}
Enter fullscreen mode Exit fullscreen mode

The problem isn't availability.

The parameters are wrong.

Retrying the exact request five times won't help.

Instead:

Validation Error
      ↓
Return structured feedback
      ↓
Agent repairs arguments
      ↓
Validate again
      ↓
Execute
Enter fullscreen mode Exit fullscreen mode

3. Authorization failures

401 Unauthorized
403 Forbidden
Enter fullscreen mode Exit fullscreen mode

These should usually not trigger blind retries.

The agent may need to:

refresh credentials
request permission
use another authorized workflow
escalate
Enter fullscreen mode Exit fullscreen mode

4. Business-rule failures

Suppose the agent calls:

refund_order(order_42)
Enter fullscreen mode Exit fullscreen mode

and receives:

{
  "code": "REFUND_WINDOW_EXPIRED",
  "retryable": false
}
Enter fullscreen mode Exit fullscreen mode

The service is working perfectly.

The requested action is simply not allowed.

This isn't an infrastructure failure.

It's a domain outcome.


5. Ambiguous failures

These became the most dangerous category.

Request sent
    ↓
Remote operation starts
    ↓
Remote operation succeeds
    ↓
Response is lost
    ↓
Agent sees timeout
Enter fullscreen mode Exit fullscreen mode

From the agent's perspective:

UNKNOWN
Enter fullscreen mode Exit fullscreen mode

not:

FAILED
Enter fullscreen mode Exit fullscreen mode

That distinction is critical.


A Timeout Does Not Mean Failure

Consider:

result = payment_api.refund(
    transaction_id="TX-48291",
    amount=100
)
Enter fullscreen mode Exit fullscreen mode

Then:

TimeoutError
Enter fullscreen mode Exit fullscreen mode

The naive implementation does this:

retry()
Enter fullscreen mode Exit fullscreen mode

But the real timeline might be:

Agent                  Payment API

  │                         │
  │──── refund $100 ───────►│
  │                         │
  │                    Refund succeeds
  │                         │
  │◄──── response ──────────│
  │
  X network connection lost

Agent sees:
TIMEOUT
Enter fullscreen mode Exit fullscreen mode

The refund already happened.

Retrying could create another side effect if the downstream system doesn't deduplicate requests.

So we changed our mental model.

Timeout
   ≠
Failure

Timeout
   =
Outcome Unknown
Enter fullscreen mode Exit fullscreen mode

Now the runtime needs reconciliation.


Verify Before Retry

For state-changing operations, we want something closer to:

Tool Call
   ↓
Timeout
   ↓
Did operation actually happen?
   ↓
 ┌───────────────┐
 │               │
YES              NO
 │               │
 ↓               ↓
Continue       Retry Safely
Enter fullscreen mode Exit fullscreen mode

Pseudo-code:

try:
    result = execute_tool(action)

except TimeoutError:

    state = verify_action(action)

    if state == "COMPLETED":
        return recover_result(action)

    if state == "NOT_COMPLETED":
        return retry(action)

    return escalate_unknown_state(action)
Enter fullscreen mode Exit fullscreen mode

That single verification step can prevent a lot of dangerous behavior.


Idempotency Changed Everything

Verification isn't always possible.

Another powerful tool is idempotency.

Suppose the agent wants to create an invoice.

Instead of:

create_invoice(
    customer="C42",
    amount=1200
)
Enter fullscreen mode Exit fullscreen mode

we send:

create_invoice(
    customer="C42",
    amount=1200,
    idempotency_key="invoice:C42:order:991"
)
Enter fullscreen mode Exit fullscreen mode

The server records that key.

First request:

Key not seen
     ↓
Create invoice
     ↓
Store result
Enter fullscreen mode Exit fullscreen mode

Retry:

Key already exists
     ↓
Do NOT create another invoice
     ↓
Return previous result
Enter fullscreen mode Exit fullscreen mode

Conceptually:

                    ┌────────────────────┐
Agent ─────────────►│ Idempotency Store  │
                    └─────────┬──────────┘
                              │
                   Key already exists?
                         ↙           ↘
                       YES           NO
                        │             │
                  Return result    Execute
                                      │
                                 Save result
Enter fullscreen mode Exit fullscreen mode

Now retrying the intent doesn't necessarily repeat the effect.

This is especially important for tools that:

send
create
charge
refund
delete
publish
transfer
update
Enter fullscreen mode Exit fullscreen mode

Recent research into agent failures under non-atomic tool execution similarly found benefits from combining postcondition verification, verify-before-retry, and idempotency keys rather than assuming tool calls are atomic.


Not Every Failure Should Be Retried

Once we had structured errors, our retry logic became much simpler.

Something like:

RETRYABLE_ERRORS = {
    "RATE_LIMITED",
    "SERVICE_UNAVAILABLE",
    "CONNECTION_RESET",
    "TEMPORARY_FAILURE"
}

def should_retry(error):
    return error.code in RETRYABLE_ERRORS
Enter fullscreen mode Exit fullscreen mode

And explicitly:

NON_RETRYABLE_ERRORS = {
    "INVALID_ARGUMENT",
    "PERMISSION_DENIED",
    "RESOURCE_NOT_FOUND",
    "BUSINESS_RULE_VIOLATION"
}
Enter fullscreen mode Exit fullscreen mode

The important idea is:

Retry because the condition might change, not because an error occurred.

If the email address is invalid, waiting two seconds doesn't make it valid.


Retry Budgets Matter

Even retryable failures need limits.

Otherwise:

Tool fails
   ↓
Agent retries
   ↓
Tool fails
   ↓
Agent retries
   ↓
Tool fails
   ↓
Agent retries
   ↓
...
Enter fullscreen mode Exit fullscreen mode

Now one failed request can become:

high latency
+
more tokens
+
more API calls
+
higher cost
+
extra load on an already failing service
Enter fullscreen mode Exit fullscreen mode

We introduced retry budgets.

MAX_ATTEMPTS = 3
Enter fullscreen mode Exit fullscreen mode

with exponential backoff and jitter.

Conceptually:

Attempt 1
   ↓ fail

wait ~1s

Attempt 2
   ↓ fail

wait ~2s

Attempt 3
   ↓ fail

STOP / FALLBACK / ESCALATE
Enter fullscreen mode Exit fullscreen mode

The important word is stop.

An autonomous agent needs boundaries.


Don't Let Multiple Layers Retry

This one is easy to miss.

Imagine:

Agent retries 3 times
       ↓
Tool wrapper retries 3 times
       ↓
HTTP client retries 3 times
Enter fullscreen mode Exit fullscreen mode

One logical operation can now generate:

3 × 3 × 3 = 27
Enter fullscreen mode Exit fullscreen mode

attempts.

Exactly when the dependency is already unhealthy.

We decided that every failure path should have a clear retry owner.

Agent Runtime
      ↓
owns retry policy

Tool Wrapper
      ↓
reports structured failure

HTTP Client
      ↓
no hidden application-level retries
Enter fullscreen mode Exit fullscreen mode

The exact layer can vary.

The principle shouldn't:

Know who owns the retry.


Tool Schemas Are Part of Error Handling

An agent should not discover invalid inputs by crashing production APIs.

Tool inputs should be validated before execution.

For example:

class RefundRequest(BaseModel):
    order_id: str
    amount: float
    reason: str
Enter fullscreen mode Exit fullscreen mode

Then:

LLM proposes tool call
        ↓
Schema validation
    ↙          ↘
 INVALID       VALID
    ↓            ↓
Return error   Execute
to agent       tool
Enter fullscreen mode Exit fullscreen mode

If the model generates:

{
  "order_id": 42,
  "amount": -500,
  "reason": null
}
Enter fullscreen mode Exit fullscreen mode

we should catch it before reaching the payment system.

This is why tool design matters so much in agent architecture.

The LLM is not a trusted caller.

Treat it like an external client.


Give the Agent Errors It Can Reason About

This:

Exception: 0x8004FA32
Enter fullscreen mode Exit fullscreen mode

is useful to almost nobody.

This is much better:

{
  "code": "CUSTOMER_NOT_FOUND",
  "retryable": false,
  "message": "No customer exists with ID C-984.",
  "suggested_actions": [
    "search_customer_by_email",
    "ask_user_to_confirm_customer"
  ]
}
Enter fullscreen mode Exit fullscreen mode

Now recovery becomes part of the agent loop.

Action
  ↓
Failure
  ↓
Structured Observation
  ↓
Reason About Failure
  ↓
Choose Recovery
Enter fullscreen mode Exit fullscreen mode

This is one of the important differences between traditional exception handling and AI agent error handling.

The error isn't only for developers.

It may also become an observation consumed by the model.


But Don't Let the LLM Decide Everything

There is a tempting architecture:

Anything fails
     ↓
Tell LLM
     ↓
Let LLM decide
Enter fullscreen mode Exit fullscreen mode

We don't want that for every failure.

Some policies should be deterministic.

For example:

Maximum retry attempts
Payment limits
Permission checks
Timeout budgets
Allowed tools
Rate limits
Escalation thresholds
Enter fullscreen mode Exit fullscreen mode

Those belong in code.

Not in a prompt.

Our rule became:

LLM decides:
"What should I try next?"

Runtime decides:
"Am I allowed to try it?"
Enter fullscreen mode Exit fullscreen mode

That separation is extremely useful.


Circuit Breakers Protect the Agent Too

Suppose an external CRM API begins returning:

503
503
503
503
503
Enter fullscreen mode Exit fullscreen mode

Without protection, every active agent continues hitting it.

Instead:

Failures exceed threshold
        ↓
Open circuit
        ↓
Stop sending requests
        ↓
Use fallback / wait / escalate
        ↓
Probe later
        ↓
Close when healthy
Enter fullscreen mode Exit fullscreen mode

Conceptually:

CLOSED
  │
  │ failures exceed threshold
  ↓
OPEN
  │
  │ cooldown
  ↓
HALF-OPEN
  │
  ├── success → CLOSED
  │
  └── failure → OPEN
Enter fullscreen mode Exit fullscreen mode

Circuit breakers aren't uniquely “AI.”

That's exactly the point.

Agents interact with distributed systems.

They inherit distributed-system problems.


Partial Failure Is Harder Than Total Failure

Imagine an agent performing:

1. Create customer
2. Create invoice
3. Charge payment
4. Email receipt
Enter fullscreen mode Exit fullscreen mode

Step 1 succeeds.

Step 2 succeeds.

Step 3 fails.

What does “retry the workflow” mean?

If we restart at step 1:

duplicate customer?
duplicate invoice?
Enter fullscreen mode Exit fullscreen mode

So workflow state must be durable.

Something like:

{
  "workflow_id": "wf_123",
  "steps": {
    "create_customer": "completed",
    "create_invoice": "completed",
    "charge_payment": "failed",
    "send_receipt": "pending"
  }
}
Enter fullscreen mode Exit fullscreen mode

Now recovery can resume from the correct point.

Workflow Restart
      ↓
Load State
      ↓
Skip Completed Steps
      ↓
Resume From Failure
Enter fullscreen mode Exit fullscreen mode

This is much safer than asking the model to reconstruct everything from conversation history.


Sometimes You Need Compensation

Not every workflow can simply resume.

Imagine:

Reserve inventory
      ↓
Charge customer
      ↓
Create shipment
Enter fullscreen mode Exit fullscreen mode

Inventory reservation succeeds.

Payment succeeds.

Shipment creation permanently fails.

The system may need compensating actions:

Release inventory
Refund payment
Enter fullscreen mode Exit fullscreen mode

So instead of pretending the entire workflow is one transaction:

Step A
 ↓
Step B
 ↓
Step C fails
 ↓
Compensate B
 ↓
Compensate A
Enter fullscreen mode Exit fullscreen mode

This resembles Saga-style recovery in distributed systems.

The important part is that compensation is explicit.

The LLM should not invent rollback logic on the fly for financially or operationally important actions.


Give Every Workflow a State Machine

Agent loops can become difficult to reason about when state exists only inside messages.

We prefer explicit workflow states.

PLANNING
   ↓
EXECUTING
   ↓
WAITING_FOR_TOOL
   ↓
VERIFYING
   ↓
RECOVERING
   ↓
COMPLETED
Enter fullscreen mode Exit fullscreen mode

with terminal states such as:

FAILED
CANCELLED
ESCALATED
Enter fullscreen mode Exit fullscreen mode

Then transitions are controlled.

For example:

if state == "WAITING_FOR_TOOL":
    if tool_result.success:
        transition("EXECUTING")

    elif tool_result.outcome_unknown:
        transition("VERIFYING")

    elif tool_result.retryable:
        transition("RECOVERING")

    else:
        transition("ESCALATED")
Enter fullscreen mode Exit fullscreen mode

This makes the system much easier to debug.


Failure Handling Needs Memory

Suppose the agent already tried:

search_customer("John Smith")
Enter fullscreen mode Exit fullscreen mode

three times.

Every attempt failed because the CRM was unavailable.

If the agent doesn't retain execution state, it may reason:

Maybe I should search the customer.

Again.

Useful agent memory isn't only conversation memory.

It includes operational state:

tools attempted
arguments used
results
errors
retry count
side effects
verification results
remaining budget
Enter fullscreen mode Exit fullscreen mode

Think of it as:

Conversation Memory
+
Execution Memory
+
World State
Enter fullscreen mode Exit fullscreen mode

Those are different things.


Put a Budget Around the Entire Agent

Retry limits aren't enough.

Agents can fail by looping without technically repeating the same operation.

For example:

Search
 ↓
Reason
 ↓
Different Search
 ↓
Reason
 ↓
Another Search
 ↓
Reason
 ↓
...
Enter fullscreen mode Exit fullscreen mode

So we add global budgets.

AgentBudget(
    max_steps=15,
    max_tool_calls=10,
    max_retries=3,
    max_tokens=20_000,
    timeout_seconds=90
)
Enter fullscreen mode Exit fullscreen mode

Then:

Budget available?
   ↙       ↘
 YES       NO
  ↓         ↓
Continue  Stop safely
Enter fullscreen mode Exit fullscreen mode

Autonomy without a budget is just an unbounded loop with API credentials.


Fallbacks Should Be Designed Before Failure

Suppose the preferred search service is unavailable.

Possible fallback:

Primary Search
      ↓ failure
Secondary Search
Enter fullscreen mode Exit fullscreen mode

Model unavailable?

Primary Model
      ↓
Fallback Model
Enter fullscreen mode Exit fullscreen mode

Real-time data unavailable?

Live API
   ↓
Cached Data
   ↓
Tell user data may be stale
Enter fullscreen mode Exit fullscreen mode

But fallback behavior needs semantic awareness.

If a payment API fails, “try another payment API” may not make sense.

Fallbacks aren't interchangeable dependencies.

They are part of product behavior.


Human Escalation Is a Feature

There's a tendency to view human intervention as agent failure.

We don't.

Sometimes the safest agent behavior is:

I cannot determine whether this transaction
completed successfully.

I've stopped further actions and escalated
the workflow for review.
Enter fullscreen mode Exit fullscreen mode

That is a successful safety outcome.

We define escalation conditions such as:

unknown state after side effect
retry budget exhausted
conflicting tool results
insufficient permissions
high-value transaction
low-confidence irreversible action
policy ambiguity
Enter fullscreen mode Exit fullscreen mode

This creates an important boundary:

Agent Autonomy
      ↓
Safe Operating Envelope
      ↓
Human Review
Enter fullscreen mode Exit fullscreen mode

Agentic AI introduces precisely these broader questions around stability, reliability, planning failures, and human control. A useful overview of those architectural concerns is this discussion of Agentic AI fundamentals.

The objective isn't maximum autonomy.

It's appropriate autonomy.


Observability: Can You Reconstruct the Failure?

When somebody says:

“The agent did something weird yesterday.”

you need more than application logs.

For every execution, we want a trace resembling:

trace_id: agent_8f219

User Goal
   ↓
Model Decision
   ↓
Tool Selected
   ↓
Arguments
   ↓
Validation
   ↓
Tool Request
   ↓
Tool Response
   ↓
State Change
   ↓
Retry Decision
   ↓
Verification
   ↓
Next Action
Enter fullscreen mode Exit fullscreen mode

Useful fields include:

{
  "trace_id": "...",
  "workflow_id": "...",
  "step": 7,
  "model": "...",
  "prompt_version": "...",
  "tool": "create_invoice",
  "arguments_hash": "...",
  "idempotency_key": "...",
  "attempt": 2,
  "latency_ms": 1842,
  "result": "timeout",
  "verification": "completed",
  "next_action": "continue"
}
Enter fullscreen mode Exit fullscreen mode

Be careful about storing raw arguments when they contain secrets or personal information.

Observability shouldn't become a data leak.


We Started Measuring Recovery, Not Just Success

A basic agent dashboard might show:

Task Success Rate: 94%
Enter fullscreen mode Exit fullscreen mode

Useful.

But incomplete.

We also want:

Tool failure rate
Retry rate
Retry success rate
Unknown-outcome rate
Duplicate-action rate
Recovery success rate
Escalation rate
Average steps/task
Cost/task
Timeout rate
Circuit-breaker activations
Enter fullscreen mode Exit fullscreen mode

Then we can distinguish:

Agent completed because everything worked
Enter fullscreen mode Exit fullscreen mode

from:

Agent completed because recovery worked
Enter fullscreen mode Exit fullscreen mode

The second one tells us much more about production reliability.


Our Production Architecture Became Boring

And that's a compliment.

The first architecture looked like:

User
 ↓
LLM
 ↓
Tools
 ↓
Answer
Enter fullscreen mode Exit fullscreen mode

The production version looked more like:

                    User Goal
                        ↓
                ┌───────────────┐
                │ Agent Planner │
                └───────┬───────┘
                        ↓
                Policy / Budget
                        ↓
                Tool Selection
                        ↓
                Input Validation
                        ↓
                Idempotency Layer
                        ↓
                   Tool Call
                        ↓
                ┌───────────────┐
                │ Result Class. │
                └───────┬───────┘
                        ↓
         ┌──────────────┼──────────────┐
         │              │              │
      SUCCESS        RETRYABLE       UNKNOWN
         │              │              │
         ↓              ↓              ↓
      Continue       Backoff        Verify State
                        │              │
                        ↓              ↓
                      Retry      ┌─────┴─────┐
                                 │           │
                              Complete    Unknown
                                 │           │
                                 ↓           ↓
                              Continue    Escalate

                + Circuit Breakers
                + Durable State
                + Compensation
                + Observability
                + Human Review
Enter fullscreen mode Exit fullscreen mode

Most of that architecture isn't “AI magic.”

It's software engineering.

That's exactly why it works.


The Error-Handling Hierarchy

If I were building another production agent, I'd think about recovery in this order.

1. Prevent

Catch errors before execution.

schemas
permissions
input validation
policy checks
preconditions
Enter fullscreen mode Exit fullscreen mode

2. Detect

Know exactly what happened.

structured errors
timeouts
postconditions
health signals
Enter fullscreen mode Exit fullscreen mode

3. Classify

Determine the failure type.

transient
validation
authorization
business rule
unknown outcome
permanent
Enter fullscreen mode Exit fullscreen mode

4. Recover

Choose a bounded strategy.

repair
retry
fallback
resume
compensate
Enter fullscreen mode Exit fullscreen mode

5. Verify

Confirm the expected world state.

Did the email send?
Did the payment settle?
Did the record exist?
Enter fullscreen mode Exit fullscreen mode

6. Escalate

When certainty disappears:

stop
preserve state
explain
request human review
Enter fullscreen mode Exit fullscreen mode

7. Learn

Turn failures into regression tests.

Every meaningful production failure should eventually become:

incident
   ↓
test case
   ↓
evaluation
   ↓
release gate
Enter fullscreen mode Exit fullscreen mode

A Practical Agent Failure Test Suite

Happy-path testing is nowhere near enough.

Inject failures deliberately.

Tool failures

429
500
502
503
timeout
connection reset
Enter fullscreen mode Exit fullscreen mode

Model failures

malformed tool arguments
wrong tool
missing required field
unexpected text instead of JSON
Enter fullscreen mode Exit fullscreen mode

State failures

tool succeeds but response disappears
partial database write
delayed consistency
duplicate request
Enter fullscreen mode Exit fullscreen mode

Workflow failures

step 3 of 7 fails
dependency unavailable
budget exhausted
human approval never arrives
Enter fullscreen mode Exit fullscreen mode

Then ask:

Did we duplicate a side effect?

Did the workflow resume correctly?

Did retries stop?

Was state preserved?

Was the user told the truth?

Could an engineer reconstruct what happened?
Enter fullscreen mode Exit fullscreen mode

If those aren't part of the test suite, we haven't really tested agent reliability.


Reliability Is More Important Than Clever Recovery

It is easy to make the recovery loop increasingly sophisticated:

fail
 ↓
reflect
 ↓
replan
 ↓
self-correct
 ↓
try another strategy
Enter fullscreen mode Exit fullscreen mode

Sometimes that's useful.

But we shouldn't confuse cleverness with reliability.

For important operations, I would rather have:

3 predictable recovery paths
Enter fullscreen mode Exit fullscreen mode

than:

unlimited autonomous improvisation
Enter fullscreen mode Exit fullscreen mode

Enterprise AI systems also need the surrounding engineering disciplines—system integration, monitoring, governance, security controls, and post-deployment optimization—not just a capable model. Those broader considerations are covered in this overview of AI consulting and production AI architecture.

The model can reason.

The runtime should enforce boundaries.


The Most Important Question

Our original question was:

How do we make the agent succeed?

The more useful question became:

How do we make the agent fail safely?

Because production systems will fail.

Models fail.

APIs fail.

Networks fail.

Credentials expire.

Schemas change.

Databases become unavailable.

Users provide ambiguous instructions.

External systems return contradictory states.

No architecture eliminates all of that.

Good AI agent error handling assumes failure is normal and gives the system a controlled way through it.


Final Takeaway

A production-ready AI agent needs more than:

LLM
+
Tools
+
Prompt
Enter fullscreen mode Exit fullscreen mode

It needs:

LLM
+
Tools
+
Validation
+
Structured Errors
+
Timeouts
+
Safe Retries
+
Idempotency
+
Verification
+
Durable State
+
Circuit Breakers
+
Fallbacks
+
Compensation
+
Budgets
+
Observability
+
Human Escalation
Enter fullscreen mode Exit fullscreen mode

The most reliable agent isn't the one that never encounters an error.

That agent doesn't exist.

The reliable agent is the one that knows:

what failed, whether anything changed, whether retrying is safe, when to recover, and when to stop.

That's when an AI agent stops being an impressive demo and starts behaving like production software.

Top comments (0)