For: LangChain devs already shipping agents in production

Why LangChain needs SBO3L (and what changes when you wire them)

~5 min read

LangChain wraps your agent's reasoning. SBO3L wraps LangChain's tool calls in policy + signed audit. Five lines of code, one boundary your CFO understands.

You wrap your agent in LangChain. LangChain wraps your agent's reasoning: which tool to call, with which arguments, in what order. That's load-bearing. But LangChain doesn't wrap the boundary between "the agent decided" and "the action executed." That's the gap SBO3L closes.

The five-line wire

Vanilla LangChain — the agent reasons, the tool fires, no audit:

const chain = createOpenAIToolsAgent({ llm, tools, prompt });
const result = await chain.invoke({ input });
// → tool calls happened. Where's the receipt?

With @sbo3l/langchain — same chain, plus a callback handler:

import { Sbo3lCallbackHandler } from "@sbo3l/langchain";

const sbo3l = new Sbo3lCallbackHandler({
  url: "http://localhost:8730",
  agentId: "research-01",
  onDeny: (reason) => logger.warn("policy deny:", reason),
});

const chain = createOpenAIToolsAgent({ llm, tools, prompt });
const result = await chain.invoke({ input }, { callbacks: [sbo3l] });
// → every tool call now produces a signed receipt
// → policy denies surface as a tool_result with deny_code
// → handler.receipts contains the full audit trail

What changes

  • Every tool call has a signed receipt. Not a callback log. Not a database row your daemon writes after the fact. A cryptographic receipt produced before the tool executes, signed with the daemon's Ed25519 key, with the request hash and policy hash baked in.
  • Policy denies are part of the chain output. LangChain's standard tool-call result protocol carries the deny code through to the LLM, so the agent can reason about rejection ("I can't transfer that much; ask the user to lower the amount") instead of crashing.
  • The audit chain is queryable. handler.receipts is a list of all the receipts from this chain run. Hash-chained, exportable, replayable. Your SOC 2 auditor doesn't ask "did the agent do something it shouldn't?" — they ask "show me the receipts" and you ship the list.

Why your CFO wants this

Imagine the conversation when the LangChain-driven agent does something expensive (or wrong, or both):

Without SBO3LWith SBO3L
"What happened?" — engineer reconstructs from logs, OpenAI traces, blockchain explorer, maybe LangSmith if they paid for it. Hours of forensic work. "What happened?" — engineer pulls capsule by request_hash, verifies offline against the daemon's published Ed25519 pubkey, has cryptographic proof of what the agent decided and what policy was in force. Minutes.
"Could it have been worse?" — uncertain. Logs might be incomplete; the agent's reasoning chain is ephemeral. "Could it have been worse?" — query the policy snapshot referenced by policy_hash; show the rules that prevented worse outcomes from firing.
"Who approved this?" — depends on which review you logged. Not always available. "Who approved this?" — the receipt's matched_rule_id points at the exact policy rule. The rule's git history shows who shipped it.

What this isn't

  • Not a LangChain replacement. The handler is additive. Same agent, same tools, same prompts. SBO3L doesn't second-guess the LLM's reasoning — it just enforces the boundary around what the LLM can actually do.
  • Not a free latency lunch. Each tool call adds one round-trip to the daemon (typically <1ms over Unix socket, ~5ms over HTTP). For human-perceived latency this is invisible; for high-frequency batch agents you can run the daemon in-process via the Rust crate directly.
  • Not a substitute for prompt engineering. SBO3L can't stop the LLM from trying to call the wrong tool — but it can stop the call from succeeding. The LLM gets a deny code; your prompt should teach it to handle that gracefully.

Try it

The Node.js + Python LangChain adapter quickstart is at /quickstart/langchain — five minutes from npm install to your first signed receipt against a local daemon.