Agent decision attestation v0.1
| Predicate type | https://dragoscv.github.io/axiom/spec/attestation/agent-decision/v0.1 |
| Envelope | in-toto Statement v1, one per line (JSON Lines); --sign wraps each in a DSSE envelope (Ed25519) |
| Producer | axiom journal attest decisions [--since <iso>] [--limit <n>] [--sign] (old verb axiom attest decisions) over .axiom/decisions.jsonl |
| Builder | buildAgentDecisionStatement in packages/canon/src/statement.ts |
| Legacy URI | https://auxidus.dev/attestation/agent-decision/v0.1 — what 2.x produced. 3.x never produces it; a verifier that must accept 2.x statements treats both URIs as the same predicate (AGENT_DECISION_V0_1_LEGACY in @codai/axiom-canon) |
| Status | draft (v0.1) — follows the schema proposed in in-toto/attestation#554; an in-toto.io alias will be added once that proposal is accepted |
The decision journal is written by the gate when the profile sets record: true (and always
for relaxed decisions — warn and override). Each record maps 1:1 onto one statement.
Subject
Section titled “Subject”Exactly one entry, content-addressed by the invocation:
name |
digest.sha256 |
|---|---|
the tool name (Write, run_in_terminal, apply_patch, …) |
hex of args_hash: sha256 of the JCS (RFC 8785) form of the tool arguments |
Predicate
Section titled “Predicate”| Field | Type | Meaning |
|---|---|---|
agent_id |
string | harness / agent session identity the payload carried |
principal |
{ subject: string } |
who the agent acted for: the first 16 hex characters of sha256(OS username), or the plain username when AXIOM_JOURNAL_USERNAME=1 |
policy_evaluations |
{ policy, decision, reason, policy_ids? }[] |
one entry: policy: "axiom.gate", decision: "allow" | "deny", the gate’s reason, and policy_ids: [<rule code>] when a rule decided (e.g. house.forcePush, path.deny) |
tool_calls |
{ name, args_hash }[] |
the tool and sha256:<hex> of its JCS arguments |
decided_at |
string (ISO 8601) | when the gate decided — supplied by the journal; the builder reads no clock |
args_state |
"recorded" | "unavailable" |
whether the arguments were available to hash |
axiom |
{ paths: string[], profile: string } |
AXIOM extension: root-relative paths judged (sorted) and the gate profile source |
Example
Section titled “Example”{ "_type": "https://in-toto.io/Statement/v1", "subject": [{ "name": "run_in_terminal", "digest": { "sha256": "e3b0…55" } }], "predicateType": "https://dragoscv.github.io/axiom/spec/attestation/agent-decision/v0.1", "predicate": { "agent_id": "copilot:5f2c…", "principal": { "subject": "9a1f03c47be2d815" }, "policy_evaluations": [ { "policy": "axiom.gate", "decision": "deny", "reason": "Force-push rewrites remote history; ask the user first.", "policy_ids": ["house.forcePush"] } ], "tool_calls": [{ "name": "run_in_terminal", "args_hash": "sha256:e3b0…55" }], "decided_at": "2026-10-19T09:12:44.120Z", "args_state": "recorded", "axiom": { "paths": [], "profile": ".axiom/axiom.json" } }}Verifying
Section titled “Verifying”- Recompute the subject: hash the JCS form of the arguments you hold and compare with
subject[0].digest.sha256. A mismatch means the statement is about a different call. - Check the signature when produced with
--sign: the DSSE envelope is Ed25519 over the PAE of the statement; verify it against a key in.axiom/trust/keys.json(Signing). - Cross-check the journal:
axiom explain <decision-id>prints the same decision in human-readable form, andaxiom journal verifyproves the apply journal chain is intact. - Uploaded to Sigstore from CI:
gh attestation verify <file> --repo <owner>/<repo> --predicate-type https://dragoscv.github.io/axiom/spec/attestation/agent-decision/v0.1.
Related: Apply attestation v1 for the tree-level claim.