Adapter hub

One adapter per model provider.

An ABOM adapter wraps a model provider's SDK so your agent's calls are recorded automatically, emitting signed Action Provenance and, optionally, enforcing the gate inline with no manual instrumentation. Pick the adapter for the provider you use.

The Anthropic, OpenAI, and Mistral SDK adapters, the LangChain framework adapter, and the Model Armor guardrail adapter ship today; the rest are on the roadmap. Each entry below is marked Available or Planned. You can already produce a signed bill of materials with abom scan and enforce actions with abom gate — adapters automate the runtime-provenance capture on top of that. Want a provider prioritized? Email hello@abom.ai.
Zero instrumentation at all? Adapters are cooperative — they observe an agent that routes calls through them. The MCP gateway needs no adapter and no code change: point the agent's MCP client at abom gateway run and every tools/call becomes a signed, hash-chained Action Provenance Record — inspect-only by default, --enforce to block. See the gateway quickstart.

What an adapter does

How an adapter works

An adapter does one thing: it wraps the method your SDK uses to call the model, leaving your code unchanged. instrument(client, …) replaces that method in place and returns the same client object.

The intercepted method (per provider)

Each adapter wraps the single call surface its SDK exposes for a model request:

ProviderSDKMethod wrapped
Anthropic Availableanthropicclient.messages.create
OpenAI Availableopenaiclient.chat.completions.create
Mistral Availablemistralaiclient.chat.complete
LangChain Availablelangchaincallbacks: on_tool_start · on_llm_end
1
Gate (optional, before the call)

If gate=True, the adapter checks the requested model against the signed manifest first. A model that isn't declared is denied — ActionDenied is raised and the request never leaves your boundary.

2
Call through

The original SDK method runs unchanged. Same arguments, same return value — your agent code doesn't know it's wrapped.

3
Record (after the call)

The adapter reads the model, token usage, and output from the response, hashes the output, and appends a record to the run's hash-chained Action Provenance log — bound to the manifest's composition_sha256.

Adapter catalog

One adapter per model provider — the providers ABOM targets for runtime capture. Anthropic, OpenAI, and Mistral are available now; the rest are Planned and the catalog grows as adapters ship.

Framework adapters

Model-SDK adapters capture what an agent says; framework adapters also capture what it does — tool calls, gated deny-by-default before they execute. LangChain is available now via a callback handler.

Guardrail & screening adapters

A third kind of adapter: it doesn't capture calls, it notarizes another product's verdicts. The gateway's detector seam accepts any (tool, args) → findings callable and seals the findings into each signed record's detectors[] — the screen never gates, policy decides. Google Model Armor is available now: abom gateway run --model-armor-template projects/… keeps the guardrail you already bought and gives you the third-party-verifiable evidence its console can't. They decide, you prove.

Using an adapter

The flow is the same for every provider — generate a manifest, install, wrap, verify.

0

Generate the manifest (abom.json)

The adapter needs a signed Composition Manifest to record against and gate on. You don't write it by hand — abom scan produces it from your code, detecting the models, tools, and frameworks it uses, then signs it.

shell
abom scan . -o abom.json      # scan this repo → signed abom.json
# abom.json now declares e.g. the model "claude-3-5-sonnet-20241022"

The model you call at runtime must be one this manifest declares — that's what the gate enforces. New to this? See Get started for the full scan walkthrough.

1

Install

Adapters ship with the CLI. Install abom-cli and the provider extra for the SDK you use.

shell
pip install "abom-cli[anthropic]"   # the provider extra for the SDK you use
2

Wrap your client

Point the adapter at your existing model client and your signed manifest. No changes to your call sites — the adapter records each call and can deny one that falls outside the signed envelope.

agent.py
from abom.adapters import instrument
from anthropic import Anthropic

client = instrument(Anthropic(), manifest="abom.json", gate=True)

# use the client exactly as before — calls are now recorded
client.messages.create(model="claude-3-5-sonnet-20241022", messages=[...])

What instrument returns. It hands back the same client object — your code keeps calling it normally. It doesn't replace the client; it does two things to it:

  • swaps the model method (messages.create) for a wrapped version that gates → calls → records;
  • attaches a run handle at client.abom that holds the live record list.

client.abom.chain is that record list — one Action Provenance record per model call, appended as your agent runs. After two calls it has two entries; each is hash-chained to the previous one and bound to the manifest. Read it, save it, or hand it to abom verify:

python
import json

# client.abom.chain is a list of provenance records — one per model call
for record in client.abom.chain:
    print(record["seq"], record["data"]["model_calls"])

# persist it next to the manifest for later verification
json.dump(client.abom.chain, open("provenance.json", "w"), indent=2)

Each record looks like this — see the Action Provenance reference for every field:

one record in client.abom.chain
{
  "run_id": "loan-doc-agent",
  "seq": 0,
  "event_type": "ActionProvenance",
  "prev_hash": "GENESIS",           # chains to the previous record
  "hash": "a1b2c3…",
  "data": {
    "composition_sha256": "…",    # the manifest this ran under
    "decision": "model_call",
    "model_calls": [
      { "model": "claude-3-5-sonnet-20241022", "tokens": 128, "output_sha256": "…" }
    ]
  }
}

On LangChain instead? Frameworks act through tools, so the LangChain adapter is a callback handler rather than a client wrapper — it records model and tool calls, and with gate=True an undeclared tool raises ActionDenied before the tool body runs:

langchain_agent.py
from abom.adapters.langchain import AbomLangChainHandler

handler = AbomLangChainHandler(manifest="abom.json", gate=True, sink="auto")

# the ONE change to your agent: pass the handler as a callback
agent.invoke({"input": "..."}, config={"callbacks": [handler]})

# model + tool records, hash-chained and persisted like any run
handler.abom.chain
3

Verify the run

Each call appends to a hash-chained Action Provenance log bound to the manifest. Verify the whole run — signature, chain integrity, and policy — after the fact.

shell
abom verify abom.json --policy finance.json
#  ✓ VALID — signature OK, chain intact, policy clean

See the Action Provenance reference for the record shape, and the policy hub for a policy to enforce.

Contribute an adapter

Want a provider that isn't available yet? The fastest path is to email hello@abom.ai with the provider name and SDK — we'll implement it. If you'd like to spec or prototype it, here's the whole contract.

An adapter does exactly two provider-specific things: recognize its client and wrap the one method that calls the model. Everything else — the provenance chain, the gate, binding to the manifest — is shared in abom.adapters.base, so an adapter is small.

1

The contract

An adapter implements three things; parse normalizes a provider response into a ModelCall.

abom/adapters/base.py (the interface)
# what every adapter implements
class Adapter(Protocol):
    provider: str                              # "acme"
    def matches(self, client) -> bool: ...    # is this my client?
    def wrap(self, client, run) -> Any: ...   # instrument it

# the normalized capture each call produces
class ModelCall:
    model: str
    tokens: int | None
    output_sha256: str | None
    endpoint: str | None
2

Write the adapter

Wrap the provider's model method: gate → call through → record. The run object (shared) does the recording and gating; you only supply provider knowledge.

abom/adapters/acme.py
import functools
from .base import AbomRun, ModelCall, _sha256, register

ACME_ENDPOINT = "api.acme.ai"

def parse(model, response) -> ModelCall:
    # read the provider's response shape → normalized fields
    text = response.output_text
    return ModelCall(
        model=model,
        tokens=response.usage.total_tokens,
        output_sha256=_sha256(text) if text else None,
        endpoint=ACME_ENDPOINT,
    )

class AcmeAdapter:
    provider = "acme"

    def matches(self, client) -> bool:
        return type(client).__module__.split(".")[0] == "acme"

    def wrap(self, client, run: AbomRun):
        original = client.generate                 # the method that calls the model

        @functools.wraps(original)
        def generate(*args, **kwargs):
            model = kwargs["model"]
            run.enforce(ModelCall(model=model, endpoint=ACME_ENDPOINT))  # gate
            response = original(*args, **kwargs)                          # call through
            run.record(parse(model, response))                           # record
            return response

        client.generate = generate
        return client

register(AcmeAdapter())    # makes instrument() auto-detect it

Duck-type on the client (check the module / attributes) rather than importing the SDK, so the provider package stays an optional dependency.

3

Test it

Prove the three guarantees with a fake client — no live API, no SDK install. Every shipped adapter has this exact test shape.

tests/test_adapters_acme.py
def test_records_and_gates():
    client = instrument(FakeAcme(), manifest=signed_manifest, gate=True)
    client.generate(model="acme-1", prompt="hi")

    chain = client.abom.chain
    assert chain[0]["data"]["model_calls"][0]["model"] == "acme-1"
    assert verify_chain(chain)["valid"]        # hash chain holds

    # a model not in the manifest is denied before the call runs
    bad = instrument(FakeAcme(), manifest=other_manifest, gate=True)
    with pytest.raises(ActionDenied):
        bad.generate(model="acme-1", prompt="hi")
Ready? Email hello@abom.ai with the provider name and SDK, or attach a prototype following the shape above — we'll get it reviewed and shipped, and its tile flips to Available.