{ config, ... }: # LibreChat — web chat UI, talking to the local ollama server (see # hosts/terra/configuration.nix) over its OpenAI-compatible /v1 route. # Only reachable over the tailnet (networking.firewall.trustedInterfaces = # [ "tailscale0" ] in services/vpn/tailscale.nix) — openFirewall stays off. { services.librechat = { enable = true; enableLocalDB = true; # spins up a local, unauthenticated-on-localhost mongodb # LibreChat's isEnabled() treats an UNSET var as false, not true — so # registration is closed unless this is explicit, despite .env.example # suggesting true is the default. Only reachable over the tailnet # (trusted interface, see module comment below), so leaving it open is # fine; flip to false once your account exists if you want it locked down. env.ALLOW_REGISTRATION = true; credentials = { CREDS_KEY = config.sops.secrets.librechat_creds_key.path; CREDS_IV = config.sops.secrets.librechat_creds_iv.path; JWT_SECRET = config.sops.secrets.librechat_jwt_secret.path; JWT_REFRESH_SECRET = config.sops.secrets.librechat_jwt_refresh_secret.path; }; settings = { version = "1.2.1"; endpoints.custom = [ { name = "Ollama"; # required field but unchecked by ollama's OpenAI-compat shim apiKey = "ollama"; baseURL = "http://127.0.0.1:11434/v1"; models = { # schema requires >=1 entry even though fetch=true overwrites it # at runtime with whatever's pulled (see loadModels in # hosts/terra/configuration.nix) — kept roughly in sync anyway # so the UI has sane names before the first fetch completes. default = [ "gemma4:12b" "qwen3.6:35b-a3b" ]; fetch = true; # pull the model list from ollama at startup }; titleConvo = true; } ]; # Persistent memory is opt-in at the CONFIG level — omitting this block # (as before) leaves the feature entirely off, no matter what a user # toggles in Settings > Personalization. `agent.provider` must match # endpoints.custom[].name above exactly ("Ollama"), which is how the # memory-extraction agent picks a backend/model. memory = { personalize = true; # still needs a per-user opt-in toggle in the UI # instructions REPLACES the default extraction prompt entirely (not # appended to it) — the 3b model (llama3.2:3b, dropped) was # defaulting to saving things like its own "I am a helpful # assistant..." boilerplate under an invented "user_conversation" # key, and even after adding this prompt, still saved "I am an AI # assistant with tool calling capabilities" as personal_info after # the user introduced THEMSELVES — a capability ceiling, not a # prompting problem. validKeys constrains it to a fixed whitelist # and instructions spells out the bar for each one. validKeys = [ "user_preferences" "personal_info" "ongoing_projects" "technical_context" ]; agent = { enabled = true; provider = "Ollama"; # same model as the chat endpoint's primary driver — when that's # the active chat model, extraction needs no second model swapped # into VRAM alongside it. model = "gemma4:12b"; instructions = '' Save memory ONLY using the keys below, and only when the user's message states something durable and genuinely useful to recall in a LATER, unrelated conversation. Small talk, greetings, and anything about what the assistant said or is capable of are NOT memories — if nothing meets the bar, save nothing. set_memory REPLACES the entire value stored at a key — it does NOT append to it. Before calling set_memory for a key, check the "Existing memory" section below. If that key already has a value, your new value MUST merge the old and new information into one complete sentence or short paragraph — calling set_memory with only the newest fact silently ERASES everything already stored under that key. Only drop prior details if the user is explicitly correcting or replacing them. - user_preferences: explicitly stated preferences (tools, formats, style). - personal_info: durable facts about the user (name, role, timezone). - ongoing_projects: projects or tasks the user is actively working on. - technical_context: durable facts about the user's setup/stack relevant to future answers (e.g. "runs NixOS", "GPU is AMD ROCm"). ''; }; }; }; }; }