What happens when someone messages Oshun for the first time
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.
src/webhooks/chatwoot.js → app.post('/webhooks/chatwoot') (line 23)src/contacts.js → findOrCreateContact() (line 6)
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.contacts, conversations, channel_messagesCHATWOOT_WEBHOOK_SECRET, CHATWOOT_API_TOKENThe 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.
src/ai.js → chat() (line 125)src/context-builder.js → buildContext() (line 20)src/prompt-builder.js → buildDefaultPrompt() (line 217)
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.contacts, contact_preferences, memory_conversations, memory_knowledge, memory_core, memory_profilesKaren'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.
src/model.js → callOpenRouter(), parseActionTags(), classifyComplexity()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.token_usage, action_outcomesOPENROUTER_API_KEY, OPENROUTER_MODELKaren 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.
src/reflection.js → shouldTriggerReflection(), reflectOnResponse()src/telegram_message.js → reconcileNarration()
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.buildDefaultPrompt() — every Karen response must: acknowledge → connect to goals → suggest actionable next step.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.
src/webhooks/utils.js → replyToChatwoot()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.CHATWOOT_BASE_URL, CHATWOOT_API_TOKENEvery 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.
src/memory.js → storeConversation() (line 177)src/consolidator_memory.js → consolidateTurn() (line 38)
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.memory_conversations, memory_knowledge, memory_sessionsIf 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.
src/nurture.js → enrollContact(), createSequence()src/contacts.js → markWarmLead() (line 270)
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.nurture_sequences, nurture_steps, nurture_enrollments