Python SDK that fully drives the Mux-Swarm engine binary (stdio NDJSON + WebSocket).
Project description
muxswarm — Python SDK for the Mux-Swarm engine
A Python library that fully drives the Mux-Swarm engine binary — a .NET CLI-native agentic swarm runtime. The SDK runs the compiled engine as a subprocess (or talks to its WebSocket bridge) and speaks the engine's NDJSON protocol, giving you typed events, streaming, interactive sessions, headless one-shots, prefilled-config bootstrapping, and binary management.
Status. The full stack is implemented and verified offline against a protocol mock (
tests/, 9/9) and live against a real downloaded engine binary. The SDK wires and validates the engine's entire CLI + slash-command surface (seemuxswarm.commands) and drives the entireConfig.json/Swarm.jsonschema via a fluent editor (muxswarm.manage.MuxConfig, 1:1 with the engine's C# config classes). Core SDK is pure stdlib; the WebSocket adapter needs the optionalwebsocketsextra and the guarded shell needsmcpat runtime.
Why this exists
Mux-Swarm runs as a console app. With --stdio it emits NDJSON (one JSON object per line) on stdout and reads plain text lines on stdin. With --serve <port> it bridges that same NDJSON contract over a WebSocket. This SDK wraps that protocol so you can drive the whole runtime from Python without touching the CLI by hand.
Install
pip install -e . # core SDK (stdlib only)
pip install -e ".[ws]" # + WebSocket adapter (websockets)
pip install -e ".[dev]" # + pytest for the test suite
You also need the engine binary. Either:
- set
MUXSWARM_BINARY=/path/to/MuxSwarm(.exe), or - pass
binary="..."toMuxSwarm(...), or - let the SDK fetch a release:
MuxSwarm(..., auto_download=True, install_dir="./engine").
Release assets (from the GitHub Releases tab) are per-platform:
mux-swarm-win-x64.zip, mux-swarm-linux-x64.tar.gz, mux-swarm-osx-x64.tar.gz, mux-swarm-osx-arm64.tar.gz. Each zip/tarball contains the exe plus the sibling dirs it needs (Configs/, Prompts/, Context/, Sessions/, Skills/, Runtime/).
Quick start (auth + prefilled config → running)
import asyncio
from muxswarm import MuxSwarm
async def main():
mux = MuxSwarm.quickstart(
endpoint="https://openrouter.ai/api/v1",
api_key="sk-...", # raw key (in-process only) ...
# api_key_env_var="OPENROUTER_API_KEY", # ... or reference an env var
work_dir="./.mux", # writes Configs/Config.json + Swarm.json
sandbox_path="./.mux/sandbox",
binary="/path/to/MuxSwarm.exe", # or auto_download=True
)
result = await mux.run_goal("Summarize this repo", mode="agent", agent="MuxAgent")
print(result.final_summary or result.streamed_text)
asyncio.run(main())
quickstart writes a complete Config.json + Swarm.json with setupCompleted=true, an enabled provider, and model IDs auto-resolved from the endpoint (OpenRouter / Anthropic / OpenAI / Ollama), then launches the engine with --cfg/--swarmcfg. No interactive engine setup is triggered — that's the "natural code-side get-up-and-running" path.
Even shorter:
from muxswarm import run
res = await run("What is 2+2?", endpoint="https://openrouter.ai/api/v1",
api_key="sk-...", binary="/path/MuxSwarm.exe", mode="agent", agent="MuxAgent")
Models
quickstart resolves model IDs from the endpoint (OpenRouter / Anthropic / OpenAI / Ollama). For anything else — including a local OpenAI-compatible proxy (e.g. CLIProxyAPI on 127.0.0.1) — the endpoint default is the ollama placeholder llama3, which most providers reject (HTTP 502: unknown provider for model llama3). Set the model explicitly:
# simplest: one id for the entire swarm (orchestrator + all agents + light + compaction)
mux = MuxSwarm.quickstart(endpoint, api_key_env_var="CLI_PROXY_TOKEN", model="claude-sonnet-4-6")
# per role (orchestrator / agent / light / compaction)
mux = MuxSwarm.quickstart(endpoint, ..., models={
"orchestrator": "claude-sonnet-4-6",
"agent": "claude-sonnet-4-6",
"light": "claude-haiku-4-5-20251001",
"compaction": "claude-haiku-4-5-20251001",
})
# per named agent (mix with role keys; a per-agent entry wins for that agent)
mux = MuxSwarm.quickstart(endpoint, ..., models={
"agent": "claude-sonnet-4-6", # base for every agent
"CodeAgent": "claude-opus-4-6", # ...except CodeAgent
"WebAgent": "gpt-5.2-2025-12-11", # ...and WebAgent
})
Precedence (low → high): endpoint default → model= shorthand → models= role key / per-agent-name. If you ship a placeholder id (llama3/model-id-here) with no override, the SDK emits a UserWarning pointing you here. model=/models= are accepted by quickstart, run(), build_quickstart_config, and write_quickstart.
Driving the config (model swap · agent creation · whole-schema edits)
MuxConfig is a fluent, round-trip-safe editor over the (Config.json, Swarm.json)
pair. Its dataclasses map the engine's C# config classes 1:1 (every
[JsonPropertyName]), so it can drive the entire config surface
deterministically — which is the reliable alternative to the racy interactive
REPL config commands (/setmodel, /swap, /provider, /maxp, …; see below).
from muxswarm import MuxSwarm, MuxConfig
mux = MuxSwarm.quickstart(endpoint, api_key_env_var="CLI_PROXY_TOKEN",
model="claude-sonnet-4-6", work_dir="./.mux")
# edit the live config this client points at, then it applies on next launch
(mux.config()
.set_all_models("claude-sonnet-4-6") # every slot at once
.set_model("compaction", "claude-haiku-4-5-20251001") # one slot
.set_agent_model("WebAgent", "gpt-5.2-2025-12-11") # one named agent
.add_agent("DataAgent", # create a new agent
description="Crunches CSV/parquet and emits summaries.",
model="claude-sonnet-4-6",
mcp_servers=["Filesystem", "ReplShellMcp"])
.set_single_agent("CodeAgent") # bind the /agent loop
.save())
Model swapping
cfg = MuxConfig.from_dir("./.mux") # or .from_paths(cfg, swarm)
cfg.get_models() # {orchestrator, compaction, vision,
# singleAgent, WebAgent, CodeAgent, ...}
cfg.set_model("orchestrator", "id") # role or agent-name slot
cfg.set_models({"orchestrator": "a", "CodeAgent": "b"})
cfg.set_all_models("id", include_light=True)
cfg.set_model_opts("orchestrator", {"temperature": 0.2, "reasoning": {"effort": "high"}})
cfg.save()
Agent creation / management
cfg.add_agent("DataAgent", description="...", model="...",
prompt_path="Prompts/Agents/dataagent.md", # default: <name>.md
can_delegate=False, mcp_servers=["Filesystem"],
tool_patterns=["*"], skill_patterns=["*"])
cfg.update_agent("DataAgent", description="v2", model="...")
cfg.remove_agent("DataAgent")
cfg.list_agents(); cfg.get_agent("CodeAgent")
cfg.set_orchestrator(model="...", prompt_path="...")
Bind the single-agent loop (the reliable /swap)
The interactive /swap picker is a racy stdin handshake over piped I/O. Instead,
set Swarm.json.singleAgent — the engine reads it directly, so the interactive
/agent loop runs exactly that agent:
cfg.set_single_agent("CodeAgent").save() # copies the CodeAgent def into singleAgent
# then: async with mux.session(mode="agent") as s: await s.ask("...") # runs CodeAgent
Provider · MCP servers · paths · limits · security
cfg.set_provider("https://api.openai.com/v1", api_key_env_var="OPENAI_API_KEY")
cfg.add_server("ChromaDB", command="uvx", args=["chroma-mcp"])
cfg.enable_server("Playwright"); cfg.disable_server("ChromaDB"); cfg.list_servers()
cfg.set_allowed_paths(["/work"]); cfg.add_allowed_path("/extra"); cfg.set_sandbox("/work")
cfg.set_user_info(name="Jane", role="SRE", timezone="America/Chicago")
cfg.set_execution_limits(maxOrchestratorIterations=20, activityTimeoutSeconds=600)
cfg.set_docker_exec(True); cfg.set_telemetry(enabled=True, endpoint="http://otel:4317")
cfg.set_security("trusted") # rewrites only managed shell/browser + CodeAgent
cfg.save()
Every mutator returns self (chainable). The underlying typed models
(AppConfig, SwarmConfig, AgentConfig, ModelOpts, …) are exported too if
you prefer to build/serialize the pair by hand.
Headless one-shot (-p style)
Like claude -p, you can fire a single goal and get a structured result back without ever opening an interactive session. run_goal is the full headless driver: it maps every launch-relevant engine flag to a typed keyword and validates it against the engine surface before spawning.
res = await mux.run_goal(
"Audit the logs in ./var and summarize anomalies",
mode="pswarm", # --parallel
max_parallelism=8, # --max-parallelism 8
goal_id="nightly-audit", # --goal-id nightly-audit
continuous=True, # --continuous
min_delay=600, # --min-delay 600
watchdog=True, # --watchdog
mcp_strict=False, # --mcp-strict false
plan=False,
timeout=900,
)
print(res.ok, res.exit_code, res.final_summary)
Every one of those kwargs is validated; a bad value raises muxswarm.commands.CliValidationError before the engine launches. For a fully dynamic call use the escape hatch:
res = await mux.run_cli(goal="do it", parallel=True, delimiter="---", watchdog=True)
run_cli(**flags) accepts any snake_case param from CLI_FLAGS and validates the whole argv via build_cli_args. Lifecycle flags get dedicated methods instead (below).
Lifecycle launches
proc = await mux.serve(6723) # --serve 6723 (long-lived; NDJSON over ws://.../ws)
proc = await mux.daemon() # --daemon (file watch / cron / status triggers)
rc = await mux.register_service() # --register (OS service, then exits)
rc = await mux.remove_service() # --remove
await proc.exit() # stop a long-lived launch
Package layout
| Module | Purpose |
|---|---|
muxswarm.events |
NDJSON event model: MuxEvent, EventType, RequestKind, parse_line, request_kind. |
muxswarm.driver |
Low-level async stdio driver: MuxProcess (spawn --stdio, stream events, auto-answer blocking requests, cancel, exit). |
muxswarm.client |
High-level ergonomic client: MuxSwarm, MuxSession, RunResult, module-level run(). |
muxswarm.config |
Typed Config.json/Swarm.json models + setup helpers (build_quickstart_config, write_quickstart, resolve_model_ids). Dataclasses map the engine C# config classes 1:1. |
muxswarm.manage |
Fluent whole-config editor MuxConfig: model swap, agent create/update/remove, single-agent bind, provider/MCP/paths/limits/security. |
muxswarm.binary |
Locate or download the engine release binary + dir structure (find_binary, ensure_binary, download_release, BinaryManager). |
muxswarm.commands |
Authoritative, validated map of the entire engine surface: CLI_FLAGS, SLASH_COMMANDS, build_cli_args, validate_cli_args, is_known_slash, normalize_slash. |
muxswarm.guard |
Claude-Code-style guarded shell + security policy (ShellPolicy, DEFAULT_DENY_COMMANDS). |
muxswarm.ws |
Optional WebSocket adapter + REST helpers for --serve mode (MuxWebSocketClient, MuxRestClient). Needs muxswarm[ws]. |
The protocol, in brief
Outbound events ({"type": "...", ...}): splash, info/success/warning/error/debug/body/markup, prompt, step, rule, panel, table, stream / stream_end, thinking_start/update/end, agent_turn_start/end, delegation, tool_call, tool_result, task_start/done, task_complete, user_input.
Blocking request events — the engine waits for a stdin reply:
| event | reply |
|---|---|
input_request |
a line of text (empty → engine default) |
confirm_request |
y / n |
select_request |
1-based index |
multiselect_request |
comma-separated 1-based indices |
The driver auto-answers these via a pluggable request_handler (default: accept/yes/first) so the engine never deadlocks. Supply your own handler to take control.
Inbound (you → engine): slash command to enter a mode (/agent, /swarm, /pswarm, /stateless), then plain text per turn. __CANCEL__ cancels the active turn; /exit or EOF shuts down.
Modes (run_goal(mode=...)): swarm (default, full orchestration), pswarm (--parallel), agent (--agent <name>), stateless (one-shot interactive).
Full CLI flag reference
Mirrored 1:1 from the engine's argument parser (muxswarm.commands.CLI_FLAGS, engine v0.10.3). All are validated by the SDK. * = changes process lifecycle (handled by a dedicated method, not build_cli_args).
| Flag (aliases) | Value | Description |
|---|---|---|
--goal |
text | file | Explicit goal (inline or a path to a goal file). |
--goal-id |
str | Attach a goal/session id (groups continuous runs). |
--continuous |
— | Continuous autonomous mode (loop the goal). |
--parallel |
— | Parallel swarm (concurrent batch dispatch). |
--max-parallelism |
uint (4) | Max concurrent agent tasks in parallel mode. |
--agent |
str | Single-agent mode with the named swarm agent. |
--plan |
— | Plan mode (confirm before executing). |
--workflow (--wf) |
file | Run a deterministic, replayable workflow file. |
--provider |
str | Set the active LLM provider on launch. |
--model |
str | Override the model id for this run. |
--min-delay |
uint (300) | Min seconds between continuous loops. |
--persist-interval |
uint (60) | Persist the session every N seconds. |
--session-retention |
uint (10) | Keep the last N sessions. |
--stdio |
— | NDJSON output / line stdin (driver injects this). |
--delimiter |
str | Multi-line input delimiter (e.g. ---). |
--watchdog |
— | External watchdog (auto-restart on crash). |
--mcp-strict |
true|false (true) | Require all MCP servers to connect. |
--docker-exec |
true|false | Route execution through docker skills. |
--prod |
— | Production mode. |
--report * |
str (optional) | Generate audit report(s) and exit. |
--cfg |
path | Override Config.json path (wired by quickstart). |
--swarmcfg |
path | Override Swarm.json path. |
--clear |
— | Clear the screen and continue (interactive). |
--serve * |
port (6723) | Embedded web UI / WebSocket bridge — mux.serve(). |
--daemon * |
— | Daemon mode (file watch / cron / status) — mux.daemon(). |
--register * |
— | Register as an OS service, then exit — mux.register_service(). |
--remove * |
— | Unregister the OS service, then exit — mux.remove_service(). |
--help (-h) * |
— | Print help and exit (not driven by the SDK). |
Full slash-command reference
Mirrored 1:1 from the engine's interactive switch + /help text (muxswarm.commands.SLASH_COMMANDS). Send any of these in a live session via sess.send("/cmd") or proc.send_command("/cmd") — aliases are auto-normalized and unknown commands warn.
| Command (aliases) | Description |
|---|---|
/swarm |
Interactive multi-agent swarm loop. |
/pswarm |
Parallel swarm (concurrent batch dispatch). |
/agent |
Interactive single-agent loop (/agent <name>). |
/onboard |
Create/update operator profile (BRAIN.md + MEMORY.md). |
/stateless |
Stateless single-agent loop (one-off tasks). |
/subagents (/sub) |
Enable sub-agent delegation in the single-agent loop. |
/parasubagents (/psub) |
Enable parallel sub-agent delegation. |
/addcontext |
Configure context injected into each agent. |
/plan |
Toggle plan mode. |
/continuous (/cont) |
Toggle continued autonomous execution. |
/workflow |
Run a workflow file (/workflow <file>). |
/resume |
Resume a previous single-agent session. |
/compact |
Compact current session context (single-agent loops). |
/maxp ‡ |
Set max parallel agents (/maxp <n>). |
/model |
View current swarm models. |
/setmodel ‡ |
Change a model for an agent/orchestrator/compaction. |
/swap ‡ |
Swap the active single-agent. |
/provider ‡ |
View or switch the active LLM provider. |
/limits † |
Display execution limits. |
/tools † |
List MCP tools across enabled servers. |
/skills † |
List local skills. |
/memory † |
View the knowledge graph. |
/sessions † |
List saved sessions. |
/dockerexec |
Toggle Docker execution mode. |
/delimiter |
Toggle the multi-line input delimiter. |
/dbg / /nodbg |
Enable / disable tool-call output (stdio only). |
/disabletools |
Toggle disabling of tool calls. |
/setup |
Run initial setup / reconfigure. |
/reloadskills |
Refresh the skills directory. |
/refresh |
Full system refresh (config + MCP + skills). |
/report |
Generate session audit reports (/report [id]). |
/clear |
Clear the screen. |
/status † |
System status: provider, models, tools, skills, sessions. |
/qc / /qm |
Quick-config / quick-model helpers. |
/help |
Show the command reference. |
/exit |
Exit Mux-Swarm (driver sends this on graceful shutdown). |
† Read-only info commands are exposed as typed MuxSwarm methods that drive a
transient outer-REPL process and return the resulting events:
list_tools(), list_skills(), list_sessions(), status(), show_memory(),
show_models(), show_limits(). (list_tools/status take warmup= — default
6s — because MCP servers connect in the background after boot.) These run at the
outer REPL only; inside an /agent//swarm loop a line is consumed by the
agent as a turn.
‡ Interactive config commands emit their own input_request sub-prompts and
are racy to drive over piped stdio — they are not orchestrated. Use
MuxConfig (above) for deterministic equivalents: /setmodel→set_model,
/swap→set_single_agent, /provider→set_provider, /maxp→
set_execution_limits/config, /dockerexec→set_docker_exec,
/delimiter→--delimiter, /onboard//setup//addcontext→config-side.
Programmatic access:
from muxswarm import CLI_FLAGS, SLASH_COMMANDS, build_cli_args, validate_cli_args, normalize_slash
normalize_slash("/cont") # -> "/continuous"
build_cli_args(goal="x", parallel=True, max_parallelism=8) # validated argv tail
Security profiles
quickstart / run generate configs under a security profile (default standard). The shell tool is a Claude-Code-style guarded MCP server: a command denylist plus path containment to the sandbox allow-list.
| profile | shell | python exec | browser | code agent |
|---|---|---|---|---|
locked |
none | off | no | no |
standard (default) |
guarded | off | yes | yes |
trusted |
guarded | subprocess (cwd-pinned) | yes | yes |
yolo |
raw (mcp-async-repl) |
in-process | yes | yes |
mux = MuxSwarm.quickstart(endpoint, api_key="...", security="trusted")
# granular overrides:
mux = MuxSwarm.quickstart(endpoint, api_key="...", security="standard",
allow_browser=False, deny_commands=["npm"], allow_commands=["git","ls"])
Caveat: the guard is strict on the shell tool surface (denylist + path containment), not a kernel sandbox.
trustedPython exec is a cwd-pinned subprocess, still not a hard jail. For true isolation, run the engine in a container. The guarded shell launches viauvx --with mcp, so the engine host needsuv/uvx; install the SDK with theguardruntime dep available if you shipshell_guard.pyyourself.
API highlights
# headless one-shot -> aggregated result
res = await mux.run_goal(goal, mode="swarm", plan=False, continuous=False,
on_event=lambda ev: ...) # RunResult: .ok .exit_code
# .final_summary .streamed_text .errors
# multi-turn interactive session (runs the configured singleAgent)
async with mux.session(mode="agent") as sess:
events = await sess.ask("do a thing") # list[MuxEvent] for that turn
await sess.send("/plan") # raw line if you need it
await sess.cancel() # __CANCEL__
# drive the whole config; read-only info commands at the outer REPL
mux.config().set_single_agent("CodeAgent").set_all_models("claude-sonnet-4-6").save()
tools = await mux.list_tools() # -> panel event w/ tool list
# low-level, full control
from muxswarm import MuxProcess
async with MuxProcess(binary_path, args=[...], cfg=..., swarmcfg=...) as p:
await p.send_command("/agent")
await p.send("goal")
async for ev in p.events(): ...
Examples
Runnable scripts in examples/:
01_quickstart.py— auth + prefilled config → running.02_streaming_events.py— classify every event type live.03_interactive_session.py— multi-turn session + custom request handler.04_prefilled_config_and_auth.py— build/tweak the config pair explicitly.05_headless_batch.py— parallel swarm + therun()one-liner.06_websocket_serve.py— drive a--serveengine over WebSocket + REST file ops.
Tests
Fully offline (no binary, no key) via a protocol mock:
python tests/test_sdk.py # plain runner
pytest tests/test_sdk.py # or under pytest
Covers event parsing, quickstart config generation, the stdio driver end-to-end (including blocking-request auto-reply), and multi-turn interactive sessions. The mock (tests/mock_engine.py) re-implements the engine's NDJSON handshake.
Note: the mock is a Python script, so tests build the driver with
stdio_flag=False(so--stdioisn't injected before the script path). The real engine uses the defaultstdio_flag=True.
Notes & known follow-ups
- Full surface, validated:
muxswarm.commandsmirrors the engine'sParseArgs+ slash switch +/helptext 1:1.run_goal/run_clivalidate every flag before launch;send_commandnormalizes slash aliases and warns on unknown commands. - Whole config, deterministically:
muxswarm.manage.MuxConfig(+ the typedmuxswarm.configmodels) map the engine's C# config classes 1:1 and drive the entireConfig.json/Swarm.jsonschema. Prefer it over the interactive/setmodel//swap//provider//maxpcommands, which emit sub-prompts that are racy over piped stdio. - Interactive single-agent selection:
session(mode="agent")runs whateverSwarm.json.singleAgentnames. Bind it withmux.config().set_single_agent("CodeAgent").save()—session(agent=...)does NOT drive the racy/swap. - Setup surface:
setupCompleted=trueinConfig.jsonis the gate that makes the engine skip its interactive 7-step setup. The config layer always emits it, so a prefilled cfg pair runs immediately. - Auth: a raw key is never written to config — it's passed in the launch env as
MUXSWARM_SESSION_API_KEY(matching the engine's own behavior); or reference an existing env var by name viaapi_key_env_var. - Binary resolution gotcha:
find_binary/ensure_binaryresolve an installed engine onPATH/MUXSWARM_BINARYfirst. Passbinary=explicitly to force a specific exe. - See
SCAN_REPORT.mdfor the full engine interface map this SDK was built against.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file muxswarm-0.3.2.tar.gz.
File metadata
- Download URL: muxswarm-0.3.2.tar.gz
- Upload date:
- Size: 104.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
136688e1c9d45ed27c64398feb72e193536300d6c5f0c4fe689d6bdd40f52f3c
|
|
| MD5 |
ac9a160cd1e512ff5cbccb92937f7ea6
|
|
| BLAKE2b-256 |
b88d77d4a1c01a8507f5b874add412fcbb02e3b04ab46a315a3796a7bbe1c069
|
File details
Details for the file muxswarm-0.3.2-py3-none-any.whl.
File metadata
- Download URL: muxswarm-0.3.2-py3-none-any.whl
- Upload date:
- Size: 97.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9713b279a5bddb1723660d4e63d492a8340bc8eaddd50e6561d00b8cdbf59b0
|
|
| MD5 |
b6a7b486a333410570a4a34846d92e4b
|
|
| BLAKE2b-256 |
236746bbfef4c1142e9571aaf2ed31a2ee8f531045b56b395da38201af57d6cd
|