Configuration
Memory Service is configured through CLI flags or environment variables. Use the toggle below to switch between formats.
Tip: Your format preference is saved across visits. Every CLI flag has a corresponding environment variable.
Server Configuration
| Flag | Values | Default | Description |
|---|---|---|---|
--tls-cert-fileMEMORY_SERVICE_TLS_CERT_FILE | path | (none) | TLS certificate file |
--tls-key-fileMEMORY_SERVICE_TLS_KEY_FILE | path | (none) | TLS private key file |
--tls-self-signedMEMORY_SERVICE_TLS_SELF_SIGNED | true, false | false | Generate an ephemeral self-signed certificate when cert/key files are omitted |
--advertised-addressMEMORY_SERVICE_ADVERTISED_ADDRESS | host:port | auto-detected | Advertised address for client redirects |
--read-header-timeout-secondsMEMORY_SERVICE_READ_HEADER_TIMEOUT_SECONDS | integer | 5 | HTTP read header timeout in seconds |
--body-read-timeoutMEMORY_SERVICE_BODY_READ_TIMEOUT | duration | 30s | Maximum time allowed to read ordinary REST request bodies (0 disables) |
--attachment-body-read-timeoutMEMORY_SERVICE_ATTACHMENT_BODY_READ_TIMEOUT | duration | 5m | Maximum time allowed to read multipart attachment upload bodies (0 disables) |
--max-page-sizeMEMORY_SERVICE_MAX_PAGE_SIZE | positive integer | 1000 | Maximum items accepted by listing endpoints |
--temp-dirMEMORY_SERVICE_TEMP_DIR | path | system temp dir | Directory for temporary files |
--management-access-logMEMORY_SERVICE_MANAGEMENT_ACCESS_LOG | true, false | false | Enable operational logging for /health, /ready, /metrics |
MEMORY_SERVICE_LOG_LEVEL | debug, info, warn, error, fatal | info | Server 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.
| Flag | Values | Default | Description |
|---|---|---|---|
--portMEMORY_SERVICE_PORT | integer | 8080 | HTTP/gRPC server port |
--hostMEMORY_SERVICE_HOST | host/IP | 127.0.0.1 | HTTP/gRPC bind host; containers should set 0.0.0.0 intentionally |
--unix-socketMEMORY_SERVICE_UNIX_SOCKET | path | (unset) | Absolute path to a Unix socket for the HTTP/gRPC server |
--unix-socket-authMEMORY_SERVICE_UNIX_SOCKET_AUTH | credentials, local | credentials | Require credentials or trust access to the main Unix socket |
--local-user-idMEMORY_SERVICE_LOCAL_USER_ID | string | OS username | User identity used by unix-socket-auth=local |
--local-client-idMEMORY_SERVICE_LOCAL_CLIENT_ID | string | local-agent | Client identity used by unix-socket-auth=local |
--plain-textMEMORY_SERVICE_PLAIN_TEXT | true, false | true | Enable plaintext HTTP/1.1 + h2c + gRPC |
--tlsMEMORY_SERVICE_TLS | true, false | true | Enable TLS HTTP/1.1 + HTTP/2 + gRPC |
--allow-non-loopback-plaintextMEMORY_SERVICE_ALLOW_NON_LOOPBACK_PLAINTEXT | true, false | false | Explicitly allow plaintext API traffic on a non-loopback TCP bind outside testing mode |
--max-header-bytesMEMORY_SERVICE_MAX_HEADER_BYTES | bytes | 1048576 | Maximum request header bytes on the main listener |
--idle-timeoutMEMORY_SERVICE_IDLE_TIMEOUT | duration | 120s | HTTP keep-alive idle timeout on the main listener |
--trusted-proxy-cidrsMEMORY_SERVICE_TRUSTED_PROXY_CIDRS | IP/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.
| Flag | Values | Default | Description |
|---|---|---|---|
--management-portMEMORY_SERVICE_MANAGEMENT_PORT | integer | (unset) | Dedicated port for health and metrics |
--management-on-main-listenerMEMORY_SERVICE_MANAGEMENT_ON_MAIN_LISTENER | true, false | false | Explicitly acknowledge serving management routes on the main API listener outside testing mode |
--management-hostMEMORY_SERVICE_MANAGEMENT_HOST | host/IP | 127.0.0.1 | Management bind host; containers should set 0.0.0.0 intentionally |
--management-allow-non-loopbackMEMORY_SERVICE_MANAGEMENT_ALLOW_NON_LOOPBACK | true, false | false | Explicitly allow a dedicated management listener to bind beyond loopback outside testing mode |
--management-unix-socketMEMORY_SERVICE_MANAGEMENT_UNIX_SOCKET | path | (unset) | Absolute path to a Unix socket for the management server |
--management-plain-textMEMORY_SERVICE_MANAGEMENT_PLAIN_TEXT | true, false | true | Enable plaintext HTTP for management server |
--management-tlsMEMORY_SERVICE_MANAGEMENT_TLS | true, false | true | Enable TLS for management server (uses same cert/key as main listener) |
--management-max-header-bytesMEMORY_SERVICE_MANAGEMENT_MAX_HEADER_BYTES | bytes | 65536 | Maximum request header bytes on the management listener |
--management-idle-timeoutMEMORY_SERVICE_MANAGEMENT_IDLE_TIMEOUT | duration | 30s | HTTP 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.sockDB Configuration
Memory Service supports PostgreSQL, SQLite, and MongoDB as database backends. The database URL uses standard connection string formats (not JDBC).
| Flag | Values | Default | Description |
|---|---|---|---|
--db-kindMEMORY_SERVICE_DB_KIND | postgres, sqlite, mongo | postgres | Database backend |
--db-urlMEMORY_SERVICE_DB_URL | URL | (required) | Database connection URL |
--db-max-open-connsMEMORY_SERVICE_DB_MAX_OPEN_CONNS | integer | 25 | Maximum number of open database connections (see backend notes below) |
--db-max-idle-connsMEMORY_SERVICE_DB_MAX_IDLE_CONNS | integer | 5 | Idle/minimum connections (see backend notes below) |
MEMORY_SERVICE_DB_MIGRATE_AT_START | true, false | true | Run database migrations at startup |
DB: PostgreSQL Configuration
PostgreSQL is the recommended database backend for Memory Service.
--db-max-open-connsMEMORY_SERVICE_DB_MAX_OPEN_CONNSsets the maximum number of open connections.--db-max-idle-connsMEMORY_SERVICE_DB_MAX_IDLE_CONNSsets 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/memoryserviceDB: 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_URLcan be a plain file path or afile:SQLite URI.- SQLite uses a shared database handle with conservative internal pooling and write serialization. The
--db-max-open-connsand--db-max-idle-connssettings are not used for SQLite. - When
--attachments-kindMEMORY_SERVICE_ATTACHMENTS_KINDis omitted in SQLite mode, it defaults tofs. - When
MEMORY_SERVICE_ATTACHMENTS_FS_DIRis 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-attachmentsDB: MongoDB Configuration
--db-max-open-connsMEMORY_SERVICE_DB_MAX_OPEN_CONNSsets the maximum connection pool size.--db-max-idle-connsMEMORY_SERVICE_DB_MAX_IDLE_CONNSsets 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/memoryserviceCache 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.
| Flag | Values | Default | Description |
|---|---|---|---|
--cache-kindMEMORY_SERVICE_CACHE_KIND | none, local, redis, infinispan | none | Cache backend for context entries and response recording |
MEMORY_SERVICE_CACHE_EPOCH_TTL | duration | PT10M | TTL 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 Var | Values | Default | Description |
|---|---|---|---|
MEMORY_SERVICE_RESPONSE_RESUMER_TEMP_FILE_RETENTION | duration | PT30M | How 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.
| Flag | Values | Default | Description |
|---|---|---|---|
--redis-hostsMEMORY_SERVICE_REDIS_HOSTS | Redis URL | redis://localhost:6379 | Redis 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:6379Cache: 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 Var | Values | Default | Description |
|---|---|---|---|
--cache-local-max-bytesMEMORY_SERVICE_CACHE_LOCAL_MAX_BYTES | memory size | 64M | Total process-local memory budget for cached entries |
--cache-local-num-countersMEMORY_SERVICE_CACHE_LOCAL_NUM_COUNTERS | integer | 100000 | Higher values improve eviction/admission decisions for larger working sets, but use more memory |
--cache-local-buffer-itemsMEMORY_SERVICE_CACHE_LOCAL_BUFFER_ITEMS | integer | 64 | Internal 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=64MCache: Infinispan Configuration
Infinispan provides a distributed cache for context entries and response recordings.
| Flag | Values | Default | Description |
|---|---|---|---|
--infinispan-hostMEMORY_SERVICE_INFINISPAN_HOST | host:port | localhost:11222 | Infinispan server host |
--infinispan-usernameMEMORY_SERVICE_INFINISPAN_USERNAME | string | (none) | Infinispan username |
--infinispan-passwordMEMORY_SERVICE_INFINISPAN_PASSWORD | string | (none) | Infinispan password |
MEMORY_SERVICE_CACHE_INFINISPAN_STARTUP_TIMEOUT | duration | PT30S | Startup 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=passwordEvent 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 Var | Values | Default | Description |
|---|---|---|---|
--eventbus-kindMEMORY_SERVICE_EVENTBUS_KIND | local, redis, infinispan, postgres | local | Event bus backend |
--eventbus-outbound-bufferMEMORY_SERVICE_EVENTBUS_OUTBOUND_BUFFER | integer | 200 | Outbound channel capacity for cross-node publish pipeline |
--eventbus-batch-sizeMEMORY_SERVICE_EVENTBUS_BATCH_SIZE | integer | 100 | Max events per cross-node publish batch |
SSE Stream Settings
| Flag / Env Var | Values | Default | Description |
|---|---|---|---|
--sse-keepalive-intervalMEMORY_SERVICE_SSE_KEEPALIVE_INTERVAL | duration | 30s | Interval between SSE keepalive comments |
--sse-max-connections-per-userMEMORY_SERVICE_SSE_MAX_CONNECTIONS_PER_USER | integer | 5 | Max concurrent SSE connections per user; older local streams are evicted when exceeded |
--sse-subscriber-buffer-sizeMEMORY_SERVICE_SSE_SUBSCRIBER_BUFFER_SIZE | integer | 64 | Per-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 Var | Values | Default | Description |
|---|---|---|---|
--outbox-enabledMEMORY_SERVICE_OUTBOX_ENABLED | true, false | false | Enable durable event outbox writes and replay-capable event streaming |
--outbox-replay-batch-sizeMEMORY_SERVICE_OUTBOX_REPLAY_BATCH_SIZE | integer | 1000 | Max 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=logicalmax_replication_slots >= 1max_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=localEvent 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:6379Event 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=passwordEvent 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=postgresAttachment 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 Var | Values | Default | Description |
|---|---|---|---|
--attachments-kindMEMORY_SERVICE_ATTACHMENTS_KIND | db, fs, s3 | db | Storage backend for uploaded files |
MEMORY_SERVICE_ATTACHMENTS_FS_DIR | path | (auto) | Root directory for the fs attachment backend. In SQLite mode this defaults to <db-path>.attachments. |
MEMORY_SERVICE_ATTACHMENTS_MAX_SIZE | memory size | 10M | Maximum file size per upload (e.g., 10M, 512K, 1G) |
MEMORY_SERVICE_ATTACHMENTS_DEFAULT_EXPIRES_IN | duration | PT1H | Default TTL for unlinked attachments |
MEMORY_SERVICE_ATTACHMENTS_MAX_EXPIRES_IN | duration | PT24H | Maximum allowed TTL clients can request |
MEMORY_SERVICE_ATTACHMENTS_CLEANUP_INTERVAL | duration | PT5M | How often the cleanup job runs |
MEMORY_SERVICE_ATTACHMENTS_DOWNLOAD_URL_EXPIRES_IN | duration | PT5M | Signed 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=dbAttachment Storage: Filesystem Configuration
The fs backend stores attachment bytes on the local filesystem while keeping metadata in the primary datastore.
- SQLite uses
fsby default. - If
MEMORY_SERVICE_ATTACHMENTS_FS_DIRis unset in SQLite mode, memory-service derives the directory from the SQLite DB path and creates it automatically on startup. fsalso 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/attachmentsAttachment Storage: S3 Configuration
S3 storage offloads attachments to any S3-compatible object store. Use this for large files or high-throughput workloads.
| Flag / Env Var | Values | Default | Description |
|---|---|---|---|
--attachments-s3-bucketMEMORY_SERVICE_ATTACHMENTS_S3_BUCKET | string | memory-service-attachments | S3 bucket name |
--attachments-s3-use-path-styleMEMORY_SERVICE_ATTACHMENTS_S3_USE_PATH_STYLE | true, false | false | Use path-style S3 addressing (required for LocalStack/MinIO) |
MEMORY_SERVICE_ATTACHMENTS_S3_PREFIX | string | (empty) | Optional key prefix for all objects |
MEMORY_SERVICE_ATTACHMENTS_S3_DIRECT_DOWNLOAD | true, false | false | Redirect clients directly to S3 via presigned URLs |
MEMORY_SERVICE_ATTACHMENTS_S3_EXTERNAL_ENDPOINT | URL | (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-attachmentsDirect 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:
- Proxy mode (default) — downloads stream through memory-service; no client-reachable S3 endpoint needed.
- Direct download with external endpoint — set
MEMORY_SERVICE_ATTACHMENTS_S3_DIRECT_DOWNLOAD=trueandMEMORY_SERVICE_ATTACHMENTS_S3_EXTERNAL_ENDPOINTto 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 Var | Values | Default | Description |
|---|---|---|---|
--encryption-kindMEMORY_SERVICE_ENCRYPTION_KIND | comma-separated IDs | plain | Ordered list of providers (plain, dek, vault, kms); first provider encrypts new data |
--encryption-db-disabledMEMORY_SERVICE_ENCRYPTION_DB_DISABLED | true, false | false | Disable at-rest encryption for the database even when an encryption provider is active |
--encryption-attachments-disabledMEMORY_SERVICE_ENCRYPTION_ATTACHMENTS_DISABLED | true, false | false | Disable at-rest encryption for the attachment store even when an encryption provider is active |
--encryption-allow-plainMEMORY_SERVICE_ENCRYPTION_ALLOW_PLAIN | true, false | false | Explicitly 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 Var | Values | Default | Description |
|---|---|---|---|
--encryption-dek-keyMEMORY_SERVICE_ENCRYPTION_DEK_KEY | hex 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 Var | Values | Default | Description |
|---|---|---|---|
--encryption-vault-transit-keyMEMORY_SERVICE_ENCRYPTION_VAULT_TRANSIT_KEY | string | (required) | Vault Transit key name |
Standard HashiCorp Vault environment variables are used for authentication:
| Env Var | Description |
|---|---|
VAULT_ADDR | Vault server URL (e.g. https://vault.example.com) |
VAULT_TOKEN | Vault 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 Var | Values | Default | Description |
|---|---|---|---|
--encryption-kms-key-idMEMORY_SERVICE_ENCRYPTION_KMS_KEY_ID | string | (required) | AWS KMS key ID or ARN |
Standard AWS SDK environment variables are used for authentication:
| Env Var | Description |
|---|---|
AWS_REGION | AWS region (e.g. us-east-1) |
AWS_ACCESS_KEY_ID | AWS access key ID |
AWS_SECRET_ACCESS_KEY | AWS 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_KINDto a value other thannone. See Embedding Configuration.
| Flag / Env Var | Values | Default | Description |
|---|---|---|---|
--vector-kindMEMORY_SERVICE_VECTOR_KIND | none, pgvector, qdrant, sqlite, infinispan | none | Vector store backend |
MEMORY_SERVICE_VECTOR_MIGRATE_AT_START | true, false | true | Run vector store migrations at startup |
MEMORY_SERVICE_SEARCH_SEMANTIC_ENABLED | true, false | true | Enable semantic (vector) search |
MEMORY_SERVICE_SEARCH_FULLTEXT_ENABLED | true, false | true | Enable 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=pgvectorVector 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 Var | Default | Description |
|---|---|---|
--vector-qdrant-hostMEMORY_SERVICE_VECTOR_QDRANT_HOST | localhost | Qdrant server hostname or host:port |
MEMORY_SERVICE_VECTOR_QDRANT_PORT | 6334 | Qdrant gRPC port |
MEMORY_SERVICE_VECTOR_QDRANT_COLLECTION_PREFIX | memory-service | Prefix 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_TLS | false | Enable TLS for gRPC connection |
MEMORY_SERVICE_VECTOR_QDRANT_STARTUP_TIMEOUT | PT30S | Startup 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:6334By 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.1or later for vector search support. - The default URL is
http://localhost:11222. - If
MEMORY_SERVICE_VECTOR_INFINISPAN_CACHE_NAMEis unset, memory-service derives a cache name from the embedding model and dimensions. MEMORY_SERVICE_VECTOR_INFINISPAN_URL,MEMORY_SERVICE_VECTOR_INFINISPAN_USERNAME, andMEMORY_SERVICE_VECTOR_INFINISPAN_PASSWORDalso accept the sharedMEMORY_SERVICE_INFINISPAN_*connection values as fallbacks.
| Flag / Env Var | Default | Description |
|---|---|---|
--vector-infinispan-urlMEMORY_SERVICE_VECTOR_INFINISPAN_URL | http://localhost:11222 | Infinispan 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_TYPE | digest | Auth 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=passwordVector 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=sqliterequiresMEMORY_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=localEmbedding Configuration
The embedding provider controls how text is converted to vectors for semantic search. Embedding requires a vector store to be configured.
| Flag / Env Var | Values | Default | Description |
|---|---|---|---|
--embedding-kindMEMORY_SERVICE_EMBEDDING_KIND | none, local, openai | local | Embedding 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=localEmbedding: OpenAI Configuration
The openai provider uses the OpenAI Embeddings API for higher-quality embeddings.
| Flag / Env Var | Default | Description |
|---|---|---|
--embedding-openai-api-keyMEMORY_SERVICE_EMBEDDING_OPENAI_API_KEY | (required) | OpenAI API key |
MEMORY_SERVICE_EMBEDDING_OPENAI_MODEL_NAME | text-embedding-3-small | OpenAI model name |
MEMORY_SERVICE_EMBEDDING_OPENAI_BASE_URL | https://api.openai.com/v1 | API 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.
| Flag | Values | Default | Description |
|---|---|---|---|
--episodic-policy-dirMEMORY_SERVICE_EPISODIC_POLICY_DIR | path | built-in policies | Directory 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-1Clients 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.
| Flag | Values | Default | Description |
|---|---|---|---|
--trusted-user-id-clientsMEMORY_SERVICE_TRUSTED_USER_ID_CLIENTS | exact 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_processorThe 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-processorMEMORY_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-processorOne 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-IDis 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/capabilitiesand gRPCSystemService/GetCapabilitiesreportauth.user_id_assertion_enabledwithout 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.
| Flag | Values | Default | Description |
|---|---|---|---|
--oidc-issuerMEMORY_SERVICE_OIDC_ISSUER | URL | (none) | OIDC issuer URL; enables OIDC auth |
--oidc-discovery-urlMEMORY_SERVICE_OIDC_DISCOVERY_URL | URL | (none) | Internal discovery URL when the issuer URL is not directly reachable |
--oidc-tls-insecure-skip-verifyMEMORY_SERVICE_OIDC_TLS_INSECURE_SKIP_VERIFY | true, false | false | Skip TLS certificate verification for OIDC discovery and JWKS requests; rejected outside testing mode |
--oidc-allowed-clientsMEMORY_SERVICE_OIDC_ALLOWED_CLIENTS | CSV | (none) | Optional OIDC client IDs allowed to call Memory Service; empty allows any client from the configured issuer |
--oidc-allowed-audiencesMEMORY_SERVICE_OIDC_ALLOWED_AUDIENCES | CSV | (none) | Required OIDC audiences accepted by Memory Service when OIDC is enabled |
--oidc-user-id-claimMEMORY_SERVICE_OIDC_USER_ID_CLAIM | JSON Pointer | /sub | RFC 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_CLAIMS | JSON Pointer(s) | /realm_access/roles | RFC 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=trueAudience-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-apiMEMORY_SERVICE_OIDC_ISSUER=https://idp.example.com/realms/memory-service
MEMORY_SERVICE_OIDC_ALLOWED_AUDIENCES=memory-service-apiAccepted credential shapes:
| Deployment | Accepted credentials |
|---|---|
| OIDC only | OIDC JWTs whose client and/or audience claims match the configured boundary |
| OIDC plus API keys | OIDC JWTs; OIDC JWTs paired with X-API-Key; and X-API-Key-only service-principal requests for admin/operational APIs |
| API keys without OIDC | X-API-Key service principals |
| Production default binaries | Raw bearer user IDs such as Authorization: Bearer alice are rejected, even when paired with a valid API key |
| Test fixture binaries | Raw 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
| Role | Access | Description |
|---|---|---|
admin | Read + Write | Full administrative access across all users. Implies auditor and indexer. |
auditor | Read-only | View any user’s conversations, attachments, and memories; search system-wide. Cannot modify data. |
indexer | Index only | Read 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.
| Flag | Default | Description |
|---|---|---|
--roles-admin-oidc-roleMEMORY_SERVICE_ROLES_ADMIN_OIDC_ROLE | admin | OIDC role name that maps to admin |
--roles-auditor-oidc-roleMEMORY_SERVICE_ROLES_AUDITOR_OIDC_ROLE | auditor | OIDC 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-indexerOIDC 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 key | Config property | Use when |
|---|---|---|
user | --oidc-scopes-userMEMORY_SERVICE_OIDC_SCOPES_USER | One 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_READ | One 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_WRITE | One OIDC scope should allow all normal user API writes, but reads need a separate scope. |
admin | --oidc-scopes-adminMEMORY_SERVICE_OIDC_SCOPES_ADMIN | One 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_READ | One OIDC scope should allow all admin API reads, but writes need a separate scope. |
admin_write | --oidc-scopes-admin-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_WRITE | One 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 key | Config property | Use when |
|---|---|---|
conversations | --oidc-scopes-conversationsMEMORY_SERVICE_OIDC_SCOPES_CONVERSATIONS | One scope should cover user conversation, entry, fork, child, and response-cancellation reads and writes. |
sharing | --oidc-scopes-sharingMEMORY_SERVICE_OIDC_SCOPES_SHARING | One scope should cover membership and ownership-transfer reads and writes. |
search | --oidc-scopes-searchMEMORY_SERVICE_OIDC_SCOPES_SEARCH | One scope should cover search/list-unindexed reads and indexing writes. |
memories | --oidc-scopes-memoriesMEMORY_SERVICE_OIDC_SCOPES_MEMORIES | One scope should cover user memory reads and writes. |
attachments | --oidc-scopes-attachmentsMEMORY_SERVICE_OIDC_SCOPES_ATTACHMENTS | One scope should cover user attachment reads and writes. |
recordings | --oidc-scopes-recordingsMEMORY_SERVICE_OIDC_SCOPES_RECORDINGS | One scope should cover gRPC response-recording reads and writes. |
admin_conversations | --oidc-scopes-admin-conversationsMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CONVERSATIONS | One scope should cover admin conversation and entry reads and writes. |
admin_memories | --oidc-scopes-admin-memoriesMEMORY_SERVICE_OIDC_SCOPES_ADMIN_MEMORIES | One scope should cover admin memory, policy, usage, and index APIs. |
admin_attachments | --oidc-scopes-admin-attachmentsMEMORY_SERVICE_OIDC_SCOPES_ADMIN_ATTACHMENTS | One scope should cover admin attachment reads and writes. |
admin_checkpoints | --oidc-scopes-admin-checkpointsMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CHECKPOINTS | One scope should cover admin checkpoint reads and writes. |
Read/write-specific keys are the most granular option:
| Permission key | Config property | Use when |
|---|---|---|
system_read | --oidc-scopes-system-readMEMORY_SERVICE_OIDC_SCOPES_SYSTEM_READ | Capabilities and authenticated system reads need their own scope. Existing public health endpoints remain public. |
conversations_read | --oidc-scopes-conversations-readMEMORY_SERVICE_OIDC_SCOPES_CONVERSATIONS_READ | Conversation, entry, fork, and child reads need a separate scope. |
conversations_write | --oidc-scopes-conversations-writeMEMORY_SERVICE_OIDC_SCOPES_CONVERSATIONS_WRITE | Conversation, entry, sync, and response-cancellation writes need a separate scope. |
sharing_read | --oidc-scopes-sharing-readMEMORY_SERVICE_OIDC_SCOPES_SHARING_READ | Membership and ownership-transfer reads need a separate scope. |
sharing_write | --oidc-scopes-sharing-writeMEMORY_SERVICE_OIDC_SCOPES_SHARING_WRITE | Membership and ownership-transfer writes need a separate scope. |
search_read | --oidc-scopes-search-readMEMORY_SERVICE_OIDC_SCOPES_SEARCH_READ | Search and list-unindexed reads need a separate scope. |
search_write | --oidc-scopes-search-writeMEMORY_SERVICE_OIDC_SCOPES_SEARCH_WRITE | Indexing writes need a separate scope. |
memories_read | --oidc-scopes-memories-readMEMORY_SERVICE_OIDC_SCOPES_MEMORIES_READ | User memory reads need a separate scope. |
memories_write | --oidc-scopes-memories-writeMEMORY_SERVICE_OIDC_SCOPES_MEMORIES_WRITE | User memory writes need a separate scope. |
attachments_read | --oidc-scopes-attachments-readMEMORY_SERVICE_OIDC_SCOPES_ATTACHMENTS_READ | User attachment reads need a separate scope. |
attachments_write | --oidc-scopes-attachments-writeMEMORY_SERVICE_OIDC_SCOPES_ATTACHMENTS_WRITE | User attachment writes need a separate scope. |
events_read | --oidc-scopes-events-readMEMORY_SERVICE_OIDC_SCOPES_EVENTS_READ | User event streams need their own scope. |
recordings_read | --oidc-scopes-recordings-readMEMORY_SERVICE_OIDC_SCOPES_RECORDINGS_READ | gRPC response-recording reads need a separate scope. |
recordings_write | --oidc-scopes-recordings-writeMEMORY_SERVICE_OIDC_SCOPES_RECORDINGS_WRITE | gRPC response-recording writes need a separate scope. |
admin_conversations_read | --oidc-scopes-admin-conversations-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CONVERSATIONS_READ | Admin conversation and entry reads need a separate scope. |
admin_conversations_write | --oidc-scopes-admin-conversations-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CONVERSATIONS_WRITE | Admin conversation writes need a separate scope. |
admin_memories_read | --oidc-scopes-admin-memories-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_MEMORIES_READ | Admin memory, policy, usage, and index reads need a separate scope. |
admin_memories_write | --oidc-scopes-admin-memories-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_MEMORIES_WRITE | Admin memory, policy, and indexing writes need a separate scope. |
admin_attachments_read | --oidc-scopes-admin-attachments-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_ATTACHMENTS_READ | Admin attachment reads need a separate scope. |
admin_attachments_write | --oidc-scopes-admin-attachments-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_ATTACHMENTS_WRITE | Admin attachment writes need a separate scope. |
admin_events_read | --oidc-scopes-admin-events-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_EVENTS_READ | Admin event streams need their own scope. |
admin_checkpoints_read | --oidc-scopes-admin-checkpoints-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CHECKPOINTS_READ | Admin checkpoint reads need a separate scope. |
admin_checkpoints_write | --oidc-scopes-admin-checkpoints-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_CHECKPOINTS_WRITE | Admin checkpoint writes need a separate scope. |
admin_stats_read | --oidc-scopes-admin-stats-readMEMORY_SERVICE_OIDC_SCOPES_ADMIN_STATS_READ | Admin stats APIs need their own read scope. |
admin_maintenance_write | --oidc-scopes-admin-maintenance-writeMEMORY_SERVICE_OIDC_SCOPES_ADMIN_MAINTENANCE_WRITE | Admin 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:adminUser-Based Assignment
Assign roles directly to user IDs (matched against the OIDC token principal name):
| Flag | Default | Description |
|---|---|---|
--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-userMEMORY_SERVICE_ROLES_ADMIN_USERS=alice,bob
MEMORY_SERVICE_ROLES_AUDITOR_USERS=charlie,dave
MEMORY_SERVICE_ROLES_INDEXER_USERS=indexer-userClient-Based Assignment (API Key)
Assign roles to API key client IDs, allowing agents or services to call admin APIs.
| Flag | Default | Description |
|---|---|---|
--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-agentMEMORY_SERVICE_ROLES_ADMIN_CLIENTS=admin-agent
MEMORY_SERVICE_ROLES_AUDITOR_CLIENTS=monitoring-agent,audit-agent
MEMORY_SERVICE_ROLES_INDEXER_CLIENTS=indexer-service,summarizer-agentAudit Logging
All admin API calls are logged. Each request can include a justification field explaining why the admin action was taken.
| Flag | Values | Default | Description |
|---|---|---|---|
--admin-require-justificationMEMORY_SERVICE_ADMIN_REQUIRE_JUSTIFICATION | true, false | false | Require justification for all admin API calls |
CORS Configuration
| Env Var | Values | Default | Description |
|---|---|---|---|
MEMORY_SERVICE_CORS_ENABLED | true, false | false | Enable CORS |
MEMORY_SERVICE_CORS_ORIGINS | comma-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:3000Developer 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 Var | Values | Default | Description |
|---|---|---|---|
--developer-frontend-enabledMEMORY_SERVICE_DEVELOPER_FRONTEND_ENABLED | true, false | false | Enable the developer frontend |
--developer-frontend-dirMEMORY_SERVICE_DEVELOPER_FRONTEND_DIR | path | (none) | Directory containing built frontend assets |
--developer-frontend-client-idMEMORY_SERVICE_DEVELOPER_FRONTEND_CLIENT_ID | string | developer-frontend | Client ID used by the frontend |
--developer-frontend-auth-modeMEMORY_SERVICE_DEVELOPER_FRONTEND_AUTH_MODE | oidc, api-key | oidc | Authentication mode |
--developer-frontend-api-keyMEMORY_SERVICE_DEVELOPER_FRONTEND_API_KEY | string | (none) | Browser-visible credential used only in api-key mode |
--base-urlMEMORY_SERVICE_BASE_URL | URL | (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-frontendWhen 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
| Endpoint | Description |
|---|---|
GET /health | Liveness — returns 200 {"status":"ok"} as soon as the process is up |
GET /ready | Readiness — returns 200 {"status":"ready"} once all initialization (migrations, store connections, network listeners) has completed; returns 503 {"status":"starting"} before that |
GET /metrics | Prometheus 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
| Flag | Values | Default | Description |
|---|---|---|---|
--prometheus-urlMEMORY_SERVICE_PROMETHEUS_URL | URL | (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
| Endpoint | Description |
|---|---|
/v1/admin/stats/request-rate | HTTP request rate (requests/sec) |
/v1/admin/stats/error-rate | 5xx error rate (percent) |
/v1/admin/stats/latency-p95 | P95 response latency (seconds) |
/v1/admin/stats/cache-hit-rate | Cache hit rate (percent) |
/v1/admin/stats/db-pool-utilization | DB connection pool usage (percent) |
/v1/admin/stats/summary | Current datastore-backed totals |
/v1/admin/stats/store-latency-p95 | Store operation P95 latency by type |
/v1/admin/stats/store-throughput | Store 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
- Learn about Core Concepts
- Explore Deployment Options