Last week we needed to add approval gates to a LangChain agent that sends customer emails. The agent worked fine. The problem was it worked too fine, and nobody reviewed what it was sending.
Here’s the before-and-after.
Before: ungoverned tool call
import { DynamicStructuredTool } from "@langchain/core/tools";
import { z } from "zod";
const sendEmailTool = new DynamicStructuredTool({
name: "send_email",
description: "Send an email to a customer",
schema: z.object({
to: z.string().email(),
subject: z.string(),
body: z.string(),
}),
func: async ({ to, subject, body }) => {
await emailService.send({ to, subject, body });
return `Email sent to ${to}`;
},
});
That tool does what the LLM tells it. No questions asked. If the agent hallucinates a recipient or drafts something unhinged, it ships.
After: governed tool call with SidClaw
import { governTools } from "@sidclaw/sdk/langchain";
import { SidClawClient } from "@sidclaw/sdk";
const sc = new SidClawClient({
baseUrl: "https://app.sidclaw.com",
apiKey: process.env.SIDCLAW_API_KEY,
agentId: "support-bot",
});
const governedTools = governTools(sc, [sendEmailTool]);
// Use governedTools instead of [sendEmailTool] in your agent
That’s it. governTools() wraps each tool. When the agent calls send_email, SidClaw evaluates it against your policies. If a policy says “email-sending requires approval,” the action holds. A reviewer gets a card showing the recipient, subject, body, and the agent’s reasoning. They approve or deny. The agent resumes or stops.
What’s actually happening under the hood
governTools() does three things per tool call:
Evaluates the action against your policy set. Policies are priority-ordered. The first matching policy wins. Possible outcomes:
allow,deny,flag(hold for approval), orlog(allow but trace).If flagged, creates an approval request with full context: agent identity, action name, input payload, the agent’s reasoning (if available from the framework), risk classification, and which policy triggered the flag.
Records a trace event with a hash-chain link to the previous event. Every action, approval, and denial is cryptographically chained. Tampering with any record breaks the chain.
Setting up policies
The CLI handles initial setup:
npx create-sidclaw-app
# Creates agent, default policies, and API key in ~60 seconds
Default policies block destructive database operations and flag high-risk actions like sending emails or modifying production infrastructure. You customize from there.
Why not just use LangChain callbacks?
Fair question. You could write a callback that intercepts tool calls and does... something. But:
- Callbacks don’t have a built-in approval workflow. You’d need to build the “hold execution, notify a human, wait for response, resume” flow yourself.
- Callbacks don’t produce compliance-grade audit trails. If FINRA asks for documented human checkpoints, a console.log isn’t going to cut it.
- Callbacks are LangChain-specific. If you also use the Vercel AI SDK or MCP servers, you’re writing the same interception logic three times.
SidClaw’s governTools() works across 13 frameworks with the same governance protocol underneath. Switch from LangChain to CrewAI and your policies, approval workflows, and audit trail carry over.
What this doesn’t solve
SidClaw governs actions. It doesn’t filter LLM outputs for toxicity, check prompt injections, or validate that the agent’s reasoning is sound. Those are different problems with different tools (Pangea, Lakera, etc.). SidClaw sits at the tool-call layer: the moment the agent decides to do something in the real world.
Docs: https://docs.sidclaw.com
TypeScript SDK: npm install @sidclaw/sdk (v0.1.5)
Python SDK: pip install sidclaw (v0.1.2)
Top comments (0)