71 KiB
summary, read_when, title, doc-schema-version
| summary | read_when | title | doc-schema-version | |||
|---|---|---|---|---|---|---|
| Set up and operate a PostgreSQL-backed Gateway, with support boundaries, ports, backups, and recovery |
|
PostgreSQL operator guide | 1 |
PostgreSQL operator guide
Use this guide to provision PostgreSQL for one Gateway, move existing data, and operate the resulting installation. PostgreSQL is a supported alternative for the five configurable store families on qualified PostgreSQL 16–18 runtimes. SQLite remains the default. Selecting PostgreSQL does not move every local file into the server. Review the support matrix before deployment.
Support matrix
Supported means the named flow has PostgreSQL conformance or live acceptance proof. It does not promise identical search ranking or latency across engines. Full conformance covers PostgreSQL 16–18 with UTF8 and the search extensions below. The additional-major qualification uses local Homebrew servers; Linux CI continues to run PostgreSQL 17. See the qualification evidence.
| Family or surface | Status | Scope or remaining proof |
|---|---|---|
state, including Matrix account/token roots |
Supported | Runtime, bidirectional port, matched backup/restore and schema migrations. |
agent |
Supported | Runtime, transcripts, memory/search, bidirectional port and matched backup/restore. |
workboard |
Supported | Runtime, bidirectional port and matched backup/restore. |
logbook |
Supported | Runtime, bidirectional port and matched backup/restore; frame files stay local. |
team-reports |
Supported | Runtime, bidirectional port and matched backup/restore; collection scratch stays session-local. |
| CLI and interactive setup/onboard | Supported | Includes one-shot commands and Doctor; PostgreSQL onboard --reset deliberately refuses. |
| Gateway turns and CLI parity | Supported | Real agent turns, tools, compaction, restart, child sessions and plugin operations. |
| Doctor | Supported | Per-store configured/observed engines, schema, extensions, reachability and custody; unknown evidence stays unknown. |
| Update admission, forward migrations and recovery | Supported | Exact captures and state/agent rollback; independent writes can prevent automatic recovery. |
| Published stable 2026.10.5 → current upgrade | Supported, qualified scope | Published 2026.10.5 SQLite → current → PG17 port, foreground Gateway and matched-backup rollback verified. Stable PG → current is not applicable; evidence and service/turn limits. |
| Port, backup, restore and disaster recovery | Supported | All five families, retained roots and mixed engines; memory indexes require the documented rebuild step. No cluster/PITR orchestration. |
| Mixed engines | Supported | All four state/agent pairs and independently selected plugin families; one shared PostgreSQL connection configuration. |
| Independent Gateways on one server | Supported | Separate databases recommended; distinct namespaces in one database also tested. One live Gateway per store. |
| Multiple active Gateways sharing a store | Not supported | No shared-store failover or active-active ownership protocol. Use independent stores. |
| Memory/search and standing intents | Supported | Real indexing, rebuild, filtering, bounds, forget/reset and missing-extension refusal; ranking is engine-local. |
| Plugins using SDK stores | Supported | SDK shared-state stores and registered family owners; arbitrary private plugin databases and LanceDB are outside this selection. |
| Warm-path performance | Supported, measured | No equal-latency guarantee; see performance evidence. |
| PostgreSQL 16–18, UTF8 | Supported | Local Homebrew 16.15/18.6 full suites and PG17 CI/live proof; scope and results. |
| Other admitted PostgreSQL majors | Experimental | The admission floor is 16. Majors outside 16–18 need their own full conformance run. |
| DSN with env/file SecretRef | Supported | Host resolution, missing-secret refusal and CLI/Gateway use. |
| Exec SecretRef | Supported | CLI/Gateway host resolution, worker propagation, restart, and failed/empty provider refusal; qualification. |
| TLS DSN | Supported | verify-full with an absolute sslrootcert path; certificate/hostname verification and invalid-certificate/plaintext refusal; qualification. |
unaccent, pg_trgm, vector |
Supported requirements | Positive and missing-extension cases tested; administrators install extensions. |
| Headless Linux, including Linux under WSL2 | Supported | Linux PostgreSQL conformance and CLI/Gateway paths. |
| Headless macOS CLI | Supported | Real PostgreSQL CLI, interactive setup and Gateway runs. |
| Native Windows + PostgreSQL | Supported | Native Windows client and PG17 server, including fresh backup restore; qualification and limits. |
| Native Windows managed service | Supported | Install, readiness, turns, graceful stop, restart continuity and data-preserving uninstall; qualification. |
| Native Windows package update with PG state | Not yet applicable | No published release selects PostgreSQL for state and agent stores; requalify when one ships. Evidence. |
| Native Windows database port | Not supported yet | Use fresh PostgreSQL state on Windows, or run the port on Linux/macOS; details. |
| macOS app | Experimental: pending signed-app proof | Credentials stay local, runtime pins stay local, and exec approvals use the shared owner. Combined signed-app acceptance is pending. |
| iOS/iPad app | Not supported without real-device proof | Simulator evidence is insufficient; real-device authentication and approval flows remain unqualified. |
Anchors, client credentials, daemon runtime pins, quarantine, managed-update and package-activation journals remain local by design, not missing PostgreSQL families. There are no other configurable families. Device caches, external read-only sources and file artifacts also remain outside engine selection.
Server-major qualification
The PGQUAL-MAJORS-UPGRADE runs on 2026-10-19 used Homebrew PostgreSQL 16.15_1,
17.11_1 and 18.6_1 on macOS arm64, with unaccent 1.1, pg_trgm 1.6 and
vector 0.8.7. PostgreSQL 16 required building pgvector with that major's
pg_config. No Docker image was used, so these are Homebrew bottle SHA256s,
not container-image digests:
| Major | Bottle SHA256 |
|---|---|
| 16 | d60d675afb25019c6fab16aac94d44b2410534123799d87dfbda6f1485ebbc8b |
| 17 | a5bf59bb6b458d858dbf2b69f83277917cb04404fc5add128f47ffd69d0501e2 |
| 18 | c03f8a2999453ed0c4f0290daba750f25602e3f32ab694d99830c7d252868137 |
The pgvector source SHA256 was
cac0b10c360f05b2d521200105ba3697e773d4cd3731f5a915a7e37ebe0bea85.
Full runs used the CI entry point, without target filters:
node scripts/test-database-conformance.mjs --engine postgres --connection "$OPENCLAW_DATABASE_DSN"
| Local run label | Source | Result |
|---|---|---|
pg16-full |
8082bfa81ab3 |
948 passed, 2 failed, 37 skipped; three affected files, including cleanup hooks. |
pg17-comparison / pg16-rerun |
ca36f46d5dc6 |
The same three files passed on both majors: 10 tests each, including cleanup. |
pg18-full |
3ca588744dbc |
947 passed, 3 failed, 37 skipped; failures in archive restore and temporary-database cleanup. |
pg17-final-comparison |
1ae8522f56c0 |
Archive restore and search-admission files: 9 passed, including cleanup. |
pg18-final-comparison |
1ae8522f56c0 |
The same files plus port and recovery integration: 43 passed, including cleanup. |
pg18-journal-phase |
c707172ff525 |
State port and port-command integration after the published-install repair: 25 passed. |
The full-run failures remain part of the record. PG16's archive timeout and
cleanup failures did not reproduce in its single focused rerun. PG18's three
failures also passed in its single focused rerun and on PG17. No server-version
incompatibility was reproduced. PG18's initial
malformed-JSON rejection exposed an overly specific conformance assertion:
PG18 may report JSON parse failure before a CHECK violation, while still
rejecting the row. Its first full attempt also exhausted the default lock table.
The recorded full PG18 run used max_locks_per_transaction=512 with 100
connections; production sizing depends on actual concurrent store use.
Native Windows qualification
The PGQUALWIN qualification on 2026-10-25 used Windows Server 2022 Datacenter
build 20348.5761, Node 24.22.1, PostgreSQL 17.11 from the official EDB
Windows installer, and pgvector 0.8.7 built from its official release tag with
MSVC. Extensions were pg_trgm 1.6 and unaccent 1.1. Both OpenClaw and
PostgreSQL ran natively on Windows, connected over loopback TLS verify-full.
Source pins, commands, timings, and redacted evidence are recorded in
PR #183512.
Covered flows include non-interactive onboarding through a file DSN SecretRef,
Doctor/status/models, foreground Gateway readiness and health, scripted turns,
memory indexing/search, and forced Gateway restart continuity. A populated PG
backup was created, verified, and restored into fresh staging with exact canonical
digests and no embedding calls during backup/restore and index rebuild. Path checks
covered spaces/non-ASCII names, drive-letter/backslash SecretRef and sslrootcert
paths, and a 324-character state directory. An unreachable DSN failed without
credential leaks, and Gateway startup rejected invalid config with exit 78.
The PGQUALWIN2 follow-up on the same Windows/Node/PostgreSQL versions qualified
native Scheduled Task service lifecycle with a file DSN SecretRef. Patched build
8f8721740bdb, whose service fix landed in
PR #183632, passed installation,
readiness/health, a scripted turn, and completed graceful shutdown. No Gateway
worker/bridge processes or OpenClaw role backends remained in pg_stat_activity.
Start and restart preserved exact six-table, session/history and memory digests.
Uninstall removed the service registration and retained PostgreSQL data, local
anchors and the file SecretRef. Lifecycle checks emitted no embedding calls
before deliberate memory-search probes, which separately embedded their queries.
Host reboot recovery passed on the earlier build 5abc0c66e293: the Scheduled
Task ran automatically, a subsequent gateway start returned already-running,
and readiness and continuity checks passed. Reboot was not repeated on the
patched build. The baseline 2f95e977c77d, target 5abc0c66e293 and patched service
build used state 32 / agent 34; this run does not qualify the later state
33 / agent 35–36 migrations.
Package update with PostgreSQL state is not yet applicable. No published release selected PostgreSQL for state and agent stores in the 2026-10-25 registry inspection. Stable 2026.10.7 lacks that support; 2026.10.10-beta.3 accepts the configuration but its bundled runtime selects SQLite for state and agent stores, with PostgreSQL enabled only for Workboard. Requalify published-release updates when a version selecting PostgreSQL state and agent stores ships.
The previous-main update and managed rollback attempts did not complete. Separate handoff-path and candidate-admission fixes landed in PR #183622 and PR #183645. Manual package replacement followed the documented recovery procedure, but does not qualify managed activation or rollback. Commands, source pins, continuity digests and timings are recorded in PR #183634.
Native Windows does not yet support SQLite ↔ PostgreSQL database port, including dry-run, resume, and rollback. The command refuses before creating recovery artifacts and leaves existing data and engine selection unchanged. Start with fresh PostgreSQL state on Windows, or run the port on Linux/macOS.
When to use PostgreSQL
Choose PostgreSQL when you want supported Gateway stores on an independently managed database server and can operate its availability, access control, and backups. Keep SQLite when you want the default local installation without that server dependency. PostgreSQL does not add multiple active Gateways for one installation, automatic failover, or automatic replication management.
Keep the local state directory and configured agent directories. Each PostgreSQL store still has a small SQLite anchor at its usual database path. It records the store UUID and server namespace, not the server's application rows. An anchor and its matching PostgreSQL data are one recovery unit. Never delete an anchor to change engines.
The quarantine, managed-update, and package-activation safety journals remain
local SQLite. Daemon runtime pins remain in control/services.sqlite so service
recovery does not require PostgreSQL. Device-local caches and file artifacts
remain local; this includes Logbook frame images and cold transcript blobs.
Incognito storage remains memory-only. The memory-lancedb plugin uses its own
LanceDB store and is not switched by database.engine.
Client credentials live in the fixed-local client/credentials.sqlite store.
Exec approvals go through the shared-state owner and require PostgreSQL to be
reachable when that family uses PostgreSQL; they fail closed if it is unavailable.
These ownership changes are complete, but the macOS app claim remains pending
signed-app proof. See the native credential contract.
When present, the local credential store is captured as a fixed-local resource, outside the engine families. User backups apply its credential sanitization; updater recovery captures it exactly.
Existing-state reads, one-shot writes, and read-only agent database opens such as session loads reuse up to four idle PostgreSQL connections per worker. Writable agent database owners and maintenance opens keep dedicated connections. Each caller has exclusive use until close; an open transaction is never returned to the cache. Connection settings and the store namespace separate cache entries. Session setup runs when a backend connects, and an idle connection lost by the server is replaced at the next open. Workers close their cached connections after three minutes idle or on shutdown. Taking a connection from the cache cancels its idle timeout; checked-out connections are not expired by this cache.
Gateway workers share four PostgreSQL I/O services per process. The Gateway
starts them when it selects PostgreSQL, overlapping their initialization with
config and plugin preparation. Replies wake only their own connection; blocked
callers check for service failure at intervals of at most 50 milliseconds.
This changes where connections run, not their exclusive ownership or idle retention. An
externally constructed SDK or plugin worker without inherited pool endpoints
starts its own four services on first PostgreSQL use. It emits a once-per-thread
warning and openclaw.postgres.pool.self-root diagnostic with the worker label
and thread ID. Worker diagnostics count these roots separately from physical
I/O services, so unexpected worker creation remains visible.
Usage reports retain their reader in the existing history worker between requests. Each report still uses a fresh read snapshot; database maintenance and worker shutdown close the retained connection before releasing custody.
Requirements
Provision a database and runtime role
Use a qualified PostgreSQL 16–18 runtime with UTF8 server and client encoding. Admission accepts 16 or newer, but other majors retain the experimental qualification noted above. Provision a separate database per Gateway. The runtime role must be able to connect, create schemas, and own the objects it creates. It does not need superuser, role-administration, or database-creation privileges.
For a fresh database, an administrator can run the following, then set the runtime role's password using the administrator's credential-management tooling:
CREATE ROLE openclaw_runtime LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION;
CREATE DATABASE openclaw ENCODING 'UTF8' TEMPLATE template0;
REVOKE ALL ON DATABASE openclaw FROM PUBLIC;
GRANT CONNECT, CREATE, TEMPORARY ON DATABASE openclaw TO openclaw_runtime;
In the new openclaw database, install the search extensions as an administrator:
CREATE EXTENSION IF NOT EXISTS unaccent WITH SCHEMA public;
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
GRANT USAGE ON SCHEMA public TO openclaw_runtime;
For semantic or hybrid memory search, also install pgvector 0.7 or newer:
CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA public;
If vector is already installed at an older version, upgrade the server's
pgvector package, then have an administrator run ALTER EXTENSION vector UPDATE
in the selected database before reopening the store.
unaccent is required for full-text search; pg_trgm is required when a store
declares trigram search. Installing both covers either text mode. The extension
SQL never runs automatically. Install any missing server extension packages
before running it. Managed services must expose the extensions in the actual
database you select.
CREATE on the database is needed beyond the first store: schema verification
can create and drop a private comparison namespace during bootstrap, migrations,
and server-major upgrades. TEMPORARY supports memory rebuild staging and
session-local plugin scratch tables. The runtime role owns its store schemas,
tables, sequences, indexes, functions, and text-search configurations; keep that
ownership when restoring or changing roles. Port and restore check namespace
ownership, so table grants alone are insufficient.
The grants above assume PostgreSQL's default public execution privileges for extension functions and usage privileges for extension types. If your administrator has revoked those defaults, also grant the runtime role access to the required extension functions and types. Do not grant the runtime role permission to install extensions or access other installations' schemas merely to make admission pass.
Supply the connection securely
Store the DSN in your secret manager and make it available to both the Gateway
service and offline maintenance commands. Reference it from config; do not put
the DSN itself in openclaw.json:
{
database: {
engine: "postgres",
postgres: {
connection: { source: "env", provider: "default", id: "OPENCLAW_DATABASE_DSN" },
schemaPrefix: "openclaw",
},
},
}
Env, file, and exec SecretRefs are qualified. A store-backed SecretRef cannot bootstrap the database that holds that secret. The host resolves the connection before passing it to workers. Restart after changing the connection, prefix, or engine map; these are fixed for the process. An all-SQLite configuration does not resolve an unused PostgreSQL connection.
Offline model credential commands, including models auth paste-api-key, use the
configured engine before the Gateway starts. Supply the same DSN SecretRef to
these commands. With PostgreSQL selected, credentials are stored on the server;
the local database path contains an anchor rather than SQLite application state.
Setup commands (onboard, setup --wizard, and the legacy crestodian entry),
including interactive sessions, and one-shot commands such as agents add, models list,
models status, config set, and doctor (including --fix) can access
PostgreSQL synchronously in their own CLI process. The Gateway still requires
worker access outside synchronous admission and migrations. Setup starts any
temporary or foreground Gateway in a separate process with its own admission;
the setup process keeps its one-shot role throughout.
Interactive remote Gateway setup also uses local migration and plugin metadata, so the configured local PostgreSQL credential must resolve before setup starts.
If existing config cannot be parsed, interactive setup offers Doctor's file-only repair before selecting a database. Declining repair or leaving the config unparseable stops setup without creating application state.
onboard --reset refuses PostgreSQL before changing configuration or data:
onboarding reset retains database state and cannot discard its PostgreSQL routing.
Rerun without --reset, or explicitly port supported stores to SQLite first.
If the connection SecretRef is unavailable, config set, config patch, and
config unset can still repair paths under database while the Gateway is
stopped. These repairs leave database files untouched. A batch that also changes
other settings requires the configured database connection as usual.
TLS options are DSN query parameters handled by the PostgreSQL driver, not
additional database.postgres config keys.
For example, the secret value could have this shape (the password is a placeholder):
postgresql://openclaw_runtime:<URL_ENCODED_PASSWORD>@db.example.com:5432/openclaw?sslmode=verify-full&sslrootcert=/etc/openclaw/postgres-ca.pem
Use sslmode=verify-full for certificate and hostname verification. Omit
sslrootcert when the server uses a certificate trusted by the runtime's normal
CA configuration. sslcert and sslkey can supply client-certificate file paths
when required by the server. Those files must be readable on the Gateway and
maintenance host. URL-encode special characters in credentials and paths.
sslmode=disable disables TLS; reserve it for a deliberately local connection.
Keep DSNs out of shell history, diagnostic captures, and logs.
TLS and exec connection qualification
For a private CA, install its certificate on every host that runs the Gateway or
maintenance commands. Use an absolute sslrootcert path: the driver opens that
file in the process making the connection. The server certificate must include
the DSN hostname in its subject alternative names. To require encryption on the
server, use a pg_hba.conf rule such as this, without a matching plaintext
host rule:
hostssl openclaw openclaw_runtime 127.0.0.1/32 scram-sha-256
Enable ssl = on and configure the server's certificate and private key in
postgresql.conf. A client using sslmode=disable then receives a pg_hba.conf
refusal with no encryption; it cannot silently use plaintext.
A resolver that prints only the DSN needs jsonOnly: false:
{
secrets: {
providers: {
postgres: {
source: "exec",
command: "/usr/local/libexec/openclaw-postgres-dsn",
jsonOnly: false,
},
},
},
database: {
engine: "postgres",
postgres: {
connection: { source: "exec", provider: "postgres", id: "database-dsn" },
},
},
}
The executable must satisfy the exec provider trust rules.
Alternatively, retain jsonOnly: true and return the documented protocol-v1
response. Keep the DSN out of command arguments and provider configuration;
retrieve it from the secret manager inside the executable.
The PostgreSQL conformance suite exercises CLI onboarding and Doctor with an exec connection and Gateway bootstrap, workers, and restart with the same provider contract. Run it against a TLS-only PostgreSQL server to exercise those paths with certificate verification:
node scripts/test-database-conformance.mjs --engine postgres --connection "$OPENCLAW_DATABASE_DSN" -- \
src/infra/database-engine/cli-oneshot.postgres.test.ts \
src/gateway/server-database-engine.postgres.test.ts \
src/infra/database-engine/postgres-search-admission.postgres.test.ts
The PostgreSQL 17 TLS acceptance matrix records the live CLI and Gateway runs, certificate rejection cases, and secret-redaction checks behind these support claims.
Set up a fresh installation
-
Provision the database, role, extensions, and connection secret above.
-
Add the PostgreSQL config before the installation creates its first stores. Configure other Gateway settings through the normal setup flow. If it has already created SQLite stores, follow migration instead of changing the engine setting directly.
-
Start the Gateway through its normal service owner. First open creates each store's local anchor, a UUID-qualified PostgreSQL schema, and the current generated schema. Stores are created as their owners open them; an unused plugin need not have a namespace yet.
-
Verify from the same configured installation:
openclaw doctor openclaw status openclaw health
Doctor's Database engines section should include:
Shared state: default=postgres, override=inherit, effective=postgres (configured)
Agent data: default=postgres, override=inherit, effective=postgres (configured)
The (configured) suffix matters: these lines describe selection, not a
successful open of every store. Doctor also reports physical shared-state roots
and namespaces, including mismatches and actionable repair steps. Verify Gateway
health and exercise the agent/plugin features you enabled. A failed PostgreSQL
connection or schema admission never silently switches that store to SQLite.
Run independent Gateways on one server
Use a separate PostgreSQL database, runtime role, connection secret, and local
state directory for each Gateway. Each database may use the default
schemaPrefix: "openclaw". Install extensions in each database.
Alternatively, independent installations can share a database with distinct
prefixes such as installation_a and installation_b. Prefixes contain 1–27
lowercase letters, digits, or underscores, beginning with a letter or underscore.
New schemas have the shape <prefix>_<store>_<uuid-prefix>. Existing anchors keep
their recorded namespace when the configured prefix changes.
A prefix is a naming convention, not an access-control boundary. Use separate
roles and appropriate schema permissions if sharing a database. Extensions in
public are shared; store objects remain in their own namespaces. Keep each
anchor with its originating installation.
There is still one Gateway per store. Do not mount the same state directory on multiple Gateways or copy its anchors to make an HA/failover installation. The Gateway records and heartbeats an owner lease in shared state. A live or unverifiable previous owner prevents takeover; lease expiry alone is insufficient. Advisory writer locks protect transactions; they do not provide multi-Gateway ownership, routing, or failover.
Mixed engines
database.families can override state, agent, workboard, logbook, and
team-reports. Every override requires an engine. All PostgreSQL families
share the same connection settings and schema prefix. Fixed-local journals cannot
be overridden.
For fresh stores, this selects PostgreSQL shared state while retaining SQLite agents and plugin stores:
{
database: {
engine: "sqlite",
families: { state: { engine: "postgres" } },
postgres: {
connection: { source: "env", provider: "default", id: "OPENCLAW_DATABASE_DSN" },
},
},
}
Both SQLite and PostgreSQL agents are supported with either shared-state engine. Workboard, Logbook, and Team Reports can independently select either engine. Matrix account and token-hash roots belong to the state family and follow its effective engine, including retained and disabled accounts.
Use a scoped port to change an existing family:
openclaw database port --family workboard --to postgres --dry-run
openclaw database port --family workboard --to postgres
openclaw database port --family agent --to postgres
openclaw database port --family workboard --to sqlite
Each operation ports every existing physical store of that family and publishes
only its override. The default and unrelated families remain unchanged. A
destination equal to the default removes the redundant override. Unscoped ports
retain whole-installation behavior for homogeneous eligible families; mixed
selections require --family.
Mixed installations support matched backup/restore and compatible updates. Whole-installation backups containing any PostgreSQL store require offline custody. Archive restore imports captured PostgreSQL stores into fresh namespaces and publishes matching local anchors. Updates require candidate proof of family-override support as well as support for each selected or retained PostgreSQL family.
Do not change a populated family's engine to evade a refusal. Configuration does not migrate data.
Migrate an existing installation
Prepare and port to PostgreSQL
While the installation still selects SQLite, run:
openclaw doctor --fix
This is the legacy migration gate. Port preflight refuses pending declared
migrations, including native task assignments, Matrix inbound dedupe, and legacy
memory-index sidecars. Upgrade older shared state with a compatible binary and
Doctor before retrying. Agent schema upgrades can run in the port's private copy.
A scoped port checks only migrations declared for that family. Fresh Matrix
roots without legacy inbound-dedupe rows do not require a completion receipt.
Port also refuses reserved-incognito session keys: run openclaw doctor --fix on the SQLite installation before retrying; PostgreSQL Doctor leaves such stores unchanged and requires re-adoption from a repaired SQLite source.
Provision PostgreSQL and add database.postgres.connection, but leave the source
engine selections unchanged. Stop the Gateway through its service owner and stop
embedded writers using the same state directory. Port never stops or restarts
services for you.
openclaw database port --to postgres --dry-run
openclaw database port --to postgres
The dry run inventories sources and checks read-only prerequisites. It does not
create a recovery manifest, copy data, create destination namespaces, or complete
checks that need private migration or a privilege probe. --json is available.
Empty retained retired tables such as fleet_cells are skipped with an inspection
note. Populated retired tables refuse the port so their rows cannot be silently
discarded; preserve the SQLite store and archive the retired data through its owner
before retrying. Unrecognized tables still require complete descriptor coverage.
The real port includes registered shared-state, agent, Workboard, Logbook, and
Team Reports stores, including retained roots and disabled plugin data. Keep
matching separately installed plugins available so their schema owners can be
loaded. Source files and config, including includes, are captured under
<stateDir>/recovery/database-port/<operation-id>/ before publication. Destination
stores are privately migrated and verified; config switches last and preserves
SecretRefs. Retain the captures until recovery is confirmed.
Cold transcript references and file-artifact paths remain in place. Rebuildable
memory indexes are omitted from the agent port; canonical memory origins,
tombstones, and the embedding cache are retained. After publication releases
port ownership, the command acquires fresh offline ownership and rebuilds each
ported agent through the selected memory plugin. Unchanged cached chunks reuse
their embeddings. Per-agent results appear in the command output and --json.
A rebuild failure warns without failing the completed port and includes
openclaw memory index --agent <id> to retry while the Gateway remains stopped.
Retained stores outside the active agent configuration require that original
configuration before rebuilding. Check Memory Core before relying on recall:
openclaw memory status
These commands require offline ownership. See the memory CLI for per-agent selection and other memory plugins. Then start the Gateway through its service owner and run Doctor, status, and health.
Recover an interrupted port
An incomplete operation blocks startup using the active manifest at
<stateDir>/recovery/database-port/manifest.json. Keep the Gateway stopped and
choose one action:
openclaw database port --resume <stateDir>/recovery/database-port/manifest.json
openclaw database port --rollback <stateDir>/recovery/database-port/manifest.json
Resume revalidates captured evidence and finishes the operation. Rollback
restores its original stores and config. These options are mutually exclusive
and cannot be combined with --family, --to, or --dry-run. Preserve unreadable
or damaged manifests and their captures for diagnosis. A completed, archived manifest is
not an undo operation after runtime writes have resumed.
Understand refusals
Port refuses live or unverifiable writer ownership, an ambiguous unscoped mixed selection, pending legacy migrations for the affected families, unsupported stores or source schema, schema drift, mismatched anchors, insufficient namespace ownership/privileges, and changed recovery evidence. PostgreSQL cannot represent NUL-containing text; invalid source data can therefore refuse a port. Duplicate-key voice JSON also refuses rather than silently losing data. Follow the reported repair and retain the original data; never edit metadata or discard an anchor to force admission.
Backup and restore
Capture both server data and local state
Stop the Gateway and embedded writers before capturing PostgreSQL. Then run:
openclaw backup create --output ~/Backups/openclaw --verify
The archive combines local files and SQLite images with each actual PostgreSQL store's matched format-2 envelope: original anchor, portable SQLite export image, and manifest containing identity, schema versions, active fragments, allocation facts, sizes, and hashes. The DSN is not in that envelope. The surrounding archive contains config and may contain other credentials; protect it accordingly. Supply external SecretRef values separately during recovery.
User backups apply the owning stores' sanitization rules. They are not the
exact unsanitized captures used for updater recovery. SQLite and PostgreSQL Git
exports apply the same --exclude-secrets policy, including credential tables,
machine-state keys, and generated model-catalog credentials. Keep Logbook images, cold
transcript blobs, workspaces, config includes, and local state with the database
backup. Neither a server dump alone nor a copied anchor covers the installation.
Restore all PostgreSQL stores
openclaw backup verify <archive> checks the PostgreSQL capsules declared by the
backup producer, including plugin stores and retained Matrix roots. The archive
records each generated capsule's archive path and manifest hash; verification
checks that binding, its artifact hashes, and its matching anchor.
openclaw backup restore <archive> --target <directory> verifies and extracts
into a fresh staging directory, then imports every captured PostgreSQL store into
a fresh namespace: shared state, agents, Workboard, Logbook, Team Reports, and
retained Matrix roots. It publishes matching anchors only after all stores pass
their providers' verification. A failed restore discards its new namespaces and
cleans the incomplete staging tree. Original namespaces are left untouched.
Restore imports and consumes only producer-declared capsules. A copied export in a workspace or another archived directory is ordinary user data: restore preserves it without importing it or deleting its members. Consuming generated capsules after successful import lets a restored installation create another full backup without colliding with the previous exports.
Older archives without the capsule inventory can automatically recover only the global and agent snapshots declared by their existing database inventory. Other exports remain unchanged as files. Verification and restore warn when an older archive contains unlisted exports: re-run backup with this version on the source installation to capture plugin PostgreSQL stores with ownership metadata before activating a full recovery. Unmanaged SQLite files also remain opaque.
Archive restore validates its captured configuration under offline custody and
uses its family selections. Supply the captured configuration's external
connection SecretRef before restoring; point that secret at the recovery server
when recovering to another server. The live installation's selections do not
override the archive. Restore publishes the recovered configuration last, after
all stores and their matching anchors pass verification.
openclaw backup sqlite restore remains SQLite-only.
Git restore supports --global, --agent <id>, and --store <id> for recorded
plugin or additional-root identities. Use the identity recorded under stores/
in the Git repository. Each restore creates a fresh namespace and publishes its
matching local anchor after verification.
Disaster recovery from Git
Prepare explicit Git recovery points while offline, alongside the full archive:
openclaw backup git init --repository ~/Backups/openclaw-git
openclaw backup git create --repository ~/Backups/openclaw-git --all --exclude-secrets
openclaw backup git log --repository ~/Backups/openclaw-git
--all captures configured agents and discovered PostgreSQL plugin stores and
retained roots with distinct identities. Record the resulting commit and keep
the repository private. Git database backups do not replace file/config backups.
openclaw backup git verify checks PostgreSQL export hashes and the matched
anchor locally. It does not connect to PostgreSQL or create a scratch namespace.
New Git captures also bind the source store to the scope manifest, so verification
and restore reject a capsule copied from another store. Older captures remain
readable; keep their complete scope artifacts together when editing Git history.
SQLite verification retains its private restore and integrity checks.
To recover these scopes:
-
Keep all writers stopped. Provision a compatible PostgreSQL server, a fresh database, runtime role, and required extensions as above.
-
Set the recovery installation's state/config paths and agent directories; supply its external connection secret separately, pointing at the recovery database. Preserve the matching
database.engineanddatabase.familiesselections, and use a compatible OpenClaw version and matching plugins. Recover the matching non-database files alongside the Git snapshots. A full archive restore already rehydrates its PostgreSQL stores; use Git restore when recovering selected database scopes instead. -
Restore each Git scope from the same recorded commit to a nonexistent local target, outside the backup repository:
openclaw backup git restore --repository ~/Backups/openclaw-git --ref <commit> --global --target ./recovered/openclaw.sqlite openclaw backup git restore --repository ~/Backups/openclaw-git --ref <commit> --agent main --target ./recovered/main/openclaw-agent.sqlite openclaw backup git restore --repository ~/Backups/openclaw-git --ref <commit> --store workboard --target ./recovered/workboard.sqliteEach PostgreSQL restore loads a fresh namespace using the configured connection and publishes a matching local anchor only after verification. It does not overwrite an existing target or restore into a live namespace.
-
Put those verified anchors at the recovery installation's corresponding shared-state, agent, or plugin database paths, replacing only staged historical anchors while offline. Preserve the old installation and namespaces until recovery is confirmed. Restore every required store before starting the installation.
-
Run
openclaw doctor, start through the service owner, then checkopenclaw health,openclaw status, representative sessions, search, and enabled plugins. Review pending approvals and channel relinking needs after rollback; a backup restores historical state.
Published-stable upgrade qualification
The published baseline is OpenClaw 2026.10.5.
Its packaged configuration schema does not support database.engine=postgres,
so a native stable-PostgreSQL → current upgrade is not applicable. Existing
users adopt PostgreSQL by updating their SQLite installation first, then using
openclaw database port --to postgres.
The local PGQUAL-MAJORS-UPGRADE lane installed that published package in an
isolated npm global prefix, onboarded on SQLite, created an agent and persisted
session/memory data, and made a verified backup. The installed stable updater
activated the packed candidate from b1d415c2448f in run
d3b04126-11d3-4f28-9dd0-146f9fe914b5 and exited successfully. The installed
build identity was compared with the tarball, since both packages report version
2026.10.5. The candidate archive SHA256 was
262bcd6929ded71c7066752438b7768deca990e1dddb6457905256158abd7491.
| Cell | Result and evidence |
|---|---|
| Published stable on PostgreSQL → current | Not applicable: the stable package has no PostgreSQL configuration schema. |
| Published stable SQLite → current | Passed through the installed updater; run d3b04126-11d3-4f28-9dd0-146f9fe914b5. |
| Current SQLite → PostgreSQL 17 | Passed; port aae1aaff-ec7e-4dac-afe6-7b46f8589867 moved the populated shared and agent stores and retained recoverable SQLite source captures. |
| Foreground Gateway and continuity | Passed: Gateway readiness, Doctor/status, agent listing, the existing session and memory search. Doctor also returned exit 2 with ok: false for an unreachable DSN. |
| Rollback to published stable SQLite | Passed: restored the verified pre-upgrade backup and matching stable package, then checked the exact build, foreground Gateway, Doctor/status, agent, session and memory. The direct downgrade dry run reported unsupported state/agent schemas; it was not a PostgreSQL downgrade. |
| Live model turns | Skipped: no provider key was available. |
No provider key was available, so live model turns were skipped. Service activation was covered by the separate signed-Mac/Parallels proof, not by this local run; this lane uses foreground Gateways and does not install a service. The native-client support rows above retain their own qualification scope.
Updates and schema migrations
Use openclaw update for supported update admission. The
published-stable qualification covers
SQLite update, PostgreSQL adoption and matched-backup rollback, with foreground
Gateway verification. Its service and live-turn limits still apply.
Before stopping the previous Gateway or changing data, it checks the target's declared engine and PostgreSQL family support against selected families and retained physical stores. Explicit family overrides also require successful candidate admission of the complete selection; global PostgreSQL support alone is insufficient. A target without that proof is refused. A raw package-manager downgrade does not provide this guard.
Before mutation, the updater captures exact PostgreSQL rows, sequence allocation state, schema facts, and matching anchors alongside local SQLite state and config. Recovery restores into fresh namespaces and publishes matching anchors. It preserves current update-run history. Independent writes, changed recovery generations, or uncertain custody/commit outcomes prevent automatic restore and retain captures for recovery. This is not an unconditional rollback guarantee. See update compatibility and recovery.
Store owners run consecutive forward schema migrations under a writer transaction. Admission verifies the generated destination schema before publication. Missing migrations, newer schemas, and drift refuse admission; use a compatible binary or restore a matched backup. Never edit version/fingerprint metadata. Doctor's state migrations take their own exact backup before mutation.
PostgreSQL schema upgrades run through openclaw doctor --fix or the normal
update flow with writers stopped. A direct Gateway start on an older schema
refuses and directs you to Doctor. With the Gateway and embedded writers stopped,
run openclaw doctor --fix before restarting. Doctor takes a backup and applies
the consecutive migrations needed by each store:
- State 32 and agent 34 add nullability-aware PostgreSQL index ordering.
- State 33 and agent 35 extend that ordering to partial indexes whose predicates prove a key is non-null. Logbook 3 applies the same correction when its service opens the store.
- Agent 36 adds the three memory keyword path indexes described below.
The ordering migrations rebuild only changed indexes; agent 36 creates only its three new indexes. SQLite advances version metadata without changing its indexes for these migrations. See the ordering migration contract and agent schema 36. Older binaries require the verified pre-upgrade backup and matching build; reinstalling an older package does not reverse these migrations.
After a PostgreSQL major-version upgrade, REINDEX the
idx_memory_index_sources_exact_path, idx_memory_index_sources_basename, and
idx_memory_index_sources_stem indexes because normalize() follows PostgreSQL's
Unicode tables.
First boot, ports, rebuilds, and migrations can take longer on slow disks or a
remote server. Connection waits allow 60 seconds. On runtime store connections,
OpenClaw sets session-level lock_timeout to 60 seconds and statement_timeout
to 90 seconds. These liveness bounds override role, database, and DSN startup
settings. The synchronous bridge has a 120-second deadline. If it expires, the
process root terminates the whole I/O service, resetting every connection assigned
to that service (roughly a quarter of the process's PostgreSQL connections) with
an unknown outcome. Runtime statements time out after 90 seconds, so the bridge
deadline covers a wedged service or a black-holed network. Offline port and restore
materialization use a direct asynchronous client with a one-hour statement timeout;
those statements do not pass through the synchronous bridge.
These are not per-operation config options.
A timeout is a failure to investigate, not permission to delete recovery state.
Inspect server load, locks, network, and storage, and retry only
after resolving the reported cause.
OpenClaw inherits the operator's synchronous_commit and other durability
settings. PostgreSQL's default on makes acknowledged commits durable across
PostgreSQL or operating-system crashes. For SQLite WAL
synchronous=NORMAL-like throughput, operators can opt in
with ALTER ROLE <openclaw_role> SET synchronous_commit = off or the equivalent
ALTER DATABASE setting. This can lose recently acknowledged commits on a server
or operating-system crash, without corruption. Ports and backups always commit
durably.
Search on PostgreSQL
Full text uses PostgreSQL's simple plus unaccent configuration and native
ranking. Trigram mode performs literal contains matching with native similarity
ranking. Memory vector search is approximate: a stored binary sketch provides a
fast Hamming-distance prefilter, followed by exact float32 cosine reranking
through pgvector. Model, source and exclusion filters apply before preselection;
small candidate-ID scopes use exact search. This recall-tuned behavior is on by
default with no extra configuration option. SQLite memory vector search stays
exact, so semantic neighbors need not match across engines.
Semantic memory requires pgvector 0.7 or newer. The agent schema migration backfills the generated sketches without building an HNSW index; ordinary writes maintain them automatically. Canonical embeddings, zero-vector eligibility and the result shape are unchanged, and backup/port operations rebuild the sketches.
Scores and tokenization are engine-local. Expect different ordering from SQLite
BM25, particularly for punctuation, compound words, and Unicode. Hybrid retrieval
keeps the existing text/vector weighting, not cross-engine score equivalence.
Memory full-text rank uses unnormalized frequency ranking (ts_rank) and maps
its rank r to r / (r + 0.0027) before the shared hybrid merge. This calibration
makes keyword relevance contribute meaningfully alongside vectors. It does not
include corpus-wide inverse document frequency (IDF), so common-term and
mixed-frequency queries remain a ranking limitation compared with SQLite BM25.
Memory keyword search retrieves body, exact-path, and broad-path candidates in
one statement, retaining each branch's ranking and limit. That statement includes
the match-any body fallback, used only when both body and path searches return
no matches.
Memory transcript visibility retains the same 200-candidate ACL postfilter as
SQLite; it does not promise complete recall beyond that candidate window. The
separate transcript search API applies its agent/session/role filters before its
result cap. See search modes and limits.
Without pgvector, explicitly select keyword-only memory before opening its store:
{
memory: {
search: { provider: "none" },
},
}
Memory status then reports provider: "none" and searchMode: "fts-only".
Missing pgvector or an embedding-provider failure does not silently choose this
mode. Missing unaccent or a required pg_trgm refuses admission rather than
returning empty search results. Reopen the store after installing the extension.
Performance evidence
The recorded PostgreSQL 17 warm-turn census at f5de32afc6a5 used 16.67 worker
requests, 68.67 round trips and 82.67 SQL statements per traced turn. Its 64-session
run completed 320/320 turns at 5.04 turns/s, with p95 16.86 seconds. A later
census at d5aeceb2b89e reduced traced work to 72.33 SQL statements and 59.33
round trips; the matched SQLite workload used 52 statements. PostgreSQL's
transaction and advisory-lock work accounts for part of that difference.
These are workload and host measurements, not a latency SLA. A later apparent fourfold concurrency slowdown reproduced on an unchanged control under host load; the remaining roughly 5% difference was confounded by optional cost-summary work. Measure network latency, concurrency and server load for your deployment.
The four-service I/O pool qualification measured a synthetic 20-agent installation at 7,714.1 → 1,950.7 MiB idle process-tree RSS and 337 → 12 I/O workers across three process roots. It did not reduce server connections: idle backends were 144 → 146. These are fixture measurements on the original pool candidate, not a memory budget for a deployed Gateway. The accepted follow-up measured about 0.1 ms higher foreground query p95 and a 25.2% higher cold-ready p95 across five paired runs, alongside a lower cold-ready median. The footprint reduction trades some query latency for memory; the pool does not promise faster queries.
Monitoring and troubleshooting
Startup rejects invalid configuration before readiness and reports the fields that
need repair. With PostgreSQL selected, invalid Gateway configuration exits with
status 78 (EX_CONFIG) and a Doctor hint. An unreadable config file instead exits
with status 1. For retired settings such as agents.defaults.heartbeat, run
openclaw doctor --fix before starting the Gateway again.
During startup, existing-state readers can briefly retry while committed schema admission facts are still being published. This wait lasts at most one second, bounded by the caller's deadline and cancellation. Persistent admission failures remain visible; the retry does not replay application operations.
Recover missing local client credentials
If PostgreSQL state survives but client/credentials.sqlite is missing, restore
its matching backup or run openclaw doctor --fix. Doctor asks before creating
replacement local identities. Use openclaw doctor --fix --yes to accept that
repair without prompting; --non-interactive alone leaves the identities unchanged.
Clients on this host must pair again after replacement. Remote devices that kept their credentials retain their pairing records in PostgreSQL. Gateway startup does not replace the missing identities itself. This repair does not overwrite an existing mismatched, corrupt, or incomplete credential store.
Read the Doctor and status store report
openclaw doctor enumerates physical stores through the same family and plugin
resource descriptors used by database port. The report includes shared-state roots, every discovered agent database, Workboard, Logbook,
Team Reports, and retained Matrix account/token roots. Quarantine, managed-update,
package-activation, and local credentials are labeled local by design.
Read default, override, and effective as configuration; observed comes
from the physical SQLite file or PostgreSQL anchor. A mismatch means changing
configuration did not migrate existing data. Keep the Gateway stopped and use
openclaw database port --family <family> --to <engine>, or restore the matching
configuration. Never delete an anchor to clear a mismatch.
schema, expected, and pending compare observed metadata with the installed
store owner's descriptor. A version match alone does not prove complete schema
integrity. Readable state and agent schema versions remain visible when port
validation refuses an older or newer schema. Unreadable files and directories
remain in the inventory with unknown contents and a permission-repair step;
they are not reported as absent. Missing resolved store files remain listed with
initialization guidance. Unversioned local journals are identified separately. Installed server
extensions and recorded admitted search extensions are separate facts;
degraded identifies recorded keyword-only operation and missing vector support.
Install a named missing extension as a database administrator, grant USAGE on
its schema to the runtime role, and reopen the store through its owner.
A PostgreSQL anchor can remain readable while the server is down. In that case
observed=postgres and the namespace remain visible, but connectivity is
unreachable and server schema, extensions, and lease information stay unknown.
Check the PostgreSQL service and port, network/TLS settings, and
database.postgres.connection or its SecretRef. Raw connection strings and
connection errors are omitted from the report.
custody and lease distinguish a verified live owner, no owner, and unverifiable
ownership. These are observations, not permission to take over a store.
backup=unknown means no matching recorded outcome was observed; it does not
assert that no backup exists. The installation backup ledger records backup
destinations, which do not establish coverage of an individual physical store. legacy=pending names imports that must complete
with openclaw doctor --fix before porting. If a detector or store is unavailable,
unknown includes a next step instead of claiming success.
Doctor's JSON includes every inspected store in databaseStores, even when its finding is below --severity-min. Visible store findings also include the same fields in details.
Status JSON includes databaseEngines.stores, alongside the configured
families. Its source=admitted rows consume the owners' existing admission
facts, without opening stores or probing PostgreSQL. They can show a recorded
engine and schema version while connectivity, backup, or lease state remains
unknown. An unopened or unadmitted store remains unknown. Configured agent paths
stay listed before admission, without filesystem probes. Status uses the selected
Gateway's projection, including remote Gateways; if that projection is unavailable,
it directs you to run Doctor on the Gateway host. Doctor's bounded, read-only
inspection supplies fresh physical evidence without adding Gateway hot-path probes.
For example, this excerpt from a read-only inspection catches an unported SQLite state store after PostgreSQL was selected (paths shortened; other fields omitted):
state (~/.openclaw/state/openclaw.sqlite); default=postgres, override=inherit, effective=postgres; observed=sqlite (mismatch); schema=26, expected=26, pending=0; connectivity=local
The same observation is machine-readable in the store finding's details:
{
"family": "state",
"observed": "sqlite",
"mismatch": true,
"schema": {
"versioning": "versioned",
"version": 26,
"expectedVersion": 26,
"pending": 0
},
"connectivity": "local",
"backup": null
}
The next step is openclaw database port --family state --to postgres with the
Gateway stopped, or restoring the matching engine configuration. A current
SQLite schema does not make the engine mismatch safe to ignore.
Use openclaw health and actual enabled feature operations alongside this report.
Monitor PostgreSQL availability, connections, locks, storage, and backup success
through your database operator's normal monitoring.
The following are exact error strings or exact stable prefixes (variable details follow a prefix):
| Error text | Next step |
|---|---|
PostgreSQL requires version 16 or newer and UTF8 server/client encoding; upgrade the server or port to a UTF8 database and set client_encoding=UTF8. |
Upgrade PostgreSQL or select a UTF8 database/client connection. |
PostgreSQL bootstrap cannot use a store-backed SecretRef; use env, file or exec. |
Move the connection secret to an external bootstrap source. |
Cannot open PostgreSQL connection: |
Check the service's SecretRef environment, DSN/TLS, network, role, and server availability. |
PostgreSQL search capability unavailable: insufficient CREATE privilege. |
Restore database/schema creation privileges and store ownership; see requirements. |
DATABASE_SEARCH_CAPABILITY_UNAVAILABLE |
Follow the named extension installation SQL, or explicitly choose keyword-only memory when only vectors are unavailable. |
Missing PostgreSQL anchor; restore the matching PostgreSQL schema and anchor. |
Restore the complete matched set. Do not create a replacement anchor manually. |
Invalid PostgreSQL anchor identity. Restore a matching backup. |
Restore the original matching anchor and server data. |
PostgreSQL database identity mismatch: |
Restore the matching pair; an anchor from another namespace is not interchangeable. |
PostgreSQL schema drift detected. |
Use the compatible binary or restore matching data; do not edit metadata. |
PostgreSQL connection lost; transaction rolled back |
Restore connectivity and reopen the failed store through its owner before retrying. |
PostgreSQL connection lost; outcome unknown |
Reconcile whether the operation committed before repeating an external effect. |
Outcome unknown after a connection loss
Connection loss closes the failed connection. The engine distinguishes a known rolled-back transaction from an uncertain outcome, invalidating cached facts when a commit may have occurred. It does not switch to SQLite or promise transparent replay of every operation. The shared-state worker retires its failed connection so a subsequent operation can reopen the store once PostgreSQL is available. The interrupted operation is not replayed. Recover server connectivity and inspect the operation's recorded result before repeating an external effect. If another store remains unavailable, restart the Gateway through its service owner.
The 120-second bridge deadline also has an unknown outcome and resets the whole I/O service, including its other assigned connections. Check the recorded result through the owning feature after connectivity recovers; a lost reply does not prove that the write rolled back. Do not blindly repeat an operation that could send a message, charge an account, or perform another external effect.
Slow first search after a bulk import
Stale PostgreSQL planner statistics can contribute to slow queries after loading
many rows. Database port and its post-port memory rebuild attempt ANALYZE after
committing verified data. If that best-effort refresh fails, the command warns
that the data is retained and autovacuum can refresh statistics later. Check for
that warning and have the database operator inspect statistics and autovacuum,
or run ANALYZE on the affected tables. This is not a required delay after every
import: the agent 36 exact-path indexes support indexed lookup even before
ANALYZE.
Roll back to SQLite
Use the current compatible binary, keep the PostgreSQL secret available, and stop all writers. Run a new reverse port:
openclaw database port --to sqlite --dry-run
openclaw database port --to sqlite
For a mixed installation, port one family at a time instead:
openclaw database port --family workboard --to sqlite --dry-run
openclaw database port --family workboard --to sqlite
The new port carries writes accepted since the earlier migration. Restoring an old SQLite capture would lose those writes. After success, start the Gateway and verify Doctor, health, and the affected features. Former PostgreSQL namespaces are retained, not dropped automatically. Reverse-port before a downgrade to a build that lacks PostgreSQL support.