-
Notifications
You must be signed in to change notification settings - Fork 46
Expand file tree
/
Copy path.env.example
More file actions
578 lines (517 loc) · 26.6 KB
/
Copy path.env.example
File metadata and controls
578 lines (517 loc) · 26.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
# ============================================================
# Beever Atlas v2 — Environment Configuration
# ============================================================
# cp .env.example .env → fill 2 keys in §1.8 → docker compose up
#
# >>> FILL IN before first boot (§1.8) <<<
# GOOGLE_API_KEY Gemini — https://aistudio.google.com/apikey
# JINA_API_KEY Jina v4 — https://jina.ai/api-dashboard/
#
# >>> Before going to PRODUCTION <<<
# Set BEEVER_ENV=production (fails fast on every dev default below)
# Rotate §1.3 BEEVER_API_KEYS / BEEVER_ADMIN_TOKEN
# Rotate §1.7 NEO4J_AUTH / NEO4J_PASSWORD
# Match §1.4 VITE_BEEVER_* to the rotated §1.3 values
# Regen §1.5 CREDENTIAL_MASTER_KEY
# python -c "import secrets; print(secrets.token_hex(32))"
# ============================================================
# 1. REQUIRED
# ============================================================
# --- 1.1 Mode (development | production | test) -----------
BEEVER_ENV=development
# --- 1.2 URLs & CORS --------------------------------------
BEEVER_API_URL=http://localhost:8000
VITE_API_URL=http://localhost:8000
CORS_ORIGINS=http://localhost:5173,http://localhost:3000,http://localhost:3010
# --- 1.3 Backend auth (ROTATE for prod) -------------------
BEEVER_API_KEYS=dev-key-change-me
BEEVER_ADMIN_TOKEN=dev-admin-change-me
# --- 1.4 Web UI tokens (PUBLIC in Vite bundle) ------------
# Must match one §1.3 value. Changes need `npm run build`.
VITE_BEEVER_API_KEY=dev-key-change-me
VITE_BEEVER_ADMIN_TOKEN=dev-admin-change-me
# --- 1.4b Loader signed-URL tokens (file proxy auth) ------
# Short-lived HMAC tokens issued for ?loader_token=… on file-proxy URLs
# so a leaked URL expires within LOADER_TOKEN_TTL seconds. Production
# REQUIRES it (server fails fast on empty in BEEVER_ENV=production).
# Empty in dev works but the mint endpoint returns 503 on every image
# load — frontend falls back to raw ?access_token=, which spams the
# backend log. `./atlas` auto-generates a 64-char hex value here; set
# one manually for clean logs on Manual Docker / Option 2 setups:
# python -c "import secrets; print(secrets.token_hex(32))"
LOADER_TOKEN_SECRET=
LOADER_TOKEN_TTL=300
# During the migration window, accept the legacy ?access_token=<api-key>
# form as a fallback when no ?loader_token= is present OR the signed
# token verifies false. Flip to false after monitoring confirms zero
# auth.loader_fallback_raw_key log lines.
BEEVER_LOADER_RAW_KEY_FALLBACK=true
# --- 1.5 Master encryption key (REGENERATE for prod) ------
CREDENTIAL_MASTER_KEY=00000000000000000000000000000000000000000000000000000000deadbeef
# --- 1.6 Datastores ---------------------------------------
MONGODB_URI=mongodb://localhost:27017/beever_atlas
REDIS_URL=redis://localhost:6379
WEAVIATE_URL=http://localhost:8080
# Local-dev / demo default. A NON-EMPTY value is required: docker-compose.yml
# guards it with ${WEAVIATE_API_KEY:?} and Weaviate uses it as its own API key,
# so a blank value makes `make demo` fail fast before anything starts. The
# `./atlas` installer regenerates this (32 hex) for real installs — rotate it
# for anything network-exposed.
WEAVIATE_API_KEY=beever_atlas_dev_weaviate_key
# Optional Mongo + Redis auth (issue #50). Empty by default = no auth, fine
# for local dev where 127.0.0.1 host bindings already block external access.
# Set these and apply the docker-compose.auth.yml overlay for shared-host or
# pre-prod environments:
# docker compose -f docker-compose.yml -f docker-compose.auth.yml up
# MONGODB_USERNAME=
# MONGODB_PASSWORD=
# REDIS_PASSWORD=
# --- 1.7 Graph backend ("neo4j" | "none") ------
GRAPH_BACKEND=neo4j
NEO4J_URI=bolt://localhost:7687
NEO4J_AUTH=neo4j/beever_atlas_dev
NEO4J_PASSWORD=beever_atlas_dev
# --- 1.8 LLM & embeddings (>>> FILL IN <<<) ---------------
# GOOGLE_API_KEY drives the chat-side Gemini agents.
# Embedding provider is set in §3.1b — defaults to Jina, so JINA_API_KEY
# is the right key to fill below for a zero-config install.
# To use a different embedding provider (OpenAI / Cohere / Voyage / …)
# set EMBEDDING_PROVIDER and that provider's API key env var (see §3.1b).
GOOGLE_API_KEY=
JINA_API_KEY=
# ============================================================
# 2. OPTIONAL (off / blank by default)
# ============================================================
# --- 2.1 MCP server (inbound for Claude Code, Cursor) -----
# BEEVER_MCP_API_KEYS must be disjoint from §1.3 and BRIDGE_API_KEY.
# RATE_LIMIT_BACKEND: "memory" (single-worker) or "redis" (multi-worker).
BEEVER_MCP_ENABLED=false
BEEVER_MCP_API_KEYS=
BEEVER_MCP_RATE_LIMIT_BACKEND=memory
# Outbound MCP tool servers — JSON: [{"name","url","auth_token"}]
EXTERNAL_MCP_SERVERS=
# --- 2.2 Chat bridge --------------------------------------
# ADAPTER_MOCK: dev-only fixture mode. Requires the source repo (fixtures
# are NOT shipped in the Docker image). Leave false for Docker installs.
# BRIDGE_ALLOW_UNAUTH: local-dev only, exact "true" disables bridge auth.
ADAPTER_MOCK=false
BOT_PORT=3001
BACKEND_URL=http://localhost:8000
BRIDGE_URL=http://localhost:3001
# PUBLIC_BOT_URL: where the bot's inbound webhooks are reachable from the public
# internet. Inbound-webhook platforms (Slack Events API, Microsoft Teams) need
# this; outbound ones (Discord, Mattermost, and Slack Socket Mode) do not.
# • Local dev: run a tunnel (`ngrok http 3001`) and put its https URL here.
# Reserve a free static ngrok domain so it survives restarts (see
# docs/guides/slack-setup.md). Then re-pointing Slack/Teams is a one-time step.
# • Production: your real public domain, e.g. https://beever-atlas.example.com
# The Settings → connection wizard reads this to show the exact Slack Request
# URL ({PUBLIC_BOT_URL}/api/slack) and Teams messaging endpoint
# ({PUBLIC_BOT_URL}/api/teams) to paste. Leave blank if you only use outbound
# platforms / Slack Socket Mode.
PUBLIC_BOT_URL=
# Reboot-proofing for the tunnel (used by `make tunnel-up` / scripts.tunnel_up,
# and the macOS launchd agent in deploy/launchd/). Only relevant when running
# inbound platforms (Slack Events API / Teams) behind a local tunnel.
# NGROK_DOMAIN: a reserved STATIC ngrok domain (free tier includes one at
# dashboard.ngrok.com/domains). With it, the public URL never changes across
# restarts, so Slack/Teams are configured once. Without it, the tunnel URL is
# ephemeral and tunnel_up re-syncs PUBLIC_BOT_URL + the Teams endpoint each run.
NGROK_DOMAIN=
# TEAMS_APP_ID: the Teams app id from `teams app list` whose messaging endpoint
# tunnel_up should re-point to {PUBLIC_BOT_URL}/api/teams. Leave blank to skip.
TEAMS_APP_ID=
# ── Reply-feature tuning (all optional; safe defaults shown) ─────────────────
# BOT_SESSION_SECRET: HMAC key for per-thread conversation-memory session ids.
# SET THIS IN PRODUCTION — when unset, session ids are derived from a known
# default, making them predictable to anyone who knows a thread id.
BOT_SESSION_SECRET=
# BOT_TRIGGER_REDESIGN: master switch for the gated triggers (mention / 1:1 /
# go-quiet-when-humans-join). "off" reverts to legacy behavior but still skips
# self/other-bots so it can't reply-storm. Default: on.
BOT_TRIGGER_REDESIGN=on
# BOT_HUMAN_QUIET_THRESHOLD: humans in a thread at/above which the bot withdraws
# from non-mention follow-ups (anti-spam). Default: 2.
BOT_HUMAN_QUIET_THRESHOLD=2
# BOT_DM_ENABLED: answer direct messages (private 1:1 Q&A). Default: on.
BOT_DM_ENABLED=on
# BOT_RATELIMIT_PER_MIN: max questions answered per (platform, channel, user)
# per minute before a one-time notice, then silent drop. Default: 12.
BOT_RATELIMIT_PER_MIN=12
# BOT_ASK_TIMEOUT_MS: total budget for one /ask call (shared across retries).
# Default: 45000.
BOT_ASK_TIMEOUT_MS=45000
# BOT_PARTICIPANT_CACHE_TTL_MS: cache a thread's human count to avoid a
# getParticipants() call per non-mention message. 0 disables. Default: 30000.
BOT_PARTICIPANT_CACHE_TTL_MS=30000
BRIDGE_API_KEY=
BRIDGE_ALLOW_UNAUTH=
BEEVER_BRIDGE_HMAC_DUAL=false
# File-proxy host allowlist — controls which hosts the /api/files/proxy
# endpoint will fetch from. Two modes:
# FILE_PROXY_HOST_ALLOWLIST — full replacement of the platform defaults.
# FILE_PROXY_HOST_ALLOWLIST_EXTRA — additive on top of the defaults
# (use this for self-hosted Mattermost /
# Slack / SharePoint setups).
# Defaults already cover: files.slack.com, cdn.discordapp.com,
# api.telegram.org, files.mattermost.com, graph.microsoft.com,
# *.sharepoint.com, *.slack-edge.com.
FILE_PROXY_HOST_ALLOWLIST=
FILE_PROXY_HOST_ALLOWLIST_EXTRA=
# --- 2.3 Local LLM (Ollama) -------------------------------
OLLAMA_ENABLED=false
OLLAMA_API_BASE=http://localhost:11434
# --- 2.4 External web search (Tavily) ---------------------
TAVILY_API_KEY=
# Optional: Olostep web search (alternative to Tavily)
OLOSTEP_API_KEY=
# Options: tavily, olostep
WEB_SEARCH_PROVIDER=tavily
# --- 2.5 Chat history DB (blank = reuse MONGODB_URI) ------
BEEVER_CHAT_HISTORY_DB=
# --- 2.6 Multi-tenant & access control --------------------
# SINGLE_TENANT=false → enforce per-owner ACL on every channel.
BEEVER_SINGLE_TENANT=true
BEEVER_ALLOW_BRIDGE_AS_USER=false
# --- 2.7 Multilingual ingestion ---------------------------
LANGUAGE_DETECTION_ENABLED=false
DEFAULT_TARGET_LANGUAGE=en
LANGUAGE_DETECTION_CONFIDENCE_THRESHOLD=0.6
SUPPORTED_LANGUAGES=en,zh-HK,zh-TW,zh-CN,ja,ko,es,fr,de,pt,it,nl,sv,da,no,fi,pl,cs,ru,uk,tr,ar,he,hi,th,el,vi,id
# ============================================================
# 3. ADVANCED — defaults are safe, touch only when needed
# ============================================================
# --- 3.1 Agent LLM (provider-pluggable) -------------------
#
# LLM_FAST_MODEL / LLM_QUALITY_MODEL accept any LiteLLM-prefixed model id.
# Bare "gemini-2.5-flash" is treated as "gemini/gemini-2.5-flash" for
# backward compat. The 16 ADK agents default to these two tiers; you can
# override individual agents in Settings → AI Setup, or declaratively via
# `atlas apply` with an atlas.yaml.
#
# Examples (uncomment + set the matching key below):
# LLM_FAST_MODEL=gemini/gemini-2.5-flash LLM_QUALITY_MODEL=gemini/gemini-2.5-pro
# LLM_FAST_MODEL=openai/gpt-4o-mini LLM_QUALITY_MODEL=openai/gpt-4.1
# LLM_FAST_MODEL=anthropic/claude-haiku-4-5 LLM_QUALITY_MODEL=anthropic/claude-sonnet-4-6
# LLM_FAST_MODEL=mistral/mistral-small-latest LLM_QUALITY_MODEL=mistral/mistral-large-latest
# LLM_FAST_MODEL=deepseek/deepseek-chat LLM_QUALITY_MODEL=deepseek/deepseek-chat
# LLM_FAST_MODEL=groq/llama-3.3-70b-versatile LLM_QUALITY_MODEL=groq/llama-3.3-70b-versatile
# LLM_FAST_MODEL=ollama_chat/gemma3:e4b LLM_QUALITY_MODEL=ollama_chat/qwen2.5:14b (set OLLAMA_ENABLED=true)
LLM_FAST_MODEL=gemini-2.5-flash
LLM_QUALITY_MODEL=gemini-2.5-flash
WEAVIATE_HYBRID_ALPHA=0.6
USE_LLM_STRUCTURED_OUTPUT=true
#
# Cutover flag for the agent-llm-provider-pluggable change. false (default)
# routes Gemini agent calls through ADK native google.genai (honors
# response_mime_type for extraction). Set true to funnel through LiteLLM —
# only after LiteLlm wrapper learns to translate response_mime_type.
LLM_USE_LITELLM_FOR_GEMINI=false
#
# SSRF guard for the operator-only Endpoint "Test connection" / "Discover
# models" routes. Off by default so the fully-local presets (Ollama / vLLM /
# LM Studio at localhost) work out of the box. Set true on hardened
# multi-operator deployments — it refuses base_urls that resolve to
# private / link-local / cloud-metadata addresses before any probe.
# LLM_ENDPOINT_SSRF_GUARD=false
#
# Per-provider agent keys — set the one(s) matching your LLM_FAST_MODEL /
# LLM_QUALITY_MODEL prefix(es). GOOGLE_API_KEY is set in §3 above.
# OPENAI_API_KEY= # https://platform.openai.com/api-keys
# ANTHROPIC_API_KEY= # https://console.anthropic.com/settings/keys
# MISTRAL_API_KEY= # https://console.mistral.ai/api-keys
# DEEPSEEK_API_KEY= # https://platform.deepseek.com/api_keys
# GROQ_API_KEY= # https://console.groq.com/keys
# XAI_API_KEY= # https://console.x.ai/team
# MINIMAX_API_KEY= # https://platform.minimaxi.com/document/Models
# TOGETHER_API_KEY= # https://api.together.xyz/settings/api-keys
#
# Declarative config (CI / Docker / Helm) — alternatives to the per-key vars:
# BEEVER_LLM_API_KEY=AIza... # single-provider shortcut; auto-detects + applies a balanced preset
# BEEVER_ENDPOINTS='[{"name":"openai","preset":"openai","api_key":"$OPENAI_API_KEY"}]'
# BEEVER_PRESET=openai-quality # applied after BEEVER_ENDPOINTS endpoints are created
# …or commit an atlas.yaml and run `atlas apply` — see docs/runbooks/atlas-yaml.md.
#
# Per-provider RPM caps for the throttle (optional; conservative defaults shipped):
# LLM_PROVIDER_RPM_GEMINI=1000
# LLM_PROVIDER_RPM_OPENAI=500
# LLM_PROVIDER_RPM_ANTHROPIC=100
# LLM_PROVIDER_RPM_GROQ=30
# --- 3.1b Embedding (provider-pluggable via LiteLLM) ------
# Defaults match legacy Jina-v4 @ 2048d, so existing installs keep
# working with no edits. Set EMBEDDING_PROVIDER + EMBEDDING_MODEL +
# EMBEDDING_DIMENSIONS to switch providers.
#
# Supported provider prefixes (env var to set the API key):
# jina_ai JINA_AI_API_KEY (or legacy JINA_API_KEY — auto-bridged)
# openai OPENAI_API_KEY
# cohere COHERE_API_KEY
# voyage VOYAGE_API_KEY
# gemini GEMINI_API_KEY
# mistral MISTRAL_API_KEY
# ollama (local — no key)
# bedrock AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY + AWS_REGION_NAME
# vertex_ai GOOGLE_APPLICATION_CREDENTIALS
#
# WARNING: changing EMBEDDING_DIMENSIONS on a populated Weaviate is
# unsafe. The dim guard refuses to start; run `make reembed-all` first.
EMBEDDING_PROVIDER=jina_ai
EMBEDDING_MODEL=jina-embeddings-v4
EMBEDDING_DIMENSIONS=2048
EMBEDDING_RPM=500
# Optional: override the provider's default base URL (most users leave blank).
EMBEDDING_API_BASE=
# Optional: per-process API key override. Empty = LiteLLM reads the
# provider-specific env var listed above (or the encrypted DB-stored key
# if the operator supplied one through the Settings UI).
EMBEDDING_API_KEY=
# Provider-specific kwarg. Honored by jina_ai + voyage; silently dropped
# elsewhere (litellm.drop_params=true). Set "" to omit entirely.
EMBEDDING_TASK=text-matching
# Refuse to boot when the configured dim disagrees with the dim already
# stored in Weaviate. Override with caution — silent dim mismatches
# corrupt hybrid search.
EMBEDDING_DIM_GUARD=true
# In-flight LiteLLM calls during a re-embed migration job (1-16).
EMBEDDING_REEMBED_CONCURRENCY=4
# --- 3.1c LEGACY embedding (DEPRECATED — see 3.1b) --------
# These are auto-bridged into the EMBEDDING_* fields when set, with a
# one-shot deprecation warning per field. Removed in v0.3.
JINA_API_URL=https://api.jina.ai/v1/embeddings
JINA_MODEL=jina-embeddings-v4
JINA_DIMENSIONS=2048
# --- 3.2 Data pipeline & quality gates --------------------
SYNC_BATCH_SIZE=50
SYNC_MAX_MESSAGES=1000
SYNC_BATCH_TIMEOUT_SECONDS=600
QUALITY_THRESHOLD=0.5
ENTITY_THRESHOLD=0.6
MAX_FACTS_PER_MESSAGE=2
RECONCILER_INTERVAL_MINUTES=15
SEMANTIC_SEARCH_MIN_SIMILARITY=0.7
CONTRADICTION_CONFIDENCE_THRESHOLD=0.8
CONTRADICTION_FLAG_THRESHOLD=0.5
ORPHAN_GRACE_PERIOD_DAYS=7
# --- 3.3 Consolidation & clustering -----------------------
CONSOLIDATION_ENABLED=true
CLUSTER_SIMILARITY_THRESHOLD=0.6
CLUSTER_MERGE_THRESHOLD=0.85
CLUSTER_MAX_SIZE=100
CONSOLIDATION_MAX_CONCURRENT_LLM=5
CITATION_REGISTRY_ENABLED=true
# Defer cluster/channel summary generation from per-batch to memory_settled.
# Default true — runs LLM summaries ONCE at end of sync instead of N×K times
# per batch. Set false to restore legacy per-batch behavior (cost regression).
CONSOLIDATION_SUMMARIZE_ON_SETTLE=true
# --- 3.4 Gemini Batch API (off by default) ----------------
USE_BATCH_API=false
BATCH_POLL_INTERVAL_SECONDS=15
BATCH_MAX_WAIT_SECONDS=3600
BATCH_MAX_PROMPT_TOKENS=6000
BATCH_TIME_WINDOW_SECONDS=600
BATCH_MAX_OUTPUT_TOKENS=24000
# --- 3.5 LLM resilience -----------------------------------
LLM_OUTAGE_BREAKER_THRESHOLD=3
FACT_MAX_RETRIES=3
STALE_JOB_THRESHOLD_HOURS=1.0
# Issue #223 — stream the long extraction call so a >120s gemini-2.5-pro
# generate_content does not idle past the ~127-131s edge-proxy disconnect
# threshold (aiohttp ServerDisconnectedError → rows succeeded=0 total_facts=0).
# Routes the ingestion runner through ADK SSE streaming
# (RunConfig(streaming_mode=SSE)). Default ON — this IS the fix; with it off the
# long call still disconnects and yields 0 facts. Set 0 to revert. No-streaming
# fallback knobs (keep each call under the idle ceiling): lower BATCH_MAX_MESSAGES
# (default 30) to ~12-16, cap BATCH_MAX_OUTPUT_TOKENS, and/or set
# LLM_QUALITY_MODEL=gemini-2.5-flash.
INGEST_ADK_STREAMING_SSE=true
# --- 3.5b LLM rate-limit overrides (B2 token-bucket throttle) -------
# Tune to your provider's published RPM/TPM if you upgrade beyond the
# free-tier defaults baked into services/llm_throttle.py. Leave blank
# to use the conservative defaults (gemini=10/250k, openai=500/200k,
# voyage=300/1M, cohere=100/1M, mistral=60/500k, jina_ai=500/1M,
# ollama=10k/10M). Variable name is LLM_RPM_OVERRIDE_<PROVIDER> /
# LLM_TPM_OVERRIDE_<PROVIDER> with PROVIDER uppercased and underscored
# (LLM_RPM_OVERRIDE_GEMINI, LLM_RPM_OVERRIDE_JINA_AI, …).
# LLM_RPM_OVERRIDE_GEMINI=360
# LLM_TPM_OVERRIDE_GEMINI=4000000
# LLM_RPM_OVERRIDE_OPENAI=
# LLM_TPM_OVERRIDE_OPENAI=
# Cooldown applied after a 429 — the bucket fill rate is halved for
# this many seconds. Multiple 429s inside the window do not stack;
# the cooldown end is reset to ``now + cooldown`` on each report.
# LLM_BACKOFF_COOLDOWN_SECONDS=60
# --- 3.6 Coreference -------------------------------------
COREF_ENABLED=true
COREF_HISTORY_LIMIT=20
COREF_MODEL=gemini-2.5-flash
# --- 3.7 Entity dedup & thread context --------------------
ENTITY_SIMILARITY_THRESHOLD=0.85
MERGE_REJECTION_TTL_DAYS=30
CROSS_BATCH_THREAD_CONTEXT_ENABLED=true
THREAD_CONTEXT_MAX_LENGTH=200
# --- 3.8 Ingestion concurrency & rate limits --------------
# Low-memory hosts (<8 GiB, e.g. a t4g.medium running the full compose
# stack): lower INGEST_BATCH_CONCURRENCY and IMAGE_EXTRACTOR_CONCURRENCY
# to 2. Extraction memory scales with concurrent LLM batches × concurrent
# image OCR; at 4×4 the API process can exceed its container memory cap
# (ATLAS_API_MEM_LIMIT below) during multi-channel syncs.
INGEST_BATCH_CONCURRENCY=4
CONTRADICTION_CONCURRENCY=4
IMAGE_EXTRACTOR_CONCURRENCY=4
GEMINI_RPM=300
# Container memory cap for the API service (docker-compose.yml). The cap
# keeps a runaway extraction from destabilising the host — worst case the
# API container restarts instead of the host OOM-ing. Raise on hosts with
# plenty of RAM if extraction keeps hitting the limit (watch for exit 137
# in `docker ps`); memswap should stay ≥ mem so spikes can spill to swap.
ATLAS_API_MEM_LIMIT=2048m
ATLAS_API_MEMSWAP_LIMIT=3072m
# When true, persister batches Neo4j name_vector writes via one UNWIND
# Cypher per batch instead of N serial round-trips. Saves ~300ms/batch.
# Falls back to per-entity loop on Cypher error.
NEO4J_BATCH_NAME_VECTOR=true
# When true, upsert_relationship + batch_create_episodic_links use MERGE
# to auto-create stub Entity nodes for unknown endpoint names, eliminating
# silent relationship loss when batch N+1 references an entity batch N
# is still writing. Stubs are tagged ``{"stub": true, "reason": "..."}``
# with ``type='Topic'`` and ``scope='global'``. Set false to revert to
# legacy MATCH-and-skip behaviour. Caps at 50 stubs/batch (logs ERROR
# and emits ``sync_summary: metric=stub_explosion_detected`` if exceeded).
NEO4J_RELATIONSHIP_STUB_ENDPOINTS=true
# DEPRECATED — see EMBEDDING_RPM in §3.1b. Auto-bridged when set alone.
JINA_RPM=500
# --- 3.9 QA agent -----------------------------------------
QA_CONFIDENCE_THRESHOLD=0.4
QA_ADK_STREAMING_SSE=1
QA_RICH_OUTPUT=true
QA_SKILLS_ENABLED=true
QA_ONBOARDING_LENGTH_MONITOR=true
QA_HISTORY_NEGATIVE_FILTER=false
# --- 3.10 Wiki compiler -----------------------------------
BEEVER_WIKI_PARSE_HARDENING=1
BEEVER_WIKI_PARALLEL_DISPATCH=1
BEEVER_WIKI_TOKEN_BUDGET_V2=1
BEEVER_WIKI_COMPILER_V2=0
# --- 3.11 Media pipeline ----------------------------------
MEDIA_VIDEO_MAX_DURATION_MINUTES=10
MEDIA_VIDEO_MAX_SIZE_MB=100
MEDIA_AUDIO_MAX_DURATION_MINUTES=30
MEDIA_OFFICE_MAX_CHARS=10000
MEDIA_MAX_FILE_SIZE_MB=20
MEDIA_VISION_TIMEOUT_SECONDS=180
MEDIA_VISION_MODEL=gemini-2.5-flash
MEDIA_SUPPORTED_IMAGE_TYPES=png,jpg,jpeg,gif,webp
MEDIA_DIGEST_ENABLED=true
# --- 3.11b Channel-media durable byte backend --------------
# The refs / dedup / url_key metadata ALWAYS stays in Mongo; only the raw
# bytes move. 'gridfs' (default) is zero-infra; 'minio' points bytes at an
# S3-compatible store (MinIO via docker-compose --profile minio, or real
# AWS S3 for EE — leave CHANNEL_MEDIA_MINIO_ENDPOINT empty for AWS).
CHANNEL_MEDIA_BACKEND=gridfs
CHANNEL_MEDIA_MINIO_ENDPOINT=http://localhost:9000
CHANNEL_MEDIA_MINIO_ACCESS_KEY=changeme-minio-access-key
CHANNEL_MEDIA_MINIO_SECRET_KEY=
CHANNEL_MEDIA_MINIO_BUCKET=atlas-media
CHANNEL_MEDIA_MINIO_REGION=us-east-1
CHANNEL_MEDIA_MINIO_SECURE=false
# --- 3.12 PDF extraction ----------------------------------
PDF_CHUNK_PAGES=4
PDF_MAX_PAGES=100
PDF_LARGE_DOC_THRESHOLD=50
PDF_SUMMARIZE_LARGE_DOCS=false
# --- 3.13 File import (CSV/XLSX) --------------------------
FILE_IMPORT_LLM_MAPPING_ENABLED=true
FILE_IMPORT_STAGING_DIR=.omc/imports
FILE_IMPORT_STAGING_TTL_SECONDS=3600
FILE_IMPORT_MAX_ROWS=100000
# ============================================================
# 4. PIPELINE FLAGS (redesign defaults — fresh installs)
# ============================================================
# Fresh-install defaults are the redesign path: durable channel_messages
# Message Store + decoupled background ExtractionWorker + per-page wiki
# documents + Karpathy-style auto WikiMaintainer. These give you the
# fast-sync / compounding-wiki behavior described in
# docs/architecture/oss-pipeline.md.
#
# Flip any of these to false / "manual" ONLY if you're rolling back to
# the pre-redesign legacy path (e.g. an instance that started before
# this branch landed and has a populated wiki_cache you want to keep).
# UI reads chat messages from durable channel_messages collection.
READ_FROM_MESSAGE_STORE=true
# Same for file imports (kept separate so you can roll back independently).
READ_FILE_IMPORTS_FROM_CHANNEL_MESSAGES=true
# Dual-write to legacy imported_messages during the migration window.
# Flip to false 1 week after the read flags are stable in prod.
WRITE_DUAL_FILE_IMPORTS=true
# Sync writes to channel_messages then returns; worker extracts in background.
# THIS is the lever that makes a Gemini 503 storm survivable.
DECOUPLE_EXTRACTION=true
# Wiki reads from per-page documents instead of the legacy single-doc cache.
PER_PAGE_WIKI=true
# manual = user clicks "Maintain Wiki" to refresh affected pages.
# auto = maintainer auto-fires per-page LLM rewrites on new facts (default).
# Per-channel override available via the channel Settings tab.
WIKI_MAINTENANCE_MODE=auto
# Auto-trigger an initial wiki build the first time a channel crosses the
# fact-count threshold under WIKI_MAINTENANCE_MODE=auto. The maintainer
# is incremental-only and cannot produce the initial structure plan, so
# without this a brand-new channel sits at "no wiki yet" until the user
# manually clicks Generate. Disable to revert to manual-first-build flow.
WIKI_AUTO_INITIAL_BUILD=true
# Minimum extracted-fact count before auto-initial-build fires. A
# one-fact wiki is worse than no wiki — wait for enough signal to
# produce a useful structure plan.
WIKI_AUTO_INITIAL_BUILD_THRESHOLD=10
# ─── Auto-build channel overview wiki on first sync ────────────────────
# When true, the channel-overview wiki page auto-generates after the
# first extraction batch wave finishes for a channel (provided the
# channel has at least 5 extracted facts and no overview exists yet).
# This runs INDEPENDENTLY of WIKI_MAINTENANCE_MODE so the "Channel
# Wiki" tab no longer shows "No Wiki Yet" forever on a fresh sync,
# regardless of whether the operator chose auto/manual maintenance.
#
# Default: true for fresh installs (zero existing overview rows),
# false for upgrades (auto-detected by the lifespan when any
# wiki_pages row with page_type=overview exists at startup). Set
# AUTO_OVERVIEW_WIKI=true|false explicitly to override the auto-detect.
# Reuses the same code path as the manual Generate button — no
# parallel implementation. Resolves target language via per-channel
# wiki.default_language → DEFAULT_TARGET_LANGUAGE → en.
# AUTO_OVERVIEW_WIKI=true
# Folder planner: when ON the structure planner emits hierarchical
# folder pages (≥ N topics required, see threshold below). When OFF the
# wiki is rendered as a flat topic list — useful for very small channels
# or when folder synthesis cost is not warranted.
WIKI_FOLDER_PLANNER=true
# Minimum number of topic pages required before the folder planner
# synthesizes folder index pages. Below this the wiki stays flat to
# avoid one-folder-per-topic noise on sparse channels.
WIKI_MIN_TOPICS_FOR_FOLDERS=6
# Max concurrent topic-page LLM compilations during a wiki build. Default
# 6 matches the Gemini Flash RPM sweet spot. Set higher (up to 16) on
# ultra-large channels with 30+ topics on paid Gemini tiers.
WIKI_TOPIC_COMPILE_PARALLELISM=6
# Grace window (seconds) before the maintainer flushes dirty wiki pages
# after a memory_settled event. Default 5s — short because the queue has
# already drained when memory_settled fires; there's nothing left to
# coalesce. Distinct from WIKI_MAINTAINER_DEBOUNCE_SECONDS (=60) which
# applies to the mid-sync on_memory_changed path.
WIKI_MAINTAINER_SETTLE_DEBOUNCE_SECONDS=5
# Wiki page-voice drift A/B comparator (off by default — only flip ON
# during the 2-week soak that gates flipping WIKI_MAINTENANCE_MODE=auto
# as a system-wide default; see docs/runbooks/wiki-maintenance-soak.md).
WIKI_DRIFT_AB=false
WIKI_DRIFT_AB_RATE_LIMIT_SECONDS=60
# Per-kind drift A/B sampler — fraction of apply_update calls that
# trigger the redesign-vs-legacy synthesis comparison. 0.05 = 5%; 0.0
# disables the per-kind sampler. Independent from WIKI_DRIFT_AB above.
WIKI_DRIFT_AB_PER_KIND_SAMPLE_RATE=0.05
# LLM-native wiki redesign (change wiki-llm-native-redesign).
# OFF for legacy installs — flip ON after the per-kind drift soak passes
# (see openspec/changes/wiki-llm-native-redesign/tasks.md §9). When ON,
# the maintainer dispatches per-page-kind synthesis prompts, parses
# [[wikilinks]], and emits structured kind_schema payloads.
WIKI_LLM_NATIVE_REDESIGN=false
# Fact-overlap (Jaccard) threshold for page-merge proposals. Operator
# approves via the curation UI; no auto-merge.
WIKI_PAGE_MERGE_THRESHOLD=0.70