Operate a self-hosted Optimal Engine

This page is for a self-hosted Engine. On MIOSA, backups, upgrades, and resizing are done for you; see Optimal Engine on MIOSA.

Backups

A backup is an online copy of the SQLite database, taken with VACUUM INTO while the Engine keeps serving.

mix optimal.backup /var/backups/engine-$(date +%F).db --verify

--verify runs an integrity check on the copy. Over HTTP, an admin key can call POST /api/backup with an optional {"name"}; the file is written in the backup folder and the response has its path, size_bytes, checksum_sha256, and integrity_status.

Offsite backups

With OPTIMAL_BACKUP_KEY and the OPTIMAL_S3_* settings, backups can be encrypted and sent to S3-compatible storage:

mix optimal.backup /tmp/snapshot.db --offsite
mix optimal.backup --list-offsite

Set OPTIMAL_OFFSITE_BACKUP_INTERVAL_MS (60000 or more) to take them on a schedule. The Engine keeps the newest 14 offsite and 3 local copies by default (OPTIMAL_OFFSITE_BACKUP_KEEP, OPTIMAL_OFFSITE_BACKUP_KEEP_LOCAL).

Restore

Stop the Engine first.

mix optimal.restore /var/backups/engine-2026-10-23.db
mix optimal.restore --from-offsite <backup id>

The restore checks the copy’s checksum and integrity before it changes anything, and keeps the previous database beside it as <db>.pre-restore-<time>.

Upgrades

  1. Take a backup.
  2. Install the new version: pull the new tag and rebuild, or start the new image on the same volume.
  3. Start it. Migrations run at boot, each in its own transaction, before the API answers.

If a migration fails, it is rolled back, the Engine refuses to open the store, and it exits with migration <number> failed and was rolled back: <cause>. Migrations that ran before it in the same boot stay applied, so going back means restoring the backup from step 1.

Check what is running and what it expects:

curl -s http://127.0.0.1:4200/api/version -H "Authorization: Bearer $OPTIMAL_ENGINE_API_KEY"
mix optimal.migrate --status

mix optimal.conformance --url <url> --key <key> runs the conformance suite against a live Engine, prints a JSON report, and exits non-zero on any failure, so it can gate an upgrade.

A release build runs the same maintenance with the API off: bin/optimal maintenance migrate, health, embed_backfill, graph_backfill, facts, or claims. It exits 0 when done, 1 on failure, and 78 when the Engine is in the wrong state for the task.

Sizing

OPTIMAL_SIZE_PROFILE sets the rate limit per key and the advised limits the Engine measures itself against.

ProfileMachineDiskVectorsMemoriesAPI requests per minute per key
agent1 vCPU, 1 GiB2 GiB50,000100,000300
workspace1 vCPU, 2 GiB10 GiB250,000500,000600
business (default)2 vCPU, 4 GiB25 GiB1M2M1,200
platform4 vCPU, 8 GiB100 GiB5M10M3,000
scale8 vCPU, 16 GiB or more250 GiB or moremoremore6,000

The vector and memory numbers are advice, not hard caps. At 70% of any of them, GET /api/storage and /api/metrics recommend the next profile. A key can get its own rate limit with rate_limit_per_minute in its metadata.

The Engine is a single writer: run one Engine per data folder.

Replication

Two SQLite Engines can keep a workspace in sync.

RouteWhat it does
POST /api/sync/enableStart recording changes in a workspace
POST /api/sync/peersAdd a peer: {"workspace", "url", "api_key", "policy", "max_class"} (admin)
POST /api/sync/runSync now
GET /api/sync/status, /changes, /conflictsState, the change log, and conflicts
POST /api/sync/conflicts/:id/resolveResolve one conflict

policy is push, pull, both, or none. max_class is the most sensitive data class that may leave: public, internal, confidential (default), or restricted. The peer’s key is stored encrypted with CONNECTOR_KEY. Peers sync every 20 seconds by default. Assets, connectors, API keys, jobs, and tools are not replicated.

Move a workspace

A workspace bundle is one file with a whole workspace in it.

RouteWhat it does
POST /api/workspaces/:id/bundleWrite a bundle
POST /api/bundles/importImport one
POST /api/bundles/jobs/export, POST /api/bundles/jobs/importThe same as background jobs, for large workspaces
GET /api/bundles/jobs/:idFollow a job
POST /api/workspaces/:id/finalize-moveRemove the workspace from the old Engine after the move

Bundles are limited to 5 GiB (OPTIMAL_BUNDLE_MAX_BYTES).

Erasure and retention

mix optimal.compliance dsar <principal id>
mix optimal.compliance erase <principal id> --preview
mix optimal.compliance erase <principal id>
mix optimal.compliance hold place <signal id> --by <actor> --reason "litigation"
mix optimal.compliance hold list
mix optimal.compliance retention sweep --dry-run
ActionEffect
dsarExports what the Engine holds about one person
eraseDeletes it, and what was derived from it. --preview shows what would go; --force overrides legal holds.
holdPlaces, lists, or releases a legal hold that stops deletion
retention sweepApplies each source’s retention rule
POST /api/workspaces/:id/archiveArchives a workspace; its rows are kept

An erasure does not reach backups, offsite copies, exported bundles, or replicas that already synced the data. SQLite does not overwrite freed pages, so deleted text can remain in the database file until it is vacuumed.

Observability

RouteWhat it returns
GET /api/healthup, degraded, or down, with the check that failed
GET /api/metricsJSON counters, latencies, connector state, and size gauges
GET /api/metrics/prometheusPrometheus text, such as optimal_engine_search_latency_ms, optimal_engine_memories_total, optimal_engine_uptime_seconds
GET /api/viabilityA report on the workspace as an operating system, with alerts

The Prometheus route needs a key when keys are required; it is only exempt from rate limits. deploy/grafana-dashboard.json in the repository is a ready dashboard for it. Logs go to standard output as [level] message.

Was this page helpful?