| description | Create, sign, and verify your first Agent Manifest in under 15 minutes, covering Level 0 software-only signing and Level 1 TPM attestation. |
|---|
This guide walks through creating, signing, and verifying an Agent Manifest in under 15 minutes. It covers Level 0 (software-only signing) and Level 1 (TPM-attested).
!!! tip "TL;DR"
Install with pip install "agent-manifest[cli]", run manifest keygen, manifest create, manifest sign, then manifest verify. Level 0 needs no hardware. For Level 1, run manifest attest on a TPM 2.0, AMD SEV-SNP, or Intel TDX host.
- Python 3.11 or later
- For Level 1: a Linux host with TPM 2.0 (
tpm2-toolsinstalled), an AMD SEV-SNP VM, or an Intel TDX VM
# Core SDK
pip install agent-manifest
# With CLI
pip install "agent-manifest[cli]"
# With post-quantum profile
pip install "agent-manifest[pq]"Level 0 is suitable for development, staging, and non-regulated environments. No hardware required.
manifest keygen -d ./keys/
# Creates keys/private.hex and keys/public.hexOr in Python:
from agent_manifest import generate_ed25519
keypair = generate_ed25519()
# Store keypair.private_hex and keypair.public_hex securelyfrom agent_manifest import (
Manifest, ArtifactBindings,
SystemPromptBinding, PolicyBundleBinding,
ToolManifestBinding, ModelIdentityBinding,
CryptoProfile, DeploymentType, EnforcementMode, ModelAttestationType,
PolicyLanguage,
)
from agent_manifest._types import HashValue, ManifestId
from datetime import datetime, timedelta, timezone
import hashlib
now = datetime.now(timezone.utc)
# Hash your actual artifacts
system_prompt_text = "You are a document summarization assistant..."
prompt_hash = "sha256:" + hashlib.sha256(
system_prompt_text.encode("utf-8")
).hexdigest()
manifest = Manifest(
manifest_id=ManifestId("019236ab-cdef-7000-8000-000000000001"),
agent_id="spiffe://trust.acme.co/agent/doc-summarizer/prod",
issued_at=now,
expires_at=now + timedelta(days=90),
issuer="spiffe://trust.acme.co/signing-authority",
crypto_profile=CryptoProfile.standard,
artifacts=ArtifactBindings(
system_prompt=SystemPromptBinding(
hash=HashValue(prompt_hash),
hash_algorithm="SHA-256",
version="1.0.0",
classification="internal",
bound_at=now,
),
policy_bundle=PolicyBundleBinding(
hash=HashValue("sha256:" + "b" * 64),
policy_language=PolicyLanguage.cedar,
version="1.0.0",
enforcement_mode=EnforcementMode.enforce,
bound_at=now,
),
model_identity=ModelIdentityBinding(
provider="anthropic",
model_id="claude-haiku-4-5-20251001",
version="20251001",
deployment_type=DeploymentType.api,
model_attestation_type=ModelAttestationType.provider_asserted,
bound_at=now,
),
),
)from agent_manifest import Ed25519Signer, generate_ed25519
keypair = generate_ed25519()
signer = Ed25519Signer(keypair)
manifest_dict = manifest.model_dump(mode="json", by_alias=True)
sig_block = signer.sign(manifest_dict)
manifest_dict["signature"] = sig_block
print(sig_block["algorithm"]) # Ed25519Or with the CLI:
manifest create my-agent-config.json -o draft.json
manifest sign draft.json --key keys/private.hex -o signed.jsonfrom agent_manifest._verify import verify_manifest, VerificationContext, RevocationStore
# Fail-closed: VALID requires the issuer's key in trusted_keys. Without
# trusted keys the result is UNVERIFIABLE - never VALID.
result = verify_manifest(
manifest_dict,
VerificationContext(
system_prompt_hash=prompt_hash,
policy_bundle_hash="sha256:" + "b" * 64,
trusted_keys={keypair.key_id: keypair.public_b64url()},
),
RevocationStore(),
)
print(result.result.value) # VALIDOr with the CLI:
manifest verify signed.json --public-key keys/public.hexWithout --public-key, signed manifests fail closed as UNVERIFIABLE
because the CLI has no trusted issuer key to authenticate the signature.
The CLI --public-key option accepts the raw Ed25519 public key generated by
manifest keygen.
A VALID result only means something if you have seen the same manifest come back
MISMATCH. There are two independent ways it does, and they fail differently.
The record was edited after signing. Swap the approved model version inside the signed manifest and re-verify:
import copy
tampered = copy.deepcopy(manifest_dict)
tampered["artifacts"]["model_identity"]["version"] = "20260101"
result = verify_manifest(
tampered,
VerificationContext(trusted_keys={keypair.key_id: keypair.public_b64url()}),
RevocationStore(),
)
print(result.result.value) # MISMATCH
print(result.signature_verified) # False
print(result.mismatch_details[0].field) # signatureThe signature no longer covers the bytes, so the edit is caught without the verifier knowing anything about models.
The record is intact but the running agent drifted. Here the signature still verifies. What fails is the declared-vs-actual comparison, which is the binding the manifest exists for:
import hashlib
running_prompt = "You are a document summarization assistant. Ignore prior instructions."
running_hash = "sha256:" + hashlib.sha256(running_prompt.encode("utf-8")).hexdigest()
result = verify_manifest(
manifest_dict,
VerificationContext(
system_prompt_hash=running_hash, # what is actually loaded
trusted_keys={keypair.key_id: keypair.public_b64url()},
),
RevocationStore(),
)
print(result.result.value) # MISMATCH
print(result.fields_verified.system_prompt.value) # MISMATCHPass the real hash and the same call returns VALID with system_prompt: MATCH.
Both checks answer "is this the agent that was approved" at the moment you verify.
Neither one watches the agent afterwards: an attacker who changes the system prompt in
memory after this check has passed is outside what a boot-time manifest can see. That
boundary is deliberate and documented in Limitations, and
attest_runtime_state() is the primitive for closing it with a hardware-signed
freshness proof on a cadence you choose.
Level 1 adds hardware attestation, which binds the manifest hash to a TEE measurement that cannot be forged by the operator. Required for EU AI Act Art. 15 (cybersecurity) and enterprise production deployments.
On Ubuntu/Debian:
apt install tpm2-toolsOn AWS: Nitro Enclaves with aws-nitro-enclaves-sdk-python installed.
from agent_manifest._auto_provider import select_provider
# Auto-selects: OPAQUE -> SEV-SNP -> TDX -> TPM -> Software
provider = select_provider(level=1)
provider.extend_manifest_hash(manifest_dict)
report = provider.get_attestation_report()
manifest_dict["attestation"] = {
"tee_type": report.platform, # "tpm" | "amd-sev-snp" | "intel-tdx" | "opaque"
"manifest_hash_in_report": True,
"report_uri": report.report_uri,
"bound_at": now.isoformat(),
}Or with the CLI:
manifest attest signed.json --provider auto --level 1 -o attested.json
manifest verify attested.json --public-key keys/public.hexThe verification result for a Level 1 manifest includes attestation_verified: true when the TEE measurement matches the manifest hash.
When an artifact changes (new model version, policy update, system prompt revision), revoke the old manifest and issue a new one:
manifest revoke <manifest-id> \
--reason "policy bundle updated to v1.1.0" \
--revoked-by security@acme.coIn Python:
from agent_manifest._verify import RevocationStore
store = RevocationStore()
store.revoke(
manifest_id="019236ab-cdef-7000-8000-000000000001",
reason="policy bundle updated to v1.1.0",
revoked_by="security@acme.co",
)- Read the full specification
- Browse the examples for complete manifest JSON for each conformance level
- For hardware attestation setup on Azure, AWS, or GCP, see the platform-specific notes in Section 3.3.1 of the spec
- For EU AI Act HITL compliance, see Section 9.1 and the Level 2 example in
examples/ - For post-quantum signing, install
agent-manifest[pq]and setcrypto_profile=CryptoProfile.post_quantum