JACS Python Library
Cryptographic identity, signing, and verification for AI agents from Python.
Registry status (observed 2026-07-11): PyPI serves
jacs==0.11.3; this branch is source0.13.0. Pin the package when exact cross-language contracts matter.
pip install jacs
Prebuilt native bindings are distributed via maturin. A normal install does not require compiling Rust.
First use of the jacs command
Installing the Python library does not download a CLI. The first invocation of
the jacs command downloads the matching cli/vX.Y.Z GitHub Release into an
exact-version and platform-specific user cache. It fetches sha256sums.txt
first (with a validated per-asset checksum fallback), verifies SHA-256, rejects
unsafe archive paths, and then executes the cached binary. Linux musl is not a
published CLI target, so musl systems fail clearly instead of downloading the
glibc build. Unsupported platforms or failed verification exit nonzero with the
fallback command cargo install jacs-cli. In an active virtual environment the
Python shim may remain first on PATH; invoke Cargo's installed binary directly
as ~/.cargo/bin/jacs (or .cargo\\bin\\jacs.exe under your Windows user
profile). If CARGO_HOME is set, use its bin/jacs executable instead.
Importing or using the Python library performs no CLI download.
Full documentation | Quick Start
Quick start
import jacs.simple as jacs
info = jacs.quickstart(name="my-agent", domain="my-agent.example.com")
signed = jacs.sign_message({"action": "approve", "amount": 100})
result = jacs.verify(signed.raw)
print(f"Valid: {result.valid}, Signer: {result.signer_id}")
quickstart() creates a persistent agent with keys on disk. If jacs.config.json exists, it loads it; otherwise it creates a new agent.
Core operations
| Operation | Description |
|---|---|
quickstart(name, domain) |
Create or load a persistent agent |
load() |
Load an existing agent from config |
sign_message() |
Sign JSON-serializable data |
sign_file() |
Sign a file with optional embedding |
verify() |
Verify a signed document |
verify_standalone() |
Verify without loading an agent |
create_agreement_v2() |
Create a standalone Agreement v2 document |
sign_agreement_v2() |
Sign as signer, witness, or notary |
verify_agreement_v2() |
Verify Agreement v2 hash, policy, transcript, and status |
export_agent() |
Export agent JSON for sharing |
audit() |
Run a security audit |
Public human-approved document verification
The wheel build profile enables human-approval-vendored: the existing native
WebAuthn verifier with OpenSSL compiled into the extension, without a separate
OpenSSL installation. maturin develop uses this same profile. This configures
new builds; it does not claim that an older published wheel has the method.
Release gates check the installed artifact and reject external OpenSSL linkage.
Browser WASM does not provide this verifier.
Rust defaults remain unchanged. A minimal custom extension can omit the feature
using cargo build -p jacspy --features extension-module,a2a,agreements,attestation;
it does not expose this method. human-approval alone remains available for
custom builds intentionally using a system OpenSSL installation.
from jacs import SimpleAgent
report = SimpleAgent.verify_human_approved_document(
bundle_json, expected_json, authority_json, provenance_json
)
This static method needs no agent, signing key, configuration, or network
lookup. All four arguments are JSON strings. Read stored public evidence into
bundle_json; select the expected human/action/credential and the two public
key pins from your application's trusted state, not from the bundle. The
complete report is a dict. Success confirms retained proof and document
integrity, not permission to execute now: current remains
not_evaluated. Live authorization, revocation and one-use checks stay with
the relying application. Feature-enabled tests require
JACS_TEST_HUMAN_APPROVAL=1 pytest tests/test_human_approved_document.py.
Request authentication and signed events
The instance-based SimpleAgent exposes the transport protocol helpers. Build
the header from the exact request values that will be sent:
import json
from jacs import SimpleAgent
agent, _ = SimpleAgent.ephemeral(algorithm="ed25519")
body = '{"action":"approve"}'
authorization = agent.build_request_auth_header(
"POST",
"https://api.example.com/v1/jobs?mode=strict",
body,
"jobs-api",
)
assert authorization.startswith("JACS v2.")
envelope = agent.sign_response(json.dumps({"decision": "allow"}))
signer_id = json.loads(envelope)["jacsSignature"]["agentID"]
server_keys = json.dumps({signer_id: agent.get_public_key_pem()})
verified = json.loads(agent.unwrap_signed_event(envelope, server_keys))
assert verified["verified"] is True
assert verified["data"]["decision"] == "allow"
build_request_auth_header() accepts bytes for arbitrary request bodies or str
(encoded as UTF-8), and binds the exact bytes with method, absolute URL
including query, audience, signer/key, issue time, and nonce. Do not serialize
the body again after building the header. sign_response() emits a 2.0.0
envelope whose jacs-response-v2 signature covers both payload and metadata.
unwrap_signed_event() is fail-closed: plain events, legacy payload-only
envelopes, unknown signers, and mutations raise instead of returning data.
Multi-replica consumers should release signed-event payloads through their own shared atomic replay store:
from jacs import unwrap_signed_event_with_replay_store
# store.scope must be "shared". consume(key, ttl_seconds) returns True only
# for the first atomic consumer and False for a duplicate.
verified = await unwrap_signed_event_with_replay_store(
agent,
envelope,
server_keys,
store,
)
Native verification and freshness checks run before the store is called and
return no payload. The helper validates the exact-input digest, consumes the
native replay key once, rechecks expiry, and only then returns data with its
authenticated signer, timestamp, algorithm, and document ID. Store failures,
timeouts, invalid results, process-local stores, duplicates, and expiry all fail
closed with a stable ReplayError.code.
The server_keys object is a trust decision, not discovery: each key must be
pinned or resolved under the application's configured policy. Successful
verification proves possession of that key; it does not independently prove a
person, organization, domain, or other real-world identity.
The old no-argument build_auth_header() remains available for source
compatibility and emits a WARN because it does not bind the request. Migrate
clients and servers together to JACS v2; strict deployments can reject the
legacy method with JACS_REJECT_UNBOUND_AUTH_HEADER=true.
Text and image provenance
Python exposes the same inline text and image signing surface as the CLI:
import jacs.simple as jacs
from jacs import MissingSignatureError
jacs.load("./jacs.config.json")
# Markdown/text: append and verify an inline signature block.
jacs.sign_text("README.md")
text = jacs.verify_text("README.md")
print(text.status) # 'signed' | 'missing_signature' | 'malformed'
try:
jacs.verify_text("README.md", strict=True)
except MissingSignatureError:
print("not signed")
jacs.verify_text("README.md", key_dir="./trusted-keys/")
# Images: embed and verify a signature in PNG, JPEG, or WebP metadata.
jacs.sign_image("photo.png", out="signed.png")
image = jacs.verify_image("signed.png")
print(image.status) # 'valid'
payload = jacs.extract_media_signature("signed.png")
The same methods are available on the instance-based JacsClient for multi-agent processes. These signatures prove that an agent signed specific canonical bytes at its claimed time; they do not prove first creation or legal ownership.
Verify without an agent
result = jacs.verify_standalone(signed_json, key_directory="./keys")
Cross-language interop is tested on every commit. Documents signed in Rust or Node.js verify in Python, and Python-signed documents verify in the other bindings.
Agreement v2
Use Agreement v2 for new multi-agent consent workflows:
from jacs import SimpleAgent
agent, info = SimpleAgent.ephemeral(algorithm="ed25519")
agent_id = info["agent_id"]
agreement = agent.create_agreement_v2({
"title": "Refund approval",
"description": "Approval for a bounded refund.",
"terms": "Refund up to $25 for order 123.",
"status": "proposed",
"parties": [{"agentId": agent_id, "agentType": "ai", "role": "signer"}],
"signaturePolicy": {"partyQuorum": "all", "witnessRequired": 0, "notaryRequired": 0},
"controllers": [agent_id],
})
signed = agent.sign_agreement_v2(agreement, "signer")
assert agent.verify_agreement_v2(signed)["valid"]
The same operations are available as module-level functions once an agent is loaded (quickstart() / create() / load()), matching Node's @hai.ai/jacs/simple surface:
import jacs.simple as jacs
jacs.quickstart(name="my-agent", domain="agent.example.com")
agent_id = jacs.get_agent_info().agent_id
agreement = jacs.create_agreement_v2({
"title": "Refund approval",
"description": "Approval for a bounded refund.",
"terms": "Refund up to $25 for order 123.",
"status": "proposed",
"parties": [{"agentId": agent_id, "agentType": "ai", "role": "signer"}],
"signaturePolicy": {"partyQuorum": "all", "witnessRequired": 0, "notaryRequired": 0},
"controllers": [agent_id],
})
signed = jacs.sign_agreement_v2(agreement, "signer")
assert jacs.verify_agreement_v2(signed)["valid"]
Verifying another agent's agreement signature requires that agent's public key, so distinct agents must share a data_directory or exchange public keys; ephemeral agents verify only their own signatures.
The older create_agreement() / sign_agreement() / check_agreement() methods remain for simple jacsAgreement sidecars on existing documents.
Framework adapters
pip install jacs[langchain] # LangChain / LangGraph
pip install jacs[fastapi] # FastAPI / Starlette
pip install jacs[anthropic] # Anthropic / Claude SDK
pip install jacs[a2a] # A2A protocol
pip install jacs[all] # Everything
jacs.a2a_server.jacs_a2a_routes() caches all six discovery documents together
and refreshes them on the first request at six days after the binding's signed
issuedAt. Concurrent FastAPI handlers share one locked refresh. Failed
refreshes retry at most once per minute; the old snapshot is served only within
its seven-day Strict lifetime and any earlier expiresAt. Unavailable discovery
returns a non-cacheable 503. HTTP freshness ends by the six-day renewal boundary
or earlier expiry. Still-valid snapshots awaiting renewal use no-store.
Finite expiry is never extended: renew authorization and
remount the router to serve a newly authorized binding after expiry.
Separate resource requests can straddle renewal even though publication is
atomic within the process. Clients must verify the card/JWKS/binding together
and refetch on a mismatch; these endpoints are not a transactional bundle.
Instance-based API
For multiple agents in one process:
from jacs.client import JacsClient
client = JacsClient.quickstart(name="my-agent", domain="example.com")
signed = client.sign_message({"action": "approve"})
See DEVELOPMENT.md for the full API reference, advanced usage, framework adapter examples, and testing utilities.
Links
Metadata
Release files for jacs 0.15.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| jacs-0.15.0.tar.gz | 1.7 MB | With provenance | Details for jacs-0.15.0.tar.gz |
Built distributions (wheels)
| File | Actions Reset | |||
|---|---|---|---|---|
| jacs-0.15.0-cp310-abi3-musllinux_1_2_x86_64.whl | CPython 3.10 | abi3 | Linux musl 1.2+ x86-64 | Details for jacs-0.15.0-cp310-abi3-musllinux_1_2_x86_64.whl |
| jacs-0.15.0-cp310-abi3-manylinux_2_28_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.28+ x86-64 | Details for jacs-0.15.0-cp310-abi3-manylinux_2_28_x86_64.whl |
| jacs-0.15.0-cp310-abi3-manylinux_2_28_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.28+ ARM64 | Details for jacs-0.15.0-cp310-abi3-manylinux_2_28_aarch64.whl |
| jacs-0.15.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details for jacs-0.15.0-cp310-abi3-macosx_11_0_arm64.whl |
| jacs-0.15.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details for jacs-0.15.0-cp310-abi3-macosx_10_12_x86_64.whl |
Total release size: 61.5 MB
Release files / jacs-0.15.0.tar.gz
| Download URL | jacs-0.15.0.tar.gz |
|---|---|
| Size | 1.7 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
56bfbf5d1e0b970718de7fa43e07a665ee90899ddbe731390e94ebe1b0be8e13
|
|
BLAKE2b-256 checksum How to use checksums |
f31d8cd06146f670c4eafda4fb42422334df46fb9d6d859418db29fb9166d94d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logRelease files / jacs-0.15.0-cp310-abi3-musllinux_1_2_x86_64.whl
| Download URL | jacs-0.15.0-cp310-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 11.3 MB |
| Tags | CPython 3.10 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
df4b4fb12c3321d04816926482051498904b942a9755fbf9a6f46574abe8736e
|
|
BLAKE2b-256 checksum How to use checksums |
e3979376de2aecca02051ed17607e3ca9f0ffe91d72c9ca45c0cae04631e9f59
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logRelease files / jacs-0.15.0-cp310-abi3-manylinux_2_28_x86_64.whl
| Download URL | jacs-0.15.0-cp310-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 12.6 MB |
| Tags | CPython 3.10 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
be44612351b6ef0ffe1a135f033bdb6f1befb7cdbf4e028756063433b731f15f
|
|
BLAKE2b-256 checksum How to use checksums |
117b42ffd181828e582445ea168f6ee6117aff61735c89c0076067118eb45057
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logRelease files / jacs-0.15.0-cp310-abi3-manylinux_2_28_aarch64.whl
| Download URL | jacs-0.15.0-cp310-abi3-manylinux_2_28_aarch64.whl |
|---|---|
| Size | 12.9 MB |
| Tags | CPython 3.10 Linux glibc 2.28+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
c39e31354c58dc342a2ef47bf4a0a0694cee2ff3dc171583b8a5895ca319850a
|
|
BLAKE2b-256 checksum How to use checksums |
e06b3f4c68e3c6e44de54b65e3c473f9f083e043bc66d9aa3064c76d522ace1a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logRelease files / jacs-0.15.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | jacs-0.15.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 11.4 MB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
4db57dfd8ec558e0f1068728020e7d04ffe32df2ee9986f21c64dbeb7cfa891d
|
|
BLAKE2b-256 checksum How to use checksums |
14f98f3ba1decc00124d05248c4e53d33ea3ce27fe24d5d3cb666481cf6c3c40
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logRelease files / jacs-0.15.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | jacs-0.15.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 11.6 MB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
3bd730951275a09de59adb130e54e72ab2dcb7f3242b603172d884a06e2bac48
|
|
BLAKE2b-256 checksum How to use checksums |
c185d1db5bf28ff8de76b06b38146409fc986fb733ab04a7b039cdf4fd2e5362
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logSecurity for jacs 0.15.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 6 files in this release have been uploaded with provenance metadata.
Attestation for jacs-0.15.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.