# 비동기 작업

> LMSP가 장시간 생성을 영속화된 작업으로 실행하는 방법. 작업 생성, 작업 상태 기계, 멱등한 취소, 이벤트 스트림, 미완료 작업의 재시작 정책을 정의합니다.

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

## 7.1 작업 만들기 {#create}

클라이언트는 실행하려는 응답 요청을 작업 엔벨로프에 담아 게시하여 작업을 만든다. `Idempotency-Key` 헤더를 권장한다([9.2절](https://madeforagent.com/ko/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/ko/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/ko/lmsp/1.0/tasks.md#states)). |
| `created_at`, `updated_at` | Unix 에포크 이후의 초 단위 시각. |
| `response_id` | 작업이 만드는 응답의 id. 작업이 만들어질 때 할당된다. |
| `events_url` | 이벤트 스트림의 경로. `event_resume` 기능이 `true`일 때 존재한다. |
| `on_restart` | 작업의 재시작 정책. |
| `response` | `status`가 `succeeded`가 되면 응답 객체, 그렇지 않으면 `null`. |
| `error` | `status`가 `failed`이면 problem 객체([9.3절](https://madeforagent.com/ko/lmsp/1.0/common-rules.md#errors)), 그렇지 않으면 `null`. |

`GET /lmsp/v1/tasks/{task_id}`는 작업 객체를 반환한다. 이벤트 스트림을 쓰지 않는 클라이언트는 이 엔드포인트를 폴링한다. 알 수 없는 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`로 응답한다. 알 수 없는 id는 `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/ko/lmsp/1.0/responses.md#streaming)의 응답 이벤트이다. 번호 매김과 재개는 [6.4절](https://madeforagent.com/ko/lmsp/1.0/responses.md#reconnection)을 따른다.
- 스트림은 작업이 접수될 때 기록되는 `response.created`로 시작한다. 결과에 맞는 최종 이벤트 하나로 끝난다. `response.completed`, `response.incomplete`, `response.failed`, `response.cancelled` 중 하나이다.
- 늦게 연결한 클라이언트는 먼저 놓친 이벤트를 받고, 그다음에 실시간 이벤트를 받는다. 서비스는 최종 이벤트 뒤에 스트림을 닫는다.
- 이벤트는 보존 기간 동안 보관된다([11.2절](https://madeforagent.com/ko/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`와 함께 쓰고 도구 호출을 멱등하게 만들어**야 한다**.
