AdmissibleAI

Direct SDK Integration Docs

This page is for developers who want direct runtime control and are willing to work one layer lower than the adapters without routing through an OpenAI or LangChain wrapper.

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

Intro

This guide runs a short Direct SDK 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 SDK in the service or orchestrator that will call the Admissible API directly. The SDK path itself only requires @baseline-labs/sdk.

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

If you want to run the snippets locally as standalone TypeScript files, add tsx separately as a dev convenience. It is not part of the SDK runtime dependency surface.

Install

npm install @baseline-labs/sdk

When to use this integration

Use the SDK when

  • your orchestration is framework-independent or already custom
  • you want to call execute and commit directly
  • you want full control over request shape, trace continuity, and runtime context
  • you do not want an adapter deciding how tool calls map into runtime requests

Use an adapter instead when

Choose an adapter when your application is already centered on a specific tool surface and you want Admissible to sit inside that existing flow.

Already in OpenAI or LangChain? OpenAI docs or LangChain docs.

Minimal setup

The SDK loop is explicit: instantiate BaselineClient, send an ExecuteRequest, inspect the returned operation response, branch in your application code, and only call commit when that earlier response and operation context support a durable change.

Minimal setup

import {  BaselineClient,  type CommitRequest,  type ExecuteRequest,} from "@baseline-labs/sdk";const client = new BaselineClient({  baseUrl: process.env.ADMISSIBLE_BASE_URL!,  apiKey: process.env.ADMISSIBLE_API_KEY!,  userAgent: "acme-runtime/1.0",});function createExecuteRequest(sessionId: string): ExecuteRequest {  return {    requestId: "req_execute_001",    agent: {      id: "release-agent",      framework: "custom-runtime",    },    session: {      id: sessionId,    },    environment: {      name: "staging",      service: "release-control",      region: "ord",    },    action: {      type: "tool_call",      name: "read.file",      description: "Inspect the staged release manifest before proposing a change.",      input: {        path: "releases/staging.json",      },      target: {        system: "release-control",        resourceType: "environment",        resourceId: "staging",      },    },    intent: {      goal: "Inspect release state safely before proposing a durable change.",      task: "Read the current staging release manifest.",      operator: "release-operator",    },    constraints: {      mode: "enforce",      maxRiskScore: 0.4,      allowModification: false,      allowDeferral: true,      approvalMode: "auto",    },    metadata: {      source: "release-control",    },  };}function createCommitRequest(  sessionId: string,  supportingOperationId: string,): CommitRequest {  return {    requestId: "req_commit_001",    agent: {      id: "release-agent",      framework: "custom-runtime",    },    session: {      id: sessionId,    },    proposedCommit: {      type: "finalize_release_snapshot",      system: "production",      payload: {        migration: "users_v3_backfill",      },    },    commitType: "irreversible",    supportingOperationId,    rollbackRef: "irreversible:none",  };}const sessionId = "sess_release_123";const executeResponse = await client.execute(createExecuteRequest(sessionId));console.log(executeResponse.status, executeResponse.message);console.log(executeResponse.nextAction.type, executeResponse.operation.id);if (executeResponse.nextAction.type !== "proceed") {  return;}const commitResponse = await client.commit(  createCommitRequest(sessionId, executeResponse.operation.id),  {    idempotencyKey: "idem_commit_001",  },);console.log(commitResponse.status, commitResponse.message);console.log(commitResponse.nextAction.type, commitResponse.operation.id);
  • configure ADMISSIBLE_BASE_URL and ADMISSIBLE_API_KEY in new BaselineClient(...)
  • build a full ExecuteRequest that describes the action, target, intent, constraints, and metadata explicitly
  • inspect executeResponse.result, executeResponse.status, and executeResponse.nextAction.type before you assume the workflow should continue
  • carry executeResponse.operation.id into supportingOperationId on the later CommitRequest
  • call commit with an explicit idempotencyKey only for the later durable or irreversible step that the earlier execute result actually supports
  • keep the request builders in your own codebase so the SDK stays a direct runtime control surface, not hidden orchestration

Example

client.execute(...) and client.commit(...) both return a V2ImmediateResponse. Runtime outcomes stay on the response contract through result, status, message, and nextAction.

Inspect runtime decisions

const executeResponse = await client.execute(  createExecuteRequest(sessionId),);console.log({  result: executeResponse.result,  status: executeResponse.status,  terminal: executeResponse.terminal,  message: executeResponse.message,  nextAction: executeResponse.nextAction.type,  operationId: executeResponse.operation.id,});if (executeResponse.nextAction.type !== "proceed") {  return;}const commitResponse = await client.commit(  createCommitRequest(sessionId, executeResponse.operation.id),  {    idempotencyKey: "idem_commit_001",  },);console.log({  result: commitResponse.result,  status: commitResponse.status,  terminal: commitResponse.terminal,  message: commitResponse.message,  nextAction: commitResponse.nextAction.type,  operationId: commitResponse.operation.id,  continuationHandle: commitResponse.continuation?.handle ?? null,});

What you should see

  • one allowed execute request returns a successful runtime status
  • one denied or stopped commit request returns a runtime decision before durable state changes
  • status, code or reason data, message, next action, and operation or trace id are visible
  • the execute operation id is carried into the later commit path
  • no irreversible action is executed when the action is denied

Core concepts

The normal SDK loop is explicit: construct the client once, call execute first, inspect the immediate operation response, follow operation.id when the runtime tells you to check status or await approval, then call commit only when the earlier response supports a later durable change.

BaselineClient

Creates the direct API client and keeps transport details like base URL, auth, timeout, and default headers out of the rest of your integration code.

  • takes baseUrl, apiKey, timeoutMs, defaultHeaders, and userAgent
  • stays framework-independent
  • does not change your orchestration or request builders

client.execute(...)

Sends an explicit execute request to the runtime and returns the immediate operation response for the proposed action.

  • call this first when the system is deciding whether an action should proceed
  • inspect result, status, message, nextAction, continuation, and operation.id on the returned result
  • establishes the operation id you carry into any later commit step

client.getOperation(...)

Fetches the current operation status when the immediate response tells you to poll, await approval, or inspect the latest continuation state.

  • use the operation.id from execute or commit responses as input
  • inspect approval, continuation, retry, rollback, and supersession state
  • pair this with resolveOperationApproval when an operator needs to approve or deny an operation

client.commit(...)

Sends a commit request for the durable or irreversible state change after the earlier execute path has produced a supporting operation id.

  • call this only after you have inspected the earlier execute result and decided the workflow should continue
  • use this for durable writes, external effects, or irreversible actions
  • requires an idempotencyKey and supports commitType, supportingOperationId, artifact, rollback, approval, and policy context
  • keeps commit gating separate from execute evaluation

BaselineClientError

Represents a non-success API response with the HTTP status, parsed error data, and the runtime-facing code when one is available.

  • use this for denied, deferred, or otherwise rejected API paths that return a non-2xx response
  • inspect status, code, message, and data
  • keep the parsed error payload because runtime reason data often lives there

What changes and what stays the same

What stays the same

  • your application owns the orchestration flow
  • your own tool runner or server logic stays yours
  • your app decides when execute and commit calls happen
  • your surrounding service architecture stays under your control

What changes

  • you model actions explicitly in execute and commit requests
  • you handle structured runtime decisions explicitly
  • you preserve continuity between stages by carrying the returned operation data forward intentionally
  • durable and irreversible paths may need explicit commit gating

Handling execute, commit, and transport failures

In the SDK path, your app branches on returned runtime responses first and treats transport or client failures as a separate operational problem. Do not collapse those two paths together.

Handle denials

import {  BaselineClientError,  BaselineTimeoutError,} from "@baseline-labs/sdk";const executeResponse = await client.execute(createExecuteRequest(sessionId));if (executeResponse.nextAction.type === "stop") {  logger.warn("admissible_execute_blocked", {    result: executeResponse.result,    status: executeResponse.status,    message: executeResponse.message,    nextAction: executeResponse.nextAction.type,    operationId: executeResponse.operation.id,  });  return;}try {  const commitResponse = await client.commit(    createCommitRequest(sessionId, executeResponse.operation.id),    {      idempotencyKey: "idem_commit_001",    },  );  if (commitResponse.nextAction.type === "await_approval") {    const operation = await client.getOperation(commitResponse.operation.id);    logger.info("admissible_commit_pending_approval", {      operationId: operation.operationId,      status: operation.status,      approvalState: operation.approval.state,      nextAction: operation.nextAction.type,    });    return;  }  if (    commitResponse.nextAction.type !== "proceed" &&    commitResponse.nextAction.type !== "check_status"  ) {    logger.warn("admissible_commit_outcome", {      result: commitResponse.result,      status: commitResponse.status,      message: commitResponse.message,      nextAction: commitResponse.nextAction.type,      operationId: commitResponse.operation.id,    });    return;  }} catch (error) {  if (error instanceof BaselineClientError) {    logger.warn("admissible_commit_blocked", {      status: error.status,      code: error.code,      message: error.message,      data: error.data,    });    return;  }  if (error instanceof BaselineTimeoutError) {    logger.error("admissible_timeout", {      timeoutMs: error.timeoutMs,    });    return;  }  throw error;}
  • inspect executeResponse.nextAction.type before you assume a commit step is valid
  • do not call commit blindly; branch explicitly on proceed, check_status, await_approval, and stop when they come back on the runtime success path
  • when approval is required or the runtime tells you to check status later, treat that as governed application state, not as a transport error
  • catch BaselineClientError for rejected API paths and keep the parsed error payload
  • catch BaselineTimeoutError when the request boundary itself times out
  • retry only after the policy, input, approval, or operator intent has materially changed

Modeling execute, commit, and operation continuity

Keep agent identity stable

Use a stable <InlineCode>agent.id</InlineCode> for the real capability or service role so runtime policy and audit data stay attributable.

Keep session continuity stable

Use a stable <InlineCode>session.id</InlineCode> for the user flow, workflow run, or job boundary that owns the decision sequence.

Carry the execute operation forward

Use <InlineCode>executeResponse.operation.id</InlineCode> as the continuity anchor by passing it into <InlineCode>supportingOperationId</InlineCode> on the later commit request. Without it, the commit path loses the explicit supporting context from the earlier execute stage.

Keep context explicit

Environment, target, intent, request metadata, approval context, and policy context should stay coherent across the full multi-step flow instead of being inferred implicitly.

Common patterns

Server-side orchestrators

Use the SDK when a backend service or control plane already owns the multi-step workflow and just needs direct runtime decisions.

Custom tool runners

Map your own tool runner or action dispatcher into explicit execute and commit requests instead of trying to force it into an adapter abstraction.

Irreversible-action review flows

Run the proposal or inspection step first, then gate the later commit on the earlier operation response and supporting operation id when the workflow reaches a durable or irreversible boundary.

Multi-step continuity

Carry session, request, and operation continuity through queue or job boundaries so later stages can preserve decision context instead of collapsing multiple runtime stages into one assumption.

From quickstart to production

This is where you replace the demo request builders with request shaping that reflects your real application model.

  • replace the quickstart request builders with domain-specific request factories in your own service code
  • move from demo IDs to stable agent.id and session.id values that map to real operator, workflow, or user context
  • make environment, target, intent, and constraints explicit instead of relying on demo defaults
  • keep free Sandbox reads on staging or development data; production reads and security-sensitive reads require a policy that allows broader access
  • treat the execute-to-commit operation boundary as a first-class part of your orchestration
  • handle denied requests inside real product flow instead of surfacing them as generic transport failures

Observability and debugging

The SDK does not maintain managed helper history for you, so your own request, response, and operation logging becomes the primary observability surface.

Observability

logger.info("admissible_execute", {  requestId: executeRequest.requestId,  sessionId: executeRequest.session.id,  operationId: executeResponse.operation.id,  result: executeResponse.result,  status: executeResponse.status,  nextAction: executeResponse.nextAction.type,});logger.info("admissible_commit", {  requestId: commitRequest.requestId,  supportingOperationId: commitRequest.supportingOperationId,  operationId: commitResponse.operation.id,  result: commitResponse.result,  status: commitResponse.status,  nextAction: commitResponse.nextAction.type,  continuationHandle: commitResponse.continuation?.handle ?? null,});
  • log requestId, session id, agent id, result, status, and nextAction on both execute and commit paths
  • preserve operationId and supportingOperationId so execute and commit can be correlated later
  • persist parsed BaselineClientError.data when the runtime rejects a request at the API boundary
  • add a stable userAgent and any required default headers so traces can be attributed back to the calling service

Production guidance

Once the SDK integration is live, keep the runtime-facing context and transport behavior stable across the services that call it.

  • use stable agent.id values that map to real capabilities or service roles, not random ids per request
  • use stable session.id values that reflect a real workflow, thread, or user-controlled run boundary
  • set timeoutMs intentionally for your environment instead of leaving every service on the same implicit default
  • keep userAgent and default headers consistent so traces and logs remain attributable
  • carry tenant or policy-specific context explicitly in request metadata or commit policy context when your runtime policy depends on it
  • avoid hidden control flow between execute and commit; keep the runtime branch points visible in your service code
  • roll out by gating lower-risk execute paths first, then expand into durable and irreversible commit paths
  • treat production reads and secret, environment, credential, or API-key reads as sensitive access unless the effective account policy explicitly allows them

Common mistakes

  • treating blocked or approval-gated runtime outcomes like generic transport failures
  • calling commit without checking the earlier execute response or carrying the supporting operation id
  • using unstable request, session, or agent identifiers that break continuity
  • logging only the thrown message and discarding BaselineClientError status, code, or data
  • keeping demo request ids, session ids, or environment names after moving into production
  • putting irreversible state change on the execute path because it feels shorter in a demo
  • collapsing multiple runtime stages into one application assumption instead of preserving explicit branching between them

Reference

BaselineClient

new BaselineClient({
  baseUrl,
  apiKey,
  timeoutMs,
  defaultHeaders,
  userAgent,
})

Creates the direct SDK client for execute and commit API calls.

  • baseUrl and apiKey are required
  • timeoutMs defaults to 15000
  • defaultHeaders and userAgent let you carry service-level metadata into the request

client.execute(...)

client.execute({
  requestId,
  agent,
  session,
  action,
  intent,
  constraints,
  metadata,
})

Sends an execute request and returns the immediate operation response.

  • use this when the system is evaluating whether an action should proceed
  • inspect result, status, nextAction.type, and operation.id on the response

client.commit(...)

client.commit(
  {
    requestId,
    agent,
    session,
    proposedCommit,
    commitType,
    supportingOperationId,
  },
  {
    idempotencyKey,
  },
)

Sends a commit request for a durable or irreversible state change.

  • use this when a later state change needs explicit commit gating
  • keep supportingOperationId tied to the earlier execute path
  • add artifact, rollback, approval, or policy context when the commit path needs it and always pass an explicit idempotencyKey

client.getOperation(...)

client.getOperation(operationId)

Fetches the full status view for an operation that is still processing, pending approval, or otherwise needs follow-up.

  • use the operation id returned by execute or commit
  • inspect approval, continuation, retry, rollback, and supersession state
  • use this before operator follow-up when nextAction tells you to check status

client.resolveOperationApproval(...)

client.resolveOperationApproval(operationId, {
  decision: "approve" | "deny",
  comment,
  reviewerNotes,
  reason,
})

Submits an operator approval or denial decision for an operation that is waiting on approval.

  • call this only after getOperation shows approval.state === pending
  • decision must be approve or deny
  • follow with getOperation when you need the updated operation state

BaselineClientError

error.status
error.code
error.message
error.data

Represents a non-success API response from the runtime.

  • status is the HTTP status
  • code is the parsed runtime-facing error code when one is present
  • data preserves the parsed response body for deeper inspection

BaselineTimeoutError

error.timeoutMs

Represents a client-side timeout at the SDK transport layer.

  • treat this as transport behavior, not a runtime decision
  • choose retry behavior based on your own idempotency and workflow model