-
Notifications
You must be signed in to change notification settings - Fork 73
Expand file tree
/
Copy path.env.example
More file actions
576 lines (492 loc) · 27.4 KB
/
Copy path.env.example
File metadata and controls
576 lines (492 loc) · 27.4 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
# Trinity Agent Platform - Environment Configuration Template
# Copy this file to .env and fill in your actual values
# ===========================================
# REQUIRED SETTINGS (SECURITY-CRITICAL)
# ===========================================
# JWT Secret Key - REQUIRED for production
# Generate with: openssl rand -hex 32
# SECURITY: Never use default value in production!
SECRET_KEY=
# Credential Encryption Key - REQUIRED for production
# Used to encrypt sensitive data (OAuth tokens, subscription credentials)
# Generate with: openssl rand -hex 32
# SECURITY: If lost, all encrypted credentials become unrecoverable!
CREDENTIAL_ENCRYPTION_KEY=
# Optional decrypt-only fallback used ONLY during key rotation (#267): set it to
# the PREVIOUS CREDENTIAL_ENCRYPTION_KEY while the line above holds the new one,
# run scripts/deploy/rotate-credential-key.py --apply, then remove this line.
# See docs/migrations/CREDENTIAL_KEY_ROTATION.md. Leave empty in normal operation.
CREDENTIAL_ENCRYPTION_KEY_SECONDARY=
# Internal API Secret - Used for scheduler-to-backend communication (C-003)
# Generate with: openssl rand -hex 32
# Falls back to SECRET_KEY if not set
INTERNAL_API_SECRET=
# Agent Auth Secret (#1159) - master from which the backend derives each
# agent's in-container :8000 auth token. Generate with: openssl rand -hex 32
# SECURITY: must stay stable — rotating it 401s the running fleet until every
# agent container is recreated. start.sh auto-generates on first boot if blank.
AGENT_AUTH_SECRET=
# Admin credentials
# SECURITY: Use a strong password, minimum 12 characters
ADMIN_USERNAME=admin
ADMIN_PASSWORD=
# Anthropic API Key - Required for agents to use Claude
# You can leave this empty and configure it in Settings after login
ANTHROPIC_API_KEY=
# ===========================================
# EMAIL AUTHENTICATION (Default)
# ===========================================
# Email-based login is enabled by default
# Users enter email → receive 6-digit code → login
# Whitelist emails in Settings → Email Whitelist
# Public self-signup (trinity-enterprise#10) — DISABLED by default (secure).
# When false, POST /api/access/request returns 403 and never auto-whitelists,
# so the email whitelist stays authoritative. Set to true ONLY if you want
# frictionless CLI onboarding (`trinity:connect`) where anyone who can reach
# the backend can add their own email to the login whitelist.
PUBLIC_ACCESS_REQUESTS_ENABLED=false
# Operator intake (trinity-enterprise#38). At first-run setup the operator can
# OPT IN to occasionally receive important security & product updates; on that
# explicit consent, their email + company are submitted ONCE to a hosted
# Ability.ai intake endpoint (a sibling of #1116's bug-report intake on the same
# Cloudflare domain). Identifiable opt-in contact capture — NOT anonymous
# telemetry — so it only fires on an affirmative checkbox. Fire-and-forget and
# once-per-install: a blocked/failed POST never delays or breaks setup.
# Set to false (or DO_NOT_TRACK=1) to fully disable the outbound submission for
# air-gapped / privacy-strict installs — the consent box still appears, but
# nothing ever leaves the box.
OPERATOR_INTAKE_ENABLED=true
# Stable Cloudflare-fronted endpoint (override only to self-host the intake).
OPERATOR_INTAKE_URL=https://intake.abilityai.dev/v1/operator-intake
# Cross-tool Do-Not-Track convention (consoledonottrack.com). 0/empty/false =
# tracking allowed; ANY other value (1, true, yes, on, …) disables the operator-
# intake POST — equivalent to OPERATOR_INTAKE_ENABLED=false.
DO_NOT_TRACK=0
# Tier-2 opt-in fleet telemetry sharing (ent#12). Anonymized aggregate counts,
# egressed ONLY after explicit admin consent in Settings (default off) AND this
# config switch. Set to false (or DO_NOT_TRACK=1) as the hard kill switch —
# consent can then never enable egress (the Settings toggle 409s).
TELEMETRY_SHARING_ENABLED=true
# Hosted intake endpoint (override only to self-host the intake).
TELEMETRY_SHARING_URL=https://intake.abilityai.dev/v1/telemetry-share
# Heartbeat cadence and the default consent-time backfill window.
TELEMETRY_SHARING_INTERVAL_HOURS=24
TELEMETRY_SHARING_BACKFILL_DEFAULT_DAYS=30
# Email service provider: console (dev), smtp, sendgrid, resend
EMAIL_PROVIDER=console
# Resend API (recommended for production)
# Get from: https://resend.com/api-keys
RESEND_API_KEY=
# SendGrid API (when EMAIL_PROVIDER=sendgrid)
# Get from: https://app.sendgrid.com/settings/api_keys
SENDGRID_API_KEY=
# SMTP provider (when EMAIL_PROVIDER=smtp)
SMTP_HOST=
SMTP_PORT=587
SMTP_USER=
SMTP_PASSWORD=
# From address for verification emails
SMTP_FROM=noreply@your-domain.com
# ===========================================
# CORS ORIGINS
# ===========================================
# Additional CORS origins (comma-separated) [PROD: wire through docker-compose.prod.yml]
# Add your production domains here
EXTRA_CORS_ORIGINS=https://your-domain.com,http://your-domain.com
# ===========================================
# OAUTH PROVIDERS (Optional)
# ===========================================
# Google OAuth (for Google Workspace MCP)
# Get from: https://console.cloud.google.com/apis/credentials
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Slack OAuth (for Slack MCP)
# Get from: https://api.slack.com/apps
SLACK_CLIENT_ID=
SLACK_CLIENT_SECRET=
# Slack Signing Secret (for verifying Slack webhook requests)
# Get from: https://api.slack.com/apps → Basic Information → Signing Secret
# [PROD: also set in docker-compose.prod.yml environment block]
SLACK_SIGNING_SECRET=
# Slack Socket Mode concurrent connections (#244)
# Slack allows up to 10 concurrent WebSocket connections per app. Running >1
# means the others keep absorbing traffic when one half-closes — eliminating
# the brief reconnect gap. Range 1–10; out-of-range values are clamped, and
# malformed values fall back to the default (2).
SLACK_SOCKET_CONNECTION_COUNT=2
# GitHub OAuth (for GitHub MCP)
# Get from: https://github.com/settings/developers
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
# GitHub Personal Access Token (for cloning private template repos)
# Get from: https://github.com/settings/tokens (classic token with 'repo' scope)
# This will be auto-uploaded to Redis on startup for local development
GITHUB_PAT=
# Self-hosted git support (#387) [OVERLAY: forwarded by docker-compose.gitea.yml only]
# Optional overrides — default to github.com / api.github.com (standard GitHub).
# Set both when targeting GitHub Enterprise Server, Gitea, or a dev harness.
# To activate: use `docker compose -f docker-compose.yml -f docker-compose.gitea.yml up -d`
# TRINITY_GIT_BASE_URL=https://git.example.com
# TRINITY_GIT_API_BASE=https://git.example.com/api/v1
# Notion OAuth (for Notion MCP)
# Get from: https://www.notion.so/my-integrations
NOTION_CLIENT_ID=
NOTION_CLIENT_SECRET=
# ===========================================
# IMAGE GENERATION (Optional - IMG-001)
# ===========================================
# Google Gemini API Key (for platform image generation)
# Used by the image generation service for prompt refinement and image creation
# Get from: https://aistudio.google.com/apikey
GEMINI_API_KEY=
# Google API Key (for Gemini-powered agents)
# Used as a fallback for GEMINI_API_KEY in the platform; injected into agent
# containers that depend on Gemini models (e.g., gemini-1.5-pro).
# Get from: https://aistudio.google.com/apikey
# May be the same value as GEMINI_API_KEY.
GOOGLE_API_KEY=
# ElevenLabs — outbound voice replies across channels (epic #24; Telegram #25).
# When set, agents with voice replies enabled speak their reply as a voice note
# (synthesized by ElevenLabs, transcoded to OGG/Opus). Empty = feature off
# (adapters deliver text). Get a key at https://elevenlabs.io/app/settings/api-keys
ELEVENLABS_API_KEY=
# Optional model + shared cost guardrail (reply longer than this many chars is
# delivered as text instead of paying to synthesize it).
ELEVENLABS_MODEL_ID=eleven_multilingual_v2
TTS_MAX_CHARS=1500
# Gemini model overrides (#1130) — set these if Google retires the built-in
# default (both default to gemini-3.5-flash, set in src/backend/config.py).
# Two vars because the modalities differ: TEXT is text-only (image-gen prompt
# refinement), TRANSCRIPTION needs inline-audio support (Telegram voice).
# Leave commented unless overriding — an uncommented empty value exports ""
# which shadows the default (#1076).
# GEMINI_TEXT_MODEL=gemini-3.5-flash
# GEMINI_TRANSCRIPTION_MODEL=gemini-3.5-flash
# ===========================================
# VOICE & VoIP TELEPHONY (Optional - VOICE-001 / VOIP-001)
# ===========================================
# Both ride on Gemini Live, so GEMINI_API_KEY (above) must be set for either.
# Voice chat (VOICE-001). Default ON in code; set to false to disable platform-wide.
VOICE_ENABLED=true
# Gemini Live model override. Leave commented to use the built-in default
# (models/gemini-3.1-flash-live-preview, set in src/backend/config.py). #1076:
# an uncommented `VOICE_MODEL=` exports an empty string that shadows the default
# and breaks all voice paths ("model is required") — keep it commented unless
# you are overriding with a real model id.
# VOICE_MODEL=models/gemini-3.1-flash-live-preview
# Voice Workspace canvas — opt-in BETA (#860).
WORKSPACE_ENABLED=false
# VoIP telephony (VOIP-001, #1056) — agents place outbound phone calls over
# Gemini Live via Twilio Media Streams. Opt-in, default OFF. Set to true to
# expose `voip_available` in the feature flags; the feature ALSO requires a
# per-agent voip_bindings row (Twilio account_sid / auth_token / from_number,
# configured via PUT /api/agents/{name}/voip) to actually place calls.
VOIP_ENABLED=false
# PSTN abuse / spend controls (sane defaults — tune for your Twilio plan).
VOIP_MAX_CALL_DURATION=600 # hard per-call cap (seconds)
VOIP_DEFAULT_DAILY_CALL_CAP=50 # per-agent calls/day (overridable per binding)
VOIP_CALL_RATE_LIMIT=5 # outbound calls per owner+destination window
VOIP_CALL_RATE_WINDOW=60 # rate-limit window (seconds)
VOIP_TICKET_TTL_SECONDS=180 # Media Streams WSS ticket TTL (covers dial+ring)
VOIP_INTENT_TTL_SECONDS=180 # staged Gemini-session intent TTL
# Agent structured reports (#918)
REPORT_RATE_LIMIT=30 # max reports an agent may create per 60s window
# Public webhook triggers (#1023, #1424) — 60s windows fixed in code
WEBHOOK_RATE_LIMIT=10 # triggers per token per 60s
WEBHOOK_IP_RATE_LIMIT=60 # pre-auth requests per IP per 60s (unknown-token flood guard)
WEBHOOK_MAX_BODY_BYTES=16384 # request-body size cap in bytes (413 over)
# Agent self-reminders (#1296) — tunable caps (all have working code defaults)
REMINDER_MESSAGE_MAX_CHARS=4000 # max reminder message length
REMINDER_MIN_DELAY_SECONDS=60 # earliest a reminder may fire (floor)
REMINDER_MAX_DELAY_SECONDS=2592000 # latest a reminder may fire (30d ceiling)
MAX_PENDING_REMINDERS_PER_AGENT=25 # concurrent pending reminders per agent
MAX_REMINDERS_PER_AGENT_PER_DAY=100 # rolling 24h creation cap per agent
REMINDER_RATE_LIMIT=30 # set_reminder calls per agent per 60s window
# Operator-queue agent ingestion caps (#1632) — bound a compromised/runaway agent
# flooding ~/.trinity/operator-queue.json. Generous by design (cap abuse, not use).
OPERATOR_QUEUE_MAX_PENDING_PER_AGENT=25 # per-agent pending-DEPTH cap (primary, DB-measured)
OPERATOR_QUEUE_CREATE_RATE_LIMIT=60 # per-agent create rate / window
OPERATOR_QUEUE_CREATE_RATE_WINDOW=60 # rate-limit window (seconds)
OPERATOR_QUEUE_FLEET_CREATE_RATE_LIMIT=300 # fleet-wide create rate / window
OPERATOR_QUEUE_MAX_SCAN_PER_CYCLE=500 # max requests scanned per agent per 5s cycle
OPERATOR_QUEUE_MAX_FILE_BYTES=2097152 # skip an operator-queue.json larger than this (bytes)
OPERATOR_QUEUE_TITLE_MAX=300 # title char cap (truncate-with-marker)
OPERATOR_QUEUE_QUESTION_MAX=4000 # question char cap (truncate-with-marker)
OPERATOR_QUEUE_CONTEXT_MAX_BYTES=8192 # context serialized-byte cap (marker over)
OPERATOR_QUEUE_OPTIONS_MAX_BYTES=4096 # options serialized-byte cap (dropped-marker over)
OPERATOR_QUEUE_ID_MAX=256 # max agent-supplied id length
OPERATOR_QUEUE_EXECUTION_ID_MAX=128 # max execution_id preserved in a context marker
OPERATOR_QUEUE_FLOOD_ALERT_COOLDOWN_SECONDS=300 # one flood alert per agent per this window
# ===========================================
# SERVICE URLS (Usually no need to change)
# ===========================================
# REDIS_URL is built by docker-compose with the `backend` ACL user and password.
# Do NOT set REDIS_URL manually here — it is composed at render time from
# REDIS_BACKEND_PASSWORD (see "REDIS SECURITY" section below).
BACKEND_URL=http://localhost:8000
# Host port the frontend nginx binds to. Default 80 collides with anything
# already on the host's port 80 (other web server, system service, etc.) —
# remap here if you see "port is already allocated" on docker compose up.
# The in-container port is fixed; only this host-side mapping is tunable.
FRONTEND_PORT=80
# FRONTEND_URL is defined under "PUBLIC ACCESS CONFIGURATION" below.
# ===========================================
# REDIS SECURITY (REQUIRED — do not deploy without these)
# ===========================================
# Issue #589: Redis enforces requirepass + per-user ACL. Both passwords are
# mandatory; docker-compose will refuse to render without them.
# Generate each with: openssl rand -hex 24
#
# Two separate passwords by design:
# REDIS_PASSWORD — admin / `default` ACL user (recovery, ad-hoc ops)
# REDIS_BACKEND_PASSWORD — runtime user for backend + scheduler containers
#
# Leaking the runtime password from a compromised platform container does NOT
# grant admin (FLUSHALL, CONFIG, SHUTDOWN). See docs/migrations/REDIS_AUTH.md.
REDIS_PASSWORD=
REDIS_BACKEND_PASSWORD=
# ===========================================
# LOG RETENTION & ARCHIVAL (Optional)
# ===========================================
# Days to keep raw Vector logs before archival/deletion (default: 5 — #1039
# community retention floor; an enterprise `retention` license unlocks longer)
LOG_RETENTION_DAYS=5
# Whether to compress logs to /data/archives instead of deleting (default: true)
LOG_ARCHIVE_ENABLED=true
# Hour of day (UTC, 0-23) to run the daily log cleanup job (default: 3)
LOG_CLEANUP_HOUR=3
# ===========================================
# PUBLIC ACCESS CONFIGURATION (Optional)
# ===========================================
# External URL for public chat links (PUB-002) [PROD: also set in docker-compose.prod.yml]
# Set this when you want to share public agent links with users outside VPN
# This is the public-facing domain (e.g., https://public.your-domain.com)
# Used by: public chat links, Telegram webhooks, Slack OAuth, Nevermined payments
# When set, enables "Copy External Link" button in PublicLinksPanel
# Leave empty if all users have VPN access
PUBLIC_CHAT_URL=
# Frontend URL (for email links, OAuth callbacks) [PROD: set in docker-compose.prod.yml]
# Leave empty to auto-detect from request headers
FRONTEND_URL=
# ===========================================
# CLOUDFLARE TUNNEL (Optional - public access)
# ===========================================
# Cloudflare Tunnel token for public endpoint access
# Enables external access to public chat, Telegram webhooks, Slack OAuth, etc.
# without exposing the instance directly or opening firewall ports
#
# Setup:
# 1. Go to Cloudflare Zero Trust → Networks → Tunnels → Create
# 2. Copy the tunnel token
# 3. Configure public hostname and ingress rules in Cloudflare dashboard
# 4. Add CNAME record in your DNS: public.your-domain.com → <tunnel-id>.cfargotunnel.com
# 5. Start with: docker compose --profile tunnel up -d
#
# Exposed paths (configure in Cloudflare dashboard):
# / → Frontend SPA root
# /chat/* → Public chat UI
# /api/public/* → Public API + Slack OAuth callback
# /api/telegram/webhook/* → Telegram bot webhooks
# /api/paid/*/chat → Nevermined paid chat
# /api/paid/*/info → Payment info
# /assets/* → Static assets
#
# Leave empty to disable tunnel (instance stays private via Tailscale only)
TUNNEL_TOKEN=
# ===========================================
# SSH ACCESS CONFIGURATION (Optional)
# ===========================================
# SSH host for agent SSH access (MCP tool get_agent_ssh_access)
# [DEV: not forwarded by docker-compose.yml; add manually if needed]
# Auto-detected from FRONTEND_URL domain in production, or:
# - Set explicitly for custom setups (e.g., Tailscale IP, public IP)
# - Leave empty to use auto-detection
SSH_HOST=
# ===========================================
# DATABASE BACKEND (#300 — optional)
# ===========================================
# Trinity defaults to SQLite (zero-config, file at TRINITY_DB_PATH). To use
# PostgreSQL instead, start the optional postgres service and point the backend
# at it:
# 1. Set POSTGRES_PASSWORD below.
# 2. Bring up Postgres: docker compose --profile postgres up -d postgres
# 3. Set DATABASE_URL (uncomment) and restart the backend.
# Leave DATABASE_URL unset for the default SQLite behavior.
#
# DATABASE_URL=postgresql://trinity:your-postgres-password@postgres:5432/trinity
# DB_POOL_SIZE=10 # PostgreSQL connection pool size (ignored for SQLite)
# DB_MAX_OVERFLOW=20 # PostgreSQL pool overflow (ignored for SQLite)
#
# Credentials for the optional bundled postgres service (compose --profile postgres).
# POSTGRES_PASSWORD is required only when that profile is enabled.
# POSTGRES_DB=trinity
# POSTGRES_USER=trinity
# POSTGRES_PASSWORD=
# ===========================================
# DATA PERSISTENCE (Production only)
# ===========================================
# Host bind-mount path for /data inside the backend + scheduler containers.
# Holds trinity.db, log archives, agent file shares.
# Default: ./trinity-data (relative to compose file)
# Recommended for production: an absolute path on a persistent disk
# Example: TRINITY_DATA_PATH=/opt/trinity/data
TRINITY_DATA_PATH=
# Host path that gets read-only-mounted into the backend at /agent-configs/templates.
# Source of truth for agent template definitions consumed at agent creation.
# Default: ${PWD}/config/agent-templates (relative to compose file)
# Override only when running compose from a directory other than the repo root.
HOST_TEMPLATES_PATH=
# Size of each agent container's /tmp RAM-backed tmpfs (#1231). Format: <int>m
# or <int>g (e.g. 512m, 2g). noexec,nosuid are always applied — only the size
# is configurable. tmpfs counts against the agent's memory cgroup, so keep it
# bounded. Empty/invalid falls back to the default (512m). Existing agents pick
# up a change on recreate, not restart.
# Default: 512m
AGENT_TMP_SIZE=
# Pull-pilot routing (#946, experiment, default OFF). When true, the MCP server
# routes sequential agent→agent chat_with_agent calls through the durable async
# /task path instead of the synchronous /chat. Read by BOTH the mcp-server (the
# routing gate) and the backend (feature-flags observability) from this one key.
# Phase 2 PoC for pull/work-stealing coordination (Epic #1045, umbrella #1081).
# Default: false
MCP_AGENT_CHAT_PULL_ENABLED=false
# Pull / work-stealing pilot (Epic #1045, umbrella #1081 — Phases 0-3, dark).
# PULL_MODE_PILOT_AGENTS: comma-separated agent names opted into the agent-side
# pull worker pool (backend-only, process-env → needs a backend restart, and the
# agent recreated so the worker is injected). Empty = pull fully dark (default).
# MAX_REDELIVERY: lease-reaper poison-task cap — re-deliveries of an expired pull
# lease before the row is poison-parked to the operator queue. Default: 3.
PULL_MODE_PILOT_AGENTS=
MAX_REDELIVERY=3
# ===========================================
# OPENTELEMETRY CONFIGURATION (Optional)
# ===========================================
# Enable OpenTelemetry metrics export from Claude Code agents
# Set to 1 to enable, 0 to disable (default: enabled)
OTEL_ENABLED=1
# OTEL Collector endpoint (only used when OTEL_ENABLED=1)
# Default points to Docker service name for in-network access
OTEL_COLLECTOR_ENDPOINT=http://trinity-otel-collector:4317
# Exporter configuration (usually no need to change)
OTEL_METRICS_EXPORTER=otlp
OTEL_LOGS_EXPORTER=otlp
OTEL_EXPORTER_OTLP_PROTOCOL=grpc
# Metrics export interval in milliseconds (default: 60 seconds)
OTEL_METRIC_EXPORT_INTERVAL=60000
# ===========================================
# CONTAINER TELEMETRY (Optional, #1096)
# ===========================================
# GET /api/telemetry/containers serves a short-TTL, background-refreshed
# cache so the request path never blocks on Docker (the endpoint is
# non-blocking out of the box — these knobs are optional tuning overrides).
# Like OTEL_SAMPLE_RATE, they are read via os.getenv with safe defaults and
# are NOT wired into compose by default; to tune on a standalone prod
# deployment, add them to the backend service's environment.
# Seconds a cached container-stats payload stays "fresh" before a background
# refresh is scheduled (default: 10; "0" disables the cache → every request
# schedules a refresh and serves stale, still non-blocking).
TELEMETRY_CONTAINER_STATS_TTL=10
# Max concurrent Docker stat fetches during a background refresh
# (default: 16; clamped to 1..64). Sized to the fleet so a cold refresh is
# ~one Docker-sample window rather than ceil(N/4) windows.
TELEMETRY_DOCKER_POOL_SIZE=16
# ===========================================
# DOCKER SOCKET GROUP (all runtimes)
# ===========================================
# Issue #874 / #1131: backend runs as UID 1000 (non-root). It joins the group
# that owns /var/run/docker.sock via compose's `group_add` so it can talk to
# the Docker daemon. That GID is whatever a container sees on the socket:
# a Linux bind mount → the host `docker` group (GID 999 on Debian/Ubuntu,
# ~991 on RHEL/Fedora, 990 on Arch); Docker Desktop / Colima / Rancher →
# the root group (GID 0). Docker Desktop does NOT ignore group_add, so the
# value matters on every runtime (this was the #1131 regression).
#
# Leave blank: `./scripts/deploy/start.sh` auto-detects on first run by probing
# the GID a throwaway container sees on the socket (correct on every runtime,
# including Docker Desktop / Colima / rootless where it is 0), with a host
# `getent group docker` only as the offline Linux fallback. It writes the value
# back to .env. Set explicitly to override (e.g. a non-default socket group;
# Docker Desktop = 0). Compose still falls back to 999 if .env carries no value,
# so Debian/Ubuntu hosts work even without start.sh.
DOCKER_GID=
# ===========================================
# CANARY INVARIANT HARNESS (Optional, staging/dev)
# ===========================================
# Continuous orchestration-invariant watcher (CANARY-001 / Issue #411).
# Set to 1 on staging/dev to run the 5-min check loop. Production stays 0.
CANARY_ENABLED=0
# Slack incoming webhook URL for canary green→red transitions.
# Get from: https://api.slack.com/apps → your app → Incoming Webhooks → Add
# The URL is the credential — anyone with it can post to that one channel.
# Unset = canary cycles run silently (violations still persisted to DB).
CANARY_SLACK_WEBHOOK_URL=
# ===========================================
# FIRE-AND-FORGET DISPATCH (Optional, #1083)
# ===========================================
# When true, eligible autonomous turns ({schedule, webhook}) are dispatched to
# the agent with a 202 accept and finalized via the result-callback endpoint, so
# a wedged turn holds zero backend coroutine/slot beyond its lease. Default
# false. Safe to flip early: a non-202 agent response (old image / non-Claude
# runtime) falls back to today's synchronous handling. Backend-only (the
# scheduler doesn't dispatch to agents). Requires a base-image rebuild + rolled
# agent containers for the 202 path to engage.
DISPATCH_ASYNC=false
# ===========================================
# DISPATCH CIRCUIT BREAKER (Optional, RELIABILITY-007 / #526)
# ===========================================
# GLOBAL gate for the producer-side per-agent dispatch breaker that fast-fails
# NEW executions (HTTP 503) when an agent is auth-dead, instead of poisoning the
# persistent backlog. Two-tier gating: this global flag AND the per-agent toggle
# (agent_ownership.circuit_breaker_enabled, set via
# PUT /api/agents/{name}/circuit-breaker) must BOTH be on for the breaker to
# engage. Default false — with this off, the owner-facing UI toggle silently
# no-ops. Backend-only.
DISPATCH_BREAKER_ENABLED=false
# ===========================================
# Re-delivery governor — correlated-failure / thundering-herd controls (#1085)
# ===========================================
# Master switch for the BACKEND re-delivery controls (rate caps + shared-cause
# pause) on the #1083 fire-and-forget callback path. Default false — inert until
# flipped; a flip back is the whole rollback. Agent-side jitter ships unflagged.
# Everything here is fail-open (a Redis blip degrades to allow/no-op, never to
# blocking or dropping a terminal). No DB schema change — all state is Redis.
REDELIVERY_GOVERNOR_ENABLED=false
# Fleet-wide re-delivery cap (~10/s) and per-agent cap, each a rolling window.
REDELIVERY_FLEET_LIMIT=600
REDELIVERY_FLEET_WINDOW_SECONDS=60
REDELIVERY_AGENT_LIMIT=20
REDELIVERY_AGENT_WINDOW_SECONDS=60
# Shared-cause detector: this many DISTINCT agents posting an AUTH/BILLING
# terminal within the window arms a fleet-wide re-delivery pause.
CORRELATED_FAILURE_THRESHOLD=20
CORRELATED_FAILURE_WINDOW_SECONDS=120
# Pause flag TTL (auto-expiry — no explicit unpause). Kept under the lease
# window (timeout + SLOT_TTL_BUFFER, buffer=300) so a held row isn't failed.
CORRELATED_PAUSE_TTL_SECONDS=300
# Retry-After hint (seconds) on a 503 while paused/throttled (jittered ±50%).
REDELIVERY_PAUSE_RETRY_AFTER_SECONDS=30
# --- Frontend build-time (Vite) knobs — baked into the image at `npm run build` (#1116/#1489) ---
# Honored by the PRODUCTION frontend build (docker-compose.prod.yml) only; rebuild the
# frontend image to apply. The dev Vite server ignores compose build args — for dev, set
# these in src/frontend/.env instead.
# WARNING: every VITE_* var is compiled into the world-readable client bundle. Never put a
# secret in a VITE_* var.
#
# In-app "report a bug" widget (HelpChatWidget). Set to false to fully disable in-app bug
# reporting (privacy-strict / air-gapped installs) — the Bug/Feature/Feedback tabs disappear.
VITE_BUG_REPORTING_ENABLED=true
# Repoint bug reports to a self-hosted intake endpoint (default: hosted AbilityAI intake).
# NOTE: repointing to a non-default host ALSO requires adding that host to the CSP
# `connect-src` in BOTH src/frontend/security-headers.conf (prod nginx) and
# src/frontend/vite.config.js (dev) — otherwise the browser blocks the fetch. Full CSP
# parameterization is an umbrella #1485 follow-up.
VITE_BUG_INTAKE_URL=https://intake.abilityai.dev/v1/report-bug
# ============================================================
# First-Run Default System Seed (trinity-enterprise#124)
# ============================================================
# On a genuinely fresh install, Trinity auto-deploys a bundled starter fleet
# (config/manifests/default-system.yaml) once, right after first-time setup.
# Leave empty for the bundled default. Set to a path (bind-mount the file into
# the backend container) to seed a private distribution manifest instead, or
# set to `disabled` to skip first-run fleet seeding entirely.
TRINITY_DEFAULT_SYSTEM_MANIFEST=