Blog · · 10 min read

AI agent guardrails that cover every runtime path

AI agent guardrails that cover every runtime path

AI agent guardrails fail when a product protects the chat request but misses another runtime path. A streamed token can reach a browser before an output rule blocks it. A hosted tool can bypass middleware written for custom functions. A resumed run can rely on a policy decision made before permissions changed. The dashboard still says the guardrail fired, even though the disclosure or side effect has already happened.

Adding another classifier will not close those gaps. Guardrails need to be a versioned product enforcement system. Inventory every execution path, place the right check before each irreversible boundary, specify how checker failures behave, and test the paths that should be impossible.

Guardrails are an enforcement problem, not a callback list

A guardrail is any rule that constrains what an agent may consume, disclose, or do. That includes content safety, sensitive-data handling, tenant isolation, authorization, action approval, allowed destinations, grounding, task adherence, and business-specific restrictions.

Framework hooks are useful, but their scope is narrower than a product's full runtime. The OpenAI Agents SDK guardrail documentation says agent-level input checks run only for the first agent and output checks only for the final agent. Its tool guardrails apply to custom function tools, not handoffs, hosted tools, computer tools, shell tools, or patch tools. It also warns that parallel input checks may run while the agent is already producing output or invoking tools.

Other runtimes expose different boundaries. LangChain guardrails can run through middleware before or after an agent, model, or tool call. That is useful coverage, but the product still has to decide which paths use that middleware and what happens outside it.

Start with an explicit contract:

guardrail_policy:
  policy_id: "agent-safety"
  version: "2026-08-13.4"
  subject_scope: ["user", "tenant", "agent-release"]
  decisions:
    - allow
    - transform
    - block
    - require_approval
    - quarantine
    - checker_error
  enforcement_points:
    - request_admission
    - context_assembly
    - model_input
    - stream_delivery
    - tool_argument_commit
    - handoff_creation
    - result_persistence
    - run_resume

The policy version is part of the run's identity. Each decision records the enforcement point, current subject and tenant, rule, resource versions, outcome, and recovery action. Do not store rejected sensitive content just to prove it was rejected. A digest, category, rule ID, and redacted evidence are usually enough.

Map every path before choosing controls

Draw the runtime as a graph. Include entry points, transformations, model calls, transports, tools, stores, handoffs, retries, and recovery paths. The goal is to find every edge where data leaves a trust boundary or an effect becomes real.

A practical inventory includes:

PathBoundaryRequired decision
User request to context builderUntrusted input enters product data flowAccept, transform, or block
Product records to model requestAuthorized data leaves product controlPermit exact fields and destination
Model token to client streamGenerated data becomes visibleDeliver, redact, buffer, or stop
Tool proposal to executorA suggestion can become a side effectAuthorize current arguments and state
Parent agent to child agentData and authority cross an execution identityNarrow scope, budget, and deadline
Tool result to memory or product recordUntrusted or uncertain output becomes durableValidate, label, or quarantine
Checkpoint to resumed runOld state returns under current policyReauthorize and revalidate
Retry to external serviceWork may be repeatedConfirm idempotency and current permission

Do not equate one SDK callback with one row in this table. A model provider's hosted search, browser, or execution tool may not pass through the same wrapper as a local function. A final-output check may not inspect intermediate handoff content. A reconnecting stream may replay buffered output through a different transport handler.

Assign an owner and a negative test to every row. During an incident, an enforcement point without an owner tends to become optional. Without a failing test, it is only an assumption.

Use deterministic checks at hard boundaries

Model-based checks help with semantic risks, such as whether a response contains disguised abuse or whether a proposed action matches the user's stated task. They should not replace deterministic controls for identity, permissions, tenancy, destination, schemas, budgets, and state versions.

Use deterministic code to answer questions that the product already knows:

  1. Is this user allowed to call this tool in this tenant?
  2. Does the destination match an allowlisted service and operation?
  3. Does the argument object match the current schema?
  4. Is the product record still at the version the agent read?
  5. Has this idempotency key already produced an accepted effect?
  6. Is the run inside its time, token, cost, and action budgets?
  7. Does the delegated child scope fit inside the parent's current grant?

Use classifiers or model judges for semantic signals, then turn the result into a product decision. Amazon Bedrock Guardrails separates checks for content, denied topics, sensitive information, grounding, and automated reasoning. It can apply a guardrail independently of a model invocation. Azure AI Content Safety exposes prompt shields, groundedness, protected-material, custom-category, and task-adherence controls. These services produce evidence. Your runtime still decides whether that evidence calls for transformation, blocking, approval, quarantine, or degraded operation.

A low-risk explanatory response might continue after a semantic checker times out, with a warning and no tools. A payment, deletion, permission change, or external message should fail closed if its required authorization or action-policy checker is unavailable.

Stop streaming leaks before delivery

An output check that runs after generation is not necessarily a pre-delivery check. In LangChain issue #35011, the reporter reproduces streamed PII reaching the console before middleware raises an exception. This is an author report about one implementation path, but it exposes a general ordering problem: detection after delivery cannot retract data.

Choose the streaming policy by risk:

  • Buffer the complete response for high-risk data or external audiences, run output checks, then release it.
  • Buffer small chunks or complete semantic units when full-response latency is unacceptable.
  • Transform tokens before delivery only when the transformer can safely handle partial text and boundary splits.
  • Disable streaming for workflows whose output may contain secrets, regulated data, or irreversible action confirmations.
  • Keep tool arguments and hidden reasoning channels out of the user stream entirely.

The stream gateway, not the model callback, owns delivery. It needs a state machine such as pending, screening, deliverable, blocked, and closed. Once bytes are delivered, a later block is an incident signal rather than successful prevention.

Test reconnects and alternate transports. Server-sent events, WebSockets, polling, mobile push, exports, notifications, and trace viewers can each become an output path. A rule applied to the web chat controller does not protect the email sent by a background run.

Put tool policy immediately before execution

Validate a tool proposal when the model creates it, but authorize it again at the execution boundary. The second decision uses current identity, tenant, resource version, approval state, and policy version. This closes the gap between proposal and effect.

function execute_guarded_tool(run, proposal):
    policy = load_policy(run.guardrail_policy_version)
    subject = resolve_current_subject(run.delegation_id)
    resource = load_current_resource(proposal.resource_id)

    decision = policy.evaluate(
        point="tool_argument_commit",
        subject=subject,
        tenant=run.tenant_id,
        agent_release=run.agent_release,
        tool=proposal.tool_name,
        arguments=canonicalize(proposal.arguments),
        expected_resource_version=proposal.resource_version,
        current_resource_version=resource.version,
        approval=load_current_approval(proposal.approval_id)
    )

    record_redacted_decision(run.id, proposal.id, decision)

    if decision.outcome == "require_approval":
        pause_with_bound_action(run, proposal, decision)
        return
    if decision.outcome != "allow":
        return typed_tool_error(decision.reason_code)

    return invoke_with_idempotency_key(
        proposal.tool_name,
        proposal.arguments,
        proposal.action_id
    )

This wrapper must cover every executor. If a framework guardrail supports only custom functions, place the product policy beneath the framework at the shared execution gateway for hosted tools, browser actions, shell commands, MCP calls, and internal jobs. If a tool cannot be mediated there, restrict or remove it for action-taking flows.

Handoffs need the same treatment. A child agent receives an intersection of the user's current grant, the parent's grant, and the task-specific scope. Bind a deadline, action budget, data scope, and cancellation lineage. Recheck before the child causes an effect or returns data the initiating subject may no longer read.

Define checker failures as product outcomes

Guardrail services fail too. They time out, return malformed output, disagree, hit quotas, or become unavailable. If the runtime treats every checker exception as allow, the safety system disappears during the exact period when operators have less visibility.

Define behavior by enforcement point and impact:

FailureLow-impact responseHigh-impact action
Semantic checker timeoutContinue without tools or show a safe fallbackBlock or require approval
Authorization store unavailableDeny access to protected dataBlock
Output transformer errorReplace with a generic errorDo not deliver buffered content
Evidence write failureContinue only if a durable local queue existsBlock if policy requires an audit receipt
Policy version missingStart no new runPause resumed work
Conflicting checker resultsApply the stricter decision and alertBlock or escalate

Retries must reuse the same logical action identity but obtain a fresh decision where authority or product state can change. Resumed runs must reload current policy and permissions even when they preserve the historical policy version for reproducibility. Record both: the policy that produced the proposal and the policy that authorized the eventual effect.

An OpenAI Agents SDK issue about terminal guardrail handling reports different session-persistence behavior for a max_turns terminal path in streaming and non-streaming runs. The specific defect may change. The design rule does not: terminal errors, cancellation, budget exhaustion, and recovery handlers belong in the enforcement map.

Measure interventions without logging the payload

A useful guardrail event answers what rule ran, where it ran, which policy version applied, how long it took, what decision it returned, and what happened next. It should not copy the secret, abusive text, personal data, or unsafe tool arguments into a broad log sink.

Track rates by enforcement point, rule, release, tenant cohort, tool, and outcome. Monitor checker latency and timeouts, transformed responses, approvals, blocks, false-positive reviews, and bypass tests. Arthur's production guardrail guidance discusses pre-model and post-model placement, latency and cost tradeoffs, intervention telemetry, and self-correction paths. Use those signals to tune a declared policy instead of letting each team invent its own thresholds.

Alert on missing events too. If tool executions continue while tool_argument_commit decisions fall to zero, the absence is more important than the block rate.

Test bypasses, not just blocked examples

A test set of toxic prompts proves only that one classifier detects familiar text. Runtime tests must prove that no path reaches a protected sink without the required decision.

Before release, run these cases:

  1. Stream a sensitive value split across token and chunk boundaries; verify that no byte reaches the client.
  2. Invoke the same action through a custom function, hosted tool, MCP server, browser tool, shell tool, and background worker; verify equivalent policy decisions.
  3. Hand work to a child agent and confirm its data, action, cost, and deadline scopes cannot exceed the parent grant.
  4. Revoke permission after proposal but before execution; verify the action is denied.
  5. Pause for approval, change the target record, then resume; verify stale approval cannot authorize the changed action.
  6. Force each checker to time out, throw, return malformed data, and hit a quota; verify the declared degraded mode.
  7. Trigger maximum-turn, cancellation, and terminal-error paths in streaming and non-streaming runs; verify that rejected state is not committed as accepted history.
  8. Retry a blocked or uncertain action; verify the same logical action is not duplicated and the current policy is applied.
  9. Reconnect to every output transport; verify buffered or blocked content is not replayed.
  10. Remove or misconfigure a policy version; verify new runs stop and resumed runs pause safely.
  11. Disable evidence storage; verify actions that require a receipt do not proceed without one.
  12. Deploy a new agent release; compare its enforcement matrix with the previous release and fail the build if a protected path disappears.

A practitioner asking about non-probabilistic security for LLM agents describes the boundary problem in practical terms: deterministic action authorization outside model context, canonical tool intent, risky sequence detection, and latency control. Keep product permissions and effect controls deterministic. Semantic checks can add risk evidence, but should not decide hard authorization.

Build the enforcement matrix for one real flow

Pick one production flow that reads protected data and can perform an external action. List every input, model, stream, tool, handoff, persistence, retry, and resume edge. For each edge, name the policy rule, code owner, failure mode, decision evidence, and negative test. Then run the flow with each checker disabled or broken.

Do not ship until every protected sink has exactly one unavoidable enforcement gateway and the test suite proves that alternate paths cannot bypass it. That is the difference between having AI agent guardrails in configuration and enforcing them in the product.

References

  • OpenAI Agents SDK: Guardrails supports the documented scope of agent and tool guardrails, the first-agent and final-agent boundaries, excluded tool classes, and the side-effect tradeoff of parallel input checks.
  • LangChain: Guardrails supports deterministic and model-based guardrail choices plus middleware placement around agent, model, and tool execution.
  • Amazon Bedrock Guardrails supports versioned, independently applicable checks for content, denied topics, sensitive information, grounding, and automated reasoning.
  • Azure AI Content Safety supports prompt-shield, groundedness, custom-category, protected-material, and task-adherence controls.
  • Arthur: AI Agent Guardrails supports pre-model and post-model placement, intervention telemetry, self-correction, and latency and cost tradeoffs.
  • LangChain issue #35011 provides a reproducible author report of streamed PII reaching the consumer before a guardrail exception.
  • OpenAI Agents SDK issue #4393 provides an author report of inconsistent terminal-path guardrail persistence between streaming and non-streaming runs.
  • Ask HN: non-probabilistic security for LLM agents provides practitioner problem evidence for deterministic action authorization and canonicalized tool intent outside model context.

Be first in line.

Join the waitlist and we'll email you the moment it's ready. No sales call.

Try the demo →
or talk to us