Py
Python SDK
v1.2.0 · MIT License · Python 3.11+ · asyncio
asyncio-native Aroha SDK with FastAPI transport, Pydantic validation, and first-class bridges for LangChain, CrewAI, and AutoGen.
Installation
# Core only python -m venv .venv && source .venv/bin/activate # required on macOS/Linux pip install aroha # With LangChain bridge pip install "aroha[langchain]" # With CrewAI bridge pip install "aroha[crewai]" # With AutoGen bridge pip install "aroha[autogen]" # Everything pip install "aroha[langchain,crewai,autogen]"
Python 3.11+ required. Modern macOS (Homebrew) and most Linux distros block system-wide
pip install (PEP 668) — always create a venv first.ArohaServer
Minimal server
import asyncio
from aroha.transport import ArohaServer
from aroha.identity import generate_did
from aroha.messages import ArohaEnvelope, NonceRegistry
# generate_did(agent_id, endpoint) → AgentKeyPair
# AgentKeyPair has: .did, .private_key, .public_key, .public_key_bytes, .document
keypair = generate_did("my-agent", "http://localhost:3000")
my_did = keypair.did
async def handle_message(envelope: ArohaEnvelope, respond, stream):
if envelope["type"] == "ArohaRequest": # envelope is a plain dict
capability = envelope["body"]["capability"]
params = envelope["body"]["params"]
if capability == "greet":
await respond({
"type": "ArohaResponse",
"body": {"capability": "greet", "result": {"message": f"Hello, {params['name']}!"}},
})
async def resolve_public_key(did: str) -> bytes | None:
# Return sender's Ed25519 public key bytes, or None if unknown
return None
server = ArohaServer(
agent_did=keypair.did,
private_key=keypair.private_key,
port=3000,
on_message=handle_message,
resolve_public_key=resolve_public_key,
dev_mode=True, # skip crypto in development
)
asyncio.run(server.start())Production server with middleware
from aroha.transport import ArohaServer
from aroha.middleware import rbac_middleware, rate_limit_middleware
server = ArohaServer(
agent_did=my_did,
private_key=private_key,
port=3000,
on_message=handle_message,
resolve_public_key=resolve_public_key,
middleware=[
rbac_middleware(required_trust_level=2),
rate_limit_middleware(requests_per_minute=100),
],
clock_tolerance_ms=5000,
max_body_bytes=1_048_576,
)ArohaClient
Python-specific: The first parameter of
build_envelope() is type_ (with underscore) because type is a Python builtin. Passing type= raises unexpected keyword argument.import asyncio
from aroha.client import ArohaClient
from aroha.messages import build_envelope, new_correlation_id
from aroha.identity import generate_did
keypair = generate_did("orchestrator", "http://localhost:3001")
async def main():
client = ArohaClient()
# Note: first param is type_ (not type) — Python builtin conflict
envelope = build_envelope(
type_="ArohaRequest", # ← type_, not type
from_did=keypair.did,
to_did="did:aroha:travel-agent",
body={
"capability": "search-flights",
"params": {"from": "JFK", "to": "LHR", "date": "2026-08-01"},
},
correlation_id=new_correlation_id(),
private_key=keypair.private_key,
)
response = await client.send("http://travel-agent.example.com", envelope)
if response and response["type"] == "ArohaResponse":
print(response["body"]["result"]) # {"flights": [...]}
asyncio.run(main())Identity & DIDs
from aroha.identity import generate_did
# generate_did(agent_id: str, endpoint: str) → AgentKeyPair
keypair = generate_did("travel-agent", "http://localhost:3000")
# AgentKeyPair fields:
keypair.did # "did:aroha:travel-agent"
keypair.private_key # Ed25519PrivateKey
keypair.public_key # Ed25519PublicKey
keypair.public_key_bytes # raw bytes
keypair.document # DIDDocument dataclass
# DIDDocument → JSON-serialisable dict
doc_dict = keypair.document.to_dict()Spending Mandates
from aroha.mandate import (
issue_intent_mandate,
attenuate_mandate,
verify_mandate,
SpendingConstraints,
)
from datetime import datetime, timedelta
constraints = SpendingConstraints(
spend_limit_usd=500.0,
allowed_capabilities=["search-flights", "reserve-flight"],
expires_at=datetime.utcnow() + timedelta(hours=1),
)
# User authorises the orchestrator
mandate = issue_intent_mandate(
grantor_did=user_did,
grantee_did=orchestrator_did,
constraints=constraints,
private_key=user_private_key,
)
# Orchestrator delegates a sub-mandate to a sub-agent (limits can only narrow)
sub_mandate = attenuate_mandate(
parent=mandate,
grantee_did=flight_agent_did,
constraints=SpendingConstraints(
spend_limit_usd=300.0, # ≤ 500
allowed_capabilities=["reserve-flight"],
),
private_key=orchestrator_private_key,
)
# Verify on the receiving agent
is_valid, reason = await verify_mandate(sub_mandate, expected_grantee=flight_agent_did)
print(is_valid, reason) # True, "ok"Saga Orchestration
from aroha.orchestrator import SagaEngine, SagaStep
import asyncio
engine = SagaEngine(client=client, orchestrator_did=my_did, private_key=private_key)
result = await engine.run([
SagaStep(
agent_did="did:aroha:flight-agent",
endpoint="http://flights.example.com",
capability="reserve-flight",
params={"from": "JFK", "to": "LHR", "date": "2026-08-01"},
budget_usd=300,
),
SagaStep(
agent_did="did:aroha:hotel-agent",
endpoint="http://hotels.example.com",
capability="reserve-hotel",
params={"city": "London", "nights": 3},
budget_usd=200,
),
])
print(result.status) # "committed" or "compensated"
for step in result.steps:
print(step.commit_token) # use for billing reconciliationLangChain Bridge
# pip install "aroha[langchain]"
from aroha.bridges.langchain_bridge import (
aroha_capability_to_langchain_tool,
langchain_tools_to_aroha_provider,
)
from langchain_anthropic import ChatAnthropic
from langchain.agents import create_tool_calling_agent, AgentExecutor
# Wrap an Aroha capability as a LangChain tool
flight_tool = aroha_capability_to_langchain_tool(
capability_id="search-flights",
endpoint="http://travel-agent.example.com",
agent_did="did:aroha:travel-agent",
caller_did=my_did,
caller_private_key=private_key,
description="Search available flights between airports",
)
# Build a LangChain agent that can call Aroha agents
llm = ChatAnthropic(model="claude-sonnet-4-6")
agent = create_tool_calling_agent(llm, [flight_tool], prompt)
executor = AgentExecutor(agent=agent, tools=[flight_tool])
result = await executor.ainvoke({"input": "Find flights from JFK to London next Tuesday"})
print(result["output"])CrewAI Bridge
# pip install "aroha[crewai]"
from aroha.bridges.crewai_bridge import aroha_capability_to_crewai_tool
from crewai import Agent, Task, Crew
flight_tool = aroha_capability_to_crewai_tool(
capability_id="search-flights",
endpoint="http://travel-agent.example.com",
agent_did="did:aroha:travel-agent",
caller_did=my_did,
caller_private_key=private_key,
description="Search available flights — params: from, to, date",
)
travel_agent = Agent(
role="Travel Coordinator",
goal="Find the best travel options for the user",
tools=[flight_tool],
llm="claude-sonnet-4-6",
)
task = Task(
description="Find flights from JFK to LHR on 2026-08-01",
agent=travel_agent,
)
crew = Crew(agents=[travel_agent], tasks=[task])
result = crew.kickoff()
print(result)AutoGen Bridge
# pip install "aroha[autogen]"
from aroha.bridges.autogen_bridge import aroha_capability_to_autogen_function
from autogen import AssistantAgent, UserProxyAgent
search_flights_fn = aroha_capability_to_autogen_function(
capability_id="search-flights",
endpoint="http://travel-agent.example.com",
agent_did="did:aroha:travel-agent",
caller_did=my_did,
caller_private_key=private_key,
)
assistant = AssistantAgent(
name="TravelAssistant",
llm_config={
"functions": [search_flights_fn.schema],
"config_list": [{"model": "claude-sonnet-4-6"}],
},
function_map={search_flights_fn.name: search_flights_fn.execute},
)
user_proxy = UserProxyAgent(name="User", human_input_mode="NEVER")
user_proxy.initiate_chat(assistant, message="Find me flights from JFK to London on August 1st")Envelopes & Validation
Message type strings are identical between Python and TypeScript:
"ArohaRequest", "ArohaResponse", etc. Envelopes are plain dicts in Python — access fields with envelope["type"] not envelope.type.from aroha.messages import (
build_envelope, # build_envelope(type_, from_did, to_did, body, correlation_id, private_key)
validate_envelope, # returns (bool, Optional[str]) — NOT a dict
NonceRegistry, # required for validate_envelope — tracks seen nonces
new_correlation_id,
ArohaErrorCode,
)
from aroha.crypto import sign_message # sign_message(message, private_key, verification_method_id)
# NonceRegistry prevents replay attacks — create one per server instance
registry = NonceRegistry()
# Validate inbound envelope
valid, reason = validate_envelope(
envelope=incoming,
sender_public_key=sender_pub_key, # Ed25519PublicKey object
my_did="did:aroha:my-agent",
nonce_registry=registry, # required — use skip_signature=True only on trusted mesh
)
if not valid:
raise ValueError(f"Invalid envelope: {reason}")
# Build outbound envelope (type_ with underscore — 'type' is a Python builtin)
env = build_envelope(
type_="ArohaRequest",
from_did=keypair.did,
to_did="did:aroha:travel-agent",
body={"capability": "search-flights", "params": {}},
correlation_id=new_correlation_id(),
private_key=keypair.private_key,
ttl_seconds=300, # optional, default 300
)
# sign_message is called automatically by build_envelope.
# Only call directly if you need a standalone proof:
proof = sign_message(
message=env,
private_key=keypair.private_key,
verification_method_id=f"{keypair.did}#key-1", # 3rd positional arg, not keyword 'key_id'
)All Modules
aroha.cryptosign_message(msg, key, vm_id), verify_message_signature(), AES-256-GCM
aroha.identitygenerate_did(agent_id, endpoint) → AgentKeyPair, DIDDocument
aroha.messagesbuild_envelope(type_, ...), validate_envelope() → (bool, str|None), NonceRegistry
aroha.transportArohaServer (FastAPI), ArohaClient (httpx)
aroha.clientArohaClient — standalone HTTP client for sending envelopes
aroha.orchestratorSagaEngine, SagaStep, AgentSelector
aroha.mandateSpendingMandate: issue_intent_mandate(), attenuate_mandate(), verify_mandate()
aroha.reputationBayesian Beta engine, satisfaction signals
aroha.csnCapability Schema Negotiation
aroha.middlewarerbac_middleware, rate_limit_middleware
aroha.bridges.langchain_bridgeLangChain tools ↔ Aroha capabilities
aroha.bridges.crewai_bridgeCrewAI tools ↔ Aroha capabilities
aroha.bridges.autogen_bridgeAutoGen functions ↔ Aroha capabilities