Rivet Cloud
Note: The
rivetos cloudcommands ship in the next RivetHub release and are not in the current installer yet.
Rivet Cloud is hosted memory (rivetos.cloud) that any coding harness already
on your laptop can capture into and search. This page covers the public CLI:
rivetos cloud connect, rivetos cloud export, rivetos cloud import,
rivetos memory export, and rivetos memory import.
No RivetOS runtime or PGlite is required on the laptop for cloud mode. The harness hooks talk to Postgres and the embed endpoint directly.
Connect
Section titled “Connect”Copy these values from your rivetos.cloud dashboard.
RIVETOS_PG_URL=<connection string from your rivetos.cloud dashboard>RIVETOS_EMBED_URL=<embed URL from your dashboard>RIVETOS_EMBED_MODEL=qwen3-embedding-0.6bRIVETOS_CLOUD_TOKEN=<tenant_token>(sslmode on the Postgres URL must be require, verify-ca, or
verify-full. disable, allow, prefer, and a missing sslmode are
rejected. node-pg verifies against system CAs. Do not add
sslrootcert=system — node-pg ENOENTs on that parameter.)
rivetos cloud connect '<connection string from your rivetos.cloud dashboard>' \ --embed-url '<embed URL from your dashboard>' \ --token '…'What it does:
- Validates the Postgres URL (
postgres/postgresql,sslmodeofrequire/verify-ca/verify-full) and that the embed URL ishttps. - Upserts
RIVETOS_PG_URL,RIVETOS_EMBED_URL,RIVETOS_EMBED_MODEL, and (when--tokenis passed)RIVETOS_CLOUD_TOKENinto$RIVETOS_ENV_FILEif set, otherwise~/.rivetos/.env(created0600if missing; existing files are chmod’d0600even when contents do not change; other keys kept). The same path is passed toplugins installso a custom env file cannot keep old credentials. - Smokes before installing hooks:
SELECT count(*) FROM ros_messagesand onePOST <embed-url>/v1/embeddingswith{input:"ping", model}expecting a 1024-d vector. - Runs
rivetos plugins installfor detected harnesses (or--harness <id>) withoverrideEnv: true, so an existing~/.hermes/.envis rewritten to the newRIVETOS_PG_URL/RIVETOS_EMBED_URL(other lines kept, file0600). Ordinaryrivetos plugins installstill preserves a nonempty HermesRIVETOS_PG_URL. - Prints a checklist:
DB ok (12 messages)embed ok (1024 dims)claude-code: installedgrok-build: skipped (not found)next: open your harness and run one turn; then use the memory_search toolFlags: --embed-model (default qwen3-embedding-0.6b), --token (written as
RIVETOS_CLOUD_TOKEN), --harness (repeatable), --root, --dry-run (print
the env diff and the install plan; write nothing), --yes (accepted for
non-interactive scripts; connect does not prompt).
rivetos cloud status shows which env vars are set (host and database only,
never the password) and pings DB + embed.
Which export/import command?
Section titled “Which export/import command?”| Store | Export | Import |
|---|---|---|
| Local / self-hosted datahub (owner Postgres URL) | rivetos memory export |
rivetos memory import |
Rivet Cloud (RIVETOS_PG_URL host rivetos.cloud) |
rivetos cloud export |
rivetos cloud import |
Tenant roles on Rivet Cloud can INSERT conversations/messages but cannot INSERT
summaries/wiki or enqueue graphile jobs. rivetos memory import against a
rivetos.cloud URL prints a hint to use rivetos cloud import and exits 2
without attempting a half-failed restore.
Cloud HTTPS export / import
Section titled “Cloud HTTPS export / import”Host and slug come from RIVETOS_PG_URL: hostname (not the Postgres port)
and database name with a leading tenant_ stripped (tenant_demo → demo).
The bearer token is RIVETOS_CLOUD_TOKEN (set by rivetos cloud connect --token).
rivetos cloud export --out mem.ndjson.gzrivetos cloud export > mem.ndjson.gzrivetos cloud import mem.ndjson.gzGET /api/t/<slug>/export—Authorization: Bearer <token>,Accept: application/gzip. Usesnode:httpsrequest()and streams the response to--outor stdout (does not buffer the gzip, no socket timeout). Default destination is stdout; refuses gzip to a TTY (redirect or--out). Progress (bytes received) is printed when writing to--out. An inactivity guard of 120 seconds applies to the response.POST /api/t/<slug>/import— streams the gzip file withfs.createReadStreampiped intonode:httpsrequest()(Content-Type: application/gzip,Content-Lengthfromfs.stat,Authorization: Bearer <token>). No socket timeout; an inactivity guard of 120 seconds on the response is the only limit. Prints bytes sent from the read stream. On a server error, prints the JSONcommittedcounts if present.
Direct (local / datahub) export
Section titled “Direct (local / datahub) export”rivetos memory export > mem.ndjson.gzrivetos memory export --out mem.ndjson.gz --since 2026-09-01T00:00:00ZGzip NDJSON v1. Default destination is stdout so a shell redirect works. The
command refuses to write gzip to a TTY — redirect or pass --out.
Export pins one client, BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY,
and reads each table through a server-side cursor in pages of 1000 (backpressure
on the gzip writer is respected). The cursor and transaction are closed on
success and in finally on failure, so the dump is one snapshot.
Uses RIVETOS_PG_URL from the environment or ~/.rivetos/.env (loadRivetEnv),
same as the other rivetos memory subcommands.
--since closure
Section titled “--since closure”--since is not an independent timestamp filter per table. The dump is the
dependency closure of the selected rows so it restores into an empty schema:
- messages —
created_at >= --since. - conversations — the union of (1) rows with
created_atorupdated_atat or after--sinceand (2) the conversation of every exported message (and of selected summaries, including recursive parents). Older conversations are pulled in when a recent message needs them. - summaries —
created_at >= --since, plus each selected row’sparent_idchain (recursive) so the DAG can be inserted. - summary_sources — only rows whose
summary_idis in the exported summaries and whosemessage_idis in the exported messages. Bound with= ANY($1::uuid[])over those id sets (chunks of 5k). Junction rows are never exported in full “to keep FKs”; dangling endpoints are omitted instead. - wiki — when
--sinceis absent,ros_wiki_topics,ros_wiki_redirects, andros_wiki_citationsare exported in full. When--sinceis present: topics withcreated_atorupdated_at>= the cutoff, plus redirects whoseto_slugis one of those topics, plus citations whosetopic_slugis one of those topics.
Direct (local / datahub) import
Section titled “Direct (local / datahub) import”rivetos memory import mem.ndjson.gzrivetos memory import mem.ndjson.gz --dry-runConnects the pool client before consuming the gzip so a slow connect cannot
drop the header. Then pipeline(source, gunzip) with the readline iterator
already attached, so a missing file or truncated gzip rejects the import (not
an unhandled source error). Header check, then batches of 500 per table.
Each batch is grouped by the exact present column set. One INSERT per
shape names only those columns, so omitted keys take SQL defaults. An explicit
JSON null is a present key and is inserted as NULL. Empty column sets use
DEFAULT VALUES.
INSERT INTO <t> (<present-cols>)SELECT <present-cols> FROM json_populate_recordset(NULL::<t>, $1::json)ON CONFLICT DO NOTHINGUnknown columns in a row are ignored (forward-compat).
ros_conversations also has UNIQUE (session_key, agent). After each
conversation batch the importer resolves destination ids with
SELECT id, session_key, agent … WHERE (session_key, agent) IN (…) and keeps
an incoming id → destination id map for the rest of the import. Messages
are rewritten through that map before insert. A natural-key hit on a different
id is counted in merged.ros_conversations. A message whose conversation is
not in the map and does not exist in the destination is counted in
skipped.orphan_messages and is not inserted. Wiki topics already merge on
PK slug; summaries still conflict on id only (their nullable
conversation_id is rewritten through the same map, or dropped if dangling).
Each messages and summaries batch runs in BEGIN … COMMIT with
SET LOCAL rivet.defer_embed_enqueue = on so the insert trigger does not
enqueue one embed job per row, the GUC does not leak to later pool borrowers,
and a summaries-only dump still defers. Failure ROLLBACKs that batch.
Summaries are two-pass (same as the cloud API importer): insert with
parent_id omitted (NULL), then UPDATE parent links after every summary in
the file is present. Unresolved parent ids (child inserted, parent missing) are
counted on the result as unresolvedParentLinks.
After the file: one graphile_worker.add_job('enqueue-unembedded', '{}') if
the graphile_worker schema exists (pg_namespace). Local-mode databases may
not have it yet — import then prints a hint and skips the enqueue.
--dry-run parses and validates the file without writing.
What is exported
Section titled “What is exported”Line 1 is the header:
{ "type": "rivet-memory-export", "version": 1, "exported_at": "<iso>", "source": { "kind": "cloud|local|datahub", "id": "<slug|hostname>" }, "tables": [ "ros_conversations", "ros_messages", "ros_summaries", "ros_summary_sources", "ros_wiki_topics", "ros_wiki_redirects", "ros_wiki_citations" ]}Following lines are {"t":"<table>","r":{…}} in that table order (FK-safe).
Every non-generated, non-vector column of migrations 0001–0016 is included
except embed bookkeeping.
Omitted: embedding, generated tsvector columns (content_tsv), and
embed_status / embed_error / embed_failures (and any embedded_at-style
column) on ros_messages, ros_summaries, and ros_wiki_topics. Importers
get SQL defaults (NULL status) and re-embed; terminal status in a dump would
skip enqueue-unembedded. Timestamps are ISO-8601, UUIDs are strings, jsonb
is objects.
Not in the dump: ros_message_chunks (rebuilt by the embed worker),
ros_tasks, wiki provenance/extraction tables.
The same format is used by the cloud API (GET/POST /api/t/:slug/export|import).
A file exported from local mode (PGlite, PG18 schema identical) imports into
the cloud (via rivetos cloud import) and vice versa.
Local ↔ cloud portability
Section titled “Local ↔ cloud portability”| Direction | How |
|---|---|
| Local → cloud | rivetos memory export --out dump.ndjson.gz against the laptop RIVETOS_PG_URL, then rivetos cloud import dump.ndjson.gz (or the dashboard Import control). |
| Cloud → local | rivetos cloud export --out dump.ndjson.gz, then rivetos memory import dump.ndjson.gz into the local store. |
| Cloud → cloud | Same file through rivetos cloud import; ON CONFLICT DO NOTHING so a re-import is idempotent. |
Embeddings are always rebuilt at the destination. After import, wait for the
embed worker (enqueue-unembedded) before hybrid search is complete; FTS and
trigram recall work immediately.
