> ## Documentation Index
> Fetch the complete documentation index at: https://docs.avocadostudio.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# State and backups

> Where the orchestrator keeps draft pages, history and sessions, what is not persisted, how snapshots work, and what a clean shutdown does.

The orchestrator holds real work that is not in your CMS yet — drafts people
have not published, undo history, the version log. This page is for the
self-hoster: where that work lives, how to keep it, and what a restart does to
it.

## One SQLite file

Session state lives in a single SQLite file managed by `better-sqlite3`: draft
pages, undo and redo history, the version log, recent edits, chat history, site
configs, the publish log, and the per-page publish baselines that decide whether
a page follows its site. The same is true in library mode and on the standalone
server.

Mutations are coalesced: a request's synchronous writes are debounced for 30 ms
and then snapshotted into SQLite inside one transaction, rather than each write
hitting the disk on its own.

### Files on disk

Under `.data/` in the process's working directory by default. In library mode
that is your project root. In a monorepo checkout of Avocado itself, the
standalone server uses the repository root's `.data/`.

| File | What it is |
| - | - |
| `orchestrator.db` | the live state |
| `orchestrator.db-wal`, `orchestrator.db-shm` | SQLite's write-ahead log and shared-memory index |
| `orchestrator-state.json.migrated-<iso-ts>` | a one-shot archive of the legacy JSON writer's output, kept as a safety net |
| `orchestrator.db.backup-<ts>` | rolling snapshots, standalone server only |
| `generated-images/` | generated and uploaded images, unless `ORCHESTRATOR_GENERATED_IMAGE_DIR` moves them |
| `chat-telemetry.ndjson`, `chat-feedback.ndjson` | [telemetry](/observability/chat-telemetry-events) and thumbs-up/down feedback |
| `secrets.key` | only with the pre-alpha `SITE_OPS_AGENTS=1`: the key that encrypts stored connection secrets, unless `AVOCADO_SECRETS_KEY` is set. Back it up with the database, or those secrets cannot be decrypted. |

<Warning>
  **`.data/` must be in `.gitignore`.** The orchestrator writes it into the project
  root on the first request, and `create-next-app`'s `.gitignore` does not cover
  it — so the first `git add .` after the first run commits a database.
</Warning>

### Configuration

| Variable | Default | What it does |
| - | - | - |
| `ORCHESTRATOR_DB_FILE` | `.data/orchestrator.db` | Path to the database. Empty means the default. It becomes `:memory:` under `DEMO_MODE=1` or `NODE_ENV=test`. Set the literal `:memory:` to force ephemeral state anywhere. |
| `ORCHESTRATOR_DB_BACKUP_INTERVAL_HOURS` | `24` | How often the standalone server takes a `VACUUM INTO` snapshot. Minimum 1. |
| `ORCHESTRATOR_DB_BACKUP_LIMIT` | `14` | How many rolling snapshots to keep. Minimum 1. |
| `ORCHESTRATOR_STATE_FILE` | — | A legacy JSON state file, read once on first boot. It is renamed after migration and never rewritten. |
| `ORCHESTRATOR_JSON_MIGRATION_TTL_DAYS` | `14` | Retention for that archive before it is swept |

## Backups

**The standalone server** writes a consistent snapshot with `VACUUM INTO`
without stopping. The first one is taken about a minute after start, so a crash
loop does not skip the day's backup. After that it runs every 24 hours by
default and keeps the last 14, next to the database as
`orchestrator.db.backup-<timestamp>`. A failed snapshot is logged and skipped.
Nothing is snapshotted when the database is `:memory:`.

**Library mode takes no snapshots.** `createOrchestrator()` has no timer of its
own, so back up `.data/orchestrator.db` yourself, with SQLite's online backup or
a `VACUUM INTO` from a scheduled job. A plain file copy of a database that is
being written to is not a consistent backup.

That gives you point-in-time recovery inside the retention window and nothing
outside it. **If the draft state matters to you, copy those snapshots off the
volume**, the same way you would for any other database. Snapshot count and
interval are the two dials.

Restoring is a file copy: stop the orchestrator, put the snapshot in place of
`orchestrator.db` (removing the `-wal` and `-shm` files alongside it), start it
again.

## What is capped

State does not grow without limit. The caps are per session, and each site has
its own session. Hitting a cap discards the oldest entry rather than failing:

| What | Cap |
| - | - |
| Undo/redo stacks | 50 entries per page, per direction |
| Version log | 100 entries |
| Publish log | 200 entries |
| Recent edits | 10 |
| Chat history | 6 messages |

The practical consequence for the people using the editor is on
[review and undo](/editing/review-and-undo): history is deep, but it is not
forever.

## What is *not* persisted

Several things live only in memory and are gone on restart, by design:

* pending approval plans awaiting an Apply/Discard decision
* continuation chains
* publish status
* per-session image-source preferences

So a restart mid-review loses the held plan, not the draft. The person is asked
again; nothing they had already applied is affected.

A restart also voids every editor sign-in token, so people are asked for the
password again. See [security and access](/reference/security#what-happens-on-restart).

## Checking that writes reach disk

A store that cannot open does not fail requests. The orchestrator keeps
answering from memory, so the only symptom is edits that revert after a restart
or a module reload. `GET /status/planner` reports it on both transports:

```json theme={null}
{ "persistence": { "ok": false, "reason": "Could not load better-sqlite3 (...)" } }
```

`reason` is the first failure, which is usually why the store never opened. In
library mode the usual cause is `better-sqlite3` missing from the host app's own
dependencies.

## Shutdown

On the standalone server, `SIGTERM` and `SIGINT` run three steps in order:

1. `app.close()` — drain in-flight handlers
2. `persistStateNow` — flush any debounced write, then flush feedback
3. `resetStore()` — close the database, which checkpoints the WAL

Give the container time to do that. A hard kill during step 1 can lose up to the
last 30 ms debounce window, and skipping step 3 leaves a WAL for the next boot to
recover from — survivable, but not free.

In library mode the host app owns the process, and the orchestrator has no
shutdown sequence of its own. Each change is written 30 ms after it is made, so
a normal stop loses nothing that finished more than a moment earlier.

## Running with no durable state

Setting `ORCHESTRATOR_DB_FILE=:memory:` is legitimate for a public demo or an
ephemeral preview environment, where losing drafts on restart is the point.
`DEMO_MODE=1` selects it for you. [Demo mode](/operations/demo-mode) is the
other half of that setup.

Anywhere else, a serverless or otherwise ephemeral filesystem is a trap: the
orchestrator will appear to work and silently lose every draft that was not
published before the instance recycled. Two instances running at once each keep
their own state, so edits also appear to vanish between requests. This applies
to library mode on a serverless host too. See
[Vercel](/operations/vercel-deployment).

## Deploying

* [Docker](/operations/docker-deployment) — mount a persistent volume at the
  directory holding `ORCHESTRATOR_DB_FILE`
* [Vercel](/operations/vercel-deployment) and
  [Netlify](/operations/netlify-deployment) — the site and the editor deploy
  there; the orchestrator's state needs somewhere with a persistent disk
* [Security and access](/reference/security) — what has to be set before any of
  this is reachable


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.