# Asynchronous tasks

> How LMSP runs long generations as persisted tasks, with task creation, the task state machine, idempotent cancellation, event streams and restart policies for unfinished tasks.

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

## 7.1 Create a task {#create}

A client creates a task by posting the response request it wants to run, wrapped in a task envelope. An `Idempotency-Key` header is recommended ([section 9.2](https://madeforagent.com/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"
}
```

| Field | Required | Meaning |
| --- | --- | --- |
| `type` | Yes | `response`, the only task type in 1.0. |
| `input` | Yes | A response request, exactly as for `POST /lmsp/v1/responses`. Its `stream` field is ignored. |
| `on_restart` | No | `fail` (the default), `retry` or `resume`. See [section 7.5](https://madeforagent.com/lmsp/1.0/tasks.md#restart). |

The service **MUST** persist the task before it answers. It then returns:

```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
}
```

| Task field | Meaning |
| --- | --- |
| `id` | Task identifier, such as `task_...`. |
| `object` | Always `"task"`. |
| `type` | `response`. |
| `status` | The current state ([section 7.2](https://madeforagent.com/lmsp/1.0/tasks.md#states)). |
| `created_at`, `updated_at` | Times in seconds since the Unix epoch. |
| `response_id` | Id of the response the task produces. It is assigned when the task is created. |
| `events_url` | Path of the event stream. Present when the `event_resume` capability is `true`. |
| `on_restart` | The restart policy of the task. |
| `response` | The response object once `status` is `succeeded`, otherwise `null`. |
| `error` | A problem object ([section 9.3](https://madeforagent.com/lmsp/1.0/common-rules.md#errors)) when `status` is `failed`, otherwise `null`. |

`GET /lmsp/v1/tasks/{task_id}` returns the task object. Clients that do not use event streams poll this endpoint. An unknown id gives `404` with `task_not_found`. A finished task looks like this:

```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 State machine {#states}

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

| State | Meaning | Next states |
| --- | --- | --- |
| `queued` | Accepted and persisted, waiting to start. | `loading`, `cancelled`, `failed` |
| `loading` | The model is being loaded. | `running`, `cancelling`, `failed` |
| `running` | The model is generating. | `succeeded`, `failed`, `cancelling` |
| `cancelling` | Cancellation was requested, and the backend has not stopped yet. | `cancelled` |
| `succeeded` | Final. The response is complete. | none |
| `failed` | Final. | none |
| `cancelled` | Final. | none |

The terminal states are `succeeded`, `failed` and `cancelled`. A task in a terminal state **MUST NOT** change again. A task whose response ends as `incomplete`, for example at the token limit, is `succeeded`.

Every transition is atomic, so a task is never both cancelled and completed. Whichever transition is committed first wins.

## 7.3 Cancellation {#cancel}

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

The service returns `200 OK` and the current task object. A `queued` task becomes `cancelled` at once. A `loading` or `running` task becomes `cancelling` until the backend has stopped, and then `cancelled`.

Cancellation **MUST** be idempotent. Cancelling a task that is already `cancelling` or `cancelled` returns the current task and produces no new error.

Cancelling a task that has already `succeeded` or `failed` is answered with `409` and `task_not_cancelable`. An unknown id gives `404` with `task_not_found`.

## 7.4 Event streams {#events}

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

This endpoint is optional. It is available when the `event_resume` capability is `true`.

- The events are the response events of [section 6.3](https://madeforagent.com/lmsp/1.0/responses.md#streaming), for the response that the task produces. Numbering and resumption follow [section 6.4](https://madeforagent.com/lmsp/1.0/responses.md#reconnection).
- The stream starts with `response.created`, which is recorded when the task is accepted. It ends with one final event that matches the outcome: `response.completed`, `response.incomplete`, `response.failed` or `response.cancelled`.
- A client that connects late first receives the events it missed, then live events. The service closes the stream after the final event.
- Events are kept for a retention period ([section 11.2](https://madeforagent.com/lmsp/1.0/persistence.md#rules)). Older events give `410 Gone` with `event_history_expired`.

## 7.5 Restart semantics {#restart}

`on_restart` says what happens to an unfinished task when the service restarts:

| Value | Behavior |
| --- | --- |
| `fail` | The default. The service marks the unfinished task `failed`, with the error code `server_restarted`. |
| `retry` | The service runs the task again from the beginning. |
| `resume` | The service continues from a snapshot. This is allowed only when the backend explicitly supports resumable snapshots. Otherwise the create request is rejected with `400` and `unsupported_capability`. |

A task that is `cancelling` when the service restarts completes its cancellation and becomes `cancelled`, whatever its policy.

`retry` is not recommended as a default, because the task may already have run tools or produced external side effects. A client that chooses `retry` **MUST** combine it with an `Idempotency-Key` and make its tool calls idempotent.
