# Background Tasks

<Badge type="warning" text="Not yet released" />

A background task is a function that keeps running while the Extension is on, such as a sync every 15 minutes. It needs `service` in `dozin.capabilities`. Register it with `api.registerService(definition)`.

## ServiceDefinition

| Field | Type | Required | Description |
|---|---|---|---|
| `id` | `string` | No | Defaults to the package name. Lowercase letters, digits and hyphens. At most 8 per Extension |
| `name` | `string` | No | Shown in **Settings › Extensions**, 1–80 characters; defaults to `id` |
| `start` | `'auto' \| 'manual'` | No | Default `manual`. `auto` starts with the Extension once its `secrets` are filled in. The person's choice wins |
| `restart` | `{ policy?, maxRetries?, backoffSeconds? }` | No | `policy`: `never`, `on-failure` (default) or `always`; `maxRetries` 0–100 (default 5); `backoffSeconds` 1–10 waits of 1–3600 seconds each (default 5, 30, 120) |
| `stopTimeoutSeconds` | `number` | No | 1–30, default 10: how long Stop waits for `run` to return |
| `run(ctx)` | function | Yes | See below |

An invalid field refuses the whole task (`registerService` throws `TypeError`); nothing is corrected for you.

## run(ctx) <Badge type="warning" text="Not yet released" />

The task itself. Run until `ctx.signal` fires.

| Name | Type | Description |
|---|---|---|
| `ctx` | `ServiceContext` | The fields and functions below |

Returns `Promise<void>`. Returning ends the run. Throwing counts as a failure and is retried by `restart`. When Stop is pressed and `run` has not returned within `stopTimeoutSeconds`, the Extension's process is restarted.

## ServiceContext fields

| Field | Type | Description |
|---|---|---|
| `serviceId` | `string` | The task's key: the package name, or `<package>.<id>` |
| `runId` | `string` | This run |
| `signal` | `AbortSignal` | Fires on Stop, Restart, switching the Extension off, uninstalling and quitting Cowork. Check it before each request |
| `env` | `Readonly<Record<string, string>>` | The values of `dozin.secrets`. A task with a missing value does not start and shows "Missing configuration" |
| `dataDir` | `string` | The Extension's own data folder, also its working folder |

## ctx.status(text, level?, progress?) <Badge type="warning" text="Not yet released" />

Sets the line shown under the task in **Settings › Extensions**.

| Name | Type | Required | Description |
|---|---|---|---|
| `text` | `string` | Yes | The line; at most 300 characters are kept |
| `level` | `'info' \| 'warn' \| 'error'` | No | Default `info` |
| `progress` | `{ done: number, total: number }` | No | Shown as progress |

Returns nothing. Values of `dozin.secrets` are replaced with `***`. Keep personal data out of it: the line is visible to anyone at the computer.

```js
ctx.status('Synced 52 patients', 'info', { done: 52, total: 52 });
```

## ctx.log(text) <Badge type="warning" text="Not yet released" />

Writes one line to the Extension's log. Values of `dozin.secrets` are replaced with `***`. Returns nothing.

```js
ctx.log('round started');
```

## ctx.readJson(rel) <Badge type="warning" text="Not yet released" />

Reads a JSON file inside `dataDir`.

| Name | Type | Required | Description |
|---|---|---|---|
| `rel` | `string` | Yes | Path relative to `dataDir` |

Returns `Promise<unknown>`: the parsed value, or `undefined` when the file does not exist. Rejects with `PATH_OUTSIDE_DATA` for an empty path or a path outside `dataDir`, and with the parse error when the file is not valid JSON.

## ctx.writeJson(rel, value) <Badge type="warning" text="Not yet released" />

Writes a JSON file inside `dataDir`, replacing it in one step, so a crash never leaves half a file.

| Name | Type | Required | Description |
|---|---|---|---|
| `rel` | `string` | Yes | Path relative to `dataDir` |
| `value` | `unknown` | Yes | Anything `JSON.stringify` accepts |

Returns `Promise<void>`. Rejects with `PATH_OUTSIDE_DATA` for an empty path or a path outside `dataDir`.

After a run has ended, `ctx.site` and `ctx.agent` calls from it reject with `RUN_ENDED`.

## Example

```js
export function activate(api) {
  api.registerService({
    id: 'sync', name: 'Sync', start: 'auto',
    async run(ctx) {
      while (!ctx.signal.aborted) {
        const state = (await ctx.readJson('state.json')) ?? { runs: 0 };
        state.runs += 1;
        await ctx.writeJson('state.json', state);
        ctx.status(`Run ${state.runs} done`);
        await new Promise((resolve) => setTimeout(resolve, 15 * 60 * 1000));
      }
    }
  });
}
```

## Related

- [Site Requests](https://docs.micromeet.ai/extension-sdk/site.md): `ctx.site`
- [Service Conversations](https://docs.micromeet.ai/extension-sdk/agent.md): `ctx.agent`
- [Entry and Registration](https://docs.micromeet.ai/extension-sdk/entry.md): `registerService`
