Chloei is a Next.js 16 chat app backed by Vercel AI Gateway. It currently exposes a curated model selector that defaults to Qwen 3.7 Max and also includes Kimi K2.6 and MiMo V2.5 Pro, routes Research mode to Qwen 3.7 Max with a dedicated Deep Research instruction template, and offers private Blob-backed file attachments, local code execution, optional Tavily retrieval, optional governed Upstash Search knowledge retrieval, optional Inngest jobs, normalized finance data, SEC/EDGAR filing retrieval, optional Financial Modeling Prep MCP tools, optional Mem0 long-term memory, and Better Auth email/password authentication with PostgreSQL-backed users and sessions.
- Node.js 24.x
- pnpm 10.32.1
- PostgreSQL 16 for local auth, sessions, thread storage, and rate limiting
pnpm install
cp .env.example .env.local
# Fill DATABASE_URL, BETTER_AUTH_SECRET, BETTER_AUTH_URL, and AI_GATEWAY_API_KEY.
# Cursor Cloud sets up native PostgreSQL; local machines must start PostgreSQL first.
pnpm migrate
pnpm devAdd AI_GATEWAY_API_KEY to .env.local before starting the app. Add TAVILY_API_KEY if you want Tavily search and extract tools. Add FMP_API_KEY if you want the curated finance tools. Add the Mem0 memory variables documented below if you want long-term memory. The app runs on http://localhost:3000.
To enable auth locally, provision PostgreSQL before running pnpm migrate and add:
DATABASE_URLAUTH_DATABASE_URLif Better Auth should use a different database from app dataBETTER_AUTH_SECRETBETTER_AUTH_URL=http://localhost:3000BETTER_AUTH_TRUSTED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000when you need multiple allowed originsBETTER_AUTH_COOKIE_DOMAIN=chloei.aiwhen you need shared sessions across trusted subdomains
To mirror Vercel Development locally, install the Vercel CLI, run vercel login, approve the device-login URL, verify the project link in .vercel/project.json, then run vercel env pull .env.local --yes. The pull overwrites .env.local, so preserve any local database/auth overrides you need.
For browser smoke tests on a fresh machine, install Playwright's browser dependencies once:
pnpm exec playwright install --with-deps chromiumpnpm dev: start the Next.js dev serverpnpm migrate: run both Better Auth and app-storage migrationspnpm auth:migrate: apply Better Auth schema changes to PostgreSQLpnpm app:migrate: apply app storage schema changes to PostgreSQLpnpm build: build the production apppnpm build:standalone: build the standalone production server used bypnpm startand mock smoke testspnpm start: run the standalone production server from.next/standalone/server.jspnpm bundle:budget: check built static JavaScript chunks against bundle budgetspnpm bundle:report: report built static JavaScript chunk headroom and largest first-load routespnpm desktop:dev: start the Electron app against a local Next.js dev serverpnpm desktop:build: build Next.js and prepare the packaged desktop server bundlepnpm desktop:pack: create an unpacked local Electron app for inspectionpnpm desktop:smoke: launch Electron in mock-auth mode and verify the desktop shell contractpnpm desktop:dist:mac: build macOS desktop artifactspnpm test: run regression testspnpm test:smoke: run opt-in Playwright browser smoke tests againstSMOKE_BASE_URLpnpm test:smoke:memory: run opt-in authenticated memory smoke tests againstSMOKE_BASE_URLpnpm test:smoke:mock: run the credential-free mocked Playwright smoke test used by CIpnpm test:smoke:mock:build: build the standalone server, then run the credential-free mocked smoke testpnpm eval:finance: run the finance benchmark harness in fixture modepnpm eval:finance -- --mode live: run the live finance-agent harness against AI Gatewaypnpm eval:finance:live: run the live public-markets finance acceptance suitepnpm eval:finance:grade: grade finance benchmark outputspnpm mem0:cleanup-smoke: delete authenticated memory smoke artifacts forSMOKE_EMAILpnpm mem0:smoke: run a direct add/search/delete Mem0 REST smoke testpnpm lint: run blocking ESLint checkspnpm lint:fix: apply autofixable ESLint changespnpm format: write Prettier formatting changespnpm format:check: verify formatting without writing changespnpm typecheck: run TypeScript checks
- Sync local secrets when needed with
vercel env pull .env.local, then remove any stale keys the app no longer uses. - Run
pnpm test,pnpm lint,pnpm typecheck, andpnpm build. - Open a pull request to
mainand wait for the requiredchecksandVercelstatuses. - Smoke test the preview deployment: sign in, confirm models load, send one prompt, verify thread persistence, and run
SMOKE_BASE_URL=<preview-url> pnpm test:smoke:memorywhen Mem0 is enabled. - Merge to
mainafter the preview passes, then confirm production is aliased to chloei.ai. - Run one authenticated production smoke test: sign in, load models, send a prompt, verify an existing thread still reopens cleanly, and run the memory smoke against
https://chloei.ai.
Managed integration rollout, rollback, and smoke-test steps live in docs/managed-integrations-rollout.md. Public-markets finance answer quality checks live in docs/finance-research-quality.md.
Chloei ships a macOS desktop shell through Electron. The shell starts a local Next.js server on 127.0.0.1 using a random available port, then opens that local origin in a locked-down Electron window. Production desktop builds use Next.js output: "standalone" and package the traced server files plus .next/static. Windows users should use the hosted web app.
Desktop development:
pnpm desktop:dev
pnpm desktop:smokeDesktop packaging:
pnpm desktop:build
pnpm desktop:pack
pnpm desktop:dist:macThe desktop app does not bundle server secrets. For local packaged testing, provide runtime server environment variables through the OS environment or a desktop.env file in Electron's app data directory:
- macOS:
~/Library/Application Support/Chloei/desktop.env
Use the same key/value format as .env.local. The Electron shell always overrides BETTER_AUTH_URL, BETTER_AUTH_TRUSTED_ORIGINS, HOSTNAME, and PORT for the local desktop server, and clears BETTER_AUTH_COOKIE_DOMAIN so localhost sessions work correctly.
Desktop release builds are configured in .github/workflows/desktop-release.yml. CI builds separate macOS Apple Silicon and macOS Intel artifacts, each with its own auto-update channel so split macOS builds do not overwrite each other's update metadata. Local macOS desktop builds skip signing by default because this repo often lives in a cloud-synced workspace; set CHLOEI_DESKTOP_SIGN=1 to opt into local certificate signing from a normal local checkout. CI macOS signing/notarization uses CSC_LINK, CSC_KEY_PASSWORD, APPLE_API_KEY_P8, APPLE_API_KEY_ID, and APPLE_API_ISSUER. Draft GitHub release publishing is available through the workflow's publish input.
The Trading Desk (/trading-desk) runs a full multi-agent equity analysis powered by the TauricResearch/TradingAgents framework: market / sentiment / news / fundamentals analysts, a bull-vs-bear research debate, a trader, a three-way risk committee, and a portfolio manager that returns a Buy / Overweight / Hold / Underweight / Sell decision. The page streams each agent's status, every report section, the debates, and the final call live.
TradingAgents is Python + LangGraph and a run takes ~1–3 minutes, so it cannot run inside Next.js/Vercel functions. It runs as a separate FastAPI sidecar under tradingagents-service/. Chloei calls that service server-side from /api/trading-desk/analyze (auth + rate limited), transforms its SSE into NDJSON, and streams it to the browser. By default the service routes every agent's LLM calls through the same Vercel AI Gateway key the chat app uses.
Run it locally:
# 1. Start the sidecar (see tradingagents-service/README.md for details)
cd tradingagents-service
cp .env.example .env # set AI_GATEWAY_API_KEY; or TRADINGAGENTS_MOCK=1 to try it keyless
docker compose up --build # serves http://localhost:8000
# 2. Point Chloei at it (in the app's .env.local), then `pnpm dev`
# TRADINGAGENTS_SERVICE_URL=http://localhost:8000The Trading Desk is reachable from the chat sidebar ("Trading desk") or directly at /trading-desk. Set TRADINGAGENTS_ENABLED=false to hide it. Service wiring is documented in .env.example (TRADINGAGENTS_*); the server client lives in src/lib/server/trading-agents/, the routes in src/app/api/trading-desk/, and the UI in src/components/trading-desk/.
TradingAgents is exposed through two surfaces, all backed by the same sidecar:
- Trading Desk page (
/trading-desk) — every run posts toPOST /api/trading-desk/jobsand executes as a background job through Chloei's async-jobs system (the sharedagent_jobtable + Inngest, with an inline fallback when Inngest is unconfigured), polled viaGET /api/jobs/{jobId}. This survives a dropped connection — ideal for long deep-mode runs. RequiresDATABASE_URL(andpnpm migrate); Inngest is optional. The page also still has a live-streaming endpoint (POST /api/trading-desk/analyze) available via thestart()hook method if you want a no-database streaming mode. - Chat tool — the chat agent can call a
trading_analysistool mid-conversation (e.g. "should I buy NVDA?") and fold a compact decision summary into the thread. Registered insrc/lib/server/llm/ai-sdk-trading-agents-tools.ts.
.env.example documents the supported environment variables. Required variables are:
AI_GATEWAY_API_KEY: required to enable/api/modelsand/api/agentDATABASE_URL: PostgreSQL connection string for Better AuthAUTH_DATABASE_URL: optional Better Auth database override; falls back toDATABASE_URLBETTER_AUTH_SECRET: Better Auth signing secretBETTER_AUTH_URL: public app origin used by Better Auth, such ashttp://localhost:3000; on Vercel previews it can be omitted so the deployment URL is inferred automaticallyBETTER_AUTH_TRUSTED_ORIGINS: optional comma-separated list of additional allowed originsBETTER_AUTH_COOKIE_DOMAIN: optional shared parent cookie domain for cross-subdomain sessions; keep this production-only when preview deployments usevercel.apphosts
Optional variables let you override the built-in safe defaults for message limits, response timeout, rate limiting, concurrent requests per client, rate-limit storage, and Next.js request body limits.
TAVILY_API_KEY: enables Tavily search and extract callable tools for chat requestsUPSTASH_SEARCH_REST_URL,UPSTASH_SEARCH_REST_TOKEN,UPSTASH_SEARCH_INDEX,UPSTASH_DISABLE_TELEMETRY=1: enable governed internalknowledge_searchINNGEST_EVENT_KEY,INNGEST_SIGNING_KEY,INNGEST_DEV: enable/api/inngestand async job orchestrationBLOB_READ_WRITE_TOKEN: enables private Blob upload/download and private agent artifact URLsAGENT_KNOWLEDGE_SEARCH_ENABLED,AGENT_ASYNC_REPORTS_ENABLED,AGENT_TELEMETRY_RECORD_IO,AGENT_FINANCE_WORKFLOWS_ENABLED: feature gates; defaults are off unless explicitly set or synced through Edge ConfigPOSTHOG_ANALYTICS_ENABLED,POSTHOG_ANALYTICS_INTERNAL_USERS_ONLY,NEXT_PUBLIC_POSTHOG_ANALYTICS_ENABLED,NEXT_PUBLIC_POSTHOG_HOST,NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN: enable privacy-safe PostHog product analytics. Browser autocapture, pageviews, surveys, heatmaps, feature-flag requests, and replay are disabled; server events use hashed user ids, no GeoIP, and no PostHog person profiles. Server-side capture is restricted toAGENT_INTERNAL_USER_EMAILS/AGENT_INTERNAL_USER_EMAIL_DOMAINSunlessPOSTHOG_ANALYTICS_INTERNAL_USERS_ONLY=false.SENTRY_DSN,NEXT_PUBLIC_SENTRY_DSN,SENTRY_TRACES_SAMPLE_RATE,NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE: Sentry error/performance telemetry with PII scrubbing and no replay samplingFMP_API_KEY: enables curated Financial Modeling Prep MCP tools for structured finance dataFRED_API_KEY: enables macro/rates series through the normalizedfinance_datatoolSEC_API_USER_AGENT: identifies Chloei for SEC public company-facts requestsAGENT_FINANCE_TOOL_MAX_STEPS: max tool steps for finance-analysis runs, defaulting to 20MEMORY_PROVIDER=mem0: enables long-term memory through a separate Mem0 REST service. The default isdisabled.MEM0_API_URL: Mem0 REST API origin, defaulting tohttp://localhost:8888. Usehttps://api.mem0.aifor Mem0 Platform keys.MEM0_API_KEY: Mem0 key value. Self-hosted OSS usesX-API-Key; Mem0 Platform usesAuthorization: Token ....MEMORY_AGENT_ID,MEMORY_TOP_K,MEMORY_THRESHOLD,MEMORY_CONTEXT_MAX_CHARS, andMEMORY_COMMIT_MAX_CHARS: optional memory scoping, retrieval, and prompt/commit bounds. Use distinct agent ids per environment, such aschloei-local,chloei-development,chloei-preview, andchloei-production. The default retrieval threshold is0.1.
By default, Chloei enforces safe built-in agent limits even if you leave all optional AGENT_* env vars unset.
AGENT_RATE_LIMIT_STORE defaults to auto, which uses PostgreSQL when DATABASE_URL is configured and falls back to process memory for local/no-database runs. Allowed values: auto, postgres, memory.
src/app/(home)/page.tsx: app entry for the home screensrc/app/(auth)/sign-in/page.tsx: public sign-in screensrc/app/(auth)/sign-up/page.tsx: public sign-up screensrc/app/api/auth/[...all]/route.ts: Better Auth route handlersrc/app/api/agent/route.ts: streaming agent endpointsrc/app/api/models/route.ts: available-models endpointsrc/components/agent: chat UI, prompt form, markdown rendering, and session statesrc/lib/server: Better Auth config, PostgreSQL setup, runtime config, rate limiting, and model streamingsrc/lib/server/long-term-memory.ts: Mem0 REST client, retrieval formatting, and memory commit helpers
- The current model list is defined in
src/lib/shared/llm/models.ts. /,/api/agent, and/api/modelsrequire an authenticated Better Auth session.- Native
web_searchis available through AI Gateway alongside Tavily, FMP, and local code execution. knowledge_searchis for governed static/internal material only. Live financial facts stay routed throughfinance_data, SEC/FRED/FMP, Tavily, and AI Gateway web search.browser_researchis a retired live tool name retained only so historical thread records continue to parse.- PostHog is used for coarse product analytics and rollout analysis only when
analytics.posthog.enabledorPOSTHOG_ANALYTICS_ENABLEDis on, and server-side capture remains internal-user gated by default. Do not capture prompt text, model output, uploaded filenames, blob paths, document hashes, emails, account data, or credentials in PostHog events. POST /api/jobs/reportaccepts an optional client-generatedreportIdUUID for retry idempotency. Idempotency keys must use report/thread identifiers, not prompt text or document contents.finance_datanormalizes finance operations across FMP, SEC public company facts, and optional FRED macro/rates data. FMP MCP remains available as a migration compatibility path for chat-default runs.sec_filingsis available when a normal chat or Research request is inferred as finance-analysis work, covering SEC/EDGAR company lookup, filing search, full filing fetches, section extraction, table extraction, and targeted retrieval over filing text.- Long-term memory is opt-in and best effort. When
MEMORY_PROVIDER=mem0, Chloei retrieves user-scoped memories before each agent run and writes the latest user/assistant turn after meaningful completed or incomplete responses. Memory failures never block chat. Self-hosted OSS and Mem0 Platform are both supported throughMEM0_API_URL; Platform mode stores durable memories underuser_id, keeps Chloei's agent/thread scope in metadata for reliable cross-thread retrieval and cleanup, and temporarily falls back to the legacy per-userapp_idscope for old memories. - Run Mem0 separately with its REST API and dashboard. For a low-friction shared provider setup, configure Mem0's OpenAI-compatible LLM/embedder through Vercel AI Gateway with
OPENAI_BASE_URL=https://ai-gateway.vercel.sh/v1,OPENAI_API_KEY=$AI_GATEWAY_API_KEY,openai/text-embedding-3-smallfor embeddings, and a low-cost GPT model for memory extraction. Prefer private networking and HTTPS outside local development. - Finance eval fixtures, the live public-markets acceptance suite, live-agent eval mode, and GDPval-style harness scripts live in
evals/finance. - To share logins with another Chloei app, point both apps at the same Better Auth database and secret, set
BETTER_AUTH_COOKIE_DOMAINto the shared parent domain, and include every live subdomain inBETTER_AUTH_TRUSTED_ORIGINS. - Rate limiting and concurrency protection are PostgreSQL-backed when
DATABASE_URLis configured. Local/no-database runs fall back to in-memory limits unlessAGENT_RATE_LIMIT_STORE=postgresis set. - App storage does not self-initialize on live requests. Vercel deployments in this repo run
pnpm migratebeforenext build.
pnpm test:smoke runs Playwright against SMOKE_BASE_URL or starts the local dev server at http://localhost:3000. Set SMOKE_EMAIL and SMOKE_PASSWORD for an existing test account before running the live authenticated smoke test. Optional SMOKE_PROMPT and SMOKE_EXPECTED_TEXT let you tune the live prompt assertion.
pnpm test:smoke:mock runs a CI-safe authenticated chat flow with E2E_MOCK_AUTH=1, in-memory thread storage, and a deterministic mock agent response against the standalone production server. It requires .next/standalone/server.js, so run pnpm build:standalone first or use pnpm test:smoke:mock:build. It does not require Better Auth credentials, PostgreSQL, or AI provider API keys.
pnpm mem0:smoke requires MEMORY_PROVIDER=mem0, MEM0_API_URL, and MEM0_API_KEY; it writes a disposable marker, retries search for up to 45 seconds to allow Mem0 extraction/indexing to settle, and deletes the marker. pnpm test:smoke:memory requires SMOKE_EMAIL and SMOKE_PASSWORD; use SMOKE_BASE_URL=<preview-or-production-url> for preview and production verification. pnpm mem0:cleanup-smoke removes authenticated memory smoke threads and Mem0 memories for the configured smoke user while keeping the account available for recurring checks.