AdmissibleAI

LangChain Integration Docs

This page is for developers who already use LangChain tools or agent flows and want to add Admissible around tool execution without rebuilding their chain architecture.

Just want a first run? Start with the quickstart. Need policy behavior? Read Sandbox runtime policy.

Intro

This guide runs a short LangChain example against the Admissible API using a Sandbox API key. It shows one allowed action, one denied action, and the runtime decision that explains the difference.

Install

Add the adapter in the same application where your existing LangChain tools already live. The code on this page imports only the Admissible adapter surface, so keep your current LangChain packages in place where your app already uses them.

Note: The LangChain adapter package currently publishes under the @baseline-labs npm namespace. It is the official LangChain adapter package for using Admissible AI.

Install

npm install @baseline-labs/adapter-langchain

When to use this integration

Use the LangChain adapter when

  • your app already uses LangChain tools or agent flows
  • you want to wrap existing tools incrementally instead of rebuilding them
  • you want runtime decisions to stay on the same tool invoke path
  • you want managed execution history across chained tool steps

Use the SDK instead when

Choose the SDK when you want to call execute and commit directly or when your orchestration is not centered on LangChain-style tool invocation.

Need direct runtime control? Start with the SDK quickstart.

Minimal setup

This is the smallest real integration path: create the LangChain wrapper, configure the Admissible API URL and key, wrap an existing tool, add an Admissible-managed write tool, and invoke both through the managed surface.

Minimal setup

import {  authorityPresets,  createBaselineLangChain,  defineBaselineTool,} from "@baseline-labs/adapter-langchain";const stagingBaseline = createBaselineLangChain({  baseline: {    baseUrl: process.env.ADMISSIBLE_BASE_URL!,    apiKey: process.env.ADMISSIBLE_API_KEY!,    authorityDefaults: {      env: "staging",      tenantId: "acme-co",    },  },  agentId: "support-agent",  sessionId: "sess_customer_123_staging",  environmentName: "staging",});const productionBaseline = createBaselineLangChain({  baseline: {    baseUrl: process.env.ADMISSIBLE_BASE_URL!,    apiKey: process.env.ADMISSIBLE_API_KEY!,    authorityDefaults: {      env: "production",      tenantId: "acme-co",    },  },  agentId: "support-agent",  sessionId: "sess_customer_123_production",  environmentName: "production",});const existingReadAccountTool = {  name: "readAccount",  description: "Read staging account state before proposing a change.",  authority: authorityPresets.readOnly,  async invoke(input: { accountId: string }) {    return {      accountId: input.accountId,      status: "active",    };  },};const updatePlanTool = defineBaselineTool({  name: "updatePlan",  description: "Apply a subscription change in production.",  authority: authorityPresets.externalWrite,  commit: {    system: "billing",    rollbackRef: ({ accountId }: { accountId: string }) =>      `rollback:billing:${accountId}`,  },  execute: async (input: { accountId: string; plan: string }) => ({    accepted: true,    ...input,  }),});const readAccount = stagingBaseline.wrapTool(existingReadAccountTool);const updatePlan = productionBaseline.wrapTool(updatePlanTool);await readAccount.invoke({ accountId: "acct_123" });await updatePlan.invoke({ accountId: "acct_123", plan: "pro" });
  • configure ADMISSIBLE_BASE_URL and ADMISSIBLE_API_KEY inside createBaselineLangChain(...)
  • wrap an existing tool with baseline.wrapTool(...), then use the same wrapper again or baseline.wrapTools(...) as the managed surface grows
  • define managed tools with defineBaselineTool(...) when you need explicit authority and commit metadata
  • invoke the wrapped tool through tool.invoke(...) instead of calling the raw tool directly

Example

Allowed wrapped tools continue through the normal invoke path. If Admissible denies or defers an execute or commit step, the adapter throws a structured BaselineIntegrationError and keeps the runtime decision in managed history.

Inspect runtime decisions

const managed = baseline.createManagedToolset([  readReleaseChecklist,  runProductionMigration,]);await managed.tools[0].invoke({ path: "README.md" });console.log(managed.getExecutionContext().runId);console.log(  managed.getToolExecutionHistory().map((entry) => ({    tool: entry.toolName,    execute: entry.execute?.decision,    commit: entry.commit?.decision,    code:      entry.commit?.audit?.explanationCode ??      entry.execute?.audit?.explanationCode,  })),);

What you should see

  • one allowed wrapped tool invocation continues through the LangChain flow
  • one denied write or production-style action stops before the raw tool runs
  • a runtime decision with status, reason code, reason, and trace id
  • managed execution history for each wrapped tool step
  • no irreversible action is executed when the action is denied

Core concepts

createBaselineLangChain

Creates the Admissible-managed LangChain surface, shares managed execution context across wrapped tools, and keeps the surrounding LangChain flow familiar.

  • accepts either an Admissible client/executor or a config object
  • sets shared agent, session, and environment context for wrapped tools
  • exposes getExecutionContext(), getToolExecutionHistory(), and resetExecutionContext()

wrapTool(...)

Wrap one existing tool when you want to adopt Admissible incrementally around a single high-value or high-risk action.

  • best when you are introducing Admissible around one existing tool first
  • keeps the normal invoke path through the wrapped tool handle
  • works well when a single tool already has a stable input and output contract

wrapTools(...)

Wrap a bounded group of existing tools when you want shared managed continuity across multiple LangChain steps.

  • useful for an agent loop, chain segment, or local tool registry
  • lets a mixed tool surface share one managed run context
  • works when you want multiple wrapped steps to contribute to the same history

createManagedToolset(...)

Wraps a group of tools and returns both the managed tools and the execution-history helpers in one surface.

  • useful when a bounded toolset should own its own inspectability helpers
  • returns the managed tools plus local context and history methods together
  • helps keep observability close to the same tool group that owns the flow

defineBaselineTool(...)

Adds Admissible authority to a tool definition before the tool enters the wrapped LangChain flow.

  • use this when you are creating a new managed tool instead of only wrapping an existing one
  • declares authority where execute and commit decisions are made
  • supports commit metadata for write and irreversible actions

When to wrap existing tools vs define new Admissible-managed tools

Wrap existing tools when

  • you already have LangChain tools with stable input and output contracts
  • you want to adopt Admissible incrementally without changing the wider chain flow
  • you want to put Admissible around the highest-risk tools first

Define new Admissible-managed tools when

  • you are creating a new tool surface and want authority declared from the start
  • you need explicit commit metadata on the tool definition itself
  • you want one managed tool definition that can be wrapped consistently across flows

What changes and what stays the same

What stays the same

  • your general chain or agent structure
  • your tool input and output shapes
  • your higher-level orchestration logic
  • your surrounding LangChain application flow

What changes

  • wrapped tool actions are evaluated before execution
  • denials or approvals can interrupt execute or commit before the raw tool runs
  • managed history becomes part of the runtime path
  • tool authority becomes explicit instead of implied

Handling denials and interventions

Treat denials and approval deferrals as product behavior, not transport failure. Catch BaselineIntegrationError, log the decision metadata, and route the app into a user-visible fallback or approval path.

Handle denials

import { BaselineIntegrationError } from "@baseline-labs/adapter-langchain";try {  await updatePlan.invoke({    accountId: "acct_123",    plan: "enterprise",  });} catch (error) {  if (error instanceof BaselineIntegrationError) {    logger.warn("admissible_intervention", {      code: error.code ?? error.kind,      toolName: error.toolName,      reason: error.reason,      suggestion: error.suggestion,    });    return;  }  throw error;}
  • code and reason explain why the action stopped
  • toolName, code, and reason are the first fields most apps should log
  • stop the current tool step, surface the intervention clearly, and ask for confirmation or narrower scope when appropriate
  • retry only after the policy, scope, approval, or user intent has actually changed

Authority modeling and presets

Read-only

Use authorityPresets.readOnly for safe reads and inspection steps.

  • scope: local:read
  • commit type: ephemeral
  • best for lookups, reads, and non-mutating checks
  • free Sandbox read examples should stay on staging or development data

Reversible local write

Use authorityPresets.localWrite for local state changes you expect to roll back if needed.

  • scope: local:write
  • commit type: local
  • best for reversible state changes inside your own boundary

External durable write

Use authorityPresets.externalWrite for external or production-facing writes that need more explicit control.

  • scope: external
  • commit type: external
  • best for integrations that affect external systems or durable customer state

Irreversible / production-affecting

Use authorityPresets.irreversible for actions that should never quietly auto-commit.

  • scope: external
  • commit type: irreversible
  • best for destructive actions, high-blast-radius commits, and production migrations

Common patterns

Wrap existing read tools first

Start by wrapping read-only tools so you can put Admissible in the LangChain flow without changing write behavior on day one. In free Sandbox, production reads and security-sensitive reads are denied by default; use staging or development data until your account policy allows broader access.

Add Admissible to write paths incrementally

Once read paths are stable, wrap write-capable tools where execute and commit checks matter most.

Mix managed and unmanaged tools carefully

Only the wrapped tool handles pass through Admissible. Keep it explicit which tools are managed and which are still running outside that boundary.

Handle denied steps inside chained flows

If one wrapped tool is denied, stop that step, surface the intervention, and let the app or operator decide what to do next.

From quickstart to production

This is where you move from a short wrapped demo flow to real LangChain integration code.

  • replace the toy tools from the quickstart with the tools your chain or agent already uses
  • move from demo IDs to stable agentId and sessionId values from your application
  • set authorityDefaults.env and tenantId to match your actual environment and tenant model
  • wrap tools at registration time so the managed surface is the one your chain actually invokes
  • handle denials explicitly in the user or operator flow instead of treating them like generic tool errors

Execution history and observability

The adapter already carries the metadata you need for logs, traces, and operator visibility. Persist it close to the same invoke path that owns the wrapped tools.

Execution history

const history = baseline.getToolExecutionHistory();const context = baseline.getExecutionContext();logger.info("admissible_langchain_run", {  runId: context.runId,  traceId: context.lastTraceId,  authorityExpiresAt: context.authorityExpiresAt,  toolExecutions: history,});
  • log runId, traceId, tool names, and decision codes for denied or modified actions
  • attach baseline.getToolExecutionHistory() or the managed toolset history to traces, logs, or audit records before you clear the run context
  • use resetExecutionContext() when a run boundary is complete and you do not want history to bleed into the next flow

Production guidance

Once the integration is live, keep the surrounding runtime context stable and predictable.

  • use a stable agentId for the real application capability, not a random id per invoke
  • use sessionId to represent a real user flow, thread, or workflow instance
  • set environmentName and authorityDefaults.env intentionally so policy and audit data stay meaningful
  • set tenantId when you need per-tenant separation in policy or audit trails
  • roll out by wrapping lower-risk tools first, then expand to write paths with intent
  • keep existing tool contracts stable while you introduce Admissible enforcement around them

Common mistakes

  • generating a new sessionId for every tool invoke instead of keeping one per user flow
  • wrapping tools in one part of the app but still calling the unmanaged tool handle elsewhere
  • marking production-changing tools as authorityPresets.readOnly or authorityPresets.localWrite because it makes the demo easier
  • logging only the thrown error message and dropping toolName, code, or reason
  • carrying demo environment or tenant values into production

Reference

createBaselineLangChain

createBaselineLangChain({
  baseline,
  agentId,
  sessionId,
  environmentName,
  goal,
  source,
})

Creates the Admissible-managed LangChain client and returns the wrapping and history helpers.

  • baseline can be a client, executor, or config object
  • agentId, sessionId, and environmentName should be stable and meaningful in production
  • use goal and source when you want more explicit run context in logs and audit trails

wrapTool(...) / wrapTools(...)

baseline.wrapTool(tool)
baseline.wrapTools([toolA, toolB])
baseline.createManagedToolset([toolA, toolB])

Use these surfaces to adopt Admissible around one tool, a group of existing tools, or a managed toolset with local helpers.

  • wrap one tool for incremental adoption or a group for a bounded flow
  • managed toolsets expose their own execution context and history helpers
  • wrapped tools preserve continuity across chained steps in the same managed flow

defineBaselineTool

defineBaselineTool({
  name,
  description,
  authority,
  execute,
  commit,
})

Resolves authority on a managed tool definition before the tool is sent into the wrapped LangChain flow.

  • authority defaults to readOnly if you omit it
  • commit is where write and irreversible tools carry commit metadata
  • use presets first, then add explicit scope or commit overrides only when needed

BaselineIntegrationError

error.code
error.toolName
error.reason
error.suggestion

Catch this error to handle denied or deferred actions cleanly in the application flow.

  • code identifies the runtime decision category
  • reason is the user- or operator-facing explanation anchor
  • executeResponse and commitResponse are available when you need deeper inspection