LangChain Agent Governance For developers

LangChain tool calls need a checkpoint. Gate is the checkpoint.

LangChain agents decide what to do. Gate decides whether they're allowed to do it. Every tool call goes through Gate first — evaluated against your YAML policy before the tool runs. No LLM judgment in the policy path. No exceptions.

How to add governance to LangChain tools

Wrap each tool with GateTool from @zehrava/langchain-gate. Gate intercepts the tool call before execution, submits it as a governed intent, evaluates it against a YAML policy, and returns one of three decisions:

Policy evaluation is deterministic. The same input always produces the same output. Gate reads field values, checks rate limits, and applies environment-specific thresholds from a YAML file you control. It does not call an LLM to decide. Install: npm install @zehrava/langchain-gate zehrava-gate. Self-hosted. No data leaves your infrastructure.

Install and wrap a tool in 12 lines

npm install @zehrava/langchain-gate zehrava-gate
const { Gate } = require('zehrava-gate');
const { GateTool, GateBlockedError } = require('@zehrava/langchain-gate');

const gate = new Gate({ endpoint: 'http://localhost:3001', apiKey: process.env.GATE_API_KEY });

const governed = new GateTool({
  tool: emailTool,              // your existing LangChain Tool
  gate,
  policy:      'outbound-email',
  destination: 'sendgrid.send',
});

// Drop-in replacement — same interface as the original tool
try {
  const result = await governed._call(JSON.stringify({ to: 'user@co.com', subject: '...' }));
} catch (err) {
  if (err instanceof GateBlockedError) {
    console.error('Blocked:', err.intentId, err.blockReason);
  }
}

The wrapped tool implements the same _call(input) interface. Pass governed to your agent exactly where you'd pass emailTool. No other changes.

What Gate evaluates

Gate evaluates YAML policies you define. Evaluation runs in this order:

  1. Destination allowlist — is this tool allowed to call this endpoint?
  2. Field checks — required, max, min, max_length, pattern on JSON fields
  3. Environment overrides — different thresholds for production vs staging vs development
  4. Rate limits — per_agent_per_hour and per_agent_per_day via SQLite counts
  5. Sensitive term scan — normalized matching catches obfuscated variants
  6. Record count thresholds — auto-approve under N, require approval over N
# policies/outbound-email.yaml
id: outbound-email
destinations: [sendgrid.send, loops.send]

field_checks:
  - path: "to"
    required: true
    pattern: "^[^@]+@[^@]+\\.[^@]+$"

rate_limits:
  per_agent_per_hour: 50
  per_agent_per_day:  200

environments:
  production:
    require_approval: always
  development:
    require_approval: never

Three decision paths

Gate decision What happens Your code sees
approved Tool executes immediately Tool return value
blocked Tool does not execute GateBlockedError
pending Gate polls for human approval; executes on approval Tool return value (after delay) or GateTimeoutError

Wrap multiple tools at once

const { GateToolkit } = require('@zehrava/langchain-gate');

const toolkit = new GateToolkit({
  tools: [emailTool, crmTool, slackTool],
  gate,
  policies: {
    'send-email':  'outbound-email',
    'crm-update':  'crm-low-risk',
    '__default__': 'crm-low-risk',   // fallback for unmapped tools
  },
  destinations: {
    'send-email':  'sendgrid.send',
    'crm-update':  'salesforce.api',
    '__default__': 'generic.http',
  },
});

// Use governedTools exactly where you'd pass the originals
const governedTools = toolkit.getTools();

LangGraph: branch on Gate's decision

Gate provides two StateGraph primitives. Use gateNode to submit an intent as a node. Use gateRouteAfter to branch on the result.

const { gateNode, gateRouteAfter } = require('@zehrava/langchain-gate');

graph.addNode('check_gate', gateNode({
  gate,
  buildIntent: (state) => ({
    payload:     JSON.stringify(state.crmPayload),
    destination: 'salesforce.api',
    policy:      'crm-low-risk',
    recordCount: state.records.length,
  }),
}));

// gateNode writes: gateIntentId, gateStatus, gateBlockReason into state
graph.addConditionalEdges('check_gate', gateRouteAfter('gateStatus'), {
  execute:  'run_tool',        // approved
  blocked:  'handle_blocked',  // blocked | duplicate_blocked
  pending:  'notify_human',    // pending_approval
  __end__:  END,
});

What Gate does not do

Gate is a deterministic policy engine, not an LLM-based safety layer.

Gate protects against mistakes and unauthorized actions. It's a commit checkpoint, not a jailbreak defense.


Add Gate to your LangChain project

Self-hosted. MIT license. Running in 5 minutes.

View on GitHub Read the docs