> ## Documentation Index
> Fetch the complete documentation index at: https://agent.minimax.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Python SDK

> Drive local MiniMax Code from Python with minimax-code-sdk.

The Python SDK gives you the same control as the [TypeScript SDK](https://agent.minimax.io/docs/cli/sdk.md) from Python code: start Threads, run Turns, stream events, and resume Sessions.

Every Turn runs one local `mcode exec --output-format=stream-json` process and continues the Session with `--session`. The package is `0.x`, so APIs may change between minor versions until `1.0`.

Related pages: [TypeScript SDK](https://agent.minimax.io/docs/cli/sdk.md), [Headless and CI](https://agent.minimax.io/docs/cli/automation.md). Package: [PyPI `minimax-code-sdk`](https://pypi.org/project/minimax-code-sdk/).

## Requirements

| Component | Requirement |
| - | - |
| Python | `>=3.10` |
| MiniMax Code CLI (`mcode`) | `>=0.6.3` |
| Event `schemaVersion` | `1` |

The SDK does not install the CLI. Install it separately and sign in, or configure a working API-key provider in the data directory, before the first run.

## Installation

```bash theme={null}
pip install minimax-code-sdk        # or: uv add minimax-code-sdk
npm install -g @minimax-ai/code     # the mcode CLI
```

Import the package as `minimax_code`.

## Quick start

```python theme={null}
import asyncio
from minimax_code import MCode, ThreadOptions


async def main() -> None:
    client = MCode()
    thread = client.start_thread(ThreadOptions(cwd=".", permission="smart"))

    turn = await thread.run("Summarize this repository in one sentence.")
    print(thread.id, turn.final_response, turn.usage)

    # Same Session, next Turn
    turn = await thread.run("Now list its top-level directories.")
    print(turn.final_response)


asyncio.run(main())
```

`run()` returns a `Turn` only when the status is `succeeded`. It carries `final_response`, normalized `items`, `usage`, `model`, `session_id`, `turn_id`, `duration_ms`, and the full CLI `result`. Every other outcome raises.

### Blocking API

If your code is not async, use `SyncMCode`. It has the same methods without `await`:

```python theme={null}
from minimax_code import SyncMCode

with SyncMCode() as client:
    thread = client.start_thread()
    print(thread.run("hello").final_response)
    for event in thread.run_streamed("again"):
        print(event["type"])
```

`SyncMCode` runs its own event loop, so it cannot be used where a loop is already running, such as a Jupyter notebook or inside an `async def` function. There, running a Turn raises a single `RuntimeError` that points you to the async API. Use `MCode` with `await` instead.

## CLI resolution

Lookup order:

1. `MCodeOptions(bin=...)`;
2. The `MCODE_BIN` environment variable;
3. `mcode` on `PATH`.

The SDK never starts the CLI through a shell. A missing CLI raises `MCodeNotFoundError`. Before the first run, each client runs `mcode --version` once; a CLI older than `0.6.3` raises `MCodeVersionError`.

On Windows, the `mcode.cmd` launcher written by the official installer or by npm / pnpm is followed to the JS entry and Node runtime it runs. An unrecognized `.cmd` file is rejected; pass the JS entry as `bin` instead.

## Options

Options are dataclasses passed to the client, the Thread, and each Turn.

### `MCode(MCodeOptions(...))`

| Field | Description |
| - | - |
| `bin` | Explicit `mcode` path or command name |
| `data_dir` | Data directory, passed to the CLI as `MINIMAX_DATA_DIR` |
| `env` | Child process environment. **Replaces** `os.environ` when set; values are hidden in `repr()` |
| `config_path` | Runtime config file, passed as `--config` |

To add variables instead of replacing the environment, merge `os.environ`:

```python theme={null}
import os
from minimax_code import MCode, MCodeOptions

client = MCode(MCodeOptions(env={**os.environ, "FOO": "bar"}))
```

### `start_thread` / `resume_thread(ThreadOptions(...))`

| Field | CLI flag |
| - | - |
| `cwd` | `--cwd`; defaults to the current working directory, resolved when the Thread is created |
| `model` / `effort` | `--model` (`provider/model`) / `--effort` |
| `permission` | `--permission`: `smart` / `full` / `off` |
| `mode` | `--mode`: `standard` / `lightweight`; only on a new Thread |
| `prompt_mode` | `--prompt-mode`: `tui` / `coding` / `work` |
| `system_prompt` | Replaces the system prompt (the SDK writes a temporary file) |
| `append_system_prompt` | Appended to the system prompt (the SDK writes a temporary file) |

Invalid values raise `MCodeInvocationError` before the CLI starts. `mode="lightweight"` on `resume_thread` is rejected the same way. There is no `ask` permission: each Turn is a separate process, so interactive permission prompts are not supported.

### `thread.run` / `thread.run_streamed(prompt, RunOptions(...))`

| Field | Description |
| - | - |
| `files` | Attachments (`--file`, repeatable) |
| `timeout_ms` | Turn timeout, enforced by the CLI |
| `max_steps` | `--max-steps` |

```python theme={null}
from minimax_code import RunOptions

turn = await thread.run(
    "Diagnose and fix the failing CI job",
    RunOptions(files=["./ci.log"], timeout_ms=10 * 60_000, max_steps=50),
)
```

A Thread runs one Turn at a time. A concurrent call raises `ThreadBusyError`; Turns are not queued.

## Threads and resume

`thread.id` is the Session id. It becomes available once the first Turn emits its first event. Persist it and continue later with `resume_thread`:

```python theme={null}
thread = client.start_thread(ThreadOptions(cwd="/path/to/project"))
await thread.run("...")

later = client.resume_thread(thread.id, ThreadOptions(cwd="/path/to/project"))
print((await later.run("What did I ask first?")).final_response)
```

A Session belongs to the workspace it was created in, so pass the same `cwd` to `resume_thread()`. When `cwd` is omitted, the current working directory at that call is used. A Thread pins its `cwd` when it is created, so a later `os.chdir()` does not affect it. Resuming from a different workspace fails with `MCodeRunError`, and retrying does not help.

## Streaming

`run_streamed()` returns a `StreamedTurn` that you iterate with `async for`. Text deltas for an `agent_message` can arrive on `item.started` as well as `item.updated`, so read `contentDelta` from any event that carries an item:

```python theme={null}
streamed = await thread.run_streamed("Fix the failing test.")
async for event in streamed:
    item = event.get("item")
    if item and item["type"] == "agent_message":
        print(item.get("contentDelta", ""), end="")
    elif event["type"] == "turn.failed":
        print("failed:", event.get("error"))
```

Events are the CLI's stream-json events as plain dicts, passed through unchanged, unknown types included: `exec.started`, `session.started` / `session.resumed`, `turn.started`, `item.started` / `item.updated` / `item.completed`, `turn.completed`, `turn.failed`, and `exec.completed`. The one exception: an `item.updated` snapshot (no `contentDelta`) identical to the previous snapshot of the same item is skipped. Text deltas are never skipped.

`turn.failed` is an ordinary event. Iteration raises only for process or protocol failures.

`Turn.items` are normalized dataclasses: `AgentMessageItem`, `ReasoningItem`, and `ToolCallItem`. Items that had no `item.completed` when the Turn ended have `incomplete=True`.

## Errors

Every error subclasses `MCodeError`, which carries `exit_code`, `stderr` (the CLI stderr tail, for diagnostics only), `session_id`, `result`, and `items`.

| Error | Typical cause |
| - | - |
| `MCodeInvocationError` | An option the SDK rejects before starting the CLI, or CLI exit code `2` |
| `MCodeRunError` | The Turn ended with `failed`, `timeout`, or `limit_exceeded`; adds `status` and `category` |
| `MCodeAbortError` | The Turn was cancelled (exit code `130` or a cancelled result) |
| `MCodeProtocolError` | Non-JSON stdout, missing `exec.completed`, broken pipe, and similar |
| `MCodeNotFoundError` | No CLI found |
| `MCodeVersionError` | CLI older than `0.6.3`, or an unsupported event `schemaVersion` |
| `ThreadBusyError` | Concurrent Turn on the same Thread |

`MCodeRunError.status` is `failed`, `timeout`, or `limit_exceeded`. `category` is `config`, `runtime`, `internal`, or `None`. `MCODE_EXEC_EXIT_CODES` maps the CLI exit codes by name.

```python theme={null}
from minimax_code import MCodeAbortError, MCodeError, MCodeRunError

try:
    turn = await thread.run("Run the migration")
except MCodeRunError as error:
    print(error.status, error.category, error.exit_code, error.session_id)
except MCodeAbortError:
    print("cancelled")
except MCodeError as error:
    print(type(error).__name__, error)
```

The SDK never retries automatically. When an error carries `session_id`, you can continue with `resume_thread(error.session_id, ...)`. Errors can be pickled, for example to return them from a `ProcessPoolExecutor` worker.

## Cancellation and timeouts

* `streamed.cancel()` sends `SIGTERM`, the CLI's cancel signal. The stream then ends with the CLI's final events;
* Leaving the `async for` early (`break` or an exception), or cancelling the task running `thread.run()`, stops the CLI: `SIGTERM`, then `SIGKILL` if it is still running after the CLI's 60-second shutdown budget. The next Turn on the same Thread waits for that shutdown instead of raising `ThreadBusyError`;
* `async with streamed:` or `await streamed.aclose()` stops the CLI deterministically;
* `timeout_ms` is enforced by the CLI. If the CLI is still running 90 seconds after `timeout_ms`, the SDK kills it and raises `MCodeRunError` with `status="timeout"`;
* On Windows, cancellation stops the CLI immediately.

```python theme={null}
streamed = await thread.run_streamed("Refactor the module")
async with streamed:
    async for event in streamed:
        if event["type"] == "item.completed":
            break  # leaving the block stops the CLI
```

## Not available yet

* Interactive permission prompts (`permission="ask"`) and custom in-process tools;
* `output_schema` (structured-output constraints), approval callbacks, steering a running Turn, and forking.

For direct CLI usage or structured output, see [Headless and CI](https://agent.minimax.io/docs/cli/automation.md).
