Skip to content

Verify a Human Approval Record

Some agents may only run after a person signs off. This is called human-in-the-loop (HITL) approval. This page is for developers who need to require that sign-off: you mark an approval as required, check it against an approver key you trust, and see a tampered approval rejected.

The example uses a made-up approver and a software key. It shows how the checks work; it is not a real human decision, and it does not show a key held in hardware.

Run the example

Complete the first-manifest guide, append this block to first_manifest.py, and run it again.

from datetime import datetime, timezone
from agent_manifest._delegation import HitlApprovalSigner

approver = generate_ed25519()
approver_id = "mailto:[email protected]"
approved_record = copy.deepcopy(record)
approved_record["hitl_record"] = {"required": True, "approvals": []}
approved_record["signature"] = Ed25519Signer(keypair).sign(approved_record)
approval_context = context.model_copy(update={
    "enforce_hitl": True,
    "approver_public_keys": {approver_id: approver.public_b64url()},
})
missing = verify_manifest(approved_record, approval_context, RevocationStore())
assert missing.result.value != "VALID"
assert missing.fields_verified.hitl_record.value == "MISSING"
print("PASS: missing required approval rejected")

approval = {
    "approval_id": "019236ab-cdef-7000-8000-000000000004",
    "approver_id": approver_id,
    "approver_identity_type": "email",
    "approver_role": "demo-reviewer",
    "approved_at": datetime.now(timezone.utc).isoformat(),
    "approved_scope": {
        "artifacts": ["system_prompt"],
        "risk_tier": "low",
        "approval_duration_seconds": 600,
        "conditions": [],
    },
    "approval_method": "software-key",
    "evidence_uri": "https://example.test/synthetic-approval",
}
approval["approval_signature"] = HitlApprovalSigner(approver).sign_approval(
    manifest_id=approved_record["manifest_id"],
    approved_at=approval["approved_at"],
    approved_scope=approval["approved_scope"],
    approver_id=approver_id,
    approval_method=approval["approval_method"],
)
approved_record["hitl_record"]["approvals"] = [approval]
accepted = verify_manifest(approved_record, approval_context, RevocationStore())
assert accepted.result.value == "VALID", accepted.model_dump_json()
print("PASS: trusted software approval accepted")

changed_approval = copy.deepcopy(approved_record)
changed_approval["hitl_record"]["approvals"][0]["approval_method"] = "hardware-key"
rejected = verify_manifest(changed_approval, approval_context, RevocationStore())
assert rejected.result.value != "VALID"
assert rejected.signature_verified  # The issuer signature still verifies.
print("PASS: relabeled approval rejected independently of issuer signature")

Expect three additional PASS lines. The recipient keeps its own copy of the approver key. A key that simply arrives alongside an approval is not trusted because of that.

Understand the two signatures

Two different parties sign here. The issuer signs the manifest, including the rule that an approval is required. The approver signs the approval itself. Each signature is checked on its own.

Technical detail: what each signature covers

The issuer signs the HITL requirement. The signing preimage normalizes approvals to an empty list, allowing approvals to attach after issuance. Adding or removing an approval therefore does not by itself break the issuer signature. Each approval needs its own signature and the receiving policy must enforce its presence.

The approval signature binds the manifest ID, approver ID, approval timestamp, scope, and supplied approval method. Role labels and evidence links are not substitutes for independently authenticated authority. A hardware-key string alone does not prove hardware custody; use a trusted approval service and the required evidence for that claim.

Apply it to a real workflow

In a real workflow the hard part happens before anything is signed: confirming who the person is and that they have the authority to approve. Authenticate the human and establish their authority over the requested action before issuing the approval. Configure trusted approver keys, expiry handling, required scope, and enforce_hitl on the recipient. Check operation-specific authorization before allowing side effects, including retries.

A signed approval is one piece of evidence. It does not, by itself, establish regulatory compliance, appropriate human oversight, or that an approved action completed. See revocation for withdrawing manifests and the integration gate for rejection handling.