Two document types
Everything in ABOM is one of two artifacts. They are linked: every Action Provenance Record carries the composition_sha256 of the manifest the agent ran under, so a runtime action can always be traced back to the exact, signed thing it ran from.
Composition Manifest
A signed inventory produced at build / deploy time.
- The agent's identity, owner, and risk class
- Every component: models, tools, prompts, data, policies…
- Declared controls (egress, human-in-the-loop, residency)
- A composition_sha256 + ed25519 signature
Action Provenance Record
One hash-chained record per consequential action, at runtime.
- The decision taken and the inputs it acted on
- Model calls, tools invoked, data touched
- Policy decisions and human approvals
- A prev_hash → hash chain back to GENESIS
| Top-level field | Type | What goes here |
|---|---|---|
| abom | const "0.1" | The spec version. Bumps on breaking changes — never silent. |
| type | enum | "CompositionManifest" or, for provenance, an event_type of "ActionProvenance". |
Composition Manifest
A signed inventory of everything an agent is built from.
{
"abom": "0.1",
"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": "…", "egress": false },
{ "type": "tool", "name": "http_fetch", "scope": "egress", "allowed_endpoints": ["internal-kyc.bank"] },
{ "type": "prompt", "role": "system", "sha256": "…" },
{ "type": "dataSource", "name": "core_banking_repo", "classification": "confidential" },
{ "type": "policy", "engine": "OPA", "sha256": "…" }
],
"controls": { "egress": "deny-by-default", "hitl": "required-for-consequential", "residency": "EU" },
"composition_sha256": "…",
"signature": { "alg": "ed25519", "value": "…" }
}agent required
Identity and governance metadata for the agent the manifest describes.
| Field | Type | What goes here |
|---|---|---|
| name req | string | The agent's identifier, e.g. loan-doc-agent. |
| version req | string | Semantic or build version, e.g. 1.4.0. Forms the name@version run reference. |
| risk_class opt | string | Risk tier, free-form. Often maps to a regulation, e.g. high (Annex III) for the EU AI Act. |
| owner opt | string | Accountable team or person, e.g. risk-eng@bank. |
| purpose opt | string | One line on what the agent is for. |
controls optional
Declared, human-readable posture for the agent. These are declarations in the manifest; the machine-enforced version lives in the enforcement policy.
| Field | Type | What goes here |
|---|---|---|
| egress | string | e.g. deny-by-default — the agent's network posture. |
| hitl | string | Human-in-the-loop stance, e.g. required-for-consequential. |
| residency | string | Where data must stay, e.g. EU. |
controls accepts additional keys — it's an open object for posture you want recorded.
Component types
Each entry in components[] has a type and type-specific fields. There are seven types:
An LLM or ML model the agent calls.
A function or API the agent can invoke.
A system / template prompt.
A dataset, repo, or store it reads.
A policy bundle the agent ships with.
The agent framework / runtime.
A connected MCP server.
Only type is required. The other fields are populated where they apply to that type — a model carries weights_sha256, an egress tool carries allowed_endpoints, an MCP server carries endpoints, and so on. The object is open, so a scanner can record extra detail without breaking the schema.
| Field | Type | What goes here |
|---|---|---|
| type req | enum | One of the seven types above. |
| name | string | Component identifier — match what abom scan records. |
| version | string | Component version, where applicable. |
| sha256 | string | Digest of the component's bytes (prompt text, policy bundle…). |
| weights_sha256 | string | model — digest of the model weights, pinning the exact model. |
| provenance | string | Where the component came from (registry, repo, vendor). |
| egress | boolean | Whether the component can send data out of the boundary. |
| scope | string | tool — e.g. egress, read. |
| allowed_endpoints | string[] | tool — the endpoints an egress tool is permitted to reach. |
| role | string | prompt — e.g. system, user. |
| classification | string | dataSource — data sensitivity, e.g. confidential. |
| engine | string | policy — the policy engine, e.g. OPA. |
| endpoints | string[] | mcpServer — the MCP endpoints exposed. |
Action Provenance Record
One hash-chained record of a consequential action the agent took. Each record's prev_hash points at the previous record's hash, forming a tamper-evident chain back to "GENESIS".
| Field | Type | What goes here |
|---|---|---|
| run_id req | string | Agent reference, e.g. loan-doc-agent@1.4.0. |
| seq req | integer ≥ 0 | Monotonic position of this record in the chain. |
| event_type req | const | Always "ActionProvenance". |
| actor opt | string | Who/what produced the record. |
| created_at req | date-time | RFC 3339 timestamp. |
| prev_hash req | string | Previous record's hash, or "GENESIS" for the first. |
| hash req | sha256 | This record's digest — the chain link. |
| data req | object | The action payload (below). |
data — the action payload
What the agent actually did on this step. composition_sha256 and decision are required; the rest are recorded where relevant.
| Field | Type | What goes here |
|---|---|---|
| composition_sha256 req | sha256 | The manifest this action ran under — the join key. A mismatch is a composition_match finding. |
| decision req | string | What the agent decided to do. |
| inputs | object[] | The inputs the decision acted on. |
| model_calls | object[] | Each: model, tokens, output_sha256. Checked against the allowlist + manifest. |
| tools_invoked | object[] | Each: name, endpoint. Endpoints checked against the egress allowlist. |
| data_touched | object[] | Each: classification, egress. Egress of restricted data is a residency finding. |
| policy_decisions | object[] | Each: rule, result. A result: "required" demands an approval. |
| approval | object | null | by + decision (approved/rejected). Missing approval on a required action is an approval_coverage finding. |
Signature
The manifest is signed with a detached ed25519 signature over its canonical JSON form. This is what makes the trust cryptographic, not reputational — anyone with the public key can verify the document hasn't changed.
| Field | Type | What goes here |
|---|---|---|
| alg req | string | ed25519 in production. (An hmac-sha256 stand-in exists for the MVP only.) |
| value req | string | Base64 detached signature over the canonical body. |
| signer opt | string | Key identifier / signer reference. |
The enforcement policy
· policy component — an entry inside a manifest's components[] ({ "type": "policy", "engine": "OPA" }). It declares that the agent ships with a policy bundle.
· enforcement policy document — a separate JSON file (below) that the gate evaluates before an action runs, and that abom verify audits after the fact. This is the one you author and hand to --policy.
The enforcement policy has its own schema, abom-policy-0.1.schema.json. Every field maps to a concrete check. Allowlists are deny-by-default where set: omit a field to leave it unconstrained, set it to [] to deny all.
{
"name": "finance-baseline",
"allowed_models": ["local/qwen2.5-coder"],
"allowed_egress_endpoints": ["internal-kyc.bank"],
"no_egress_classifications": ["confidential", "restricted"],
"require_approval_when": "consequential"
}| Field | Type | What it enforces |
|---|---|---|
| name | string | Policy identifier, surfaced in findings and decision cards. |
| allowed_models | string[] | model_allowlist — declared and runtime models must be on the list; a runtime model absent from the manifest is composition_drift. |
| allowed_egress_endpoints | string[] | egress_allowlist — tool endpoints not on the list are flagged. |
| no_egress_classifications | string[] | residency — these classifications must never egress. |
| egress_allowed | boolean | Runtime gate: hard deny-by-default for outbound egress when false. |
| consequential_actions | string[] | Rules that mark an action consequential: writes_outside_workspace, touches_paths:<glob>. |
| approval_required_for_consequential | boolean | Runtime gate: consequential actions need human approval. |
| require_approval_when | enum | Audit-time counterpart: consequential / never / always → approval_coverage. |
| max_iterations_cap | integer | Runtime safety cap on the agent's reasoning loop. |
Ready-made policies for finance, manufacturing, and healthcare live on the policy hub. Build and validate your own with the policy builder.
v0.2 draft — signed records, event types, commitments
v0.2 is the spec of the MCP gateway: one signed record per tool call, written before the call runs. Everything below is implemented in the reference CLI, and the full draft ships with it — spec/abom-0.2-draft.md plus the decision log (docs/adr/) in the abom-cli sdist; the breaking changes are what justify the version bump. See how the pieces deploy in the interactive architecture.
Every record individually signed breaking
In v0.1 only the manifest carries a signature; the provenance chain is hash-linked but records are unsigned. In v0.2 every record MUST carry a detached ed25519 signature over its canonical form. The hash chain and the signature cross-cover: mutating a chained field breaks both. A valid v0.1 chain is not a valid v0.2 chain.
Event types
A v0.2 log is a chain of typed, signed records — the gaps are attested, not silent:
| event_type | What it attests |
|---|---|
| ActionProvenance | One per tools/call — allowed, denied, or would_deny (inspect mode). Written and fsync'd before forwarding; carries enforcement_mode, floor_hash, policy_template_hash, jurisdiction, detectors[], payload commitments. |
| ActionOutcome | The upstream result of a forwarded call, linked to its APR by outcome_of — closes the decision≠execution gap. |
| LogEpoch | REQUIRED first record of every log file: file creation is itself attested. Carries predecessor_final_hash (rotation continuity — catches tail-truncation, file suppression, delete-and-restart) and salt_custody. |
| GatewayDegraded | An upstream failure or timeout as a signed event — "we degraded at 14:03, here's the proof", never a missing record. |
| PolicyConformanceViolation | A project template refused for contradicting the org floor — the refusal itself enters the chain. |
Payload commitments
Arguments never enter the log as content. Each call records a per-file salted HMAC (arguments_hmac) — call identity that is not dictionary-attackable — plus a de-identified arguments_shape (keys, types, sizes). The salt stays inside the boundary; the epoch seals its hash, so a salt disclosed in a dispute is authenticatable and verify_argument_claim settles what a call contained.
Policy v0.2 — floor + template
Additive over v0.1 (abom-policy-0.2.schema.json): allowed_tools / denied_tools globs, allowed_egress_endpoints at the gateway, and a max_calls session budget. Two documents of the same schema can be layered — an org floor and a project template — evaluated conjunctively: floor denials cannot be overridden, allowlists intersect, the budget is the minimum, and denial reasons name the layer that fired.