AttestationRecord to the adapter’s AttestationChain, and every decision boundary appends a DecisionRecord before the action runs. Both kinds share one chain, each record points at the hash of the one before it, and with the [crypto] extra each record is signed with Ed25519. A verifier holding your public key can then check that no record was altered, reordered or forged.
What a chain holds
Attestation records
AnAttestationRecord is the evaluator’s verdict at one point in time.
Decision records
ADecisionRecord captures what the agent was about to do and what it saw, built before the action executes so the rationale cannot be written after the fact.
Adapters capture decisions at these points:
Framework adapters produce
minimal records today: frameworks expose the candidate action but not the model’s deliberation. IntegrityMonitor.record_decision() and the adapters’ record_decision() accept a reasoning chain and rejected alternatives when you have them.
Capture fails open. If building a decision record raises, the adapter logs a warning, emits a capture_failure event carrying the decision point and exception class, and lets the action proceed.
The chain is a record of what the agent did. Decision records carry tool arguments in full and short summaries of prompts and tool output, and the full chain is written wherever you save it. Treat a saved chain with the same care as the session’s logs.
Evidence
Each attestation listsEvidence entries (evidence_type, source, content_hash, summary, timestamp) that commit it to other data by hash:
AttestationChain.verify_cross_agent_links() also recognizes peer_message and handoff entries; see Multi-agent systems.
Hash linkage
Each record’scontent_hash is a SHA-256 over its canonical payload: deterministic JSON with sorted keys and no whitespace. chain_previous is part of that payload, so each record commits to its predecessor. AttestationChain.append() sets the link, and raises ValueError if a record already carries a different one.
chain_hash_linked, and who signed the chain as chain_signatures (unsigned unless the adapter has a signing_key; see the summary fields). chain.signature_status(trusted_keys) returns the same value.
Signing
Install the extra, generate an Ed25519 key, and give it to the adapter. Every attestation and decision record is then signed over its canonical payload, link included.IntegrityMonitor and every adapter class accept signing_key=. The zero-config hooks() and instrument() functions do not take a key either, so construct the class when you want signed records. Keep the private key out of the agent’s reach: an agent that can read it can sign forged records.
Verifying signatures
verify_signatures() returns (True, None) only when every record is signed and its signature verifies, otherwise (False, index_of_first_bad_record).
trusted_keys. Each record embeds its own public key, so without a pinned set a chain an attacker re-signed with their own key verifies against itself. With trusted_keys, a record whose embedded key is not in the set fails.
Saving and loading a chain
Restoring from a checkpoint
RecoveryLayer.restore_to() rolls the layer’s score history back to a checkpoint. It never removes a record from the chain, because the records after a checkpoint are the evidence of what went wrong.
- Intact chain. Every record stays, and a
recovery_restoredecision record is appended that names the checkpoint. - Chain that no longer verifies. The whole chain, broken records included, is first saved to the checkpoint backend as an archive (
metadata["kind"] == "pre_restore_archive"). The live chain then continues from the checkpoint’s records, and therecovery_restorerecord carriesarchive_idandarchived_records. An archive cannot itself be restored to. - Refusals.
restore_to()raisesValueErrorfor a checkpoint whose chain does not verify, is not signed bytrusted_keys, or is not from this chain’s history, and for a signed chain withoutsigning_key. It raisesKeyErrorfor an unknown id.
signing_key signs the recovery record, and is required when the chain holds signed records, so a restore never leaves a signed chain failing verify_signatures(). Pass trusted_keys to require that every record in the checkpoint is signed by one of those keys. Without it the checkpoint backend is trusted: anyone who can write to it can rewrite a record and re-link the hashes under the same record ids. The score history in a checkpoint is not signed. Restore while the adapter is not handling events, since its lock does not cover a restore.
Verifying from the command line
agentegrity verify-decisions (also python -m agentegrity verify-decisions) runs all three checks on a saved chain: hash linkage, decision links, and signatures.
--trusted-key takes a file holding one hex public key and can be repeated. Without it, signatures are checked against the keys embedded in the records and the output marks them UNPINNED (self-vouched). The command does not check cross-agent links. agentegrity report renders the same chain as a Markdown or JSON audit report; see the CLI reference.
Key providers
AKeyProvider maps an agent_id to the raw 32-byte public key that agent is supposed to sign with. It is the trust anchor for verifying other agents’ chains.
get_public_key(agent_id) -> bytes | None works. verify_cross_agent_links(peer_chains, key_provider=provider) requires each referenced peer record to carry exactly the pinned key and a valid signature.
Credential provenance
An agent can declare the credentials it uses so a leaked secret can be traced to the agent and every session that used it. The chain records a keyed fingerprint and a name, never the value.- The fingerprint is HMAC-SHA256 over the stripped secret, keyed by
AGENTEGRITY_FINGERPRINT_KEY. A bare SHA-256 of a structured key (sk-,ghp_,AKIA) would be cheap to brute-force and would match across every organization. - Without the key, nothing is fingerprinted or recorded and the SDK logs one INFO line. It never falls back to an unkeyed hash.
labelis a name such asopenai/api_keyor the variable name. Do not put any characters of the secret in it.declare_from_envuses the variable name as the label.- Declaring the same secret twice is a no-op.
Evidence(evidence_type="credential_use") with the fingerprint as its content_hash. Because the exporter wire format carries no chain records, the adapter also emits a credential_declared event, replayed right after on_session_start if no exporter was attached yet, and lists the fingerprints under credentials in the session summary. To match a secret a scanner found, compute the same value with agentegrity.fingerprint(secret, key=...); credentialFingerprint() in @agentegrity/client returns the identical value.
Current limits: the coding-agent hooks do not declare credentials, and a session resumed from a saved chain does not restore the declared set, so redeclare after resuming.
Limits
- Chains are unsigned unless you pass a signing key. The coding-agent hook chains are unsigned today.
- A signature proves which key signed a record. It does not prove the key was not stolen; keep it in an HSM, KMS or secret store.
- Decision provenance shows the rationale was bound at decision time. If upstream content already corrupted the agent’s reasoning, the record faithfully attests a corrupt decision.
- Deleting a record and re-linking the rest is caught only by signature verification together with an independent record of the chain head.
- Chains serialized before 0.7.0 do not verify with current code.

