Chains
A chain is an ordered, hash-linked sequence of blocks. It’s the core data structure for building verifiable agent histories.
Creating chains
Section titled “Creating chains”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.
Appending blocks
Section titled “Appending blocks”block = synpareia.create_block(profile, "message", "First action")pos = chain.append(block)
print(pos.sequence) # 1print(pos.position_hash) # bytes — the hash linking this position to the chainEach append returns a ChainPosition containing the sequence number, position hash, and parent hash (the previous position’s hash, or None for the first block).
Position hashes
Section titled “Position hashes”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.
Verification
Section titled “Verification”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
Chain types
Section titled “Chain types”| Type | Purpose |
|---|---|
cop | Chain of Presence — one agent’s history across all contexts |
sphere | Shared history of a multi-agent interaction |
audit | Audit trail for a specific process |
| Custom | Any string — application-specific chains |
from synpareia.types import ChainType
# Explicit chain typechain = synpareia.create_chain(profile, chain_type=ChainType.SPHERE)
# Custom typechain = synpareia.create_chain(profile, chain_type="crew_execution")Querying
Section titled “Querying”# Get a specific positionpos = chain.get_position(5)
# Get the block at a positionblock = chain.get_block(5)
# Query by type or authorresults = chain.query(block_type="message", limit=10)for pos, block in results: print(f"[{pos.sequence}] {block.content}")
# Chain metadataprint(chain.length) # number of blocksprint(chain.head) # latest positionprint(chain.head_hash) # hash of latest positionStorage backends
Section titled “Storage backends”Chains need somewhere to store their blocks and positions.
In-memory (default)
Section titled “In-memory (default)”Fast, ephemeral. Data is lost when the process exits.
chain = synpareia.create_chain(profile) # uses MemoryStoreSQLite
Section titled “SQLite”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 restartsCustom backends
Section titled “Custom backends”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 protocolBuilt by Sam Hyland · Canberra, Australia