# Postgres (pgvector). For a host-run API using the Compose database on port 5433. # Docker Compose overrides this with db:5432 for the containerized API. # For native PostgreSQL, use its own credentials and port (usually localhost:5432). DATABASE_URL=postgres://riwaq:riwaq@localhost:5433/riwaq # --- LLM providers (an agent picks one via its `provider` field) --- # Anthropic Claude (provider: "anthropic"). ANTHROPIC_API_KEY= # Any OpenAI-compatible endpoint (provider: "openai"). # Works with OpenAI, OpenRouter, Groq, Together, local Ollama/vLLM/LM Studio, etc. OPENAI_API_KEY= OPENAI_BASE_URL=https://api.openai.com/v1 # OpenRouter: https://openrouter.ai/api/v1 # Groq: https://api.groq.com/openai/v1 # Ollama: http://localhost:11434/v1 # Deployment-wide LLM defaults. An org can override these (per-org config); an # agent can override the org. Anything left unset falls back to these. LLM_DEFAULT_PROVIDER=anthropic LLM_DEFAULT_MODEL= # empty → uses the provider's built-in default (claude-haiku-4-5-20251001 / gpt-4o-mini) # --- Embeddings (pluggable, with an offline fallback) --- # Provider: voyage | openai | local. If unset: 'voyage' when EMBEDDINGS_API_KEY is # set, otherwise 'local' (in-process, offline, no key, no extra service). # EMBEDDINGS_PROVIDER=local EMBEDDINGS_API_KEY= # For the 'openai' provider — any /v1/embeddings server (OpenAI, Ollama, LM Studio…): # EMBEDDINGS_BASE_URL=https://api.openai.com/v1 # Model override (empty → provider default: voyage-3 / text-embedding-3-small / all-MiniLM-L6-v2) # EMBEDDINGS_MODEL= # Vector dimension — MUST match the model. Locked into the DB at first migration. # local all-MiniLM-L6-v2 = 384 (default) · voyage-3 = 1024 · openai-3-small via dimensions = any EMBEDDING_DIM=384 # --- Safety / limits --- # development | test | production. In production, set LLM_ALLOWED_HOSTS below. NODE_ENV=development # SSRF egress control for tenant-supplied LLM/embedding base URLs. Private, # loopback, link-local and cloud-metadata addresses are ALWAYS blocked. Optionally # restrict to an explicit provider allowlist (comma-separated hostnames): # LLM_ALLOWED_HOSTS=api.openai.com,api.anthropic.com,openrouter.ai # Permit http:// (not just https) for tenant LLM URLs — local dev only. ALLOW_INSECURE_LLM_URLS=0 # Base64-encoded 32-byte key for encrypting tenant LLM API keys at rest # (AES-256-GCM). Optional in development and REQUIRED in production: # openssl rand -base64 32 SECRET_ENCRYPTION_KEY= # Trusted organization backends sign short-lived end-user identity tokens with # this base64-encoded 32+ byte HMAC secret. Required in production. END_USER_SIGNING_SECRET= # Provider SDK clients are bounded and expire so rotated credentials leave memory. LLM_CLIENT_CACHE_MAX=100 LLM_CLIENT_CACHE_TTL_SECONDS=300 # Max request body size in bytes (default 20 MB; must stay above the 15 MB upload cap). MAX_BODY_BYTES=20971520 # Maximum graceful HTTP drain before remaining connections are forced closed. SHUTDOWN_TIMEOUT_MS=15000 TELEGRAM_API_TIMEOUT_MS=10000 # Outbound long polling is enabled by default. Disable only on replicas that # should serve HTTP without owning Telegram polling work. TELEGRAM_POLLING_ENABLED=1 # Start a fresh short-term channel conversation after this much inactivity, or # after this many user turns. Durable per-user memory survives either rotation. CHANNEL_SESSION_IDLE_MINUTES=30 CHANNEL_SESSION_MAX_TURNS=20 # --- Redis-backed production controls --- # Required in production; enables durable jobs, shared rate limits, and caches. REDIS_URL= CACHE_TTL_SECONDS=30 # Required in production; gates organization provisioning, management, and /metrics. # Compose also passes it to the dashboard as RIWAQ_ADMIN_TOKEN. ADMIN_TOKEN= RATE_LIMIT_WINDOW_SECONDS=60 RATE_LIMIT_PER_ORG=120 RATE_LIMIT_SIGNUP_PER_IP=5 MAX_CONCURRENT_REQUESTS_PER_ORG=20 # Persistent tenant governance. Set 0 for token/spend limits only to disable that # specific ceiling. Cost values are deployment-calibrated micro-currency units. ORG_MAX_TOTAL_TOKENS=10000000 ORG_MAX_ESTIMATED_COST_MICROS=100000000 ORG_MAX_DOCUMENTS=10000 ORG_MAX_STORED_CHARS=100000000 COST_PER_MILLION_INPUT_TOKENS_MICROS=0 COST_PER_MILLION_OUTPUT_TOKENS_MICROS=0 # --- Retrieval / RAG tuning --- # Drop retrieved chunks below this cosine similarity (0..1); 0 = keep all. A small # floor stops off-topic questions surfacing irrelevant chunks as "citations". RETRIEVAL_MIN_SIMILARITY=0.2 # Cap total characters of retrieved context injected into a prompt. RETRIEVAL_CHAR_BUDGET=12000 # HNSW candidate-list size (pgvector default 40). Higher = better recall under the # per-tenant KB filter, at some latency cost. RETRIEVAL_HNSW_EF_SEARCH=100 # A question at least this cosine-similar to a topic centroid joins that cluster. TOPIC_MATCH_THRESHOLD=0.6 # Chunk sizing (characters). Defaults fit the shipped local embedder's ~256-token # cap so no chunk is silently truncated at embed time. Raise for long-context # embedders (Voyage/OpenAI) to get fewer, larger chunks. CHUNK_MAX_CHARS=1000 CHUNK_OVERLAP_CHARS=150 # Cap prior-turn history characters injected into a prompt (context-window guard). HISTORY_CHAR_BUDGET=8000 # Max long-term memory rows kept per (agent, end user). MEMORY_MAX_PER_USER=500 # --- Built-in user-scoped actions --- AGENT_TOOLS_ENABLED=1 # 1–8 model tool rounds, followed by a final answer; at most 12 tool calls per turn. AGENT_MAX_TOOL_ROUNDS=4 # --- Reviewed learning --- # An up-voted question this cosine-similar to an existing learned-answer candidate # can join a candidate only when the normalized answer also matches. LEARNED_DEDUP_SIMILARITY=0.9 # A question whose best retrieved chunk was below this similarity counts as a # knowledge gap in the learning report. LEARNING_GAP_SIMILARITY=0.25 # Votes never auto-publish. Operators must review exact sanitized text and confirm sharing. # PUT /organizations/learning is deprecated and accepts only autoPromoteThreshold: 0. # --- Reminders / scheduler --- # Run the due-reminder poller in this process (multi-node safe). Set 0 on nodes # that shouldn't fire reminders. REMINDER_SCHEDULER_ENABLED=1 REMINDER_POLL_INTERVAL_MS=30000 REMINDER_BATCH=50 REMINDER_MAX_ATTEMPTS=5 REMINDER_WEBHOOK_TIMEOUT_MS=10000 # Renewed while delivering; an expired claim is recoverable after a crash. REMINDER_CLAIM_LEASE_MS=300000 # Optional passive extraction adds one background model call per completed turn. # Explicit chat tools are the default; passive extraction skips missing times. REMINDER_AUTO_EXTRACT=0 # Guardrails for auto-extracted reminders. REMINDER_MAX_HORIZON_DAYS=1825 REMINDER_MAX_PER_USER=100 # Without an org webhook, delivery requires this agent’s connected private Telegram # chat for the canonical end user. Group-only sessions are never used. # The per-org webhook the scheduler posts fired reminders to is set via # PUT /organizations/webhook (SSRF-validated; a signing secret is returned once). # --- Database pool --- DB_POOL_MAX=10 # Server-side statement timeout (ms); 0 disables. Stops a runaway query from # pinning a pooled connection (migrations lift this for their own transaction). DB_STATEMENT_TIMEOUT_MS=30000 PORT=3000 # --- Optional Next.js management console (http://localhost:3001) --- # Create an organization first, then paste the one-time API key here. When these # are empty, the web console shows first-run setup instructions instead. RIWAQ_API_KEY= # Browser-facing API URL shown by the dashboard setup screen. RIWAQ_PUBLIC_API_URL=http://localhost:3000 # Generate with: openssl rand -hex 32 RIWAQ_DASHBOARD_TOKEN= WEB_PORT=3001