Skip to content

Latest commit

 

History

History
236 lines (179 loc) · 9.61 KB

deployment.mdx

File metadata and controls

236 lines (179 loc) · 9.61 KB
title Deployment
description Build and run the membraned daemon, configure Postgres, and deploy with Docker Compose.

Membrane ships a standalone daemon binary (membraned) that exposes the full gRPC API. This guide covers building the binary, command-line flags, Docker Compose, and deployment tier selection.

Building the binary

git clone https://github.com/BennettSchwartz/membrane.git
cd membrane
make build

The binary is written to ./bin/membraned. Verify the build:

./bin/membraned --version

Starting the daemon

Start the bundled development database.
```bash
export MEMBRANE_POSTGRES_PASSWORD="$(openssl rand -hex 24)"
export MEMBRANE_POSTGRES_DSN="postgres://membrane:${MEMBRANE_POSTGRES_PASSWORD}@127.0.0.1:5432/membrane_test?sslmode=disable"
docker compose up -d
```
Pass a PostgreSQL DSN. Membrane requires Postgres with the `vector` extension.
```bash
./bin/membraned --postgres-dsn "$MEMBRANE_POSTGRES_DSN"
```
Load a YAML config file. Command-line flags override file values.
```bash
./bin/membraned --config /etc/membrane/config.yaml
```
Override the DSN and listen address without a config file.
```bash
./bin/membraned --postgres-dsn "$MEMBRANE_POSTGRES_DSN" --addr 127.0.0.1:8080
```

Command-line flags

Flag Type Default Description
--config string "" Path to YAML config file
--postgres-dsn string "" PostgreSQL DSN; required unless postgres_dsn or MEMBRANE_POSTGRES_DSN is set
--addr string "127.0.0.1:9090" gRPC listen address (overrides config)
--version bool false Print version and exit
SQLite is not supported for daemon storage. The Postgres schema creates `CREATE EXTENSION IF NOT EXISTS vector` on startup.

Docker Compose setup

The repository includes a docker-compose.yml for running the Postgres + pgvector store:

services:
  postgres:
    image: pgvector/pgvector:pg16
    environment:
      POSTGRES_DB: membrane_test
      POSTGRES_USER: membrane
      POSTGRES_PASSWORD: ${MEMBRANE_POSTGRES_PASSWORD:?set MEMBRANE_POSTGRES_PASSWORD to a generated development secret}
    ports:
      - "127.0.0.1:5432:5432"

Start the database:

export MEMBRANE_POSTGRES_PASSWORD="$(openssl rand -hex 24)"
export MEMBRANE_POSTGRES_DSN="postgres://membrane:${MEMBRANE_POSTGRES_PASSWORD}@127.0.0.1:5432/membrane_test?sslmode=disable"
docker compose up -d

Then start the daemon pointing at it:

./bin/membraned --postgres-dsn "$MEMBRANE_POSTGRES_DSN"

The bundled service is development-only. Its host port is loopback-bound, and Compose refuses to start until MEMBRANE_POSTGRES_PASSWORD is supplied.


Postgres setup

Membrane requires PostgreSQL 14+ with the pgvector extension. The pgvector/pgvector:pg16 Docker image includes it pre-installed.

If you are using an existing Postgres instance, install the extension:

CREATE EXTENSION IF NOT EXISTS vector;

Membrane runs Postgres schema migrations automatically on startup. Embedding vectors are stored in a fixed-dimension pgvector column. The configured embedding_dimensions value must match the embedding model, and Membrane rejects empty, non-finite, all-zero, or wrong-dimension vectors before they reach Postgres. Dimension changes require a database migration because pgvector columns are typed by size, but same-dimension embedding_model changes are accepted: Membrane updates embedding metadata, filters lookup and search to the configured model, and lets startup backfill refresh records that only have stale vectors. Ordinary list queries use a stable salience, creation-time, and record-ID order. Vector searches use cosine distance and a stable record_id tie-breaker so equal-distance results stay deterministic.

Use your database provider's volume encryption, backups, and access controls for at-rest protection.


Environment variable reference

Variable Purpose
MEMBRANE_POSTGRES_DSN PostgreSQL DSN
MEMBRANE_EMBEDDING_ENDPOINT Embedding endpoint when embedding_endpoint is unset
MEMBRANE_EMBEDDING_MODEL Embedding model when embedding_model is unset
MEMBRANE_EMBEDDING_DIMENSIONS Embedding dimensions when the config value is still the default
MEMBRANE_EMBEDDING_API_KEY API key for the embedding endpoint
MEMBRANE_LLM_ENDPOINT Semantic extraction endpoint when llm_endpoint is unset
MEMBRANE_LLM_MODEL Semantic extraction model when llm_model is unset
MEMBRANE_LLM_API_KEY API key for the semantic extraction LLM endpoint
MEMBRANE_INGEST_LLM_ENDPOINT Ingest interpretation endpoint when ingest_llm_endpoint is unset
MEMBRANE_INGEST_LLM_MODEL Ingest interpretation model when ingest_llm_model is unset
MEMBRANE_INGEST_LLM_API_KEY API key for the ingest-side interpretation endpoint
MEMBRANE_API_KEY Bearer token for gRPC client authentication
MEMBRANE_READ_MAX_SENSITIVITY Read sensitivity ceiling when read_max_sensitivity is empty
MEMBRANE_READ_SCOPES Comma-separated gRPC read scopes; overrides the configured list when non-empty
MEMBRANE_WRITE_MAX_SENSITIVITY Write sensitivity ceiling when write_max_sensitivity is empty
MEMBRANE_WRITE_SCOPES Comma-separated gRPC write scopes; overrides the configured list when non-empty

Environment variables are read at startup for the DSN, optional endpoint/model pairs, default embedding dimensions, secrets, and gRPC policy. Config file values take precedence over endpoint/model and sensitivity environment variables unless the matching field is empty; MEMBRANE_EMBEDDING_DIMENSIONS overrides only the default dimensions value; API-key environment variables are read only when the matching config field is empty. The comma-separated read/write scope variables replace the configured lists when non-empty. Endpoint and model values must still be configured as complete pairs, and API-key variables do not enable an embedding or LLM subsystem by themselves.


Starting with a custom config file

Create a config file and pass it with --config:

postgres_dsn: "" # set MEMBRANE_POSTGRES_DSN in the environment
listen_addr: "127.0.0.1:9090"
decay_interval: "1h"
consolidation_interval: "6h"
default_sensitivity: "low"
read_max_sensitivity: "low"
read_scopes: ["default"]
write_max_sensitivity: "low"
write_scopes: ["default"]
selection_confidence_threshold: 0.7
graph_default_root_limit: 10
graph_default_node_limit: 25
graph_default_edge_limit: 100
graph_default_max_hops: 1
rate_limit_per_second: 100

# Embedding-backed retrieval
embedding_endpoint: "https://api.openai.com/v1/embeddings"
embedding_model: "text-embedding-3-small"
embedding_dimensions: 1536

# LLM-backed semantic extraction
llm_endpoint: "https://api.openai.com/v1/chat/completions"
llm_model: "gpt-5-mini"

# Optional ingest-side interpretation
ingest_llm_enabled: true
ingest_llm_endpoint: "https://api.openai.com/v1/chat/completions"
ingest_llm_model: "gpt-5-mini"

# TLS
tls_cert_file: "/etc/membrane/tls.crt"
tls_key_file:  "/etc/membrane/tls.key"

The default loopback listener is appropriate for a local client or a colocated reverse proxy. Binding to a non-loopback address requires api_key; when API-key authentication is enabled on a non-loopback listener, configure the TLS files as well. allow_insecure_credentials: true is an explicit development-only escape hatch for trusted private networks.

./bin/membraned --config /etc/membrane/config.yaml

Deployment tiers

Choose a tier based on your concurrency and feature requirements:

Tier Store Embedding LLM When to use
1 Postgres + pgvector — — Typed storage, graph edges, trust gating, decay, and revisions
2 Postgres + pgvector Yes — Hybrid vector+salience ranking for all record types
3 Postgres + pgvector Yes Yes Full system: background LLM extraction turns episodic traces into semantic facts
Required Postgres + pgvector store with typed records, graph edges, trust gating, decay, and revision history. Adds hybrid vector+salience ranking for all record types. Retrieval quality matches pure RAG while preserving Membrane's lifecycle advantages (decay, retraction, reinforcement, supersession). Adds LLM-backed consolidation that automatically extracts typed semantic facts from episodic traces during background consolidation runs.

Graceful shutdown

The daemon handles SIGINT and SIGTERM. On receipt, it:

  1. Cancels the background context for decay, consolidation, and startup embedding backfill.
  2. Drains in-flight gRPC requests.
  3. Waits for Membrane background jobs to finish.
  4. Closes the database.

Startup embedding backfill keeps processing after individual record failures, then logs an aggregate error with the number of failed records. Pair this with embedding_coverage from GetMetrics to alert when pgvector coverage remains incomplete after startup.

This ordering prevents handlers from hitting a closed database. Avoid sending SIGKILL during an active consolidation run.