Skip to content

Python SDK

The Layr8 Python SDK is an async library for building DIDComm agents. It handles WebSocket connections, Phoenix Channel protocol, DIDComm message formatting, and thread correlation. You write domain logic.

A basic agent is ~20 lines of Python. The SDK manages everything between your handler and the network.

Install

Terminal window
pip install layr8

Requires Python >= 3.11.

Core Concepts

Config

from layr8 import Client, Config
client = Client(Config(
node_url="wss://your-node.node.layr8.io/plugin_socket/websocket",
api_key="your-api-key",
agent_did="did:web:your-node.node.layr8.io:my-agent",
))

All fields fall back to environment variables if empty: LAYR8_NODE_URL, LAYR8_API_KEY, LAYR8_AGENT_DID. If agent_did is omitted, the node assigns an ephemeral DID on connect.

Handlers

Register handlers before calling connect. Use the decorator syntax — the handler’s return value determines what happens:

from layr8 import Message
@client.handle("https://layr8.io/protocols/echo/1.0/request")
async def echo(msg: Message) -> Message | None:
# Return Message → send response to sender
# Return None → no response (fire-and-forget)
# Raise → send problem report to sender

The protocol base URI is derived automatically from the message type and registered with the node on connect.

Send (Fire-and-Forget)

await client.send(Message(
type="https://didcomm.org/basicmessage/2.0/message",
to=["did:web:partner-node:agent"],
body={"content": "Hello!"},
))

Request (Request/Response)

resp = await client.request(
Message(
type="https://layr8.io/protocols/echo/1.0/request",
to=["did:web:partner-node:echo-agent"],
body={"message": "ping"},
),
timeout=5.0,
)
# resp is the correlated response, matched by thread ID

Messages

@dataclass
class Message:
id: str = "" # auto-generated if empty
type: str = "" # DIDComm message type URI
from_: str = "" # auto-filled with agent DID (wire: "from")
to: list[str] # recipient DIDs
thread_id: str = "" # auto-generated for request
parent_thread_id: str = ""
body: Any = None # serialized to JSON
context: MessageContext | None = None

Use msg.unmarshal_body() to deserialize as a dict, or msg.unmarshal_body(MyDataclass) to construct a typed dataclass. On inbound messages, msg.context provides node metadata: authorized, recipient, and sender_credentials.

Async Context Manager

async with client:
print(f"running as {client.did}")
await asyncio.Event().wait() # run forever

async with calls connect on enter and close on exit.

Durable Handlers (Persist-then-Ack)

For messages that must not be lost, use manual ack to persist before acknowledging:

@client.handle("https://layr8.io/protocols/order/1.0/created", manual_ack=True)
async def handle_order(msg: Message) -> None:
# Write to disk first — if this fails, the message is
# NOT acked and the cloud-node will redeliver it.
with open("messages.jsonl", "a") as f:
f.write(json.dumps({"id": msg.id, "body": msg.body}) + "\n")
f.flush()
msg.ack() # safe to ack now

See examples/durable_handler.py for a complete working example.

Claude Code Skill

The SDK includes a Claude Code skill that teaches Claude the full API. Install it into any agent project:

Terminal window
mkdir -p .claude/skills
cp /path/to/python-sdk/.claude/skills/build-layr8-agent-python.md .claude/skills/

Once installed, Claude Code knows how to build Layr8 agents in every session. Example prompts:

  • “Build me a FastAPI bridge agent that converts REST calls to DIDComm messages”
  • “Add a handler for order.created messages that validates the sender’s credentials”
  • “Convert this Flask endpoint into a DIDComm agent”