# Stop work and keep history

Stop a task, take it back, archive a task or a worker, and learn how long a task's history is kept on each subscription.

Work you gave sometimes needs to stop: a task goes the wrong way, a program has a bug, or a key leaked. This guide shows each way to stop, from the gentlest to the strongest, and then what is kept afterwards and for how long.

Each step shows curl, TypeScript and Python. Pick one and the page keeps it picked. The TypeScript and Python steps build one small program, so add each step's code to the same file.

## Before you start

- A key in `LITTLE_WORKER_API_KEY`. The [Quickstart](/get-started/quickstart) makes one, and [API keys](/get-started/api-keys) says more.
- For curl: curl and jq.
- For TypeScript: Node 22.18 or later, which runs a `.ts` file as it is. Save the program as `stop.ts` and run `node stop.ts`.
- For Python: Python 3 and the requests library (`pip install requests`). Save the program as `stop.py` and run `python3 stop.py`.

The program makes a worker of its own to stop, so archiving it at the end costs you nothing. Archiving a worker cannot be undone, so do not point the last step at one you keep.

## 1. Give a task that runs long

Make a worker, and give it a task that takes a while to write, so it is still working when you stop it:

```bash title="curl"
curl https://core.thelittleworker.com/api/v1/workers \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "content-type: application/json" \
  -d '{ "name": "Throwaway writer", "system": "You write long, careful drafts." }'
# Keep the worker's id as WORKER_ID, then:
curl https://core.thelittleworker.com/api/v1/tasks \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "content-type: application/json" \
  -d '{
    "worker": "'"$WORKER_ID"'",
    "title": "Long essay",
    "initial_events": [
      {
        "type": "user.message",
        "content": [{ "type": "text", "text": "Write a 2,000 word essay on why a shop should open early." }]
      }
    ]
  }'
```

```ts title="TypeScript"
const api = "https://core.thelittleworker.com/api/v1"
const headers = {
  "x-api-key": process.env.LITTLE_WORKER_API_KEY ?? "",
  "Little-Worker-Version": "2026-10-19",
  "content-type": "application/json",
}

async function call<T>(method: string, path: string, body?: unknown): Promise<T> {
  const response = await fetch(`${api}${path}`, {
    method,
    headers,
    body: body === undefined ? undefined : JSON.stringify(body),
  })
  if (!response.ok) {
    throw new Error(`${method} ${path}: ${response.status} ${await response.text()}`)
  }
  return (await response.json()) as T
}

type Task = {
  id: string
  status: string
  state: { status: string; waiting_reason: string | null }
  stop_reason: { type: string } | null
}

const essay = {
  type: "user.message",
  content: [
    { type: "text", text: "Write a 2,000 word essay on why a shop should open early." },
  ],
}

const worker = await call<{ id: string }>("POST", "/workers", {
  name: "Throwaway writer",
  system: "You write long, careful drafts.",
})
const task = await call<Task>("POST", "/tasks", {
  worker: worker.id,
  title: "Long essay",
  initial_events: [essay],
})
console.log(worker.id, task.id)
```

```python title="Python"
import os
import time

import requests

api = "https://core.thelittleworker.com/api/v1"
headers = {
    "x-api-key": os.environ["LITTLE_WORKER_API_KEY"],
    "Little-Worker-Version": "2026-10-19",
}


def call(method, path, body=None):
    response = requests.request(method, f"{api}{path}", headers=headers, json=body)
    if not response.ok:
        raise RuntimeError(f"{method} {path}: {response.status_code} {response.text}")
    return response.json()


essay = {
    "type": "user.message",
    "content": [
        {"type": "text", "text": "Write a 2,000 word essay on why a shop should open early."}
    ],
}

worker = call(
    "POST",
    "/workers",
    {"name": "Throwaway writer", "system": "You write long, careful drafts."},
)
task = call(
    "POST",
    "/tasks",
    {"worker": worker["id"], "title": "Long essay", "initial_events": [essay]},
)
print(worker["id"], task["id"])
```

With curl, keep the ids of the worker and the task:

```bash
export WORKER_ID="worker_bf5ff997fca044b38dc1cc9ea5b60100"
export TASK_ID="task_01M5TTRJKWYAK3XHFX432K5MBV"
```

## 2. Interrupt it

An interrupt, sent as a `user.interrupt` event ([Send events](/api/events#send-events)), stops the work in hand. The model stops, the turn ends with a `task.status_idle` event whose `stop_reason` is `end_turn`, and the task stays open. Your next `user.message` starts a new turn. This is the gentlest way to stop, and the one to try first.

```bash title="curl"
curl https://core.thelittleworker.com/api/v1/tasks/$TASK_ID/events \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "content-type: application/json" \
  -d '{ "events": [{ "type": "user.interrupt" }] }'

# The task is idle a moment later:
curl -s https://core.thelittleworker.com/api/v1/tasks/$TASK_ID \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  | jq '{status, state, stop_reason}'
```

```ts title="TypeScript"
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))

async function waitForIdle(taskId: string): Promise<Task> {
  for (;;) {
    const current = await call<Task>("GET", `/tasks/${taskId}`)
    if (current.status === "idle") return current
    await sleep(2000)
  }
}

await call("POST", `/tasks/${task.id}/events`, {
  events: [{ type: "user.interrupt" }],
})
const stopped = await waitForIdle(task.id)
console.log(stopped.state, stopped.stop_reason)
```

```python title="Python"
def wait_for_idle(task_id):
    while True:
        current = call("GET", f"/tasks/{task_id}")
        if current["status"] == "idle":
            return current
        time.sleep(2)


call("POST", f"/tasks/{task['id']}/events", {"events": [{"type": "user.interrupt"}]})
stopped = wait_for_idle(task["id"])
print(stopped["state"], stopped["stop_reason"])
```

The answer to the interrupt is `200`, with `run_id` naming the run it stopped. A moment later the task is `idle`, and `state` reads `not_started` with `waiting_reason` `stopped`:

```json
{
  "status": "idle",
  "state": { "status": "not_started", "needs": null, "waiting_reason": "stopped" },
  "stop_reason": { "type": "end_turn" }
}
```

An interrupt stops the next step, not the last one. An email already sent, a post already made, a file already written and a form already submitted stay done. A step that was running when you interrupted may have finished: its `worker.tool_result` event has a `status` of `succeeded`, `failed`, `cancelled` or `outcome_unknown`. Read the events after an interrupt, and when a status is `outcome_unknown`, check the outside system. To refuse an action before it runs, make it ask first ([Answer approvals from your program](/guides/answer-approvals)).

A task held at its own budget has no work in hand, so the interrupt is accepted and does nothing.

## 3. Take a task back

Taking a task back is the strongest way to stop one task. It stops every run the task has not finished, closes the Allows and questions it waits on, and marks the task taken back, whatever its worker is doing. Give a second task, and take it back:

```bash title="curl"
curl https://core.thelittleworker.com/api/v1/tasks \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19" \
  -H "content-type: application/json" \
  -d '{
    "worker": "'"$WORKER_ID"'",
    "title": "Another essay",
    "initial_events": [
      {
        "type": "user.message",
        "content": [{ "type": "text", "text": "Write a 2,000 word essay on why a shop should open early." }]
      }
    ]
  }'
# Keep its id as SECOND_ID, then take it back:
curl https://core.thelittleworker.com/api/v1/tasks/$SECOND_ID/withdraw \
  -X POST \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19"
```

```ts title="TypeScript"
const second = await call<Task>("POST", "/tasks", {
  worker: worker.id,
  title: "Another essay",
  initial_events: [essay],
})
const taken = await call<{ outcome: string }>(
  "POST",
  `/tasks/${second.id}/withdraw`,
)
console.log(taken.outcome)
console.log((await fetch(`${api}/tasks/${second.id}`, { headers })).status)
```

```python title="Python"
second = call(
    "POST",
    "/tasks",
    {"worker": worker["id"], "title": "Another essay", "initial_events": [essay]},
)
taken = call("POST", f"/tasks/{second['id']}/withdraw")
print(taken["outcome"])
print(requests.get(f"{api}/tasks/{second['id']}", headers=headers).status_code)
```

The answer names what happened:

```json
{
  "type": "task_withdrawal",
  "task_id": "task_01M5TTRK2N8Q7XH6D4B3C9ZYAE",
  "outcome": "withdrawn"
}
```

`outcome` is `withdrawn`, or `done` when the task had already finished: a finished task stays finished. Taking a task back twice answers `withdrawn` both times, so a retry is safe.

A task taken back is gone: reading it answers `404`, and lists leave it out. It takes nothing more, and a `task.status_terminated` event is written for it. Use an interrupt when you may want to go on with the task, and take it back when you are done with it.

## 4. Archive the task

Archiving keeps everything a task holds and hides it. An archived task takes no new message, and lists leave it out unless you ask for `include_archived=true`. It cannot be undone, and archiving twice changes nothing. It is how you tidy up a task you may want to read later.

```bash title="curl"
curl https://core.thelittleworker.com/api/v1/tasks/$TASK_ID/archive \
  -X POST \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19"
```

```ts title="TypeScript"
const archived = await call<Task & { archived_at: string }>(
  "POST",
  `/tasks/${task.id}/archive`,
)
console.log(archived.archived_at)
```

```python title="Python"
archived = call("POST", f"/tasks/{task['id']}/archive")
print(archived["archived_at"])
```

Archiving is refused with `409` while the task has a run that has not ended, so interrupt it and wait for it to be idle first, as step 2 does. It is also `409` with `workflow_run_open` while a workflow run of the task is open. Ask the worker to stop the run, or take the task back.

Deleting a task removes its events and the files it made that you did not keep. It needs a key made with **Can delete tasks**, which Settings does not offer yet, so a key made there gets `403` with `person_required`. See [Delete a task](/api/tasks#delete-a-task).

## 5. Archive the worker

Archiving a worker hides it, and archives its routines. It cannot be undone, and a worker is never deleted. List archived workers too with `GET /workers?include_archived=true`.

The archive is refused with `409` and `conflict_error` while the worker has a queued, running or waiting run, so a task you queued is never left behind a worker that is gone. Interrupt or take back the worker's tasks, wait for them to stop, then archive. The run of the task you took back in step 3 may need a moment to wind down, so the program tries again:

```bash title="curl"
curl https://core.thelittleworker.com/api/v1/workers/$WORKER_ID/archive \
  -X POST \
  -H "x-api-key: $LITTLE_WORKER_API_KEY" \
  -H "Little-Worker-Version: 2026-10-19"
```

```ts title="TypeScript"
for (let attempt = 0; attempt < 15; attempt += 1) {
  const response = await fetch(`${api}/workers/${worker.id}/archive`, {
    method: "POST",
    headers,
  })
  if (response.status !== 409) {
    const archivedWorker = (await response.json()) as { status: string }
    console.log(response.status, archivedWorker.status)
    break
  }
  await sleep(2000)
}
```

```python title="Python"
for attempt in range(15):
    response = requests.post(f"{api}/workers/{worker['id']}/archive", headers=headers)
    if response.status_code != 409:
        print(response.status_code, response.json()["status"])
        break
    time.sleep(2)
```

The answer is the worker, with `status` `archived` and `archived_at` set.

## Stop what starts new tasks

Stopping a task does not stop whatever gives the next one. These stop the source:

| To stop | Do this | What it leaves |
| --- | --- | --- |
| A routine that gives tasks | `POST /routines/{routine_id}/pause` stops its schedule and its other starts. You can still run it by hand. `POST /routines/{routine_id}/archive` ends it for good. | A task the routine already started goes on. Stop it with an interrupt or take it back. |
| A program that uses a key | Choose **Remove** next to the key in **Settings**, then **API Keys**. | A task the program already started goes on. |

## How long history is kept

A task's history is its runs, the messages the worker answered, its tool uses and results, its approvals, and the events of all of them. How long it is kept depends on the subscription:

| Subscription | History |
| --- | --- |
| Free | 7 days. A piece of work is deleted once every run in it ended more than 7 days ago. |
| Any paid subscription | Kept until you delete it. |
| A core on your own computer | Kept. It never deletes history. |

A workspace that moves from a paid subscription to the free one keeps what it had for 7 days first. Little Worker looks for old history every hour.

The deletion takes a piece of work with everything it did. It keeps your workers, tasks, routines, memory stores and their notes, password sets, saved files, email, receipts and your decisions. The task itself stays readable.

A file's `scope` on `GET /files/{file_id}` tells when it goes:

| How the file was made | `scope` | When it is deleted |
| --- | --- | --- |
| Uploaded with `POST /files` | `null` | Never by a purge. By `DELETE /files/{file_id}`, or at its `expires_at` if you set one. |
| Made by a task | `{ "type": "task", "id": "task_..." }` | With the task's history, by a purge or by deleting the task. |
| Made by a task, then kept with `POST /files/{file_id}/keep` | `null` | Only by `DELETE /files/{file_id}`. |

A file attached to a message in the app goes with that message. [Files](/concepts/files) says more.

### Read a task after a purge

A purge adds a `history.purged` event to each task it changed. Its `before` is the cutoff: history older than that time is gone.

```json
{
  "type": "history.purged",
  "id": "event_0c4d6e8f1a3b4c5d9e7f2a1b3c5d7e9f",
  "task_id": "task_01M5TTRJKWYAK3XHFX432K5MBV",
  "sequence": "48",
  "processed_at": "2026-10-25T23:00:11.402Z",
  "before": "2026-10-18T23:00:11.402Z"
}
```

- `GET /tasks/{task_id}/events` with no cursor, oldest first, starts with the newest `history.purged`, then the events that were kept. Ask for them alone with `types[]=history.purged`.
- A `page`, `after` or `Last-Event-ID` from before that event, one that missed deleted history, is `400` with `cursor_expired`. List the task's events from the start again. `after=0` never expires.
- No webhook is sent for a purge. Keep your own copy of the events you need for longer than 7 days.

## Next steps

- [Answer approvals from your program](/guides/answer-approvals): make an action ask before it runs, so there is less to stop.
- [Follow a task as it works](/guides/follow-a-task): watch a task, and see the `task.status_idle` that follows an interrupt.
- [Tasks](/concepts/tasks#ending-a-task): the four ways to end a task, side by side.
- [Tasks reference](/api/tasks): every field of a task, and the routes to take back, archive and delete tasks.
