Skip to content

embabel export <file> and embabel import <file>

One file that holds an appliance — its graph, its worlds and its setup — and that any other appliance can take, whatever either keeps its graph in: Neo4j, the engine’s SQLite file or snapshot journal, Postgres, Oracle.

Terminal window
embabel export my-appliance.tar.gz # the whole appliance
embabel import my-appliance.tar.gz --login ann # on the other one: replace its graph and worlds, verified
embabel export support.tar.gz --world ann --no-private # one world, nothing private: a support bundle
embabel export setup.tar.gz --setup # realms, apps, lenses, preferences; no data
embabel import setup.tar.gz # add that setup to this appliance's world

What a bundle holds. One .tar.gz — which Finder, Windows’ own tar and any Linux open without anything installed:

Entry
manifest.json what made it and when, the store it came from, live or cold, the embedding model and the widths of the vectors, counts and checksums for every combination of labels and every relationship type, and what was left out and why
graph/graph.sqlite the graph in the Embabel engine’s own format, checkpointed — the same file whatever the source was
graph/graph.json the figures the graph is checked against on the way in
data/users/… the worlds: each user’s configuration, documents, apps, realms written there
data/chat-attachments/… files attached in chat (not with --no-private)
data/admin/… what every world on the appliance shares: which realms’ code was admitted, and custom themes (not with --world)

It is written as a stream: the graph is copied once, into the engine’s format, and everything else goes from the data volume through the compressor into the file without a second copy anywhere. The size before and after compression is printed at the end — an appliance holding a 350,000-node graph with 31,000 vectors makes a 2.3 GB stream and a bundle of about 530 MB.

Why gzip: measured on that bundle, gzip wrote 527 MB in 46 seconds; xz saved a quarter of the size and took eight minutes; zstd is as fast and a little smaller, but neither macOS’s nor Windows’ built-in archive tools open it, and the CLI would need a tool most machines do not have.

Never a secret. Not the credential file (password hashes, API keys, provider keys, the licence), providers.env, the setup token, .env, any world’s secrets.env, or any file named like a key or a credential. Then every file that is selected is scanned for the shapes credentials take — a password hash, a provider or GitHub key, a private key, an OAuth token, a password inside a connection URL — and one match stops the export, naming the file and the kind of secret, never its value. Before anything is written, the export prints what goes in, what was left out, and the size, and asks.

Live or cold. An appliance on the engine’s SQLite file (every new one) exports live: SQLite’s online backup is a consistent copy of the graph as of one instant, so the app keeps running. Neo4j, the snapshot journal, Postgres and Oracle are read across many statements, and a write landing between two of them would export a graph that never existed — so for those the app stops while the graph is read (its database stays up) and starts again before the worlds are copied. The manifest says which it was and for how long.

Scoping.

Flag Leaves out
--world ann (or ann/default) every other user’s records in the graph and their worlds on disk; records that belong to nobody — reference data, the shared catalogue — stay
--no-private anything labelled Private, chat sessions, messages and attachments, memories (propositions and their mentions), email and calendar signals, the server’s summaries of email threads, notebooks and tool traces

The manifest counts what each one removed, by label group.

Import. embabel import reads the manifest first and refuses a bundle of a format this appliance does not read. It unpacks the bundle onto the data volume, checks the graph against the manifest’s checksums (a damaged or altered file is refused here, before anything changes), shows what it holds, and asks. Then:

  1. The app stops. .env and secrets.env are kept beside the new ones.
  2. The graph store is readied, empty: the SQLite file is set aside; on Postgres or Oracle the import writes under a new table prefix, so the tables already there are untouched; a Neo4j appliance moves onto the engine’s SQLite file (its Neo4j volume is kept, and GRAPH_TYPE=NEO4J goes back).
  3. The worlds, chat attachments and shared setup are set aside as *.before-import-<time>, and the bundle’s put in their place. They travel together because each world is stamped with the graph it was made with, and the server will not start on a world from another graph.
  4. The app starts once with the server’s store import switched on — the same copy embabel graph migrate uses — writes the bundle’s graph into the store and compares every value with it.
  5. When the report is verified and its counts are the manifest’s, the app restarts normally. Anything else puts every file back and starts the appliance as it was. On Postgres or Oracle the tables the attempt wrote under its new prefix are dropped (only when that prefix held nothing before it); if the drop cannot be made, the message names the prefix to drop by hand.

This appliance’s settings, keys and credentials are kept. A bundle carries no passwords, so the imported users have none here: --login ann sets one for ann (asked for, or read from EMBABEL_IMPORT_PASSWORD) through the server’s own credential store, or use embabel reset-password.

Embedding drift. Vectors made by one model and searched with another answer nonsense, even when the two models share a width. The server records the model that made the vectors — name, provider and width — in the graph beside them, so it travels with the bundle; the manifest carries it with the widths. The import compares it with this appliance’s model by name, not by width, and says when they differ; the server compares it again as it starts and reports a difference under drift.model in GET /api/v1/admin/embedding/status. The repair is to re-embed everything as an administrator:

Terminal window
curl -u admin -X POST -H 'Content-Type: application/json' -d '{"force": true}' \
http://localhost:11042/api/v1/admin/embedding/reindex

— or embabel embeddings use <model> to search with the model that made them.

Setup only (--setup). How somebody set their world up, without anything it learned, read through the running app (sign in as yourself: EMBABEL_USER / EMBABEL_PASSWORD, or asked):

What How it travels
realms as references: the repository, the ref and the commit; installed again on the other side through the server’s own install, so admission runs there. A realm written in the world travels as its files. One linked from a folder on this machine (embabel realms link) travels by name only and is reported
apps built here data/apps
lenses, watches from the graph, through the API; a watch’s delivery secret and headers are not carried
views, actions and handlers, skills, agents, schedules, commands, personalities and prompts, tours, focuses, goals, connections the world’s own files under config/ — what git says changed since the world was cloned from its template
model role assignments, active personality, notification routing, world settings config/world.yml
console preferences: the chosen theme and its rotation, wallpaper, window style and layout, pinned apps and the dock config/console.json, which the server keeps for the console (/api/v1/profile/console)
custom themes admin/themes/*.css
embedding model recorded and compared, never switched

Never in it: credentials; the persona the server writes from email and calendar; caches. A file or lens that holds a credential’s shape is withheld and named in the manifest — re-create that connection on the other side. embabel import of a setup bundle adds to a world on a running appliance (--world USER/WORLD, default the signed-in user’s): replaced files are kept under .setup-import-<time>, realms already installed are left alone, the world is rebuilt, and the report lists what was installed and what failed.

What only the browser knows. The console’s view options, sound and tours completed stay in the browser’s localStorage. No appliance has them, so no bundle carries them. A browser that was open during an import shows the imported theme, layout and pins once it is reloaded.

Flag
--world USER[/WORLD] export: one world only; import of a setup: the world to add it to
--no-private export: leave private content out
--setup export: the setup only
--login USER import: set a password for this imported user
--yes skip the confirmation (and replace an existing file on export)
--worlds / --me which door, when it cannot be told from what is running