Introduction
OpenGuardrails (OGR) is an open guardrails contract for AI agents. The
whole protocol fits in one sentence: at the two moments an integration can
still refuse — before a request reaches the model, and after the response
arrives but before the agent acts on it — it forwards the raw provider body
as a GuardEvent to
POST /v1/evaluate and gets back a
Verdict — allow or block,
with findings saying what was found and where, and redaction spans when
content must be transformed in place.
One endpoint, one recipe. There is deliberately no SDK layer: the API is the integration surface, and every plugin this project ships is written against it. Integrating your own agent is two POSTs added to a loop you already have:
while True:
step_id = uuid.uuid4().hex # binds this call's 2 events
body = {"model": "gpt-5", "messages": messages, "tools": TOOLS}
if blocked(evaluate("step/request", step_id, body)): # ① before the model
break
resp = call_llm(body) # your code, unchanged
if blocked(evaluate("step/response", step_id, resp)): # ② before acting
break
... # execute tool calls, loop
The quickstart is the complete, runnable version —
evaluate() is a single requests.post, fail-open by default, and sends the
optional session_hint (one opaque id per conversation) so sessions are
declared instead of inferred.
The layer model
OGR's foundational concept: agent traffic modeled the way the layered network model models packets. An integration sees one event at a time, the way a firewall sees one IP packet; the runtime reassembles everything above it and reads everything below it out of the payload.
| # | OGR layer | One unit is |
|---|---|---|
| L6 | Session | one conversation |
| L5 | Turn | one instruction → quiescence |
| L4 | Step | one model call, two events paired by step_id |
| L3 | Event | one GuardEvent — the only layer on the wire |
| L2 | Call | one tool call the model asked for |
| L1 | Exec | one real execution — named by the model, not carried |
You already have words for this traffic — the network stack, tracing spans, or the SDK you built on. They line up:
| # | OGR | Network | OTel GenAI | OpenAI Agents SDK | Claude Agent SDK | LangGraph |
|---|---|---|---|---|---|---|
| L6 | Session | session table | conversation.id | Session id | session_id | thread |
| L5 | Turn | flow | invoke_agent | one Runner.run() | one query() | one invoke() |
| L4 | Step | transport | chat span | generation_span ⚠️ | one round trip ⚠️ | model node |
| L3 | Event | the packet | span start / end | span start / end | AssistantMessage | around invoke() |
| L2 | Call | link | execute_tool | function_span | tool_use | ToolNode |
| L1 | Exec | physical | — | — | the host command | the tool fn |
| — | Agent | host | agent.id | Agent | agent · subagent | the graph |
⚠️ Both agent SDKs call an L4 step a "turn" — one loop iteration, which is
what max_turns counts. An OGR turn is the instruction episode above it. The
full mapping
covers handoffs, subagents, super-steps, and what to send as session_hint.
The ledger is the runtime's job, not the wire's. Everything above the event is derived server-side: sessions by conversation-prefix chaining (re-attached across context compaction), turns by instruction boundaries and idle timeout, step numbering by arrival. An integration keeps no loop state for OGR — it is an API key, eight required fields, and one endpoint. And the agent is an endpoint, not a layer — addressed by the identity four-tuple every event carries, the way hosts are addressed by packets.
The full treatment — the entity axis, the firewall vocabulary, why six layers and not OSI's seven — is the layer model.
One recipe, two vantage places
The same two POSTs serve a developer instrumenting their own agent loop and a
gateway proxying model traffic. Both forward the raw provider body they hold,
both mint a step_id per model call, both declare nothing else. The only
difference is who fills the
identity four-tuple:
an agent asserts its own; a gateway asserts its authenticated caller's, read
off the request — see gateway integration.
What the contract standardizes
- GuardEvent — eight required
fields: the step half (
kind), the pairing id (step_id), the identity four-tuple, the payload shape (llm_protocol), and the raw provider body (payload) — plus three optional:integration,connection,session_hint. - Verdict — the runtime's
decision:
allow|block, plusfindings,modifications.spans, andunjudged. - Composition — how a runtime merges many detectors' verdicts into the one decision it enforces.
- The Runtime API — the HTTP binding: evaluate, heartbeat, health. Fail-open by default when the runtime is unreachable.
Where to go next
- Quickstart — the minimal integration, end to end: the four-tuple, fail-open, and streaming with a held-back tail.
- Gateway integration — guard every agent behind your LLM gateway without touching their code: the install, the identity headers, and what to strip at the edge.
- API reference — every endpoint, error, and field.
- Plugins — ready-made integrations for gateways and agent harnesses.
OGR is Apache-2.0 and governance-neutral. Detectors compete on a neutral benchmark; you compose the winners.