Concepts¶
Six primitives carry the whole SDK. Get these in your head first and the rest of the docs read clean.
┌──────────────────────────────────────┐
│ SignatureBackend (ABC) │
│ Ed25519Backend · MLDSABackend │ ← the moat
└────────────────┬─────────────────────┘
│ signs
┌────────────────▼─────────────────────┐
│ ConsensusReceipt │
│ canonical payload · algorithm · sig │
└────────────────┬─────────────────────┘
│ produced by
┌────────────────▼─────────────────────┐
│ ConsensusStrategy (ABC) │
│ WeightedVote · Debate │
└────────────────┬─────────────────────┘
│ aggregates
┌─────────────────────────▼──────────────────────────────────┐
│ Swarm ←— holds —─ AgentConfig[] → Agent (Claude call) │
└─────────────────────────▲──────────────────────────────────┘
│ processes
┌────┴────┐
│ Task │
└─────────┘
Agent¶
A single Claude call wrapped in a tool-use loop. You rarely construct one
directly — you describe what you want via an AgentConfig and the Swarm
materializes agents on demand.
from tractum import AgentConfig
AgentConfig(
name="StrongConsistency",
role="Distributed Systems Engineer",
perspective="You weigh CAP tradeoffs and operational maturity.",
model="claude-haiku-4-5-20251001", # optional; defaults to a sensible Haiku
max_tokens=512, # optional
)
Each agent sees the task description, its own perspective, and any tools
you've configured. It returns an answer, a confidence score, and a list of
the tool calls it made.
Task¶
The unit of work a Swarm processes. Carries an id, the question, and any
input data specialists need.
from tractum import Task
Task(
id="q1",
description="Active-active Postgres, FoundationDB, or eventual consistency for payments?",
input_data={}, # optional context — schema, prior art, etc.
)
Task.id lands on the receipt, so it's how you trace a published consensus
back to the question that produced it.
Swarm¶
The concurrent collection of agents that processes a Task. Swarm.process
fires all Claude calls in parallel (asyncio.gather, not serially), feeds
the responses to the consensus strategy, and returns a ConsensusResult
with the final answer, per-agent responses, and the agreement score.
from tractum import Swarm, SwarmConfig, AgentConfig
swarm = Swarm(SwarmConfig(
name="ArchReview",
agents=[
AgentConfig("StrongConsistency", "Distributed Systems Engineer", "..."),
AgentConfig("Operations", "SRE / Platform Lead", "..."),
AgentConfig("Pragmatist", "Staff Engineer", "..."),
],
), api_key=...)
result = await swarm.process(task)
A swarm is not "ask N agents the same question and pick the first answer." It's "ask N specialists the same question, surface where they disagreed, and aggregate the responses through a defined strategy."
Consensus strategy¶
Pluggable aggregation over agent responses. Two strategies ship:
WeightedVote(default): aggregate by reported confidence. One round, cheap, deterministic.Debate: agents see peer responses and revise once. Higher-quality agreement on hard questions; early-exits when agents already agree.
Subclass ConsensusStrategy to add your own (tournament, human-in-loop,
superconductor — your call). The strategy is the seam where most
domain-specific logic lives.
Receipt¶
A ConsensusReceipt is a transport-agnostic canonical payload — the
agreed answer, the agents who produced it, the strategy used, and the
task id. It's not text; it's an attestation.
from tractum import ConsensusReceipt
receipt = ConsensusReceipt.from_result(
result,
swarm_name="ArchReview",
task_id="q1",
)
receipt.payload is canonicalized via canonical_json — two processes
producing the same receipt hash identically, anywhere. This is what makes
signatures portable across machines, languages, and decades.
Agent statements (k-of-n)¶
Since v0.4.0, the receipt isn't just the operator's claim — every agent
signs its own stance. Each agent in a Swarm gets a signing keypair
(pinned via AgentConfig.signing_key, otherwise an ephemeral key is
provisioned per swarm), and every response carries a signed
agent-statement.v1 over its answer, confidence, model, role, task, and
round.
When statements are present, ConsensusReceipt.from_result emits a v2
payload: the statements plus a quorum threshold k (default:
unanimous), all covered by the operator's aggregate signature. The
verifier counts valid agent signatures against k:
from tractum import verify_receipt_detailed
report = verify_receipt_detailed(
receipt,
expected_operator_pubkey=operator_pub, # pin the operator
expected_agent_pubkeys={"A": pub_a, "B": pub_b, "C": pub_c},
)
report.valid # aggregate valid AND quorum met
report.valid_statements # how many agent signatures held up
report.statements[0].reason # "" | key-mismatch | task-id-mismatch
# | response-mismatch:answer | ...
Pinning is the difference between "verifiable" and "verifiable as someone specific": signatures are self-consistent under any key, so a malicious operator can mint statements under fresh keys — pinning the agent pubkeys you know makes that detectable. Statements are also cross-checked against the responses they claim to attest, and pinned to their task (no cross-task replay). See the threat model for the full attack surface.
SignatureBackend¶
The crypto layer, isolated behind an ABC so consensus code never depends on which algorithm you pick. Two backends ship:
Ed25519Backend(default): compact, fast, no native deps, works everywhere.MLDSABackend(tractum.signing_pq): ML-DSA-65 / FIPS 204 post-quantum. Pure-Python viadilithium-py.
from tractum import Ed25519Backend
# or: from tractum.signing_pq import MLDSABackend
backend = Ed25519Backend()
private_key, public_key = backend.generate_keypair()
backend.sign_receipt(receipt, private_key)
assert backend.verify_receipt(receipt) is True
The same receipt can be signed under either backend — receipt.algorithm
records which. Statements work the same way (backend.sign_statement /
backend.verify_statement), and mixed-algorithm statements verify per
statement. The signing layer is fully decoupled from the transport
layer, so you can change transports (JSON → W3C VC → Nostr) without
re-signing. See Transports.
Putting it together¶
import asyncio
from tractum import (
AgentConfig, ConsensusReceipt, Debate, Ed25519Backend,
Swarm, SwarmConfig, Task,
)
async def main():
swarm = Swarm(SwarmConfig(
name="ArchReview",
agents=[
AgentConfig("StrongConsistency", "Distributed Systems Engineer", "..."),
AgentConfig("Operations", "SRE / Platform Lead", "..."),
AgentConfig("Pragmatist", "Staff Engineer", "..."),
],
consensus=Debate(),
), api_key=...)
result = await swarm.process(Task(
id="q1",
description="Active-active Postgres or eventual consistency for payments?",
input_data={},
))
receipt = ConsensusReceipt.from_result(result, swarm_name="ArchReview", task_id="q1")
Ed25519Backend().sign_receipt(receipt, private_key)
print(result.result["final_answer"])
print(f"agreement: {result.result['agreement']:.0%}")
asyncio.run(main())
Six primitives, one signed answer. The rest of the SDK is adapters, transports, and observability — all of which compose over this core.
Next¶
- Quick start — run this in under two minutes.
- Transports — JSON, W3C VC, Nostr NIP-78.
- Adapters — LangGraph, CrewAI, MAF, Buzz.
- API Reference — every public module.