Data flow
Three major flows: chat (read), upload (write), share (public). Each crosses different services — understanding the hop sequence helps when debugging.
Chat flow (docs mode)
Browser lumen-web lumen-api lumen-embedder lumen-postgres LLM provider
│ │ │ │ │ │
│ type question │ │ │ │ │
│ + Enter │ │ │ │ │
│───────────────────────▶│ │ │ │ │
│ │ POST /chat/projects/:id (SSE) │ │ │
│ │───────────────────▶│ │ │ │
│ │ │ POST /embed │ │ │
│ │ │ {text: "query: ..."} │ │ │
│ │ │──────────────────────▶│ │ │
│ │ │◀──────────────────────│ vector (384-dim) │ │
│ │ │ │ │ │
│ │ │ hybrid search (raw SQL with inline vector lit) │ │
│ │ │────────────────────────────────────────────────▶│ │
│ │ │◀────────────────────────────────────────────────│ top 50 chunks │
│ │ │ │ │ │
│ │ │ POST /rerank │ │ │
│ │ │ {query, docs, top_k} │ │ │
│ │ │──────────────────────▶│ │ │
│ │ │◀──────────────────────│ top 5-8 indices │ │
│ │ │ │ │ │
│ │ │ resolveProvider(modelId) │ │
│ │ │────────────────────────────────────────────────▶│ │
│ │ │◀────────────────────────────────────────────────│ provider config │
│ │ │ │ │ │
│ │ │ POST /chat/completions (streaming) │ │
│ │ │────────────────────────────────────────────────────────────────────▶ │
│ │ ← SSE stream │ ← SSE stream │ │ │
│ │ │ │ │ │
│ ← rendered tokens │ │ │ │ │
Latency budget:
- Embed: ~50ms
- Hybrid search: ~100ms
- Rerank: ~200ms
- LLM first token: ~500-1000ms
- Total to first token: ~1s
Code paths:
- Entry:
apps/api/src/routes/chat.ts - RAG:
apps/api/src/services/rag.ts - Provider resolver:
apps/api/src/services/llm.tsresolveProvider() - Frontend stream:
apps/web/lib/api.tschatStream()— async generator over SSE
Upload flow
Browser lumen-api lumen-postgres Redis lumen-worker lumen-embedder
│ │ │ │ │ │
│ select file + submit │ │ │ │ │
│───────────────────────▶│ │ │ │ │
│ │ save file to │ │ │ │
│ │ /data/uploads/... │ │ │ │
│ │ │ │ │ │
│ │ INSERT document │ │ │ │
│ │ status=processing │ │ │ │
│ │────────────────────▶│ │ │ │
│ │ │ │ │ │
│ │ bullmq.add( │ │ │ │
│ │ "doc-processing", │ │ │ │
│ │ {docId}) │ │ │ │
│ │────────────────────────────────────▶│ │ │
│ 200 {document} │ │ │ │ │
│◀───────────────────────│ │ │ │ │
│ │ │ │ pick up job │ │
│ │ │ │─────────────────▶│ │
│ │ │ │ │ parse (PyMuPDF etc) │
│ │ │ │ │ │
│ │ │ │ │ chunk into ~400-tok │
│ │ │ │ │ segments + 50 tok │
│ │ │ │ │ overlap │
│ │ │ │ │ │
│ │ │ │ │ POST /embed │
│ │ │ │ │ [passage: ...] │
│ │ │ │ │────────────────────▶│
│ │ │ │ │◀────────────────────│ vectors
│ │ │ │ │ │
│ │ │ │ │ INSERT chunks │
│ │ │ │ │ (embedding, content,│
│ │ │ │ │ page_number...) │
│ │ │◀─────────────────────────────────│ │
│ │ │ │ │ │
│ │ │ │ │ UPDATE document │
│ │ │ │ │ status=indexed │
│ │ │◀─────────────────────────────────│ │
│ │ │ │ │ │
│ polls status widget │ │ │ │ │
│───────────────────────▶│ GET /documents/:id │ │ │ │
│ │────────────────────▶│ │ │ │
│ 200 {status:"indexed"} │ │ │ │ │
│◀───────────────────────│ │ │ │ │
A ~10-page PDF takes ~5-15 seconds from upload to indexed.
Code paths:
- Upload handler:
apps/api/src/routes/documents.ts - Worker entry:
apps/worker/main.py - Chunking:
apps/worker/chunker.py
Share flow (public)
Member lumen-api Postgres Admin Guest
│ │ │ │ │
│ POST /share-requests │ │ │ │
│────────────────────────▶│ │ │ │
│ │ INSERT request │ │ │
│ │ status=pending │ │ │
│ │─────────────────▶│ │ │
│ 201 │ │ │ │
│◀────────────────────────│ │ │ │
│ │ │ │ │
│ │ │ │ │
│ │ │ │ sees 1 pending │
│ │ │ │ in sidebar badge │
│ │ │ │ │
│ │ │ │ approve │
│ │◀──────────────────────────────────────── │
│ │ │ │ │
│ │ INSERT share_token │ │
│ │ (token = random 32 bytes) │ │
│ │─────────────────▶│ │ │
│ │ UPDATE request │ │ │
│ │ status=approved │ │ │
│ │─────────────────▶│ │ │
│ │ │ │ │
│ (notification that │ │ │ │
│ request was approved, │ │ │ │
│ link copied or emailed)│ │ │ │
│◀────────────────────────│ │ │ │
│ │ │ │ │
│ │ │ │ │
│ shares link to guest ────────────────────────────────────────────────────────▶│
│ │ │ │ │
│ │ │ │ GET /share/<token>│
│ │◀────────────────────────────────────────────────────│
│ │ SELECT token, │ │ │
│ │ conversation, │ │ │
│ │ messages... │ │ │
│ │─────────────────▶│ │ │
│ │ 200 {snapshot} │ │ │
│ │────────────────────────────────────────────────────▶│
│ │ │ │ │
│ │ │ │ guest can ask │
│ │ │ │ follow-ups with │
│ │ │ │ project context │
Guest chat uses the same RAG flow as a member, but:
- No auth required (token in URL is the credential)
- Cannot access other conversations in the project
- Cannot see or edit project settings
- Can be revoked anytime by deleting the share token
Code paths:
- Public page:
apps/web/app/share/[token]/page.tsx(no dashboard layout) - Endpoints:
apps/api/src/routes/public-share.ts