F
CRM-Build
Tech Stack
Current build runs entirely on Will's laptop (test environment). The migration target is the production Foundations portal.
Layers marked SAME require no code changes — just deployment. SWAP = env-var or config change. NEW = new infrastructure. DROP = retired.
Layer Current Build — Test Environment Migration Target — Foundations Portal
Interface & Auth
Frontend
Next.js 15 App Router · TypeScript · Tailwind CSS · Radix UI
D3.js for the architecture map. Lucide React icons. Playfair Display + DM Sans type.
⚠ All household data in localStorage — lost on a different machine.
Integrate into Foundations portal (React) NEW
Option A: Next.js micro-frontend as a sub-path (/crm). Option B: export components as npm package consumed by portal. Decision deferred.
Auth
Clerk (JWT with advisor_id claim)
JWT template "magfin". MAGFIN_ADMIN_ADVISOR_IDS env var for firm-admin workaround (JWT role claim pending). fiwealth.com + gmail both provisioned as firm-admin.
Portal SSO + Clerk bridge SWAP
Portal has existing SSO. ClerkIdentitySync adapts to portal identity provider. Multi-tenant: each advisor sees only their clients.
State
localStorage + React Context (HouseholdsProvider)
HouseholdsProvider merges seed data + localStorage patches. All edits are append-only diffs. Financials, notes, open loops all local.
⚠ Not multi-user — Matt and Will can't share live state.
PostgreSQL (multi-advisor, multi-tenant) NEW
All household, financial, and task state moves server-side. React Context becomes a read-through cache. HouseholdEdit audit log migrates to a SQL table.
AI & Intelligence
LLM
LiteLLM → OpenAI gpt-4o-mini LIVE
OPENAI_API_KEY set · LLM_MODEL=openai/gpt-4o-mini · MAGFIN_DEV_AUTH_FALLBACK=1. Single complete() entrypoint — all skills (client-brief, draft-email, query-client, generate-summary) go through this. Provider swap is one env-var change.
◆ MAGFIN_LLM_API_KEY (Anthropic) not set — not needed while OpenAI key is present. Switch to Claude: set LLM_MODEL=claude-sonnet-4-6 + MAGFIN_LLM_API_KEY=sk-ant-...
LiteLLM → Anthropic claude-sonnet-4-6 SWAP
No code changes. Set MAGFIN_LLM_API_KEY + LLM_MODEL in production secrets. Claude is the preferred production model (better citation compliance, lower hallucination rate on financial content).
STT
OpenAI Whisper API (via whisper_helper.py, port 9000)
Browser MediaRecorder → Next.js proxy → whisper_helper.py → OpenAI. Web Speech API fallback if helper is down. No local model — switched to API mode 2026-05-12 (local model + ffmpeg broke on Windows).
OpenAI Whisper API (same) SAME
Phase 2 (mobile): on-device Whisper for offline transcription (~25% battery/hr). Eliminates Twilio transcription fees for mobile advisors.
TTS
ElevenLabs (call disclosure audio) LIVE
ensure_unavailable_audio() fires at startup — generates the two-party consent disclosure clip played before Twilio recordings. ELEVENLABS_API_KEY set 2026-05-18 — clip will auto-generate on next backend startup.
ElevenLabs (same) SAME
Disclosure audio is compliance-critical — do not swap vendor without legal review. Key goes into production secrets manager.
Vector DB
ChromaDB local (SQLite-backed, port 8000)
Per-advisor-per-client partition isolation. ChromaIndexer in brain/indexer.py. QueryChunk → Citation roundtrip. Falls back gracefully on ChromaUnavailable (503 at route layer).
Managed Chroma / Pinecone / pgvector SWAP
ChromaIndexer is abstracted — swap the driver, keep the interface. pgvector on PostgreSQL is the lowest-friction option (one less service). Pinecone if query latency becomes a constraint.
Integrations
Orion
Orion Connect — live account balances LIVE
GET /api/integrations/orion/accounts — /v1/Portfolio/Accounts endpoint. Basic auth with per-advisor credentials. Surfaced in Settings > Integrations status tile.
Orion Wealth API (same) SAME
Credentials move to production secrets. Per-advisor mapping in advisor_aliases.py.
Zoom
Zoom OAuth + Webhook + AI Companion LIVE
Zoom app connected. HMAC-sig webhook → pending queue → pre-analyze → o3 deep analysis → email/fuzzy-name match → brain ingest. Backfill available.
Zoom AI Companion (same) SAME
No changes needed. Token DB at .local/zoom_tokens.sqlite moves to server secrets.
Phone
Twilio Voice SDK + TwiML + ElevenLabs LIVE
Inbound: two-party consent disclosure (TTS) + record. Outbound: REST dial + browser Voice SDK. Post-call: 3-pass AI analysis, sidecar JSON, advisor proposal review.
Twilio (same) → Phase 2: mobile Twilio Voice SDK SWAP
Phase 2: mobile app on Twilio Voice SDK (CallKit on iOS). Phase 3: LiveKit/Plivo only if economics force it.
Storage & Data
Brain Files
Markdown brain files (filesystem, AES-256-GCM encrypted)
BrainStore.write() is the only write path — auto-appends audit_log.md. Ed25519 Merkle manifests via Windows Task Scheduler. Per-advisor directory: brain/advisor_{id}/clients/{slug}/
⚠ Gitignored — not in git. Must be manually rsync'd to DO server after every new client add. Sequential startup sync (100ms gap) prevents 504 bursts.
Azure Blob Storage / S3 NEW
BrainStore write path stays the same — swap the filesystem driver for an object storage adapter. Encryption moves to server-side KMS. audit_log.md becomes a write-ahead log streamed to blob.
Prod Server
Digital Ocean droplet (147.182.245.224) · nginx · systemd LIVE
crm.projectopsai.com. nginx: all /api/* → FastAPI (8001), 12 Next.js exceptions → port 3000. systemd: crm-frontend + crm-backend (Restart=always). Deploy user: deploy@. Brain/recordings gitignored — manual rsync required.
⚠ Brain files + recordings not in git. Manual rsync to server on each new client add.
Managed hosting (same or scale-up) SAME
DO droplet sufficient for current scale. Brain files move to object storage (S3/Spaces) when advisor count exceeds single-server threshold.
Documents
SQLite WAL mode + FTS5 (brain/documents.db)
50 MiB cap. FTS5 full-text search with porter tokenizer. pypdf background extract → LiteLLM AI summary. File bytes stored in brain/advisor_{id}/documents/
PostgreSQL + object storage (Azure Blob / S3) NEW
SQLite becomes PostgreSQL. File bytes move to object storage. FTS5 → pg_trgm or pgvector semantic search. Same upload/list/download/search API surface.
Recordings
Sidecar JSON + .vtt files (filesystem)
recordings/advisor_{id}/{YYYY-MM}/*.json + *.vtt. Transcript-only policy: audio deleted from Twilio after transcription. No audio bytes ever served.
PostgreSQL metadata + blob .vtt storage NEW
Same transcript-only policy. .vtt files to object storage. JSON sidecars become DB rows. Recordings search endpoint stays identical.
Communications & Integrations
Phone
Twilio Voice SDK (browser) + Twilio REST LIVE
All three Twilio creds set (SID · Auth Token · From Number). TwiML inbound: two-party consent disclosure (14 states). Outbound dial + quick-brief prefetch. Phone lookup: Tier 0 override table → brain profile.md scan. IncomingCallBanner + twilioDevice singleton in browser.
◆ ELEVENLABS_API_KEY set 2026-05-18 — disclosure audio clip will auto-generate on next backend startup. Compliance gap resolved.
Twilio (same) + Phase 2: iOS/Android SDK SAME
Phase 2: Twilio Voice SDK on mobile with CallKit (iOS). On-device Whisper transcription. Eliminates browser dependency for calls. endConferenceOnExit=false stays — advisor leg bridged through conference. Eliminates Twilio recording/transcription fees.
Meetings
Zoom OAuth (per-advisor SQLite token store) + Webhook LIVE
HMAC-SHA256 webhook sig verify + 5-min replay window. transcript_completed → auto-pull .vtt. Auto-matcher scores transcripts against profile.md (0.88 auto-file / 0.45 suggest). 30s background loop.
◆ LIVE 2026-05-18: Zoom app connected, real meetings flowing into ZoomInbox. Suggestion panel now includes all households (prospects + clients), not just backend-scaffolded clients.
Zoom (same) SAME
Token store moves from SQLite → PostgreSQL. Auto-matcher and webhook pipeline stay identical. Zoom AI companion (Zocks replacement) routes through same .vtt pipeline.
Email
Draft-only — AI email drafts, advisor sends manually
Hard Rule #1: CRM never sends. postDraftEmail() → LLM drafts subject + body → advisor copies and sends. Email tone setting (professional-warm / formal / conversational) persisted in localStorage.
Outlook Mail.Read (read-only context) → draft NEW
Phase 1: read-only inbox — pull recent client emails for context. Phase 2 (deferred): bidirectional, segmented campaigns (consult Matt on workflow). Azure AD app reg needed either way.
Backend & Deployment
Backend
FastAPI + uvicorn · Python 3.12
Port 8001. uvicorn magfin.main:app --host 127.0.0.1 --port 8001 --reload. Pydantic v2 models. LiteLLM in magfin/llm/client.py. CORS allowlist + 60/min rate limit (in-process).
⚠ Rate limiter in-process — needs PostgreSQL or Redis for multi-worker deployments.
FastAPI modules → portal backend integration SWAP
Extract magfin as an installable pip package. Portal mounts /api/skills, /api/clients, /api/calls routes. Rate limiting moves to API gateway / load balancer. Multi-worker safe with PostgreSQL session store.
Deploy
Digital Ocean droplet · nginx · systemd LIVE
crm.projectopsai.com (147.182.245.224). nginx: /api/* → FastAPI 8001, 12 Next.js exceptions → 3000. systemd crm-frontend + crm-backend (Restart=always). Deploy: git pull → npm run build → kill next-start process.
◆ Vercel retired 2026-06-01 — all traffic now on DO. ngrok still used for local dev Zoom OAuth callback only.
Cloud (Azure App Service / AWS ECS) NEW
No ngrok. TLS at load balancer. Backend auto-scales. Frontend served from CDN (Vercel or Cloudflare). CI/CD pipeline replaces manual alias command. Secrets in Azure Key Vault / AWS Secrets Manager.
Monitoring
None (console logs + uvicorn stdout)
PII log filter strips phone/email from uvicorn logs. Feedback widget POSTs brain/_feedback/{date}.md — Will reads during sessions. No error tracking, no uptime monitoring.
Sentry / Datadog + structured logging NEW
PII filter stays. Add structured JSON logging (correlation IDs per request). Error tracking with PII scrubbing. Compliance audit trail readable from dashboard, not raw files.
Migration Phases
Phase 1 — Data
PostgreSQL + Server State
Wire PostgreSQL. Move localStorage household/financial/task state to server-side. HouseholdEdit audit log to SQL. Multi-user access for Matt + Will + Evan simultaneously.
Phase 2 — Brain Cloud
Object Storage + Managed Vector DB
Move filesystem brain to Azure Blob / S3. Swap local ChromaDB for pgvector (or Pinecone). Ed25519 manifests run in cloud scheduler. Brain files accessible from any machine.
Phase 3 — Backend Merge
FastAPI → Portal Integration
Package magfin as installable pip module. Portal mounts skill routes (/api/skills, /api/clients, /api/calls). Rate limiting and auth move to API gateway. No more ngrok.
Phase 4 — Mobile
iOS/Android Twilio Voice SDK
In-app softphone via Twilio Voice SDK. CallKit on iOS. On-device Whisper for offline transcription. Eliminates Twilio recording/transcription fees for mobile advisors. 3–6 month build.
Phase 5 — Portal
Full Foundations Integration
Unified auth, tenant isolation, Orion custodial feeds, AutoPop SSO prefill (headline differentiator). Account-opening context pre-populated. No competing CRM integrates with the Foundations account-opening side.
Progress
— tests
Technical Notes