This page covers the mechanics of upgrading Flair — the general path, valid across
versions. For what changed in a specific release (behavior changes, new surfaces,
breaking changes), see CHANGELOG.md — each version has its own
## [X.Y.Z] section. Check the CHANGELOG entries between your current version and the
target version before upgrading anything you depend on in production.
There are two things you might be upgrading:
- A local npm install — the common case:
flairrunning on your own machine or a VPS, installed vianpm install -g @tpsdev-ai/flair. - A Flair component deployed to a Harper Fabric cluster — a different mechanism
(
flair deploy/flair upgrade --target), covered separately below.
# 1. Back up first, always
flair backup > ~/flair-backup-$(date +%Y%m%d).json
# 2. Check what's outdated (doesn't install anything)
flair upgrade --check
# 3. Upgrade — installs, restarts, and verifies the new version is actually
# serving, all in one step (see "Upgrade is a transaction" below)
flair upgrade
# 4. Verify
flair status
flair doctorflair upgrade checks and upgrades the npm-global packages (@tpsdev-ai/flair,
@tpsdev-ai/flair-mcp) and, if present, the openclaw-flair plugin (via
openclaw plugins install --force --pin, not npm install -g — it needs OpenClaw's
own plugin loader). Pass --all to also see flair-client (normally hidden as a
transitive dependency). Other integrations upgrade in their own ecosystem, not via
flair upgrade: pi-flair (pi's plugin manager), langgraph-flair / hermes-flair
(pip / your Python package manager), n8n-nodes-flair (n8n's Community Nodes UI).
As of flair#635, flair upgrade is install → restart → verify →
rollback-on-failure, in one step — installing new code without restarting used
to leave the OLD process serving while the version on disk lied about what was
actually running:
- Restart happens automatically after install. Pass
--no-restartto stage the new packages without bouncing the process yet (the old opt-in--restartflag still parses but is now a no-op — restart is the default). - Post-restart verification (skip with
--no-verify) confirms the restarted instance answers/Health, that an authenticated request round-trips, and that the reported running version matches what was just installed. - On verification failure,
flair upgradeautomatically reinstalls the previously-running@tpsdev-ai/flairversion, restarts again, and re-verifies — then exits nonzero with a clear report of what failed. If the rollback itself fails verification, it says so loudly and points at the concrete pre-upgrade snapshot path (see "Pre-upgrade snapshot" below) instead of retrying in a loop — see Downgrade for the restore procedure.
flair#637 added a physical, byte-exact snapshot of ~/.flair/data — the whole
directory (RocksDB files, keys, config, admin-pass), not just the logical records a
flair backup JSON export covers. As of 2026-07-08 this is opt-in: pass
--snapshot to flair upgrade to take one before the package swap. It's off by
default — matching how Harper's own upgrade CLI behaves (it recommends a backup before
proceeding, but never auto-tars your data directory for you) — because the
tested-downgrade guarantee below already covers the failure mode a snapshot exists
for, and the old opt-out default meant every upgrade paid the cost (the data dir can
be 800MB+; keep-last-3 retention meant up to ~2.5GB of snapshots sitting around)
whether or not you wanted it.
flair upgrade --snapshotSnapshotting data before upgrade...
✅ Snapshot: ~/.flair/upgrade-snapshots/flair-data-2026-07-08T14-32-01-118Z.tar.gz (842.3 MB)
Restore: flair snapshot restore "~/.flair/upgrade-snapshots/flair-data-2026-07-08T14-32-01-118Z.tar.gz"
Pruned 1 older snapshot (keeping last 3)
If you omit --snapshot (the default) and a data directory exists, flair upgrade
prints a non-blocking recommendation instead of silently skipping it — it never
prompts or blocks, so scripted/non-interactive upgrades are unaffected:
No pre-upgrade snapshot will be taken.
To capture one first: `flair snapshot create` (physical) or `flair backup` (logical export), or re-run with --snapshot.
- Location:
~/.flair/upgrade-snapshots/flair-data-<timestamp>.tar.gz— owner-only (0600), with every file inside it at its original mode (so a0600key oradmin-passfile stays0600after a restore, not whatever tar's default would be). - Retention: keeps the newest 3 snapshots, prunes older ones automatically after
each successful snapshot — whether taken via
--snapshotorflair snapshot create(below); both draw from the same~/.flair/upgrade-snapshots/pool. - Consistency: when a snapshot is taken (
--snapshot, orflair snapshot create), Flair is briefly stopped, snapshotted, and immediately restarted on the same version before anything else happens — a plain file copy of a running Harper's data directory isn't guaranteed point-in-time consistent (Harper 5.x stores tables in RocksDB — an LSM engine whose WAL, MANIFEST, and SST files can be mid-write/mid-compaction), so the snapshot always happens against a quiesced directory. During an upgrade this means a short stop/start blip even with--no-restart— the snapshot's correctness doesn't depend on whether you want a restart after the upgrade, those are separate questions. (A native Harper backup operation,get_backup, was evaluated and rejected here — see the code comment abovecreateDataSnapshotinsrc/cli.tsfor why: it backs up one table/schema at a time over the running HTTP API, not the whole data directory, and would be strictly less complete than a plain file copy.) - Failure is a hard stop when requested: if you passed
--snapshotand the snapshot itself fails (disk full, permissions, etc.), the upgrade aborts before any package changes — no packages are swapped, Flair is restarted on the version it was already running.
The same mechanism is available on its own, independent of upgrading:
flair snapshot create # take one now (default: ~/.flair/data)
flair snapshot create --data-dir <path>
flair snapshot list # list what's under ~/.flair/upgrade-snapshots/
flair snapshot list --json
flair snapshot restore <path> # stop Flair, replace the data dir, restart
flair snapshot restore <path> --yes # skip the confirmation promptflair snapshot restore is destructive — it deletes the current data directory and
replaces it with the snapshot's contents — so it asks for confirmation unless --yes
is passed, and refuses outright in a non-interactive shell without --yes (it will
never silently destroy data on an unattended run). Symlinks and file modes extract
exactly as the snapshot recorded them; nothing outside the original data directory is
ever touched.
flair snapshot (physical) vs flair backup/flair restore (logical) — not the
same thing:
flair snapshot |
flair backup / flair restore |
|
|---|---|---|
| What | Byte-exact tar.gz of ~/.flair/data |
JSON export of Agent/Memory/Soul records |
| Scope | Everything — RocksDB files, keys, config, admin-pass |
Just the records, over the HTTP API |
| Portability | Same host, same Flair/Harper version | Portable across hosts and versions |
| Use for | Undoing an upgrade that wrote data the old version can't read | Migrating data, or a lightweight logical restore |
Use whichever (or both) fits — they're complementary, not redundant, which is why they
live under separate command namespaces instead of overloading restore.
If you'd rather upgrade by hand instead of flair upgrade:
npm install -g @tpsdev-ai/flair@latest
npm install -g @tpsdev-ai/flair-mcp@latest # if installed
flair restartflair doctor flags issues after an upgrade (stale embeddings, hash-fallback rows,
connectivity problems) and can auto-remediate some of them with flair doctor --fix
(--dry-run to preview first).
A Flair instance deployed to a Harper Fabric cluster isn't a local npm package — it's a
component pushed via flair deploy. Upgrade it in place with:
FABRIC_USER=<admin> FABRIC_PASSWORD=<pass> \
flair upgrade --target https://<fabric-node>/<instance-name>(or --fabric-password-file <path> instead of the FABRIC_PASSWORD env var — reads the
password from a file, chmod 600). This resolves the target version (latest published
@tpsdev-ai/flair, or pin one with --version), stages a clean deployable with the
required @harperfast/harper version pin applied (--harper-version to override),
confirms the staged Harper build before deploying, then reuses flair deploy to push it
and verifies the result. --check shows the version diff and plan without deploying
anything; --yes skips the confirmation prompt for scripted use.
Inline --fabric-user/--fabric-password flags also work — discouraged: both leak to
shell history and ps for the life of the process, so avoid them on shared/multi-user
hosts:
flair upgrade --target https://<fabric-node>/<instance-name> \
--fabric-user <admin> --fabric-password <pass>As of flair#636, both flair deploy and flair upgrade --target automatically run a
fleet convergence sweep after a successful deploy — Harper's own "Successfully
deployed" (and the served-API verify above) only confirm the origin node; nothing
previously checked that peers actually converged, which is exactly the gap that let
the 0.21.0 deploy report success while a peer was still throwing replication errors.
The sweep hits the origin plus every Flair federation peer on file (GET /FederationPeers) and checks health, auth, and version. Skip it with
--no-fleet-verify, or run it standalone against any already-deployed instance:
FABRIC_USER=<admin> FABRIC_PASSWORD=<pass> \
flair fleet verify --target https://<fabric-node>/<instance-name>(inline --fabric-user/--fabric-password also work but are discouraged — see above.)
Exit codes:
| Code | Meaning |
|---|---|
| 0 | All nodes verified: healthy, authenticated, and version-matched |
| 1 | Origin failed (unreachable, unauthenticated, or wrong version) |
| 2 | Origin OK, but a reachable peer is running a different version (skew) |
| 3 | Origin OK, no skew among reachable peers, but a peer couldn't be verified at all (unreachable, auth rejected, or no endpoint on file) |
What "peer" means here — read before trusting a green sweep: this checks
Flair's own federation peer table, not Harper Fabric's own cluster-replication
nodes. Harper's cluster_status operation (the one that would answer "what nodes are
in this cluster and are they in sync") is harper-pro-only and unavailable in the OSS
@harperfast/harper build this CLI ships — there is no way for this CLI to enumerate
Fabric's own replication topology, on the origin or anywhere else. A Fabric replica
that was never separately paired as a Flair federation peer (flair federation pair)
is invisible to this sweep: 0 peers known means "0 peers on file," never "0 peers
exist." A peer with no usable endpoint is reported unverifiable — never silently
dropped, never shown green. The sweep also needs Basic-auth credentials
(FABRIC_USER/FABRIC_PASSWORD env, or the discouraged inline
--fabric-user/--fabric-password) to authenticate each peer probe; a token-only
(--fabric-token) deploy skips it with a note instead of a silent no-op.
Two situations require a re-embed pass, and flair doctor will flag both:
- The embedding model changed between versions — old memories carry vectors from the previous model and won't compare correctly against new ones.
- Harper's internal vector storage changed across a version bump (this has happened between Harper point releases, e.g. HNSW-index-internal changes) — even with the same embedding model, stored vectors may need to be regenerated to match what the new Harper build expects.
flair doctor reports the counts:
⚠️ 49 memories have hash-fallback embeddings (512-dim)
Current model produces 768-dim vectors
Run: flair reembed
Fix with:
flair reembed # all agents, all stale rows
flair reembed --stale-only # only mismatched-model-tag rows
flair reembed --agent <id> # scope to one agent
flair reembed --dry-run # show the count without writingThis runs in the background — the server stays available while it re-embeds. This is
also the step CI's upgrade-smoke job exercises directly: it upgrades a running
instance from the latest published version to the candidate build, then runs
flair reembed before asserting old memories are still searchable and new writes
round-trip. See the upgrade-smoke job in
.github/workflows/test.yml for the exact sequence
if you want to see it scripted end-to-end.
- Data format: Flair stores data in Harper's native format; Harper maintains
backward compatibility within a major line. Cross-Harper-version data compatibility
is exactly what
upgrade-smokeexists to catch regressions in — check the CHANGELOG for any called-out breaking change before a major jump. - Keys: Ed25519 keypairs are version-independent. No key migration is ever needed between Flair versions.
- Config:
~/.flair/config.yamlformat is additive — new options fall back to defaults when absent, old options aren't removed out from under you.
If an upgrade causes problems immediately after upgrading (code-level, not data):
# Install a specific previous version (substitute your last known-good)
npm install -g @tpsdev-ai/flair@<previous-version>
flair restart
# If data looks wrong, restore from your pre-upgrade backup
flair restore < ~/flair-backup-<date>.jsonflair upgrade does this automatically on a failed post-restart verification — see
"Upgrade is a transaction" above. This section is for doing it by hand, e.g. after
--no-verify, or after problems surface later than the automatic check catches.
Rolling back the package (above) assumes the data on disk is fine — only the new code was the problem. If the new version actually wrote data in a way the old version can't read, package rollback alone isn't enough. This is what the flair#637 pre-upgrade snapshot exists for.
# 1. Find the snapshot (the exact path/command `flair upgrade --snapshot` printed
# when it ran, or list them yourself):
flair snapshot list
# 2. Restore it — this stops Flair, replaces ~/.flair/data, and restarts:
flair snapshot restore ~/.flair/upgrade-snapshots/flair-data-<timestamp>.tar.gz
# 3. Install the previous version
npm install -g @tpsdev-ai/flair@<previous-version>
flair restart
# 4. Verify
flair status
flair doctorflair snapshot restore does the stop/replace/restart in one step (confirming before
the destructive replace unless you pass --yes); the equivalent by hand is flair stop && rm -rf ~/.flair/data && mkdir -p ~/.flair/data && tar -xzf <snapshot> -C ~/.flair/data && flair start, in case you'd rather not use the command.
If you don't have a snapshot (upgraded without --snapshot, or on a version from
before flair#637 shipped it), there is no tested way back short of restoring from a
flair backup JSON export on the older version — do not assume an untested downgrade
boot will work.
This used to be aspirational — nobody had actually checked. test/compat/downgrade-boot.test.ts
now checks it for real, nightly, alongside the mixed-version federation suite (both run
from .github/workflows/federation-compat.yml's bun test test/compat/): it boots the
current build, writes a memory and a presence row, stops it without wiping the data
directory, then boots the last npm-published @tpsdev-ai/flair against that exact
same directory and confirms it comes up healthy and can read both rows back.
As observed when this suite was added (2026-07-08): the npm-published baseline
(0.21.0) boots cleanly against data written by a HEAD build roughly 14 commits ahead of
it (several security-hardening and CLI-behavior changes, no Flair schema migration, and
only a patch-level @harperfast/harper bump, 5.1.15 → 5.1.17) — both the memory and
presence rows written by the newer build were readable through the older build's own
HTTP surface after the downgrade boot. No downgrade break has been found across that
gap.
This is not a blanket "downgrade is always safe" guarantee for every future release —
it's a live, continuously-checked claim. If test/compat/downgrade-boot.test.ts starts
failing (a real schema-incompatible change landing without a documented break), this
section and the test's own assertions get updated together to say so explicitly, the
same way this paragraph does today. Check the suite's latest nightly run (or the
CHANGELOG for an explicit "no downgrade past X" note) before relying on this for a jump
you haven't personally tested.
CHANGELOG.md— what actually changed, version by version.docs/releasing.md— how a release gets published in the first place (staged npm publish with 2FA approval), if you're curious why a new version shows up when it does.docs/deployment.md— initial install / deployment, as opposed to upgrading an existing one.