One SQLite file
Session state lives in a single SQLite file managed bybetter-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/.
Configuration
Backups
The standalone server writes a consistent snapshot withVACUUM 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
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:
app.close()— drain in-flight handlerspersistStateNow— flush any debounced write, then flush feedbackresetStore()— close the database, which checkpoints the WAL
Running with no durable state
SettingORCHESTRATOR_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