-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy path.env.example
More file actions
538 lines (451 loc) · 26.6 KB
/
Copy path.env.example
File metadata and controls
538 lines (451 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
# ═══════════════════════════════════════════════════════════════
# Tofu (豆腐) — Environment Configuration
# ═══════════════════════════════════════════════════════════════
#
# Copy this file to .env and fill in your values:
#
# cp .env.example .env
#
# Then start the server:
#
# python server.py
#
# Priority: Settings UI > environment variables > defaults
# (Any setting you configure in the web UI takes precedence)
#
# Lines starting with # are comments. Remove # to enable a variable.
# ═══════════════════════════════════════════════════════════════
# ── LLM Provider ──────────────────────────────────────────────
# The easiest way is to configure via Settings UI (⚙️ → Providers).
# These env vars serve as fallback for headless / Docker deployments.
# API key (required — comma-separated for multiple keys)
# LLM_API_KEYS=sk-key1,sk-key2,sk-key3
# Single key (legacy, works too)
# LLM_API_KEY=sk-your-key-here
# API endpoint (default: https://api.openai.com/v1)
# LLM_BASE_URL=https://api.openai.com/v1
# Default model (default: gpt-4o)
# LLM_MODEL=gpt-4o
# Fallback model — used when the primary model fails (default: disabled)
# FALLBACK_MODEL=gpt-4o-mini
# Keep remote provider model catalogues current from their authenticated
# /models endpoint (default: enabled). New models are added automatically;
# a missing model is retired only after two successful consecutive snapshots.
# Failed/empty responses always keep the last-good list. Per-provider control
# is also available in Settings → Providers.
# TOFU_MODEL_CATALOG_SYNC=1
# TOFU_MODEL_CATALOG_SYNC_INTERVAL=21600
# TOFU_MODEL_CATALOG_REMOVE_AFTER=2
# ── Agent-loop context economy ──
# Maximum repeatedly replayed prompt working set before automatic structured
# compaction (default: 128000 tokens). This is intentionally lower than a 1M
# model's hard context window because cache reads still cost money and time.
# Set 0 to restore the old context-window-only trigger.
# TOFU_WORKING_CONTEXT_TOKENS=128000
# MCP schema exposure (default: auto). In auto mode catalogs above the inline
# limit become three stable discovery/read/write meta tools, avoiding hundreds
# of schemas on every LLM round. Set inline for compatibility or progressive to
# force catalog search even for a small server.
# TOFU_MCP_TOOL_EXPOSURE=auto
# TOFU_MCP_INLINE_TOOL_LIMIT=16
# Unified project editor (default: enabled). It exposes one edit_file tool with
# replace/insert_before/insert_after operations instead of four competing edit
# schemas. Set to 0 for an immediate compatibility rollback to the legacy tools.
# TOFU_UNIFIED_EDIT_TOOL=1
# ── Server ────────────────────────────────────────────────────
# Server port (default: 15000, auto-increments if occupied)
# PORT=15000
# Bind address (default: 0.0.0.0 — all interfaces; the desktop-agent
# LAN pairing flow relies on it). Set 127.0.0.1 to bind loopback only.
# BIND_HOST=0.0.0.0
# Flask debug mode (default: 0)
# FLASK_DEBUG=0
# Listener TLS is opt-in. Keep the origin on HTTP behind Codelab, VS Code,
# JupyterHub, nginx, Caddy, Kubernetes ingress, or any proxy that terminates
# HTTPS. For a direct HTTPS listener, set TOFU_TLS=1; a development certificate
# is generated when no TLS_CERTFILE/TLS_KEYFILE pair is configured.
# TOFU_TLS=0
# Development TLS certificate SANs (comma-separated DNS names or IPs).
# The auto-generated certificate always includes localhost, loopback, the
# machine hostname and discovered local addresses. Delete data/certs/tofu.*
# once after changing this list so the development certificate is regenerated.
# For production/LAN fleets, terminate TLS at an ingress with a certificate
# trusted by every client device instead of distributing this self-signed cert.
# TOFU_TLS_SANS=tofu.lan,192.168.1.20
# Generic outbound HTTP connection-pool bounds (does not change request or
# LLM-stream timeouts). One requests pool is kept per worker thread and one
# httpx pool per event loop/proxy/TLS policy.
# TOFU_HTTP_POOL_MAX_CONNECTIONS=64
# TOFU_HTTP_POOL_MAX_KEEPALIVE=20
# ── Horizontal scale (advanced) ──────────────────────────────
# server.py intentionally runs ONE ASGI process per replica; its programmatic
# Hypercorn entry point cannot honor Hypercorn's CLI --workers fan-out. Scale
# by supervising multiple replicas behind an affinity-aware ingress; see
# deploy/nginx-task-affinity.conf.example and docs/ARCHITECTURE.md §7.
#
# Redis makes admission leases, the conversation latest-task index and WebSocket
# push fan-out shared across replicas. A transient Redis outage fails open and
# reconnects automatically; live task leases are heartbeated so long tasks do
# not disappear from admission accounting while their process is alive.
# TOFU_RUNTIME_STATE_BACKEND=redis
# TOFU_REDIS_URL=redis://redis.internal:6379/0
# TOFU_REPLICA_ID=chatui-1
# Optional diagnostics-only rendezvous ring (ingress routing remains authoritative).
# TOFU_REPLICA_RING=chatui-1,chatui-2
#
# Every replica also needs the same authoritative database. Multi-replica use
# with independent SQLite files is NOT a shared-data deployment; use the
# project's supported shared-database rollout path before enabling this mode.
# ── Database ──────────────────────────────────────────────────
# SQLite and PostgreSQL are equal Storage Sidecar backends. SQLite is the
# zero-configuration default; PostgreSQL selection is explicit. The selected
# backend failing preflight is fatal and never triggers an engine switch.
# Sole backend selector: exactly sqlite or postgres (default sqlite).
# TOFU_DB_BACKEND=sqlite
# Sidecar pools. PostgreSQL's combined request is capped to 80% of the server
# connection budget; SQLite always has exactly one writer connection.
# TOFU_STORAGE_SQLITE_READ_POOL=16
# TOFU_STORAGE_PG_READ_POOL=32
# TOFU_STORAGE_PG_WRITE_POOL=16
# SQLite tuning defaults were measured on the project FUSE mount. Leave them
# unset unless operating evidence says otherwise.
# TOFU_SQLITE_BUSY_TIMEOUT_MS=30000
# TOFU_SQLITE_POOL_MAX=20
# TOFU_SQLITE_WAL_AUTOCHECKPOINT_PAGES=4096
# TOFU_SQLITE_SNAPSHOT_RETENTION=2
# Verified snapshots remain below project data/backups. Copying a verified
# artifact elsewhere is an operator export step, never a live storage path.
# TOFU_SQLITE_SNAPSHOT_MAX_AGE_HOURS=26
# Fresh SQLite databases opt into incremental free-page reclamation. The
# maintenance daemon returns at most 256 pages (~1 MiB) every 15 minutes, and
# only after the freelist reaches 4096 pages (~16 MiB). Existing databases in
# auto_vacuum=NONE mode are never rewritten or converted automatically.
# Set pages to 0 to disable reclamation; raise only on fast local storage.
# TOFU_SQLITE_INCREMENTAL_VACUUM_PAGES=256
# TOFU_SQLITE_INCREMENTAL_VACUUM_MIN_FREE_PAGES=4096
# TOFU_SQLITE_INCREMENTAL_VACUUM_BUDGET_MS=250
# Normalized conversation-message rows are enabled by default for the personal
# server. Every read still fails closed per conversation unless revision,
# exact row count, and lightweight projection count all match the authoritative
# parent row. These are emergency/migration kill switches, not install steps:
# TOFU_MESSAGES_ROWS=0
# TOFU_MESSAGES_ROWS_READ=0
# After a byte-for-byte production parity audit, this makes normalized rows the
# sole transcript authority. The legacy conversations.messages value freezes;
# startup refuses incomplete rows and runtime SQL rejects archive reads.
# TOFU_MESSAGES_ROWS_AUTHORITY=1
# Upgraded row stores fill the new fixed-width activity timestamp in a delayed,
# single-threaded online pass (10s initial delay, 100ms between conversations).
# New installs have nothing to backfill. Optional slow-disk controls:
# TOFU_MESSAGES_ACTIVITY_BACKFILL=0
# TOFU_MESSAGES_ACTIVITY_BACKFILL_INITIAL_MS=10000
# TOFU_MESSAGES_ACTIVITY_BACKFILL_SLEEP_MS=100
# Online retention is intentionally conservative on FUSE-backed personal
# installs: task_results may each own MiB-sized TOAST values, so only 25 rows
# are deleted per transaction (at most 4 transactions per maintenance cycle).
# Raise these only after measuring the storage underneath PostgreSQL.
# TOFU_TASK_RESULT_PRUNE_BATCH_ROWS=25
# TOFU_TASK_RESULT_PRUNE_BATCHES=4
# Port for the local auto-bootstrapped PG. Pin this explicitly when
# multiple Tofu deployments share a host so each owns a stable port
# (the bootstrapper otherwise scans upward from 15432 and could land
# on a sibling deployment's cluster). Default: auto-selected.
# TOFU_PG_PORT=15432
# Connect to an EXTERNAL/managed PG instead of bootstrapping locally.
# Set to a remote host (managed PG, Docker Compose service, CI service
# container). When set to a non-loopback host, local bootstrap is
# skipped entirely. Default: unset (local auto-bootstrap).
# TOFU_PG_HOST=db.internal.example.com
# Require enforced POSIX advisory locks before using a local PG.
#
# The cross-host startup interlock (which prevents two hosts from
# starting postmasters on the SAME shared pgdata — a double-start
# corrupts WAL/pg_subtrans) relies on flock() actually being enforced
# by the filesystem under data/pgdata. Most filesystems (ext4/xfs/
# NFSv4/most FUSE) enforce it; some network/FUSE mounts silently treat
# flock as a no-op, which makes the interlock useless.
#
# At startup Tofu actively probes whether locks are truly enforced:
# default (unset / 0) — if NOT enforced, log a loud WARNING and
# proceed (safe for SINGLE-host deployments).
# 1 / true / refuse — if NOT enforced, REFUSE PostgreSQL and fall
# back to SQLite rather than risk corruption.
#
# ⚠️ If you run MULTIPLE Tofu hosts against one shared (FUSE/NFS)
# pgdata, set this to 1 so a non-locking mount fails loudly instead of
# silently degrading to IP/PID heuristics.
# TOFU_PG_REQUIRE_FLOCK=0
# Stop the local auto-bootstrapped PG when server.py exits.
# 0 (default) — LEAVE PG running across a server.py restart. PG is a
# SHARED resource: on a box with multiple sessions (and
# mid-flight tasks) all bound to one local PG, stopping it
# on every code-reload restart tears every session's DB
# connections out. Leaving it up also makes restarts faster
# (the next boot ATTACHES to the live PG instead of cold-
# starting a new postmaster). The tofu heartbeat is still
# cleared on exit, so cross-host takeover on shared pgdata
# stays safe.
# 1 — stop local PG on exit (true host shutdown, or before
# switching hosts on shared FUSE pgdata).
# TOFU_STOP_PG_ON_EXIT=0
# ── Data / state location (keep user data OUT of the code tree) ──
# All mutable user/runtime state — the database, config, logs, uploads,
# global memories — lives under ONE writable "data root". Keeping it
# separate from the code tree means an in-place update (git pull, or the
# tarball-overlay updater for non-git installs) only ever touches source,
# never races your open database.
#
# Resolution precedence: TOFU_DATA_DIR > TOFU_DATA_LAYOUT > auto-detect.
#
# TOFU_DATA_DIR — absolute override. Point it anywhere writable and the
# DB, logs, config and memories all follow. Highest precedence.
# TOFU_DATA_DIR=/var/lib/tofu/data
#
# TOFU_DATA_LAYOUT — for a plain source/git checkout, choose where state
# lands when TOFU_DATA_DIR is unset:
# auto (default) — an existing populated in-tree ./data keeps working
# in-place (zero migration); a FRESH clone with no
# ./data defaults OUT of the tree to a per-user dir
# (~/.local/share/Tofu, %LOCALAPPDATA%\Tofu, or
# ~/Library/Application Support/Tofu).
# intree — force the legacy ./data-inside-the-repo layout.
# xdg — force the per-user dir even if ./data exists
# (opt-in relocation for an existing install).
# TOFU_DATA_LAYOUT=auto
# ── Tunnel Authentication ─────────────────────────────────────
# Set to enable token-based auth for public tunnel access (e.g.
# VS Code port forwarding, ngrok). Leave empty for LAN-only mode.
# TUNNEL_TOKEN=your-secret-token
# TUNNEL_TOKEN=your-secret-token
# ── Open-mode per-IP throttle ─────────────────────
# In `open` auth mode (the default), requests get a synthetic local-admin
# context, and a per-IP requests-per-minute cap guards the EXPENSIVE
# surfaces (chat / agent / search / generate) against strangers hammering
# an exposed instance. The cap is scoped to the one configuration where
# unauthenticated strangers can actually arrive:
#
# • Default (loopback-only personal install): cap OFF. The only IPs
# that could ever hit it are your own tabs and background polls —
# throttling them would just throttle you.
# • TOFU_OPEN_MODE_ALLOW_REMOTE=1 (remote open-mode peers admitted):
# cap auto-arms at 120 req/min per IP. Ambient UI reads (task polls,
# browser/desktop status, model health) never count against it.
# • TOFU_OPEN_MODE_RPM=<n> always wins, both directions: n>0 arms the
# cap even on a loopback-only box; 0 disarms it even with remote
# peers admitted.
#
# Behind a same-host proxy/tunnel the cap cannot tell you from a stranger
# (everything arrives as 127.0.0.1) — protection there is TUNNEL_TOKEN or
# `private` mode, never IP throttling. Extend the exempt read paths via
# TOFU_OPEN_MODE_EXEMPT_PATHS (comma-separated path substrings).
# TOFU_OPEN_MODE_ALLOW_REMOTE=0
# TOFU_OPEN_MODE_RPM=
# TOFU_OPEN_MODE_EXEMPT_PATHS=
# ── Client IP / reverse-proxy semantics (read this if you use a proxy) ──
# Tofu ALWAYS uses the directly-connected peer address as the client IP.
# `X-Forwarded-For` and `X-Forwarded-Proto` are NEVER trusted — there is
# no trusted-proxy option, by design (a spoofable header must not be able
# to move an IP-keyed security decision).
#
# The peer address keys three real decisions, so know what it will be:
# - per-IP rate-limit buckets
# - the open-mode "local admin" grant (loopback peers only)
# - audit-log entries
#
# ⚠️ CONSEQUENCE FOR REVERSE-PROXY / TUNNEL DEPLOYMENTS
# If nginx / Caddy / ngrok / cloudflared runs on the SAME host and proxies
# to 127.0.0.1, then EVERY request — including ones from the public
# internet — arrives with the peer address 127.0.0.1. That means:
# - all clients share ONE open-mode throttle bucket (armed only when
# TOFU_OPEN_MODE_RPM / TOFU_OPEN_MODE_ALLOW_REMOTE is set — see the
# throttle section above);
# - audit logs record 127.0.0.1 instead of the real caller;
# - in `open` auth mode the loopback admin grant would apply to public
# traffic, so DO NOT run open mode behind a same-host proxy. Use
# `private` mode (a real credential) — see the auth section below.
#
# To keep real client IPs meaningful, terminate the proxy on a DIFFERENT
# host, or have the proxy enforce authentication itself.
#
# Historical note: a `TOFU_TRUST_PROXY_HOPS` setting was documented here
# but never implemented — the server is ASGI (Quart/Hypercorn) and the
# Werkzeug `ProxyFix` middleware it referred to is WSGI-only, so it could
# not have taken effect. Setting it did nothing. It has been removed
# rather than left as a promise the code does not keep.
# Guard: tests/test_proxy_trust.py asserts both the observed behaviour and
# that this file does not re-introduce an unimplemented promise.
# ── Browser / Desktop Bridge Authentication ───────────────────
# When the server is exposed over a tunnel, set TOFU_BRIDGE_SECRET to
# require an X-Bridge-Secret header on the bridge endpoints
# (/api/browser/{poll,commands,result} and /api/desktop/poll). UI status
# endpoints are unaffected. Leave empty for LAN-only mode (default).
#
# Generate with: python -c "import secrets; print(secrets.token_hex(32))"
#
# After setting, configure the same value in:
# - Browser extension popup → "Bridge Secret" field
# - Desktop agent: --bridge-secret <value> (or this same env var)
# TOFU_BRIDGE_SECRET=
# ── Image-fetch SSRF guard ────────────────────────────────────
# /api/images/generate accepts user-supplied image_url values for
# multi-turn editing. By default the server blocks any URL whose host
# resolves to loopback / RFC1918 / link-local / multicast / reserved
# IPs, and caps response bodies at 10 MB. Override only if you self-host
# image storage on a private endpoint that you trust.
# TOFU_IMAGE_FETCH_ALLOW_HOSTS=internal-images.example.com,storage.local
# TOFU_IMAGE_FETCH_MAX_BYTES=10485760
# ── Rate-limit counter backend ────────────────────────────────
# Where the @rate_limit decorator stores its per-(endpoint, ip) counters:
# memory (default) — in-process dict; correct for single-process Flask
# db — INSERT into rate_limit_events table; survives restarts and
# works across multi-worker WSGI (gunicorn / uWSGI N>1)
#
# If you run gunicorn or uWSGI with worker count > 1, you MUST set this
# to `db` — otherwise each worker has its own counter and the effective
# limit is N × the configured value. Single-process `python server.py`
# users can keep the default.
# TOFU_RATE_LIMIT_BACKEND=memory
# ── Proxy (if behind a corporate firewall) ────────────────────
# HTTP_PROXY=http://proxy.example.com:8080
# HTTPS_PROXY=http://proxy.example.com:8080
# PROXY_BYPASS_DOMAINS=.internal.example.com,.corp.example.com
# ── Feishu / Lark Bot (optional) ──────────────────────────────
# Create an app at https://open.feishu.cn/app with Bot capability.
# FEISHU_APP_ID=cli_xxx
# FEISHU_APP_SECRET=xxx
# ── Feature Flags ─────────────────────────────────────────────
# Enable trading advisor module (default: 0 = off)
# TRADING_ENABLED=0
# Enable debug mode (default: 0 = off)
# DEBUG_MODE=0
# ── Search & Fetch ────────────────────────────────────────────
# These can also be configured in Settings UI (⚙️ → Search & Fetch).
# Number of search results to auto-fetch (default: 6)
# FETCH_TOP_N=6
# Per-page fetch timeout in seconds (default: 15)
# FETCH_TIMEOUT=15
# Max characters per page for search results (default: 60000)
# FETCH_MAX_CHARS_SEARCH=60000
# Max characters per page for direct URL fetch (default: 200000)
# FETCH_MAX_CHARS_DIRECT=200000
# Depth: follow the top query-relevant outbound links one hop deeper after
# the main fetch (0 = off, default; 1 = one hop). Adds ~1-3s. Can also be
# toggled per-call via perform_web_search(deepen=True) or features.json
# (search_deepen). Increases coverage at the cost of some latency.
# SEARCH_DEEPEN_HOPS=0
# Hard wall-clock deadline (seconds) for ONE web_search call (default: 45).
# When the budget expires the pipeline force-returns whatever pages it has
# gathered so far (tagged as a partial result) instead of blocking on slow or
# unreachable hosts — it caps the fetch-wait loop and short-circuits the
# LLM-filter / deepen / rerank stages. This is the guard against a niche-domain
# query (mostly dead/paywalled hosts) wedging the turn: the only prior caps
# were a 20s engine timeout and a 90s fetch timeout that only exits early once
# enough pages succeed, which such a query never reaches. Set 0 to disable
# (legacy unbounded behaviour). Look for `⏱ DEADLINE` lines in logs/app.log.
# TOFU_SEARCH_DEADLINE_SECS=45
# Per-URL total-time cap (seconds) bounding the WHOLE fallback chain for one
# page (HTTP body-download + browser fallback + Playwright render), so a single
# dead host can't stack per-hop timeouts into 60s+ (default: 25). Soft bound:
# once blown, remaining fallback hops are skipped rather than killed mid-flight,
# so worst case ≈ deadline + one in-flight hop. Set 0 to disable.
# TOFU_SEARCH_FETCH_URL_DEADLINE_SECS=25
# Maximum formatted log records buffered while the log filesystem is slow
# (default 20000, clamped to 1000..500000). Producers never block; overload is
# shed and summarized after recovery instead of growing memory without bound.
# Normal single-user installations should keep the default.
# TOFU_LOG_QUEUE_MAX=20000
# Audit trail size rotation (defaults: 64 MiB per file, 4 backups). This keeps
# security/audit diagnostics while preventing the manually appended JSONL file
# from growing forever. Values are optional for normal single-user installs.
# TOFU_AUDIT_LOG_MAX_BYTES=67108864
# TOFU_AUDIT_LOG_BACKUPS=4
# Maximum queued audit records while the log filesystem is stalled (default
# 4096, clamped to 128..100000). Producers never block; overload discards the
# oldest queued record so memory stays bounded and recent evidence is kept.
# TOFU_AUDIT_LOG_QUEUE_MAX=4096
# Crash/stall dump bounds (16 MiB active file; newest 8 dead-process files
# within 64 MiB total). Raw SSE anomaly blocks rotate at 32 MiB × 3 files.
# These diagnostics stay enabled; the limits only prevent runaway disk use.
# TOFU_FAULT_DUMP_MAX_BYTES=16777216
# TOFU_FAULT_DUMP_FILES=8
# TOFU_FAULT_DUMP_TOTAL_BYTES=67108864
# TOFU_RAW_SSE_ANOMALY_MAX_BYTES=33554432
# TOFU_RAW_SSE_ANOMALY_BACKUPS=2
# Interrupted chat turns are always checkpointed and surfaced for manual
# Continue after a restart/crash. Automatically starting a replacement LLM
# request at boot is OFF by default because it is a new billed operation.
# Enable only when unattended recovery is desired. Ordinary chat turns from a
# clean/manual stop remain manual; unclean killed turns may be re-dispatched,
# and an explicitly armed Autopilot run may resume through the same gate.
# Killed-turn recovery is attempt-capped and concurrency-bounded to avoid an
# OOM/restart storm re-firing a large batch of turns at once.
# TOFU_BOOT_AUTO_DISPATCH=0
# TOFU_KILLED_RECOVERY_MAX_ATTEMPTS=3
# TOFU_KILLED_RECOVERY_MAX_CONCURRENT=2
# TOFU_KILLED_RECOVERY_INLINE_BOOT=1
# TOFU_KILLED_RECOVERY_DRAIN_POLL_SECS=15
# TOFU_RESTART_STORM_THRESHOLD=5
# TOFU_RESTART_STORM_WINDOW_SECS=120
# The external lifecycle manager applies the same rolling crash-loop budget to
# worker deaths, including workers that briefly became healthy between OOMs.
# Failures back off exponentially; the fifth within two minutes pauses recovery
# until an explicit start/restart, preventing a resource-amplifying storm.
# TOFU_MANAGER_MAX_FAILURES=5
# TOFU_MANAGER_FAILURE_WINDOW_SECS=120
# Agent/task backpressure. The personal server admits at most 16 resident
# agent tasks by default; 0 restores legacy unbounded task-count behavior.
# Independently, new tasks are refused while the shared cgroup is >=96% full,
# preventing an editor/browser/PostgreSQL memory spike from turning the next
# Send into an OOM kill. Set the percentage to 0 only to disable that pressure
# gate. These do not change the normal `python server.py` launch command.
# TOFU_MAX_INFLIGHT_TASKS=16
# TOFU_ADMISSION_CGROUP_PCT=96
# TOFU_AGENT_WORKERS=16
# TOFU_SYNC_WORKERS=16
# Process-local OOM prevention. At the soft RSS ceiling Tofu clears bounded
# caches, drops its log page cache and asks glibc to return free arenas. If RSS
# remains above the hard ceiling afterward, it performs one graceful worker
# recycle; the lifecycle manager/container restart policy restores a fresh
# process. The stdlib lifecycle manager independently samples /proc and enforces
# the same hard ceiling, so it also protects an older worker before that worker
# has loaded the newest in-process guard. Set either value to 0 only to disable
# that layer. Keep the hard ceiling comfortably below the deployment's memory
# limit and above legitimate peak working-set measurements.
# Defaults adapt downward on constrained containers: soft=min(4096 MiB, 50%
# of cgroup limit), hard=min(8192 MiB, 70% of cgroup limit).
# TOFU_PROCESS_RSS_RELIEF_MB=4096
# TOFU_PROCESS_RSS_RECYCLE_MB=8192
# TOFU_PROCESS_RSS_COOLDOWN_SEC=300
# Implicit native numeric pools (OpenBLAS/OpenMP/MKL/NumExpr) default to 4
# threads. This prevents a high-core host from creating 64+ idle native threads
# alongside Tofu's own executors. Explicit library-specific variables still
# win, and this knob may be raised for dedicated CPU-heavy PDF/ML workloads.
# TOFU_NUMERIC_THREADS=4
# Codex subscription prompt-cache pacing. The private Responses endpoint does
# not report cache writes, so Tofu infers a pending write and holds the next
# cacheable tool-loop request for up to 5s. It also spaces starts by 4.2s to
# stay below OpenAI's approximate 15 requests/minute per prompt_cache_key
# boundary. These apply only to oauth_codex; other providers keep their own
# cache-settle policy. Lower values trade cache hits for tool-loop latency.
# TOFU_CACHE_SETTLE_CODEX_VISIBILITY_MS=5000
# TOFU_CACHE_SETTLE_CODEX_SEND_INTERVAL_MS=4200
# TOFU_CACHE_SETTLE_CODEX_MAX_MS=6000
# TOFU_CACHE_SETTLE_CODEX_THRESHOLD_TOKENS=1024
# ── PDF Parsing ───────────────────────────────────────────────
# Default text-extract strategy for /api/pdf/parse:
# rich — pymupdf4llm (default; ships out of the box)
# structured — IBM Docling (better tables + math on academic PDFs).
# Requires `pip install docling` (~2 GB; pulls torch).
# If docling is missing or fails, server falls back to
# rich automatically — uploads never break.
# fast — raw pymupdf get_text (no Markdown structure, ~50× faster)
# PDF_TEXT_MODE=rich
# VLM PDF parser tuning (applies when VLM mode is used).
# Pages per single VLM call (1–16). Default 4. Larger = fewer HTTP
# round-trips and less 429 thrash, but more output tokens per call.
# PDF_VLM_BATCH_PAGES=4
# Cap on concurrent VLM calls. Default = unlimited (one thread per
# batch). Lower this on shared keys to avoid 429 storms.
# PDF_VLM_MAX_WORKERS=8
# Output token cap per VLM call. Default scales with batch (4096/page).
# PDF_VLM_MAX_TOKENS=16384