New to the adapters? Start with the wrapping overview and comparison
table.
Compatible versions: see the compatibility
table for the verified
langchain range. deepagents rides this adapter but has its own row and range
in that table — check the deepagents row, not the langchain one.wrap_langchain_agent builds a PolicyEnforcer once and installs it on each tool
in place (install_enforcer_on_tool) so the same instances inside the compiled
graph become policy-gated. It returns a HexgateLangchainAgent proxy that opens a
HexgateContext scope and injects a Langfuse callback into every invoke / ainvoke /
stream / astream / astream_events call. The hexgate_context is supplied per call,
so a single wrapped agent can serve many users concurrently — role resolution
happens at call time from the contextvar.
Under the hood
wrap_langchain_agentbuilds aPolicySetfor the agent, constructs onePolicyEnforcer(policy_set, agent_name=…), and callsinstall_enforcer_on_tools(tools, enforcer=…)to mutate each tool’sfuncandcoroutinewith enforcer-gated closures.handle_tool_erroris forced toTrue. Installation is idempotent — re-installing rebinds the captured originals to the new enforcer without stacking gates.- Each invocation method on
HexgateLangchainAgenttakeshexgate_context=and opens anasync with hexgate_context:(orhexgate_context.sync_scope()for sync) around the delegatedCompiledStateGraphcall. The active context is pushed onto a contextvar; the guards read it at tool-call time to resolve the matching role’s policy. - A non-allow
Decisionis rendered as{"ok": False, "error": decision.as_error_payload()}so the LangChain runtime surfaces the structured dict as the tool result instead of raising. - The wrapper also enters
propagate_attributes(...)and merges a LangfuseCallbackHandlerinto theRunnableConfig.callbacksfor the duration of the call. Anything not explicitly proxied falls through via__getattr__.
Because LangChain BYO-graph tools are mutated in place by design, the same
tools list you pass to create_react_agent flows through already gated — the
wrapper holds the policy.The policy is resolved by agent name, so pass
name="devops_agent" to
create_react_agent — it must match the agent you registered on the platform.
Omit it and the compiled graph keeps LangGraph’s default name ("LangGraph"),
so the wrapper resolves the policy for an agent called LangGraph and fails
with a 404 unless one is registered under that name.Runnable example
examples/devops_langchain.py — wrap_langchain_agent (LangChain / LangGraph)
end-to-end.