Skip to main content

behavry-verify

Independently verify a Behavry evidence package. No Behavry account, no tenant access, no database, no network.

This is the code behind verify.behavry.ai, plus the offline CLI that does the same job on an air-gapped machine.

A Behavry APR evidence package is the sealed export of a single Agent Provenance Record: what an AI agent did, in order, with each step hash-linked and signed. This repo answers one question about such a package, and answers it without asking you to trust the answer:

Is this package exactly what Behavry signed, or has something been changed, removed, or reordered?


Why this is a separate repo

Verification has to be believable to someone who does not trust Behavry: a regulator, an external auditor, a cyber-insurance assessor, an opposing party in litigation. That means the verifier must be readable, small, dependency-light, and separable from the product it checks.

The offline CLI depends on exactly one third-party package, cryptography. You can read the whole verification core in about twenty minutes.


The trust model, stated plainly

Verification is only as good as the key it checks against. There are three ways to get one, and they are not equally strong. Every report says which one it used.

Path Strength What it proves
You supply the tenant's trust anchor Strongest The package was signed by the key the tenant gave you, out-of-band. Behavry is not in the loop.
You supply a raw public key Strongest Same, with the key pinned directly.
The published registry (/trust-anchor.json) Convenience only The package is internally consistent and signed by the named key, but the key came from Behavry. Behavry vouching for Behavry.

If a result matters, get the trust anchor from the issuing tenant directly. Their admin exports it from the Behavry dashboard (GET /api/v1/admin/apr-trust-anchor), and it is a small JSON file they can send you once and you can pin forever.

Note: an evidence package does not contain the public key needed to verify it. It carries only a truncated fingerprint (public_key_hint) so you can confirm you are holding the right anchor. The anchor itself must come from the tenant or from the registry.


What gets checked

✓ Signature valid              Ed25519 over the canonical manifest
✓ Signer fingerprint matches   the anchor really is this package's signer
✓ Merkle root matches          recomputed over every event hash
✓ Event count verified         the timeline holds what the manifest claims
✓ Event signatures valid       each event's own signature
✓ No chain breaks              every event links to its predecessor
✓ Producer chain flags intact  nothing was already broken at export time

Every check runs independently. Unlike a first-failure verifier, a broken hash chain still tells you whether the signature was good, because "signed by Behavry but missing an event" and "not signed at all" are entirely different findings.

A check that cannot meaningfully run is reported as SKIPPED, never as a pass. An unknown key id skips the signature check rather than failing it: a key you do not have is not evidence of tampering.

"No chain breaks" and package_version

Behavry hashes its audit chain per agent, while an evidence package holds a single session. A session is therefore a slice of a longer chain, and its events need not link to each other: when an agent runs two sessions at once, the chains interleave, and browser-based events have no agent at all and so carry no link pointer.

Packages at package_version 1.0 claimed contiguity within the package, and this check enforced it. From 1.1 the producer states per-event integrity instead — a claim that holds for any slice — and the walk is reported SKIPPED as not applicable.

Nothing is given up. What the walk was reaching for is covered by checks that do not depend on ordering, for every version:

  • an event removed or added moves the Merkle root, which the manifest signature covers;
  • a link pointer rewritten breaks that event's own signature, because previous_hash is inside the signed tuple.

A package_version this tool cannot parse is treated as older than 1.1, so it takes the stricter path. A verifier should never relax a check because it failed to understand its input.


Use it

Web

Go to verify.behavry.ai, drop in the .zip, optionally add the trust anchor.

Nothing you submit is stored, logged, or forwarded. The package is verified in memory and discarded. It is someone's audit trail from a regulated environment; the only defensible retention policy is none.

CLI (offline / air-gapped)

pipx install behavry-verify
behavry-verify --package apr-APR-2026-001942.zip --trust-anchor behavry-trust-anchor.json

Exit codes: 0 verified, 1 not verified, 2 unusable input, so it drops straight into CI.

behavry-verify --package apr.zip --trust-anchor anchor.json --json

API

curl -X POST https://verify.behavry.ai/api/v1/verify \
  -F package=@apr-APR-2026-001942.zip \
  -F trust_anchor=@behavry-trust-anchor.json

A package that fails verification still returns 200 with "verified": false: you asked a question and got a definitive answer. 4xx means the input was malformed, not that the evidence was bad.

POST /api/v1/verify.txt returns the plain-text report and uses the status code as the verdict (200 / 422), which is handier in a shell pipeline.


Develop

python3.12 -m venv .venv && ./.venv/bin/pip install -e ".[service,dev]"
./.venv/bin/pytest
./.venv/bin/uvicorn behavry_verify.app:app --reload

The compatibility surface

Two modules must match the Behavry producer byte-for-byte, or valid evidence silently stops verifying:

  • behavry_verify/canonical.pybackend/behavry/audit/signer.py and backend/behavry/apr/packages.py
  • behavry_verify/merkle.pybackend/behavry/apr/packages.py

tests/conftest.py builds packages the way the producer builds them (same manifest fields, same two-pass signing order, same JSON dump options, same ZIP layout), so drift on either side shows up as a failing test rather than as evidence that stops verifying in the field.


Publishing a trust anchor

Anchors in anchors/*.json are served from /trust-anchor.json. They are committed files, not runtime state, so the set of published keys is auditable through git history. To publish one, open a PR adding the tenant's exported anchor. Never add a private key: an anchor contains public keys only.


License

Source code is Apache-2.0.

The bundled fonts in web/fonts/ are SIL OFL 1.1, and the Behavry name and logo are trademarks that the code license does not grant. If you fork this and run your own verifier, replace the branding. See NOTICE.md.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

behavry_verify-0.2.1.tar.gz (191.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

behavry_verify-0.2.1-py3-none-any.whl (32.6 kB view details)

Uploaded Python 3

File details

Details for the file behavry_verify-0.2.1.tar.gz.

File metadata

  • Download URL: behavry_verify-0.2.1.tar.gz
  • Upload date:
  • Size: 191.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for behavry_verify-0.2.1.tar.gz
Algorithm Hash digest
SHA256 b7761eaea54fa9b69c46e62a1437915fe498193523ebc1ab8fd50d800eab5c1b
MD5 c96f545fb2df05bc96955ac2879c7474
BLAKE2b-256 1c9df02f1d254a70febd7de1d6eb3a79ee5189bd7655a5b457b5f33ffc1bc453

See more details on using hashes here.

Provenance

The following attestation bundles were made for behavry_verify-0.2.1.tar.gz:

Publisher: release.yml on Behavry-ai/behavry-verify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file behavry_verify-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: behavry_verify-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 32.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for behavry_verify-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 48b22e1253303c88d64f520846256a114c84769e4a570b07b19a0b585c43073f
MD5 59895954f177ca2347e06af0cdeb4c4e
BLAKE2b-256 d77458cdd0f3b81024b5362063a040ccafab5a5cfaac4a95b0fd77502c1e1e54

See more details on using hashes here.

Provenance

The following attestation bundles were made for behavry_verify-0.2.1-py3-none-any.whl:

Publisher: release.yml on Behavry-ai/behavry-verify

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page