Adapters

Where a framework or a system meets the boundary

An adapter does one of two jobs. On the way in, it turns a framework's tool call into a proposal that the trusted runtime captures as an intent. On the way out, it executes an authorized action against a real system and reports what happened.

Source
agent-safe-pipeline v0.5.0
Checked
2026-10-29

On the way in

Framework tool calls

For the OpenAI Responses API and Agents SDK, the Vercel AI SDK and LangChain, the adapter is a few lines of your own code. What each framework already carries is in the coverage matrix.

Rendered from spec/intent/v1/frameworks.md, “The adapter” in decionis/agent-safe-pipeline at v0.5.0 (e1e702f), synced 2026-10-29. The repository is the source of truth. View this file on GitHub

The adapter

The adapter is the few lines between the record and IntentCapture. In the reference test it is exactly this, once per framework:

// OpenAI: the arguments are a JSON string.
const parameters = JSON.parse(item.arguments) as JsonObject;
const proposal = { action: item.name, target: `shopify:order:${parameters.orderId}`, parameters };

// Vercel AI SDK: the input is already an object.
const proposal = {
  action: part.toolName,
  target: `shopify:order:${part.input.orderId}`,
  parameters: part.input,
};

// LangChain: the args are already an object.
const proposal = {
  action: call.name,
  target: `shopify:order:${call.args.orderId}`,
  parameters: call.args,
};

// The same for all three: the trusted runtime states what the record cannot.
const captured = new IntentCapture().capture(proposal, {
  tenantId, // the organization
  actor: { id: "synthetic-support-agent", type: "AI_AGENT", runtime: "openai" }, // the principal, from the runtime's own identity
  downstreamTarget: { system: "shopify", operation: "refund", endpoint: "POST /refunds" },
  context: { source: "conformance", tool_call_id: item.call_id }, // the framework's id, kept for correlation
  idempotencyKey: "refund-synthetic-1-openai-v1", // the runtime's retry identity, never the model's id
});
// captured.intentHash is what the authority is asked about and what the grant is bound to.

The target is the adapter's to name because the framework does not distinguish it from the other arguments; which argument identifies the resource is a property of the tool, not of the framework. The framework's call id is kept in the trusted context so the binding correlates with the framework's own record, and it is not used as the idempotency key: it identifies the model's call, and a retry of the same call by the runtime must present the same intent, while a new call with the same id from a different turn must not.

Python agent frameworks

Packaged guards on PyPI, for the frameworks whose tools you wrap rather than proxy. Their source is not public.

AutoGen

decionis-autogen 0.2.0, MIT. Python 3.10+.

pip install decionis-autogen

The AutoGen adapter

CrewAI

decionis-crewai 0.2.0, MIT. Python 3.10 to 3.13.

pip install decionis-crewai

The CrewAI adapter

LangChain (Python)

decionis-langchain 0.1.0, MIT. Python 3.10+.

pip install decionis-langchain

The LangChain (Python) adapter

TypeScript agent frameworks

A packaged wrapper on npm, for LangChain.js tools and LangGraph.js nodes.

LangChain.js

@decionis/langchain 0.2.0, MIT. Its source is not public.

npm install @decionis/langchain @decionis/sdk @langchain/core

The LangChain.js adapter

On the way in

MCP tools

MCP is the one case where the adapter ships with AgentSafe. MCP decides how an agent invokes a tool; McpGuard in @decionis/agentsafe decides nothing, but binds each invocation to an intent so that Decionis can decide whether this exact invocation may run, and the tool runs once, on a claimed grant.

On the way out

Native executors

A native executor is what carries an authorized action to a system of record and accounts for its effect. In the dispatch style it performs the write itself, once; in the permit style the platform performs the write and presents the permit it was issued. It never calls the authority or decides an outcome: the runner does that, the same way for every executor, and after the attempt reports the outcome to the authority as COMMITTED, FAILED or INDETERMINATE. The contract, agent-safe.executors.native/1, as the source defines it:

/**
 * The interface every native executor implements. The runner calls it in one
 * order: `capabilities` and `prepare` at admission, `execute` once after the
 * claim (dispatch) or at presentation (permit), `observe` on the platform's
 * answer, and `reconcile` only after an indeterminate observation or a
 * missing receipt.
 */
export interface NativeExecutor<TAction = unknown, TReceipt = JsonObject, TResult = unknown> {
  readonly platform: PlatformId;
  readonly version: string;
  readonly style: ExecutionStyle;
  readonly actionTypes: readonly ActionType[];
  /** What can be executed now, and what is missing when it cannot (scope, switch, connection). */
  capabilities(scope: ExecutorScope): Promise<readonly ExecutionCapability[]>;
  /** Platform facts + `agent-safe.intent/1` binding + idempotency identity. No I/O side effects. */
  prepare(action: TAction, scope: ExecutorScope | null): Promise<PreparedAction>;
  /**
   * Optional: the target's state version, read once, read-only, after
   * `prepare` and before the authority is asked. The runner binds it into the
   * intent as `agent_safe.precondition`; the authority carries it to a
   * verifying provider, which refuses the write once the target moved on, and
   * an executor whose platform takes a conditional write sends it as the
   * write's precondition (`If-Match`) itself. Null when the action names no
   * existing target, a create. It is the only I/O before a decision, and a
   * read that fails is a refusal: nothing is decided without the version the
   * executor said it would bind.
   */
  readState?(
    action: TAction,
    prepared: PreparedAction,
    scope: ExecutorScope | null,
  ): Promise<TargetPrecondition | null>;
  /** dispatch: write once with the claimed grant. permit: return the permit the native boundary must present. */
  execute(run: ClaimedExecution<TAction>): Promise<ExecutionEffect<TReceipt>>;
  /** Map the native receipt (posting entry, feed ID, HTTP status) to COMMITTED | FAILED | INDETERMINATE. */
  observe(
    effect: ExecutionEffect<TReceipt>,
    prepared: PreparedAction,
  ): Promise<EffectObservation<TResult>>;
  /** Called only after INDETERMINATE or a missing receipt: COMPLETED | NOT_EXECUTED | UNKNOWN. */
  reconcile(context: ReconcileContext): Promise<ReconcileResult<TResult>>;
}

packages/pipeline/src/execution/NativeExecutor.ts at v0.5.0. View the file

capabilities
says what can be executed now under the connection, and what is missing when it cannot
prepare
works out the platform's facts, the intent binding and the idempotency identity, with no side effect
readState
optional: reads the target's state version once, before the authority is asked, so the write can be refused once the target has moved on
execute
dispatch: writes once with the claimed grant; permit: returns the permit the platform must present
observe
maps the platform's answer to COMMITTED, FAILED or INDETERMINATE
reconcile
called only after an indeterminate observation or a missing receipt: completed, never executed, or unknown

Two families ship with AgentSafe today. Banking is dispatch style: it maps the BEAP vocabulary onto the earlier EffectAdapter interface, which @decionis/agentsafe runs unchanged as a native executor. Cards is permit style: the issuer's authorization request presents the grant, and the issuer's result settles it. AgentSafe is pre-1.0, so the interface may still change between releases. The banking adapter · The cards executor

At the far end

Provider-side verification

The system being called can check for itself that a request carries a valid grant for exactly that request before it acts, instead of trusting the gateway in front of it.

The profile and its vectors

Three levels: the executor's message signature, the authority's claim attestation for this exact request with replay refused, and the provider's own signed receipt of the effect.

The profile · Conformance

Verifiers

A TypeScript reference and verifiers for Envoy and Kong (Go), Spring, Rust and .NET.

verifiers/