Journey 1: New Lead → First Response

What happens when someone messages Oshun for the first time

WhatsApp/IG
Chatwoot
Webhook
Karen AI
Context
Model
Response
Chatwoot
WhatsApp/IG
STEP 1 WhatsApp → Chatwoot

A lead sends a WhatsApp message

Someone finds Oshun on Instagram or gets a referral. They tap the WhatsApp link and send "Hi, how much is Botox?" The message arrives at Chatwoot Cloud (account 156369) via the WhatsApp Business API. Chatwoot creates a new conversation, assigns it to the Oshun inbox, and fires a webhook to the antigravity-core server.

▶ Webhook receipt and contact matching
Source files
src/webhooks/chatwoot.jsapp.post('/webhooks/chatwoot') (line 23)
src/contacts.jsfindOrCreateContact() (line 6)
What happens
Chatwoot POSTs a message_created event to /webhooks/chatwoot. The handler verifies the signature, deduplicates (ignores if already processed), extracts the sender phone number, and calls findOrCreateContact(). If new → auto-creates contact record with WhatsApp as primary channel. If the phone matches an existing contact (even from Instagram or website), the system auto-merges them.
DB tables
contacts, conversations, channel_messages
Env vars
CHATWOOT_WEBHOOK_SECRET, CHATWOOT_API_TOKEN
STEP 2 Chatwoot → Karen AI

Karen receives and understands the message

The webhook handler passes the message to Karen AI. Karen loads the conversation history, pulls the Oshun tenant context (persona, services, pricing from knowledge base), searches memory for anything relevant about this contact, and assembles a prompt.

▶ Context building and prompt assembly
Source files
src/ai.jschat() (line 125)
src/context-builder.jsbuildContext() (line 20)
src/prompt-builder.jsbuildDefaultPrompt() (line 217)
What happens
chat() is the orchestrator. It calls buildContext() which queries: recent conversation history from memory_conversations, contact profile from contacts + contact_preferences, relevant knowledge from memory_knowledge via hybridSearch() (pgvector + full-text + tag search combined with RRF fusion). Then buildDefaultPrompt() assembles the system prompt with the Oshun persona, available actions (selected by selectActionsForContext()), and the Forward-Thinking Protocol instruction.
DB tables
contacts, contact_preferences, memory_conversations, memory_knowledge, memory_core, memory_profiles
STEP 3 AI Pipeline

Karen decides what to do

Karen's prompt goes to OpenRouter (the AI model provider). The model reads the conversation context, Oshun's pricing, and the patient's question. It generates a response that might include action tags — like [ACTION:search_knowledge:botox pricing] to look up exact prices, or [ACTION:suggest_booking] to offer a consultation link.

▶ Model call and action parsing
Source files
src/model.jscallOpenRouter(), parseActionTags(), classifyComplexity()
What happens
callOpenRouter() sends the assembled prompt to the OpenRouter API. The response is parsed by parseActionTags() which extracts any [ACTION:name:param] tags. If actions are found, they're dispatched through the action system. classifyComplexity() determines if the query needs a more expensive model.
DB tables
token_usage, action_outcomes
Env vars
OPENROUTER_API_KEY, OPENROUTER_MODEL
STEP 4 Response Assembly

Karen crafts her response

Karen knows Botox at Oshun starts at $15,000 JMD. She responds with pricing, mentions the current promotion, and suggests booking a free consultation — always following the Forward-Thinking Protocol: acknowledge the question, connect to the patient's goals, and suggest an actionable next step. The response is checked by the reflection system if it's complex enough.

▶ Reflection and reconciliation
Source files
src/reflection.jsshouldTriggerReflection(), reflectOnResponse()
src/telegram_message.jsreconcileNarration()
What happens
For Chatwoot messages, if the response involves medical advice or pricing claims, shouldTriggerReflection() returns true. reflectOnResponse() runs a second AI pass that checks accuracy and tone. reconcileNarration() validates Karen's claims against the knowledge base before sending. Action tags are stripped from the user-facing text.
Key concept
The Forward-Thinking Protocol is baked into buildDefaultPrompt() — every Karen response must: acknowledge → connect to goals → suggest actionable next step.
STEP 5 Karen AI → Chatwoot → WhatsApp

Response sent back to WhatsApp

The final response is sent back through the Chatwoot API to the patient's WhatsApp. From their perspective, they sent a message and got a helpful, personalized reply in about 14 seconds. They don't know it's AI — Karen responds as Oshun's receptionist.

▶ Response delivery
Source files
src/webhooks/utils.jsreplyToChatwoot()
What happens
replyToChatwoot() calls the Chatwoot API to post a reply in the same conversation. The Chatwoot → WhatsApp bridge delivers it to the patient. Response time is typically 10–15 seconds.
Env vars
CHATWOOT_BASE_URL, CHATWOOT_API_TOKEN
STEP 6 Memory System

Memory stores the interaction

Every conversation turn is stored in memory. The message, Karen's response, and any actions taken are recorded. This builds up a profile of the patient over time — what procedures they've asked about, their price sensitivity, their communication preferences.

▶ Per-turn consolidation
Source files
src/memory.jsstoreConversation() (line 177)
src/consolidator_memory.jsconsolidateTurn() (line 38)
What happens
storeConversation() saves the raw exchange to memory_conversations. Then consolidateTurn() runs asynchronously — it extracts key facts (e.g., "asked about Botox pricing", "prefers WhatsApp") and stores them as knowledge in memory_knowledge with vector embeddings for future semantic search.
DB tables
memory_conversations, memory_knowledge, memory_sessions
STEP 7 Nurture System

Lead enters nurture sequence

If this is a new contact who hasn't booked yet, Karen enrolls them in a warm lead nurture sequence. Over the next 14 days, they'll receive 5 touchpoints — not pushy sales messages, but helpful content about the procedure they asked about, patient testimonials, and gentle booking nudges. This runs automatically.

▶ Nurture enrollment
Source files
src/nurture.jsenrollContact(), createSequence()
src/contacts.jsmarkWarmLead() (line 270)
What happens
markWarmLead() tags the contact for nurture. The nurture system has a 5-touch sequence: research-backed intervals at days 1, 3, 7, 10, 14. Each step is scheduled as a BullMQ job. If the lead books during the sequence, it auto-cancels remaining steps.
DB tables
nurture_sequences, nurture_steps, nurture_enrollments
Note
See Journey 5 (Re-engagement) for what happens if the lead doesn't convert after the nurture sequence ends.