Skip to main content

A Python package for signing agent setups

Project description

agent-signing

Sign and verify AI agent setups. Detects when tools, agents, or their configurations change -- and optionally proves who signed them.

  • Order-independent -- reordering tools or agents in code does not invalidate the signature
  • Framework-agnostic -- auto-discovers metadata from LangChain/LangGraph, CrewAI, or plain dicts
  • Three signing modes -- HMAC (shared secret), Ed25519 (asymmetric keypair), JWT (identity tokens)
  • Zero framework dependencies -- detection is duck-typed; cryptography is the only runtime dependency

Installation

pip install agent-signing

Optional dependencies for running the demos:

pip install agent-signing[demo-langchain]  # LangChain / LangGraph demos
pip install agent-signing[demo-crewai]     # CrewAI demo

Quick start

from agent_signing import AgentSigner

signer = AgentSigner()
signer.add_tool({"name": "search", "description": "Search the web", "parameters": {"query": "str"}})
signer.add_tool({"name": "calculator", "description": "Evaluate math", "parameters": {"expr": "str"}})
signature = signer.sign()

# Later -- rebuild the same setup and verify
verifier = AgentSigner()
verifier.add_tool({"name": "calculator", "description": "Evaluate math", "parameters": {"expr": "str"}})
verifier.add_tool({"name": "search", "description": "Search the web", "parameters": {"query": "str"}})
result = verifier.verify(signature)
assert result.valid  # True -- order doesn't matter

Signing modes

HMAC (shared secret)

signer = AgentSigner(secret="my-secret-key")
signer.add_tool(my_tool)
signature = signer.sign()

verifier = AgentSigner(secret="my-secret-key")
verifier.add_tool(my_tool)
assert verifier.verify(signature)

Ed25519 (asymmetric key pair)

Proves who signed the agent setup. Sign with a private key, verify with the corresponding public key.

from agent_signing import AgentSigner, generate_keypair

private_key, public_key = generate_keypair()

# Sign
signer = AgentSigner(private_key=private_key)
signer.add_tool(my_tool)
signature = signer.sign()

# Verify (can be a different machine -- only needs the public key)
verifier = AgentSigner(public_key=public_key)
verifier.add_tool(my_tool)
result = verifier.verify(signature)
assert result.valid

JWT identity token

Attach an identity token from an OAuth/OIDC provider. The token's claims are decoded and returned on verification.

signer = AgentSigner(identity_token="eyJhbG...")
signer.add_tool(my_tool)
signature = signer.sign()

verifier = AgentSigner()
verifier.add_tool(my_tool)
result = verifier.verify(signature)
assert result.valid
print(result.identity)  # {"sub": "user@example.com", "iss": "https://accounts.google.com"}

Ed25519 and JWT can be combined for both cryptographic proof and identity context.

Signature files

Write signatures to disk with sign_to_file() and verify with verify_file(). The file includes the signing timestamp, public key, hash, and signature.

from agent_signing import AgentSigner, generate_keypair

private_key, public_key = generate_keypair()

signer = AgentSigner(private_key=private_key)
signer.add_tool(my_tool)
signer.sign_to_file("agent_signature.json")
# Creates:
# {
#   "signed_at": "2025-06-15T12:00:00+00:00",
#   "public_key": "abcd1234...",
#   "hash": "842f9705...",
#   "signature": "{\"hash\": ...}"
# }

# Later -- verify against the file
verifier = AgentSigner(public_key=public_key)
verifier.add_tool(my_tool)
result = verifier.verify_file("agent_signature.json")
assert result.valid

# Inspect the file contents
record = AgentSigner.load_signature_file("agent_signature.json")
print(record["signed_at"], record["public_key"])

Framework support

LangChain / LangGraph

add_tool() accepts any LangChain BaseTool (including @tool-decorated functions) and auto-extracts name, description, and args (parameter schema).

add_agent() accepts a LangGraph CompiledStateGraph (from create_react_agent) and auto-discovers all tools bound to the agent.

from langchain_core.tools import tool
from langchain.agents import create_agent
from agent_signing import AgentSigner, generate_keypair

@tool
def search(query: str) -> str:
    """Search the web."""
    return "results"

agent = create_agent(llm, [search])

private_key, public_key = generate_keypair()
signer = AgentSigner(private_key=private_key)
signer.add_agent(agent)  # auto-discovers all tools bound to the agent
signer.sign_to_file("agent_signature.json")

CrewAI

add_tool() accepts CrewAI BaseTool objects and extracts name, description, and args_schema.

add_agent() accepts CrewAI Agent objects and extracts role, goal, backstory, llm, and tools.

from crewai import Agent
from crewai.tools import tool
from agent_signing import AgentSigner, generate_keypair

@tool("search")
def search(query: str) -> str:
    """Search for information."""
    return "results"

researcher = Agent(
    role="Researcher",
    goal="Find accurate information",
    backstory="You are a skilled researcher.",
    tools=[search],
)

private_key, public_key = generate_keypair()
signer = AgentSigner(private_key=private_key)
signer.add_tool(search)
signer.add_agent(researcher)
signer.sign_to_file("agent_signature.json")

Plain dicts

For any other framework, pass plain dicts:

signer.add_tool({"name": "search", "description": "Search the web", "parameters": {"query": "str"}})
signer.add_agent({"name": "researcher", "role": "Researcher", "goal": "Find info"})

What gets signed

The signature covers the semantic definition of the agent setup, not the source code. Specifically:

Component Fields extracted
LangChain tool name, description, args (JSON schema)
CrewAI tool name, description, args_schema (JSON schema)
LangGraph agent tools discovered via nodes["tools"]
CrewAI agent role, goal, backstory, llm, tools
Dict all keys passed

The signature changes when any of these fields change. It does not change when:

  • Tools or agents are reordered in code
  • Unrelated code around the agent setup changes
  • Runtime state (e.g., conversation history) changes

API reference

AgentSigner(secret=None, private_key=None, public_key=None, identity_token=None)

Parameter Type Description
secret str | None HMAC shared secret
private_key bytes | None Ed25519 private key (32 bytes) for signing
public_key bytes | None Ed25519 public key (32 bytes) for verification
identity_token str | None JWT string to attach to the signature

Methods

Method Description
add_tool(tool) Register a tool (framework object or dict)
add_agent(agent) Register an agent (framework object or dict)
sign() -> str Compute and return the signature
sign_to_file(path) -> str Sign and write a JSON signature file (timestamp, public key, hash, signature)
verify(signature) -> VerificationResult Verify against a previous signature
verify_file(path) -> VerificationResult Verify against a signature file
load_signature_file(path) -> dict (static) Read and return a signature file's contents

VerificationResult

Field Type Description
valid bool Whether verification passed
reason str Human-readable explanation
identity dict | None Decoded JWT claims (when JWT was used)

Supports bool() -- use if result: directly.

generate_keypair() -> tuple[bytes, bytes]

Returns (private_key, public_key) as raw 32-byte Ed25519 keys.

License

Apache 2.0

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

agent_signing-0.1.2.tar.gz (13.6 kB view details)

Uploaded Source

Built Distribution

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

agent_signing-0.1.2-py3-none-any.whl (12.1 kB view details)

Uploaded Python 3

File details

Details for the file agent_signing-0.1.2.tar.gz.

File metadata

  • Download URL: agent_signing-0.1.2.tar.gz
  • Upload date:
  • Size: 13.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.10 {"installer":{"name":"uv","version":"0.9.10"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for agent_signing-0.1.2.tar.gz
Algorithm Hash digest
SHA256 b5238420b9c82b31852ec860c0fadd258769b32a1376d2b62dc01b61f0a22b92
MD5 2c53b93c33e5c3dd83eb1cf25663c629
BLAKE2b-256 58cac7f3118ff4003e4016d5ccdc72fd862599643c85f3ea22411813c824f836

See more details on using hashes here.

File details

Details for the file agent_signing-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: agent_signing-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 12.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.10 {"installer":{"name":"uv","version":"0.9.10"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for agent_signing-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 1afc93776d9264cca2fae3112f6848d1bf80ed00c8d39868158ba20543f99d88
MD5 3a9689edca9103d460a1986205f3f325
BLAKE2b-256 628b0d2c77b7f8145bdf14788d786920c698cf76f0477ed1ab99cad55610cac1

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