Product · SynOI for LangChain
TestingWrap the toolkit once.
Every call after that is governed.
@synoi/langchainis a governance interceptor, not a new framework to learn: wrap a LangChain toolkit, an AutoGen function map, or a CrewAI toolkit, and each one keeps the exact interface your agent already expects. Underneath, every tool call is classified, sent to your gateway's /receipts/govern endpoint, and either allowed, denied, or held for a human, before the underlying tool ever runs.
One interceptor, three agent frameworks
Same governance, whichever framework the agent runs on.
LangChain
governToolkit(tools, config)
Wraps any BaseTool-shaped tool (duck-typed, not version-pinned) with a SynoiTool that preserves the invoke / _call interface your agent already calls.
AutoGen
governFunctionMap(functionMap, config)
Wraps every function in an AutoGen function_map, or attaches a before/after hook pair to a ConversableAgent.
CrewAI
governCrewToolkit(tools, config)
Wraps CrewAI-style tools (objects with a run(input) method) the same way, for the TypeScript side of a Python-originated framework.
Quick start
Wrap the toolkit, keep the rest of your agent code unchanged.
01
Install
LangChain's own packages are peer dependencies, so you control which version of @langchain/core you're on.
npm install @synoi/langchain @langchain/core langchain
02
Wrap your tools
governToolkit returns the same tool shape your agent constructor expects. Nothing else in your agent code changes.
import { governToolkit } from '@synoi/langchain' import { TavilySearchResults } from '@langchain/community/tools/tavily_search' import { Calculator } from '@langchain/community/tools/calculator' const tools = governToolkit( [new TavilySearchResults(), new Calculator()], { synoiKey: process.env.SYNOI_SECRET_KEY, gatewayUrl: process.env.SYNOI_GATEWAY_URL, onDecision: (receipt) => console.log('[SynOI]', receipt.tool_name, receipt.decision), } ) // Use tools anywhere you'd use the originals: full governance, same interface const agent = createOpenAIFunctionsAgent({ llm, tools, prompt })
Per-call behavior
Classify, decide, execute, receipt. Every single call.
Action class inferred automatically
Each call is classified into read / write / invalidate / export / train / admin / unknown from the tool name and arguments, unless you pin an actionClass on the interceptor yourself.
High-risk calls wait for a human
A high-risk call with an HITL id dispatches to a human and polls the gateway until approved, denied, or the timeout expires. Class-C doctrine: a timeout blocks the call, it never falls through to allow.
Denials throw, with the receipt attached
A blocked call throws SynoiBlockedError carrying the receipt id, action class, and HITL id, so your error handling has the full context, not just a rejected promise.
Receipts are recorded on every path
Allow, deny, and execution failure all produce a receipt with a decision, an output hash, and latency, fire-and-forget so a slow receipt store never blocks the agent.
Observe mode for rollout
Set observeOnly (or let it default there when the gateway is unreachable) to record every decision without ever blocking execution, the same shape as SYNOI_MODE=observe on the gateway.
Shadow-block is distinct from deny
A shadow_block decision still executes the tool while recording it as blocked, giving you a way to measure what a policy would have stopped before enforcing it.
Keep your agent framework. Add the receipt trail.
No rewrite: wrap the toolkit, point it at your gateway, and every tool call an agent makes is governed the same way as the rest of your SynOI stack.