All documentation

AI Platform Architecture

The cross-cutting AI layer every intelligent feature in this app is built on: provider-agnostic LLM access, RAG, and seven AI-powered feature modules. Lives entirely under src/lib/ai/, deliberately mirroring src/lib/connectors/'s registry/manager/port pattern — the same shape already proven across four real broker integrations this codebase built earlier.

Layout

src/lib/ai/
  sdk/            AIProvider interface, BaseAIProvider, StubAIProvider, types, errors
  registry/       AIProviderId union, AI_PROVIDERS catalog, AIModelRegistry, AIProviderRegistry
  manager/        AIProviderManager — resolve, call, fallback, retry, cost/usage recording
  providers/      openai/, anthropic/, gemini/, ollama/ — one real adapter each
  shared/         sse.ts, cost.ts, defaults.ts, rate-limit.ts, trading-heuristics.ts
  services/       the 11 foundation services (below)
  prompts/        code-defined default prompt templates, one file per feature
  tools/          AIToolExecutionService's core tool registry
  prop-firm/, journal/, coach/, risk-manager/, market/, chat/, automations/, dashboard/, knowledge/, security/
                  one thin module per feature — composes the services layer,
                  never reimplements it
  register.ts     registerOpenAIProvider() etc. — lazy, idempotent, env-gated
  compose.ts      createAIProviderManager(), createChatService(supabase)

Foundation layer

`AIProvider` interface (sdk/provider.ts):

interface AIProvider {
  readonly id: AIProviderId;
  generate(req: AIGenerateRequest): Promise<AIGenerateResponse>;
  stream(req: AIGenerateRequest): AsyncGenerator<AIStreamChunk>;
  embed(req: AIEmbeddingRequest): Promise<AIEmbeddingResponse>;
  listModels(): AIModelInfo[];
  healthCheck(): Promise<AIProviderHealth>;
}

BaseAIProvider (abstract) wraps generate/embed with metrics recording. StubAIProvider — what an unconfigured provider actually is, never a crash — throws AIProviderNotConfiguredError from every data method but returns a real 'unconfigured' status from healthCheck().

Registry — AIProviderRegistry creates a provider instance by id (real adapter if configured, StubAIProvider otherwise); AIModelRegistry resolves a model id to its provider + pricing/ capability metadata from the AI_PROVIDERS catalog (registry/providers.ts).

`AIProviderManager` (manager/ai-provider-manager.ts) — the one entry point every feature module calls through. Given a feature name and a model (with an optional fallback chain), it resolves the provider, calls it, and on a retryable failure falls back to the next model in the chain before giving up. Records one ai_usage_events row per attempt via a required, constructor-injected AIUsageRecorderPort (SupabaseUsageRecorder in production) — provider, model, feature, tokens, cost, latency, success/failure.

The 11 foundation services (services/*.ts)

ServiceResponsibility
AIConversationServiceCRUD over ai_conversations/ai_messages via an injected persistence port
AIContextServiceAssembles a prompt-ready context bundle from *existing* domain queries (accounts, trades, risk rules, plan tier)
AIEmbeddingServiceWraps manager.embed(), writes ai_embeddings
AIRAGServiceEmbeds a query, calls match_ai_embeddings() (pgvector cosine search, permission-scoped), returns top-K chunks
AIMemoryServiceDurable per-user facts distilled from conversation history (ai_memories)
AIPromptManagerRenders {{var}} templates — code default (prompts/*.ts) + optional DB override (ai_prompt_templates)
AIToolExecutionServiceTyped function-calling registry; every handler is bound (userId, args), always user-scoped
AIStreamingServiceWraps an AsyncGenerator<AIStreamChunk> into a Next.js SSE Response (toSSEStreamFromGenerator, sseResponseHeaders)
AIAnalyticsServiceAggregation queries over ai_usage_events for the admin Cost Dashboard
AIRecommendationEngineThe shared "render prompt → call AI → parse → write to ai_insights" orchestrator every feature phase below calls into
AICacheService (cache-service.ts)Per-instance TTL cache (aiCache singleton) — bounds redundant identical calls within one request lifecycle

checkAIRateLimit() (shared/rate-limit.ts) is not a service class — it reuses the existing checkRateLimit()/check_rate_limit() Postgres function with one bucket per AIFeature.

RAG and embeddings

First vector infrastructure in this codebase: create extension if not exists vector;, one fixed-width column ai_embeddings.embedding vector(1536). The whole platform is standardized on one embedding model, OpenAI's text-embedding-3-small (STANDARD_EMBEDDING_MODEL), so every row shares the same dimensionality regardless of which provider generated the surrounding chat completion.

ai_embeddings.owner_user_id is the permission boundary: null = a platform-wide document (docs, broker docs, prop-firm rules); non-null = scoped to that one user's own trading data. Enforced twice — RLS on the table, and match_ai_embeddings() (SECURITY DEFINER) reading auth.uid() internally, never from a caller-supplied parameter. AIRAGService must always be constructed with the caller's session-scoped Supabase client, never the service role — see docs/AI_SECURITY.md.

KnowledgeIngestionService (knowledge/ingestion-service.ts) chunks and embeds source text (chunkText()) for platform_doc/broker_doc/ prop_firm_rule rows.

Feature modules

FeatureModuleUI
Prop Firm Assistantai/prop-firm/ — pure rule-engine.ts (drawdown incl. trailing high-water-mark, consistency, trading-days, profit-target) + assistant-service.ts/dashboard/prop-firms
Trade Journalai/journal/ — pure metrics.ts (win rate, expectancy, profit factor, best/worst setups, discipline score) + analysis-service.ts + report-service.ts/dashboard/journal
Performance Coachai/coach/coaching-service.tsCoaching tab on /dashboard/ai-assistant
Risk Managerai/risk-manager/ — pure monitor-service.ts (loss/exposure/correlation/consecutive-losses/risk score) + automation-service.ts (lock/pause/reduce/notify)AI Risk Monitor tab on /dashboard/risk-manager
Market Intelligenceai/market/intelligence-service.ts — AI-native synthesis, real but unconfigured MarketDataProvider port/dashboard/market + the dashboard's Market News widget
Chat Assistantai/chat/chat-service.ts — RAG + tools + streamingChat tab on /dashboard/ai-assistant; POST /api/ai/chat
Automationsai/automations/automation-engine.ts/dashboard/automations; GET /api/ai/automations/run
AI Dashboardai/dashboard/dashboard-service.ts — read-only aggregation, no AI calls on load/dashboard/ai

Every feature module composes the foundation services and AIRecommendationEngine; none re-implements prompt rendering, provider calls, or usage recording.

Chat orchestration (tool-calling + streaming)

ChatService.streamReply() composes AIContextService, AIRAGService, AIPromptManager, AIToolExecutionService, AIConversationService, and AIProviderManager.stream(). It streams the model's response; if the model requests tool calls, they're executed (non-streamed, typically fast DB reads) and a second round is streamed with the tool results appended — capped at MAX_TOOL_ROUNDS = 2 to guarantee termination. The common no-tool-needed case costs exactly one AI call.

Database (additive only)

supabase/migrations/20260807200000_ai_platform.sql (+ three small follow-ups) adds: ai_conversations, ai_messages, ai_usage_events, ai_embeddings, ai_prompt_templates, ai_memories, ai_coaching_goals, risk_monitor_events, risk_automation_rules, ai_automations, ai_journal_reports, ai_market_briefs; widens prop_firms (10 named firms reseeded) and ai_insights.insight_type. Every table has RLS, owner-scoped for user data, service-role-write for platform-generated content (usage events, market briefs).

What is honestly NOT built

  • BYOK — every provider-selection seam (registry, register.ts,

AIProviderManager) is shaped so a future org-supplied key can be swapped in at the registration call site, but only platform-owned keys are wired up today.

  • Live market data — MarketDataProvider (ai/market/types.ts) is a

real, documented port with an honestly unconfigured UnconfiguredMarketDataProvider behind it, mirroring RithmicUnimplementedTransport. Market Intelligence runs on the model's own training knowledge, explicitly caveated in every prompt.

  • A background scheduler — GET /api/ai/automations/run is an

external-cron endpoint (shared-secret header), not a persistent process, matching this app's documented deployment model (docs/ARCHITECTURE.md).