# Асинхронные задачи

> Как LMSP выполняет долгие генерации как сохраняемые задачи, с созданием задач, конечным автоматом состояний, идемпотентной отменой, потоками событий и политиками перезапуска для незавершённых задач.

Source: https://madeforagent.com/ru/lmsp/1.0/tasks/
Language: ru
Specification: LMSP 1.0 (draft)
Updated: 2026-11-08

## 7.1 Создание задачи {#create}

Клиент создаёт задачу, отправляя запрос ответа, который хочет выполнить, обёрнутый в конверт задачи. Рекомендуется заголовок `Idempotency-Key` ([раздел 9.2](https://madeforagent.com/ru/lmsp/1.0/common-rules.md#idempotency)).

```http
POST /lmsp/v1/tasks
Authorization: Bearer <token>
Idempotency-Key: 01J8M7C4Y3N5P8R2T6V9X1Z4BH
```

```json
{
  "type": "response",
  "input": {
    "model": "qwen3:14b-q4_k_m",
    "input": [
      {
        "type": "message",
        "role": "user",
        "content": [
          {
            "type": "input_text",
            "text": "Write a long report."
          }
        ]
      }
    ]
  },
  "on_restart": "fail"
}
```

| Поле | Обязательное | Значение |
| --- | --- | --- |
| `type` | Да | `response` — единственный тип задачи в версии 1.0. |
| `input` | Да | Запрос ответа, точно такой же, как для `POST /lmsp/v1/responses`. Его поле `stream` игнорируется. |
| `on_restart` | Нет | `fail` (по умолчанию), `retry` или `resume`. См. [раздел 7.5](https://madeforagent.com/ru/lmsp/1.0/tasks.md#restart). |

Сервис **ДОЛЖЕН** сохранить задачу до того, как ответит. Затем он возвращает:

```http
HTTP/1.1 202 Accepted
```

```json
{
  "id": "task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC",
  "object": "task",
  "type": "response",
  "status": "queued",
  "created_at": 1794200100,
  "updated_at": 1794200100,
  "response_id": "resp_01J8M8R5X4B9D2F6H1K3N7Q0ZW",
  "events_url": "/lmsp/v1/tasks/task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC/events",
  "on_restart": "fail",
  "response": null,
  "error": null
}
```

| Поле задачи | Значение |
| --- | --- |
| `id` | Идентификатор задачи, например `task_...`. |
| `object` | Всегда `"task"`. |
| `type` | `response`. |
| `status` | Текущее состояние ([раздел 7.2](https://madeforagent.com/ru/lmsp/1.0/tasks.md#states)). |
| `created_at`, `updated_at` | Время в секундах с начала эпохи Unix. |
| `response_id` | Идентификатор ответа, который создаёт задача. Присваивается при создании задачи. |
| `events_url` | Путь к потоку событий. Присутствует, когда возможность `event_resume` равна `true`. |
| `on_restart` | Политика перезапуска задачи. |
| `response` | Объект ответа, когда `status` равен `succeeded`, иначе `null`. |
| `error` | Объект проблемы ([раздел 9.3](https://madeforagent.com/ru/lmsp/1.0/common-rules.md#errors)), когда `status` равен `failed`, иначе `null`. |

`GET /lmsp/v1/tasks/{task_id}` возвращает объект задачи. Клиенты, которые не используют потоки событий, опрашивают этот эндпоинт. Неизвестный идентификатор даёт `404` с кодом `task_not_found`. Завершённая задача выглядит так:

```json
{
  "id": "task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC",
  "object": "task",
  "type": "response",
  "status": "succeeded",
  "created_at": 1794200100,
  "updated_at": 1794200162,
  "response_id": "resp_01J8M8R5X4B9D2F6H1K3N7Q0ZW",
  "events_url": "/lmsp/v1/tasks/task_01J8M8R5X3V7B2N4Q9T1W6Y0ZC/events",
  "on_restart": "fail",
  "response": {
    "id": "resp_01J8M8R5X4B9D2F6H1K3N7Q0ZW",
    "object": "response",
    "created_at": 1794200100,
    "status": "completed",
    "model": "qwen3:14b-q4_k_m",
    "output": [
      {
        "id": "msg_01J8M8R6A2C5E8G1J4M7P0S3VY",
        "type": "message",
        "role": "assistant",
        "content": [
          {
            "type": "output_text",
            "text": "Local model services make on-device inference a shared, discoverable utility..."
          }
        ]
      }
    ],
    "usage": {
      "input_tokens": 12,
      "output_tokens": 1480,
      "total_tokens": 1492
    },
    "error": null,
    "incomplete_details": null
  },
  "error": null
}
```

## 7.2 Конечный автомат {#states}

```text
queued --> loading --> running --> succeeded
  |           |           |
  |           |           +--> failed
  |           |           +--> cancelling --> cancelled
  |           +--------------> failed
  +--------------------------> cancelled
```

| Состояние | Значение | Следующие состояния |
| --- | --- | --- |
| `queued` | Принята и сохранена, ожидает запуска. | `loading`, `cancelled`, `failed` |
| `loading` | Модель загружается. | `running`, `cancelling`, `failed` |
| `running` | Модель генерирует. | `succeeded`, `failed`, `cancelling` |
| `cancelling` | Отмена запрошена, но бэкенд ещё не остановился. | `cancelled` |
| `succeeded` | Конечное. Ответ готов. | нет |
| `failed` | Конечное. | нет |
| `cancelled` | Конечное. | нет |

Конечные состояния — `succeeded`, `failed` и `cancelled`. Сервис **НЕ ДОЛЖЕН** переводить задачу из конечного состояния в другое. Задача, ответ которой заканчивается статусом `incomplete`, например по лимиту токенов, считается `succeeded`.

Каждый переход атомарен, поэтому задача никогда не бывает одновременно отменённой и завершённой. Побеждает тот переход, который зафиксирован первым.

## 7.3 Отмена {#cancel}

```http
POST /lmsp/v1/tasks/{task_id}/cancel
```

Сервис возвращает `200 OK` и текущий объект задачи. Задача `queued` сразу становится `cancelled`. Задача `loading` или `running` становится `cancelling`, пока бэкенд не остановится, а затем `cancelled`.

Сервис **ДОЛЖЕН** обеспечивать идемпотентность отмены. Отмена задачи, которая уже `cancelling` или `cancelled`, возвращает текущую задачу и не порождает новой ошибки.

Отмена задачи, которая уже `succeeded` или `failed`, получает ответ `409` с кодом `task_not_cancelable`. Неизвестный идентификатор даёт `404` с кодом `task_not_found`.

## 7.4 Потоки событий {#events}

```http
GET /lmsp/v1/tasks/{task_id}/events
Accept: text/event-stream
```

Этот эндпоинт необязателен. Он доступен, когда возможность `event_resume` равна `true`.

- События — это события ответа из [раздела 6.3](https://madeforagent.com/ru/lmsp/1.0/responses.md#streaming) для ответа, который создаёт задача. Нумерация и возобновление следуют [разделу 6.4](https://madeforagent.com/ru/lmsp/1.0/responses.md#reconnection).
- Поток начинается с `response.created`, которое записывается при приёме задачи. Он заканчивается одним последним событием, соответствующим исходу: `response.completed`, `response.incomplete`, `response.failed` или `response.cancelled`.
- Клиент, подключившийся поздно, сначала получает пропущенные события, затем живые. После последнего события сервис закрывает поток.
- События хранятся в течение срока хранения ([раздел 11.2](https://madeforagent.com/ru/lmsp/1.0/persistence.md#rules)). Более старые события дают `410 Gone` с `event_history_expired`.

## 7.5 Поведение при перезапуске {#restart}

`on_restart` определяет, что происходит с незавершённой задачей при перезапуске сервиса:

| Значение | Поведение |
| --- | --- |
| `fail` | По умолчанию. Сервис помечает незавершённую задачу как `failed` с кодом ошибки `server_restarted`. |
| `retry` | Сервис запускает задачу заново с начала. |
| `resume` | Сервис продолжает со снимка состояния. Это допускается, только если бэкенд явно поддерживает возобновляемые снимки. В противном случае запрос на создание отклоняется с `400` и кодом `unsupported_capability`. |

Задача, которая находится в состоянии `cancelling` при перезапуске сервиса, завершает отмену и становится `cancelled` независимо от своей политики.

Значение `retry` не рекомендуется использовать по умолчанию, потому что задача могла уже запустить инструменты или вызвать внешние побочные эффекты. Клиент, выбравший `retry`, **ДОЛЖЕН** сочетать его с `Idempotency-Key` и сделать вызовы своих инструментов идемпотентными.
