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

# Python SDK

> Reference for AIR application clients, runs, workers, tools, and errors.

The PyPI distribution is `papermap-air`; the Python import is `air`. Python
3.10 or newer is required and the SDK is async-native.

```bash theme={null}
python -m pip install papermap-air
```

## `AirClient`

```python theme={null}
AirClient(base_url: str, api_key: str)
```

Use it as an async context manager or call `await client.aclose()` explicitly.

### `client.runs.create()`

Creates a run. `agent_id` is required. `model_id`, `input`, `context`, and
`webhook_url` are optional. Returns a `Run`.

### `client.runs.get(run_id)`

Returns the latest state of a run.

### `client.runs.wait(run_id, timeout=60, poll_interval=0.2)`

Polls until `succeeded`, `failed`, or `cancelled`. Raises `RunTimeoutError` when
the local timeout is reached.

### `client.runs.stream(run_id, *, after_seq=0, max_retries=3, retry_delay=0.5)`

Returns an async iterator of `RunEvent` objects. It emits existing events after
`after_seq`, continues with live events, and stops after a terminal
`run_succeeded`, `run_failed`, or `run_cancelled` event.

The SDK reconnects after a temporary stream failure and resumes after the last
sequence number it received. `max_retries` controls the number of reconnect
attempts and `retry_delay` controls the delay between them.

```python theme={null}
text = ""
async for event in client.runs.stream(run.id):
    if event.output_delta is not None:
        text += event.output_delta
    elif event.type == "model_output_reset":
        text = ""
```

### `client.runs.cancel(run_id)`

Requests cancellation and returns the server response dictionary.

## `Run`

| Field      | Type           | Meaning                                                                  |
| ---------- | -------------- | ------------------------------------------------------------------------ |
| `id`       | `str`          | Run identifier.                                                          |
| `status`   | `str`          | Current lifecycle status.                                                |
| `output`   | `dict \| None` | Validated schema object, or `{ "text": "..." }` for a schema-less agent. |
| `error`    | `str \| None`  | Failure detail when available.                                           |
| `cost_usd` | `str`          | Recorded cost as a decimal string.                                       |
| `raw`      | `dict`         | Complete server response.                                                |

## `RunEvent`

| Field          | Type          | Meaning                                                               |
| -------------- | ------------- | --------------------------------------------------------------------- |
| `seq`          | `int`         | Increasing sequence number within the run.                            |
| `type`         | `str`         | Event type, such as `model_output_delta` or `run_succeeded`.          |
| `data`         | `dict`        | Event-specific payload.                                               |
| `created_at`   | `str \| None` | Server timestamp when available.                                      |
| `output_delta` | `str \| None` | Convenience property containing text for `model_output_delta` events. |

When `type` is `model_output_reset`, discard any partial model text already
rendered. This event means AIR started another model attempt after a retry or
fallback. Terminal run events end the iterator.

## `@tool`

```python theme={null}
from air import tool


@tool(name="lookup_order", timeout=30)
def lookup_order(order_id: str, context: dict) -> dict:
    """Look up one order."""

    return {"order_id": order_id}
```

The decorator derives a JSON schema from type annotations. Synchronous tools
run in a thread; async tools are awaited. A `context` parameter receives the
run context. Every tool must return a dictionary.

Use a separate `ToolRegistry` when you want explicit isolation instead of the
process-wide default registry.

## `Worker`

```python theme={null}
Worker(
    base_url: str,
    worker_key: str,
    *,
    registry: ToolRegistry | None = None,
    environment: str = "default",
)
```

Important methods are `start()`, `poll_once()`, `heartbeat()`, `run()`,
`mount_mcp()`, and `aclose()`.

## Errors

All SDK errors inherit from `AirError`:

* `AirProtocolError` for rejected or unexpected HTTP responses
* `RunTimeoutError` when a local wait deadline expires
* `ToolDefinitionError` for invalid or duplicate tool definitions
* `ToolResultError` when a tool does not return a dictionary
* `McpError` for MCP transport or protocol failures
