SDKs

Official client libraries for MUXI

Native SDKs for 12 languages. Build custom UIs, manage user credentials, handle observability events, and control formations programmatically.

Why Use the SDKs?

The SDKs are how developers build products on top of MUXI:

Use Case What SDKs Enable
Custom Chat UIs Build your own chat interface instead of using the default
Credential Management Let users add API keys/tokens via your UI (not via chat)
Observability Dashboards Subscribe to 350+ event types for monitoring and debugging
User Management Manage scheduled tasks, memory, and sessions per user
Formation Control Deploy, start, stop, restart formations programmatically

Security note: When user credentials are required for tools (GitHub, Gmail, etc.), developers can configure whether users provide them via chat or via a dedicated credentials page. The SDK lets you build that credentials page.

Quick Install

Quick Start

from muxi import FormationClient

client = FormationClient(
    server_url="http://localhost:7890",
    formation_id="my-assistant",
    client_key="your_client_key",
)

for event in client.chat_stream({"message": "Hello!"}):
    if event.get("type") == "text":
        print(event.get("text"), end="")
import { FormationClient } from "@muxi-ai/muxi-typescript";

const client = new FormationClient({
  serverUrl: "http://localhost:7890",
  formationId: "my-assistant",
  clientKey: "your_client_key",
});

for await (const event of client.chatStream({ message: "Hello!" })) {
  if (event.type === "text") process.stdout.write(event.text);
}
client := muxi.NewFormationClient(&muxi.FormationConfig{
    ServerURL:   "http://localhost:7890",
    FormationID: "my-assistant",
    ClientKey:   "your_client_key",
})

stream, _ := client.ChatStream(ctx, &muxi.ChatRequest{Message: "Hello!"})
for chunk := range stream {
    if chunk.Type == "text" {
        fmt.Print(chunk.Text)
    }
}
require 'muxi'

client = Muxi::FormationClient.new(
  server_url: 'http://localhost:7890',
  formation_id: 'my-assistant',
  client_key: 'your_client_key'
)

client.chat_stream(message: 'Hello!') do |event|
  print event['text'] if event['type'] == 'text'
end
FormationClient client = new FormationClient(
    "http://localhost:7890",
    "my-assistant",
    "your_client_key"
);

client.chatStream(new ChatRequest("Hello!"), event -> {
    if ("text".equals(event.getType())) {
        System.out.print(event.getText());
    }
    return true;
});
val client = FormationClient(
    serverUrl = "http://localhost:7890",
    formationId = "my-assistant",
    clientKey = "your_client_key"
)

client.chatStream(ChatRequest(message = "Hello!")).collect { event ->
    if (event.type == "text") print(event.text)
}
let client = FormationClient(
    serverURL: "http://localhost:7890",
    formationID: "my-assistant",
    clientKey: "your_client_key"
)

for try await event in client.chatStream(message: "Hello!") {
    if event.type == "text" { print(event.text ?? "", terminator: "") }
}
var client = new FormationClient(
    serverUrl: "http://localhost:7890",
    formationId: "my-assistant",
    clientKey: "your_client_key"
);

await foreach (var chunk in client.ChatStreamAsync(new ChatRequest { Message = "Hello!" }))
{
    if (chunk.Type == "text") Console.Write(chunk.Text);
}
$client = new FormationClient(
    serverUrl: 'http://localhost:7890',
    formationId: 'my-assistant',
    clientKey: 'your_client_key'
);

foreach ($client->chatStream(['message' => 'Hello!']) as $event) {
    if ($event['type'] === 'text') echo $event['text'];
}
final client = FormationClient(
  serverUrl: 'http://localhost:7890',
  formationId: 'my-assistant',
  clientKey: 'your_client_key',
);

await for (final event in client.chatStream(message: 'Hello!')) {
  if (event['type'] == 'text') stdout.write(event['text']);
}
let client = FormationClient::new(
    "http://localhost:7890",
    "my-assistant",
    "your_client_key",
);

let mut stream = client.chat_stream("Hello!").await?;
while let Some(event) = stream.next().await {
    if event?.event_type == "text" { print!("{}", event.text.unwrap_or_default()); }
}
auto client = muxi::FormationClient(
    "http://localhost:7890",
    "my-assistant",
    "your_client_key"
);

client.chat_stream({{"message", "Hello!"}}, [](sdks/const auto& event) {
    if (event.type == "text") std::cout << event.text;
    return true;
});

Local Development

When developing locally with muxi up, use the mode="draft" parameter to route requests through the /draft/ endpoint on your local MUXI server. This allows you to test changes without affecting your live deployment:

client = FormationClient(
    server_url="http://localhost:7890",
    formation_id="my-assistant",
    mode="draft",  # Uses /draft/ prefix
    client_key="your_client_key",
)
const client = new FormationClient({
  serverUrl: "http://localhost:7890",
  formationId: "my-assistant",
  mode: "draft",  // Uses /draft/ prefix
  clientKey: "your_client_key",
});
client := muxi.NewFormationClient(&muxi.FormationConfig{
    ServerURL:   "http://localhost:7890",
    FormationID: "my-assistant",
    Mode:        "draft",  // Uses /draft/ prefix
    ClientKey:   "your_client_key",
})
client = Muxi::FormationClient.new(
  server_url: 'http://localhost:7890',
  formation_id: 'my-assistant',
  mode: 'draft',  # Uses /draft/ prefix
  client_key: 'your_client_key'
)
FormationClient client = new FormationClient(
    "http://localhost:7890",
    "my-assistant",
    "your_client_key",
    "draft"  // Uses /draft/ prefix
);
val client = FormationClient(
    serverUrl = "http://localhost:7890",
    formationId = "my-assistant",
    mode = "draft",  // Uses /draft/ prefix
    clientKey = "your_client_key"
)
let client = FormationClient(
    serverURL: "http://localhost:7890",
    formationID: "my-assistant",
    mode: "draft",  // Uses /draft/ prefix
    clientKey: "your_client_key"
)
var client = new FormationClient(
    serverUrl: "http://localhost:7890",
    formationId: "my-assistant",
    mode: "draft",  // Uses /draft/ prefix
    clientKey: "your_client_key"
);
$client = new FormationClient(
    serverUrl: 'http://localhost:7890',
    formationId: 'my-assistant',
    mode: 'draft',  // Uses /draft/ prefix
    clientKey: 'your_client_key'
);
final client = FormationClient(
  serverUrl: 'http://localhost:7890',
  formationId: 'my-assistant',
  mode: 'draft',  // Uses /draft/ prefix
  clientKey: 'your_client_key',
);
let client = FormationClient::new(
    "http://localhost:7890",
    "my-assistant",
    "your_client_key",
).with_mode("draft");  // Uses /draft/ prefix
auto client = muxi::FormationClient(
    "http://localhost:7890",
    "my-assistant",
    "your_client_key",
    "draft"  // Uses /draft/ prefix
);

Workflow:

  1. Run muxi up to start your formation in draft mode
  2. Use mode="draft" in your SDK client during development
  3. Run muxi deploy to deploy to production
  4. Remove mode="draft" (or don't set it) - defaults to mode="live" which uses /api/

The mode parameter only affects URL routing. All other functionality is identical between draft and live modes.

Two Client Types

All SDKs provide two clients:

FormationClient

For interacting with a running formation:

  • Chat (streaming & non-streaming)
  • Sessions & history
  • Memory management
  • Triggers & scheduled tasks
  • Agent configuration

Authentication: Client key (X-Muxi-Client-Key) or Admin key (X-Muxi-Admin-Key)

Browser usage: The TypeScript SDK's FormationClient works directly in browsers. Use the clientKey for browser apps - it's safe to expose and has limited permissions. Keep the adminKey server-side only.

ServerClient

For managing the MUXI server:

  • Deploy formations
  • Start/stop/restart formations
  • List formations
  • Server health & logs

Authentication: HMAC signature (key_id + secret_key)

Common Operations

Chat (Streaming)

for event in formation.chat_stream({"message": "Hello!"}, user_id="user_123"):
    if event.get("type") == "text":
        print(event.get("text"), end="")
    elif event.get("type") == "done":
        break
for await (const chunk of await formation.chatStream({ message: "Hello!" }, "user_123")) {
  if (chunk.type === "text") process.stdout.write(chunk.text);
  if (chunk.type === "done") break;
}
stream, errs := client.ChatStream(ctx, &muxi.ChatRequest{Message: "Hello!", UserID: "user_123"})
for chunk := range stream {
    if chunk.Type == "text" {
        fmt.Print(chunk.Text)
    }
}

Memory

# Get memories
memories = formation.get_memories(user_id="user_123")

# Add memory (user_id, mem_type, detail)
formation.add_memory(user_id="user_123", mem_type="preference", detail="User prefers Python")

# Clear buffer
formation.clear_user_buffer(user_id="user_123")
// Get memories
const memories = await formation.getMemories("user_123");

// Add memory (userId, type, detail)
await formation.addMemory("user_123", "preference", "User prefers TypeScript");

// Clear buffer
await formation.clearUserBuffer("user_123");
// Get memories
memories, _ := client.GetMemories(ctx, "user_123")

// Add memory (ctx, userId, type, detail)
client.AddMemory(ctx, "user_123", "preference", "User prefers Go")

// Clear buffer
client.ClearUserBuffer(ctx, "user_123")

Server Management

from muxi import ServerClient

server = ServerClient(
    url="http://localhost:7890",
    key_id="muxi_pk_...",
    secret_key="muxi_sk_...",
)

# Deploy
server.deploy_formation(bundle_path="my-bot.tar.gz")

# List formations
formations = server.list_formations()

# Stop/start/restart
server.stop_formation(formation_id="my-bot")
server.start_formation(formation_id="my-bot")
import { ServerClient } from "@muxi-ai/muxi-typescript";

const server = new ServerClient({
  url: "http://localhost:7890",
  keyId: "muxi_pk_...",
  secretKey: "muxi_sk_...",
});

// Deploy
await server.deployFormation({ bundlePath: "my-bot.tar.gz" });

// List formations
const formations = await server.listFormations();

// Stop/start/restart
await server.stopFormation("my-bot");
await server.startFormation("my-bot");
server := muxi.NewServerClient(&muxi.ServerConfig{
    URL:       "http://localhost:7890",
    KeyID:     "muxi_pk_...",
    SecretKey: "muxi_sk_...",
})

// Deploy
server.DeployFormation(ctx, &muxi.DeployRequest{BundlePath: "my-bot.tar.gz"})

// List formations
formations, _ := server.ListFormations(ctx)

// Stop/start/restart
server.StopFormation(ctx, "my-bot")
server.StartFormation(ctx, "my-bot")

Multi-Identity Users

Link multiple identifiers (email, Slack ID, etc.) to a single user:

# Link email, Slack ID, and internal ID to same user
result = formation.associate_user_identifiers(
    identifiers=[
        "alice@email.com",
        {"identifier": "U12345ABC", "type": "slack"},
        ("user_123", "internal"),
    ],
    user_id=None,  # Create new user (or pass existing user ID)
)
print(f"User ID: {result['muxi_user_id']}")

# Now all identifiers resolve to the same user
formation.chat("Hello", user_id="alice@email.com")  # Same user
formation.chat("Hello", user_id="U12345ABC")        # Same user
formation.chat("Hello", user_id="user_123")         # Same user
// Link identifiers to same user
const result = await formation.associateUserIdentifiers({
  identifiers: [
    "alice@email.com",
    { identifier: "U12345ABC", type: "slack" },
  ],
  userId: null,  // Create new user
});
console.log(User ID: ${result.muxiUserId});
result, _ := client.AssociateUserIdentifiers(ctx, &muxi.AssociateRequest{
    Identifiers: []interface{}{
        "alice@email.com",
        muxi.TypedIdentifier{Identifier: "U12345ABC", Type: "slack"},
    },
    UserID: nil,
})
fmt.Printf("User ID: %s
", result.MuxiUserID)

Why? Users interact via multiple channels (Slack, email, web). Multi-identity ensures they get the same memory and context everywhere.

Learn more: Multi-Identity Users โ†’

Session Restore

Restore conversation history from your external storage:

# Restore session from your database
messages = your_db.get_messages(session_id="sess_abc123")

formation.restore_session(
    session_id="sess_abc123",
    messages=[
        {"role": "user", "content": "Hello", "timestamp": "..."},
        {"role": "assistant", "content": "Hi!", "timestamp": "..."},
    ],
    user_id="alice@email.com"
)

# User continues with full context
await formation.restoreSession({
  sessionId: "sess_abc123",
  messages: [
    { role: "user", content: "Hello", timestamp: "..." },
    { role: "assistant", content: "Hi!", timestamp: "..." },
  ],
  userId: "alice@email.com",
});
client.RestoreSession(ctx, &muxi.RestoreRequest{
    SessionID: "sess_abc123",
    Messages: []muxi.Message{
        {Role: "user", Content: "Hello", Timestamp: "..."},
        {Role: "assistant", Content: "Hi!", Timestamp: "..."},
    },
    UserID: "alice@email.com",
})

Why? MUXI's buffer is ephemeral. For persistent chat history (like ChatGPT's sidebar), persist messages yourself and restore when users return.

Learn more: Sessions โ†’

Error Handling

All SDKs provide typed errors:

Error Meaning
AuthenticationError Invalid API key
AuthorizationError Insufficient permissions
NotFoundError Resource doesn't exist
ValidationError Invalid request data
RateLimitError Too many requests
ServerError Server-side error
ConnectionError Network issue
from muxi import MuxiError, AuthenticationError, RateLimitError

try:
    response = formation.chat_stream({"message": "Hello!"}, user_id="user_123")
except AuthenticationError:
    print("Invalid API key")
except RateLimitError as e:
    print(f"Rate limited, retry after {e.retry_after}s")
except MuxiError as e:
    print(f"Error: {e.code} - {e.message}")
import { MuxiError, AuthenticationError, RateLimitError } from "@muxi-ai/muxi-typescript";

try {
  await formation.chatStream({ message: "Hello!" }, "user_123");
} catch (err) {
  if (err instanceof AuthenticationError) {
    console.log("Invalid API key");
  } else if (err instanceof RateLimitError) {
    console.log(Rate limited, retry after ${err.retryAfter}s);
  } else if (err instanceof MuxiError) {
    console.log(Error: ${err.code} - ${err.message});
  }
}
resp, err := client.Chat(ctx, &muxi.ChatRequest{Message: "Hello!", UserID: "u1"})
if err != nil {
    var authErr *muxi.AuthenticationError
    var rateLimit *muxi.RateLimitError

    switch {
    case errors.As(err, &authErr):
        log.Println("Invalid API key")
    case errors.As(err, &rateLimit):
        log.Printf("Rate limited, retry after %d seconds", rateLimit.RetryAfter)
    default:
        log.Fatal(err)
    }
}

Configuration

Setting Python TypeScript Go Default
Timeout timeout=30 timeout: 30000 30s built-in 30s
Retries max_retries=3 maxRetries: 3 3 built-in 3
Debug debug=True debug: true - Off

Learn More

Reference: