Digital Jay — Pipeline Architecture

End-to-end execution trace, latency analysis, and optimization roadmap
Internal Reference • 2026-04-01 • SecondActSociety V2 + SecondAct-Kajabi
~5.8s
Time to First Token
~9.5s
Full Response
2,095
Knowledge Chunks
7
Pipeline Nodes
3
LLM Calls / Request
6
Source Types

System Architecture

Digital Jay operates as a 4-layer stack spanning 2 repositories, 2 databases, 3 LLM models, a knowledge graph, and an embedding service. Every chat request traverses all four layers.

Layer 1: Frontend Widget

Vanilla JS bundle (secondact-home.js) + on-demand chat (secondact-chat.js). EventSource SSE for streaming. Hosted on Kajabi via proxy.

Layer 2: Proxy

Express/Node.js 20+ (SecondAct-Kajabi repo). Builds user context, fetches conversation history, forwards SSE, persists chat messages. Prisma ORM.

Layer 3: V2 Backend

FastAPI + LangGraph + Python 3.11 (SecondActSociety repo). 7-node state machine with hybrid RAG, intent classification, and parallel response generation.

Layer 4: External Services

PostgreSQL + pgvector (RAG), Neo4j (knowledge graph), Claude Sonnet 4.5 (response), Claude Haiku 4.5 (suggestions), OpenAI Embeddings (text-embedding-3-small).

Two Separate Databases — V2 DB (crossover:14004) holds the RAG knowledge base. Kajabi DB (shinkansen:17053) holds leads, surveys, chat sessions, and messages. They are in different Railway projects. Never confuse them.
End-to-End Request Flow

Infrastructure

ComponentTechnologyLocation
V2 BackendFastAPI + LangGraph + Python 3.11society-v2-production.up.railway.app
ProxyExpress 4.18 + Node.js 20+secondact-proxy-production.up.railway.app
V2 DatabasePostgreSQL + pgvector (HNSW)crossover.proxy.rlwy.net:14004
Proxy DatabasePostgreSQL + Prisma ORMshinkansen.proxy.rlwy.net:17053
Knowledge GraphNeo4j (async driver)Aura
EmbeddingsOpenAI text-embedding-3-small (1536-D)OpenAI API
Response LLMClaude Sonnet 4.5Anthropic API
Suggestions LLMClaude Haiku 4.5Anthropic API

LangGraph Execution Pipeline

Every request flows through a 7-node state machine with conditional routing. The intent node determines whether the user needs clarification or retrieval. Response and suggestions run in parallel after context assembly.

LangGraph State Machine Topology

Node-by-Node Execution

Node 1: Intent Classification Bottleneck ~1.5s

Model: Claude Sonnet 4.5, temp=0.3 • File: agent/nodes/intent.py
Extracts last message, builds 3-turn history summary (100 chars each), preserves acronyms via regex (\b[A-Z]{2,}\b), detects longevity keywords. LLM classifies into 7 intent types with confidence level and search keywords. Sequential bottleneck — blocks all downstream nodes.

Node 2: Routing Gate <1ms

File: agent/routing.py
Requires ALL THREE: ambiguity_detected=true AND confidence="low" AND no injected_context. The proxy's user context (survey/lead data) effectively prevents most clarification triggers. Clarification is terminal — zero streaming tokens.

Node 3: Retrieval — Two-Phase Hybrid RAG ~2.5s

File: agent/nodes/retrieval.py
Launches 6+ parallel tasks: primary vector+keyword (24 candidates each), supplemental vector+keyword (12 each), Neo4j graph search (12, 2s timeout), persona search (3), user profile fetch. RRF fusion produces 12 final results (8 primary + 4 supplemental). Fallback broadening if <3 results. See Retrieval Deep Dive.

Node 4: Context Assembly <100ms

File: agent/nodes/context.py
Merges injected_context (proxy user data) + user_profile + RAG results + graph concepts into structured system prompt. Sections: <user_context>, Book Content, Supporting Context, Related Principles, Voice & Style, Related Concepts. Content-type tags: FRAMEWORK, EXERCISE, EXAMPLE, STORY, CONCEPT, etc.

Node 5: Response Generation Parallel ~3-5s

Model: Claude Sonnet 4.5, temp=0.3, max_tokens=4096 • File: agent/nodes/response.py
Gets journey persona from model_id, builds system prompt with deterministic voice anchors (MD5 hash of query), injects assembled_context. Streams AIMessageChunk tokens. Runs in parallel with suggestions.

Node 6: Suggestions Generation Parallel ~1-2s

Model: Claude Haiku 4.5, temp=0.7, max_tokens=512 • File: agent/nodes/suggestions.py
Extracts concepts from Neo4j graph, maps model_id to journey stage. Tries Haiku-enriched suggestions with manuscript snippets, falls back to journey-specific templates. Generates 3-4 {label, question} pairs. Conditionally appends book CTA after 2+ messages. Hidden behind response streaming — adds 0s to user-perceived latency.

Node 7: Journey Update Fire & Forget

File: agent/nodes/journey.py
UPSERT to user_profiles table mapping model_id to journey name. Non-blocking — errors logged but never raised. Returns empty dict (no state updates).

Retrieval Deep Dive

The retrieval system uses two-phase authority-weighted hybrid search with Reciprocal Rank Fusion. Manuscript content (primary authority) gets 8 of 12 final slots. Supplemental sources (podcasts, articles, tweets, columns) get 4 slots. Intent-aware filters tune what each search retrieves.

Two-Phase Hybrid RAG Pipeline

Intent-Aware Filtering

IntentAuthorityContent TypeEffect
actionprimaryexercise, framework_walkthroughPrescriptive book exercises only
searchprimary, supportingBroad access, minimal filtering
explorationprimary, supporting, illustrativeconcept_introduction, narrativeDescriptive content across sources
comparisonprimary, supportingCross-source evidence
reflectionprimary, supportingreflection, narrativeContemplative content
troubleshooting— (no filters, full corpus access)Maximum coverage
clarification— (no filters, full corpus access)Maximum coverage

RRF Fusion Formula

score = vector_weight / (k + rankv) + (1 - vector_weight) / (k + rankk)
Default: vector_weight=0.5 (equal), k=60 (standard). Confidence boost: 0.7 + 0.3 * confidence_score. Dedup by first 50 chars of content.

Knowledge Base Composition

Source Distribution (~2,095 Chunks)
SourceCountAuthorityNotes
Tweets755illustrativeTwitter/X content
Podcasts386supplementaryPodcast transcripts
Manuscript (baseline)341primaryIDs 1-341, no source_type, has chapter
Columns304supportingSubstack (AI Tuesday, Spotlight, general)
Manuscript (enhanced)255primaryRich metadata: content_type, frameworks
Articles54supportingBlog articles
Gap: ~400+ chunks missing from interviews (200-400), press kit (50-100), and pull quotes (100+). Target is 2,500+. Ingestion pipeline and processors are ready in scripts/processors/.

Search Components

Vector Search (pgvector)

HNSW index (m=16, ef=64). Cosine distance via <=> operator. OpenAI text-embedding-3-small (1536-D). Filtered by authority/content_type/source_type via JSONB.

Keyword Search (FTS)

to_tsvector + plainto_tsquery + ts_rank. Always runs ILIKE fallback for 2+ char acronyms (e.g., BOSS, PRODS). Acronym rank: 0.8.

Graph Search (Neo4j)

3-hop variable-length paths. Keyword match on node names. Returns concepts + relationships sorted by depth. 2.0s timeout with graceful degradation.

Persona Search (Neo4j)

Always retrieves 3 VoicePattern/PersonalityTrait nodes regardless of query. Tagged authority=personality for context routing. 2.0s timeout.

SSE Streaming Flow

The V2 backend uses LangGraph's dual stream mode (["messages", "updates"]) to capture both response tokens and metadata events in a single pass. The proxy selectively forwards tokens to the widget while capturing intent and suggestions for persistence.

SSE Event Lifecycle: V2 → Proxy → Widget

SSE Event Types

V2 EmitsProxy ActionWidget ReceivesData Shape
chat.completion.chunkForward contentevent:message{type:"content", content, fullText}
chat.completion.intentCapture onlyNothing{primary_intent, confidence, keywords}
chat.completion.suggestionsCapture onlyNothing[{label, question, url?, type?}]
[DONE]Build + send doneevent:done{type:"complete", fullText, suggestions}

Alternative Paths

Greeting Flow

__greeting__ sentinel routes to /api/chat/greeting. Haiku + 3 manuscript vectors, max_tokens=150, ~1-2s. Generates personalized welcome + conversation starters. Not persisted.

Clarification Path

Terminal. Requires ambiguity + low confidence + no injected context. Generates 2-3 clarifying questions. Zero streaming tokens. User must respond to continue.

Anonymous Path

No userEmail = no context, no history, no persistence, no greeting. Generic system prompt, single-turn conversation, static welcome message.

Latency Analysis

The critical path from user input to first token is ~5.8s, dominated by three sequential stages: intent classification (1.5s), retrieval (2.5s), and response TTFB (1.5s). The proxy layer adds only 50-100ms.

End-to-End Latency Waterfall
Latency Distribution by Component

Top Latency Contributors

RankComponentTypical% of TotalNotes
1Response LLM (Sonnet)3-5s35-45%Streaming mitigates perceived wait
2Retrieval (Hybrid RAG)2-3s25-30%6+ parallel tasks, Neo4j dominates
3Intent LLM (Sonnet)1-2s15-20%Sequential bottleneck
4Neo4j graph search0.5-2s5-15%High variance, 2s timeout cap
5OpenAI embedding100-300ms2-3%Single API call per request
Key Insight: Intent classification is the #1 optimization target because it is both slow (1.5s) AND sequential (blocks everything downstream). Response LLM is slower overall (3-5s) but streaming mitigates perceived wait — the user sees tokens arrive after ~5.8s, not after ~9.5s.

Critical Path

TTFB Critical Path: Intent LLM (1.5s) → Retrieval (2.5s) → Context (<0.1s) → Response TTFB (1.5s) = ~5.6s minimum TTFB

Hidden Parallel: Suggestions (Haiku, 1-2s) runs parallel to Response — adds 0s. Journey update is fire-and-forget — adds 0s. Within retrieval, 6+ tasks run concurrently, bounded by Neo4j (0.5-2s).

Data & Connection Architecture

Connection Pool Management

ConnectionTypeInit / WarmupKeepalive
PostgreSQL (V2)asyncpg.Pool singletonSELECT 1 warmup120s ping
Neo4jAsyncGraphDatabase.driver singletonverify_connectivity()120s ping
OpenAI EmbeddingsOpenAIEmbeddings singletonWarmup embedPer-request
Anthropic (Response)ChatAnthropic per-graphNonePer-request
Anthropic (Suggestions)AsyncAnthropic singletonNonePer-request
Prisma (Kajabi)PrismaClient + pg adapterAutoManaged

Data Capture Points

TriggerEndpointDatabaseTable
Email gate (any CTA)POST /api/v1/leads/registerKajabilead_registrations
Survey completionPOST /api/survey/submitKajabisurvey_submissions
Chat stream endspersistStreamingChat()Kajabichat_sessions + chat_messages
Page / CTA eventsPOST /api/activity/trackKajabiactivity_events
Journey updatejourney_node()V2user_profiles

Document Schema

second_act_documents (V2 Database)
ColumnTypePurpose
idUUIDPrimary key
contentTEXTChunk text
embeddingvector(1536)OpenAI text-embedding-3-small
chapterTEXTBook chapter reference
page_numberINTEGERPage reference for citations
metadataJSONBsource_type, content_type, authority, section, frameworks, teaching_context, confidence_score

Indexes: HNSW vector (m=16, ef=64), GIN full-text, GIN metadata, B-tree chapter, B-tree page_number.

Optimization Roadmap

Nine optimization proposals (P1-P9) across four phases, targeting TTFB reduction from ~5.8s to ~3.5s and retrieval improvement from ~2.5s to ~1.5s.

4-Phase Optimization Timeline

Optimization Proposals

P1: Intent Classification Caching

High Priority — -1.5s
Current: Every request makes a full Sonnet LLM call for intent (~1.5s).
Proposed: Cache intent results for semantically similar queries. Hash normalized query, check Redis/in-memory cache, TTL 5 minutes.
Expected: 1-1.5s savings for cached hits (est. 30-40% of requests). Risk: Low.

P2: Intent Model Downgrade to Haiku

High Priority — -1.2s
Current: Sonnet 4.5 for intent classification (~1.5s).
Proposed: Haiku 4.5 (~300-500ms). Intent is a structured JSON extraction task — doesn't need Sonnet's reasoning.
Validation: Shadow mode for 1 week — log both intents, compare accuracy. Switch if Haiku ≥ 90% agreement. Risk: Medium.

P3: Speculative Retrieval

High Priority — -1.5s
Current: Intent must complete before retrieval starts (sequential).
Proposed: Start retrieval with regex-extracted keywords while intent runs. If intent provides better keywords, merge/re-rank.
Expected: 1-2s overlap. Risk: Medium — needs fallback logic for suboptimal results.

P4-P6: Medium Priority

Medium Priority — 500ms-1s each
P4: Neo4j connection optimization — pooling, reduce 3-hop to 2-hop, cache concepts. -200-800ms.
P5: Embedding caching — LRU with 1000 entries, normalized keys. -200ms per cached hit.
P6: Retrieval phase consolidation — single query with authority weighting. -100-300ms.

P7-P9: UX & Quality

Lower Priority — UX improvements
P7: Forward intent to widget for adaptive UI ("Searching book content...", "Finding exercises...").
P8: Pre-fetch greeting during page load (before user clicks). Eliminates 1-2s greeting latency.
P9: Knowledge base expansion — interviews (200-400), press kit (50-100), pull quotes (100+). Target 2,500+ chunks.

Deferred Items

ItemActivation GateTarget
Real-time latency dashboardAfter P1+P2 implementedLangSmith integration or custom dashboard
A/B testing frameworkAfter D1 (intent model) validatedRoute traffic % through variant pipelines
Multi-session memoryAfter TTFB < 4sCross-session context retrieval
Agentic tool usePhase 4 roadmapSchedule actions, send resources, create plans

Configuration Reference

V2 Backend (agent/config.py)

ParameterValueCategory
MODELclaude-sonnet-4-5LLM
TEMPERATURE0.3LLM
MAX_OUTPUT_TOKENS4096LLM
MAX_RAG_RESULTS12Retrieval
VECTOR_WEIGHT0.5Retrieval
RRF_K60Retrieval
MAX_GRAPH_RESULTS12Retrieval
PERSONA_RETRIEVAL_COUNT3Retrieval
FALLBACK_THRESHOLD3Retrieval
KEEPALIVE_INTERVAL_SECONDS120Infra
MAX_SUGGESTIONS4Suggestions
SUGGESTION_MODELclaude-haiku-4-5-20251001Suggestions
SUGGESTION_MAX_TOKENS512Suggestions
SUGGESTION_TEMPERATURE0.7Suggestions

Proxy (src/config/environment.js)

ParameterValueCategory
STREAM_TIMEOUT60000msStreaming
STREAM_KEEPALIVE15000msStreaming
RATE_LIMIT_WINDOW15 minSecurity
RATE_LIMIT_MAX100Security
STREAM_LIMIT_MAX10Security
SESSION_BRIDGE_TTL2592000s (30d)Session

Key Files

V2 Backend (SecondActSociety-v2/)
FilePurpose
routes.pyHTTP endpoints, SSE streaming, greeting
agent/graph_builder.pyLangGraph topology (7 nodes)
agent/state.pySocietyState TypedDict
agent/routing.pyshould_clarify() gate
agent/config.pyAll config parameters
agent/nodes/intent.pyIntent classification (Sonnet)
agent/nodes/retrieval.pyTwo-phase hybrid RAG
agent/nodes/context.pyContext assembly
agent/nodes/response.pyResponse generation (Sonnet)
agent/nodes/suggestions.pySuggestions (Haiku)
shared/retrieval/vector.pypgvector search
shared/retrieval/keyword.pyPostgreSQL FTS
shared/retrieval/fusion.pyRRF fusion
shared/retrieval/graph.pyNeo4j graph search
Proxy (SecondAct-Kajabi/)
FilePurpose
src/routes/chat-stream.jsMain streaming handler
src/services/chat-context-builder.jsUser context assembly
src/services/external-chat-service.jsHistory + persistence
src/services/conversation-starters.jsGreeting starters
src/services/suggestion-generator.jsFallback suggestions
src/services/session-bridge.jsRedis cross-system context