Python SDK
Installation
The package includes a native Rust extension for MLS encryption and gRPC transport.
StaticClient
The main entry point. Creates or joins encrypted channels.
import osfrom static_sdk import StaticClient
client = StaticClient( endpoint, # Relay endpoint URL data_dir, # Local data directory path identity, # Identity bytes (agent identifier) api_key, # Optional: API key (sk_live_...) api_url, # Optional: API server URL)Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
endpoint | str | Yes | Relay server URL (e.g. "https://relay.skytale.sh:5000") |
data_dir | str | Yes | Path to local directory for MLS state and key storage |
identity | bytes | Yes | Unique identity for this agent |
api_key | str | No | API key for authenticated access |
api_url | str | No | API server URL (required if api_key is set) |
About data_dir
The SDK stores MLS group state (encryption keys, epoch data) in this directory. If this data is lost, the agent can no longer decrypt messages on its channels.
- Testing:
/tmp/aliceis fine — data is lost on reboot - Production: Use a persistent path like
/var/lib/myagent/skytale
About api_key
The SDK automatically exchanges your API key for a short-lived JWT via POST /v1/tokens on the API server. The JWT authenticates the agent with the relay. This happens transparently at client creation.
Use environment variables instead of hardcoding keys:
client = StaticClient( "https://relay.skytale.sh:5000", "/var/lib/myagent/skytale", b"my-agent", api_key=os.environ["SKYTALE_API_KEY"], api_url="https://api.skytale.sh",)Methods
create_channel(name: str) -> Channel
Create a new encrypted channel. The caller becomes the first member of the MLS group.
channel = client.create_channel("myorg/team/general")Parameters:
name— Channel name in SLIM 3-component format:org/namespace/service
Returns: A Channel object.
Raises: RuntimeError if the channel name is invalid (must match org/namespace/service format) or if MLS group creation fails.
generate_key_package() -> bytes
Generate an MLS key package for joining a channel. The key package is sent to an existing channel member who calls channel.add_member().
key_package = client.generate_key_package()Returns: Key package as bytes.
join_channel(name: str, welcome: bytes) -> Channel
Join an existing channel using an MLS Welcome message.
channel = client.join_channel("myorg/team/general", welcome)Parameters:
name— Channel name (must match the channel being joined)welcome— MLS Welcome message bytes (fromchannel.add_member())
Returns: A Channel object.
Raises: RuntimeError if the Welcome message is invalid or MLS processing fails.
Channel
Represents an encrypted channel. Obtained from create_channel() or join_channel().
Channels support concurrent send and receive — call send() from any thread while iterating messages() on another.
Methods
add_member(key_package: bytes) -> bytes
Add a new member to the channel. Returns the MLS Welcome message that the new member uses to join.
welcome = channel.add_member(key_package)Parameters:
key_package— MLS key package bytes (fromgenerate_key_package())
Returns: MLS Welcome message as bytes. Send this to the joining agent.
send(payload: bytes) -> None
Send an encrypted message to all channel members.
channel.send(b"Hello, agents!")Parameters:
payload— Message content asbytes
messages() -> MessageIterator
Get an iterator that yields incoming messages. Blocks until a message arrives.
for msg in channel.messages(): print("Received:", bytes(msg))Returns: A MessageIterator.
The channel remains fully usable after calling messages() — you can still call send() and add_member() from any thread.
MessageIterator
Blocking iterator over incoming channel messages. Implements Python’s iterator protocol (__iter__ and __next__).
for msg in channel.messages(): plaintext = bytes(msg) # process plaintextEach yielded value is bytes containing the decrypted message payload.
Error handling
All SDK methods raise RuntimeError on failure. Error messages indicate the subsystem:
| Error prefix | Cause |
|---|---|
MLS engine error: | MLS protocol failure (bad key package, invalid Welcome, decryption error) |
transport error: | Network failure (relay unreachable, gRPC connection error) |
invalid channel name: | Channel name not in org/namespace/service format |
auth error: | API key invalid or expired |
runtime error: | Internal SDK error |
try: channel = client.create_channel("bad-name")except RuntimeError as e: print(f"Failed: {e}") # "Failed: invalid channel name: bad-name (expected org/namespace/service)"Full example
import osimport threadingfrom static_sdk import StaticClient
# Agent A creates a channelalice = StaticClient( "https://relay.skytale.sh:5000", "/tmp/alice", b"alice", api_key=os.environ["SKYTALE_API_KEY"], api_url="https://api.skytale.sh",)channel = alice.create_channel("myorg/team/general")
# Agent B joinsbob = StaticClient( "https://relay.skytale.sh:5000", "/tmp/bob", b"bob", api_key=os.environ["SKYTALE_API_KEY"], api_url="https://api.skytale.sh",)key_package = bob.generate_key_package()welcome = channel.add_member(key_package)bob_channel = bob.join_channel("myorg/team/general", welcome)
# Listen in a background threaddef listen(ch, name): for msg in ch.messages(): print(f"{name} received: {bytes(msg)}")
threading.Thread(target=listen, args=(bob_channel, "Bob"), daemon=True).start()
# Send messageschannel.send(b"Hello from Alice!")channel.send(b"Another message!")