API Reference
Python
pip install qhermes-kernel qhermes-a2a Two layers per package. qhermes.kernel and qhermes.a2a are the high-level APIs for application code, with named arguments, NamedTuple types, and automatic nonce management. qhermes_kernel and qhermes_a2a are the low-level extension modules with direct bindings to the Rust primitives.
qhermes.kernel
make_identity(master, deployment, context)
Derives an ML-DSA-65 identity from a 32-byte master seed via HKDF-SHA3-512. Different deployment or context values yield independent key material from the same master. Returns an IdentityIsland.
identity = make_identity(
master=seed_32,
deployment=b"prod",
context=b"orchestrator",
) derive_public_key(identity)
Returns the 1952-byte ML-DSA-65 public key for an identity.
root_pk = derive_public_key(identity) make_policy(resources, actions, *, permissions, not_before, not_after, hours_valid, minutes_valid)
Builds a Policy. The standard form accepts two lists expanded as a cartesian product; every action is granted on every resource. For asymmetric permissions, supply explicit (resource, verb) pairs via permissions. The two forms are mutually exclusive. All time parameters are keyword-only.
# Standard form — cartesian product
policy = make_policy(
resources=[b"/reports", b"/reports/archive"],
actions=[b"GET"],
hours_valid=1,
)
# Asymmetric form — different verbs on different resources
policy = make_policy(
permissions=[
(b"/source", b"GET"),
(b"/artifacts", b"PUT"),
],
hours_valid=2,
) issue_credential(identity, child_pk, policy, depth, role)
Issues a signed Credential. depth is the position in the chain: root issues at 1, each subsequent hop increments by one. role is "node" (may delegate further) or "leaf" (may not).
cred = issue_credential(
identity=root_identity,
child_pk=agent_pk,
policy=policy,
depth=1,
role="node",
) build_chain(chain)
Encodes a sequence of Credential objects to wire bytes.
wire = build_chain((root_cred, agent_cred)) verify_chain(root_pk, wire, now)
Verifies a wire-encoded chain against root_pk. Checks signatures, depth sequence, scope containment, and time bounds. Returns the credential count. Raises on any failure. now defaults to current Unix time.
decode_chain(wire)
Deserializes wire bytes into a tuple of Credential objects without verifying signatures. For inspection only. Call verify_chain before trusting any chain from an external source.
explain_credential(cred)
Returns a human-readable string summary of a credential.
parse_payload(cred)
Deserializes a credential payload into a dict with keys: child_pk, role, depth, perms, not_before, not_after.
credential_valid_at(cred, ts)
Returns True if the credential's time caveats permit ts. Fail-closed on invalid payload.
qhermes.a2a
derive_endpoint(dsa_seed)
Derives a KemEndpoint (ML-KEM-768 keypair) from a 32-byte seed. Passing the same seed used for a kernel identity binds both under the same root secret with domain separation.
endpoint = derive_endpoint(dsa_seed=seed_32)
ek = endpoint.encapsulation_key # publish in Agent Card or discovery metadata kem_offer(peer_ek, context)
Initiator side of the KEM handshake. Returns (offer_blob, Session). Embed offer_blob in message metadata via pack_metadata. context binds the session key to a domain; supply a stable task or session identifier.
kem_accept(endpoint, offer, context)
Responder side. Returns a Session. context must match the initiator's value exactly.
seal(session, plaintext, aad)
Encrypts with ChaCha20-Poly1305. Returns (blob, next_session). The previous session must not be reused. Pass next_session to subsequent calls.
open_blob(session, blob, aad)
Decrypts a blob produced by seal. Raises on authentication failure.
seal_auth(credentials)
Accepts a Sequence[Credential] in chain order (root first). Returns wire bytes for embedding in message metadata or any other field.
wire = seal_auth([root_cred, agent_cred]) verify_auth(root_pk, wire, now)
Verifies the full delegation chain. Returns the credential count on success. Raises on failure. now defaults to current Unix time.
n = verify_auth(root_pk=root_pk, wire=wire) sign_agent_card(master, deployment, context, card_bytes)
Signs canonical Agent Card bytes with ML-DSA-65. card_bytes must be JCS-canonicalized (RFC 8785): keys sorted, no extra whitespace, signature field omitted. Returns a SIG_SIZE-byte signature.
verify_agent_card(root_pk, card_bytes, sig)
Verifies an Agent Card signature. Raises ValueError on failure.
agent_card_security_extension(root_pk, endpoint)
Returns the QHermes extension dict for embedding in discovery metadata. Contains signingKey (ML-DSA-65 public key) and encapsulationKey (ML-KEM-768), both base64url-encoded.
ext = agent_card_security_extension(root_pk, endpoint)
card = {"name": "my-agent", "extensions": {"https://qhermes.dev/a2a/v1": ext}} extract_agent_card_ek(card)
Extracts (root_pk, encapsulation_key) from a card dict containing the QHermes extension. Raises KeyError if the extension is absent.
pack_metadata(auth_wire, offer_blob, extra) / unpack_metadata(metadata)
Helpers for embedding and extracting QHermes fields in A2A message.metadata dicts. Fields are base64url-encoded under x-qhermes-auth and x-qhermes-offer.
Types
Credential
issuer_pk: bytes: 1952-byte ML-DSA-65 public keysignature: bytes: 3309-byte ML-DSA-65 signaturepayload: bytes: signed payload bytes
Policy
scope_tlv: bytes: TLV-encoded permissionscaveats: bytes: time caveat records
KemEndpoint
encapsulation_key: bytes: publish in discovery metadatadk_seed: bytes: keep secret
Session
key: ChaCha20-Poly1305 session keycounter: int: monotonic nonce counter, starts at 0
Constants
PK_SIZE = 1952: ML-DSA-65 public key, bytesSIG_SIZE = 3309: ML-DSA-65 signature, bytesEK_SIZE = 1184: ML-KEM-768 encapsulation key, bytesKEM_OFFER_SIZE = 1088: KEM offer blob, bytesNONCE_SIZE = 12: ChaCha20-Poly1305 nonce, bytesTAG_SIZE = 16: ChaCha20-Poly1305 authentication tag, bytes
Next: Rust API reference