agentattest binds an AI coding-agent run to a concrete git patch, tree, PR, and approval state by emitting an in-toto Statement v1 carrying a custom predicate https://agentattest.dev/predicate/v0.
It owns only the predicate, deterministic subject-binding rules, verifier inputs and failure codes, a default Rego policy, and CLI/Action ergonomics.
Cryptography, transparency logs, traces, SBOM, and VEX are delegated to existing standards — DSSE, Sigstore/cosign, Fulcio/Rekor, GitHub Artifact Attestations, OpenTelemetry/OpenInference, SPDX, CycloneDX, OpenVEX. No new wheels.
- Why
- Non-Goals
- Architecture
- Quick Start
- CLI
- Data Model
- Verification Pipeline
- Verification Levels
- Failure Codes
- Privacy Defaults
- Project Layout
- Existing Wheels
- Roadmap
- Hard Invariants
- License
- Contributing
AI coding agents now author large fractions of real PRs, but the only post-hoc tooling the ecosystem offers is fingerprinting — guess-who forensics over diff style. Repository owners need a first-party, verifiable claim that ties a run to a diff, plus a deterministic policy gate that fails closed on subject, repo, base-commit, identity, privacy, or level violations.
agentattest is that gate, and only that gate.
| Problem | What agentattest does |
What it does not do |
|---|---|---|
| "Was this PR produced by an agent?" | Emit a signed claim binding run → patch / tree / PR / artifact | Identify agents heuristically from style |
| "Did the same workflow really build this?" | Cross-check verified signer / builder / workflow against predicate | Re-implement Sigstore or cosign |
| "Was raw prompt content leaked?" | Reject public raw evidence and credentialed / data: URIs by schema + policy |
Scan referenced artifact bytes for secrets |
| "Was this attestation replayed?" | Bind repo / base / PR / run / freshness as deterministic verifier context | Run a transparency log of its own |
agentattest will not invent or replace any of the following — see docs/NON_GOALS.md.
- New cryptography, key formats, PKI, timestamping, or transparency logs.
- A new attestation container — it stays on in-toto Statement v1.
- A new trace protocol — references OpenTelemetry / OpenInference by URI + digest.
- A new SBOM / VEX format — links SPDX / CycloneDX / OpenVEX as sidecars.
- An observability platform, code-quality oracle, secret manager, or VSCode extension (in v0).
- A trust root for
agent.name/model.name— those are self-asserted metadata only.
Trust comes from verified signer / builder / workflow identity plus recomputed subject digests, never from anything self-asserted in the predicate.
Eight layers; dependencies flow inward (orchestration → domain) then outward through adapters. See ARCHITECTURE.md for forbidden import directions.
┌──────────────────────── integration ─────────────────────────┐
│ cmd/agentattest · action/ │
└────────────────────────────┬─────────────────────────────────┘
│
┌────────────────────────────▼─────────────────────────────────┐
│ internal/app (orchestration) │
└──────┬───────────────────────────────────────────────┬───────┘
│ │
┌───▼─────┐ ┌────────┐ ┌───────────┐ ┌────────┐ │
│ gitbind │ │ predi- │ │ statement │ │ verify │ │
│ (capt.+ │ │ cate │ │ (in-toto) │ │ (5 ph) │ │
│ binding)│ │ (v0) │ └─────┬─────┘ └───┬────┘ │
└───┬─────┘ └────┬───┘ │ │ │
│ │ │ │ │
│ │ ┌──────▼──────┐ ┌──▼─────┐ │
│ │ │ signing │ │ policy │ │
│ │ │ (DSSE/cosign│ │ (Rego) │ │
│ │ │ /Sigstore) │ └────────┘ │
│ │ └─────────────┘ │
│ │ │
┌───▼────────────▼──────────────────────────────────▼─────┐
│ internal/cache (SQLite) │
│ refs · digests · timestamps ─ never trusts pass/fail │
└─────────────────────────────────────────────────────────┘
Hard rules
- Predicate packages do not import Sigstore / Rekor / SQLite / OPA.
- Only
internal/signingmay import DSSE / cosign / Fulcio / Rekor. - Cache code never decides verification success.
- The GitHub Action wraps the CLI, never re-implements the verifier.
- Go ≥ 1.23
gitonPATH- (optional)
cueandopafor hand validation outside Go tests
go build ./cmd/agentattest
go test ./...# 1. Initialize the local cache + .agentattest config
# (no secrets, no pass/fail decisions stored)
./agentattest init --dir .
# 2. Compute deterministic git binding
# repo URL · base · head · patch digest · changed files
./agentattest digest --repo .
# 3. Emit an in-toto Statement v1 with
# predicateType = https://agentattest.dev/predicate/v0
./agentattest predicate create --repo . --out statement.json
# 4. Verify against verifier-context JSON
# (subjects, repoUrl, baseCommit, requiredLevel, ...)
./agentattest verify predicate \
--statement statement.json \
--context tests/golden/valid-minimal/context.jsonThe verifier exits non-zero on any failure code and emits a stable JSON result of shape { valid, level, failureCodes[] }.
| Command | Purpose |
|---|---|
agentattest init [--dir DIR] |
Create .agentattest/ with a SQLite cache and a JSON config. The cache holds only refs / digests / timestamps — never pass/fail decisions. |
agentattest digest [--repo DIR] |
Compute repo URL, branch, base/head commits, patch SHA-256, changed-file SHA-256s. Cross-platform deterministic. |
agentattest predicate create [--repo DIR] [--out PATH] [--repo-url ...] [--base-commit ...] [--agent-name ...] [--agent-version ...] |
Build a v0 predicate + in-toto Statement v1 with subject[] = { patch.diff: sha256 }. Defaults: evidence-grade, local execution, no raw evidence. |
agentattest verify predicate --statement PATH --context PATH |
Run the 5-phase pipeline. Returns { valid, level, failureCodes[] } JSON. |
The CLI delegates to internal/app; cmd/agentattest/main.go is a six-line entry point.
The signed claim is an in-toto Statement v1. The custom JSON Schema applies only to the inner predicate object — the outer container stays interoperable with cosign, GitHub attestations, and slsa-verifier.
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{ "name": "patch.diff", "digest": { "sha256": "aaaa…aaaa" } },
{ "name": "repo-tree", "digest": { "sha256": "bbbb…bbbb" } }
],
"predicateType": "https://agentattest.dev/predicate/v0",
"predicate": {
"predicateVersion": "v0",
"runId": "run_01HTYJ7B4A3H8M0S8P7M2N9Q",
"verificationLevel": "policy-grade",
"repo": {
"url": "https://github.com/example/agentattest",
"baseCommit": "0123…4567",
"branch": "feature/agent-binding",
"pullRequest": { "number": 123 }
},
"agent": {
"name": "example-agent",
"version": "1.0.0",
"invocationKind": "ci-step",
"declaredIdentity": "sigstore:github:example-agent"
},
"environment": {
"executionType": "github-actions",
"builder": {
"id": "https://github.com/.../workflows/agentattest.yml@refs/heads/main",
"type": "github-actions-workflow",
"workflowRef": "...@refs/heads/main",
"runnerClass": "github-hosted"
}
},
"humanApproval": { "state": "approved-after-generation" },
"privacy": {
"redactionPolicy": "default-minimal-v0",
"rawPrompt": { "stored": false, "visibility": "none" },
"rawToolOutputs": { "stored": false, "visibility": "none" },
"publicTransparencyLog": { "allowed": true, "includesRawContent": false }
},
"timestamps": { "startedAt": "2026-04-27T09:12:31Z", "finishedAt": "2026-04-27T09:16:42Z" },
"evidence": [
{
"type": "tool-summary",
"uri": "artifact://...",
"digest": { "sha256": "..." },
"storage": "artifact-store",
"visibility": "team"
}
]
}
}predicateVersion · runId · verificationLevel · repo · agent · environment · humanApproval · privacy · timestamps · evidence
model · runtime (OTel / OpenInference trace IDs) · changes · sidecars (SPDX / CycloneDX / OpenVEX) · materials · extensions (URI-keyed, closed, digest-addressed only)
The full schema is split across three files that must stay in lockstep:
| File | Owns |
|---|---|
schemas/agent-provenance-v0.schema.json |
Structure, additionalProperties: false, types, URI patterns, GitHub builder string shape |
schemas/agent-provenance-v0.cue |
Cross-field semantics — timestamp ordering, runtime ↔ trace-evidence binding, raw-visibility coupling |
policies/default.rego |
Runtime context — repo / base match, exact subject set, verified identity, level escalation, replay, freshness |
Five ordered phases. Each invariant has one owning phase; later phases must not reclassify earlier-phase failures.
| Phase | Owner | Checks |
|---|---|---|
| 01 | Go pre-schema gate (internal/verify/phase01) |
_type, predicateType, predicate.predicateVersion routing — before JSON Schema |
| 02 | JSON Schema 2020-12 (santhosh-tekuri/jsonschema) |
required fields, closed objects, enums, URI safety, GitHub builder shape |
| 03 | CUE (cuelang.org/go) |
cross-field semantics: timestamp ordering, runtime ⇒ trace evidence |
| 04 | OPA Rego (open-policy-agent/opa) |
required level, exact subject set, repo / base match, verified signer / builder / workflow / issuer, level escalation, public-log privacy, witness / approval, replay, freshness |
| 05 | Go post-policy ordering (OrderFailureCodes) |
dedupe + stable failure-code ordering for user-visible output |
Phase 04 is the only phase that emits level_escalation. JSON Schema and CUE intentionally allow a policy-grade predicate with local-only evidence so Rego can produce the stable code.
evidence-grade ──► policy-grade ──► high-assurance
────────────── ──────────── ──────────────
local execution allowed forbidden forbidden
local-only ev. allowed forbidden forbidden
verified signer optional required required
verified builder optional required required
isolated/witnessed — — required
verified witness — — ≥1 required
approved-before-
merge + digest — — required
runner allowlist — — required
| Level | Intended for | Proves | Does not prove |
|---|---|---|---|
evidence-grade |
local CLI, dev workflows, early PR evidence | a statement was produced binding one run to subjects + privacy settings | local machine integrity, agent metadata truthfulness |
policy-grade |
CI / GitHub merge gates | signed / verified envelope, exact subject equality, verified non-local builder / signer / workflow / issuer | runner cannot be compromised, code is correct |
high-assurance |
isolated runners, witnessed execution, protected merges | all of policy-grade + allowlisted isolated/witnessed builder, ≥1 verified witness, verified approved-before-merge digest, freshness/replay context |
hypervisor / firmware / SaaS plane integrity, freedom from vulnerabilities |
A level is a policy profile, not a universal trust proof. Local capture caps at evidence-grade; CI/GitHub reaches policy-grade; isolated runner + witness + protected merge gate is what approaches high-assurance.
All codes are stable strings — part of the public contract. The Go verifier emits them in the deterministic order below (defined in internal/verify/failure_order.go). Rego deny sets are unordered, so Go owns presentation.
| # | Code | Meaning |
|---|---|---|
| 10 | unsupported_statement_type |
_type is not in-toto Statement v1 |
| 20 | predicate_type_mismatch |
predicateType is not https://agentattest.dev/predicate/v0 |
| 30 | unsupported_predicate_version |
predicateVersion is not v0 (rejected before schema) |
| 40 | schema_invalid |
JSON Schema or CUE failure |
| 50 | signature_invalid |
DSSE / Sigstore envelope signature failed |
| 60 | transparency_verification_failed |
Rekor / timestamp / witness verification failed when required |
| 70 | subject_digest_mismatch |
statement subjects ≠ context subjects (set equality) |
| 80 | repo_url_mismatch |
predicate repo URL ≠ context repo URL |
| 90 | base_commit_mismatch |
predicate base commit ≠ context base commit |
| 100 | signer_identity_mismatch |
verified signer missing or ≠ agent.declaredIdentity |
| 110 | builder_identity_mismatch |
verified builder / workflow / issuer missing or unmatched; self-hosted without opt-in; high-assurance runner not allowlisted |
| 120 | replay_detected |
PR number / runId differs from verifier context |
| 130 | privacy_violation |
raw / trace evidence on transparency-log; non-public on transparency-log; public extension |
| 140 | level_escalation |
policy-grade or high-assurance with local execution / local-only evidence / missing builder / missing high-assurance evidence |
| 150 | level_below_required |
predicate level below context.requiredLevel |
| 160 | missing_evidence |
required trace / approval / witness / digest reference absent |
| 170 | stale_attestation |
outside the configured freshness window, or now missing while window > 0 |
| 180 | policy_eval_error |
policy engine cannot evaluate deterministic inputs |
| 190 | cache_untrusted |
SQLite cache row conflicts with verified statement; cache is ignored |
Multi-code ordering is verified separately from golden fixtures: each invalid golden has exactly one expected code. The ordering fixture lives at
internal/verify/testdata/unsupported-version-subject-mismatch.json.
Default rule: do not store raw prompts, raw tool outputs, raw retrieved documents, or raw model responses. Store digests, references, redacted summaries, and encrypted blobs only.
| Visibility | Use for |
|---|---|
public |
digests, predicate type, repo URL, subject names, non-sensitive summaries |
team |
redacted trace summaries, CI artifacts, approval metadata |
secret |
sensitive tool summaries, private issue refs, internal IDs |
encrypted |
raw prompts, raw tool outputs, private traces, customer data |
Hard structural rules, enforced jointly by JSON Schema, CUE, and Rego:
rawPrompt.visibilityandrawToolOutputs.visibilitymay never bepublic.- If
stored == falsethenvisibilitymust benone. evidence[].urirejectsdata:payloads and HTTP(S)userinfo(e.g.https://token:x@host/...).extensionsis URI-keyed, closed, and every value is a digest-addressed reference object — no inline blobs, nopublicextensions.publicTransparencyLog.includesRawContentis constrained tofalse.
Structural checks do not scan referenced artifact contents. Redaction is engineering hygiene, not a proof of safety.
See docs/PRIVACY_MODEL.md and docs/THREAT_MODEL.md.
agentattest/
├── cmd/agentattest/ CLI entry — 6 lines, routes to internal/app
├── internal/
│ ├── app/ orchestration init · digest · predicate · verify
│ ├── gitbind/ deterministic repo URL · base/head · patch sha256 · file sha256
│ ├── predicate/ v0 predicate build map + StatementFor() helper
│ ├── statement/ in-toto v1 parse / new / typed Document
│ ├── verify/ pipeline phase01..05, OrderFailureCodes, golden_test, testdata
│ ├── policy/ Rego adapter wraps open-policy-agent/opa
│ ├── cache/ SQLite refs · digests · timestamps (no pass/fail)
│ └── contracts/ path locator walks up to find schema/CUE/policy/context
├── schemas/
│ ├── agent-provenance-v0.schema.json JSON Schema 2020-12
│ └── agent-provenance-v0.cue CUE cross-field semantics
├── policies/
│ └── default.rego OPA Rego phase-04 policy
├── tests/golden/ ~30 fixtures · context.schema.json
│ ├── valid-minimal/ valid-github-ci/ valid-high-assurance/
│ └── invalid-*/ (one expected failure code each)
├── docs/
│ ├── DATA_MODEL.md predicate fields & semantics
│ ├── VERIFICATION_MODEL.md levels, phases, failure codes
│ ├── PRIVACY_MODEL.md defaults, visibility classes
│ ├── THREAT_MODEL.md threats, mitigations, residual risk
│ ├── NON_GOALS.md hard scope boundaries
│ └── EXISTING_WHEELS.md composition map
├── AGENTS.md entry map and strict rules
├── ARCHITECTURE.md layering, module boundaries, forbidden imports
├── TASKS.md v0-alpha → v0-beta → v0 acceptance criteria
└── CLAUDE.md working notes for AI agents on this repo
agentattest is a composition layer. Anything below is reused as-is and must not be re-implemented.
| Standard / tool | Role |
|---|---|
| in-toto Statement v1 | Outer container _type / subject / predicateType / predicate |
| DSSE | Signing envelope for statements |
| Sigstore / cosign / Fulcio / Rekor | Keyless signing, OIDC certs, transparency log |
GitHub Artifact Attestations · actions/attest · gh attestation |
GitHub-native attestation storage and verification |
| SLSA provenance vocabulary | Builder, workflow, materials terminology |
| OpenTelemetry / OpenInference | Run-time trace transport and AI semantic conventions |
| SPDX / CycloneDX / OpenVEX | SBOM / BOM / VEX sidecars |
| OPA / Rego | Policy-as-code (phase 04) |
| CUE | Schema + cross-field constraints (phase 03) |
| JSON Schema 2020-12 | Structural validation (phase 02) |
SQLite (modernc.org/sqlite) |
Local cache: refs, digests, timestamps only |
| Git | Source identity — commits, refs, normalized patch |
Tracked in TASKS.md with deterministic acceptance criteria — "no task may rely on AI judgment to decide whether it is secure."
| Milestone | Tasks | Status |
|---|---|---|
| v0-alpha | 1 Repository skeleton · 2 Predicate types & validation · 3 Deterministic git binding · 4 Local evidence capture contract · 5 SQLite cache prototype | shipped at commit f839b61 |
| v0-beta | 6 in-toto Statement assembly · 7 DSSE / Sigstore adapter · 8 Default Rego policy integration · 9 Golden test harness · 10 Privacy gate | in progress |
| v0 | 11 GitHub Action · 12 GitHub attestation verification · 13 PR summary output · 14 Release & compatibility contract · 15 Security review checklist | planned |
These will trip you up if violated. They are enforced by code and by review.
predicateTypeis exactlyhttps://agentattest.dev/predicate/v0.predicate.predicateVersionis exactlyv0. Unsupported versions are rejected in phase 01, before JSON Schema / CUE / Rego.- Every predicate object has
additionalProperties: false.extensionsis URI-keyed but each value is a closed digest-addressed reference. - A schema change without a matching CUE update and a golden fixture update is a broken change.
- Failure codes are stable strings; renaming them is a public-contract break.
- Raw prompt and raw tool-output visibility may never be
public. Ifstored == false, visibility must benone. local-onlyevidence is allowed only at evidence-grade. Rego owns this and emitslevel_escalation.- Subject digests are checked as equal sets, not supersets. Extra statement subjects are rejected.
- Verified envelope / certificate data outranks anything in the predicate. The cache never decides pass/fail.
- Only
internal/signingmay import DSSE / cosign / Fulcio / Rekor / GitHub-attestation libraries; only hashing / digest helpers may usecrypto/*elsewhere. - External commands use argv arrays, never shell-interpolated strings.
Breaking the predicate contract requires a new predicate URI and new golden fixtures — never in-place edits.
This project is licensed under the Apache License 2.0. See LICENSE for the full text.
Before any change:
AGENTS.md— entry map and strict rules.ARCHITECTURE.md— module boundaries, allowed / forbidden imports.- The doc that owns what you're touching (see the table in
CLAUDE.md). docs/NON_GOALS.md— do not negotiate scope without revisiting this.
Predicate / schema / CUE / policy changes require:
- Schema and CUE updated together.
- At least one new or updated golden fixture in
tests/golden/. - Failure codes use the existing stable strings, or add a new one and document it in
docs/VERIFICATION_MODEL.md. go test ./...passes on Linux, macOS, and Windows.