Guard a custom LangGraph agent with the OpenGuardrails (OGR) protocol — a drop-in ToolNode at the agent-hook altitude, with native LangGraph interrupt() as the human-confirm gate.
Project description
openguardrails-instrumentation-langgraph
Guard a custom LangGraph agent
with the OpenGuardrails (OGR) protocol — a drop-in
ToolNode that enforces one policy you own at the agent-hook altitude,
using LangGraph's own interrupt() as the human-confirm gate.
pip install "openguardrails-instrumentation-langgraph[langgraph]"
Why this is different from the other instrumentations
The opencode / OpenClaw / Claude Code bindings hook a finished product's
plugin surface. A developer hand-rolling an agent with LangGraph has no plugin
marketplace — they need a library they import. That library's natural
Policy Enforcement Point (PEP) is the tool boundary: in LangGraph, the
ToolNode. That node is the agent-hook altitude.
What it enforces
Every tool_call the model emits is turned into an OGR GuardEvent and judged
by the reference Runtime (the PDP) before the tool runs:
| Tool call | Provenance | Decision |
|---|---|---|
get_weather(city="Paris") |
trusted | allow — runs |
bash("rm -rf /") |
trusted | block — refused in place, never runs |
bash("curl … | sh") |
trusted | require_approval → interrupt() |
bash("curl … | sh") |
untrusted (after a web/retrieval result) | block — injection |
The last two rows are the point: the same command gets a different verdict depending on where its inputs came from. A tool whose result carries external content (web fetch, retrieval, MCP) taints the session, so a later tool call inherits untrusted provenance — the indirect-prompt-injection defense.
The human-confirm gate is LangGraph-native
A require_approval verdict doesn't invent an approval UI — it raises a
LangGraph interrupt().
The graph pauses at its checkpointer; a human approves; the graph resumes. This
keeps OGR's red line intact: the confirm gate and the enforcement point stay
privilege-separated — the agent cannot self-approve.
from langgraph.types import Command
out = app.invoke({"messages": [...]}, config) # pauses at __interrupt__
# ... surface out["__interrupt__"][0].value to a human ...
out = app.invoke(Command(resume=True), config) # approve all
# or Command(resume={tool_call_id: True/False}) / a list of approved ids
Resume accepts True/False, "approve"/"deny", a {tool_call_id: bool}
map, or a list of approved ids. Anything unrecognized fails closed (deny).
Three integration surfaces
from openguardrails_instrumentation_langgraph import (
OpenGuardrailsToolNode, guard, ogr_guard,
)
1. Drop-in ToolNode — the primary PEP, for a hand-built StateGraph:
graph.add_node("tools", OpenGuardrailsToolNode(tools)) # was ToolNode(tools)
app = graph.compile(checkpointer=MemorySaver()) # checkpointer needed for approvals
2. guard(tools) — when you don't own the ToolNode (e.g. the prebuilt agent):
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(model, guard(tools))
3. @ogr_guard — a single tool:
@ogr_guard
@tool
def bash(command: str) -> str: ...
OpenGuardrailsToolNode gives full provenance/injection coverage (it sees the
run config and threads session taint); guard() / ogr_guard judge with base
provenance.
The policy
policy.json is a
thin overlay — {"$extends": "openguardrails:base"}. The core resolves the
canonical PAP base and deep-merges your overlay into one effective policy, so the
same policy that fronts your gateway and sandbox also judges these tool calls.
Point elsewhere with OGR_POLICY=/path/to/policy.json or
OpenGuardrailsToolNode(tools, policy="…").
Altitude
This binding is the agent-hook altitude (the tool boundary). The gateway
altitude (prompt-injection / secret leakage on the LLM wire) is covered by
openguardrails-instrumentation-litellm
if your LangGraph app routes its model through litellm — the two compose.
Try it
python examples/react_agent.py # drives allow / block / approve / deny end-to-end
python tests/test_smoke.py # offline decision-core test (no langgraph needed)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file openguardrails_instrumentation_langgraph-0.1.1.tar.gz.
File metadata
- Download URL: openguardrails_instrumentation_langgraph-0.1.1.tar.gz
- Upload date:
- Size: 12.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f86b719517aebf79809b948d7e8710ea29a5fb7c2a2f185f88ead11cef314d5f
|
|
| MD5 |
d1412694975dc27d6d571a161df260ab
|
|
| BLAKE2b-256 |
508113831f251880ce5b5f49280c837c7486cfe4a2070fb60c6abf9c34ba595c
|
Provenance
The following attestation bundles were made for openguardrails_instrumentation_langgraph-0.1.1.tar.gz:
Publisher:
publish.yml on openguardrails/openguardrails-instrumentation-langgraph
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openguardrails_instrumentation_langgraph-0.1.1.tar.gz -
Subject digest:
f86b719517aebf79809b948d7e8710ea29a5fb7c2a2f185f88ead11cef314d5f - Sigstore transparency entry: 2117201653
- Sigstore integration time:
-
Permalink:
openguardrails/openguardrails-instrumentation-langgraph@9be0a6bfcfdda73b6141153c45ca078c04222b61 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/openguardrails
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9be0a6bfcfdda73b6141153c45ca078c04222b61 -
Trigger Event:
release
-
Statement type:
File details
Details for the file openguardrails_instrumentation_langgraph-0.1.1-py3-none-any.whl.
File metadata
- Download URL: openguardrails_instrumentation_langgraph-0.1.1-py3-none-any.whl
- Upload date:
- Size: 11.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bcf1db7dc65cd82a45614fb40c52a0fee4ddfc56c0f93c740a07dffaf90cb85d
|
|
| MD5 |
6d69010f6ec3a4bf109f6ed565fba1a9
|
|
| BLAKE2b-256 |
f494a171a1e85a2f6aef6615ca1f0904038fd06afcc762d91cf20edb0480dddf
|
Provenance
The following attestation bundles were made for openguardrails_instrumentation_langgraph-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on openguardrails/openguardrails-instrumentation-langgraph
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
openguardrails_instrumentation_langgraph-0.1.1-py3-none-any.whl -
Subject digest:
bcf1db7dc65cd82a45614fb40c52a0fee4ddfc56c0f93c740a07dffaf90cb85d - Sigstore transparency entry: 2117201736
- Sigstore integration time:
-
Permalink:
openguardrails/openguardrails-instrumentation-langgraph@9be0a6bfcfdda73b6141153c45ca078c04222b61 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/openguardrails
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9be0a6bfcfdda73b6141153c45ca078c04222b61 -
Trigger Event:
release
-
Statement type: