TL;DR — An AI agent is a black box that now writes to systems and moves money. ABOM (pip install abom-cli) gives it three things: a signed Composition Manifest (what it’s made of), an inline gate that denies any tool call outside that manifest before it runs, and a Notary — a Certificate-Transparency-style Merkle log — that lets an auditor verify what happened without trusting the operator. Open source, Apache-2.0, runs offline. Below: how it works, the cryptography that makes it more than a dashboard, and where it honestly is today (draft v0.1).
Why this matters
Your SIEM tells you the agent wired money to an attacker yesterday. That’s a log. A control stops the call before it executes — and proves it stopped it.
Agentic AI crossed a line in 2025–26: agents stopped suggesting and started acting. They call tools, hit internal APIs, read confidential records, and — in the worst case — move money. Meanwhile the institutions running them are legally accountable for what their software does.
Three gaps fall out of that, and none of them have a clean answer today:
- Composition is opaque. No one can say, in one signed document, which models, tools, prompts, data sources and policies an agent is built from — or prove the deployed agent matches what was approved.
- Actions are unaccountable. When an agent makes a consequential decision, the evidence is scattered application logs you have to trust — not a tamper-evident record you can verify and hand to a regulator.
- Nothing stops a bad action. A prompt-injected agent that calls a tool it was never authorized to use is, in most stacks, only logged after the fact.
We did this once before for software: the SBOM (Software Bill of Materials) went from nice-to-have to mandated in about three years. CycloneDX even shipped an ML-BOM for models. ABOM — the Agent Bill of Materials — is the obvious next step: extend that standard to full agents, and add the runtime layer SBOMs never had.
The three questions
Every capability in ABOM maps to a question a risk team actually asks about an agent:
| Question | ABOM answer | Mechanism |
|---|---|---|
| What is it made of? | Composition Manifest | abom scan → a signed inventory |
| What is it allowed to do? | The inline gate | deny-by-default, before execution |
| What did it actually do? | Provenance + Notary | Merkle-notarized, verifiable proofs |
If you’re non-technical, the analogy is: a nutrition label, a permission slip, and a flight recorder — all signed so none of them can be faked.
1. What the agent is made of — the signed manifest
abom scan walks a repo’s dependencies and source and emits a Composition Manifest: every model (with weight hashes), tool, prompt, data source, policy, framework and MCP server it can find, each ed25519-signed.
Here’s a trimmed manifest for a fictional loan-document agent:
{
"abom": "0.1",
"extends": "CycloneDX ML-BOM",
"type": "CompositionManifest",
"agent": { "name": "loan-doc-agent", "version": "1.4.0", "risk_class": "high (Annex III)" },
"components": [
{ "type": "model", "name": "local/qwen2.5-coder", "weights_sha256": "9f2c…", "egress": false },
{ "type": "tool", "name": "read_kyc_doc" },
{ "type": "tool", "name": "http_fetch", "scope": "egress", "allowed_endpoints": ["internal-kyc.bank"] }
],
"controls": { "egress": "deny-by-default", "residency": "EU" },
"composition_sha256": "411d…",
"signature": { "alg": "ed25519", "value": "…" }
}The composition_sha256 is the join key: every runtime record points back to the exact manifest it ran under, so a swapped model or a shadow tool at runtime shows up as drift.
2. What the agent is allowed to do — block, don’t just log
This is the part that turns a bill of materials into a control. The gate sits at the tool-call boundary. Before an action runs, it decides ALLOW or DENY against the signed manifest — and it is deny-by-default: a tool that isn’t in the manifest is blocked, not logged-and-allowed.
It’s framework-agnostic. The cleanest integration is a decorator:
from abom import Gate, Action, ActionDenied
gate = Gate(signed_manifest, run_id="loan-doc-agent@1.4.0")
@gate.gated() # wrap any tool
def wire_transfer(amount, to):
... # this body NEVER runs if wire_transfer
# isn't declared in the signed manifest
try:
wire_transfer(1_000_000, to="attacker-iban") # a prompt injection
except ActionDenied as e:
print(e.decision.rule) # → "tool_not_in_manifest"
# the money never moved, and the denial is notarizedThe gate ships three decidable rules, evaluated in order (first failure wins): tool_not_in_manifest, endpoint_not_allowed (egress outside a tool’s allowlist), and residency (confidential data leaving via an egress tool).
The demo, end to end
The repo ships a gate_demo.py that tells the whole story. Here’s the real output — an agent doing legitimate work, then getting prompt-injected:
[1] Agent reads a KYC document (declared tool)
→ ALLOW (manifest_allows)
[2] Agent fetches from internal-kyc.bank (allowed endpoint)
→ ALLOW (manifest_allows)
[3] ⚠ Prompt injection: agent calls wire_transfer($1,000,000)
(wire_transfer is NOT in the signed manifest)
→ DENY (tool_not_in_manifest)
tool 'wire_transfer' is not in the signed Composition Manifest
the money never moved — the call was blocked before execution
[4] Agent tries http_fetch → evil.example.com (not on allowlist)
→ DENY (endpoint_not_allowed)The same logic is available as a CLI exit code, so it drops straight into CI or agent middleware — 0 for ALLOW, 1 for DENY:
3. What the agent actually did — a Notary you can verify
Here’s where it gets interesting, and where most “audit log” products quietly fall short.
A linked hash chain proves internal consistency — but an operator who controls the store can recompute the entire chain from genesis after editing a record. So a plain chain proves nothing to a third party who doesn’t trust the operator. That third party — an auditor, a regulator, a cyber-insurer — is exactly who the record is for.
ABOM’s Notary uses an append-only Merkle transparency log: the same RFC 6962 / Certificate Transparency construction that underpins the web’s certificate ecosystem. It gives you two proofs that need no trust in the operator:
- Inclusion proof — “this decision is in the log, at this position, under this signed tree head” (an
O(log n)audit path). - Consistency proof — “the log of size m is a prefix of the log of size n; nothing already published was edited or deleted.”
Running the demo’s verification stage against a real run:
decisions notarized : 4
transparency log : size=4 root=b63298020ffc410824e941ef…
auditor checks the wire_transfer denial (seq 2):
inclusion proof valid : True (it IS in the signed log)
tree-head signature : True (signed by local key 7f7aa8d6efabb18f)
attacker tries to backdate the record (flip DENY → allow):
forged record included : False (rejected — the proof no longer matches)That last line is the whole point: you can’t quietly flip a DENY to an allow after the fact. The forged entry no longer satisfies the inclusion proof, and the signed tree head won’t validate. The trust is cryptographic, not reputational.
A demo that signs with a plaintext key on disk is rejected by any bank’s security team on sight — and it makes the tamper-evidence claim false, since whoever holds the key can forge tree heads. ABOM puts signing behind a Signer protocol: LocalSigner for dev/CI, and a KMSSigner seam where the private key lives in a KMS/HSM and never leaves it. Verification is backend-independent, so an auditor checks the same ed25519 signature regardless of where the key was held.
How it fits together
CUSTOMER TRUST BOUNDARY (your infra, air-gap capable)
┌───────────────────────────────────────────────────────────┐
│ agent runtime (any framework: LangGraph / CrewAI / MCP) │
│ │ │
│ ▼ │
│ abom scan ──▶ the gate ──▶ the Notary │
│ signed deny-by- Merkle log: inclusion + │
│ Manifest default, consistency proofs, │
│ (composition BEFORE signed tree heads (KMS) │
│ _sha256) execution │
└───────────────────────────────────────────────────────────┘
keys in KMS/HSM · ed25519 — trust is in the keys + proofsScan an agent → gate every tool call against its signed manifest → notarize every decision into a log anyone can verify.
Where this sits next to CycloneDX
ABOM extends CycloneDX ML-BOM rather than forking it — it rides the dominant, ECMA-standardized SBOM format. CycloneDX answers “what is it made of.” ABOM adds the runtime enforcement and provenance layer it doesn’t model. Component types map across (model → machine-learning-model, tool → service/application, and so on); the gate, the hash chain and the Merkle Notary are ABOM-only additions.
The "extends": "CycloneDX ML-BOM" lineage is declared on every document today; a native CycloneDX import/export is on the roadmap, not yet shipped — which brings us to the honest part.
Build status — what’s real vs. roadmap
Honesty about maturity is a feature for a trust project. ABOM is draft v0.1, pre-1.0. As of this writing:
| Capability | Status |
|---|---|
abom scan → signed Composition Manifest |
✅ Built |
| Hash-chained Action Provenance + verification | ✅ Built |
ed25519 signing (LocalSigner) |
✅ Built |
| Inline gate — deny-by-default enforcement | ✅ Built |
| Merkle transparency log — inclusion + consistency proofs | ✅ Built |
Decidable policy checks (abom verify) |
✅ Built |
KMS/HSM-backed signing (KMSSigner seam) |
🚧 In construction |
| OPA/Rego policy engine (currently JSON) | 🚧 In construction |
| Hardened, queryable Notary registry + API | 🚧 In construction |
| SDK runtime hooks / framework adapters | 🚧 In construction |
| Native CycloneDX / SIEM export · eBPF egress · air-gap bundle | 📋 Planned |
Two things I’d not over-claim. Completeness: the gate only mediates the tool calls routed through it — it doesn’t magically observe egress it can’t see, so the manifest declares its own capture boundary rather than pretending to catch everything. The regulation timeline: the EU AI Act’s high-risk record-keeping obligations (Art. 12) were deferred to ~Dec 2027 by the Digital Omnibus, and DORA doesn’t literally mandate an agent BOM. The regulatory tailwind is real, but it’s the inevitability slide — not the reason to adopt this today. The reason to adopt it today is that your agents are already taking actions you can’t bound.
Try it
Or from source, to run the full walkthrough:
Closing thought
The market is racing to make agents do more. ABOM is a bet on the unglamorous layer underneath: making an agent answerable and controllable — what is it made of, what is it allowed to do, and what did it do? — answered in a signed, standard, portable artifact you can hand to an auditor.
Underneath the software, it’s really a trust primitive. And the trust is cryptographic, not reputational — which, for anything an autonomous agent is allowed to do with your money, is the only kind that should count.
ABOM is open source (Apache-2.0) and a work in progress. Code, spec and the demo live at github.com/josephassiga/abom-dev; the package is abom-cli on PyPI. Feedback and spec proposals welcome.