Skip to main content

JACS - JSON AI Communication Standard: Cryptographic signing and verification for AI agents.

Project description

JACS Python Library

Sign it. Prove it.

Cryptographic signatures for AI agent outputs -- so anyone can verify who said what and whether it was changed. No server. Three lines of code.

Which integration should I use? | Full documentation

# Using uv (recommended)
uv pip install jacs

# Or with pip
pip install jacs

The Python package ships prebuilt native bindings (via maturin) and does not compile Rust during pip install. Packaging/build metadata is defined in pyproject.toml. setup.py is intentionally not used.

To check dependencies for known vulnerabilities when using optional extras, run pip audit (or safety check).

Quick Start

Zero-config -- one call to start signing:

import jacs.simple as jacs

info = jacs.quickstart(name="my-agent", domain="my-agent.example.com")
print(info.config_path, info.public_key_path, info.private_key_path)
signed = jacs.sign_message({"action": "approve", "amount": 100})
result = jacs.verify(signed.raw)
print(f"Valid: {result.valid}, Signer: {result.signer_id}")

quickstart(name, domain, ...) creates a persistent agent with keys on disk. If ./jacs.config.json already exists, it loads it; otherwise it creates a new agent. Agent, keys, and config are saved to ./jacs_data, ./jacs_keys, and ./jacs.config.json. If JACS_PRIVATE_KEY_PASSWORD is not set, the native Rust layer auto-generates a secure password (set JACS_SAVE_PASSWORD_FILE=true to persist it at ./jacs_keys/.jacs_password). Returned AgentInfo includes config and key file paths. Pass algorithm="ring-Ed25519" or algorithm="RSA-PSS" to override the default (pq2025).

Signed your first document? Next: Verify it standalone | Add framework adapters | Multi-agent agreements | Full docs

Advanced: Loading an existing agent

If you already have an agent (e.g., created by a previous quickstart(name=..., domain=...) call), load it explicitly:

import jacs.simple as jacs

agent = jacs.load("./jacs.config.json")

# Sign a message (accepts dict, list, str, or any JSON-serializable data)
signed = jacs.sign_message({"action": "approve", "amount": 100})
print(f"Signed by: {signed.agent_id}")

# Verify it
result = jacs.verify(signed.raw)
print(f"Valid: {result.valid}")

# Sign a file
signed_file = jacs.sign_file("document.pdf", embed=True)

# Update agent metadata
agent_doc = json.loads(jacs.export_agent())
agent_doc["jacsAgentType"] = "updated-service"
updated = jacs.update_agent(agent_doc)

# Update a document
doc = json.loads(signed.raw)
doc["content"]["status"] = "approved"
updated_doc = jacs.update_document(signed.document_id, doc)

Headless / Embedded Loading Without Env Vars

For Linux services or embedded apps that fetch secrets from a vault or secret manager, prefer passing the password in memory to the low-level binding instead of storing it in JACS_PRIVATE_KEY_PASSWORD:

import json
from jacs import JacsAgent

secret = get_secret_from_manager()

agent = JacsAgent()
agent.set_private_key_password(secret)
info = json.loads(agent.load_with_info("/srv/my-project/jacs.config.json"))

jacs.simple.load() and JacsClient(...) are convenient higher-level entry points, but the low-level JacsAgent API is the explicit path for in-memory secret injection.

Core Operations

The simplified API provides these core operations:

Operation Description
quickstart(name, domain, ...) Create a persistent agent with keys on disk -- zero config, no manual setup
create() Create a new agent programmatically (non-interactive)
load() Load an existing agent from config
verify_self() Verify the loaded agent's integrity
update_agent() Update the agent document with new data
update_document() Update an existing document with new data
sign_message() Sign a text message or JSON data
sign_file() Sign a file with optional embedding
verify() Verify any signed document (JSON string)
verify_standalone() Verify without loading an agent (one-off)
verify_by_id() Verify a document by its storage ID (uuid:version)
get_dns_record() Get DNS TXT record line for the agent
get_well_known_json() Get well-known JSON for /.well-known/jacs-pubkey.json
export_agent() Export agent JSON for sharing
get_public_key() Get the agent's public key (e.g. for DNS)
reencrypt_key() Re-encrypt the private key with a new password
trust_agent() Add an agent to the local trust store
list_trusted_agents() List all trusted agent IDs
untrust_agent() Remove an agent from the trust store
is_trusted() Check if an agent is trusted
get_trusted_agent() Get a trusted agent's JSON document
audit() Run a read-only security audit (returns risks, health_checks, summary)

Programmatic Agent Creation

import jacs.simple as jacs

# Create an agent without interactive prompts
agent = jacs.create(
    name="my-agent",
    password="Str0ng-P@ssw0rd!",  # or set JACS_PRIVATE_KEY_PASSWORD env var
    algorithm="pq2025",            # default; also: "ring-Ed25519", "RSA-PSS"
    data_directory="./jacs_data",
    key_directory="./jacs_keys",
)
print(f"Created agent: {agent.agent_id}")

Standalone Verification (No Agent Required)

Verify a signed document without loading an agent. Useful for one-off verification, CI/CD pipelines, or services that only need to verify, not sign.

import jacs.simple as jacs

result = jacs.verify_standalone(
    signed_json,
    key_resolution="local",
    key_directory="./trusted-keys/"
)
if result.valid:
    print(f"Signed by: {result.signer_id}")

Documents signed by Rust or Node.js agents verify identically in Python -- cross-language interop is tested on every commit with Ed25519 and pq2025 (ML-DSA-87). See the full Verification Guide for CLI, DNS, and cross-language examples.

Verify by Document ID

# If you have a document ID instead of the full JSON
result = jacs.verify_by_id("550e8400-e29b-41d4-a716-446655440000:1")
print(f"Valid: {result.valid}")

Re-encrypt Private Key

jacs.reencrypt_key("old-password-123!", "new-Str0ng-P@ss!")

Password Requirements

Passwords must be at least 8 characters and include uppercase, lowercase, a digit, and a special character.

Post-Quantum Algorithm

Use pq2025 (ML-DSA-87, FIPS-204) for post-quantum signing.

Type Definitions

from jacs import AgentInfo, SignedDocument, VerificationResult

# All return types are dataclasses with clear fields
agent: AgentInfo = jacs.load()
signed: SignedDocument = jacs.sign_message({"data": "hello"})
result: VerificationResult = jacs.verify(signed.raw)

JacsClient (Instance-Based API)

When you need multiple agents in one process, or want to avoid global state, use JacsClient. Each instance wraps its own JacsAgent with independent keys and config.

from jacs.client import JacsClient

# Load from config
client = JacsClient("./jacs.config.json")
signed = client.sign_message({"action": "approve"})
result = client.verify(signed.raw_json)
print(f"Valid: {result.valid}, Agent: {client.agent_id}")

# Or zero-config quickstart (creates keys on disk)
client = JacsClient.quickstart(name="my-agent", domain="my-agent.example.com")

# Context manager for automatic cleanup
with JacsClient.quickstart(name="my-agent", domain="my-agent.example.com") as client:
    signed = client.sign_message("hello")

Multi-Agent Example

from jacs.client import JacsClient

alice = JacsClient.ephemeral()
bob = JacsClient.ephemeral()

signed = alice.sign_message({"from": "alice"})
result = bob.verify(signed.raw_json)
print(f"Alice: {alice.agent_id}")
print(f"Bob verifies Alice's message: {result.valid}")

Agreements with Timeout and Quorum

create_agreement accepts flat keyword arguments for advanced options:

from datetime import datetime, timedelta, timezone

agreement = client.create_agreement(
    document={"proposal": "Deploy model v2"},
    agent_ids=[alice.agent_id, bob.agent_id, mediator.agent_id],
    question="Do you approve?",
    quorum=2,                    # 2-of-3 signatures required
    timeout=(datetime.now(timezone.utc) + timedelta(hours=1)).isoformat(),
    required_algorithms=None,    # optional: restrict signing algorithms
    minimum_strength=None,       # optional: "classical" or "post-quantum"
)

signed = alice.sign_agreement(agreement)
status = alice.check_agreement(signed)
print(f"Complete: {status.complete}, Pending: {status.pending}")

See examples/multi_agent_agreement.py for a full 3-agent agreement demo with crypto proof chain.

JacsClient API Reference

Method Description
JacsClient(config_path) Load from config
JacsClient.quickstart(name, domain, ...) Zero-config persistent agent
JacsClient.ephemeral() In-memory agent (no disk, for tests)
sign_message(data) Sign JSON-serializable data
verify(document) Verify a signed document
verify_self() Verify agent integrity
verify_by_id(doc_id) Verify by storage ID
sign_file(path, embed) Sign a file
create_agreement(...) Create multi-party agreement
sign_agreement(doc) Co-sign an agreement
check_agreement(doc) Check agreement status
trust_agent(json) Add agent to trust store
list_trusted_agents() List trusted agent IDs
update_agent(data) Update and re-sign agent
update_document(id, data) Update and re-sign document
export_agent() Export agent JSON for sharing
audit() Run security audit
reset() Clear internal state

Framework Adapters

Auto-sign AI framework outputs with zero infrastructure. Install the extra for your framework:

pip install jacs[langchain]   # LangChain / LangGraph
pip install jacs[fastapi]     # FastAPI / Starlette
pip install jacs[crewai]      # CrewAI
pip install jacs[anthropic]   # Anthropic / Claude SDK
pip install jacs[all]         # Everything

LangChain -- sign every tool result via middleware:

from jacs.adapters.langchain import jacs_signing_middleware
agent = create_agent(model="openai:gpt-4o", tools=tools, middleware=[jacs_signing_middleware()])

FastAPI -- sign all JSON responses:

from jacs.adapters.fastapi import JacsMiddleware
app.add_middleware(JacsMiddleware)

For auth-style endpoints, enable replay protection:

from jacs.adapters.fastapi import JacsMiddleware

app.add_middleware(
    JacsMiddleware,
    auth_replay_protection=True,
    auth_max_age_seconds=30,
    auth_clock_skew_seconds=5,
)

CrewAI -- sign task outputs via guardrail:

from jacs.adapters.crewai import jacs_guardrail
task = Task(description="Analyze data", agent=my_agent, guardrail=jacs_guardrail())

Anthropic / Claude SDK -- sign tool return values:

from jacs.adapters.anthropic import signed_tool

@signed_tool()
def get_weather(location: str) -> str:
    return f"Weather in {location}: sunny"

See the Framework Adapters guide for full documentation, custom adapters, and strict/permissive mode details.

Testing

The jacs.testing module provides a pytest fixture that creates an ephemeral client with no disk I/O or env vars required:

from jacs.testing import jacs_agent

def test_sign_and_verify(jacs_agent):
    signed = jacs_agent.sign_message({"test": True})
    result = jacs_agent.verify(signed.raw_json)
    assert result.valid

def test_agent_has_unique_id(jacs_agent):
    assert jacs_agent.agent_id

The fixture automatically resets after each test.

MCP Integration

The canonical full JACS MCP server is the Rust jacs-mcp binary. Python keeps FastMCP-native middleware and a partial MCP compatibility adapter for embedding JACS behavior into Python servers.

For AI tool servers using the Model Context Protocol:

from fastmcp import FastMCP
import jacs.simple as jacs

mcp = FastMCP("My Server")
jacs.load("./jacs.config.json")

@mcp.tool()
def signed_hello(name: str) -> dict:
    signed = jacs.sign_message({"greeting": f"Hello, {name}!"})
    return {"response": signed.raw}

JacsAgent Class (Advanced)

For more control, use the JacsAgent class directly:

from jacs import JacsAgent

agent = JacsAgent()
agent.load("./jacs.config.json")

# Sign raw strings
signature = agent.sign_string("data to sign")

# Verify documents
is_valid = agent.verify_document(document_json)

# Create documents with schemas
doc = agent.create_document(json_string, schema=None)

A2A Protocol Support

Every JACS agent is an A2A agent -- zero additional configuration. JACS implements the Agent-to-Agent (A2A) protocol with cryptographic trust built in. For A2A security, JACS is an OAuth alternative for service-to-service agent trust (mTLS-like at the payload layer), not a replacement for OAuth/OIDC delegated user authorization.

Quick Start

from jacs.client import JacsClient

client = JacsClient.quickstart(name="my-agent", domain="my-agent.example.com")
card = client.export_agent_card("http://localhost:8080")
signed = client.sign_artifact({"action": "classify", "input": "hello"}, "task")

Using JACSA2AIntegration Directly

For full A2A lifecycle control (well-known documents, chain of custody, extension descriptors):

from jacs.client import JacsClient
from jacs.a2a import JACSA2AIntegration

client = JacsClient.quickstart(name="my-agent", domain="my-agent.example.com")
a2a = client.get_a2a(url="http://localhost:8080")

# Export an A2A Agent Card
card = a2a.export_agent_card(agent_data)

# Sign an artifact with provenance
signed = a2a.sign_artifact({"taskId": "t-1", "operation": "classify"}, "task")

# Verify a received artifact
result = a2a.verify_wrapped_artifact(signed)
assert result["valid"]

# Build chain of custody across agents
step2 = a2a.sign_artifact(
    {"step": 2, "data": "processed"}, "message",
    parent_signatures=[signed],
)

One-Liner Quickstart

from jacs.a2a import JACSA2AIntegration

a2a = JACSA2AIntegration.quickstart(url="http://localhost:8080")
a2a.serve(port=8080)  # Publishes /.well-known/agent-card.json

Trust Policies

JACS trust policies control how your agent handles foreign signatures:

Policy Behavior
open Accept all signatures without key resolution
verified Require key resolution before accepting (default)
strict Require the signer to be in your local trust store

See the A2A Guide for well-known documents, cross-organization discovery, and chain-of-custody examples.

Installation

# Basic installation
pip install jacs

# With framework adapters
pip install jacs[langchain]    # LangChain / LangGraph
pip install jacs[fastapi]      # FastAPI / Starlette
pip install jacs[crewai]       # CrewAI
pip install jacs[anthropic]    # Anthropic / Claude SDK
pip install jacs[all]          # All adapters + MCP + A2A

# With A2A support
pip install jacs[a2a]          # Discovery helpers (native runtime, no extra Python deps)
pip install jacs[a2a-server]   # A2A server with serve() (FastAPI + uvicorn)

# With MCP support
pip install jacs[mcp]

# Optional: jacs[langgraph] (LangGraph ToolNode), jacs[ws] (WebSockets). See pyproject.toml.

Examples

See the examples/ directory:

  • quickstart.py - Basic signing and verification
  • sign_file.py - File signing with embeddings
  • mcp_server.py - Authenticated MCP server
  • p2p_exchange.py - Peer-to-peer trust establishment
  • multi_agent_agreement.py - Three-agent agreement with quorum, timeout, and crypto proof chain

Development

Using uv (recommended):

# Quick start with Makefile
make setup   # Install all dependencies
make dev     # Build for development
make test    # Run all tests

# Or manually:
uv venv && source .venv/bin/activate
uv pip install maturin pytest httpx httpx-sse
uv run maturin develop
uv run python -m pytest tests/ -v

Available Make Commands

Command Description
make setup Install dev dependencies with uv
make dev Build Rust extension for development
make test Run all tests
make check-imports Verify core imports (also checks optional jacs.hai / HaiClient if installed)

Documentation

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

jacs-0.9.12.tar.gz (993.5 kB view details)

Uploaded Source

Built Distributions

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

jacs-0.9.12-cp310-abi3-musllinux_1_2_x86_64.whl (8.0 MB view details)

Uploaded CPython 3.10+musllinux: musl 1.2+ x86-64

jacs-0.9.12-cp310-abi3-manylinux_2_38_x86_64.whl (9.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.38+ x86-64

jacs-0.9.12-cp310-abi3-manylinux_2_38_aarch64.whl (9.0 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.38+ ARM64

jacs-0.9.12-cp310-abi3-macosx_11_0_arm64.whl (8.3 MB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

jacs-0.9.12-cp310-abi3-macosx_10_12_x86_64.whl (8.5 MB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

File details

Details for the file jacs-0.9.12.tar.gz.

File metadata

  • Download URL: jacs-0.9.12.tar.gz
  • Upload date:
  • Size: 993.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for jacs-0.9.12.tar.gz
Algorithm Hash digest
SHA256 dee93b3e3f19cdcde63845184abf5f5c804d7d53fc175ffc8e86e7614fe94e15
MD5 c4cd8e8c2163a077b782cfbb9b173281
BLAKE2b-256 91694e0254467380f803e5d99ec1f275a270bc5701ed402f8babeaad60e063a0

See more details on using hashes here.

Provenance

The following attestation bundles were made for jacs-0.9.12.tar.gz:

Publisher: release-pypi.yml on HumanAssisted/JACS

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

File details

Details for the file jacs-0.9.12-cp310-abi3-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for jacs-0.9.12-cp310-abi3-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 b8598bd76797307d4e79dac0ded4d85e2322e73723ce576d899c79afff8c8a6c
MD5 34dbb95ca96ffb33b1d8fd15145399be
BLAKE2b-256 f45562eba349c9ef1c13a2e6221bfc1ffd55390832dcdeca61fdd2b4778d0817

See more details on using hashes here.

Provenance

The following attestation bundles were made for jacs-0.9.12-cp310-abi3-musllinux_1_2_x86_64.whl:

Publisher: release-pypi.yml on HumanAssisted/JACS

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

File details

Details for the file jacs-0.9.12-cp310-abi3-manylinux_2_38_x86_64.whl.

File metadata

File hashes

Hashes for jacs-0.9.12-cp310-abi3-manylinux_2_38_x86_64.whl
Algorithm Hash digest
SHA256 a1d4473d8d6c7b7d6607824bde7a1c4ad81a09c0feb39d422efaa8fc537d459b
MD5 474d40d506abcb237359ea3c71c206f3
BLAKE2b-256 e385f5b4e90b9e423e675b0b2776c3b6269560aff97296eea760c2dd7c6ec8ea

See more details on using hashes here.

Provenance

The following attestation bundles were made for jacs-0.9.12-cp310-abi3-manylinux_2_38_x86_64.whl:

Publisher: release-pypi.yml on HumanAssisted/JACS

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

File details

Details for the file jacs-0.9.12-cp310-abi3-manylinux_2_38_aarch64.whl.

File metadata

File hashes

Hashes for jacs-0.9.12-cp310-abi3-manylinux_2_38_aarch64.whl
Algorithm Hash digest
SHA256 ea1c8944ebfc11b5729d5ad58870e7d3a68715dc7e81b8be191187cd99e213de
MD5 c7bd54a63ed484f4eeba85b7cc3daef1
BLAKE2b-256 0a88e97923b4f19e63aa41857f773dc93e357fff8173852c37ad814cd5445bc2

See more details on using hashes here.

Provenance

The following attestation bundles were made for jacs-0.9.12-cp310-abi3-manylinux_2_38_aarch64.whl:

Publisher: release-pypi.yml on HumanAssisted/JACS

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

File details

Details for the file jacs-0.9.12-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for jacs-0.9.12-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 dddf8c967b26e01ec538a17ab62c9dfd05220b95d759d51deee26aa60bdc4ea7
MD5 bbc22822bc3df918b16e898de2be16ae
BLAKE2b-256 4e179d188351473c7caf4d71f4a775c002ba24cde006429b29ca16d10c20e50a

See more details on using hashes here.

Provenance

The following attestation bundles were made for jacs-0.9.12-cp310-abi3-macosx_11_0_arm64.whl:

Publisher: release-pypi.yml on HumanAssisted/JACS

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

File details

Details for the file jacs-0.9.12-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for jacs-0.9.12-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 c07af2cea4c3ed8a40c724c94e7261a0f0eb0f585f884540b97f2e37172aaf24
MD5 09ddaadc3fc92f6f8be85828fb6c3d9a
BLAKE2b-256 b4713175096d7d621251889ba9f2125fca30a1b4c775016e22b3d1539e1f0b7a

See more details on using hashes here.

Provenance

The following attestation bundles were made for jacs-0.9.12-cp310-abi3-macosx_10_12_x86_64.whl:

Publisher: release-pypi.yml on HumanAssisted/JACS

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