Files

5.1 KiB

Decisions (Architecture Decision Record)

# Decision Chosen Alternatives rejected Why
1 Language/runtime TypeScript + Node.js (LTS) Python (aiogram), Go Single-language team, grammY ecosystem, shared types across adapter/core
2 Telegram framework grammY Telegraf, raw Bot API Actively maintained, first-class webhook + middleware, TS-native
3 AI orchestration Minimal orchestration (no LangGraph/LangChain yet) LangGraph.js, LangChain.js Phase 1 needs a single LLM call; the spec allows "smallest useful surface". Introduced later only when agent/tools/memory justify it.
4 LLM access Existing LiteLLM gateway (litellm:4000/v1), model alias mem0-openai (upstream OpenAI gpt-5-mini primary + OpenRouter fallback, load-balanced inside LiteLLM) Direct OpenAI + OpenRouter dual-provider in app code Reuses the mem0 stack's existing failover; one OpenAI-compatible endpoint. NOTE: LiteLLM serves ONLY declared aliases — gpt-5-mini is NOT valid, must use mem0-openai (caused a 400 Invalid-model bug).
5 Database Reuse mem0-postgres (pgvector/pgvector:pg17) in an isolated bot database Project-specific postgres container, NocoDB Avoids a second Postgres; pgvector already provisioned for Phase 3; bot db isolates from mem0 tables.
6 DB provisioning Self-migration on startup (CREATE DATABASE + schema_migrations) Manual psql one-time Reproducible, spec §14 requires migration/startup checks.
7 Chat state Postgres bot_user/conversation/message In-process memory, Redis Spec §8.2: Postgres is the durable system of record.
8 RAG / product facts IMPLEMENTED 2026-08-31: CatalogRetriever (keyword, not vector) replaces NoopRetriever — SKU-token + name/category substring + CJK-particle-stripped segments over the generated catalog.ts; top-5 facts injected into the system prompt. Live in prod wiring (server.ts). Paid SKU hits also surface their FREE trial twin (CZH01 → FREECZH01) so free-trial questions answer from facts. Vector DB in Phase 1 Spec §8.3: no corpus justifies it yet; keyword search is enough at 254 SKUs; pgvector first later.
9 n8n role Automation/tool layer via constrained HTTP client n8n as the app backend Spec §9. Tool inputs validated; no arbitrary workflow execution exposed to the LLM.
10 Multi-channel Shared core + per-channel adapters (Channel/IncomingMessage/OutgoingMessage + capability flags) Per-channel monoliths Spec §7. Future Shopee/Lazada adapters implement the contract without touching the AI core.
11 Deployment Docker multi-stage (dev bind-mount + tsx watch; prod immutable image) Host-installed Node Spec §4/§5: reproducible prod, hot-reload dev.
12 Secrets Env vars via .env (gitignored); source-of-truth in repo ../SECRETS.md Hard-coded, dotenv-in-repo Spec §12/§15; repo convention: single credential store.
13 Webhook ingress CF tunnel bot.digikedai.comdirectly to http://digikedai-bot:8080 (container name on bridge_hoelee), no Traefik hop Traefik labels, DSM nginx cloudflared and the bot share bridge_hoelee; the bot's own Hono server answers /health + /<secret>/webhook, so no proxy middleware is needed. Simplest path that requires no shared-proxy changes.
14 Webhook timeout grammY onTimeout:"return" + 50s; LLM client timeout 45s grammY default throw@10s LLM (GPT-5-mini w/ reasoning) replies exceed 10s → grammY never returns 200 → Telegram re-delivers the same update forever ("bot keeps replying" loop).
15 Host port 5247:8080 8080 (taken by DSM nginx), 5244-5246 (alist stack) 5247 was the first free port.
16 Rate limiting In-memory fixed-window RateLimiter keyed by channel:userId, enforced in the shared MessageService (after normalize, before persist/LLM); deny replies once per window, then silence; webhook still answers 200 Redis, @grammyjs/ratelimiter, per-IP limits Single replica today (no Redis needed); channel-agnostic so Shopee/Lazada inherit it; never lets Telegram re-deliver a throttled update (same principle as D14).
17 User long-term memory Postgres user_memory KV table (keyed by bot_user.id, not conversation) + cheap LLM async summary via SUMMARY_MODEL (default = main model); trigger = new facts only (lang change / new SKU / identity keywords / ≥10 turns fallback); every memory failure degrades silently, never blocks the reply mem0 HTTP API, pgvector, per-turn summarization, rule-only extraction Reuses existing Postgres + LiteLLM with zero new infra; cross-session/cross-channel personalization with bounded cost (docs/MEMORY_FEATURE.md)

Open decisions (Phase 2)

  • D1 — Mint a dedicated LiteLLM virtual key for the bot (rate-limit + independent revocation) instead of sharing the master key.
  • D2Traefik route RESOLVED 2026-08-29: CF tunnel targets http://digikedai-bot:8080 directly (D13), no Traefik/nginx hop needed.
  • D3 — Exact free-account n8n workflow path + input schema (currently a placeholder).
  • D4 — User allowlist: enabled-by-default for production (currently open in development).