Skip to content

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 os
from 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

ParameterTypeRequiredDescription
endpointstrYesRelay server URL (e.g. "https://relay.skytale.sh:5000")
data_dirstrYesPath to local directory for MLS state and key storage
identitybytesYesUnique identity for this agent
api_keystrNoAPI key for authenticated access
api_urlstrNoAPI 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/alice is 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 (from channel.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 (from generate_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 as bytes

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 plaintext

Each yielded value is bytes containing the decrypted message payload.

Error handling

All SDK methods raise RuntimeError on failure. Error messages indicate the subsystem:

Error prefixCause
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 os
import threading
from static_sdk import StaticClient
# Agent A creates a channel
alice = 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 joins
bob = 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 thread
def 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 messages
channel.send(b"Hello from Alice!")
channel.send(b"Another message!")