Distributed Deployment

July 15, 2026 · View on GitHub

SuperLocalMemory V3.6.9+ — Multi-machine / LXC / container setup https://superlocalmemory.com | Part of Qualixar

This guide covers running SLM in distributed environments: multiple containers, LXC hosts, VMs, or any topology where the daemon runs on a different host than the MCP clients.


Quick start (single host, already works in v3.6.8)

npm install -g superlocalmemory
slm setup
slm serve start   # or: systemctl start slm-http

Python deployments can instead create and activate a dedicated virtual environment, then run python -m pip install superlocalmemory before setup.

For multi-container setups, read on.


Binding the daemon to a LAN address

By default the daemon binds 127.0.0.1 (loopback only). To serve other containers:

SLM_DAEMON_HOST=0.0.0.0 slm serve start
# or in a systemd unit:
Environment=SLM_DAEMON_HOST=0.0.0.0

Security: Host allowlists are DNS-rebinding/origin controls, not authentication. Remote HTTP MCP requires a configured SLM API key; mesh routes require their configured shared secret. Use TLS and network policy in front of any non-loopback listener. Do not expose the daemon directly to the public internet.


Opening the HTTP MCP transport to LAN clients (v3.6.9+)

The /mcp endpoint uses MCP's DNS-rebinding protection, which defaults to localhost-only even when the daemon is bound on 0.0.0.0. Set SLM_MCP_ALLOWED_HOSTS to open it:

# Allow one specific host (recommended)
SLM_MCP_ALLOWED_HOSTS=192.168.50.144:* slm serve start

# Allow multiple hosts
SLM_MCP_ALLOWED_HOSTS=192.168.50.144:*,slm.lan:* slm serve start

# Broad wildcard (not recommended; still does not replace authentication)
SLM_MCP_ALLOWED_HOSTS=* slm serve start

The value is a comma-separated list of host:port-wildcard patterns, e.g. 192.168.50.144:* allows any port on that IP. Prefer exact hosts or bounded CIDRs. * widens the rebinding/origin surface and is not recommended, even on a private LAN.

Remote HTTP MCP requests must also present the configured SLM API key. The allowlist decides which hosts may reach the transport; it does not create an authenticated actor.


One-switch LAN mode: SLM_REMOTE=1 (v3.6.12)

SLM historically assumes every dashboard browser, MCP client, and API caller is on 127.0.0.1. That breaks three things when you reach SLM across a LAN (issues #39 / #40):

  1. The Brain page can't load from a remote browser — /internal/token refuses non-loopback clients, so the dashboard never gets the install token.
  2. Mesh / forwarded MCP tools return -32600 Session not found — the Streamable-HTTP transport is stateful, so any gateway/hub that doesn't replay the Mcp-Session-Id is rejected.
  3. The dashboard CSRF origin guard only accepts loopback origins.

SLM_REMOTE=1 flips all three at once — default OFF, so the loopback-only posture is unchanged for local installs. LAN access is still gated by your existing SLM_MCP_ALLOWED_HOSTS allowlist:

export SLM_DAEMON_HOST=0.0.0.0
export SLM_MCP_ALLOWED_HOSTS=192.168.50.0/24   # exact IP, CIDR, prefix*, or *
export SLM_REMOTE=1
slm serve start

What SLM_REMOTE=1 does:

  • /internal/token can serve the install token to allowlisted clients. Treat every allowlisted host as trusted local-operator infrastructure; an IP allowlist is not user authentication.
  • MCP transport runs stateless so gateways/hubs/forwarders work without replaying the session id (fixes the mesh -32600). Available standalone as SLM_MCP_STATELESS=1 if you only need the gateway fix without opening the token endpoint.
  • The dashboard CSRF origin guard also accepts allowlisted LAN origins.
  • The dashboard rate limiter exempts allowlisted LAN browsers (they poll like the local dashboard does), so normal use doesn't trip 429.

Security: Remote mode widens the attack surface. Stateless MCP relaxes per-session isolation, and serving an install token to a LAN host gives that host a local dashboard credential. Keep SLM_MCP_ALLOWED_HOSTS specific, configure the SLM API key for remote MCP, configure SLM_MESH_SHARED_SECRET for mesh, and terminate TLS at a trusted gateway.

Tuning the dashboard rate limiter (v3.6.12)

If you hit 429 Too Many Requests while debugging over a LAN, raise the limits (defaults: 30 writes / 120 reads per 60s):

export SLM_RATE_LIMIT_WRITE=100
export SLM_RATE_LIMIT_READ=500
export SLM_RATE_LIMIT_WINDOW=60

In SLM_REMOTE=1 mode, allowlisted LAN clients are exempt from rate limiting anyway, so this is mainly for non-allowlisted callers or extra headroom.


LXC / multi-container example (the #36 reporter's setup)

SLM container:     192.168.50.144   (runs daemon + HTTP MCP)
Hub container:     192.168.50.143   (runs SLM Hub MCP client)
OpenClaw container:192.168.50.142

On the SLM container:

export SLM_DAEMON_HOST=0.0.0.0
export SLM_MCP_ALLOWED_HOSTS=192.168.50.143:*,192.168.50.142:*
export SLM_MESH_SHARED_SECRET=<random 32-char string>
slm serve start

On Hub / OpenClaw containers, point to the SLM host:

# In mcp client config or env:
SLM_DAEMON_URL=http://192.168.50.144:8765

stdio + HTTP coexistence (v3.6.9+)

slm mcp (stdio transport) now reuses the running daemon instead of starting a second one on the same port. If you run slm mcp on the same machine as a systemd slm-http service, they coexist cleanly — one daemon, many front-ends.


Per-agent identity over HTTP — /mcp/{agent_id} (v3.6.10+)

The HTTP MCP endpoint accepts an agent-id path segment so that every AI tool sharing the one daemon gets its own audit attribution — without spawning a separate slm mcp stdio process per tool (which wastes RAM).

URLResolved agent_id
http://127.0.0.1:8765/mcp/mcp_client (default, backward compatible)
http://127.0.0.1:8765/mcp/claudeclaude
http://127.0.0.1:8765/mcp/hermeshermes
http://127.0.0.1:8765/mcp/geminigemini
http://127.0.0.1:8765/mcp/codexcodex
http://127.0.0.1:8765/mcp/kimikimi

The daemon extracts the segment from the URL path into a per-request ContextVar, so remember, recall, observe, delete_memory, update_memory, session_init, and event emission all tag the correct agent — no MCP-protocol changes are required.

The path segment is metadata, not authentication. Mutation authority derives from the verified local capability, same-origin install token, configured SLM API key, or documented mesh credential. A caller cannot grant itself trust by choosing a different {agent_id}.

Precedence: URL path segment → SLM_AGENT_ID env var (stdio) → mcp_client. The bare /mcp/ endpoint is unchanged, so existing configs keep working.

Client config examples

Claude Code / Claude Desktop (~/.claude.jsonmcpServers):

"superlocalmemory": { "type": "http", "url": "http://127.0.0.1:8765/mcp/claude" }

Gemini CLI / Codex / Kimi — point each tool's MCP HTTP URL at its own segment (/mcp/gemini, /mcp/codex, /mcp/kimi). Any string is accepted as the agent id; pick a stable, lowercase name per tool.

Rollout order matters: point a client at /mcp/{agent_id} only after the daemon is running v3.6.10+ (slm --version). An older daemon mounts a bare /mcp without the extractor and will not recognise the extra path segment.


Complete SLM_* environment variable reference

Generated from source at v3.6.9. NEW marks variables added in this release.

Daemon / bind / paths

VariablePurposeDefault
SLM_DAEMON_HOSTBind address for the HTTP daemon127.0.0.1
SLM_HOSTAlias for SLM_DAEMON_HOST
SLM_DAEMON_PORTNEW Port for the HTTP daemon (fully wired as of v3.6.9)8765
SLM_DAEMON_IDLE_TIMEOUTSeconds of inactivity before auto-shutdown (0 = always-on)0
SLM_DATA_DIROverride the base data directory~/.superlocalmemory
SLM_HOMEAlias for SLM_DATA_DIR
SLM_MEMORY_DBOverride memory.db path$SLM_DATA_DIR/memory.db
SLM_CACHE_DBOverride cache.db path$SLM_DATA_DIR/cache.db
SLM_DISABLE_LEGACY_PORTSet 1 to disable the 8767 backward-compat redirect
SLM_PROFILE_IDDefault profile IDdefault
SLM_AGENT_IDOverride agent identifier for multi-agent attribution
SLM_SESSION_IDOverride session ID (usually generated by session_init)
SLM_VERSIONRead-only: current package version

MCP transport

VariablePurposeDefault
SLM_MCP_EMBEDDEDSet 1 when running MCP inside the daemon (suppresses warmup threads)
SLM_MCP_ALLOWED_HOSTSNEW Comma-separated allowlist (host:port*, exact IP, CIDR, prefix*, or *) for HTTP MCP + LAN token/origin/rate-limit (see above)localhost-only
SLM_REMOTENEW (v3.6.12) One-switch LAN mode: serves token to allowlisted LAN clients, runs MCP stateless, relaxes origin guard, exempts LAN from rate limit. Default OFF
SLM_MCP_STATELESSNEW (v3.6.12) Run MCP transport stateless only (gateway/hub fix) without opening the token endpoint
SLM_MCP_TOOLSComma-separated list of MCP tools to expose (default: all)
SLM_RATE_LIMIT_WRITENEW (v3.6.12) Max dashboard write requests per window30
SLM_RATE_LIMIT_READNEW (v3.6.12) Max dashboard read requests per window120
SLM_RATE_LIMIT_WINDOWNEW (v3.6.12) Rate-limit window in seconds60
SLM_MCP_ALL_TOOLSSet 1 to force-enable all tools regardless of mode
SLM_MCP_MESH_TOOLSSet 1 to always include mesh tools

Mesh

VariablePurposeDefault
SLM_MESH_HOSTBind address for mesh WebSocket broker127.0.0.1
SLM_MESH_WS_PORTWebSocket port for mesh broker8766
SLM_MESH_SHARED_SECRETAuth secret for the mesh HTTP API. Required when SLM_MESH_HOST is not localhost. Send as Authorization: Bearer <secret> (canonical) or X-Mesh-Secret: <secret> (legacy).
SLM_MESH_PEER_URLExplicit peer URL to register with at startup
SLM_MESH_DISCOVERYDiscovery mode: local / manuallocal

Mesh API auth (v3.6.20): When SLM_MESH_SHARED_SECRET is set, non-loopback callers must authenticate every /mesh/* request. The canonical header is Authorization: Bearer <your-secret> — this is what RemoteSyncClient sends automatically. The legacy X-Mesh-Secret: <your-secret> header is also accepted for backwards compatibility.

Example: curl http://192.168.50.144:8765/mesh/status -H "Authorization: Bearer <your-secret>"

Note: The variable is SLM_MESH_WS_PORT (not SLM_MCP_WS_PORT). SLM_DAEMON_HOST is canonical; SLM_HOST is the alias (not the other way around).

Memory / health / workers

VariablePurposeDefault
SLM_RSS_BUDGET_MBNEW Global RSS budget for the health monitor watchdog (0 = auto, 40% of RAM)auto
SLM_MAX_WORKER_MBPer-worker RSS limit before the per-worker watchdog triggers2048
SLM_MAX_EMBEDDING_WORKERSMax parallel embedding worker processes1
SLM_EMBED_WORKER_RSS_LIMIT_MBRSS limit per embedding worker process1500
SLM_EMBED_IDLE_TIMEOUTSeconds before an idle embedding worker exits120
SLM_EMBED_RECYCLE_AFTERRecycle embedding worker after N requests1000
SLM_EMBED_RESPONSE_TIMEOUTTimeout (s) for a single embedding request30
SLM_RERANKER_IDLE_TIMEOUTSeconds before an idle reranker worker exits120
SLM_MIN_AVAILABLE_MEMORY_GBMinimum free system RAM before SLM defers heavy operations1.0
SLM_TRIGRAM_BOOTSTRAP_RAM_MBMax RAM for trigram index bootstrap512

Learning / bandit / signals

VariablePurposeDefault
SLM_SIGNALS_ENABLEDEnable implicit reward signal collection1
SLM_SIGNAL_QUEUE_MAXMax buffered signals before flush500
SLM_BANDIT_DISABLEDSet 1 to disable the contextual bandit ranker
SLM_BANDIT_ALPHA_CAPMaximum bandit learning rate0.3
SLM_BANDIT_REWARD_WINDOW_SECWindow (s) for reward aggregation3600
SLM_BANDIT_PLAYS_RETENTION_DAYSKeep bandit play history for N days30
SLM_DNA_SEEDRandom seed for reproducible bandit initialisation

Evolution

VariablePurposeDefault
SLM_EVOLUTION_ENABLEDEnable skill evolution0
SLM_EVOLUTION_BACKENDEvolution backend: local / remotelocal
SLM_EVOLUTION_RETRY_CAPMax retries per evolution attempt3

Rate limiting

VariablePurposeDefault
SLM_RATE_LIMIT_READMax read requests per window120
SLM_RATE_LIMIT_WRITEMax write requests per window30
SLM_RATE_LIMIT_PER_AGENTPer-agent request cap per window
SLM_RATE_LIMIT_WINDOWRate-limit window in seconds60

Adapters / sync

VariablePurposeDefault
SLM_CURSOR_*Cursor IDE adapter settings
SLM_COPILOT_*GitHub Copilot adapter settings
SLM_ANTIGRAVITY_*Antigravity adapter settings
SLM_ADAPTER_FORCE_*Force-enable a specific adapter
SLM_CROSS_PLATFORM_SYNC_DISABLEDSet 1 to disable cross-tool sync
SLM_CROSS_PLATFORM_SYNC_INTERVALSync interval in seconds60

Recall / ingest / misc

VariablePurposeDefault
SLM_RECALL_NO_FLOORSet 1 to disable the relevance floor (returns all results)
SLM_RECALL_TIMINGSet 1 to log per-channel recall timing
SLM_RANKINGOverride ranking algorithm: bandit / bm25 / semanticauto
SLM_INGEST_NO_GATESet 1 to skip the ingest quality gate
SLM_OBSERVE_DEBOUNCE_SECDebounce window (s) for observe auto-capture5
SLM_TOPIC_SHIFT_LOGSet 1 to log topic-shift detection
SLM_HOOK_DAEMON_URLOverride daemon URL for hook integrations
SLM_HOOK_DAEMON_TIMEOUTTimeout (s) for hook→daemon requests5
SLM_DISABLE_WARMUP_SIDE_EFFECTSSet 1 to suppress daemon auto-start in tests
SLM_DISABLE_HF_DOWNLOADSet 1 to block HuggingFace model downloads
SLM_SKIP_DEP_CHECKSet 1 to skip dependency version checks
SLM_NON_INTERACTIVESet 1 to suppress interactive prompts
SLM_DISABLESet 1 to disable SLM entirely (no-op MCP tools)
SLM_V2_PIPELINE_DISABLEDSet 1 to force v1 store pipeline
SLM_INJECTION_LEGACYSet 1 to use legacy context injection format
SLM_SIGNER_KEYHMAC key for memory signing (anti-tamper)

Health config via config.json (v3.6.9+)

You can now tune the health monitor via ~/.superlocalmemory/config.json:

{
  "health": {
    "global_rss_budget_mb": 0,
    "heartbeat_timeout_sec": 60,
    "health_check_interval_sec": 15,
    "enable_structured_logging": true
  }
}

global_rss_budget_mb: 0 means auto (40% of physical RAM, floor 2500 MB). SLM_RSS_BUDGET_MB env takes priority over the config file value.