LangChain makes it genuinely easy to build capable agents. You can wire up a language model with a set of tools in a dozen lines of code and have something that reads files, queries APIs, sends messages, and takes actions in the world. That ease is also the problem. Nothing in the default LangChain setup defines what the agent is allowed to do, under what conditions, or what happens when it does something unexpected. There is no policy layer. There is no audit trail. There is no kill switch.
You will discover this is a problem the first time your agent calls a production API it was never supposed to touch, or when your security team asks you to produce a record of every action the agent took last Tuesday.
This tutorial shows you how to add a full safety layer — policy enforcement, approval workflows, and cryptographic receipts — to a LangChain agent in about five minutes.
Here is a standard LangChain agent setup. Nothing unusual about this code.
import { ChatOpenAI } from "@langchain/openai";
import { AgentExecutor, createOpenAIFunctionsAgent } from "langchain/agents";
import { DynamicTool } from "@langchain/core/tools";
const tools = [
new DynamicTool({
name: "send_email",
description: "Send an email to a recipient",
func: async (input) => {
// sends email
return `Email sent to ${input}`;
},
}),
new DynamicTool({
name: "delete_record",
description: "Delete a database record by ID",
func: async (id) => {
// deletes record
return `Record ${id} deleted`;
},
}),
];
const agent = await createOpenAIFunctionsAgent({
llm: new ChatOpenAI({ model: "gpt-4o" }),
tools,
prompt,
});
const executor = new AgentExecutor({ agent, tools });
This agent can call send_email and delete_record without restriction. Any input, any target, any time. If the model hallucinates an argument or gets injected with a malicious instruction, there is nothing between the decision and the action.
npm install @authensor/sdk @authensor/langchain
Set your API key:
export AUTHENSOR_API_KEY=your_api_key
Create policy.yaml in your project root. This is where you define what the agent is allowed to do.
version: "1"
default: deny
rules:
- action: send_email
principal: "agent:email-assistant"
effect: allow
conditions:
- field: "args.recipient"
operator: matches
value: "@yourcompany.com$"
rateLimit:
window: "1h"
max: 50
- action: delete_record
principal: "agent:email-assistant"
effect: review
reason: "Destructive actions require human approval"
- action: "*"
principal: "*"
effect: deny
Three rules. send_email is allowed but only to your company domain and rate-limited to 50 per hour. delete_record requires a human to approve it before it executes. Everything else is denied by default.
Push the policy to your Authensor control plane:
npx authensor policy push policy.yaml
Replace your AgentExecutor with the Authensor-wrapped version:
import { ChatOpenAI } from "@langchain/openai";
import { createOpenAIFunctionsAgent } from "langchain/agents";
import { createAuthensorExecutor } from "@authensor/langchain";
const agent = await createOpenAIFunctionsAgent({
llm: new ChatOpenAI({ model: "gpt-4o" }),
tools,
prompt,
});
// Replace AgentExecutor with createAuthensorExecutor
const executor = createAuthensorExecutor({
agent,
tools,
principal: "agent:email-assistant",
apiKey: process.env.AUTHENSOR_API_KEY,
});
// Usage is identical
const result = await executor.invoke({ input: "Send a summary to alice@yourcompany.com" });
That is the entire change. createAuthensorExecutor wraps the tool-call boundary so that every time the agent decides to call a tool, the intent is evaluated against your policy before the tool function runs. The agent itself does not change. The tools do not change. The policy evaluation happens transparently between the two.
When the agent decides to call send_email:
authorization/propose message to the control plane.Block all destructive actions by default:
rules:
- action: "delete_*"
principal: "*"
effect: deny
- action: "drop_*"
principal: "*"
effect: deny
Allow reads, require approval for writes:
rules:
- action: "read_*"
principal: "agent:analyst"
effect: allow
- action: "write_*"
principal: "agent:analyst"
effect: review
Time-of-day restrictions:
rules:
- action: "deploy_*"
principal: "agent:ci-bot"
effect: allow
conditions:
- field: "context.hour"
operator: between
value: [9, 17]
- field: "context.dayOfWeek"
operator: notIn
value: [0, 6]
Per-action cost caps (for agents that call paid APIs):
rules:
- action: "generate_image"
principal: "agent:content-creator"
effect: allow
budget:
perCall: 0.04
daily: 5.00
The same agent code, with one import change and one constructor swap, now has:
The policy lives in one file. It is version-controlled. You can push a new policy without redeploying the agent. If something goes wrong at 2am, you can deny all actions for a specific agent by pushing a single-rule policy that overrides everything.
The @authensor/langchain adapter is open source. Run npx create-authensor to scaffold a project with a policy, a receipt store, and an example LangChain integration. The full docs are at authensor.com/docs. If you find something that does not work the way it should, open an issue or star the repo so more people find it.