2025-12-31 · Authensor

How to Add Safety Gating to LangChain Agents

SafeClaw by Authensor intercepts every tool invocation in LangChain's agent execution loop, enforcing deny-by-default policies before any tool's _run() method executes. LangChain agents decide which tools to call and with what arguments — SafeClaw sits between that decision and the actual execution, evaluating each call against your YAML policy in sub-millisecond time.

How LangChain Tool Calling Works

LangChain agents use a Tool abstraction where each tool has a name, description, and a _run() method. The agent (whether using AgentExecutor, create_react_agent, or the newer langgraph patterns) sends the available tools to the LLM, receives a tool call decision, and invokes the tool. The execution flow is: LLM output -> parse tool call -> invoke tool -> return observation. SafeClaw intercepts at the "invoke tool" step.

LLM Decision → Tool Name + Args → [SafeClaw Policy Check] → tool._run() or Deny

Quick Start

npx @authensor/safeclaw

Creates a safeclaw.yaml in your project root. SafeClaw maps LangChain tool names directly to policy rules.

Step 1: Define Policies for LangChain Tools

# safeclaw.yaml
version: 1
default: deny

policies:
- name: "langchain-search-tools"
description: "Allow search and retrieval tools"
actions:
- tool: "tavily_search"
effect: allow
- tool: "retriever"
effect: allow
- tool: "wikipedia"
effect: allow

- name: "langchain-code-tools"
description: "Control code execution tools"
actions:
- tool: "python_repl"
effect: allow
constraints:
blocked_imports: ["os", "subprocess", "shutil", "socket"]
- tool: "shell"
effect: deny

- name: "langchain-data-tools"
description: "Control database access"
actions:
- tool: "sql_database"
effect: allow
constraints:
operation: "SELECT"
- tool: "sql_database"
effect: deny
constraints:
operation: "DROP|DELETE|TRUNCATE|ALTER"

- name: "langchain-file-tools"
description: "Restrict file operations"
actions:
- tool: "read_file"
effect: allow
constraints:
path_pattern: "data/|docs/"
- tool: "write_file"
effect: allow
constraints:
path_pattern: "output/**"
- tool: "file_delete"
effect: deny

Step 2: Wrap LangChain's Tool Execution

SafeClaw integrates as a wrapper around LangChain's tool invocation:

from langchain.tools import Tool
from langchain.agents import AgentExecutor, create_react_agent
from safeclaw import SafeClaw

safeclaw = SafeClaw("./safeclaw.yaml")

Wrap each tool with SafeClaw gating

def gate_tool(tool: Tool) -> Tool: original_run = tool._run

def safe_run(args, *kwargs):
decision = safeclaw.evaluate(tool.name, {"args": args, "kwargs": kwargs})
if not decision.allowed:
return f"Action denied by SafeClaw: {decision.reason}"
return original_run(args, *kwargs)

tool._run = safe_run
return tool

Apply to all tools

tools = [gate_tool(t) for t in [search_tool, sql_tool, file_tool]]

agent = create_react_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

Step 3: Integrate with LangGraph Agents

For LangGraph-based agents (the newer pattern), SafeClaw integrates at the tool node:

from langgraph.prebuilt import ToolNode
from safeclaw import SafeClaw

safeclaw = SafeClaw("./safeclaw.yaml")

def safe_tool_node(state):
"""Custom tool node with SafeClaw gating."""
messages = state["messages"]
last_message = messages[-1]

results = []
for tool_call in last_message.tool_calls:
decision = safeclaw.evaluate(tool_call["name"], tool_call["args"])

if decision.allowed:
tool = tool_map[tool_call["name"]]
result = tool.invoke(tool_call["args"])
results.append(ToolMessage(content=str(result), tool_call_id=tool_call["id"]))
else:
results.append(ToolMessage(
content=f"Denied: {decision.reason}",
tool_call_id=tool_call["id"]
))

return {"messages": results}

Use safe_tool_node instead of ToolNode in your graph

graph.add_node("tools", safe_tool_node)

Step 4: Handle Agent Loops

LangChain agents iterate until they reach a final answer. SafeClaw evaluates each tool call independently across iterations, and the audit log captures the full reasoning chain:

# The agent loop calls tools multiple times

SafeClaw evaluates each independently

result = executor.invoke({"input": "Analyze Q4 sales data and write a report"})

Review what happened

npx @authensor/safeclaw audit --last 20

Step 5: Policy Per Chain

Different LangChain chains can use different SafeClaw policies:

# Research chain — broad read access
research_safeclaw = SafeClaw("./policies/research.yaml")

Action chain — restricted write access

action_safeclaw = SafeClaw("./policies/action.yaml")

Why SafeClaw

Related Pages

Try SafeClaw

Action-level gating for AI agents. Set it up in your browser in 60 seconds.

$ npx @authensor/safeclaw