Skip to content

Chains

A chain is an ordered, hash-linked sequence of blocks. It’s the core data structure for building verifiable agent histories.

import synpareia
profile = synpareia.generate()
chain = synpareia.create_chain(profile)

Every chain has an owner (the profile that created it), a type, and a unique ID.

block = synpareia.create_block(profile, "message", "First action")
pos = chain.append(block)
print(pos.sequence) # 1
print(pos.position_hash) # bytes — the hash linking this position to the chain

Each append returns a ChainPosition containing the sequence number, position hash, and parent hash (the previous position’s hash, or None for the first block).

The position hash is what makes chains tamper-evident. It’s computed as:

SHA-256(sequence : author_id : type : created_at : content_hash : parent_hash)

Because each position hash includes the previous position’s hash (parent_hash), modifying any block invalidates every subsequent position in the chain. This is the same principle behind Git’s commit hashes.

valid, errors = chain.verify()
if not valid:
for error in errors:
print(f"Integrity violation: {error}")

Verification walks the entire chain and checks:

  • Every position hash recomputes correctly
  • Every parent hash matches the previous position
  • Every block signature is valid
  • Sequence numbers are monotonically increasing
TypePurpose
copChain of Presence — one agent’s history across all contexts
sphereShared history of a multi-agent interaction
auditAudit trail for a specific process
CustomAny string — application-specific chains
from synpareia.types import ChainType
# Explicit chain type
chain = synpareia.create_chain(profile, chain_type=ChainType.SPHERE)
# Custom type
chain = synpareia.create_chain(profile, chain_type="crew_execution")
# Get a specific position
pos = chain.get_position(5)
# Get the block at a position
block = chain.get_block(5)
# Query by type or author
results = chain.query(block_type="message", limit=10)
for pos, block in results:
print(f"[{pos.sequence}] {block.content}")
# Chain metadata
print(chain.length) # number of blocks
print(chain.head) # latest position
print(chain.head_hash) # hash of latest position

Chains need somewhere to store their blocks and positions.

Fast, ephemeral. Data is lost when the process exits.

chain = synpareia.create_chain(profile) # uses MemoryStore

Persistent storage using SQLite. Install with pip install synpareia[sqlite].

from synpareia.chain.storage.sqlite import SQLiteStore
store = SQLiteStore("chains.db")
chain = synpareia.create_chain(profile, store=store)
# Data persists across process restarts

Implement the ChainStore protocol for any storage backend:

from synpareia.chain.storage import ChainStore
class MyStore(ChainStore):
def store_block(self, chain_id, block): ...
def store_position(self, chain_id, position): ...
def get_block(self, block_id): ...
# ... see API reference for full protocol

Built by Sam Hyland · Canberra, Australia