Ship on Containment, Not Perfection
How to expose a non-deterministic feature to real users safely, without waiting for a flawlessness that never comes
An AI feature is never "done" the way a deterministic feature is. A unit-tested function either works or it does not, and once it passes you move on. An AI feature will be wrong some fraction of the time, forever, no matter how good the eval score looked yesterday. That single fact should change how you ship it. If you gate release on "no bad outputs," you never ship, because a non-deterministic system cannot make that promise. So you gate on something you can actually deliver: containment. Is the blast radius small, observable, and reversible?
This post is the whole discipline of shipping a feature you know will sometimes be wrong, without either freezing forever or throwing it at everyone on day one. It is part six of the From Demo to Product series, and the first on the OPERATE stage: what happens once the thing is good enough to put in front of real people.
The mental model: expanding a new trader's limits#
A brand-new trader does not get the full book on day one. They paper-trade first: orders generated, nothing executed, the results compared against the desk's baseline. Then they get a small position limit, watched closely, with a risk manager who can flatten the book in seconds. Limits grow only as a track record proves out, and even a trusted trader still needs sign-off for anything above a size threshold. Trust is per-trade, not a personality trait you grant once. Nobody promotes a trader to "no oversight, any size" the day they start.
Shipping a non-deterministic AI feature is the same discipline, and I mean that literally, not as a loose analogy. Each rollout technique maps onto a piece of how you would onboard that trader:
| Trading practice | Rollout equivalent |
|---|---|
| Paper trading (orders generated, none executed) | Shadow mode: run it, do not act on it, compare to baseline |
| Growing position limits | Staged percentage ramp: small exposure first, checkpoint before growing it |
| Per-trade-size sign-off | The autonomy ladder: the rung depends on what the action is, not a global maturity level |
| Risk manager's kill switch | One-switch rollback: it exists before you need it, and you have rehearsed pulling it |
The reframe that does the work here: "when do we ship" stops being a quality question ("is it good enough?") and becomes a containment question ("is the blast radius small, observable, and reversible?"). A non-deterministic system is never "good enough" in the sense a unit-tested function is. It clears a bar on a sample and will still miss sometimes in production. So you ship on containment, not on the illusion of zero defects.
The gate: four conditions, for a narrow slice#
Ship when all four of these hold, and only for a narrow slice, not the whole roadmap:
| Condition | What it means | Where it is decided |
|---|---|---|
| Clears the eval gate on real data | The offline eval bar is met on a versioned golden set, not a demo that looked good once | Evals as the product discipline |
| Users get value in a pilot | Real usage signal (task completion, retention, qualitative feedback) is positive, not just internal enthusiasm | A pilot or beta with real users |
| Cost is within target | The cost per task holds under the pricing plan's margin, including retries | Unit economics |
| A fallback exists for when it is wrong | A deterministic path absorbs errors, timeouts, and low-confidence cases | This post, below |
Two rules bound this. Never wait for perfect, because a non-deterministic system never is. And never ship a high-stakes autonomous action you cannot contain, no matter how good the eval score looks. The bar is "better than baseline, safe, cheap, and reversible for a small audience," not "flawless for everyone."
Scope the MVP narrow#
The first ship is the smallest version that delivers the core value: one use case, one user segment, the happy path plus explicit failure handling. Narrow scope is not a compromise you make reluctantly. It is the thing that makes the rest of this post possible, and it pays off three ways at once.
Fewer inputs and code paths mean a smaller failure surface, so there are simply fewer ways to be wrong. A narrow slice means a tractable eval set: you can actually build a golden set that covers one workflow, versus trying to evaluate "the whole product," which you never really can. And narrow scope gets you real usage fast, which is the only way to find the failure modes you did not think of, and those are always the ones that matter.
Resist "and it can also..." at this stage. Breadth is where AI products drown. Every added use case multiplies the space of things that can go wrong and dilutes the eval set that is supposed to catch them. You are not being timid by shipping narrow; you are keeping the failure surface small enough that your safety machinery can actually cover it.
Never flip to 100%#
Because a bad output can reach a real user, exposure grows in checkpointed stages, and each stage is a go/no-go decision, not an automatic advance:
internal dogfood ──► 5-10% ──► 25% ──► 50% ──► 75% ──► 100%
│ │ │ │ │
└── at every checkpoint, check the guardrail metrics below;
advance only if they hold, else pause, roll back, or hold ─┘"The checkpoint holds" is not a vibe. It is a specific set of signals, each watching for a different way the ramp can go wrong:
| Signal | What it catches |
|---|---|
| Quality / faithfulness (sampled online eval) | A regression the offline eval set never covered |
| Cost per request | A prompt or model change that is technically better but not affordable at scale |
| p95 latency | A slower model or an added retrieval step quietly degrading the experience |
| Error and intervention rate | How often a human had to step in, the same signal that later justifies more autonomy |
| User feedback (thumbs, complaints, tickets) | The qualitative miss your metrics do not capture |
If any signal breaks its threshold, pause the ramp, roll back to the last-good stage, and fix before advancing. Do not push through on hope.
The exact percentages and the number of steps are tunable to your traffic volume and risk tolerance; practitioners run tighter cadences that skip the internal-only first rung. What is not tunable is the shape: monotonic staged exposure, a checkpoint before every increase, and a rollback path. And write the checkpoint thresholds before the ramp starts. Deciding "hold or roll back" live, in the middle of a real incident, is how you talk yourself into shipping something you should not. Once you trust a guardrail metric enough, you can automate the hold-advance-rollback decision on it, but the manual version, with thresholds written down in advance, is the correct starting point.
Flag the model config, not just the feature#
This is the enabling move that makes everything else practical, and most teams get it half-right. The model ID, the prompt template, and the inference parameters should live behind a feature flag as one bundled config, not hardcoded in application code. Bundle them together because they are interdependent: a parameter tuned for one prompt can misbehave under another, and that interaction only shows up under real traffic, so you want to move them as a unit.
What this buys you is not marginal:
- Change without a deploy. Flip the flag and the config changes. No build, no release, no waiting on CI. The change also does not land in your CI/CD history, which is a real tradeoff to be aware of, so treat the flag store as the record.
- A/B against the incumbent on live traffic. Run the new model or prompt alongside the current one and compare real outcomes, task completion and engagement, in the one environment where real-time evaluation consistently reveals gaps that offline checks miss.
- Rollback that is a flag flip, not an incident. This is what makes the "one-switch rollback" at the end actually one switch.
The reason this specific config must be flag-driven, and not merely "nice to have," is that deprecation is a deadline you do not set. Providers retire models on a published calendar and on their own schedule, not yours. If your model ID is a string buried in application code, a retirement date becomes an emergency deploy under time pressure. If it is a flagged config, it is a scheduled flag change you can even A/B before the old model goes away. I go deeper on this in treat the model as a flaky dependency.
Flagging the UI feature but not the model config leaves the real risk unflagged. The toggle that turns the feature on and off is not the dangerous surface. The model ID, prompt, and parameters are. Flag those specifically.
Shadow mode for high-stakes changes#
Before a high-stakes change touches a real decision, run it alongside the current process without acting on its output. Log what it would have done, compare against the human or rules baseline, and only let it take the wheel once that comparison holds up. This is the paper-trading step from the mental model, and it is the only rollout stage with literally zero user-facing blast radius, so reach for it whenever the downside of a wrong action, not just a wrong read, is large. One caveat: shadow mode without a real baseline to diff against is not shadow mode, it is just logging. If there is no rules or human process currently running to compare to, you are not shadow-testing anything.
The deterministic fallback#
Every path that can go wrong needs an explicit, deterministic exit, designed in, not discovered during an incident:
| Trigger | Fallback options |
|---|---|
| Model or provider error, timeout | A canned response, a retry with backoff, or a simpler non-AI path |
| Low confidence | Route to a simpler deterministic path, or hand off to a human |
| Guardrail block (unsafe content or action) | Refuse with a clear message; never silently substitute a worse but confident answer |
The failure this exists to prevent is not "the feature breaks." A broken feature that says "I can't help with that right now" is fine, even good. The one outcome the fallback design must make impossible is a broken feature that answers anyway, fluently and wrong. A hard error is honest. A confident hallucination is the failure mode you are designing against.
And a specific trap: a fallback that is itself an LLM call is not deterministic. If your "safe path" is another model prompt, it inherits the exact failure it was supposed to contain. The fallback has to be a rule, a cache, a simpler system, or a human, something that cannot hallucinate by construction.
The autonomy ladder: earn it per action#
Autonomy is evidence-based and scoped to the action, not a maturity level the whole product graduates through:
| Rung | The AI does | The human does | Move up when |
|---|---|---|---|
| Human-in-the-loop | Proposes | Approves every action before it takes effect | Starting point for anything consequential |
| Human-on-the-loop | Acts | Monitors and can intervene; high-stakes or irreversible steps still route to approval | Track record plus a falling intervention rate on routine actions |
| Autonomous | Acts fully unsupervised | Reviews aggregate signal, not each action | Evals plus a proven-low intervention rate for this specific action class |
The middle rung is the 2026 default for capable agents: present a plan, get approval once, then execute across multiple steps, with guardrails and observability doing the supervising instead of a human checking every step. Not clamped down, not a free-for-all.
The move that matters is matching the rung to the action, in the same product, at the same time. A support agent can read order history autonomously while refund_order requires approval and send_money never leaves human-in-the-loop. Grading "the agent" as one trust level is the wrong unit. And least privilege holds at every rung regardless of how much trust an action class has earned: a trusted action with too broad a scope is still a large blast radius waiting to fire.
The failure to watch for is autonomy creeping by product rather than by action. "The agent has been reliable for three months" is not evidence for autonomy on an action class it has never actually performed. Intervention-rate evidence has to be per action, not borrowed from a different action's track record.
One-switch rollback at the gateway#
The endpoint of all of this is a single control that reverts to the last-known-good model, prompt, and parameter config, instantly, without a deploy. It belongs at your gateway, the choke point every model call already routes through, not scattered across call sites where you would have to change several things under pressure. And rehearse it before you need it. A rollback switch nobody has tested under a real incident is a rollback switch you do not actually have. Treat it like a fire drill.
Gotchas, or what actually bites teams#
- "It works in the demo" is not a launch signal. A demo is a hand-picked happy path. A pilot with real, uncurated input is the first honest signal. Do not confuse enthusiasm about a demo with the eval gate being cleared.
- The ramp stalls because nobody defined "hold" versus "roll back." Write the checkpoint thresholds before the ramp starts. Deciding them live, under a live incident, produces bad calls.
- Flagging the feature but not the model config leaves the real risk unflagged. The UI toggle is not the dangerous surface; the model, prompt, and params are.
- Autonomy creeps by product, not by action. Reliability on one action class is not evidence for a different one it has never performed.
- A fallback that is also an LLM call is not deterministic. It inherits the failure it was meant to contain.
- Shadow mode without a baseline is theatre. With nothing to diff against, you are logging, not testing.
Where this goes next#
Shipping is the start of the work, not the end. The next post is what running the thing actually looks like once it is live: LLMOps and the data flywheel, where drift, online evals, and the improvement loop turn a launch into a product that compounds. If you skipped the fundamentals, start with why the demo is only 5% of the product.

Folarin Akinloye is an AI Engineer based in London, UK. He builds production-ready agentic AI systems, multi-agent architectures, and sophisticated RAG implementations, and writes about the engineering decisions behind them.