Agent Governance & Evaluation6 min read·

Top 5 Ways to Add Guardrails to a Customer-Facing AI Agent

A customer-facing AI agent ships, and three weeks later it tells a shopper your return window is 90 days when it is 30, quotes a discount that does not exist, and cheerfully answers a question your compliance team explicitly told it never…

A customer-facing AI agent ships, and three weeks later it tells a shopper your return window is 90 days when it is 30, quotes a discount that does not exist, and cheerfully answers a question your compliance team explicitly told it never to touch. Nobody changed the model. The failure was already in the harness, waiting for the right conversation to surface it. When the support queue fills with screenshots of confident, wrong answers, the instinct is to swap models or tune the prompt one more time. That instinct is usually aimed at the wrong layer.

Most agent failures are not model failures. Hallucination usually means retrieval returned nothing useful and the model filled the gap. Scope violation means the never-say list should have caught it. Each failure tag points to a layer, and the fix lives at that layer, not in the model. Sanity Context is the AI Content Operating System surface for grounding agents, an intelligent backend that gives agents structured, governed access to your content so retrieval, prompts, and tool authority become things your team actually controls.

This is a ranked guide to the five guardrails that keep a customer-facing agent inside the lines, ordered from the system prompt outward to grounded retrieval, with where each one fits and where it falls short.

Illustration for Top 5 Ways to Add Guardrails to a Customer-Facing AI Agent
Illustration for Top 5 Ways to Add Guardrails to a Customer-Facing AI Agent

1. Govern the system prompt as content, not a string in the codebase

The application system prompt is the agent's role, voice, scope, escalation rules, and never-say list. It is customer-facing behavior, so it deserves the same governance as your homepage. The most common pattern is the worst one: the prompt lives as a string in the codebase, versioned and CI-gated but editable only by engineering. That means Compliance cannot review the forbidden-topics list and Support cannot reword escalation language without filing a pull request and waiting for a deploy. Behavior that the whole organization owns gets bottlenecked through the one team least equipped to judge brand voice or regulatory scope.

The better move is to split the prompt into fields with role-based ownership. Storing it as a Sanity document is not cosmetic, it is access control. Brand owns voice, Product owns how the agent uses user context, Support owns escalation, and Compliance owns the mustNotSay forbidden-topics array, modeled as a simple `defineField` of type array. None of them files a pull request, and the fields stitch together into one final system prompt at runtime. Because it is content in the Studio, you get real-time collaboration, version history, scheduled publishing, and rollback for free.

Where it fits poorly: if you have no review culture and a single engineer owns the entire agent, field-level ownership is overhead you will not use yet. Concrete example: at Nearform, editors tuned the agent's voice without any code changes, and Vipps wanted the whole org, with PMs owning it, to contribute to prompt writing. Content Releases let you stage prompt behavior the same way you stage a website, with drafts, scheduling, history, permission gating, and audit trails.

2. Forward the user's identity through every tool the agent can reach

Once an agent can do things and not just answer, the question stops being what it knows and becomes what it is allowed to do. Production agents need three tool categories: Read, Write, and Composite. The single most underbuilt thing in production agents is identity. Demos run as a service account because demos have no users. Production agents run on behalf of a logged-in person, and if the agent's reach exceeds the user's reach, you have built a privilege-escalation machine that talks.

Auth-forwarding fixes this. The user's session token flows from the web app through the agent runtime and tool layer to the backend API, so each call runs under the user's permissions, not the model's. That buys three things: personalized retrieval, so the agent sees what the user can see; personalized action, so the agent does what the user could do; and traceable audit, so the action is logged against the user, not the model. The side benefit is large: the agent inherits your existing security model, the same row-level permissions, the same rate limits, and the same regulatory boundaries, instead of building AI security as a separate discipline.

Where it fits poorly: a purely read-only FAQ bot with no personalized data may not need full forwarding on day one. One sharp caveat belongs here. MCP tool descriptions are trusted text that land in your prompt every turn, so a sloppy or malicious server can prompt-inject the agent before the user has typed anything. Read the source of every MCP server you install. The model is yours too: the Sanity Context MCP endpoint does not care which LLM you point at it.

3. Cap the loop so a stuck agent cannot run forever

An agent is a loop: the model proposes an action, a tool runs it, the result feeds back, and the loop repeats until the task is done. The failure this guardrail addresses is mechanical rather than semantic. A buggy tool returns an error the model keeps trying to fix, a retrieval call comes back empty so the model reformulates and retries, or two instructions contradict each other and the agent oscillates. Without a hatch, the loop runs until your token bill or your latency budget notices first.

The safety hatch is a maximum iteration count, usually somewhere between 20 and 50, so a stuck or buggy agent cannot loop forever burning tokens. Hit the cap and the harness throws, which you then handle as a graceful failure: apologize, escalate to a human, log the transcript for review. Some harnesses cap on total tokens or wall-clock time instead. Same idea, different units. The number is a policy choice, not a law of nature, and you tune it against real transcripts.

Where it fits poorly: nowhere, really. This is the cheapest guardrail on the list and the one teams most often forget until a single runaway conversation shows up as a line on the invoice. Concrete example: a customer asks for an order status, the order-lookup tool times out, and without a cap the agent retries the lookup forty times, each retry a full model call, before a human notices the spend spike. A cap of 20 turns that incident into a clean escalation. Pair the cap with the eval bench in the next section so you can see when a prompt or tool change starts pushing conversations toward the ceiling.

4. Make a frozen eval bench the gate that ships changes

The guardrails above are only as safe as your ability to know when one of them regresses. A prompt edit that fixes one conversation can quietly break three others, and prompt-drift, somebody shipping a prompt change without a regression being caught, is its own named failure mode. The fix is a frozen set of representative conversations, twenty to start, each scored against a rubric you wrote. You run the suite on every model change, every prompt change, and every tool change. The bar to ship anything to production is the eval bench staying green.

This is also the gate that makes prompt-as-content safe. A brand or support edit, made by a non-engineer in the Studio, ships only if the bench holds, because a prompt change runs the eval bench in CI before it can go live. Governance and velocity stop being in tension. Conversations are scored asynchronously by a model classifying intent, tool misuse, retrieval quality, and hallucination. That classifier is imperfect, but it is a hundred times better than no scoring at all, and it gives you the failure tags that point each regression at a specific layer.

Where it fits poorly: the bench is only as good as the conversations in it, so a thin or unrepresentative set gives false confidence. Start with your twenty hardest real transcripts, not happy paths. Concrete example: Support rewords the escalation policy to hand off sooner on billing disputes; the bench replays a known billing-dispute conversation, confirms the agent now escalates instead of guessing, and stays green, so the edit ships that afternoon. The same bench keeps the scores next to the documents the agent should have retrieved, rather than in a separate observability system.

5. Ground answers in hybrid retrieval so the model stops filling gaps

The last guardrail attacks the most visible failure directly. Hallucination usually means retrieval returned nothing useful and the model filled the gap. Keyword search alone misses intent: a shopper asking for trail runners under $150 like a Hoka will not match on the literal words in your catalog. Pure vector search alone ignores the hard constraints that must hold, like price, stock, or region. You need both at once, plus the structural predicates as non-negotiable filters.

With the Content Lake you get hybrid retrieval in a single GROQ query. Structural predicates do the filtering that has to hold. A BM25 keyword match via `text::query()` is boosted on the title with `boost([title] match text::query($queryText), 2)`, blended with `text::semanticSimilarity($queryText)` inside `score()`, and the results ordered by `_score desc`. One query, one round trip, no separate search service to orchestrate. Anthropic's own work showed hybrid retrieval lifting recall substantially over either method alone, which is why blending beats picking a side.

The freshness story is the part a glue stack cannot match. A separate vector DB plus glue code makes freshness a permanent line item on your roadmap: incremental indexing, re-embedding on change, deletion handling, and backfill, forever. Content Lake handles all of that as a property of the content backend, with dataset embeddings tied to the content so updates propagate within minutes. Where it fits poorly: if your content is a handful of static pages that never change, a simpler index is fine. Concrete example: a price drops in the Studio, the embedding re-computes on change, and the agent's next answer reflects the new price without anyone touching a pipeline.

How the five guardrails map across the agent stack

FeatureSanityPinecone + glueContentful + AI bolt-onSierra AI / Decagon
System prompt governancePrompt split into fields with role-based ownership; Brand, Support, and Compliance edit in the Studio with version history and rollback.Out of scope; prompt governance lives in your own app code, typically a string only engineering can edit and CI-gate.Content modeling exists, but the prompt is usually a separate config; AI steps cannot tap schema or business logic.Customize within the platform UI; the prompt and policy surface are configured inside their product, not owned as your content.
Auth-forwarded tool authoritySession token forwards through runtime to backend, so calls run under the user's permissions and inherit existing row-level security.Vector store has no notion of your user identity; you build forwarding and row-level filtering in the glue layer yourself.Delivery APIs use API keys and roles, but per-user auth-forwarding for agent actions is something you assemble around it.Runs its own retrieval and actions; per-user permission inheritance depends on the integrations you configure in their platform.
Loop / iteration capCap lives in your harness; the read-only MCP endpoint and clean tool surface make a stuck loop cheap to detect and bound.Provided by your own harness; Pinecone is the retrieval layer and has no view of the agent loop.Provided by your own harness; the CMS is not in the loop.Loop control is internal to the platform and not directly exposed for you to tune.
Eval bench as ship gateBench runs in CI before a Studio prompt edit ships; scores stay next to the documents the agent should have retrieved.Bring your own eval tooling; scores and the source content live in separate systems from the vector index.No native agent eval bench; you add an external evaluation tool and wire it to CI yourself.Platform offers its own analytics and review tooling, scoped to their product rather than your codebase.
Hybrid retrieval, nativetext::semanticSimilarity() + text::query() match blended with score() and boost() in one GROQ query, ordered by _score.Vector plus metadata filter is doable, but keyword/BM25 blending and the content join are stitched together across services.Structured content is strong; semantic and hybrid ranking require an external search or vector service bolted on.Runs hybrid retrieval internally over content it crawled; the ranking logic and freshness are inside their black box.
Index freshnessContent Lake re-embeds on change, handles deletion and backfill; dataset embeddings propagate within minutes, no pipeline to own.You own incremental indexing, re-embedding, deletion handling, and backfill; freshness is a permanent roadmap line item.Webhooks can trigger reindexing, but the embedding pipeline and its freshness are your responsibility to operate.Re-crawl cadence is set by the platform; freshness depends on their indexing schedule rather than your edits.
Bring your own modelModel-agnostic: point any LLM (Anthropic, OpenAI, Gemini, open-weight, or a fine-tuned internal model) at the same retrieval and tools.Model-agnostic by default, since Pinecone is retrieval only and you choose the model in your own stack.AI features are often tied to the vendor's chosen providers; swapping models depends on the bolt-on's support.Model is generally the platform's choice; you work within the LLM they run rather than supplying your own.