Self-Hosting
Self-hosting means running your own relayer, either pointing at an existing Walrus Memory package ID or deploying an entirely new Walrus Memory instance with your own contract, database, and server wallet.
The managed relayer provided by Walrus Foundation is a reference implementation. You can also build your own implementation that fits the same API surface with custom logic. This guide covers how to run the reference implementation as your own self-hosted relayer.
If you want the default relayer-handled SDK flow while reducing trust in the host operator, see the TEE Deployment Pattern.
Personas and when to self-host
There are two primary personas who typically self-host the relayer:
- Builders and Teams: Self-hosting for their own agentic needs or internal team usage, keeping the trust boundary, encryption, and embeddings under their control.
- Infra Operators / Managed Service Providers (MSPs): Hosting the relayer as a reliable platform or service for other external development teams and agentic builders.
The most common reasons to self-host include:
- Control the trust boundary, keeping plaintext, encryption, and embedding under your own control rather than trusting a third-party.
- Run your own Walrus Memory instance, deploying your own contract with a separate package ID, Seal encryption keys, and hard data isolation.
- Choose your own embedding provider, using your own OpenAI-compatible API and credentials.
- Guarantee availability, the managed relayer is a beta service with no SLA.
Data isolation (namespaces)
With the current architecture, Walrus Memory isolates data strictly by User (Owner address) and Namespace.
Because the relayer inherently scopes all vector searches and storage operations by owner + namespace, multiple agents or applications can safely share the same relayer deployment simply by using different namespaces or operating under different delegate keys.
Horizontal scaling
If you are a Managed Service Provider or need to handle high agentic throughput, you can horizontally scale your hosted relayer natively. To run multiple instances of the relayer behind a load balancer for the same account/package ID:
- Point all relayer instances to the same PostgreSQL database.
- Supply the same
SERVER_SUI_PRIVATE_KEYSpool to all instances so they can seamlessly execute concurrent Walrus uploads. - Configure the same Redis cluster (
REDIS_URL) across all nodes so that the rate limiter sliding window accurately tracks global user quotas across your deployment.
What runs
A self-hosted Walrus Memory backend has:
| Component | Location | Description |
|---|---|---|
| Rust relayer | services/server | Axum HTTP server, auth, routing, embedding, vector search |
| TypeScript sidecar | services/server/scripts | Seal encrypt/decrypt, Walrus upload, blob query (uses @mysten/seal and @mysten/walrus) |
| PostgreSQL + pgvector | External | Vector storage, auth cache, indexer state |
| Indexer (recommended) | services/indexer | Polls Sui events, syncs account data into PostgreSQL |
The Rust relayer starts the TypeScript sidecar as a child process on boot. They communicate over HTTP (localhost:9000 by default). If the sidecar fails to start within 15 seconds, the relayer exits.
Quick start
If you do not already have PostgreSQL + pgvector running, start it with:
$ docker compose -f services/server/docker-compose.yml up -d postgres
Then run the relayer:
$ cp services/server/.env.example services/server/.env
$ cd services/server/scripts
$ npm ci
$ cd ..
$ cargo run
Then check:
$ curl http://localhost:8000/health
Required
DATABASE_URLMEMWAL_PACKAGE_IDMEMWAL_REGISTRY_IDSERVER_SUI_PRIVATE_KEYorSERVER_SUI_PRIVATE_KEYSSIDECAR_AUTH_TOKEN, shared secret for Rust-to-sidecar calls. The sidecar refuses to start without it.
Recommended
OPENAI_API_KEY, enables real embeddings (falls back to mock embeddings without it)OPENAI_API_BASE, point to an OpenAI-compatible provider like OpenRouter
Rate limits and storage (optional)
By default, the relayer enforces rate limits and storage quotas through Redis to prevent abuse. You can customize these limits:
RATE_LIMIT_REQUESTS_PER_MINUTE, max burst weighted-requests per minute per user (default: 60)RATE_LIMIT_REQUESTS_PER_HOUR, max sustained weighted-requests per hour per user (default: 500)RATE_LIMIT_DELEGATE_KEY_PER_MINUTE, max weighted-requests per minute per delegate key (default: 30)RATE_LIMIT_STORAGE_BYTES, max storage per user in bytes (default: 1 GB,1073741824)REDIS_URL, required to track sliding windows for rate limits (default:redis://localhost:6379)
Defaults
PORTdefaults to8000SIDECAR_URLdefaults tohttp://localhost:9000SUI_NETWORKdefaults tomainnetSUI_RPC_URL, Walrus endpoints, andWALRUS_PACKAGE_IDfall back to network defaults based onSUI_NETWORKSEAL_SERVER_CONFIGSandSEAL_KEY_SERVERSare optional overrides for encrypt/decrypt; preferSEAL_SERVER_CONFIGSfor custom committeesWALRUS_AGGREGATOR_URLScan add comma-separated proxy/aggregator candidates for cold-read tail racing after Redis cache missesWALRUS_SKIP_CONSISTENCY_CHECK=falseby default; enable only for trusted Walrus Memory-written cold reads after accepting the consistency tradeoff- The sidecar Walrus upload route defaults storage
epochsby network:50ontestnet,2onmainnet(unless the request passesepochs) SEAL_THRESHOLDdefaults tomin(2, total configured server weight). A single committee server config defaults to threshold1.
Server keys
SERVER_SUI_PRIVATE_KEYis the main server keySERVER_SUI_PRIVATE_KEYSis a comma-separated key pool for parallel Walrus uploads- if both are set, the key pool takes priority for uploads
Staging (testnet)
SUI_NETWORK=testnet
MEMWAL_PACKAGE_ID=0xcf6ad755a1cdff7217865c796778fabe5aa399cb0cf2eba986f4b582047229c6
MEMWAL_REGISTRY_ID=0xe80f2feec1c139616a86c9f71210152e2a7ca552b20841f2e192f99f75864437
Production (mainnet)
SUI_NETWORK=mainnet
MEMWAL_PACKAGE_ID=0xcee7a6fd8de52ce645c38332bde23d4a30fd9426bc4681409733dd50958a24c6
MEMWAL_REGISTRY_ID=0x0da982cefa26864ae834a8a0504b904233d49e20fcc17c373c8bed99c75a7edd
If neither SEAL_SERVER_CONFIGS nor SEAL_KEY_SERVERS is set, the sidecar uses built-in defaults for the selected SUI_NETWORK. On testnet, the default remains Mysten's original independent key server pair so existing encrypted memories remain decryptable:
SEAL_KEY_SERVERS=0x73d05d62c18d9374e3ea529e8e0ed6161da1a141a94d3f76ae3fe4e99356db75,0xf5d14a81a982144ae441cd7d64b09027f116a468bd36e7eca494f750591623c8
On mainnet, the default remains the legacy independent key server pair until Mysten publishes an official committee aggregator.
Use SEAL_SERVER_CONFIGS to opt into a committee key server. Committee entries require an aggregatorUrl, for example:
SEAL_SERVER_CONFIGS=[{"objectId":"0x...","weight":1,"aggregatorUrl":"https://seal-aggregator.example.com"}]
Mysten's official Testnet committee aggregator is:
SEAL_SERVER_CONFIGS=[{"objectId":"0xb012378c9f3799fb5b1a7083da74a4069e3c3f1c93de0b27212a5799ce1e1e98","weight":1,"aggregatorUrl":"https://seal-aggregator-testnet.mystenlabs.com"}]
Although that committee is 3-of-5 internally, Seal exposes it to the SDK as one logical server config. The aggregator handles the internal committee threshold, so leave SEAL_THRESHOLD unset or set it to 1 when using this committee config. Because it uses a different key server object, do not switch an existing deployment to it until older data has been migrated or re-encrypted.
Changing Seal key server defaults only affects new encryption. If a deployment already has memories encrypted with the Testnet independent key servers, keep those servers as the default or pin them with SEAL_KEY_SERVERS until the data has been migrated or re-encrypted. Otherwise, recall and restore for older blobs might fail to decrypt.
Using official key server of SDK is recommended.
VITE_MEMWAL_PACKAGE_ID and VITE_MEMWAL_REGISTRY_ID are frontend env vars for the app or playground, not for the relayer.
Database setup
The relayer requires PostgreSQL with the pgvector extension. The relayer runs migrations automatically on boot, creating these tables:
vector_entries, 1536-dimensional embeddings with HNSW index for cosine similarity searchdelegate_key_cache, auth optimization (delegate key → account mapping)accounts, populated by the indexer (account → owner mapping)indexer_state, indexer cursor tracking
See Database Sync for the full schema.
Operational notes
- The server starts the sidecar automatically on boot, if sidecar startup fails, the relayer exits
- DB migrations run automatically on boot (
pgvectormust already be installed as a PostgreSQL extension) - Connection pool: 10 max connections (relayer), 3 max connections (indexer)
/healthis the basic service check,/metricsexposes Prometheus metrics, API routes live under/api/*- The indexer is recommended for fast account lookup in production, without it, the relayer falls back to onchain registry scans
- Without
OPENAI_API_KEY, the server uses deterministic mock embeddings (hash-based), useful for local testing but not production - Use
LOG_FORMAT=jsonin production and see Observability for dashboards and alerts
Docker
services/server/Dockerfilefor the relayerservices/indexer/Dockerfilefor the indexer