LangGraph
Integrate Intutic governance with LangGraph — LangChain's graph-based agent framework.
LangGraph is governed on two independent surfaces:
- LLM egress — point the model client's
base_urlat the Intutic proxy (or launch underintutic exec). Every LLM call crosses the proxy and is governed like any other harness. - Local tool execution — LangGraph tools are plain Python callables running inside your process. No config or hook file can gate them, so the blocking gate ships SDK-side in
intutic-clawdeand wraps your tools in-process.
How it works
The langgraph adapter is detected when pyproject.toml, requirements.txt, or uv.lock declares a langgraph (or langchain) dependency. Like Codex, it writes a .env.intutic file with proxy base-URL env vars — plus a generated comment block pointing at the SDK gate, because env vars govern egress but not local tool execution.
Setup
1. Initialize Intutic
intutic init ✔ langgraph → /path/to/project/.env.intuticintutic init only detects the harness and records it in ~/.intutic/config.json; it writes no harness files. The files described on this page are written by intutic gates install (open core, no control plane), from the rule sets in .intutic/sops/ — see What writes harness files:
intutic gates install2. Route LLM traffic through the proxy
Either source the generated env file:
source .env.intutic
python my_agent.py…or launch under intutic exec, which injects the same base-URL env vars for the child process:
intutic exec -- python my_agent.py…or set base_url explicitly on the model client:
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from typing import TypedDict
# 1. Initialize LLM pointing at the Intutic proxy
llm = ChatOpenAI(
model="gpt-4o",
base_url="http://localhost:4000/v1", # or your hosted proxy
api_key="your-intutic-api-key",
)
class AgentState(TypedDict):
input: str
response: str
def call_model(state: AgentState):
# This call is governed pre-flight by Intutic
res = llm.invoke(state["input"])
return {"response": res.content}
# Compile Graph
builder = StateGraph(AgentState)
builder.add_node("agent", call_model)
builder.add_edge(START, "agent")
builder.add_edge("agent", END)
graph = builder.compile()LangGraph.js works the same way:
import { StateGraph, START, END } from "@langchain/langgraph";
import { ChatOpenAI } from "@langchain/openai";
const model = new ChatOpenAI({
model: "gpt-4o",
configuration: {
baseURL: "http://localhost:4000/v1", // or your hosted proxy
apiKey: "your-intutic-api-key",
}
});3. Gate local tool execution (SDK)
pip install intutic-clawdeWrap your tools before handing them to the graph:
from intutic_clawde.gate import Gate, GateConfig, install, guard_tools
install(Gate(GateConfig()))
# LangChain/LangGraph tool objects are wrapped in place (duck-typed — no
# langchain import inside the SDK). Plain callables work too.
tools = guard_tools([shell_tool, write_file_tool, deploy_tool])Or gate a single callable with the decorator:
from intutic_clawde.gate import guard
@guard
def shell(command: str) -> str:
...The python-raise contract: on deny the gate raises IntuticGateRefusal before the tool body runs, with a message beginning [Intutic Governance] BLOCKED: — the same prefix family as the Open WebUI filter, so anything that already recognises that contract recognises this one. The gate evaluates, in order:
- the workspace's SSO group policy for the member the snapshot was issued to, carried in the policy snapshot,
- the compiled policy snapshot (
~/.intutic/hooks/policy-snapshot.rules) — the same artifact the sync-daemon compiles for every shipped harness, - the workspace's MCP server registry and
mcpAllowedServerslist, from the same snapshot, on anmcp__<server>__<tool>call, - SOP rules authored in the product, and the workspace's
hook_rulepolicy guardrails for your member role, including the argument-levelWHEREclause (argPattern) — the gate sees the tool's full rendered arguments, with positionals bound to their parameter names so an argPattern cannot be dodged by calling convention, - container image provenance on deploy commands,
- the control plane's hook-gate (
POST /api/v1/hook-gate) — fail-closed by default: if the control plane cannot be reached, or answersGOVERNANCE_UNAVAILABLEbecause one of its checks could not run, the call is refused rather than waved through.fail_closed=Falseon the client lets such calls run; a 400INVALID_REQUESTis refused either way.
Steps 4 and 6 need a GateClient: install(Gate(GateConfig(), GateClient.from_env(session_id=run_id, harness="langgraph"))). A Gate built without one runs the local steps only. from_env() reads INTUTIC_API_KEY or the key a control-plane login stored; with no key the client is local-only, and the hook gate's answer is an allow with code LOCAL_ONLY. See Tool Gate SDK.
With a client, every decision is reported via /api/v1/hook-events, so gated LangGraph runs appear in the same audit feed as hook-based harnesses.
4. Trace attribution
The proxy attributes OpenAI-shaped traffic to a harness by wire protocol unless the client says who it is. Send the x-intutic-harness: langgraph header so traces file under LangGraph (the proxy honours it; older proxies ignore unknown headers, so it is always safe to send). The SDK builds the right headers for you:
from intutic_clawde.gate import intutic_headers
llm = ChatOpenAI(
base_url="http://localhost:4000/v1",
default_headers=intutic_headers(session_id=run_id),
)intutic_headers also sets x-session-id — worth doing, because the proxy defaults an unset session ID to the literal "unknown", which merges every run into a single dashboard session.
What gets written
A .env.intutic file with proxy URLs and a pointer at the SDK gate:
# Intutic Governance Rules (auto-generated)
# DO NOT EDIT — managed by intutic sync daemon
# Last sync: 2026-08-10T00:00:00Z
# Source this file: source .env.intutic
export ANTHROPIC_BASE_URL="http://localhost:4000"
export OPENAI_BASE_URL="http://localhost:4000/v1"
export INTUTIC_PROXY_URL="http://localhost:4000"
export INTUTIC_SOP_COUNT=5
# These env vars govern LLM egress only. LangGraph tools run in your own
# Python process, where no config or hook file can gate them — the
# blocking tool gate ships SDK-side:
# pip install intutic-clawde
# from intutic_clawde.gate import guard_tools
# guard_tools(tools) refuses denied calls by raising, before the tool
# body runs. See https://docs.intutic.ai/integrations/langgraphTo undo what intutic gates install writes here, run intutic disconnect --harness langgraph: each file goes back to what it held before Intutic first wrote it, or is deleted if Intutic created it, and edits you made since are kept. .env.intutic stays while another harness that writes it is still connected. See intutic disconnect.
What the adapter does NOT do
Stated plainly, because the gaps are structural:
- There is no sync-daemon hook file. Every hook-based harness has an on-disk config the daemon writes a pre-tool gate into; LangGraph has none — tools are Python callables inside your process. The daemon cannot gate what it cannot reach from disk. The adapter's registry entry records this deliberately (
NO_GATEin the sync-daemon's gate registry) rather than leaving it to look like an oversight. - Argument-level gating requires the SDK wrapper in your code. Without
guard_tools/@guard, the only tool enforcement is the proxy's response gate (Vector D), which withholds model-emitted denied tool calls (by name only, on streams) and those an enforcinghook_ruleguardrail refuses, arguments included — but cannot see a tool your code invokes directly. Argument-level SOP rules (argPattern) evaluate against a tool call's full arguments only inside the SDK gate. - Attribution is client-supplied.
x-intutic-harnessscopes traces and observability; it is not authorization, and a client that lies about its harness is filed as what it claims. Authorization stays bound to the virtual key.
Multi-agent graphs
For per-node identity (node_id, agent_role, graph_id, parent_session_id, depth), graph-wide budgets, and ordering constraints across nodes, see Graph Guardrails.
Config details
| Property | Value |
|---|---|
| Harness type | langgraph |
| Config file | .env.intutic |
| Detection | langgraph/langchain in pyproject.toml, requirements.txt, or uv.lock |
| Format | Shell environment variables |
| Write strategy | Atomic (write to .intutic-tmp, then rename) |
| Tool gate | SDK-side (intutic_clawde.gate, python-raise) — no sync-daemon hook file |