Skip to main content
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/.
.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.

Configuration

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: The practical consequence for the people using the editor is on 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.

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:
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 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.

Deploying

  • Docker — mount a persistent volume at the directory holding ORCHESTRATOR_DB_FILE
  • Vercel and Netlify — the site and the editor deploy there; the orchestrator’s state needs somewhere with a persistent disk
  • Security and access — what has to be set before any of this is reachable