Reliable AI Starts Where the Demo Ends
An AI demo asks one question:
Can the model produce a useful answer?
A real product has harder questions:
- What happens when the model times out?
- What happens when a tool returns bad data?
- Can the same job run twice without creating two results?
- How do we know that a new prompt is better than the old one?
- Can we explain a slow, expensive, or unsafe request?
The interesting engineering work starts after the first successful model call.
1. Treat the workflow as a state machine
An AI workflow should have clear states. For example:
received -> processing -> succeeded
\-> retryable failure -> processing
\-> permanent failure
The state belongs in durable storage. This makes the workflow visible to the worker, the user, and the person operating the system.
It also gives us a safe place to record why a job stopped. “The AI failed” is not useful enough. We want to know whether the failure came from a timeout, a bad input, a tool error, a rate limit, or an invalid model response.
2. Retries need boundaries
Retries are useful, but an unbounded retry loop can turn one failure into a larger outage.
A safe retry path needs:
- a timeout for each external call;
- a maximum number of attempts;
- backoff between attempts;
- an idempotency key for the job;
- a dead-letter or manual-review path;
- a record of every attempt.
The idempotency key matters because queues can deliver the same message more than once. Repeating a request should not publish two videos, charge a user twice, or create two matching sessions.
3. Evaluate behavior before release
“It worked for my example” is not an evaluation set.
Start with a small versioned set of real-looking cases. Include normal inputs, empty inputs, confusing inputs, unsafe requests, and cases where the correct answer is to ask for help or refuse.
Track the behavior that matters for the product:
- correct output;
- safe output;
- valid structure;
- correct tool choice;
- latency;
- estimated cost;
- useful failure behavior.
The goal is not to pretend that one score explains quality. The goal is to make a change easy to compare with the previous version. A prompt, model, or retrieval change should leave evidence behind.
4. Trace the whole path
The model call is only one part of an AI request. A useful trace connects:
HTTP request -> queue job -> retrieval -> model call -> tool call -> database
With that path, an operator can answer simple questions:
- Where did the time go?
- Which model or tool was used?
- How many retries happened?
- Did the request fail before or after the model responded?
- Did cost rise after a release?
5. Keep a human failure path
Some problems should not be solved by another automatic retry. A protected question, an unclear user instruction, a security warning, or a possible false attestation needs a person who can make the decision.
Good automation knows when to stop. It records the visible problem, preserves the current state, and makes the next action clear.
The work I am applying this to
I am building and documenting public examples around this idea:
- Claude Code Slack uses session persistence, project boundaries, webhook authentication, rate limits, and audit logging to make an agent easier to operate.
- Pineapple Server is a Go backend with services, queues, real-time flows, observability, and Kubernetes delivery.
- Snitch is an older data-pipeline project that uses incremental transfer and Redis checkpoints.
These are different systems, but the lesson is the same: reliable software needs visible state, bounded failure, and a way to inspect what happened.
Where I am discussing this
- Swift Forums discussion on honest failure states in SwiftUI clients.
- AWS Builder Center article on durable workflow state, retries, evaluation, and observability.
- LangChain Observability & Evals forum for the related evaluation discussion, which is awaiting moderator approval.
Add the failure path before adding the next clever feature.
I write more notes on oduonye.com/blog and keep the public code on GitHub.