Skip to main content

AI agent governance - audit trails, policy enforcement, compliance

Project description

asqav

Python SDK for asqav.com, the evidence layer for AI agents. Sign every agent action with ML-DSA-65 (FIPS 204), enforce policies before execution, and produce regulator-ready audit trails. All cryptography runs server-side; the package has zero native dependencies.

Install

pip install asqav

Quick start

import asqav

asqav.init(api_key="sk_...")
agent = asqav.Agent.create("my-agent")

sig = agent.sign(
    "payment.wire_transfer",
    {"amount_eur": 850000, "beneficiary_iban": "DE89370400440532013000"},
    receipt_type="protectmcp:decision",
    risk_class="high",
    issuer_id="legal:Acme GmbH",
    iteration_id="task-2026-Q2-4821",
)

print(sig.compliance_mode)        # True (default; pass compliance_mode=False to opt out)
print(sig.action_ref)             # "sha256:..." over the JCS-canonical action
print(sig.previous_receipt_hash)  # 64 hex; "0"*64 on the first record per agent
print(sig.verification_url)

Each signed action lands on a Compliance Receipt under IETF Internet-Draft draft-marques-asqav-compliance-receipts by default: ML-DSA-65 (FIPS 204) signature, chain hash, retained policy_digest, fail-closed anchoring, and a public verification URL. Pass compliance_mode=False if you want a non-Compliance receipt.

CLI

The package ships an asqav CLI mirroring the Python API. Set ASQAV_API_KEY and run:

asqav verify <signature_id> [--output json]   # IETF axes when present
asqav sign --agent-id ID --action-type T --action-json action.json \
           --compliance-mode --receipt-type protectmcp:decision \
           --risk-class high --issuer-id legal:Acme
asqav agents list / create / revoke
asqav sessions list / end <session_id>
asqav replay <agent_id> <session_id>
asqav replay-verify <agent_id> <session_id> [--strict]     # IETF chain
asqav preflight <agent_id> <action_type>
asqav budget check / record
asqav approve <session_id> <entity_id>
asqav compliance frameworks / export
asqav audit-pack export --start ISO --end ISO --output-file bundle.json
asqav audit-pack policy <sha256:hex>
asqav payloads erase <signature_id>                        # P4 right-to-erasure
asqav org set-compliance-strict <org_id> --enable|--disable
asqav keys generate --algorithm ed25519|es256 [--out priv.pem]
asqav migrate run v3-20|v3-21|v3-22                        # X-Maintenance-Key required
asqav policies / webhooks list / create / delete

Every command listed here works on the free tier. The Asqav cloud is the source of truth for what your key may do.

Data handling modes

The SDK auto-detects whether you're pointing at the Asqav cloud or a self-hosted deployment and selects the safer default for each:

  • Cloud (*.asqav.com): hash-only by default. The SDK builds a fingerprint of your action context, computes a SHA-256 hash locally, and sends only the hash plus a small metadata bag of action_type, agent_id, session_id, model_name, and tool_name. Raw prompts and tool arguments stay on your side.
  • Self-hosted: full-payload by default. The server can run policy checks, PII redaction, and richer audit. Recommended when you control the deployment.

Override anytime:

asqav.init(api_key="...", base_url="https://api.asqav.com", mode="hash-only")

The fingerprint format is sorted JSON with no whitespace per JCS, hashed with SHA-256. See docs/fingerprint-spec.md and conformance/vectors.json for the spec and cross-language test vectors.

Compliance receipts: the IETF profile

Compliance Receipts are the SDK default. Each agent.sign(...) call produces a receipt that conforms to draft-marques-asqav-compliance-receipts: ML-DSA-65 signature, JCS canonicalization, retained policy_digest, hash-chained previous_receipt_hash, OpenTimestamps anchoring. Opt out with compliance_mode=False if you want the older shape.

The envelope extensions most callers reach for:

  • receipt_type - protectmcp:decision, protectmcp:restraint, protectmcp:lifecycle, protectmcp:lifecycle:configuration_change, protectmcp:acknowledgment, protectmcp:observation, or protectmcp:observation:result_bound, which is an observation receipt that binds tool output via result_digest.
  • risk_class - controlled vocabulary: low | medium | high | unknown.
  • iteration_id - logical task id, distinct from session.
  • sandbox_state - enabled | disabled | unavailable for high-risk gating.
  • incident_class - DORA / NYDFS / CIRCIA token, or an array of tokens.
  • issuer_id - LEI per ISO 17442, EIN, CIK, or a W3C DID for non-LEI deployers.

Shadow AI capture with passive_telemetry

Two receipt_type values cover the gating axis: protectmcp:decision records that a policy ran and gated the action; protectmcp:observation records that a passive monitor saw the event without gating it. Pick observation when the producer never had the option to block, such as a SIEM forwarder, a browser extension in observe-only mode, or a NetFlow-style proxy with no enforcement hook.

Set capture_topology='passive_telemetry' to declare the producer is observing after the fact. The SDK client-side check pre-flights the Asqav cloud's full rule 8 gate: a capture_topology='passive_telemetry' receipt MUST use receipt_type='protectmcp:observation'. Any other receipt_type paired with passive_telemetry, namely :decision, :restraint, :lifecycle, :lifecycle:configuration_change, or :acknowledgment, raises ValueError with the verbatim false_attestation_guard: capture_topology=passive_telemetry receipts must use receipt_type=protectmcp:observation, not :<offending> (rule 8) message before the HTTP roundtrip. The guard lives as false_attestation_guard in python/src/asqav/client.py.

sig = agent.sign(
    "mcp:tool_call",
    {"server": "filesystem", "tool": "read"},
    receipt_type="protectmcp:observation",
    capture_topology="passive_telemetry",
    issuer_id="legal:Acme GmbH",
)

capture_topology is stamped on the audit-pack manifest entry but never on the signed payload. The other accepted topologies are in_process_sdk, network_proxy, browser_extension, ebpf_observer, and mcp_proxy; only passive_telemetry triggers the false-attestation guard. The full topology semantics live in the cloud's docs/capture-topology.md, and the wire vocabulary is published live at https://api.asqav.com/.well-known/governance.json for discovery.

Configuration change receipts, rule 9

A receipt_type='protectmcp:lifecycle:configuration_change' receipt declares the agent's runtime configuration was mutated. The SDK pre-flights the Asqav cloud's rule 9 cross-field gate for NSA CSI U/OO/6030316-26 alignment: the receipt MUST carry config_manifest_digest. Omitting it raises ValueError with the verbatim false_attestation_guard: receipt_type=protectmcp:lifecycle:configuration_change requires config_manifest_digest (rule 9) message before the HTTP roundtrip.

sig = agent.sign(
    "mcp:config_update",
    {"server": "filesystem", "delta": "tool_added"},
    receipt_type="protectmcp:lifecycle:configuration_change",
    policy_decision="none",
    config_manifest_digest="sha256:<hex of manifest>",
    cve_inventory_digest="sha256:<hex of cve snapshot>",
)

Expiry precedence, rule 10

The legacy valid_seconds, where the server computes valid_until = signed_at + valid_seconds, and the caller-supplied horizon expires_at are mutually exclusive. Pass exactly one of the two: valid_seconds=3600 for "expire one hour after signing", or expires_at="2026-06-01T00:00:00Z" for an explicit horizon. Passing both raises ValueError with the verbatim expiry_collision_guard: pass either valid_seconds or expires_at, not both (rule 10) message before the HTTP roundtrip. Passing neither falls back to the server-side default of valid_seconds=86400.

Digest format, rule 11

Every caller-supplied self-describing digest field, namely config_manifest_digest, cve_inventory_digest, executable_hash, and sbom_digest, MUST match the regex ^sha256:[a-f0-9]{64}$. tool_fingerprint is the exception: it uses the cloud wire form of 32 bare lowercase hex chars, SHA-256[:32], matching ^[0-9a-f]{32}$ with no sha256: prefix; anything else raises ValueError with the verbatim tool_fingerprint_not_32_hex_chars: must be 32 lowercase hex chars (SHA-256[:32]). guard. The two URL pointer fields slsa_provenance_pointer and supply_chain_pointer MUST start with http:// or https://. Anything else raises ValueError with a verbatim guard message before the HTTP roundtrip. To avoid wire drift, use the SDK's deterministic helpers, each byte-deterministic under JCS:

from asqav.client import (
    _compute_tool_fingerprint,
    _compute_config_manifest_digest,
    _compute_cve_inventory_digest,
)

fp = _compute_tool_fingerprint("search", {"args": {"q": "string"}})
cfg = _compute_config_manifest_digest({"server": "filesystem", "tools": ["read"]})
cve = _compute_cve_inventory_digest([{"id": "CVE-2026-0001", "severity": "high"}])

NSA-aligned receipt fields

Six wire fields on agent.sign(...) carry the NSA CSI U/OO/6030316-26 alignment for MCP server lifecycle and tool output binding:

The protectmcp:observation:result_bound receipt_type variant carries result_digest and lets observation receipts bind to a specific tool result without claiming the policy gated the call.

Build-provenance 4-tuple

Four optional wire fields bind build-side provenance into the signed receipt:

  • executable_hash - sha256:<hex> of the executable that invoked the action.
  • sbom_digest - sha256:<hex> of the canonical CycloneDX or SPDX SBOM document.
  • slsa_provenance_pointer - https URL to the SLSA attestation envelope.
  • supply_chain_pointer - https URL to the in-toto, Sigstore, or Rekor entry.
sig = agent.sign(
    "build:provenance",
    {"image": "asqav/cloud:0.5.1"},
    compliance_mode=True,
    executable_hash="sha256:<hex of executable>",
    sbom_digest="sha256:<hex of SBOM>",
    slsa_provenance_pointer="https://attestations.example.com/slsa/build-1.intoto.jsonl",
    supply_chain_pointer="https://rekor.sigstore.dev/api/v1/log/entries/abc",
)

See https://www.asqav.com/docs/executable-hash-and-sbom-provenance.

Optional threat-framework mappings

Seven optional wire fields let a caller pin the receipt to industry threat-and-control taxonomies. Each list is caller-supplied and Asqav-preserved verbatim; the cloud sets framework_mappings_self_declared=true on the receipt whenever any of the six list fields is populated, so verifiers can tell self-declared classifications apart from cloud-verified ones.

  • mitre_techniques - list of MITRE ATT&CK technique ids, for example ["T1059", "T1078"].
  • mitre_atlas - list of MITRE ATLAS ids for AI-system threats, for example ["AML.T0051"].
  • owasp_llm_top10 - list of OWASP Top 10 for LLM ids, for example ["LLM01", "LLM02"].
  • nist_ai_rmf - list of NIST AI RMF function ids, for example ["GOVERN-1.1", "MEASURE-2.7"].
  • iso_42001 - list of ISO/IEC 42001 control ids, for example ["A.6.2.6"].
  • eu_ai_act_articles - list of EU AI Act article ids, for example ["Article-12", "Article-15"].
  • rfc3161_timestamp - caller-supplied base64-encoded RFC 3161 TimeStampResp in DER.
sig = agent.sign(
    "api:call",
    {"user": "..."},
    compliance_mode=True,
    mitre_techniques=["T1059", "T1078"],
    owasp_llm_top10=["LLM01"],
    nist_ai_rmf=["GOVERN-1.1", "MEASURE-2.7"],
    eu_ai_act_articles=["Article-12"],
)

Each list must be a non-empty list of strings, each entry up to 128 characters; empty lists, non-string entries, or oversize entries raise ValueError with the verbatim guard messages <field>_must_be_non_empty_list or <field>_entry_invalid and skip the HTTP roundtrip. The rfc3161_timestamp must be valid base64 or raises rfc3161_timestamp_not_base64.

See https://www.asqav.com/docs/threat-framework-mapping.

Optional witness policy

witness_policy lets a caller declare an N-of-M durable-anchoring quorum on the receipt. The receipt reaches witness_quorum_met only when required witnesses hold a real inclusion proof.

  • Shape: {"required": int, "witnesses": [<subset of "rfc3161", "opentimestamps">]}.
  • required must be in [1, len(witnesses)].
  • witnesses must be a non-empty subset of the two shipped witnesses: rfc3161 and opentimestamps.
  • rekor is rejected. It is not a shipped witness.
sig = agent.sign(
    "deploy:release",
    {"build": "..."},
    compliance_mode=True,
    witness_policy={"required": 1, "witnesses": ["rfc3161", "opentimestamps"]},
)

Bad input raises ValueError with a verbatim guard message before the HTTP roundtrip: witness_policy_unknown_witness for an entry like rekor, witness_policy_required_out_of_range, witness_policy_witnesses_must_be_non_empty_list, witness_policy_required_must_be_int, or witness_policy_duplicate_witness. Omit the field for today's behaviour.

Audit Pack export

The cloud signs a Compliance Audit Pack over a window of receipts. The SDK wraps the endpoint:

pack = asqav.fetch_audit_pack(start="2026-05-01T00:00Z", end="2026-06-01T00:00Z")
print(pack["bundle_digest"])              # sha256:<hex>
print(pack["bundle_signature"])           # base64 ML-DSA-65 sig over the bundle
print(pack["regime_mapping"])             # {regime_token: [record_id, ...]}
print(pack["algorithm_registry_version"]) # registry version pinned at issuance

asqav.export_bundle(signatures, framework="dora") is the offline alternative for air-gapped flows: it computes a Merkle root over an in-memory list of receipts without calling the cloud. Use fetch_audit_pack whenever the cloud is reachable, since only the cloud signature gives the auditor a tamper-evident manifest.

Local-side sanity checks, covering presence of REQUIRED fields, namespace, the 300s skew bound, and predecessor rederivation, are available as asqav.verify_compliance_receipt(envelope, predecessor_envelope=...). The cloud is the authoritative verifier; this helper is a convenience.

Algorithm agility is exposed via asqav.SUPPORTED_ALGORITHMS. Pass algorithm="ed25519" or "es256" to Agent.create(...) for non-post-quantum identities, or asqav.generate_local_keypair("ed25519") for offline scenarios.

Integrations

The SDK ships native callbacks and adapters for common agent frameworks under asqav.extras.*:

  • LangChain - from asqav.extras.langchain import AsqavCallbackHandler, pass to a chain's callbacks=[handler]. Signs each chain, tool, and LLM lifecycle event.
  • CrewAI - from asqav.extras.crewai import AsqavCrewHook, AsqavGuardrailProvider, attach the hook to the Crew so every task and agent step signs through Asqav.
  • LiteLLM - from asqav.extras.litellm import AsqavGuardrail, register on litellm.callbacks. Signs every completion across providers.
  • OpenAI Agents SDK - from asqav.extras.openai_agents import AsqavGuardrail, AsqavTracingProcessor, attaches to the runner and signs tool calls plus tracer spans.
  • LlamaIndex, Haystack, smolagents, DSPy, Letta, Strands, Instructor - same pattern via asqav.extras.<framework>; each exposes one AsqavAdapter subclass.
  • pytest - run pytest --asqav --asqav-agent=test-runner with ASQAV_API_KEY set; each test result signs and a Merkle-rooted compliance bundle emits on session finish.

Cookbooks for FastAPI middleware, AutoGen, and reasoning-trace capture live under python/examples/. See https://asqav.com/docs/integrations for the per-framework guide.

Errors

All raised exceptions extend AsqavError. AuthenticationError for 401, RateLimitError for 429, and APIError carrying status_code are exported for fine-grained handling:

from asqav import AsqavError, AuthenticationError, RateLimitError, APIError

try:
    agent.sign("api:call", {"model": "gpt-4"})
except AuthenticationError:
    # rotate ASQAV_API_KEY
    raise
except RateLimitError as exc:
    # exc.retry_after holds the cooldown in seconds
    raise
except APIError as exc:
    # exc.status_code holds the HTTP status
    raise

ValueError is raised before the HTTP roundtrip for the verbatim rule 8 / 9 / 10 / 11 guards listed above. Catch ValueError separately so guard violations surface as developer errors, not API errors.

Requirements

Python 3.10 or newer. Uses httpx for the API client. Zero native dependencies; ML-DSA cryptography runs server-side.

Standards

Asqav's compliance receipts are profiled in IETF Internet-Draft draft-marques-asqav-compliance-receipts, an Independent Submission that profiles draft-farley-acta-signed-receipts for EU AI Act Articles 12 and 26, and DORA Article 17 bindings. The SDK aligns with NIST FIPS 204 (ML-DSA), JCS canonicalization, and the NSA Cybersecurity Information Sheet U/OO/6030316-26 on MCP server lifecycle telemetry.

Roadmap

What ships on Asqav today. Each item is available on main:

  • Hash-only mode for cloud - default for *.asqav.com.
  • Self-hosted signer with split-trust - compose file in the Asqav backend repo.
  • Bring-your-own KMS for AWS KMS or GCP KMS - Enterprise tier.
  • Customer-owned storage - self-hosted; relay payload allowlist enforced in code.
  • SCITT / COSE_Sign1 receipt export - public GET /api/v1/signatures/{id}/cose returns application/cose.
  • Air-gapped / on-prem mode - offline license + zero-egress; see the backend repo docs/airgapped-mode.md.

See https://asqav.com/docs for the live feature set.

Documentation

License

Apache-2.0. Get an API key at asqav.com.

Project details


Download files

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

Source Distribution

asqav-0.5.12.tar.gz (140.2 kB view details)

Uploaded Source

Built Distribution

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

asqav-0.5.12-py3-none-any.whl (169.1 kB view details)

Uploaded Python 3

File details

Details for the file asqav-0.5.12.tar.gz.

File metadata

  • Download URL: asqav-0.5.12.tar.gz
  • Upload date:
  • Size: 140.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for asqav-0.5.12.tar.gz
Algorithm Hash digest
SHA256 027c55f9338847965329f1f62ed31c8baa3f4e2652b1fdc0b7e7c097e678eba0
MD5 29b76f1d3219f01ffbf2f31156f5a836
BLAKE2b-256 7b54f5452f1b9022b2c5957c5342b22d7130d7f34c1d0e5a13ccd5f642128519

See more details on using hashes here.

File details

Details for the file asqav-0.5.12-py3-none-any.whl.

File metadata

  • Download URL: asqav-0.5.12-py3-none-any.whl
  • Upload date:
  • Size: 169.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for asqav-0.5.12-py3-none-any.whl
Algorithm Hash digest
SHA256 7b21d09870a5c5c632285fc0afdfa10ee854fbc2b53b74fd04c5bb4c4efb6107
MD5 0ef8bb440d0b6d0802d185c2c0c08f48
BLAKE2b-256 10f1161f605d63d839e02cc91f6002d60dd3964a5c125db55cef7e47d5b3e9a0

See more details on using hashes here.

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