AdmissibleAI

OpenAI Integration Docs

This page is for developers who already use OpenAI tool calling and want to add Admissible at the execution boundary without rebuilding their app.

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

Intro

This guide runs a short OpenAI tool-calling 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

Install the adapter and the OpenAI SDK in the application that already runs your OpenAI tool calls.

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

Install

npm install @baseline-labs/adapter-openai openai

When to use this integration

Use the OpenAI adapter when

  • your app already uses OpenAI tool calling
  • you want Admissible to evaluate actions before execution
  • you want decisions to surface in the same response flow
  • you want to adopt Admissible without rebuilding your tool architecture

Use the SDK instead when

Choose the SDK when you want to call execute and commit directly or when your orchestration does not naturally fit the OpenAI tool surface.

Need direct runtime control? Start with the SDK quickstart.

Minimal setup

This is the smallest real integration path: create a normal OpenAI client, wrap it with Admissible, configure the Admissible API URL and key, define managed tools, and run a real response through the wrapped client.

Minimal setup

import OpenAI from "openai";import {  authorityPresets,  createBaselineOpenAI,  defineBaselineTool,} from "@baseline-labs/adapter-openai";const openai = new OpenAI({  apiKey: process.env.OPENAI_API_KEY!,});const stagingClient = createBaselineOpenAI({  openai,  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 productionClient = createBaselineOpenAI({  openai,  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 readAccount = defineBaselineTool({  name: "readAccount",  description: "Read staging account state before proposing a change.",  authority: authorityPresets.readOnly,  execute: async (input: { accountId: string }) => ({    accountId: input.accountId,    status: "active",  }),});const updatePlan = defineBaselineTool({  name: "updatePlan",  description: "Apply a subscription change in production.",  authority: authorityPresets.externalWrite,  commit: {    system: "billing",    payload: (input: { accountId: string; plan: string }) => input,  },  execute: async (input: { accountId: string; plan: string }) => ({    accepted: true,    ...input,  }),});const readResponse = await stagingClient.responses.create({  model: "gpt-4.1",  input: "Read account acct_123 before the production update attempt.",  tools: [readAccount],});const updateResponse = await productionClient.responses.create({  model: "gpt-4.1",  input: "Upgrade account acct_123 to pro if needed.",  tools: [updatePlan],});
  • create the normal OpenAI client with your OPENAI_API_KEY
  • configure ADMISSIBLE_BASE_URL and ADMISSIBLE_API_KEY inside createBaselineOpenAI(...)
  • define each managed tool with defineBaselineTool(...) and an explicit authority preset
  • run a real client.responses.create(...) request through the wrapped client instead of the raw OpenAI client

Example

Allowed actions continue through the wrapped OpenAI flow. If Admissible denies an execute or commit step, the adapter throws a structured BaselineIntegrationError and keeps the runtime decision in managed execution history.

Inspect runtime decisions

const response = await client.responses.create({  model: "gpt-4.1",  input: "Read the checklist, then run the migration.",  tools: [readReleaseChecklist, runProductionMigration],});console.log(response.baseline.runId);console.log(response.baseline.traceId);console.log(  response.baseline.toolExecutions.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 read-style action continues through the OpenAI flow
  • one denied write or production-style action stops before the tool completes
  • a runtime decision with status, reason code, reason, and trace id
  • managed tool execution history on the same request path
  • no irreversible action is executed when the action is denied

Core concepts

createBaselineOpenAI

Wraps the OpenAI client, evaluates managed tool calls before execution, and keeps runtime decision data on the same request path.

  • accepts either an Admissible client/executor or a config object
  • keeps the normal OpenAI request surface through responses.create(...)
  • exposes getExecutionContext(), getToolExecutionHistory(), and resetExecutionContext()

defineBaselineTool

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

  • keeps tool definition close to the normal OpenAI tool shape
  • declares authority where execute and commit decisions are made
  • supports commit metadata for write and irreversible actions

What changes and what stays the same

What stays the same

  • your OpenAI model choice
  • your general tool-calling flow
  • your overall application structure
  • your tool implementations and orchestration

What changes

  • managed tool actions are evaluated before execution
  • denials can interrupt execute or commit before the action completes
  • decision metadata becomes part of the runtime path
  • tool authority becomes explicit instead of implied

Handling denials and interventions

Treat denials 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-openai";try {  await client.responses.create({    model: "gpt-4.1",    input: "Delete customer acct_123 and remove all billing history.",    tools: [readAccount, deleteCustomer],  });} 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 action, surface the denial clearly, and request confirmation or a 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 production migrations, destructive actions, and high-blast-radius commits

Common patterns

Wrap existing read tools first

Start by wrapping read-only tools so you can place Admissible in the OpenAI 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 execution and commit checks matter most.

Mix safe reads with gated writes

Keep low-risk reads flowing while routing write and irreversible tools through stricter presets and user-visible handling.

Handle denied steps inside multi-tool flows

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

From quickstart to production

This is where you replace demo-shaped code with application-shaped integration code.

  • replace the toy tools from the quickstart with your real app tools
  • 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
  • define authorities per tool based on real side effects, not convenience
  • handle denials explicitly in the user or operator flow instead of surfacing them as generic failures

Execution history and observability

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

Execution history

const history = client.getToolExecutionHistory();const context = client.getExecutionContext();logger.info("admissible_openai_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 client.getToolExecutionHistory() 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 request
  • 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 call instead of keeping one per user flow
  • 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
  • leaving demo environment and tenant values in place after moving into a real app
  • treating Admissible denials as unexpected exceptions instead of designed runtime decisions

Reference

createBaselineOpenAI

createBaselineOpenAI({
  baseline,
  openai,
  agentId,
  sessionId,
  environmentName,
  maxToolRoundTrips,
})

Wraps the OpenAI client and returns a managed client with responses.create(...) plus execution-context helpers.

  • baseline can be a client, executor, or config object
  • openai accepts the OpenAI client or { client: openai }
  • agentId, sessionId, and environmentName should be stable and meaningful in production

defineBaselineTool

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

Resolves authority on a managed tool definition before the tool is sent into the wrapped OpenAI 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 interrupted 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

Execution helpers

client.getExecutionContext()
client.getToolExecutionHistory()
client.resetExecutionContext()

Use these helpers to inspect the current run and control how much managed history stays attached to the client.