How to Govern LangGraph Agents
LangGraph gives you durable, stateful agent graphs with checkpointers and interrupts. It controls the shape of execution, not whether a given action is allowed to run. Cordum adds an out-of-process Safety Kernel that evaluates policy before each tool-execution edge dispatches.
The problem with ungoverned LangGraph agents
- LangGraph decides control flow, not authority. A StateGraph routes the agent from node to node, but when a tool node fires, the call executes with whatever permissions the process holds. The graph has no concept of a policy that should block, constrain, or hold that action.
- interrupt() and Command give you a place to pause, but the pause is wired into application code. Every team rebuilds its own human-in-the-loop logic, and the decision to require a human is a branch in the graph rather than an enforceable, centrally managed rule.
- Checkpointers persist state so a run can resume, yet they record what the graph did, not why an action was permitted. There is no policy-decision evidence linking a tool call to the rule that allowed it.
- Subgraphs and conditional edges make real runs branch widely. One high-risk tool buried three levels down in a subgraph executes the moment the model routes to it, with no out-of-process checkpoint in between.
- Guardrail logic written inside nodes couples policy to the graph definition. Changing what an agent is allowed to do means editing and redeploying the graph instead of updating a policy bundle.
How Cordum governs LangGraph
Submit the tool-execution edge through Cordum
Keep your StateGraph exactly as it is, but have the node that runs tools submit each call as a Cordum job over CAP v2 instead of invoking the tool directly. The Safety Kernel evaluates the requested action against your policy bundle before dispatch, so the graph only proceeds once the call is allowed.
from langgraph.graph import StateGraph, END
from cordum import CordumClient
cordum = CordumClient()
# Tool-execution node: every tool call is governed before it runs
async def execute_tools(state):
results = []
for call in state["pending_tool_calls"]:
result = await cordum.jobs.submit({
"type": "langgraph.tool.call",
"payload": {"tool": call.name, "args": call.args},
"metadata": {"thread_id": state["thread_id"], "node": "execute_tools"},
})
results.append(result)
return {"tool_results": results}
graph = StateGraph(AgentState)
graph.add_node("agent", call_model)
graph.add_node("execute_tools", execute_tools)
graph.add_conditional_edges("agent", route_tools, {"tools": "execute_tools", "end": END})
graph.add_edge("execute_tools", "agent")Replace interrupt() with policy-bound approvals
Where you would have hard-coded an interrupt() before a sensitive action, let policy decide. A REQUIRE_APPROVAL decision pauses the job until an approver acts, and the approval is bound to the exact policy snapshot that produced it, so the human-in-the-loop gate lives in your policy bundle instead of in graph code.
# cordum-policy.yaml
rules:
- name: approve-external-writes
match:
topics: ["job.langgraph.tool.call"]
capabilities: ["email-send", "db-write"]
action: REQUIRE_APPROVAL
approvers: ["ops-oncall"]
- name: deny-prod-delete-after-hours
match:
capabilities: ["db-delete"]
labels:
env: prod
action: DENYConstrain instead of branching
For actions that should run but only within limits, ALLOW_WITH_CONSTRAINTS returns a scoped constraint payload alongside the allow, so the graph keeps flowing while file paths, record counts, or destination allowlists are bounded. You express the boundary once as a rule rather than as conditional edges scattered across the graph.
Audit the full run, including subgraphs
Every governed tool-execution edge writes a structured pre/post record with the thread, node, decision, matching rule, and approval reference. Because governance is out-of-process, a high-risk call inside a deeply nested subgraph lands in the same queryable run timeline as a top-level one, giving compliance a single source of truth instead of per-run checkpointer dumps.
LangGraph native vs Cordum governance
| Area | LangGraph Native | With Cordum |
|---|---|---|
| Pre-dispatch policy check | Checkpointers persist state but do not gate actions | Out-of-process Safety Kernel evaluates policy before each tool-execution edge dispatches |
| Human-in-the-loop | interrupt() / Command, wired into graph code | REQUIRE_APPROVAL decided by policy, not hard-coded in the graph |
| Approval integrity | Resumed via checkpointer state, no policy linkage | Approvals bound to the policy snapshot that produced them |
| Constrained execution | Conditional edges and node logic | ALLOW_WITH_CONSTRAINTS scopes the action declaratively |
| Subgraph governance | Each subgraph governs itself in node code | Policy-before-dispatch applies uniformly across nested subgraphs |
| Audit trail | Checkpointer history (execution state) | Structured run timeline with policy decisions and evidence |
FAQ
Do I have to rewrite my StateGraph?
No. You keep your nodes, edges, and state schema. The only change is that the node which executes tools submits each call through Cordum instead of invoking it directly, so the graph topology stays the same while every tool-execution edge becomes governed.
How does Cordum relate to LangGraph's interrupt() and Command?
They solve different problems. interrupt() and Command control where a graph pauses and resumes; Cordum decides whether an action is allowed and whether a human must approve it. You can keep interrupt() for purely conversational pauses and let Cordum policy handle the security-relevant approvals so that gate is centrally managed.
Does this work with LangGraph checkpointers?
Yes. Checkpointers continue to persist and resume graph state as normal. Cordum governs the tool-execution boundary out of process, so the two are complementary: the checkpointer remembers where the run was, and Cordum's run timeline records why each action was permitted.
What makes the policies deterministic?
Policy decisions come from a version-controlled bundle evaluated by the Safety Kernel, not from model reasoning inside the graph. The same job context produces the same decision, and approvals are bound to the policy snapshot in effect, so behavior does not drift between runs.
Related guides
Ready to govern your LangGraph agents?
Start with the open-source Cordum platform. Add policies, approvals, and audit trails in minutes.