Getting Started¶
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).
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.
Prerequisites¶
- 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
Installation¶
# Core SDK
pip install agent-manifest
# With CLI
pip install "agent-manifest[cli]"
# With post-quantum profile
pip install "agent-manifest[pq]"
Level 0 - Software-only signing¶
Level 0 is suitable for development, staging, and non-regulated environments. No hardware required.
Step 1: Generate a signing key¶
Or in Python:
from agent_manifest import generate_ed25519
keypair = generate_ed25519()
# Store keypair.private_hex and keypair.public_hex securely
Step 2: Build the manifest¶
from 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,
),
),
)
Step 3: Sign the manifest¶
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"]) # Ed25519
Or with the CLI:
manifest create my-agent-config.json -o draft.json
manifest sign draft.json --key keys/private.hex -o signed.json
Step 4: Verify¶
from 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) # VALID
Or with the CLI:
Without --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.
Step 5: Watch it catch a change¶
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) # signature
The 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) # MISMATCH
Pass 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 - TPM attestation¶
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.
Prerequisites¶
On Ubuntu/Debian:
On AWS: Nitro Enclaves with aws-nitro-enclaves-sdk-python installed.
Attest the signed manifest¶
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.hex
The verification result for a Level 1 manifest includes attestation_verified: true when the TEE measurement matches the manifest hash.
Revocation¶
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 [email protected]
In 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="[email protected]",
)
Next steps¶
- 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