Metadata-Version: 2.5
Name: mutagent-sdk
Version: 0.5.0
Summary: Python SDK for the MutagenT Server API Documentation
License: Apache-2.0
License-File: LICENSE
Requires-Python: >=3.12
Requires-Dist: httpx>=0.27.0
Requires-Dist: pydantic>=2.0.0
Requires-Dist: typing-extensions>=4.0.0
Provides-Extra: dev
Requires-Dist: mypy>=1.9.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest-httpx>=0.30.0; extra == 'dev'
Requires-Dist: pytest>=7.0.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Description-Content-Type: text/markdown

![MutagenT](../assets/brand/mutagent-banner.svg)

# MutagenT Python SDK

<p align="center">
  <a href="https://pypi.org/project/mutagent-sdk/"><img src="https://img.shields.io/pypi/v/mutagent-sdk?style=for-the-badge&color=3776AB&logo=pypi&logoColor=white" alt="PyPI"></a>
  <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/Python-3.12+-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python 3.12+"></a>
  <a href="https://www.python-httpx.org/"><img src="https://img.shields.io/badge/httpx-0.27+-yellow?style=for-the-badge" alt="httpx"></a>
  <a href="https://docs.pydantic.dev/"><img src="https://img.shields.io/badge/Pydantic-2.0+-e92063?style=for-the-badge&logo=pydantic&logoColor=white" alt="Pydantic v2"></a>
  <a href="#license"><img src="https://img.shields.io/badge/License-Apache_2.0-blue?style=for-the-badge" alt="Apache 2.0"></a>
</p>

<p align="center">
  <em>The Python client for the MutagenT platform API.</em>
</p>

**Status:** `mutagent-sdk` is `0.4.1` on PyPI, generated from `openapi.json` via `mutagent-xgen`. `Mutagent` (sync) and `AsyncMutagent` (async) expose the same 13 groups. Streaming (`sandbox.stream_sandbox`, `helix_sessions.stream_helix_session`) is known-limited — see [Reference § Known limitations](#known-limitations).

---

## Context

`mutagent-sdk` is the official Python client for the MutagenT platform API, generated from the platform's OpenAPI document by [mutagent-xgen](../mutagent-xgen/), MutagenT's in-house SDK generator. A Pydantic v2 model covers every request/response body in the API spec. See the [root README](../README.md) for where this SDK sits relative to the platform API and the other clients (TypeScript SDK, CLI).

## Concepts

- **Groups** — one namespace per API tag (`client.agents`, `client.sandbox`, …), each with a sync method on `Mutagent` and an async form on `AsyncMutagent`. See the table in [Components](#components).
- **Models** — a Pydantic v2 model for every request/response body; a `...TypedDict` counterpart accepts the same fields as a plain `dict`.
- **Two-credential model** — a platform `api_key` for most groups, versus a short-lived sandbox bearer token for `sandbox`, `environments` and `helix_sessions`; see [Cloud Sandboxes, Environments and Helix Sessions](#cloud-sandboxes-environments-and-helix-sessions).
- **HTTPS by default** — a plain-HTTP `server_url` is refused for a non-local host unless `allow_insecure_http=True`.
- **Request bodies** — a method that sends a body accepts either a model instance or a plain `dict` with the API's camelCase wire names.
- **`SDKError`** — the uniform exception for any non-2xx response, carrying `status_code`, `body` and the raw `httpx.Response`.

## Components

```
mutagent-sdk-python/
├── openapi.json          # fetched from a running server via scripts/dump-spec.sh
├── .mutagen/               # mutagent-xgen config (gen.yaml, gen.lock)
├── src/mutagent/
│   ├── client.py           # Mutagent (sync)
│   ├── async_client.py     # AsyncMutagent
│   ├── <group>.py           # one file per group (agents.py, sandbox.py, …)
│   ├── models/               # [generated] Pydantic models
│   ├── lib/                   # [generated] security, HTTP helpers
│   └── _funcs/                 # [generated] per-operation implementations
├── docs/sdks/                # [generated] one page per group
├── tests/                     # pytest suite
└── README.md, CHANGELOG.md, CLAUDE.md   # hand-maintained (safe to edit; see CLAUDE.md)
```

| Command | What it does |
| --- | --- |
| `pip install mutagent-sdk` | Install from PyPI (import path is `mutagent`) |
| `pip install -e ".[dev]"` | Install with the `dev` extras group: pytest, pytest-asyncio, pytest-httpx, ruff, mypy |
| `pytest tests/` | Run the test suite |
| `bash scripts/dump-spec.sh` | Fetch the latest OpenAPI spec from a running server (see the provenance warning in [CLAUDE.md](CLAUDE.md)) |
| `mutagent-xgen run` | Regenerate the SDK from `openapi.json` |

### Quick start

```python
from mutagent import Mutagent

client = Mutagent(api_key="YOUR_API_KEY")

agents = client.agents.list_agents(limit=10)
print(f"Found {len(agents['data'])} agents")
```

`server_url` defaults to `MUTAGENT_SERVER_URL`, and to `https://api.mutagent.io` when that is unset (see [Configuration](#configuration)); pass `server_url="http://localhost:3003"` for a local server. Set `MUTAGENT_API_KEY` in your environment to omit `api_key` entirely:

```python
from mutagent import Mutagent

client = Mutagent()  # reads MUTAGENT_API_KEY
```

Async client:

```python
import asyncio
from mutagent import AsyncMutagent

async def main():
    async with AsyncMutagent(api_key="YOUR_API_KEY") as client:
        profile = await client.user_profile.get_profile()
        print(profile)
        print(await client.workspaces.list_workspaces())

asyncio.run(main())
```

A `dict` request body is sent as written (camelCase keys); a model instance accepts either the snake_case field name or the camelCase alias:

```python
agent = client.agents.create_agent({
    "name": "Support Agent",
    "slug": "support-agent",
    "systemPrompt": "You answer customer support questions.",
})
```

A non-2xx response raises `SDKError`:

```python
from mutagent import Mutagent, SDKError

client = Mutagent()
try:
    client.agents.get_agent(404)
except SDKError as e:
    print(f"HTTP {e.status_code}: {e.body}")
```

### Groups

Per-method reference lives in [`docs/sdks/`](./docs/sdks/), one page per group:

| Group | Reference |
|-------|---------|
| `client.user_profile` | [docs/sdks/user_profile.md](docs/sdks/user_profile.md) |
| `client.organizations` | [docs/sdks/organizations.md](docs/sdks/organizations.md) |
| `client.organization_members` | [docs/sdks/organization_members.md](docs/sdks/organization_members.md) |
| `client.workspaces` | [docs/sdks/workspaces.md](docs/sdks/workspaces.md) |
| `client.workspace_members` | [docs/sdks/workspace_members.md](docs/sdks/workspace_members.md) |
| `client.invitations` | [docs/sdks/invitations.md](docs/sdks/invitations.md) |
| `client.agents` | [docs/sdks/agents.md](docs/sdks/agents.md) |
| `client.llm_providers` | [docs/sdks/llm_providers.md](docs/sdks/llm_providers.md) |
| `client.managed_agents` | [docs/sdks/managed_agents.md](docs/sdks/managed_agents.md) |
| `client.traces` | [docs/sdks/traces.md](docs/sdks/traces.md) |
| `client.sandbox` | [docs/sdks/sandbox.md](docs/sdks/sandbox.md) |
| `client.environments` | [docs/sdks/environments.md](docs/sdks/environments.md) |
| `client.helix_sessions` | [docs/sdks/helix_sessions.md](docs/sdks/helix_sessions.md) |

```python
# Organizations, members and invitations
members = client.organization_members.list_organization_members("<org-id>")
invitation = client.invitations.create_invitation("<org-id>", {"email": "dev@example.com", "role": "editor"})

# LLM providers
provider = client.llm_providers.create_provider_config({"name": "Anthropic", "provider": "anthropic", "apiKey": "<provider-key>"})
print(client.llm_providers.test_provider_config(provider["id"]))
```

### Cloud Sandboxes, Environments and Helix Sessions

`sandbox`, `environments` and `helix_sessions` call routes that accept only a short-lived sandbox token, sent as `Authorization: Bearer`. Exchange your API key for one via `sandbox.create_sandbox_token`, then pass the result to a second client as `bearer_token`. An explicit `bearer_token` wins over `MUTAGENT_API_KEY`; passing both `api_key` and `bearer_token` raises `ValueError`.

```python
from mutagent import Mutagent
from mutagent.models import HelixSignal, Mode

platform = Mutagent(api_key="YOUR_API_KEY")
token = platform.sandbox.create_sandbox_token({"workspaceId": "<workspace-id>"})

client = Mutagent(bearer_token=token["token"])
session = client.helix_sessions.launch_helix_session(body={"mode": Mode.INTERACTIVE})
client.helix_sessions.send_helix_session_input(
    session["reference"],
    {"line": '{"type": "prompt", "id": "p1", "message": "List the files"}'},
)
client.helix_sessions.signal_helix_session(session["reference"], {"signal": HelixSignal.SIGINT})
```

| Operation | Method | Route |
|-----------|--------|-------|
| List sandbox providers | `sandbox.list_sandbox_providers` | `GET /api/sandbox/providers` |
| Spawn a sandbox | `sandbox.create_sandbox` | `POST /api/sandbox` |
| Run a command in a sandbox | `sandbox.exec_sandbox_command` | `POST /api/sandbox/{id}/exec` |
| Watch a sandbox's output | `sandbox.stream_sandbox` | `GET /api/sandbox/{id}/stream` |
| Exchange an API key for a sandbox token | `sandbox.create_sandbox_token` | `POST /api/sandbox/token` |
| List workspace Environments | `environments.list_environments` | `GET /api/sandbox/environments` |
| Launch a Helix session | `helix_sessions.launch_helix_session` | `POST /api/sandbox/helix/sessions` |
| Send a JSON command to a session | `helix_sessions.send_helix_session_input` | `POST /api/sandbox/helix/sessions/{reference}/input` |
| Stream a session's output | `helix_sessions.stream_helix_session` | `GET /api/sandbox/helix/sessions/{reference}/stream` |

Full route list per group: the [Groups](#groups) reference pages above.

## Configuration

| Variable | Required | Default | Read at | Secret | Purpose |
| --- | --- | --- | --- | --- | --- |
| `MUTAGENT_API_KEY` | No (or pass `api_key=`) | none | `src/mutagent/lib/security.py:13,22,28` | Yes | Platform API key sent as `x-api-key` or `Authorization: Bearer` |
| `MUTAGENT_SERVER_URL` | No | `https://api.mutagent.io` | `src/mutagent/client.py:57`, `src/mutagent/async_client.py:45` | No | Base URL for platform API requests |

Both are user-set (this package owns no platform infrastructure). See [`.env.example`](.env.example) for placeholder values.

---

## Reference

### 0.4.0 breaking changes

`0.4.0` is the first published release to carry these changes. Code written against `0.1.1` needs the following (two versions sit between them on PyPI — `0.2.0` and `0.3.0` — both later builds of the same pre-rename client, so every rename here applies when upgrading from either; #2028 reset the repository's version to `0.2.0`, a number PyPI already held, so no Python release could publish until `0.4.0`).

**Client groups**

- `sandbox` holds sandbox providers, presets, the sandbox lifecycle, commands, the token exchange (`create_sandbox_token`) and the session routes addressed by sandbox id.
- New group `helix_sessions`: `launch_helix_session`, `list_helix_sessions`, `send_helix_session_input`, `close_helix_session_input`, `signal_helix_session`, `checkpoint_helix_session`, `list_helix_session_checkpoints`, `restore_helix_session`, `stream_helix_session`, `get_helix_models`, `set_helix_default_models`. These existed as `sandbox` methods in `0.1.1`.
- New group `environments`. In `0.1.1` its operations were methods of `sandbox`.
- `helix_agent_deployments` is renamed `managed_agents`; method names are unchanged.
- `provider_configs` is renamed `llm_providers`; its methods are renamed too (table below).
- `user_profile` keeps its name; two of its methods are renamed (table below).

<details><summary>39 renamed operations</summary>

| 0.1.1 | 0.4.0 |
|-------|-------|
| `sandbox.get_api_sandbox_providers` | `sandbox.list_sandbox_providers` |
| `sandbox.post_api_sandbox_preflight` | `sandbox.preflight_sandbox` |
| `sandbox.get_api_sandbox_presets` | `sandbox.list_sandbox_presets` |
| `sandbox.post_api_sandbox_run` | `sandbox.run_sandbox_command` |
| `sandbox.post_api_sandbox` | `sandbox.create_sandbox` |
| `sandbox.get_api_sandbox` | `sandbox.list_sandboxes` |
| `sandbox.get_api_sandbox_by_id` | `sandbox.get_sandbox` |
| `sandbox.delete_api_sandbox_by_id` | `sandbox.delete_sandbox` |
| `sandbox.get_api_sandbox_by_id_stream` | `sandbox.stream_sandbox` |
| `sandbox.post_api_sandbox_by_id_exec` | `sandbox.exec_sandbox_command` |
| `sandbox.post_api_sandbox_telemetry_v1_traces` | `sandbox.ingest_sandbox_traces` |
| `sandbox.get_api_sandbox_by_id_traces` | `sandbox.list_sandbox_spans` |
| `sandbox.post_api_sandbox_by_id_session` | `sandbox.start_sandbox_helix_session` |
| `sandbox.get_api_sandbox_helix_defaults` | `helix_sessions.get_helix_models` |
| `sandbox.put_api_sandbox_helix_defaults` | `helix_sessions.set_helix_default_models` |
| `sandbox.get_api_sandbox_by_id_sessions` | `sandbox.list_sandbox_helix_sessions` |
| `sandbox.post_api_sandbox_by_id_input` | `sandbox.send_sandbox_helix_session_input` |
| `sandbox.post_api_sandbox_by_id_signal` | `sandbox.signal_sandbox_helix_session` |
| `sandbox.post_api_sandbox_by_id_checkpoint` | `sandbox.checkpoint_sandbox_helix_session` |
| `sandbox.get_api_sandbox_by_id_checkpoints` | `sandbox.list_sandbox_checkpoints` |
| `sandbox.post_api_sandbox_by_id_restore` | `sandbox.restore_sandbox` |
| `sandbox.post_api_sandbox_token` | `sandbox.create_sandbox_token` |
| `sandbox.get_api_sandbox_environments` | `environments.list_environments` |
| `sandbox.get_api_sandbox_environments_by_name` | `environments.get_environment` |
| `sandbox.put_api_sandbox_environments_by_name` | `environments.replace_environment` |
| `sandbox.patch_api_sandbox_environments_by_name` | `environments.update_environment` |
| `sandbox.delete_api_sandbox_environments_by_name` | `environments.delete_environment` |
| `user_profile.list_sessions` | `user_profile.list_user_sessions` |
| `user_profile.delete_session` | `user_profile.revoke_user_session` |
| `sandbox.list_helix_workspace_sessions` | `helix_sessions.list_helix_sessions` |
| `sandbox.input_helix_session` | `helix_sessions.send_helix_session_input` |
| `provider_configs.list_providers` | `llm_providers.list_provider_configs` |
| `provider_configs.create_provider` | `llm_providers.create_provider_config` |
| `provider_configs.get_provider` | `llm_providers.get_provider_config` |
| `provider_configs.update_provider` | `llm_providers.update_provider_config` |
| `provider_configs.delete_provider` | `llm_providers.delete_provider_config` |
| `provider_configs.get_models_catalog` | `llm_providers.get_model_catalog` |
| `provider_configs.list_provider_models` | `llm_providers.list_provider_config_models` |
| `provider_configs.test_provider` | `llm_providers.test_provider_config` |

</details>

- **Model and enum names** in `mutagent.models` come from the schema names in the API document (`Mode`, `HelixArm`, `HelixSignal`, `HelixLaunchRequest`, `LlmProviderType`, `ErrorResponse`). Generator-invented names from `0.1.1` (`Schemadefaul7`, `ModeArmModel`, `NameSlugDescription5`) no longer exist. String enums (`Mode`, `HelixArm`, `HelixSignal`, `QueryFlag`, `WorkspaceDriftPolicy`) subclass `str`, so a member compares equal to its value (`Mode.HEADLESS == "headless"`).
- **Session mode values**: `"headless"` or `"interactive"` (`Mode.HEADLESS`, `Mode.INTERACTIVE`). The `0.1.1` values `"oneshot"` and `"rpc"` are gone.
- **Default server**: `https://api.mutagent.io` when `server_url` and `MUTAGENT_SERVER_URL` are both unset; was `http://localhost:3003` in `0.1.1`.
- **Stream methods**: `sandbox.stream_sandbox` and `helix_sessions.stream_helix_session` changed — see Known limitations below.

### Known limitations

- A field whose wire name is a Python keyword or builtin is escaped on the model (`Workspace.id` is read as `workspace.id_`); the alias keeps the wire name, so both directions still send and receive `id`.
- Streaming in `0.4.1`: the sync `stream_sandbox` and `stream_helix_session` send `Accept: application/json`, read the whole response, and return `None` — the event data is discarded. The async versions raise `TypeError` before a request is sent.

### Generated by mutagent-xgen

This package is generated by [mutagent-xgen](../mutagent-xgen/). To regenerate after API changes:

```bash
bash scripts/dump-spec.sh   # fetch latest spec from a running server
mutagent-xgen run            # regenerate the SDK
```

`README.md` is emitted once and never rewritten by a regeneration, so hand-written content here is safe.

---

## License

This SDK is released under the [Apache License 2.0](./LICENSE).

Copyright 2026 MutagenT. All rights reserved.

<p align="center">
  <sub>Built with care by the MutagenT Team &bull; <a href="https://mutagent.io">mutagent.io</a></sub>
</p>
