LangChain
The LangChain adapter wraps anAgentExecutor (or any Runnable that fires LangChain callbacks) so every tool invocation, LLM call, and chain step is recorded as a governance event.
If you’re using LangGraph specifically, the LangGraph adapter is more direct. Use the LangChain adapter for classic AgentExecutor-based agents, custom Runnable pipelines, or when you need tool-call-level granularity that the LangGraph adapter doesn’t provide.
Install
langchain and langchain-core if not already present.
Supported LangChain versions: 0.2.x - 0.3.x. Install with a LangChain version in this range; the version constraint is pinned in
sdk/pyproject.toml.Quick start
governed has the same interface as the underlying executor. ainvoke and invoke both work; tool calls inside the executor are recorded automatically.
How it works
The adapter attaches anAsyncCallbackHandler to the executor’s config. This handler fires on:
on_tool_start— every tool call before it executeson_tool_end— every tool call after it completeson_tool_error— if the tool raisedon_chain_start/on_chain_end— top-level executor invocations
tool_call events with the tool name as action_name and the input as the payload. Tool results are attached as tool_result events.
Unlike the LangGraph adapter (which records node-level state), the LangChain adapter records at tool-call granularity. If your agent calls search_web, then calculate_offer, then send_email, those are three separate events — and each one passes through the policy engine before executing.
What gets recorded
For each tool call:tool_callevent with tool name asaction_name, tool input as payload, with the agent’s identity inagent_nametool_resultevent with the tool’s output (truncated if very large)tool_errorevent if the tool raised, with the exception type and message
chain_start/chain_endat the top level marking the executor invocation boundaries
Policy enforcement
Pre-execution policy checks happen aton_tool_start. If a tool call is denied:
require_approval decisions, the callback blocks until the approver responds. The agent appears to “pause” mid-execution — your await governed.ainvoke(...) is waiting for the human. On approval the tool runs; on denial or timeout it raises.
Identifying the agent
The adapter derives theagent_name for recorded events from the executor’s class name at wrap time — specifically type(agent_executor_or_chain).__name__. So a vanilla AgentExecutor shows up as "AgentExecutor" in the audit trail.
There’s no govern() kwarg to override the agent name in this version. If you need a more descriptive label, two options:
- Subclass
AgentExecutorwith a meaningful class name (class SupportAgent(AgentExecutor): ...), then wrap your subclass - Use the framework-agnostic
proofrail.Chaindirectly and callrecord_agent_action(agent_name="support-agent-v2", ...)yourself for full control
Configuring the chain
chain_name appears in your dashboard and identifies the workflow type. metadata is arbitrary key/value pairs attached to every invocation by this governed executor.
Passing LangChain config
Standard LangChain config (other callbacks, run names, tags) is preserved:Sync invocation
RuntimeError if called from inside a running event loop. In that case use await governed.ainvoke(...).
Tool input extraction
The adapter captures the input passed to each tool and records it in the payload:dictinputs pass through unchangedstrinputs are wrapped as{"tool_input": "..."}- Pydantic model inputs call
.model_dump()(v2) or.dict()(v1) - Other types fall back to
str(input)(truncated)
proofrail.init(). See Configuration Reference for both sensitive_field_patterns (key-name matching) and sensitive_value_patterns (value-prefix matching).
Edge cases
ReAct-style agents. Agents using the ReAct pattern (think → act → observe loops) work normally. Each “act” step’s tool call is a separate event. Multiple tool calls in parallel. If your agent uses parallel tool calling (modern OpenAI/Anthropic models), each parallel call is recorded as a separate event. Policy evaluation happens per-call; one denial doesn’t automatically halt others already in flight. Tools that themselves call agents. Nested agent calls (one agent’s tool is another agent’s executor) work, but currently flatten into the same chain. There’s no automatic parent-child relationship in the audit trail. Streaming responses.astream works but the streaming output isn’t customized for governance. Events are recorded as they happen; the stream itself is unchanged from the underlying executor.
Custom runnables. Any Runnable that fires standard LangChain callbacks works with govern(), not just AgentExecutor. Custom chains, sequential pipelines, and Runnable graphs are all supported as long as they emit on_tool_start / on_tool_end callbacks.
Tool wrapping vs. agent wrapping. govern() wraps the executor, not individual tools. If you want to govern a single tool outside an agent context, instrument it manually with proofrail.Chain.record_agent_action instead.
Where to go next
LangGraph adapter
For compiled-graph workflows with node-level granularity.
MCP adapter
Govern MCP server tool calls at the protocol level.
Policies
What decisions get applied to your tool calls.
SDK reference
The complete
govern() signature and options.