intutic_clawde.gate.adapters.langchain
LangChain adapter: IntuticMiddleware, plus notes on the pre-1.0 path.
LangChain v1.x has a documented middleware veto point: AgentMiddleware.wrap_tool_call(request, handler) — call handler(request) to allow the call through, or return a ToolMessage WITHOUT calling handler to veto it. IntuticMiddleware below implements exactly that, calling the same Gate.guard(tool_name, tool_input) core framework.py uses.
Verified live (not inferred from docs) by installing langchain==1.3.15 / langchain-core==1.5.6 in a scratch venv and reading langchain.agents.middleware.types.ToolCallRequest and langchain_core.messages.ToolMessage directly:
request.tool_callis a{"name", "args", "id"}dict — always present (unlikerequest.tool, which isNonefor an unregistered tool).ToolMessage(content=..., tool_call_id=..., status="error")is a valid veto payload; LangChain does not require callinghandlerfirst.
Pre-1.0 LangChain / plain LangGraph tool objects: this middleware is a v1.x-only mechanism (AgentMiddleware did not exist before it). For pre-1.0 LangChain and for LangGraph itself, keep using the framework-agnostic guard_tools(tools) helper in intutic_clawde.gate — it already duck-types .func (StructuredTool/@tool) and ._run (BaseTool subclasses) without importing langchain at all, and needs no change here; tests/test_gate_framework.py's TestGuardTools class is its regression coverage. Use whichever path matches your LangChain version; both funnel into the same Gate.guard() decision.
Optional import: importing this module never fails even without langchain installed. Only instantiating IntuticMiddleware requires it — pip install intutic-clawde[langchain].
Classes
IntuticMiddleware
class IntuticMiddleware(*, gate: Optional[Gate] = None)Bases: AgentMiddleware
LangChain v1.x AgentMiddleware — vetoes a tool call before it runs.
Usage:
from langchain.agents import create_agent
from intutic_clawde.gate.adapters.langchain import IntuticMiddleware
agent = create_agent(model, tools, middleware=[IntuticMiddleware()])On deny, returns a ToolMessage(status="error") carrying the [Intutic Governance] BLOCKED: ... message, WITHOUT calling the handler — the tool body never runs, matching every other adapter's "deny raises/returns before the body runs" contract.
Methods
IntuticMiddleware.wrap_tool_call()
def wrap_tool_call(request: Any, handler: Any) -> Any