Back to Blog
AI agents in production: the boring parts that decide whether they work
Agentic AI
11 min read
38 views

AI agents in production: the boring parts that decide whether they work

Abhijith N M's avatar
Abhijith N M

October 24, 2026

The first agent demo is deceptively easy. You give a model a few tools, ask it to book a meeting or update a record, and it works. Then you put it in front of users and discover that the hard part was never the prompt. It is everything around the model: what happens when a tool times out after doing the work, what stops a loop from spending forty dollars, and how you prove the agent is still correct after a model upgrade.

None of that makes a good conference talk. All of it decides whether the feature survives its second week.

State is the first design decision

A stateless request/response endpoint is easy to retry. An agent is a sequence of decisions, so it needs a place to record what happened. At minimum, store a run record with the input, the plan or messages so far, every tool call and its result, and the final output. If the process crashes, you want to resume from the last completed step instead of starting over.

Do not put that state only in the model's context window. Context is a working set, not a database. A durable run record also gives you an audit trail when a user asks why the agent sent an email.

🐍Python
# A minimal run record. The exact store does not matter;
# the fields do.
run = {
    "id": run_id,
    "user_id": user_id,
    "status": "running",          # running | waiting | done | failed
    "step": 3,
    "messages": messages,          # append-only
    "budget": {"tokens_left": 40_000, "tool_calls_left": 12},
}

Tool calls are network calls

Every tool is an HTTP request, a database write, or a payment. Those fail in ways a function call does not: they time out after the side effect, return a stale success, or succeed twice after a retry. Treat every tool as unreliable and every mutation as something that needs an idempotency key.

The pattern that works is boring. Generate a key per logical action, pass it to the tool, and have the tool reject duplicates. Then retries are safe, and the agent can recover from a timeout without booking the same meeting twice.

🐍Python
def create_ticket(idempotency_key: str, payload: dict):
    existing = db.get_by_key(idempotency_key)
    if existing:
        return existing
    ticket = tickets.insert(payload, key=idempotency_key)
    return ticket

If a tool can change something a user cares about, it needs an idempotency key, a timeout, and a dry-run path.

Permissions and blast radius

An agent with a database tool and a broad service account is a remote code execution bug with better manners. Give each tool the narrowest credential that can do its job. Prefer read-only tools by default. Put human approval in front of irreversible actions: sending email, moving money, deleting data, changing permissions.

Also decide what the agent cannot see. Retrieval permissions should be applied before documents reach the model, not after. A prompt that says 'do not reveal other users' data' is not an access control.

Evals, not vibes

You cannot ship a prompt change every day without a way to tell whether it made things worse. Keep a small set of representative tasks with expected outcomes. Run them on every prompt or model change. Start with twenty cases; twenty honest cases beat a dashboard nobody trusts.

Use three layers. Unit-style checks for deterministic tool routing. End-to-end evals for task success. A sampled review of real runs for tone and edge cases. Log the model, prompt version and tool versions with every run so a regression is attributable.

Cost is a product constraint

Agents are loops, and loops can spend. Set a per-run token budget and a maximum number of steps. When the budget is exhausted, stop and return a partial result rather than letting the agent negotiate with itself. Track cost per successful task, not cost per request. A cheap run that fails and gets retried is expensive.

  • Cap steps and tokens per run; fail closed when the cap is hit.
  • Cache deterministic tool results where the data allows it.
  • Route simple tasks to a smaller model and escalate only when needed.
  • Alert on cost per successful task, not just total spend.

A production checklist

  1. Every run has a durable record and a resumable step.
  2. Every mutating tool has an idempotency key and a timeout.
  3. Every tool has the narrowest possible credential.
  4. Irreversible actions require human approval.
  5. A representative eval set runs on every prompt or model change.
  6. Per-run token and step budgets are enforced server-side.
  7. Traces show each model call, tool call, latency and cost.

The model will keep changing. The scaffolding around it is what you actually own, and it is what makes the difference between a clever demo and a product people can rely on.

What to remember

  • Persist run state outside the context window so a crash does not lose work.
  • Treat tools as unreliable network calls with idempotency, timeouts and least-privilege credentials.
  • Gate irreversible actions behind human approval.
  • Use a small, honest eval set and rerun it on every model or prompt change.
  • Enforce budgets server-side and measure cost per successful task.
Share

About the Author

Abhijith N M's avatar
Abhijith N M

Founder, writer and editor

I'm a software developer based in India. I write about AI engineering, system design and the parts of software that tutorials skip. I started nexGenCoders to put the explanations, diagrams and working examples I wanted while learning in one place.

Comments (0)

Loading comments...