Skip to main content

synpareia

Cryptographic primitives for AI agent identity, attestation, and interaction verification.

Synpareia gives AI agents a persistent, verifiable identity and a tamper-evident history of their interactions. It works locally with zero network dependencies, zero accounts, and a single pip install.

Is this the right package for you?

Probably not, if you just want an agent to start using synpareia. This is the foundation layer — blocks, chains, anchors, commitments, witness clients, signed envelopes. It gives you building blocks, deliberately without imposing a flow.

What most people actually want is the layer above it: making commitments that mean something, vetting a counterparty before relying on them, and recording an interaction so either side can prove their account later. That is synpareia-trust-mcp — the Trust Toolkit MCP server, which wraps this SDK with runtime-ready ergonomics and names its tools after the social act rather than the mechanism.

Installing the toolkit today: the latest synpareia-trust-mcp on PyPI is 0.8.0, which declares mcp[cli]>=1.0 with no upper bound. mcp 2.0.0 (2026-07-28) moved mcp.server.fastmcp, so an unpinned install resolves 2.0.0 and the server fails on import. Install it as pip install synpareia-trust-mcp 'mcp<2' (or uvx --with 'mcp<2' synpareia-trust-mcp) for as long as 0.8.0 is the latest release. The bound ships in 0.9.0; once pip index versions synpareia-trust-mcp shows 0.9.0 or newer, drop the pin and this note. This SDK is unaffected.

Reach for this SDK when you are building the thing agents use, not using it: a framework adapter (CrewAI, LangGraph, a custom platform), another MCP server, your own agent runtime, or verification embedded in a service of your own. Come back here when the toolkit's ergonomics stop fitting.

Install

pip install synpareia

Requires Python 3.11+. Four runtime dependencies: cryptography (signing), http-message-signatures (RFC 9421 request auth), rfc8785 (JCS canonicalisation), idna. Tiers 1–3 exercise only the first three; nothing here pulls in a web framework. Network access needs an extra — pip install "synpareia[witness]" or [profile], both of which add httpx.

("Only dependency: cryptography" was true when the tier table below was written and stopped being true as soon as signed requests and canonical JSON arrived.)

Verify you have the SDK

import synpareia
assert synpareia.__version__ >= "0.2.0"
assert hasattr(synpareia, "generate"), "you have a different 'synpareia' in your venv"

If hasattr(synpareia, "generate") is False, something else in your environment is shadowing the SDK. Use a fresh virtualenv (python -m venv .venv && source .venv/bin/activate && pip install synpareia) and try again.

Quick Start

Create an identity

Every agent gets an Ed25519 keypair. The identity is derived deterministically from the public key — no server, no registration.

import synpareia

# Generate a new agent identity
profile = synpareia.generate()
print(profile.id)  # did:synpareia:a1b2c3...

Sign a block

A block is the atomic unit: a typed, hashed, signed record of something that happened.

block = synpareia.create_block(
    profile,
    type="message",
    content="Hello from Agent A",
)

# Anyone with the public key can verify authorship
assert synpareia.verify_block(block, profile.public_key)

Build a chain

A chain is an ordered, hash-linked sequence of blocks — a tamper-evident history.

# Create a Chain of Presence (personal history).
# Every chain carries a policy — templates.cop() is the personal-history shape.
chain = synpareia.create_chain(profile, policy=synpareia.templates.cop(profile))

# Append blocks
another_block = synpareia.create_block(profile, type="message", content="Hello again")
pos1 = synpareia.append_block(chain, block)
pos2 = synpareia.append_block(chain, another_block)

# Verify the chain is intact. Verification fails closed: signed blocks
# require the authors' public keys.
valid, errors = synpareia.verify_chain(chain, public_keys={profile.id: profile.public_key})
assert valid

Export and verify independently

Chains are portable. Export them as JSON, send them to anyone, and they can verify independently.

from synpareia import export_chain, verify_export

# Export
data = export_chain(chain)

# Anyone with the authors' public keys can verify — no network, no trust, just math
valid, errors = verify_export(data, public_keys={profile.id: profile.public_key})
assert valid

Link chains with anchors

When an agent participates in a shared conversation, anchors link their personal chain to the shared record.

# A peer keeps the shared record on their own chain...
peer = synpareia.generate()
shared_chain = synpareia.create_chain(peer, policy=synpareia.templates.cop(peer))
shared_block = synpareia.create_block(peer, type="message", content="shared record")
synpareia.append_block(shared_chain, shared_block)

# ...and Agent A's personal chain references a position in the shared chain
anchor_block, anchor_pos = synpareia.create_anchor_block(
    profile,
    chain,
    target_chain_id=shared_chain.id,
    target_sequence=2,
    target_block_hash=shared_block.content_hash,
    anchor_type="correspondence",
)

Commit-reveal for independent evaluation

Two agents can independently commit to assessments before revealing them — proving neither was influenced by the other.

# Agent commits (keeps nonce secret)
commitment_block, nonce = synpareia.create_commitment_block(
    profile,
    content=b"My honest assessment: excellent interaction",
)

# Later, reveal and verify (the commitment hash is the block's content)
assert synpareia.verify_commitment(
    commitment_block.content,
    b"My honest assessment: excellent interaction",
    nonce,
)

Core Concepts

Four Primitives

Primitive Purpose Example
Block Atomic signed record A message, thought, reaction, or system event
Chain Ordered, hash-linked sequence of blocks An agent's personal history, a conversation log
Anchor Cross-chain reference "This block in my chain corresponds to that block in theirs"
Seal Third-party attestation (Tier 4) A witness timestamps or checkpoints a chain

Chain of Presence (CoP)

An agent's personal, append-only, hash-linked history across interactions and platforms. Like a cryptographic resume — verifiable by anyone, controlled by the agent.

Spheres

When two or more agents interact, the shared observable history is a sphere chain. Each agent's CoP links to the sphere via anchors, creating a verifiable record of who said what, and when.

What You Can Verify

Claim How
"Agent X authored this content" Ed25519 signature on the block
"This content existed at time T" Block timestamp + chain position
"This history hasn't been tampered with" Hash-linked chain verification
"Agent X participated in conversation Y" Anchor from CoP to sphere chain
"Both assessments were independent" Commit-reveal: both commitments precede both reveals
"This chain export is authentic" verify_export() — offline, no service in the loop; the verifier supplies the authors' public keys

API Reference

Identity

Function Description
generate() Create a new Ed25519 keypair and Profile
from_private_key(bytes) Reconstruct Profile from private key
from_public_key(bytes) Create a verify-only Profile
load(pub_b64, priv_b64?) Load Profile from base64-encoded keys

Blocks

Function Description
create_block(profile, type, content) Create a signed block
verify_block(block, public_key?) Verify content hash and signature. Fails closed: with a key, the block must carry a signature that verifies against it; with no key, it's an explicit structure-only check (signed blocks fail)
reveal_block(block, content) Reveal content of a hash-only block

Chains

Function Description
create_chain(profile, *, policy=...) Create a new chain. A policy is required — use templates.cop(profile) for a personal chain, templates.sphere(a, b) for a two-party sphere, templates.audit(...) for oversight.
append_block(chain, block) Append a block, returns ChainPosition
verify_chain(chain, *, public_keys=...) Walk hash links, enforce chain policy, and verify Ed25519 signatures. Fails closed if signatures are present but public_keys is omitted — structure-only callers should use verify_chain_structure instead.
verify_chain_structure(chain) Structural validation only (no signatures, no policy). Explicit opt-in.
export_chain(chain) Export as portable, verifiable JSON
verify_export(data, *, public_keys=...) Verify an export without the original chain. Fails closed the same way verify_chain does: exports do not carry public keys, so omitting them returns (False, [...]) rather than reporting an unchecked chain as sound.

Anchors

Function Description
create_anchor_block(profile, chain, ...) Create a cross-chain reference
verify_anchor(anchor, source, target) Verify anchor references are valid

Commitments

Function Description
create_commitment(content, nonce?) Create a commitment hash
verify_commitment(hash, content, nonce) Verify a commitment reveal
create_commitment_block(profile, content) Create a commitment as a block

Chain policy

A chain carries a Policy that declares who the signatories are, what block types are permitted, and how the chain transitions through lifecycle states (Proposed → Pending → Active → Concluded). Use the templates module for the canonical three shapes:

from synpareia import create_chain, templates, verify_chain_policy

alice = synpareia.generate()
chain = create_chain(alice, policy=templates.cop(alice))  # Chain of Presence (single-party)

# For a two-party sphere:
bob = synpareia.generate()
sphere = create_chain(alice, policy=templates.sphere(alice, bob))

# Standalone policy validation (no signature check):
valid, errors = verify_chain_policy(sphere)

verify_chain invokes verify_chain_policy internally on every verification — callers get policy enforcement (permitted block types, signatory constraints, lifecycle state) by default.

Multi-party block proposals

A BlockProposal is a shareable draft of a block that multiple parties sign before it becomes a full Block. Use when two or more agents need to jointly commit to a statement that none of them can later disavow.

from synpareia import start_proposal, sign_proposal, assemble_block

# Alice drafts a block that requires both Alice and Bob's signatures.
proposal = start_proposal(
    alice,
    type="message",
    content="Alice and Bob agree: integration passed.",
    required_signers={alice.id, bob.id},
)

# Each party signs in turn.
proposal = sign_proposal(proposal, alice)
proposal = sign_proposal(proposal, bob)

# Assembly verifies every signature before producing the final block.
# public_keys is required — assemble_block refuses to produce an
# unverified block.
block = assemble_block(
    proposal,
    public_keys={alice.id: alice.public_key, bob.id: bob.public_key},
)

# The resulting block carries Alice as the primary signer and Bob in
# co_signatures; both bindings are committed to by Alice's signature.

Threshold commitments (XOR n-of-n)

Reveal-requires-cooperation primitive. Two or more parties each contribute a random share; the joint nonce is the XOR of all shares. No single party can reveal the committed content without the others' cooperation, because none of them knows the joint nonce.

from synpareia import (
    create_threshold_commitment,
    random_shares,
    verify_threshold_commitment,
    xor_shares,
)

content = b"Alice and Bob's sealed assessment"

# Each party holds their own 32-byte share (generated together here for brevity).
alice_share, bob_share = random_shares(2)

# Commit jointly — neither party alone can open this.
commitment, joint_nonce = create_threshold_commitment(content, [alice_share, bob_share])

# Later, to reveal: both shares are contributed.
assert xor_shares([alice_share, bob_share]) == joint_nonce
assert verify_threshold_commitment(commitment, content, [alice_share, bob_share])

create_threshold_commitment rejects share-sets that XOR to an all-zero joint nonce (catches duplicate shares or accidentally collapsed entropy). random_shares enforces a 16-byte minimum share length.

Types

BlockType, ChainType, AnchorType, ContentMode — extensible enums for all standard types.

Storage

MemoryStore (default), ChainStore protocol for custom backends.

Directory client (network — pip install "synpareia[profile]")

ProfileClient is the async client for the synpareia directory; SyncProfileClient is the same surface for non-async callers. Every mutating call is signed with RFC 9421 HTTP Message Signatures using your Ed25519 key, so the directory authenticates you without an API key.

from synpareia.identity import generate
from synpareia.profile import SyncProfileClient

me = generate()

with SyncProfileClient("https://synpareia.com") as directory:
    view = directory.get_existence(did=me.id)
    print(view["exists"])

publish, get_existence, get_history, get_well_known, delete_history_version, delete_profile, request_witness_anchor, submit_event, get_reputation. The last two are new in 0.7.0: submit_event posts a signed, content-less topology event (/api/v2/events, live), and get_reputation reads the asker-anchored aggregate.

get_reputation is not yet served by the hosted directory. The route exists in the directory's source tree but is not in the deployed image, so calling it against synpareia.com returns 404 today rather than the documented confidence: 0.0. Point base_url at a directory built from a tree that mounts it, or wait for the deploy.

Design Choices

  • Ed25519 for signatures — fast, small, deterministic, safe-by-default
  • SHA-256 for hashing — universal, well-audited, interoperable
  • JCS (RFC 8785) for canonicalization — deterministic JSON serialization for reproducible hashes
  • Frozen dataclasses — immutable primitives, zero framework dependency
  • Length-prefixed commitments — prevents separator collision in commit-reveal payloads
  • Constant-time comparison — timing-safe commitment verification via hmac.compare_digest

Tiers

The SDK is structured in tiers of increasing dependency:

Tier What Dependencies
1-3 Blocks, Chains, Anchors cryptography, rfc8785, http-message-signatures
4 Witness seals, liveness + httpx (synpareia[witness])
5 Reputation, directory + httpx (synpareia[profile]) + a directory to talk to

Tiers 1-3 work entirely offline. No server, no account, no network.

Tier 5 said "(coming)" until 0.7.0. It ships now — ProfileClient.submit_event and ProfileClient.get_reputation, documented above — with the caveat that the hosted directory serves the write route and not yet the read one.

License

Apache 2.0

Metadata

Release files for synpareia 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for synpareia 0.7.0
File Size Uploaded
synpareia-0.7.0.tar.gz 288.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for synpareia 0.7.0
File Interpreter ABI Platform
synpareia-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 418.4 kB

Release files / synpareia-0.7.0.tar.gz

Download URL synpareia-0.7.0.tar.gz
Size 288.7 kB
Tags Source
SHA-256 checksum
How to use checksums
b138c97a8d941d343b7c590e2afbdadb61a32162ffefef86196e5f8fada3b8c0
BLAKE2b-256 checksum
How to use checksums
52e518f3a26816efb32fed8cbbd85cfb645ed9e21b685c54cf03546ac76c1e66
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Aug 7, 2026.

Transparency log

Release files / synpareia-0.7.0-py3-none-any.whl

Download URL synpareia-0.7.0-py3-none-any.whl
Size 129.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9efa035bf01950503eaec68eff27bdc83b930b41165fdf8633e77de89cf12443
BLAKE2b-256 checksum
How to use checksums
bb76a879513579b43d66ec9cf77b0e407b16fda8eba5c3654cd7f479e981b29e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Aug 7, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.7.0 This release

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page