Skip to Content
PlatformData HandlingGuardrail verdicts

Guardrail verdicts

A guardrail is a check you run on an agent’s input or output: a provider’s built-in filter, a library such as NeMo Guardrails or Guardrails AI, a gateway policy, or a service you call yourself. Morse records the verdict each one returns and puts it on the trace, so you can see which agents have a guardrail, how often it trips, and which vendor said what.

Morse records verdicts and nothing else. It sits beside your traffic, not in it: it never blocks, delays, retries or changes a request or a response, and it does not call your guardrail vendor. Coverage figures describe your guardrails, not Morse.

One verdict record

Whatever the source, Morse stores the same small record on the guardrail span:

FieldValues
vendorA lowercase slug of up to 40 characters, such as lakera or bedrock. unknown if the source gave none.
verdictallow, flag, redact, block, error or unknown.
actionOptional slug of up to 40 characters, such as input or pre_call.
categoriesUp to 10 slugs, such as prompt_attack or pii.
sourceWhere Morse read it from: openai_agents_py, openai_agents_ts, openinference, otlp_convention, litellm or nemo.

The record holds slugs and enums only. It never holds the text a guardrail checked, the text it returned, or its explanation. A category that is longer than 40 characters, or contains anything other than letters, digits, _ or -, is dropped rather than stored. A value Morse does not recognise in verdict is stored as unknown, and the span is still ingested.

Verdict meanings:

  • allow: the guardrail ran and let the content through.
  • flag: it let the content through but reported a concern.
  • redact: it removed or masked part of the content.
  • block: it stopped the content.
  • error: the guardrail itself failed to answer. Morse has no verdict to record.
  • unknown: the guardrail ran but the source did not say what it decided.

What Morse reads

SourceHow the verdict is read
OpenAI Agents SDK, PythonA guardrail span with tripwire_triggered true is block; otherwise allow. A guardrail span that ended in failure with no tripwire is error. Vendor is openai_agents.
OpenAI Agents SDK, TypeScriptA tool span marked as a guardrail is re-typed as a guardrail span. passed: false is block, passed: true is allow, anything else is unknown. Vendor is openai_agents.
LiteLLM proxy guardrails (OTel)The guardrail child span the proxy exports. Status success is allow, guardrail_flagged is flag, guardrail_intervened is block (or redact when the guardrail also masked entities), guardrail_failed_to_respond is error, not_run is unknown. Vendor is the guardrail’s name, action is its mode, categories come from its violation categories and masked entity types.
NeMo Guardrails (OTel)A guardrails.rail span: rail.stop true is block, otherwise allow. Vendor is nemo, action is the rail type (input, output), and the category is the rail’s name. NeMo has no “modified” signal, so Morse never reports redact for it.
Guardrails AI via OpenInferenceA validator span (validator_name): output pass is allow, fail is flag. Vendor is guardrails_ai and the category is the validator’s name.
Any other OpenInference GUARDRAIL spanRecorded as a guardrail span with verdict unknown, because the OpenInference convention defines the span kind but no result attributes.
The morse.guardrail.* OTLP attributesRead as written. This is how to report a guardrail Morse has no reader for. See below.

For the OpenAI Agents SDK, LiteLLM, NeMo and Guardrails AI you do not change your code. Point their OpenTelemetry export at Morse (see OTLP) and the verdicts arrive on the guardrail spans they already emit. NeMo needs its tracing enabled with the OpenTelemetry span format.

If a source’s export does not carry a guardrail result, Morse cannot invent one. Use the attributes below.

Report a guardrail you call yourself

Many guardrails are an API call you make from your own code: a provider’s built-in filter such as Amazon Bedrock Guardrails, or a service such as Lakera. Morse does not call these. Wrap the call in a span, set the four morse.guardrail.* attributes from the result, and send the span over OTLP.

AttributeRequiredValue
morse.guardrail.vendorYesThe guardrail’s name, e.g. bedrock, lakera. Lowercased; spaces become _.
morse.guardrail.verdictYesallow, flag, redact, block, error or unknown. Anything else is stored as unknown.
morse.guardrail.actionNoA short slug such as input or output.
morse.guardrail.categoriesNoComma-separated slugs, for example prompt_attack,pii. At most 10 are kept.

A span that carries morse.guardrail.verdict is stored as a guardrail span. The raw attribute values are replaced by the normalized record, so nothing free-form is kept beside it.

An example for Amazon Bedrock Guardrails, called from inside an agent run. The span is a child of whatever span is current, so it appears inside the agent’s trace.

import os import boto3 from opentelemetry import trace tracer = trace.get_tracer("my-agent") bedrock = boto3.client("bedrock-runtime") def check_input(user_text: str) -> bool: """Return True if the guardrail let the input through.""" with tracer.start_as_current_span("guardrail.input") as span: result = bedrock.apply_guardrail( guardrailIdentifier=os.environ["BEDROCK_GUARDRAIL_ID"], guardrailVersion=os.environ["BEDROCK_GUARDRAIL_VERSION"], source="INPUT", content=[{"text": {"text": user_text}}], ) intervened = result["action"] == "GUARDRAIL_INTERVENED" span.set_attribute("morse.guardrail.vendor", "bedrock") span.set_attribute("morse.guardrail.verdict", "block" if intervened else "allow") span.set_attribute("morse.guardrail.action", "input") if intervened: # Slugs you choose. Never put the guardrail's own text here. span.set_attribute("morse.guardrail.categories", "denied_topic,pii") return not intervened

The same pattern works for Lakera or any other service: set vendor to its name and map its response to one of the six verdicts. Decide the mapping once, in your own code; Morse stores what you send.

Keep the categories short and generic. Morse drops any category that is not a slug, so a sentence from a vendor’s explanation never reaches storage.

Where verdicts show

  • A trace. The guardrail panel on the span shows the verdict as text with an icon, for example “Blocked by lakera”, with the vendor, the action and the categories. A guardrail span from before the verdict record existed, with only a tripwire flag, reads “Blocked by openai_agents”. Colour is never the only signal.
  • An agent. A coverage line under Capture health: the share of runs with a guardrail verdict in the selected window, or “No guardrail seen in N runs”, or “Too few runs to measure” below 20 runs.
  • Data handling. The Guardrail coverage section lists every agent, with its trip rate and the vendors seen.
  • Attention. The Home worklist can show two items. One says an agent with at least 20 runs has no guardrail verdict at all; it is raised only for a workspace that has recorded at least one guardrail verdict in the last 30 days, and at most once a week, so a workspace that never uses guardrails is not nagged (the Data handling page still shows the fact). The other says an agent’s trip rate rose by at least 10 percentage points and at least 1.5 times between two windows that each have at least 20 guarded runs. Both describe your guardrail.
  • Data handling — the page that collects coverage.
  • OTLP — sending spans to Morse.
  • Traces — where the guardrail panel lives.
Last updated on