# Persistence

> How an LMSP service stores its state, with an embedded SQLite database, the tables to keep, the transactional rules for tasks and events, retention and recovery at startup.

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

## 11.1 Storage {#storage}

A service **SHOULD** keep its state in an embedded [SQLite](https://www.sqlite.org/) database. A suggested set of tables:

| Table | Content |
| --- | --- |
| `service_config` | Service identity and configuration. |
| `clients` | Paired clients, token hashes and scopes. |
| `models` | Model metadata and content digests. |
| `responses` | Stored responses. |
| `tasks` | Asynchronous tasks. |
| `task_events` | The event log of each task, with sequence numbers. |
| `idempotency_keys` | Idempotency keys, request digests and results. |

Model files **MUST** stay outside the database. The database holds only metadata and content digests.

Table names and layouts are an implementation choice. What clients can observe, and what has to interoperate, is the behavior in the next two sections.

## 11.2 Rules {#rules}

Whatever storage a service uses, it:

- **MUST** persist a task before it returns `202 Accepted`.
- **MUST** update event sequence numbers and task status in the same transaction.
- **MUST** make the cancel transition atomic, to avoid the race of a task that is cancelled and then completed.
- **SHOULD** keep finished tasks and events for a configurable retention period.
- **MUST NOT** delete unfinished tasks during log cleanup.
- **MUST**, at startup, scan the leftover `queued`, `loading`, `running` and `cancelling` tasks, and apply the restart policy of each ([section 7.5](https://madeforagent.com/lmsp/1.0/tasks.md#restart)).

## 11.3 Recovery at startup {#recovery}

The table shows the state a leftover task is moved to, by its `on_restart` policy:

| State found at startup | `fail` | `retry` | `resume` |
| --- | --- | --- | --- |
| `queued` | `failed` | `queued` | `queued` |
| `loading` | `failed` | `queued` | `queued` |
| `running` | `failed` | `queued` | `running`, continuing from the snapshot |
| `cancelling` | `cancelled` | `cancelled` | `cancelled` |

A task moved to `failed` gets the error code `server_restarted` and a `response.failed` event. A task moved to `cancelled` gets a `response.cancelled` event. The new state and its event are written in one transaction ([section 11.2](https://madeforagent.com/lmsp/1.0/persistence.md#rules)).
