Skip to content

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.

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
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
{
"_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" }
}
}
  1. 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.
  2. 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).
  3. Cross-check the journal: axiom explain <decision-id> prints the same decision in human-readable form, and axiom journal verify proves the apply journal chain is intact.
  4. 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.