Product · SynOI for LangChain

Testing

Wrap 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.

  1. 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
  2. 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.