Repository navigation
Expand file tree
/
Copy path.env.example
More file actions
274 lines (251 loc) · 13.5 KB
/
Copy path.env.example
File metadata and controls
274 lines (251 loc) · 13.5 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
# Deploy-only variables — not read by the app (Next.js / LangGraph).
# Only consumed by docker-compose.yml + the `caddy` container. Skip
# these for Mac-local `pnpm dev`; set them when deploying per
# docs/DEPLOY.md.
#
# Image tag (see docs/CI.md § Tag strategy — main → :latest, dev → :beta-<sha>).
# REQUIRED for `docker compose up`: `app:` service uses `image: ${IMAGE}`
# and `build:` together. VPS: set to `ghcr.io/<owner>/langgraph-app:latest`
# (so `docker compose pull app && docker compose up -d` uses the GHCR
# image, never builds from source). Local dev: set to `langgraph-app`
# (local tag — `docker compose up` builds from source on first run,
# reuses the cached image afterwards).
IMAGE=
# Public domain Caddy should serve. Becomes the `{$CADDY_DOMAIN}` host
# in the Caddyfile; Caddy substitutes it at startup. Required for the
# `caddy` service — compose errors out if missing.
CADDY_DOMAIN=
# Postgres service credentials (compose reads these to provision the
# `postgres` container; the `app` container's DATABASE_URL is built from
# them via ${VAR} substitution in compose's environment: block).
POSTGRES_USER=
POSTGRES_PASSWORD= # generate with: openssl rand -hex 24
POSTGRES_DB=
# Backend (LangGraph server) — provider is OpenAI-compatible.
OPENAI_API_KEY=
# Optional override; defaults to gpt-4o-mini
OPENAI_MODEL=
# Optional. Override the upstream base URL when pointing at an
# OpenAI-compatible endpoint (Azure, local proxy, third-party gateway, ...).
# Leave unset to use the default https://api.openai.com/v1.
OPENAI_BASE_URL=
# LangSmith tracing (optional)
LANGSMITH_TRACING=false
LANGSMITH_API_KEY=
LANGSMITH_PROJECT=
# Public site URL — used by app/robots.ts, app/sitemap.ts, and
# app/(landing)/layout.tsx (metadataBase + OG image). Server-only.
# Leave unset to fall back to the ai.firetable.tech default.
SITE_URL=
# Frontend (Next.js proxy to LangGraph)
LANGGRAPH_API_URL=http://localhost:2024
# Forwarded as `x-api-key`. Leave blank for local dev.
LANGCHAIN_API_KEY=
# Graph id. Must match a key under "graphs" in langgraph.json.
LANGGRAPH_ASSISTANT_ID=agent
# Optional. Bypasses the /api proxy and lets the browser hit LangGraph
# directly (e.g. behind Cloudflare Tunnel). Leave unset to route
# everything through the Next.js proxy on the current host. Read in
# the browser via window.__CONFIG__.
# Distinct from the server-side LANGGRAPH_API_URL above so deploys where
# the proxy destination and the public browser URL diverge (tunnels,
# split domains) keep both knobs.
LANGGRAPH_PUBLIC_URL=
# Postgres — local dev: postgresql://USER@localhost:5432/DBNAME (no password).
# Production: postgresql://USER:PASSWORD@HOST:5432/DBNAME
DATABASE_URL=
# Test database URL — used by `pnpm test` (vitest + NODE_ENV=test reads .env.test).
DATABASE_URL_TEST=
# Better Auth (required) — 32-byte hex secret for cookie signing.
# Generate with: openssl rand -hex 32
BETTER_AUTH_SECRET=
# Base URL of the Next.js app. Used for OAuth callback URL construction.
BETTER_AUTH_URL=http://localhost:3000
# GitHub OAuth App — https://github.com/settings/developers
# Callback URL: ${BETTER_AUTH_URL}/api/auth/callback/github
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
# Google OAuth Client — https://console.cloud.google.com/apis/credentials
# Callback URL: ${BETTER_AUTH_URL}/api/auth/callback/google
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
# Resend — https://resend.com/api-keys (free tier: 100 emails/day).
# Sender domain must be verified in production; dev can use onboarding@resend.dev.
RESEND_API_KEY=
RESEND_FROM_EMAIL=onboarding@resend.dev
# Jina AI — https://jina.ai/reader
# Comma-separated API keys. The pool picks one at random per request;
# 401/403 responses remove that key from the pool and retry.
# Get free keys at https://jina.ai/reader after sign-up.
# - REQUIRED for the `search_web` tool (s.jina.ai returns 401 without auth).
# The tool is not registered in the agent's list when this is empty.
# - OPTIONAL for the `fetch_url` tool — r.jina.ai accepts unauthenticated
# requests on the free tier (lower rate limit). A key just lifts the
# limit; absence does NOT disable the tool.
JINA_API_KEYS=
# Alchemy RPC — https://dashboard.alchemy.com/
# Server-only. Used by app/api/alchemy/[...path] to proxy JSON-RPC
# requests (browser-side wallet portfolio view) AND by the
# `get_NFT_holdings` crypto tool (server-side Alchemy NFT API call).
# When unset, `get_NFT_holdings` is not registered in the agent's
# tool list (the model just doesn't see it); the JSON-RPC proxy
# stays up but returns 401 on every request.
# Never reaches the browser.
ALCHEMY_API_KEY=
# Optional. Comma-separated Alchemy network slugs the proxy will REJECT
# (turn-off list). The proxy accepts every network in the static catalog
# (lib/alchemy/networks.ts) by default — set this to disable specific
# chains in production (e.g. testnets). Leave empty to enable all.
ALCHEMY_DISABLED_NETWORKS=
# fal.ai — https://fal.ai/dashboard/keys
# Used by the `regenerate_image` tool (backend/tool/regenerate-image.ts)
# which the chatAgent invokes when the user asks for new images on the
# canvas. Gated on the key per rule #10 — without it the tool stays
# unregistered and the canvas flow falls back to upload-only. Free tier
# credits cover development; check the dashboard for the per-model
# rate once you wire it in production.
FAL_KEY=
# WalletConnect / Reown projectId — https://dashboard.reown.com
# Bundled into the browser (via window.__CONFIG__ in app/layout.tsx) on
# purpose: it's a public dapp identifier, not a secret (Reown docs:
# "this is a public identifier"). The free tier covers 100k connections
# / month. Required for WalletConnect-based wallets (binanceWallet,
# bitgetWallet) to expose their mobile-QR fallback; injected wallets
# (MetaMask, Coinbase, Rainbow, Safe) work without it.
WALLET_CONNECT_PROJECT_ID=
# Deno Deploy — https://console.deno.com/
# Server-only. Used by the code agent's `execute_code` tool via the
# @deno/sandbox SDK to run untrusted code in a Firecracker microVM.
# Supported languages: TypeScript / JavaScript (Deno `eval`) and
# Python (`python3 -c`, stdlib only — sandbox image ships CPython 3.13).
# Create an access token at
# https://console.deno.com/ → Sandbox tab → access tokens.
# - If you create an ORGANIZATION token (starts with ddo_*), you only
# need DENO_DEPLOY_TOKEN.
# - If you create a PERSONAL token (starts with ddp_*), you also need
# DENO_DEPLOY_ORG — your org slug from the console URL.
# When unset, `execute_code` is not registered in the agent's tool
# list — the model can still propose code via `write_code`, and on
# Run the model surfaces a graceful fallback. Free tier: 1M requests
# / 15h CPU / month — see https://deno.com/deploy/pricing.
DENO_DEPLOY_TOKEN=
DENO_DEPLOY_ORG=
# Cloudflare R2 — https://dash.cloudflare.com/?to=/:account/r2
# Server-only. Backs chat attachments (issue #12, design at
# docs/ATTACHMENTS.md). 5 required vars + 2 optional.
# Create an API token at dash.cloudflare.com → R2 → "Manage R2 API
# Tokens" → "Create API token" with Object Read & Write scoped to
# the bucket. R2_ACCOUNT_ID is the account id from the R2 dashboard URL.
# When any of R2_ACCOUNT_ID / R2_ACCESS_KEY_ID / R2_SECRET_ACCESS_KEY /
# R2_BUCKET / R2_PUBLIC_BASE_URL is unset, every route under
# /api/attachments/* returns 503 with
# `{ "error": "attachments_not_configured", "message": ... }`.
# Mirrors the DENO_DEPLOY_TOKEN / ALCHEMY_API_KEY lazy-register
# pattern. The bucket also needs a CORS rule allowing PUT from
# <YOUR_PUBLIC_DOMAIN> — see docs/ATTACHMENTS.md.
R2_ACCOUNT_ID=
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=
R2_BUCKET=
R2_PUBLIC_BASE_URL=
# Optional. Per-file size cap in bytes; default 10485760 (10 MiB).
R2_MAX_BYTES=10485760
# Optional. User-scoped R2 root for chat attachments, KB ingest derived
# objects (page screenshots, embedded images), and avatars. Default
# "u". Override only if you need to namespace a multi-tenant bucket or
# migrate to a new prefix — every R2 key in the app routes through
# `lib/r2/keys.ts` and reads this getter, so changing it here is the
# single source of truth. Existing objects in R2 won't be reachable
# after a rename (no migration script ships).
R2_FOLDER_USER=u
# Optional. Comma-separated MIME allow-list used by BOTH the chat
# composer (`R2AttachmentAdapter.accept`) and the KB AddDoc dialog
# (read via window.__CONFIG__). The presign route
# (`app/api/attachments/presign/route.ts`) is the single gate — server
# rejects anything not in this list with 400, so the picker filter and
# the server agree on the same set. Safe to expose: public config,
# not a secret.
#
# KB-owned types below: PDF + markdown + plain + image/* + Office Open
# XML (DOCX/XLSX/PPTX). The KB AddDoc dialog flow routes every one of
# these through kbAgent for parsing + chunking. The chat composer
# also accepts them — extractAllPdfParts (now broadened to all KB
# types) stamps a kb_ref sibling on the file part so the router
# dispatches kbAgent on the next pass.
R2_ALLOWED_CONTENT_TYPES=image/png,image/jpeg,image/webp,application/pdf,text/markdown,text/plain,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.openxmlformats-officedocument.presentationml.presentation
# Attachments feature toggle — gates the assistant-ui `<Composer />`
# attachment UI AND the Settings → Account avatar upload (both backed by
# R2). Default false; flip to "true" once every required R2_* above is
# set. Frontend and backend must be in sync: if this stays "false" the
# composer hides the attachment widget + the avatar upload is disabled,
# but routes still answer when called. Read in browser via window.__CONFIG__.
ATTACHMENTS_ENABLED=false
# Observability — how long CapturedSpan rows are kept before retention
# deletes them. Must be a positive integer. Default 30 days.
OBSERVABILITY_RETENTION_DAYS=30
# Memory (LangGraph long-term store). Read once at module load (NFR-004).
# Missing / empty / non-numeric values fall back to the defaults below.
# KEEP_RECENT — single knob for thread summarization. Controls all
# three at once: BATCH SIZE per compress pass (KEEP_RECENT human
# turns), TRIGGER CADENCE (fires every KEEP_RECENT new turns), and
# RECENT FLOOR (the last KEEP_RECENT turns are never compressed).
# Default 10. Must be a non-negative integer.
# BUFFER_RECENT_TURNS — recent human turns to retain in raw messages array
# even after they are covered by summaries, preventing sudden context loss.
# Default 5. Must be a non-negative integer.
# PROFILE_MAX_BYTES — hard cap on serialized profile doc size. save_memory
# rejects patches that would push the doc over this limit.
# Default 8192.
MEMORY_THREAD_SUMMARY_KEEP_RECENT=10
MEMORY_THREAD_MESSAGES_BUFFER_RECENT_TURNS=5
MEMORY_PROFILE_MAX_BYTES=8192
# Provider encryption KEK — AES-256-GCM key that wraps every entry in
# `provider.apiKeys[].encryptedKey`. REQUIRED to start the server; the
# admin UI returns 503 on first request if this is missing or malformed
# (we refuse to silently fall back to "no encryption" mode).
# Generate once with: openssl rand -hex 32
# Set-and-forget: rotating it is out of scope for this project (would
# require a one-shot re-encryption script over every row in provider.apiKeys).
LLM_KEY_ENCRYPTION_KEY=
# Bootstrap admin email. On signup, if the user's email matches this value
# (case-insensitive), their role_id is set to 'admin'. Idempotent — leave
# set forever; only the FIRST signup with this email is promoted. To add
# a second admin later, use the admin UI role management tab.
INITIAL_ADMIN_EMAIL=
# KB retrieval knobs (issue #13 v3). All server-only — backend tool
# surface and mention resolver read these via lib/kb/env.ts. Defaults
# reflect the community survey in `.claude/13-kb-v3.md`:
# LangChain EnsembleRetriever / LlamaIndex QueryFusionRetriever /
# Haystack DocumentJoiner — per-leg 50, fused 5–10; Mastra = 3;
# Mastra + Vercel AI SDK RAG chunks = 512–1024 tokens.
# KB_MENTION_TOPK_DEFAULT — chunks per single @-mention. 5.
# KB_MENTION_TOPK_MAX — per-mention upper bound when user overrides. 20.
# KB_MENTION_TOKEN_BUDGET — total token cap across multi-mention turns.
# Per-mention topK is rebudgeted as
# ceil(BUDGET / (CHUNK_MAX_CHARS/4 * mentions)).
# 8192.
# KB_HYBRID_TOPK_DEFAULT — fused topK for the search_kb tool. 8.
# KB_HYBRID_TOPK_MAX — fused topK upper bound for the tool. 20.
# KB_CHUNK_MAX_CHARS — per-chunk truncation before stuffing into
# the LLM prompt; ~512 tokens at 4 chars/token.
# 2000.
# KB_RERANK_MIN_SCORE — minimum rerank relevance score threshold for filtering.
# Chunks below this score are filtered out. 0.4.
# KB_HYBRID_ENTRY_TOPK — per-leg entry cap (kw/vec/tag/rel/entity) before
# RRF fusion. 50.
# KB_GRAPH_HOPS — graph traversal hop count for the B-phase entity-leg
# expandFromEntities step. 2.
KB_MENTION_TOPK_DEFAULT=5
KB_MENTION_TOPK_MAX=20
KB_MENTION_TOKEN_BUDGET=8192
KB_HYBRID_TOPK_DEFAULT=8
KB_HYBRID_TOPK_MAX=20
KB_CHUNK_MAX_CHARS=2000
KB_RERANK_MIN_SCORE=0.4
KB_HYBRID_ENTRY_TOPK=50
KB_GRAPH_HOPS=2
# Decision Model (Route B - Optional generic env config; Route A via DB Provider is preferred)
# If neither DB kind: "decision" nor DECISION_MODEL_API_KEY is configured, router falls back to chat model.
DECISION_MODEL_API_KEY=
DECISION_MODEL_BASE_URL=
DECISION_MODEL_NAME=