Configuration

Memory Service is configured through CLI flags or environment variables. Use the toggle below to switch between formats.

Config format

Tip: Your format preference is saved across visits. Every CLI flag has a corresponding environment variable.

Server Configuration

FlagValuesDefaultDescription
--tls-cert-fileMEMORY_SERVICE_TLS_CERT_FILEpath(none)TLS certificate file
--tls-key-fileMEMORY_SERVICE_TLS_KEY_FILEpath(none)TLS private key file
--tls-self-signedMEMORY_SERVICE_TLS_SELF_SIGNEDtrue, falsefalseGenerate an ephemeral self-signed certificate when cert/key files are omitted
--advertised-addressMEMORY_SERVICE_ADVERTISED_ADDRESShost:portauto-detectedAdvertised address for client redirects
--read-header-timeout-secondsMEMORY_SERVICE_READ_HEADER_TIMEOUT_SECONDSinteger5HTTP read header timeout in seconds
--body-read-timeoutMEMORY_SERVICE_BODY_READ_TIMEOUTduration30sMaximum time allowed to read ordinary REST request bodies (0 disables)
--attachment-body-read-timeoutMEMORY_SERVICE_ATTACHMENT_BODY_READ_TIMEOUTduration5mMaximum time allowed to read multipart attachment upload bodies (0 disables)
--max-page-sizeMEMORY_SERVICE_MAX_PAGE_SIZEpositive integer1000Maximum items accepted by listing endpoints
--temp-dirMEMORY_SERVICE_TEMP_DIRpathsystem temp dirDirectory for temporary files
--management-access-logMEMORY_SERVICE_MANAGEMENT_ACCESS_LOGtrue, falsefalseEnable operational logging for /health, /ready, /metrics
MEMORY_SERVICE_LOG_LEVELdebug, info, warn, error, fatalinfoServer log level

Operational event logging

Memory Service emits one canonical completion event for every REST and gRPC operation and every claimed background task attempt. Long-running streams and substantial background runs also emit a phase=start event. REST operation names use route templates, such as http GET /v1/conversations/{conversationId}, and never use the concrete request URL. gRPC operation names use the full service method, and background operation names use stable job.* identifiers.

Completion events include phase, status, duration, and one semantic result: success, invalid, unauthenticated, forbidden, not_found, conflict, rate_limited, timed_out, canceled, rejected, failed, or retrying. Depending on the operation they may also include requestID, authenticated identity IDs, resource IDs, connectionID, cursor, retry attempt, work/failure counts, and bounded provider diagnostics.

Canonical events never include concrete unmatched paths, query values, headers, cookies, request or response bodies, credentials, message or memory content, raw provider responses, raw errors, or stack traces. Internal failure point logs retain stack traces where required. Provider diagnostics are allowlisted, bounded metadata under errorDetails.

Admin audit records remain separate from operational events. They can contain the configured justification and share the same requestID; the canonical operational event never contains the justification. By default, management probes are omitted. Set --management-access-logMEMORY_SERVICE_MANAGEMENT_ACCESS_LOG to include /health, /ready, and /metrics operations.

Network Listener

--port and --unix-socket are mutually exclusive when both are explicitly configured. If --unix-socket is set, the default port value is ignored for the main listener.

FlagValuesDefaultDescription
--portMEMORY_SERVICE_PORTinteger8080HTTP/gRPC server port
--hostMEMORY_SERVICE_HOSThost/IP127.0.0.1HTTP/gRPC bind host; containers should set 0.0.0.0 intentionally
--unix-socketMEMORY_SERVICE_UNIX_SOCKETpath(unset)Absolute path to a Unix socket for the HTTP/gRPC server
--unix-socket-authMEMORY_SERVICE_UNIX_SOCKET_AUTHcredentials, localcredentialsRequire credentials or trust access to the main Unix socket
--local-user-idMEMORY_SERVICE_LOCAL_USER_IDstringOS usernameUser identity used by unix-socket-auth=local
--local-client-idMEMORY_SERVICE_LOCAL_CLIENT_IDstringlocal-agentClient identity used by unix-socket-auth=local
--plain-textMEMORY_SERVICE_PLAIN_TEXTtrue, falsetrueEnable plaintext HTTP/1.1 + h2c + gRPC
--tlsMEMORY_SERVICE_TLStrue, falsetrueEnable TLS HTTP/1.1 + HTTP/2 + gRPC
--allow-non-loopback-plaintextMEMORY_SERVICE_ALLOW_NON_LOOPBACK_PLAINTEXTtrue, falsefalseExplicitly allow plaintext API traffic on a non-loopback TCP bind outside testing mode
--max-header-bytesMEMORY_SERVICE_MAX_HEADER_BYTESbytes1048576Maximum request header bytes on the main listener
--idle-timeoutMEMORY_SERVICE_IDLE_TIMEOUTduration120sHTTP keep-alive idle timeout on the main listener
--trusted-proxy-cidrsMEMORY_SERVICE_TRUSTED_PROXY_CIDRSIP/CIDR CSV(unset)Trusted TCP proxy IPs/CIDRs for client-IP resolution; unset trusts no forwarded proxies

When a Unix socket path points into a directory that does not exist yet, Memory Service creates the parent directory with permissions restricted to the current user.

TCP listeners bind to loopback by default. Container and Kubernetes deployments that expose a port must set MEMORY_SERVICE_HOST=0.0.0.0 explicitly.

Outside testing mode, a single TCP listener cannot serve both plaintext and TLS. Choose MEMORY_SERVICE_TLS=false for plaintext behind an ingress/TLS terminator and MEMORY_SERVICE_ALLOW_NON_LOOPBACK_PLAINTEXT=true to acknowledge that boundary, or MEMORY_SERVICE_PLAIN_TEXT=false when Memory Service terminates TLS itself.

unix-socket-auth=local is valid only when the main API listener uses a Unix socket. It gives REST and gRPC the same local identity without granting privileged roles automatically. Any process running as the socket-owning Unix user is inside this trust boundary. Keep the default credentials mode when bearer-token or API-key authentication should remain mandatory.

trusted-proxy-cidrs affects client-IP resolution only. The resolved address also keys pre-authentication source and authentication-failure rate limits. Leave the setting unset to ignore forwarded client-IP headers, configure known proxy addresses for a restricted trust boundary, or use 0.0.0.0/0,::/0 to accept forwarded client IPs from every peer in a simpler deployment. With a universal range, direct callers can select their reported client IP and evade IP-based limits, so use it only when that behavior is acceptable or network policy ensures all traffic comes through a controlled proxy. Forwarded host/proto values do not determine browser-facing URLs; set MEMORY_SERVICE_BASE_URL for the developer frontend.

Management Network Listener

Outside testing mode, /health, /ready, and /metrics require an explicit exposure decision. Set --management-port or --management-unix-socket to move them onto a dedicated listener. If you intentionally serve them on the main API listener, set --management-on-main-listener=true.

--management-port and --management-unix-socket are mutually exclusive when both are explicitly configured. If --management-unix-socket is set, the default management port value is ignored for that listener.

FlagValuesDefaultDescription
--management-portMEMORY_SERVICE_MANAGEMENT_PORTinteger(unset)Dedicated port for health and metrics
--management-on-main-listenerMEMORY_SERVICE_MANAGEMENT_ON_MAIN_LISTENERtrue, falsefalseExplicitly acknowledge serving management routes on the main API listener outside testing mode
--management-hostMEMORY_SERVICE_MANAGEMENT_HOSThost/IP127.0.0.1Management bind host; containers should set 0.0.0.0 intentionally
--management-allow-non-loopbackMEMORY_SERVICE_MANAGEMENT_ALLOW_NON_LOOPBACKtrue, falsefalseExplicitly allow a dedicated management listener to bind beyond loopback outside testing mode
--management-unix-socketMEMORY_SERVICE_MANAGEMENT_UNIX_SOCKETpath(unset)Absolute path to a Unix socket for the management server
--management-plain-textMEMORY_SERVICE_MANAGEMENT_PLAIN_TEXTtrue, falsetrueEnable plaintext HTTP for management server
--management-tlsMEMORY_SERVICE_MANAGEMENT_TLStrue, falsetrueEnable TLS for management server (uses same cert/key as main listener)
--management-max-header-bytesMEMORY_SERVICE_MANAGEMENT_MAX_HEADER_BYTESbytes65536Maximum request header bytes on the management listener
--management-idle-timeoutMEMORY_SERVICE_MANAGEMENT_IDLE_TIMEOUTduration30sHTTP keep-alive idle timeout on the management listener

Unix socket example:

# Main API over a Unix socket
--unix-socket=$HOME/.local/run/memory-service/api.sock
--unix-socket-auth=local

# Optional dedicated management socket

--management-unix-socket=$HOME/.local/run/memory-service/mgmt.sock
# Main API over a Unix socket
MEMORY_SERVICE_UNIX_SOCKET=$HOME/.local/run/memory-service/api.sock
MEMORY_SERVICE_UNIX_SOCKET_AUTH=local

# Optional dedicated management socket

MEMORY_SERVICE_MANAGEMENT_UNIX_SOCKET=$HOME/.local/run/memory-service/mgmt.sock

DB Configuration

Memory Service supports PostgreSQL, SQLite, and MongoDB as database backends. The database URL uses standard connection string formats (not JDBC).

FlagValuesDefaultDescription
--db-kindMEMORY_SERVICE_DB_KINDpostgres, sqlite, mongopostgresDatabase backend
--db-urlMEMORY_SERVICE_DB_URLURL(required)Database connection URL
--db-max-open-connsMEMORY_SERVICE_DB_MAX_OPEN_CONNSinteger25Maximum number of open database connections (see backend notes below)
--db-max-idle-connsMEMORY_SERVICE_DB_MAX_IDLE_CONNSinteger5Idle/minimum connections (see backend notes below)
MEMORY_SERVICE_DB_MIGRATE_AT_STARTtrue, falsetrueRun database migrations at startup

DB: PostgreSQL Configuration

PostgreSQL is the recommended database backend for Memory Service.

  • --db-max-open-connsMEMORY_SERVICE_DB_MAX_OPEN_CONNS sets the maximum number of open connections.
  • --db-max-idle-connsMEMORY_SERVICE_DB_MAX_IDLE_CONNS sets the maximum number of idle connections kept in the pool.
# Select PostgreSQL as the datastore
--db-kind=postgres

# PostgreSQL connection (standard URL format)

--db-url=postgresql://postgres:postgres@localhost:5432/memoryservice
# Select PostgreSQL as the datastore
MEMORY_SERVICE_DB_KIND=postgres

# PostgreSQL connection (standard URL format)

MEMORY_SERVICE_DB_URL=postgresql://postgres:postgres@localhost:5432/memoryservice

DB: SQLite Configuration

SQLite is intended for local development, demos, single-node deployments, and CI runs that do not need a separate database container.

  • --db-urlMEMORY_SERVICE_DB_URL can be a plain file path or a file: SQLite URI.
  • SQLite uses a shared database handle with conservative internal pooling and write serialization. The --db-max-open-conns and --db-max-idle-conns settings are not used for SQLite.
  • When --attachments-kindMEMORY_SERVICE_ATTACHMENTS_KIND is omitted in SQLite mode, it defaults to fs.
  • When MEMORY_SERVICE_ATTACHMENTS_FS_DIR is omitted in SQLite mode, the attachment directory is derived from the SQLite DB path by appending .attachments.
# Select SQLite as the datastore
--db-kind=sqlite

# SQLite file path or file: URI

--db-url=/tmp/memory-service.sqlite

# Optional: explicit attachment root (otherwise /tmp/memory-service.sqlite.attachments)

--attachments-kind=fs
--attachments-fs-dir=/tmp/memory-service-attachments
# Select SQLite as the datastore
MEMORY_SERVICE_DB_KIND=sqlite

# SQLite file path or file: URI

MEMORY_SERVICE_DB_URL=/tmp/memory-service.sqlite

# Optional: explicit attachment root (otherwise /tmp/memory-service.sqlite.attachments)

MEMORY_SERVICE_ATTACHMENTS_KIND=fs
MEMORY_SERVICE_ATTACHMENTS_FS_DIR=/tmp/memory-service-attachments

DB: MongoDB Configuration

  • --db-max-open-connsMEMORY_SERVICE_DB_MAX_OPEN_CONNS sets the maximum connection pool size.
  • --db-max-idle-connsMEMORY_SERVICE_DB_MAX_IDLE_CONNS sets the minimum pool size — connections MongoDB keeps open proactively. This is not an idle-connection cap.
# Select MongoDB as the datastore
--db-kind=mongo

# MongoDB connection

--db-url=mongodb://localhost:27017/memoryservice
# Select MongoDB as the datastore
MEMORY_SERVICE_DB_KIND=mongo

# MongoDB connection

MEMORY_SERVICE_DB_URL=mongodb://localhost:27017/memoryservice

Cache Configuration

Memory Service uses a unified cache configuration for all cache-dependent features, including the response recording manager and the context entries cache. Configure the cache backend once, and all features will use it automatically.

FlagValuesDefaultDescription
--cache-kindMEMORY_SERVICE_CACHE_KINDnone, local, redis, infinispannoneCache backend for context entries and response recording
MEMORY_SERVICE_CACHE_EPOCH_TTLdurationPT10MTTL for cached context entries

Memory Entries Cache

When a cache backend is configured, Memory Service caches context entries to reduce database load and improve GET/sync latency. The cache stores the complete list of context entries at the latest epoch for each conversation/client pair.

Features of the context entries cache:

  • Automatic population: Cache is populated on first read and updated after sync operations
  • Write-refreshed TTL: TTL is refreshed when entries are written back into cache
  • In-memory pagination: Cache stores complete entry list; pagination is applied in-memory
  • Graceful degradation: Falls back to database queries if cache is unavailable

Response Recording Settings

Response Recording and Resumption lets clients reconnect to in-progress streaming responses after a network interruption. It automatically uses the configured cache backend and is enabled when cache is local, redis, or infinispan.

Env VarValuesDefaultDescription
MEMORY_SERVICE_RESPONSE_RESUMER_TEMP_FILE_RETENTIONdurationPT30MHow long to retain temp files

Cache: Redis Configuration

Redis provides a fast, distributed cache with full support for the context entries cache and response recording manager.

FlagValuesDefaultDescription
--redis-hostsMEMORY_SERVICE_REDIS_HOSTSRedis URLredis://localhost:6379Redis connection URL
# Enable Redis cache (response recording manager will automatically use it)
--cache-kind=redis

# Redis connection

--redis-hosts=redis://localhost:6379
# Enable Redis cache (response recording manager will automatically use it)
MEMORY_SERVICE_CACHE_KIND=redis

# Redis connection

MEMORY_SERVICE_REDIS_HOSTS=redis://localhost:6379

Cache: Local Configuration

The local backend is intended for single-instance deployments such as local agents or embedded development setups. It is process-local, so do not use it when the service is running with replicas.

Flag / Env VarValuesDefaultDescription
--cache-local-max-bytesMEMORY_SERVICE_CACHE_LOCAL_MAX_BYTESmemory size64MTotal process-local memory budget for cached entries
--cache-local-num-countersMEMORY_SERVICE_CACHE_LOCAL_NUM_COUNTERSinteger100000Higher values improve eviction/admission decisions for larger working sets, but use more memory
--cache-local-buffer-itemsMEMORY_SERVICE_CACHE_LOCAL_BUFFER_ITEMSinteger64Internal batch size for cache reads. Higher values can help under very high read contention
# Enable process-local cache
--cache-kind=local

# Process-local cache budget

--cache-local-max-bytes=64M
# Enable process-local cache
MEMORY_SERVICE_CACHE_KIND=local

# Process-local cache budget

MEMORY_SERVICE_CACHE_LOCAL_MAX_BYTES=64M

Cache: Infinispan Configuration

Infinispan provides a distributed cache for context entries and response recordings.

FlagValuesDefaultDescription
--infinispan-hostMEMORY_SERVICE_INFINISPAN_HOSThost:portlocalhost:11222Infinispan server host
--infinispan-usernameMEMORY_SERVICE_INFINISPAN_USERNAMEstring(none)Infinispan username
--infinispan-passwordMEMORY_SERVICE_INFINISPAN_PASSWORDstring(none)Infinispan password
MEMORY_SERVICE_CACHE_INFINISPAN_STARTUP_TIMEOUTdurationPT30SStartup timeout for Infinispan connection
# Enable Infinispan cache (response recording manager will automatically use it)
--cache-kind=infinispan

# Infinispan connection

--infinispan-host=localhost:11222
--infinispan-username=admin
--infinispan-password=password
# Enable Infinispan cache (response recording manager will automatically use it)
MEMORY_SERVICE_CACHE_KIND=infinispan

# Infinispan connection

MEMORY_SERVICE_INFINISPAN_HOST=localhost:11222
MEMORY_SERVICE_INFINISPAN_USERNAME=admin
MEMORY_SERVICE_INFINISPAN_PASSWORD=password

Event Bus Configuration

The event bus enables real-time Server-Sent Events (SSE). You can use that stream in two ways:

  • Frontend cache invalidation with live, best-effort event delivery
  • Reliable replayable delivery with the event outbox enabled

Single-node deployments use the default local bus. Multi-node deployments use one of the other types for cross-node fan-out.

For higher-scale multi-node deployments, prefer redis or infinispan. The current event routing model is user-scoped, and PostgreSQL LISTEN/NOTIFY does not scale as well with the resulting per-user channel fan-out. PostgreSQL remains supported, but it is better suited to smaller clustered installs.

Flag / Env VarValuesDefaultDescription
--eventbus-kindMEMORY_SERVICE_EVENTBUS_KINDlocal, redis, infinispan, postgreslocalEvent bus backend
--eventbus-outbound-bufferMEMORY_SERVICE_EVENTBUS_OUTBOUND_BUFFERinteger200Outbound channel capacity for cross-node publish pipeline
--eventbus-batch-sizeMEMORY_SERVICE_EVENTBUS_BATCH_SIZEinteger100Max events per cross-node publish batch

SSE Stream Settings

Flag / Env VarValuesDefaultDescription
--sse-keepalive-intervalMEMORY_SERVICE_SSE_KEEPALIVE_INTERVALduration30sInterval between SSE keepalive comments
--sse-max-connections-per-userMEMORY_SERVICE_SSE_MAX_CONNECTIONS_PER_USERinteger5Max concurrent SSE connections per user; older local streams are evicted when exceeded
--sse-subscriber-buffer-sizeMEMORY_SERVICE_SSE_SUBSCRIBER_BUFFER_SIZEinteger64Per-subscriber channel buffer; full buffer triggers eviction

Event Outbox Settings

Enable the outbox when consumers need durable replay with /v1/events?after=<cursor> or gRPC SubscribeEvents.after_cursor. Leave it disabled if you only need live frontend cache invalidation.

Flag / Env VarValuesDefaultDescription
--outbox-enabledMEMORY_SERVICE_OUTBOX_ENABLEDtrue, falsefalseEnable durable event outbox writes and replay-capable event streaming
--outbox-replay-batch-sizeMEMORY_SERVICE_OUTBOX_REPLAY_BATCH_SIZEinteger1000Max outbox events read per replay page before switching back to live tailing

When MEMORY_SERVICE_OUTBOX_ENABLED=true:

  • REST SSE supports GET /v1/events?after=<cursor>
  • Admin SSE supports GET /v1/admin/events?after=<cursor>
  • gRPC supports SubscribeEvents.after_cursor
  • Consumers can store the opaque cursor and resume from the last processed event
  • PostgreSQL deployments must run with logical replication enabled because the outbox relay uses pgoutput

For PostgreSQL, enabling the outbox requires the database server to start with at least:

  • wal_level=logical
  • max_replication_slots >= 1
  • max_wal_senders >= 1

If those settings are missing, memory-service startup fails while creating the PostgreSQL logical replication slot for the outbox relay.

When it is disabled, the event stream still works for live delivery, but replay is unavailable.

Event Bus: Local

The default local backend uses in-process channels. Suitable for single-node deployments and development.

# Local event bus (default, no configuration needed)
--eventbus-kind=local
# Local event bus (default, no configuration needed)
MEMORY_SERVICE_EVENTBUS_KIND=local

Event Bus: Redis

The redis backend uses Redis Pub/Sub for cross-node event fan-out. It reuses the same Redis connection configured for caching and is the recommended choice for better event-stream scaling.

# Redis event bus (reuses cache Redis connection)
--eventbus-kind=redis
--cache-kind=redis
--redis-hosts=redis://localhost:6379
# Redis event bus (reuses cache Redis connection)
MEMORY_SERVICE_EVENTBUS_KIND=redis
MEMORY_SERVICE_CACHE_KIND=redis
MEMORY_SERVICE_REDIS_HOSTS=redis://localhost:6379

Event Bus: Infinispan

The infinispan backend uses Infinispan’s RESP-compatible Pub/Sub endpoint through the same implementation as the Redis event bus. If you already run Infinispan for caching, it is a good choice for higher-scale event delivery.

# Infinispan event bus (reuses Infinispan RESP endpoint)
--eventbus-kind=infinispan
--cache-kind=infinispan
--infinispan-host=localhost:11222
--infinispan-username=admin
--infinispan-password=password
# Infinispan event bus (reuses Infinispan RESP endpoint)
MEMORY_SERVICE_EVENTBUS_KIND=infinispan
MEMORY_SERVICE_CACHE_KIND=infinispan
MEMORY_SERVICE_INFINISPAN_HOST=localhost:11222
MEMORY_SERVICE_INFINISPAN_USERNAME=admin
MEMORY_SERVICE_INFINISPAN_PASSWORD=password

Event Bus: PostgreSQL

The postgres backend uses PostgreSQL LISTEN/NOTIFY for cross-node fan-out. It reuses the configured database connection, but it is not the preferred choice for larger multi-node deployments because Redis/Infinispan scale better with user-scoped event routing.

# PostgreSQL event bus (reuses database connection)
--eventbus-kind=postgres
# PostgreSQL event bus (reuses database connection)
MEMORY_SERVICE_EVENTBUS_KIND=postgres

Attachment Storage

Configure file attachment storage, size limits, and lifecycle. For production attachment deployments, also review the Security Hardening guidance on proxying downloads and durable storage.

Flag / Env VarValuesDefaultDescription
--attachments-kindMEMORY_SERVICE_ATTACHMENTS_KINDdb, fs, s3dbStorage backend for uploaded files
MEMORY_SERVICE_ATTACHMENTS_FS_DIRpath(auto)Root directory for the fs attachment backend. In SQLite mode this defaults to <db-path>.attachments.
MEMORY_SERVICE_ATTACHMENTS_MAX_SIZEmemory size10MMaximum file size per upload (e.g., 10M, 512K, 1G)
MEMORY_SERVICE_ATTACHMENTS_DEFAULT_EXPIRES_INdurationPT1HDefault TTL for unlinked attachments
MEMORY_SERVICE_ATTACHMENTS_MAX_EXPIRES_INdurationPT24HMaximum allowed TTL clients can request
MEMORY_SERVICE_ATTACHMENTS_CLEANUP_INTERVALdurationPT5MHow often the cleanup job runs
MEMORY_SERVICE_ATTACHMENTS_DOWNLOAD_URL_EXPIRES_INdurationPT5MSigned download URL expiry
Signed download URLs are enabled automatically when --encryption-dek-key is set; the signing key is derived from it via HKDF-SHA256.

Attachment Storage: DB Configuration

The default db backend stores attachments directly in PostgreSQL. MongoDB does not support database-backed attachments; configure s3 or fs with an explicit filesystem directory when using MongoDB.

# Use database storage (default)
--attachments-kind=db
# Use database storage (default)
MEMORY_SERVICE_ATTACHMENTS_KIND=db

Attachment Storage: Filesystem Configuration

The fs backend stores attachment bytes on the local filesystem while keeping metadata in the primary datastore.

  • SQLite uses fs by default.
  • If MEMORY_SERVICE_ATTACHMENTS_FS_DIR is unset in SQLite mode, memory-service derives the directory from the SQLite DB path and creates it automatically on startup.
  • fs also works with non-SQLite backends when you want filesystem-backed blobs without S3.
# Store attachment bytes on local disk
--attachments-kind=fs
--attachments-fs-dir=/var/lib/memory-service/attachments
# Store attachment bytes on local disk
MEMORY_SERVICE_ATTACHMENTS_KIND=fs
MEMORY_SERVICE_ATTACHMENTS_FS_DIR=/var/lib/memory-service/attachments

Attachment Storage: S3 Configuration

S3 storage offloads attachments to any S3-compatible object store. Use this for large files or high-throughput workloads.

Flag / Env VarValuesDefaultDescription
--attachments-s3-bucketMEMORY_SERVICE_ATTACHMENTS_S3_BUCKETstringmemory-service-attachmentsS3 bucket name
--attachments-s3-use-path-styleMEMORY_SERVICE_ATTACHMENTS_S3_USE_PATH_STYLEtrue, falsefalseUse path-style S3 addressing (required for LocalStack/MinIO)
MEMORY_SERVICE_ATTACHMENTS_S3_PREFIXstring(empty)Optional key prefix for all objects
MEMORY_SERVICE_ATTACHMENTS_S3_DIRECT_DOWNLOADtrue, falsefalseRedirect clients directly to S3 via presigned URLs
MEMORY_SERVICE_ATTACHMENTS_S3_EXTERNAL_ENDPOINTURL(none)Override endpoint in presigned URLs
# Select S3 storage
--attachments-kind=s3

# S3 bucket configuration

--attachments-s3-bucket=memory-service-attachments
# Select S3 storage
MEMORY_SERVICE_ATTACHMENTS_KIND=s3

# S3 bucket configuration

MEMORY_SERVICE_ATTACHMENTS_S3_BUCKET=memory-service-attachments

Direct download vs. proxy mode: By default, downloads are proxied through memory-service. Set MEMORY_SERVICE_ATTACHMENTS_S3_DIRECT_DOWNLOAD=true to redirect clients directly to the S3 backend via presigned URLs instead.

When using a self-hosted S3-compatible store (like MinIO) that is only reachable at an internal address, you have two options:

  1. Proxy mode (default) — downloads stream through memory-service; no client-reachable S3 endpoint needed.
  2. Direct download with external endpoint — set MEMORY_SERVICE_ATTACHMENTS_S3_DIRECT_DOWNLOAD=true and MEMORY_SERVICE_ATTACHMENTS_S3_EXTERNAL_ENDPOINT to a client-reachable URL (e.g., http://minio-api.example.com).

S3 incompatibility: S3 direct download is incompatible with encryption. Keep direct download disabled (the default) when using a real encryption provider.

See Attachments for details on how attachments work.

Encryption

Memory Service supports transparent encryption of stored data using AES-256-GCM. Configure encryption by selecting one or more providers via --encryption-kind. The first provider is primary — used for all new encryptions. Additional providers are decryption-only fallbacks, enabling zero-downtime key rotation.

Flag / Env VarValuesDefaultDescription
--encryption-kindMEMORY_SERVICE_ENCRYPTION_KINDcomma-separated IDsplainOrdered list of providers (plain, dek, vault, kms); first provider encrypts new data
--encryption-db-disabledMEMORY_SERVICE_ENCRYPTION_DB_DISABLEDtrue, falsefalseDisable at-rest encryption for the database even when an encryption provider is active
--encryption-attachments-disabledMEMORY_SERVICE_ENCRYPTION_ATTACHMENTS_DISABLEDtrue, falsefalseDisable at-rest encryption for the attachment store even when an encryption provider is active
--encryption-allow-plainMEMORY_SERVICE_ENCRYPTION_ALLOW_PLAINtrue, falsefalseExplicitly allow plain as the primary provider outside testing; unsafe for production data

Outside testing, plain as the primary provider is rejected unless MEMORY_SERVICE_ENCRYPTION_ALLOW_PLAIN=true is set deliberately. Encrypted fields accept only MSEH v4 and encrypted attachment streams accept only MSEH v3. Headerless values are accepted only when plain is primary; malformed or unsupported envelopes fail closed.

Encryption: DEK (AES-256-GCM)

The dek provider encrypts data locally using AES-256-GCM with a key you supply. No external service is required — all encryption happens in-process. Signed attachment download URLs are automatically derived from this key via HKDF-SHA256.

Flag / Env VarValuesDefaultDescription
--encryption-dek-keyMEMORY_SERVICE_ENCRYPTION_DEK_KEYhex or base64 string(none)Comma-separated AES-256 keys (16/24/32 bytes). First key encrypts new data; additional keys are legacy decryption-only (for key rotation).
# Enable DEK encryption
--encryption-kind=dek
--encryption-dek-key=<base64-encoded-32-byte-key>
# Enable DEK encryption
MEMORY_SERVICE_ENCRYPTION_KIND=dek
MEMORY_SERVICE_ENCRYPTION_DEK_KEY=<base64-encoded-32-byte-key>

Generate a key with:

openssl rand -base64 32

Key rotation: Supply a comma-separated list of keys — the first key encrypts new data; additional keys are tried in order for decryption only. Remove old keys once all stored data has been re-encrypted with the primary key.

MEMORY_SERVICE_ENCRYPTION_DEK_KEY=new-primary-key,old-legacy-key

Encryption: Vault (HashiCorp Vault Transit)

The vault provider uses HashiCorp Vault Transit to wrap data-encryption keys (DEKs). DEKs are loaded from the encryption_deks database table at startup — Vault is called only once at load time (never per request). A random DEK is generated on first start, wrapped via Vault Transit, and stored in the table. Loss of Vault access prevents startup but does not affect already-running instances.

Flag / Env VarValuesDefaultDescription
--encryption-vault-transit-keyMEMORY_SERVICE_ENCRYPTION_VAULT_TRANSIT_KEYstring(required)Vault Transit key name

Standard HashiCorp Vault environment variables are used for authentication:

Env VarDescription
VAULT_ADDRVault server URL (e.g. https://vault.example.com)
VAULT_TOKENVault token (or use any other Vault auth method)
# Enable Vault encryption
--encryption-kind=vault
--encryption-vault-transit-key=memory-service
# Enable Vault encryption
MEMORY_SERVICE_ENCRYPTION_KIND=vault
MEMORY_SERVICE_ENCRYPTION_VAULT_TRANSIT_KEY=memory-service

# HashiCorp Vault connection (standard env vars)

VAULT_ADDR=https://vault.example.com
VAULT_TOKEN=<token>

Encryption: KMS (AWS KMS)

The kms provider uses AWS KMS to wrap data-encryption keys (DEKs). DEKs are loaded from the encryption_deks database table at startup — KMS is called only once at load time (never per request). A random DEK is generated on first start, wrapped via kms:Encrypt, and stored in the table. Loss of KMS access prevents startup but does not affect already-running instances.

Flag / Env VarValuesDefaultDescription
--encryption-kms-key-idMEMORY_SERVICE_ENCRYPTION_KMS_KEY_IDstring(required)AWS KMS key ID or ARN

Standard AWS SDK environment variables are used for authentication:

Env VarDescription
AWS_REGIONAWS region (e.g. us-east-1)
AWS_ACCESS_KEY_IDAWS access key ID
AWS_SECRET_ACCESS_KEYAWS secret access key

Any other credential source supported by the AWS SDK (IAM roles, AWS_PROFILE, instance metadata, etc.) is also honoured.

# Enable KMS encryption
--encryption-kind=kms
--encryption-kms-key-id=arn:aws:kms:us-east-1:123456789012:key/mrk-...
# Enable KMS encryption
MEMORY_SERVICE_ENCRYPTION_KIND=kms
MEMORY_SERVICE_ENCRYPTION_KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/mrk-...

# AWS authentication (standard env vars)

AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...

Automatic attachment encryption

When a non-plain provider is the primary provider, file attachments are encrypted automatically — no extra configuration is needed. When plain is explicitly allowed as the primary provider, attachments are stored as-is with no encryption overhead.

S3 incompatibility: Attachment encryption is incompatible with S3 direct download. Direct download is disabled by default; do not enable it when using encryption with S3.

Key rotation

To rotate between providers, list the new provider first and the old provider as a fallback in --encryption-kind. New data is always encrypted by the first (primary) provider; existing current-format ciphertext is decrypted by trying each provider in order.

Example: migrate from dek to vault:

MEMORY_SERVICE_ENCRYPTION_KIND=vault,dek

New data is encrypted with Vault; existing dek-encrypted data is still readable. Remove dek from the list once all data has been re-encrypted.

Vector Store Configuration

For semantic search capabilities, configure a vector store backend. The vector store holds embeddings alongside the metadata needed to map them back to context entries.

Note: A vector store requires an embedding provider. When a vector store is enabled, configure --embedding-kindMEMORY_SERVICE_EMBEDDING_KIND to a value other than none. See Embedding Configuration.

Flag / Env VarValuesDefaultDescription
--vector-kindMEMORY_SERVICE_VECTOR_KINDnone, pgvector, qdrant, sqlite, infinispannoneVector store backend
MEMORY_SERVICE_VECTOR_MIGRATE_AT_STARTtrue, falsetrueRun vector store migrations at startup
MEMORY_SERVICE_SEARCH_SEMANTIC_ENABLEDtrue, falsetrueEnable semantic (vector) search
MEMORY_SERVICE_SEARCH_FULLTEXT_ENABLEDtrue, falsetrueEnable full-text search

Vector Store: pgvector Configuration

pgvector integrates directly with PostgreSQL to add vector search capabilities alongside your existing data. It is the natural choice when the datastore is already PostgreSQL.

# pgvector (auto-selected when datastore is postgres)
--vector-kind=pgvector
# pgvector (auto-selected when datastore is postgres)
MEMORY_SERVICE_VECTOR_KIND=pgvector

Vector Store: Qdrant Configuration

Qdrant is a dedicated vector database for semantic search. When using Qdrant, the primary datastore (PostgreSQL or MongoDB) still stores all conversation data — Qdrant only stores embeddings and metadata.

Flag / Env VarDefaultDescription
--vector-qdrant-hostMEMORY_SERVICE_VECTOR_QDRANT_HOSTlocalhostQdrant server hostname or host:port
MEMORY_SERVICE_VECTOR_QDRANT_PORT6334Qdrant gRPC port
MEMORY_SERVICE_VECTOR_QDRANT_COLLECTION_PREFIXmemory-servicePrefix for derived collection name
MEMORY_SERVICE_VECTOR_QDRANT_COLLECTION_NAME(none)Optional explicit collection name override
MEMORY_SERVICE_VECTOR_QDRANT_API_KEY(none)API key for authentication
MEMORY_SERVICE_VECTOR_QDRANT_USE_TLSfalseEnable TLS for gRPC connection
MEMORY_SERVICE_VECTOR_QDRANT_STARTUP_TIMEOUTPT30SStartup migration timeout
# Qdrant vector store
--vector-kind=qdrant
--vector-qdrant-host=localhost:6334
# Qdrant vector store
MEMORY_SERVICE_VECTOR_KIND=qdrant
MEMORY_SERVICE_VECTOR_QDRANT_HOST=localhost:6334

By default, memory-service derives the collection name as memory-service_<model>-<dimensions>, for example memory-service_openai-text-embedding-3-small-1536. Set MEMORY_SERVICE_VECTOR_QDRANT_COLLECTION_NAME to force a specific collection.

Vector Store: Infinispan Configuration

The infinispan vector backend uses the Infinispan REST API v3 for vector search. It is separate from the RESP-based cache and event-bus integration, so vector search uses its own URL and optional auth settings even when you already use Infinispan elsewhere in the service.

  • Requires Infinispan 16.1 or later for vector search support.
  • The default URL is http://localhost:11222.
  • If MEMORY_SERVICE_VECTOR_INFINISPAN_CACHE_NAME is unset, memory-service derives a cache name from the embedding model and dimensions.
  • MEMORY_SERVICE_VECTOR_INFINISPAN_URL, MEMORY_SERVICE_VECTOR_INFINISPAN_USERNAME, and MEMORY_SERVICE_VECTOR_INFINISPAN_PASSWORD also accept the shared MEMORY_SERVICE_INFINISPAN_* connection values as fallbacks.
Flag / Env VarDefaultDescription
--vector-infinispan-urlMEMORY_SERVICE_VECTOR_INFINISPAN_URLhttp://localhost:11222Infinispan REST endpoint URL
--vector-infinispan-cache-nameMEMORY_SERVICE_VECTOR_INFINISPAN_CACHE_NAME(auto)Optional explicit cache name override
--vector-infinispan-usernameMEMORY_SERVICE_VECTOR_INFINISPAN_USERNAME(none)Authentication username
--vector-infinispan-passwordMEMORY_SERVICE_VECTOR_INFINISPAN_PASSWORD(none)Authentication password
--vector-infinispan-auth-typeMEMORY_SERVICE_VECTOR_INFINISPAN_AUTH_TYPEdigestAuth mechanism: basic or digest

Use an https:// value for MEMORY_SERVICE_VECTOR_INFINISPAN_URL when TLS is required.

# Infinispan vector store
--vector-kind=infinispan
--vector-infinispan-url=http://localhost:11222
--vector-infinispan-username=admin
--vector-infinispan-password=password
# Infinispan vector store
MEMORY_SERVICE_VECTOR_KIND=infinispan
MEMORY_SERVICE_VECTOR_INFINISPAN_URL=http://localhost:11222
MEMORY_SERVICE_VECTOR_INFINISPAN_USERNAME=admin
MEMORY_SERVICE_VECTOR_INFINISPAN_PASSWORD=password

Vector Store: SQLite Configuration

The sqlite vector backend stores conversation embeddings and episodic memory vectors in the same SQLite database file by using sqlite-vec scalar functions.

  • MEMORY_SERVICE_VECTOR_KIND=sqlite requires MEMORY_SERVICE_DB_KIND=sqlite.
  • This backend enables both conversation semantic search and episodic memory semantic search.
  • You still need an embedding provider such as MEMORY_SERVICE_EMBEDDING_KIND=local.
# SQLite vector store (requires --db-kind=sqlite)
--vector-kind=sqlite
--embedding-kind=local
# SQLite vector store (requires MEMORY_SERVICE_DB_KIND=sqlite)
MEMORY_SERVICE_VECTOR_KIND=sqlite
MEMORY_SERVICE_EMBEDDING_KIND=local

Embedding Configuration

The embedding provider controls how text is converted to vectors for semantic search. Embedding requires a vector store to be configured.

Flag / Env VarValuesDefaultDescription
--embedding-kindMEMORY_SERVICE_EMBEDDING_KINDnone, local, openailocalEmbedding provider selection

Embedding: Local Configuration

The default local provider uses an in-process all-MiniLM-L6-v2 ONNX model (384 dimensions). No external API calls are required.

# Use local embedding model (default)
--embedding-kind=local
# Use local embedding model (default)
MEMORY_SERVICE_EMBEDDING_KIND=local

Embedding: OpenAI Configuration

The openai provider uses the OpenAI Embeddings API for higher-quality embeddings.

Flag / Env VarDefaultDescription
--embedding-openai-api-keyMEMORY_SERVICE_EMBEDDING_OPENAI_API_KEY(required)OpenAI API key
MEMORY_SERVICE_EMBEDDING_OPENAI_MODEL_NAMEtext-embedding-3-smallOpenAI model name
MEMORY_SERVICE_EMBEDDING_OPENAI_BASE_URLhttps://api.openai.com/v1API base URL (for Azure OpenAI or proxies)
MEMORY_SERVICE_EMBEDDING_OPENAI_DIMENSIONS(model default)Optional dimension override
# Use OpenAI embeddings
--embedding-kind=openai
--embedding-openai-api-key=sk-...
# Use OpenAI embeddings
MEMORY_SERVICE_EMBEDDING_KIND=openai
MEMORY_SERVICE_EMBEDDING_OPENAI_API_KEY=sk-...

Memories Configuration

Configure namespaced Memories policy loading.

FlagValuesDefaultDescription
--episodic-policy-dirMEMORY_SERVICE_EPISODIC_POLICY_DIRpathbuilt-in policiesDirectory containing memory OPA/Rego policy files: authz.rego (data.memories.authz.decision), attributes.rego (data.memories.attributes.attributes), filter.rego (data.memories.filter)

API Key Authentication

Memory Service supports API key authentication for trusted agents. Configure API keys by client ID using environment variables:

# Format: MEMORY_SERVICE_API_KEYS_<CLIENT_ID>=key1,key2,...
MEMORY_SERVICE_API_KEYS_AGENT_A=agent-a-key-1,agent-a-key-2
MEMORY_SERVICE_API_KEYS_AGENT_B=agent-b-key-1
# Format: MEMORY_SERVICE_API_KEYS_<CLIENT_ID>=key1,key2,...
MEMORY_SERVICE_API_KEYS_AGENT_A=agent-a-key-1,agent-a-key-2
MEMORY_SERVICE_API_KEYS_AGENT_B=agent-b-key-1

Clients include the API key in requests via the X-API-Key header. The API key resolves to the client ID from the environment variable suffix.

API-key-only requests authenticate a client service principal, not a user. They can call admin or operational APIs when that client ID has the required role, but they cannot call normal user-scoped APIs such as conversation listing, conversation entry APIs, user event streams, or memory APIs because those endpoints require a user principal for ownership and policy checks.

API keys are accepted only through X-API-Key. When OIDC is configured, the Authorization: Bearer slot is reserved for OIDC JWTs; raw bearer user IDs are rejected by production builds.

Trusted Client User Identity Assertion

The X-User-ID header lets an explicitly trusted agent application use normal user-scoped APIs with a service credential. gRPC uses the equivalent x-user-id request metadata. The assertion is not a credential: the request must still authenticate with a valid API key or OIDC token.

FlagValuesDefaultDescription
--trusted-user-id-clientsMEMORY_SERVICE_TRUSTED_USER_ID_CLIENTSexact client ID CSV(none)Clients trusted to assert an effective user on normal user APIs; empty disables the feature

API key client IDs come from the lower-cased environment-variable suffix, with underscores preserved. For example, MEMORY_SERVICE_API_KEYS_COGNITION_PROCESSOR resolves to client ID cognition_processor:

# Configure the service credential
# MEMORY_SERVICE_API_KEYS_<CLIENT_ID> has no equivalent CLI flag.
MEMORY_SERVICE_API_KEYS_COGNITION_PROCESSOR=replace-with-a-secret

# Trust the resolved client ID to assert users

--trusted-user-id-clients=cognition_processor
# Configure the service credential
MEMORY_SERVICE_API_KEYS_COGNITION_PROCESSOR=replace-with-a-secret

# Trust the resolved client ID to assert users

MEMORY_SERVICE_TRUSTED_USER_ID_CLIENTS=cognition_processor

The REST request headers are:

X-API-Key: replace-with-a-secret
X-User-ID: alice

The equivalent gRPC request metadata is:

x-api-key: replace-with-a-secret
x-user-id: alice

OIDC deployments trust the validated signed azp claim, falling back to signed client_id. The trusted-user list is separate from the OIDC caller and audience boundaries:

--oidc-issuer=https://idp.example.com/realms/memory-service
--oidc-allowed-clients=cognition-processor
--oidc-allowed-audiences=memory-service
--trusted-user-id-clients=cognition-processor
MEMORY_SERVICE_OIDC_ISSUER=https://idp.example.com/realms/memory-service
MEMORY_SERVICE_OIDC_ALLOWED_CLIENTS=cognition-processor
MEMORY_SERVICE_OIDC_ALLOWED_AUDIENCES=memory-service
MEMORY_SERVICE_TRUSTED_USER_ID_CLIENTS=cognition-processor

One deployment may trust both API-key and OIDC client IDs:

MEMORY_SERVICE_TRUSTED_USER_ID_CLIENTS=cognition_processor,cognition-processor

The behavior is deliberately narrow:

  • Matching is exact and case-sensitive; wildcards are not supported.
  • A normal user API uses the assertion only when the authenticated client is trusted.
  • An assertion from an untrusted client is ignored and the operation behaves as if the header were absent.
  • Invalid or missing credentials remain unauthenticated even when X-User-ID is present.
  • Admin and system APIs ignore the asserted user.
  • Assertion trust grants no admin, auditor, or indexer role. Configure client roles separately with settings such as MEMORY_SERVICE_ROLES_ADMIN_CLIENTS.
  • When a trusted OIDC client changes the effective user, roles derived from the original token user are dropped; client-derived roles and OIDC scope gates remain in effect.
  • GET /v1/capabilities and gRPC SystemService/GetCapabilities report auth.user_id_assertion_enabled without exposing the trusted-client list.

Do not remove existing auth_testfixtures builds or use their raw bearer and X-Client-ID behavior as a production substitute for this feature.

Authenticated agent/app clients can call GET /v1/capabilities or gRPC SystemService/GetCapabilities to retrieve a secret-free summary of the server’s configured capabilities and backend choices. These endpoints allow either a resolved client context or an authenticated admin/auditor role; bearer-authenticated user calls without a client ID are rejected.

OIDC Authentication

Memory Service supports OIDC authentication via Keycloak or any compliant provider.

FlagValuesDefaultDescription
--oidc-issuerMEMORY_SERVICE_OIDC_ISSUERURL(none)OIDC issuer URL; enables OIDC auth
--oidc-discovery-urlMEMORY_SERVICE_OIDC_DISCOVERY_URLURL(none)Internal discovery URL when the issuer URL is not directly reachable
--oidc-tls-insecure-skip-verifyMEMORY_SERVICE_OIDC_TLS_INSECURE_SKIP_VERIFYtrue, falsefalseSkip TLS certificate verification for OIDC discovery and JWKS requests; rejected outside testing mode
--oidc-allowed-clientsMEMORY_SERVICE_OIDC_ALLOWED_CLIENTSCSV(none)Optional OIDC client IDs allowed to call Memory Service; empty allows any client from the configured issuer
--oidc-allowed-audiencesMEMORY_SERVICE_OIDC_ALLOWED_AUDIENCESCSV(none)Required OIDC audiences accepted by Memory Service when OIDC is enabled
--oidc-user-id-claimMEMORY_SERVICE_OIDC_USER_ID_CLAIMJSON Pointer/subRFC 6901 claim path used as the persistent user identity; authentication fails if the claim is absent, blank, or not a string. Set to /preferred_username for Keycloak demo environments. Microsoft Entra deployments should use /oid.
--oidc-role-claimMEMORY_SERVICE_OIDC_ROLE_CLAIMSJSON Pointer(s)/realm_access/rolesRFC 6901 claim path(s) used for claim-derived roles; env value is a JSON array of pointer strings

When OIDC is enabled, startup requires successful issuer discovery and at least one allowed audience. Allowed clients are optional; when configured, both the client and audience checks must pass. Allowed clients are matched against signed azp or client_id token claims. Allowed audiences are matched against the signed aud claim.

The --oidc-user-id-claim value is the single source of truth for the persistent user identity that is stored with conversations, memberships, memories, and event routing. Use a claim your provider guarantees is stable and unique. OIDC only guarantees sub; providers that use pairwise sub values (e.g. some Entra tenants) may require a tenant-specific immutable claim such as oid. Local Keycloak demo environments should set this to /preferred_username so conversations can be shared using readable usernames. Authentication fails hard when the configured claim is absent, blank, or not a string — there is no silent fallback.

Claim-derived roles come only from configured JSON Pointer paths. The default is Keycloak-compatible /realm_access/roles; scope, top-level roles, and groups are not role sources unless explicitly configured. The scope claim remains available for resource/API scope gates.

# OIDC configuration
--oidc-issuer=http://localhost:8180/realms/memory-service
--oidc-allowed-clients=memory-service-client,frontend,developer-frontend
--oidc-allowed-audiences=memory-service
# Testing mode with self-signed issuers only:
--oidc-tls-insecure-skip-verify
# OIDC configuration
MEMORY_SERVICE_OIDC_ISSUER=http://localhost:8180/realms/memory-service
MEMORY_SERVICE_OIDC_ALLOWED_CLIENTS=memory-service-client,frontend,developer-frontend
MEMORY_SERVICE_OIDC_ALLOWED_AUDIENCES=memory-service
# Testing mode with self-signed issuers only:
MEMORY_SERVICE_OIDC_TLS_INSECURE_SKIP_VERIFY=true

Audience-only configuration is valid when the identity provider mints access tokens specifically for Memory Service:

--oidc-issuer=https://idp.example.com/realms/memory-service
--oidc-allowed-audiences=memory-service-api
MEMORY_SERVICE_OIDC_ISSUER=https://idp.example.com/realms/memory-service
MEMORY_SERVICE_OIDC_ALLOWED_AUDIENCES=memory-service-api

Accepted credential shapes:

DeploymentAccepted credentials
OIDC onlyOIDC JWTs whose client and/or audience claims match the configured boundary
OIDC plus API keysOIDC JWTs; OIDC JWTs paired with X-API-Key; and X-API-Key-only service-principal requests for admin/operational APIs
API keys without OIDCX-API-Key service principals
Production default binariesRaw bearer user IDs such as Authorization: Bearer alice are rejected, even when paired with a valid API key
Test fixture binariesRaw bearer user IDs are accepted only when built with the auth_testfixtures tag and running in testing mode

Admin Access Configuration

Memory Service provides /v1/admin/* APIs for platform administrators and auditors. Episodic memory administration uses /admin/v1/* routes and the same role assignment model. Access is controlled through role assignment, which can be configured via OIDC token roles, explicit user lists, or API key client IDs. All three mechanisms are checked — if any grants a role, the caller has that role.

Roles

RoleAccessDescription
adminRead + WriteFull administrative access across all users. Implies auditor and indexer.
auditorRead-onlyView any user’s conversations, attachments, and memories; search system-wide. Cannot modify data.
indexerIndex onlyRead full unindexed history entries system-wide and set or replace their searchable indexed text. Does not grant other Admin APIs.

Role Assignment

Roles can be assigned through three complementary mechanisms:

OIDC Role Mapping

Map OIDC token roles to internal Memory Service roles.

FlagDefaultDescription
--roles-admin-oidc-roleMEMORY_SERVICE_ROLES_ADMIN_OIDC_ROLEadminOIDC role name that maps to admin
--roles-auditor-oidc-roleMEMORY_SERVICE_ROLES_AUDITOR_OIDC_ROLEauditorOIDC role name that maps to auditor
--roles-indexer-oidc-roleMEMORY_SERVICE_ROLES_INDEXER_OIDC_ROLE(none)OIDC role name that maps to indexer
# Map OIDC "administrator" role to internal "admin" role
--roles-admin-oidc-role=administrator

# Map OIDC "manager" role to internal "auditor" role

--roles-auditor-oidc-role=manager

# Map OIDC "transcript-indexer" role to internal "indexer" role

--roles-indexer-oidc-role=transcript-indexer
# Map OIDC "administrator" role to internal "admin" role
MEMORY_SERVICE_ROLES_ADMIN_OIDC_ROLE=administrator

# Map OIDC "manager" role to internal "auditor" role

MEMORY_SERVICE_ROLES_AUDITOR_OIDC_ROLE=manager

# Map OIDC "transcript-indexer" role to internal "indexer" role

MEMORY_SERVICE_ROLES_INDEXER_OIDC_ROLE=transcript-indexer

OIDC Resource/API Scopes

OIDC resource/API scopes are optional extra gates for OIDC-bearing requests. They do not grant access by themselves; user ownership, memberships, admin/auditor/indexer roles, and API-key client roles must still pass first. API-key-only and embedded MCP identities are not blocked by these OIDC scope mappings.

Each mapping is a comma-separated list of token scope values. Empty means that permission has no OIDC scope gate. Configure the least granular key that matches your delegation model, then move down to resource or read/write keys only when you need tighter control.

Coarse keys gate broad API families:

Permission keyConfig propertyUse when
user--oidc-scopes-userMEMORY_SERVICE_OIDC_SCOPES_USEROne OIDC scope should allow all normal user API reads and writes after normal user authorization passes.
user_read--oidc-scopes-user-readMEMORY_SERVICE_OIDC_SCOPES_USER_READOne OIDC scope should allow all normal user API reads, but writes need a separate scope.
user_write--oidc-scopes-user-writeMEMORY_SERVICE_OIDC_SCOPES_USER_WRITEOne OIDC scope should allow all normal user API writes, but reads need a separate scope.
admin--oidc-scopes-adminMEMORY_SERVICE_OIDC_SCOPES_ADMINOne OIDC scope should allow all admin API reads and writes after normal admin/auditor/indexer authorization passes.
admin_read--oidc-scopes-admin-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_READOne OIDC scope should allow all admin API reads, but writes need a separate scope.
admin_write--oidc-scopes-admin-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_WRITEOne OIDC scope should allow all admin API writes, but reads need a separate scope.

Resource aggregate keys are more granular than user/admin, but still cover both read and write for a resource family:

Permission keyConfig propertyUse when
conversations--oidc-scopes-conversationsMEMORY_SERVICE_OIDC_SCOPES_CONVERSATIONSOne scope should cover user conversation, entry, fork, child, and response-cancellation reads and writes.
sharing--oidc-scopes-sharingMEMORY_SERVICE_OIDC_SCOPES_SHARINGOne scope should cover membership and ownership-transfer reads and writes.
search--oidc-scopes-searchMEMORY_SERVICE_OIDC_SCOPES_SEARCHOne scope should cover search/list-unindexed reads and indexing writes.
memories--oidc-scopes-memoriesMEMORY_SERVICE_OIDC_SCOPES_MEMORIESOne scope should cover user memory reads and writes.
attachments--oidc-scopes-attachmentsMEMORY_SERVICE_OIDC_SCOPES_ATTACHMENTSOne scope should cover user attachment reads and writes.
recordings--oidc-scopes-recordingsMEMORY_SERVICE_OIDC_SCOPES_RECORDINGSOne scope should cover gRPC response-recording reads and writes.
admin_conversations--oidc-scopes-admin-conversationsMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CONVERSATIONSOne scope should cover admin conversation and entry reads and writes.
admin_memories--oidc-scopes-admin-memoriesMEMORY_SERVICE_OIDC_SCOPES_ADMIN_MEMORIESOne scope should cover admin memory, policy, usage, and index APIs.
admin_attachments--oidc-scopes-admin-attachmentsMEMORY_SERVICE_OIDC_SCOPES_ADMIN_ATTACHMENTSOne scope should cover admin attachment reads and writes.
admin_checkpoints--oidc-scopes-admin-checkpointsMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CHECKPOINTSOne scope should cover admin checkpoint reads and writes.

Read/write-specific keys are the most granular option:

Permission keyConfig propertyUse when
system_read--oidc-scopes-system-readMEMORY_SERVICE_OIDC_SCOPES_SYSTEM_READCapabilities and authenticated system reads need their own scope. Existing public health endpoints remain public.
conversations_read--oidc-scopes-conversations-readMEMORY_SERVICE_OIDC_SCOPES_CONVERSATIONS_READConversation, entry, fork, and child reads need a separate scope.
conversations_write--oidc-scopes-conversations-writeMEMORY_SERVICE_OIDC_SCOPES_CONVERSATIONS_WRITEConversation, entry, sync, and response-cancellation writes need a separate scope.
sharing_read--oidc-scopes-sharing-readMEMORY_SERVICE_OIDC_SCOPES_SHARING_READMembership and ownership-transfer reads need a separate scope.
sharing_write--oidc-scopes-sharing-writeMEMORY_SERVICE_OIDC_SCOPES_SHARING_WRITEMembership and ownership-transfer writes need a separate scope.
search_read--oidc-scopes-search-readMEMORY_SERVICE_OIDC_SCOPES_SEARCH_READSearch and list-unindexed reads need a separate scope.
search_write--oidc-scopes-search-writeMEMORY_SERVICE_OIDC_SCOPES_SEARCH_WRITEIndexing writes need a separate scope.
memories_read--oidc-scopes-memories-readMEMORY_SERVICE_OIDC_SCOPES_MEMORIES_READUser memory reads need a separate scope.
memories_write--oidc-scopes-memories-writeMEMORY_SERVICE_OIDC_SCOPES_MEMORIES_WRITEUser memory writes need a separate scope.
attachments_read--oidc-scopes-attachments-readMEMORY_SERVICE_OIDC_SCOPES_ATTACHMENTS_READUser attachment reads need a separate scope.
attachments_write--oidc-scopes-attachments-writeMEMORY_SERVICE_OIDC_SCOPES_ATTACHMENTS_WRITEUser attachment writes need a separate scope.
events_read--oidc-scopes-events-readMEMORY_SERVICE_OIDC_SCOPES_EVENTS_READUser event streams need their own scope.
recordings_read--oidc-scopes-recordings-readMEMORY_SERVICE_OIDC_SCOPES_RECORDINGS_READgRPC response-recording reads need a separate scope.
recordings_write--oidc-scopes-recordings-writeMEMORY_SERVICE_OIDC_SCOPES_RECORDINGS_WRITEgRPC response-recording writes need a separate scope.
admin_conversations_read--oidc-scopes-admin-conversations-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CONVERSATIONS_READAdmin conversation and entry reads need a separate scope.
admin_conversations_write--oidc-scopes-admin-conversations-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CONVERSATIONS_WRITEAdmin conversation writes need a separate scope.
admin_memories_read--oidc-scopes-admin-memories-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_MEMORIES_READAdmin memory, policy, usage, and index reads need a separate scope.
admin_memories_write--oidc-scopes-admin-memories-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_MEMORIES_WRITEAdmin memory, policy, and indexing writes need a separate scope.
admin_attachments_read--oidc-scopes-admin-attachments-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_ATTACHMENTS_READAdmin attachment reads need a separate scope.
admin_attachments_write--oidc-scopes-admin-attachments-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_ATTACHMENTS_WRITEAdmin attachment writes need a separate scope.
admin_events_read--oidc-scopes-admin-events-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_EVENTS_READAdmin event streams need their own scope.
admin_checkpoints_read--oidc-scopes-admin-checkpoints-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CHECKPOINTS_READAdmin checkpoint reads need a separate scope.
admin_checkpoints_write--oidc-scopes-admin-checkpoints-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CHECKPOINTS_WRITEAdmin checkpoint writes need a separate scope.
admin_stats_read--oidc-scopes-admin-stats-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_STATS_READAdmin stats APIs need their own read scope.
admin_maintenance_write--oidc-scopes-admin-maintenance-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_MAINTENANCE_WRITEAdmin eviction and maintenance APIs need their own write scope.
# Aggregate scope accepted for conversation reads and writes.
--oidc-scopes-conversations=memory-service:conversations

# Separate read/write scopes.

--oidc-scopes-memories-read=memory-service:memories:read
--oidc-scopes-memories-write=memory-service:memories:write

# Admin API scopes still require normal admin/auditor/indexer role authorization.

--oidc-scopes-admin=memory-service:admin
# Aggregate scope accepted for conversation reads and writes.
MEMORY_SERVICE_OIDC_SCOPES_CONVERSATIONS=memory-service:conversations

# Separate read/write scopes.

MEMORY_SERVICE_OIDC_SCOPES_MEMORIES_READ=memory-service:memories:read
MEMORY_SERVICE_OIDC_SCOPES_MEMORIES_WRITE=memory-service:memories:write

# Admin API scopes still require normal admin/auditor/indexer role authorization.

MEMORY_SERVICE_OIDC_SCOPES_ADMIN=memory-service:admin

User-Based Assignment

Assign roles directly to user IDs (matched against the OIDC token principal name):

FlagDefaultDescription
--roles-admin-usersMEMORY_SERVICE_ROLES_ADMIN_USERS(empty)Comma-separated user IDs with admin access
--roles-auditor-usersMEMORY_SERVICE_ROLES_AUDITOR_USERS(empty)Comma-separated user IDs with auditor access
--roles-indexer-usersMEMORY_SERVICE_ROLES_INDEXER_USERS(empty)Comma-separated user IDs with indexer access
--roles-admin-users=alice,bob
--roles-auditor-users=charlie,dave
--roles-indexer-users=indexer-user
MEMORY_SERVICE_ROLES_ADMIN_USERS=alice,bob
MEMORY_SERVICE_ROLES_AUDITOR_USERS=charlie,dave
MEMORY_SERVICE_ROLES_INDEXER_USERS=indexer-user

Client-Based Assignment (API Key)

Assign roles to API key client IDs, allowing agents or services to call admin APIs.

FlagDefaultDescription
--roles-admin-clientsMEMORY_SERVICE_ROLES_ADMIN_CLIENTS(empty)Comma-separated API client IDs with admin access
--roles-auditor-clientsMEMORY_SERVICE_ROLES_AUDITOR_CLIENTS(empty)Comma-separated API client IDs with auditor access
--roles-indexer-clientsMEMORY_SERVICE_ROLES_INDEXER_CLIENTS(empty)Comma-separated API client IDs with indexer access
--roles-admin-clients=admin-agent
--roles-auditor-clients=monitoring-agent,audit-agent
--roles-indexer-clients=indexer-service,summarizer-agent
MEMORY_SERVICE_ROLES_ADMIN_CLIENTS=admin-agent
MEMORY_SERVICE_ROLES_AUDITOR_CLIENTS=monitoring-agent,audit-agent
MEMORY_SERVICE_ROLES_INDEXER_CLIENTS=indexer-service,summarizer-agent

Audit Logging

All admin API calls are logged. Each request can include a justification field explaining why the admin action was taken.

FlagValuesDefaultDescription
--admin-require-justificationMEMORY_SERVICE_ADMIN_REQUIRE_JUSTIFICATIONtrue, falsefalseRequire justification for all admin API calls

CORS Configuration

Env VarValuesDefaultDescription
MEMORY_SERVICE_CORS_ENABLEDtrue, falsefalseEnable CORS
MEMORY_SERVICE_CORS_ORIGINScomma-separated origins(none)Allowed CORS origins

Credentialed CORS requires exact http or https origins such as https://app.example.com; wildcard, null, path, query, and fragment values are rejected at startup. See Security Hardening for the production stance.

# Enable CORS
MEMORY_SERVICE_CORS_ENABLED=true
MEMORY_SERVICE_CORS_ORIGINS=http://localhost:3000
# Enable CORS
MEMORY_SERVICE_CORS_ENABLED=true
MEMORY_SERVICE_CORS_ORIGINS=http://localhost:3000

Developer Frontend Configuration

The developer frontend is an optional web UI for browsing conversations, memories, and system administration. It is served directly from the memory-service binary under the /developer path.

Flag / Env VarValuesDefaultDescription
--developer-frontend-enabledMEMORY_SERVICE_DEVELOPER_FRONTEND_ENABLEDtrue, falsefalseEnable the developer frontend
--developer-frontend-dirMEMORY_SERVICE_DEVELOPER_FRONTEND_DIRpath(none)Directory containing built frontend assets
--developer-frontend-client-idMEMORY_SERVICE_DEVELOPER_FRONTEND_CLIENT_IDstringdeveloper-frontendClient ID used by the frontend
--developer-frontend-auth-modeMEMORY_SERVICE_DEVELOPER_FRONTEND_AUTH_MODEoidc, api-keyoidcAuthentication mode
--developer-frontend-api-keyMEMORY_SERVICE_DEVELOPER_FRONTEND_API_KEYstring(none)Browser-visible credential used only in api-key mode
--base-urlMEMORY_SERVICE_BASE_URLURL(auto)External URL used by runtime configuration and OIDC redirects

OIDC mode requires:

  • OIDC authentication to be configured
  • Admin or auditor role for access
  • Built frontend assets in the specified directory

API-key mode requires the exposed key to also be registered to the configured frontend client with MEMORY_SERVICE_API_KEYS_<CLIENT_ID> and that client to be listed in MEMORY_SERVICE_ROLES_ADMIN_CLIENTS. It is a no-login local-development option: /developer/config.json sends the key to every browser that loads the console. Do not use API-key mode in production.

# Enable developer frontend
--developer-frontend-enabled
--developer-frontend-dir=/app/memory-service-developer
--oidc-issuer=http://localhost:8081/realms/memory-service
--oidc-allowed-clients=memory-service-client,frontend,developer-frontend
# Enable developer frontend
MEMORY_SERVICE_DEVELOPER_FRONTEND_ENABLED=true
MEMORY_SERVICE_DEVELOPER_FRONTEND_DIR=/app/memory-service-developer
MEMORY_SERVICE_OIDC_ISSUER=http://localhost:8081/realms/memory-service
MEMORY_SERVICE_OIDC_ALLOWED_CLIENTS=memory-service-client,frontend,developer-frontend

When using the official Docker image, the developer frontend is pre-built. Set MEMORY_SERVICE_DEVELOPER_FRONTEND_ENABLED=true to activate it.

Access the developer frontend at http://your-server:8080/developer/ using the configured authentication mode.

Monitoring

Memory Service exposes Prometheus metrics and provides admin stats endpoints that query Prometheus for aggregated metrics across all service replicas.

Management Endpoints

EndpointDescription
GET /healthLiveness — returns 200 {"status":"ok"} as soon as the process is up
GET /readyReadiness — returns 200 {"status":"ready"} once all initialization (migrations, store connections, network listeners) has completed; returns 503 {"status":"starting"} before that
GET /metricsPrometheus metrics

Use /ready for Kubernetes readiness probes and Docker Compose healthcheck (so dependent services wait for full startup). Use /health for liveness probes.

Prometheus Configuration

FlagValuesDefaultDescription
--prometheus-urlMEMORY_SERVICE_PROMETHEUS_URLURL(none)Prometheus server URL for admin stats queries

When --prometheus-url is not configured, admin stats endpoints return 501 Not Implemented. All other Memory Service functionality works normally.

GET /v1/admin/stats/summary is different: it is datastore-backed, does not query Prometheus, and remains available without --prometheus-url.

Available Stats Endpoints

EndpointDescription
/v1/admin/stats/request-rateHTTP request rate (requests/sec)
/v1/admin/stats/error-rate5xx error rate (percent)
/v1/admin/stats/latency-p95P95 response latency (seconds)
/v1/admin/stats/cache-hit-rateCache hit rate (percent)
/v1/admin/stats/db-pool-utilizationDB connection pool usage (percent)
/v1/admin/stats/summaryCurrent datastore-backed totals
/v1/admin/stats/store-latency-p95Store operation P95 latency by type
/v1/admin/stats/store-throughputStore operations/sec by type

Prometheus Scrape Configuration

For production, use a dedicated management port so Prometheus scrapes only the metrics endpoint without going through the main API’s middleware:

# Expose health and metrics on a dedicated port
--management-port=8085

# In containers, bind the dedicated management listener beyond loopback only when

# network policy or firewall rules restrict access.

--management-host=0.0.0.0
--management-tls=false
--management-allow-non-loopback
# Expose health and metrics on a dedicated port
MEMORY_SERVICE_MANAGEMENT_PORT=8085

# In containers, bind the dedicated management listener beyond loopback only when

# network policy or firewall rules restrict access.

MEMORY_SERVICE_MANAGEMENT_HOST=0.0.0.0
MEMORY_SERVICE_MANAGEMENT_TLS=false
MEMORY_SERVICE_MANAGEMENT_ALLOW_NON_LOOPBACK=true
# prometheus.yml — scrape the management port
scrape_configs:
  - job_name: "memory-service"
    scrape_interval: 15s
    metrics_path: /metrics
    static_configs:
      - targets: ["memory-service:8085"]

Without a management port, scrape the main port instead:

# prometheus.yml — no dedicated management port
scrape_configs:
  - job_name: "memory-service"
    scrape_interval: 15s
    metrics_path: /metrics
    static_configs:
      - targets: ["memory-service:8080"]

Example: Docker Compose

services:
  memory-service:
    image: ghcr.io/chirino/memory-service:latest
    environment:
      # Datastore selection
      MEMORY_SERVICE_DB_KIND: postgres

      # PostgreSQL connection (standard URL format)
      MEMORY_SERVICE_DB_URL: postgresql://postgres:postgres@postgres:5432/memoryservice

      # Cache with Redis (response recording manager automatically enabled)
      MEMORY_SERVICE_CACHE_KIND: redis
      MEMORY_SERVICE_REDIS_HOSTS: redis://redis:6379

      # Event bus (cross-node SSE fan-out via Redis)
      MEMORY_SERVICE_EVENTBUS_KIND: redis

      # Authentication
      MEMORY_SERVICE_OIDC_ISSUER: http://keycloak:8180/realms/memory-service
      MEMORY_SERVICE_OIDC_ALLOWED_CLIENTS: memory-service-client,frontend,developer-frontend

      # Admin stats
      MEMORY_SERVICE_PROMETHEUS_URL: http://prometheus:9090

      # Management port (health + metrics on a dedicated port)
      MEMORY_SERVICE_MANAGEMENT_PORT: 8085
    ports:
      - "8080:8080"
      - "8085:8085"
    depends_on:
      - postgres
      - redis

Next Steps