Guardian-Graph is a reliability layer for LangGraph applications. It validates state transitions against strict Pydantic contracts, detects runaway loops, and emits structured events you can route to your observability stack.
from guardian_graph import wrap_with_guardian
from pydantic import BaseModel
class MyState(BaseModel):
query: str
answer: str | None = None
class DemoGraph:
def invoke(self, state: dict) -> dict:
return {**state, "answer": "hello"}
guarded_graph = wrap_with_guardian(DemoGraph(), schema=MyState)
result = guarded_graph.invoke({"query": "hello"})docs/index.md— documentation map and quickstartdocs/getting-started.md— install and first guarded graphdocs/concepts.md— validation, loop guard, judge, auto-healdocs/api-reference.md— public API referencedocs/events.md— event schema referencedocs/integrations.md— LangSmith, Postgres, FastAPI ingestion
from guardian_graph import wrap_with_guardian
# `base_graph` is your compiled LangGraph graph with an `.invoke(...)` method.
guarded_graph = wrap_with_guardian(
base_graph,
schema=MyState,
max_steps=20,
stagnation_limit=5,
)
result = guarded_graph.invoke({"query": "hello"})max_steps is enforced across the full .invoke(...) execution (counting steps
across nodes) to prevent runaway tool loops.
def repair(prompt: str, bad_state: dict) -> dict:
# Replace with a model call; keep return value JSON-serializable.
return {**bad_state, "answer": "repaired"}
guarded_graph = wrap_with_guardian(
base_graph,
schema=MyState,
repair=repair,
repair_retries=2,
)from guardian_graph import CompositeEmitter, JsonlFileEmitter
file_emitter = JsonlFileEmitter("logs/guardian.jsonl")
base_graph = DemoGraph()
guarded_graph = wrap_with_guardian(
base_graph,
schema=MyState,
emitter=CompositeEmitter([file_emitter]),
)from guardian_graph.core.judge import JudgeDecision, JudgeInput, JudgeResult
from guardian_graph import wrap_with_guardian
def judge(input: JudgeInput) -> JudgeResult:
if "risky" in input.state.get("answer", ""):
return JudgeResult(decision=JudgeDecision.block, rationale="unsafe output")
return JudgeResult(decision=JudgeDecision.allow)
base_graph = DemoGraph()
guarded_graph = wrap_with_guardian(base_graph, schema=MyState, judge=judge)from guardian_graph import interrupt_human_approval
def dangerous_tool(state: dict) -> dict:
interrupt_human_approval(action="delete_users", payload={"ids": [1, 2, 3]})
return {}Guardian-Graph can return a LangGraph Postgres checkpointer when LangGraph is installed:
from guardian_graph import get_postgres_checkpointer
checkpointer = get_postgres_checkpointer("postgresql://user:pass@localhost:5432/db")If langgraph.checkpoint.postgres is not installed, the helper raises a clear
ImportError with guidance.
See docs/integrations.md for LangSmith, Postgres checkpointers, and FastAPI event ingestion.
python -m pytest
ruff check .
