env.example
Rama main · réplica de lectura del repositorio original.
# exomem — environment configuration template.
#
# Copy to `.env` and fill in: cp env.example .env
# `.env` is gitignored; this template is the ONE complete reference for every
# variable exomem reads. Not every var is needed for every mode — each block
# says which. `exomem setup` (local) and `exomem setup --remote` write the
# blocks they own for you; this file documents the rest.
#
# Local Claude Code (stdio) -> only EXOMEM_VAULT_PATH.
# Remote connector (claude.ai / iOS) -> also the "Remote / GitHub OAuth" block.
# Docker/tunnel deployment -> also the "Tunnel + Docker" block.
# ---------------------------------------------------------------------------
# Core — required in every mode
# ---------------------------------------------------------------------------
# Absolute path to your Obsidian vault ROOT — the folder that CONTAINS
# "Knowledge Base/" (not the Knowledge Base folder itself). Required for every
# mode: claude.ai connects over HTTP and passes no environment of its own, so
# the remote service resolves the vault solely from here at startup.
EXOMEM_VAULT_PATH=/path/to/your/Obsidian
# ---------------------------------------------------------------------------
# Search profile — optional
# ---------------------------------------------------------------------------
# Capability profile for `exomem doctor` when --profile is omitted.
# lean | hybrid | media | remote
# EXOMEM_PROFILE=hybrid
#
# Set to 1 for a lean, keyword/BM25-only install (no embeddings, no torch).
# Leave unset for hybrid semantic search (needs `uv sync --extra embeddings`).
# NOTE: this gates embedding WRITES + model warmup, not query-time reads. If a
# `.embeddings.sqlite` sidecar already exists on disk, find() will still use the
# vector lane for reads; to force a truly keyword-only comparison, query with
# mode="keyword" (or remove/rename the sidecar).
# EXOMEM_DISABLE_EMBEDDINGS=1
# Path to the Tesseract OCR binary if it is not on PATH (media profile only).
# EXOMEM_TESSERACT_CMD=%ProgramFiles%\Tesseract-OCR\tesseract.exe
# ---------------------------------------------------------------------------
# Activation packet — optional
# ---------------------------------------------------------------------------
# What `recall_context` returns. `maps` (the default) returns a map: an index of
# what the vault holds, which the client reads and pulls from by ref. `anchors`
# restores the earlier packet, which resolves the turn to anchors. Any other
# value logs one warning and serves `maps`. A change takes effect when the
# service restarts. See docs/deployment.md ("The activation packet mode").
# EXOMEM_ACTIVATION_PACKET=anchors
# With the `maps` packet, the turn slice (on by default) lists the leads that the turn's own
# words reached, with their evidence, beside the map. `off` restores the map alone. Any other
# value logs one warning and serves the slice. A change takes effect when the service
# restarts. See docs/deployment.md ("The turn slice").
# EXOMEM_ACTIVATION_TURN_SLICE=off
# ---------------------------------------------------------------------------
# Compute mode & device (per-machine) — optional
# ---------------------------------------------------------------------------
# The steady-state default is CPU (idle GPU VRAM ~0). GPU is an opt-in. The
# simplest control is the MODE; set it once with `exomem mode <name>`. It writes a
# machine-wide config (%PROGRAMDATA%\exomem\config.json on Windows, ~/.exomem/config.json
# on macOS/Linux) read by BOTH the server and CLI ops — machine-wide on purpose so a
# LocalSystem service and your user CLI share it. The server applies a change live within
# ~10s, no restart. EXOMEM_MODE below overrides the file for THIS process only (the
# server reads .env; CLI ops do not). EXOMEM_CONFIG_PATH overrides the file location.
# quiet - CPU everywhere, no model preload, release models when idle.
# normal - default. CPU steady-state (MPS on Apple Silicon); never auto-CUDA.
# performance - use the GPU when a capable one is present; preloaded models stay
# resident so no request pays a load (aliases: gpu, turbo).
# EXOMEM_MODE=normal
#
# Startup model preload. Normal/quiet default to lazy model load on every OS;
# performance preloads. Set 1 only when startup latency matters more than RAM.
# Preload also PINS the models against the idle reaper — otherwise the reload
# lands on whichever request arrives after the idle window (~35s inside a write).
# EXOMEM_PRELOAD_MODELS=1
#
# Offline-first weight loading (default on). Weights already in the local HF cache
# load without revalidating every file against the hub; an absent or unusable cache
# still falls back to a normal download. Set 0 to always revalidate.
# EXOMEM_MODEL_OFFLINE=0
#
# Where a served model's int8 artefact lives (about 0.55 GB for bge-m3). Default:
# `exomem-artifacts` beside the HF hub cache.
# EXOMEM_MODEL_ARTIFACT_DIR=/data/hf/exomem-artifacts
#
# Where the published artefact is downloaded from (a base URL; the asset name is
# appended). Every byte is checked against the pinned digest; without a usable
# download the host builds it locally, which peaks at about 8.7 GB of memory.
# Default: the project's `served-models` GitHub release. `off` always builds locally.
# EXOMEM_MODEL_ARTIFACT_URL=https://mirror.example.com/exomem/served-models
#
# Explicit device override (wins over the mode). cpu | gpu | auto | cuda | cuda:1 | mps
# 'gpu'/'auto' opt in with a marginal-VRAM guard; 'cuda' forces it (legacy behavior).
# EXOMEM_DEVICE= # global; legacy alias EXOMEM_TORCH_DEVICE
# EXOMEM_EMBED_DEVICE= # bge embedder + reranker (text path) only
# EXOMEM_CLIP_DEVICE= # CLIP image/text only
# EXOMEM_ASR_DEVICE= # faster-whisper ASR (cpu|cuda|auto); own path, not via the above
# EXOMEM_GPU_MIN_FREE_GB=2 # min free VRAM to accept a GPU (the marginal-VRAM guard)
#
# ASR startup preload defaults off on every OS. Product mode starts a disposable
# media child only when work arrives. Set 1 to preload inside that child.
# EXOMEM_ASR_PREWARM=1
# EXOMEM_DISABLE_ASR_PREWARM=1
#
# Idle-unload reaper: release resident models/caches after N idle minutes.
# Default on in every mode, except that preloaded MODELS are pinned (caches still
# reap). Setting this explicitly wins either way — 'on' reaps models even under
# preload, at the cost of a cold load on the next request.
# EXOMEM_RELEASE_GPU_WHEN_IDLE= # on|off
# EXOMEM_IDLE_MINUTES=15
# EXOMEM_MEDIA_IDLE_SECONDS=300 # disposable media child exits after this idle window
# EXOMEM_MEDIA_WORKER_MODE=process # process (product default) | inline (debug rollback)
#
# Model selection & footprint (advanced). The reranker is a stateless scorer, so it
# can be swapped or disabled with no re-index.
# EXOMEM_RANKING_MODEL=BAAI/bge-reranker-base # legacy alias EXOMEM_RERANKER_MODEL
# EXOMEM_DISABLE_RANKING=1 # turn the reranker OFF entirely (~0.44 GB + latency saved)
#
# "lite" recipe for a low-resource machine (keeps semantic search, drops the heavy
# extras): hybrid embeddings ON, reranker + image search OFF.
# EXOMEM_DISABLE_RANKING=1
# EXOMEM_DISABLE_CLIP=1
#
# Live write/import semantic indexing. Ordinary note writes embed immediately.
# Larger watcher batches keep lexical/freshness indexes current and defer
# semantic embeddings so imports do not load the full vector stack in every
# local client process. Run `exomem index --scope vault` after a bulk import.
# EXOMEM_WATCHER_MAX_EMBED_FILES=32
# EXOMEM_LIVE_EMBED_MAX_CHUNKS=256 # maximum chunks passed to one live encode call
# ---------------------------------------------------------------------------
# HTTP transport bind — optional (remote / service use)
# ---------------------------------------------------------------------------
# Bind address for the http transport. Default 127.0.0.1 (fronted by a tunnel).
# Set 0.0.0.0 to also serve a direct LAN / Tailscale route.
# EXOMEM_HOST=127.0.0.1
# ---------------------------------------------------------------------------
# Observability — optional (see docs/observability.md)
# ---------------------------------------------------------------------------
# Default-on and stdlib-only: every piece here soft-fails independently, so a
# bug in logging or metrics can never break a tool call. Nothing below is
# required to run exomem; the disable flags exist as an operator escape
# hatch, not because the default posture is unsafe.
# Log directory. Default: <checkout>/logs, or /data/logs in the Docker image.
# Must be a PROCESS env var (container ENV, service environment, shell) —
# logging configures before .env loads, so a value only in .env arrives too late.
# EXOMEM_LOG_DIR=/path/to/logs
# Root log level. Canonical name; FASTMCP_LOG_LEVEL is honored as a fallback
# so fastmcp's own DEBUG lines stay reachable without a code change.
# EXOMEM_LOG_LEVEL=INFO
# Per-process JSONL file rotation (exomem.log / exomem-cli.log / exomem-media.log).
# EXOMEM_LOG_MAX_MB=5
# EXOMEM_LOG_BACKUPS=5
# Size cap for logs/{queries,writes,reads,mutations}.jsonl before rotating to
# one kept `.1` generation.
# EXOMEM_JSONL_MAX_MB=64
# The call ledger: one hash-chained row per MCP tool call, in logs/ledger.jsonl.
# It is the only source that covers every call and the only one that records
# which client made it. Arguments are recorded as name + length + sha256, never
# as values, so turning it off does not protect content — it only removes the
# record you would want during an incident.
# EXOMEM_DISABLE_CALL_LEDGER=1
# Move the ledger off the log directory. Keep it host-local and outside the
# vault: a ledger that cannot write during a read-only-vault incident cannot
# explain that incident.
# EXOMEM_CALL_LEDGER_DIR=/path/to/ledger
# Live-file bound. Past ROTATE_BYTES, the oldest rows beyond KEEP_ROWS move
# byte-exact into logs/ledger-archive/; `sequence` does not reset and the hash
# chain spans the boundary. Archives are not pruned automatically.
# EXOMEM_CALL_LEDGER_ROTATE_BYTES=8000000
# EXOMEM_CALL_LEDGER_KEEP_ROWS=2000
# How often the metrics registry persists its counters to the writer-lease
# state directory so a restart doesn't reset them to zero. 0 disables the
# background snapshotter thread entirely.
# EXOMEM_METRICS_SNAPSHOT_SECONDS=60
# Turn off the structured HTTP access log middleware (uvicorn's own access
# log stays silenced either way — see docs/observability.md).
# EXOMEM_DISABLE_ACCESS_LOG=1
# Turn off the metrics registry: /metrics.json returns 404 and counters stop
# accumulating.
# EXOMEM_DISABLE_METRICS=1
# Writer-lease idle release (HA only). A NON-preferred replica voluntarily
# releases the writer lease after this many seconds without a mutation, so
# the preferred replica reclaims within about a third of the lease TTL.
# Preferred replicas never idle-release; 0 disables. When unset the default
# tracks the TTL — max(60, EXOMEM_WRITER_LEASE_TTL) — so raising the TTL
# alone never trips validation. Under EXOMEM_WRITER_LEASE_TOPOLOGY=single-host
# the unset default is 0, because no other replica can take the lease. An
# explicit value must be 0 or >= the TTL.
# EXOMEM_WRITER_LEASE_IDLE_SECONDS=60
# ---------------------------------------------------------------------------
# Remote connector / GitHub OAuth — required for claude.ai + iOS access
# ---------------------------------------------------------------------------
# `exomem setup --remote` writes this whole block. See docs/remote-quickstart.md.
# Public HTTPS origin the connector reaches. NO trailing slash, NO /mcp suffix.
# e.g. https://kb.example.com or https://you.ngrok-free.dev
EXOMEM_BASE_URL=https://your-host.example.com
# GitHub OAuth App (github.com/settings/developers -> OAuth Apps -> New OAuth App).
# The Authorization callback URL must be exactly https://<host>/auth/callback
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
# The single GitHub login allowed to authenticate to this connector.
EXOMEM_GITHUB_USERNAME=
# Immutable positive numeric ID for that login (`GET /users/<login>` -> `id`).
# This prevents a renamed/recycled login from becoming the trusted account.
EXOMEM_GITHUB_USER_ID=
# Optional. Set to github:<the value of EXOMEM_GITHUB_USER_ID> when that account
# is your own: its remote sign-ins then act as the owner, labelled remote in the
# ledgers. Unset, a remote sign-in is a separate non-owner principal.
# EXOMEM_OWNER_OAUTH_SUBJECT=
# Required stable root for opaque durable sessions. Rotating it intentionally
# invalidates every Exomem session. Generate with:
# python -c "import secrets; print(secrets.token_urlsafe(48))"
EXOMEM_JWT_SIGNING_KEY=
# Optional manual cold-standby readiness identity. This is a public, non-secret
# host label used only by /health/ready (for example: desktop-01). It must match
# [A-Za-z0-9][A-Za-z0-9._-]{0,63}; invalid values are reported as null.
# EXOMEM_INSTANCE_ID=
# HA only: all three values must be the SAME non-empty random bearer. The
# coordinator enforces it; replicas use it for leases and authoritative auth state.
# EXOMEM_LEASE_COORDINATOR_TOKEN=
# EXOMEM_WRITER_LEASE_TOKEN=
# EXOMEM_OAUTH_STORAGE_TOKEN=
# ---------------------------------------------------------------------------
# Remote extras — optional
# ---------------------------------------------------------------------------
# Bearer key for the personal REST facade (/api/<tool>). Generate:
# uv run --no-sync python scripts/set-rest-key.py
#
# Setting this ALSO unlocks a loopback-only HTTP server:
# exomem --transport http --host 127.0.0.1 --port 8765
# GitHub OAuth is skipped for it, but ONLY when all three hold — the bind host
# is literally 127.0.0.1 or ::1, EXOMEM_BASE_URL is unset, and this key is set.
# /api/* still requires this bearer key; what the skip relaxes is the MCP
# endpoints, which become reachable by any local process — the same boundary a
# local stdio server already has. Set EXOMEM_BASE_URL plus the GitHub OAuth
# block for a remote connector: the two are mutually exclusive by design, so a
# misconfigured remote deployment cannot silently degrade into this mode.
# EXOMEM_REST_API_KEY=
# Secret that enables binary upload/download token minting. Generate:
# uv run python scripts/set-upload-token.py
# EXOMEM_UPLOAD_TOKEN=
# Managed Linux/WSL service only: open a second, loopback-only listener for
# same-machine clients holding `exomem auth issue-local` tokens. Off when unset;
# takes effect when the service unit restarts. See docs/deployment.md
# ("Same-machine clients: the local listener").
# EXOMEM_LOCAL_PORT=8764
# ---------------------------------------------------------------------------
# Tunnel + Docker — compose deployment only (see compose.yaml / docs/docker.md)
# ---------------------------------------------------------------------------
# HOST path bind-mounted to /vault in the container. The compose stack sets the
# container's own EXOMEM_VAULT_PATH=/vault, so this is the host side of that mount.
# EXOMEM_VAULT_HOST_PATH=/path/to/your/Obsidian
# ngrok (docker compose --profile ngrok, or scripts/setup-ngrok.ps1):
# NGROK_DOMAIN is your free static dev domain; NGROK_AUTHTOKEN from the dashboard.
# NGROK_AUTHTOKEN=
# NGROK_DOMAIN=you.ngrok-free.dev
# Cloudflare Tunnel (docker compose --profile cloudflared):
# CLOUDFLARE_TUNNEL_TOKEN=