Skip to main content

LangGraph

VigilGraphHook in adapters/langgraph/vigil_checkpoint.py is a LangChain callback handler (BaseCallbackHandler) that turns a graph run into VIGIL events, built on vigil-sdk:

  • output_generated when each node finishes: {"node": <name>, "content_length": <bytes of the node output>}. LangGraph tags node runs with metadata["langgraph_node"]; the parent graph run has no node name and is not counted.
  • config_write when a configurable key changes between node runs. LangGraph copies primitive configurable values into run metadata, so a node that rewrites its own model or temperature produces {"key", "value", "previous", "operation": "write", "initiated_by": "agent"}. Call hook.config_write(key, value, initiated_by="operator") for mutations the callbacks cannot see.

Every event is source: platform_hook, actor: agent:<agent_id>. Emission failures go to on_error and never interrupt the graph; gates go to on_gate and hook.last_gate. langchain_core is imported lazily.

Wire it

import os
from vigil import Vigil
from adapters.langgraph.vigil_checkpoint import VigilGraphHook

vg = Vigil(api_key=os.environ["VIGIL_API_KEY"])
vg.register_agent("planner", "Planner", exist_ok=True)

config = {"configurable": {"thread_id": "t1", "model": "claude-sonnet"}}
hook = VigilGraphHook(vg, agent_id="planner", config=config)
result = graph.invoke(state, config={**config, "callbacks": [hook]})

if hook.last_gate and hook.last_gate.held:
... # park the result; vg.wait_for_release(hook.last_gate.action_id)

Try it

adapters/langgraph/example.py runs a two-node graph whose second node rewrites the model key when langgraph is installed, otherwise it drives the same callbacks by hand. Either way you see two output_generated events and one config_write.

VIGIL_API_KEY=vg_... python -m adapters.langgraph.example

The config_write the hook sends when the act node changes model from claude-sonnet to gpt-4o, as raw HTTP:

curl -sf -X POST "$VIGIL_URL/agents/" -H "Authorization: Bearer $VIGIL_KEY" -H "Content-Type: application/json" \
-d '{"agent_id":"docs-langgraph","name":"Docs LangGraph"}' > /dev/null || true
curl -sf -X POST "$VIGIL_URL/events/" -H "Authorization: Bearer $VIGIL_KEY" -H "Content-Type: application/json" \
-d '{"agent_id":"docs-langgraph","event_type":"config_write","source":"platform_hook","actor":"agent:docs-langgraph",
"payload":{"key":"model","value":"gpt-4o","previous":"claude-sonnet","operation":"write","initiated_by":"agent"}}'

model is not a protected key, so one agent-initiated write weighs 1.0 and the tamper signal drops without collapsing. Register model in the authorization registry with allowed_actors: ["user:*"] and the same event is judged by the registry instead.