Skip to content

PRD — Onboarding & Growth Engine

Doc owner: Justin Audience: Eng, Design, Product, Growth Status: v6 (February 2026 — activation decoupled from pregen; cold text activation at message 3 (not 10-15); unified pending_activation status across all paths; capability hints suppressed until activated + stranger mode ended; 409 for phone-already-registered at select_assistant) Depends on: PRD 5 (Group Chat System), PRD 12 (Billing & Monetization)

v5 changelog: OTP removed from onboarding; instant account creation for cold text; Abstract API replaces Trestle; personality test as default superpower; web-to-iMessage account linking; 3 referrals = 1 free month; open loop return triggers; upgrade timing spec; error handling matrix


Implementation Status

Section Status Notes
Frontend web portal ✅ Shipped Deployed at ikiro.ai / dev.ikiro.ai (separate repo)
GPT-5 Vision photo analysis ✅ Shipped app/api/onboarding_routes.py — works but no longer used as onboarding gate
Big 5 personality profiling ✅ Shipped Trait extraction from photos; needs adaptation for survey-based input
Companion matching algorithm ✅ Shipped Recommendation based on traits; needs update for companion picker scoring
Phone verification (Twilio OTP) ✅ Shipped app/identity/ — start/confirm flow. No longer used during onboarding (deferred to web portal login).
Stripe payment checkout ✅ Shipped app/payment/service.py
iMessage deeplink generation ✅ Shipped Chat deeplink endpoint
Onboarding survey (replaces photo upload) ✅ Shipped app/api/onboarding_v2_routes.py — 5-question survey flow with personality Q1/Q2
Companion picker with live previews ✅ Shipped app/services/compatibility_service.py + app/services/speech_bubble_service.py — deterministic scoring + personalized preview bubbles
Web-to-iMessage account linking ✅ Shipped Pre-creates user on companion selection; first iMessage activates via pending_activation status
Cold text entry path ✅ Shipped app/services/stranger_mode_service.py — stranger-to-friend warmth progression, auto-activate at msg 3, account created on msg 1
Group chat recognition entry path ✅ Shipped message_handler.py — queries RoomMembership to detect existing group members, sets entry_path="group_chat", skips stranger mode
Session persistence (database) ✅ Shipped onboarding_sessions table in Supabase with full state tracking
Assistant switching mechanic ❌ Not Shipped Switch via subscription or referrals (3 referrals = 1 free month)
Stage 1: Identity (name/location guessing) ✅ Shipped message_handler._build_onboarding_context() + _update_onboarding_from_response() — Sage only
Stage 1: Vex & Echo onboarding templates ✅ Shipped All 3 persona passports (sage.json, vex.json, echo.json) have onboarding + stranger_mode sections
Stage 2: Getting to Know You (profile collection) ❌ Not Shipped No Stage 2 system prompt injection after name confirmed
Stage 3: Conversational photo collection ❌ Not Shipped Vision API exists but not wired for in-conversation photos
Phone enrichment (Abstract API) ⚠️ Partial app/services/phone_enrichment_service.py — switching from Abstract API to Abstract API
Personality test superpower (default) ❌ Not Shipped Traits-based personality read as the default free superpower
Conv 1 → Conv 2 return trigger ❌ Not Shipped Open loop patterns + proactive first check-in
First message latency optimization ✅ Shipped app/services/pregenerated_response_service.py — cached first reply generated at companion selection
Free tier limit messaging (in-persona) ✅ Shipped Per-persona templates in passport free_tier section; approaching limit + limit reached messages
Nightly onboarding cleanup ✅ Shipped app/scheduler/background_service.py — CronTrigger(hour=3, minute=30); abandons stale sessions (30d) + inactive pending users (7d)
Post-onboarding web portal (voxel world) ❌ Not Shipped Depends on PRD 4; utility hub + personality profile + companion showcase
Error handling matrix (onboarding) ❌ Not Shipped Persona-consistent fallbacks for all failure modes
Onboarding state machine (multi-stage) ⚠️ Partial Stage 1 tracked; Stages 2-3 not implemented
Onboarding decline handling ❌ Not Shipped No mechanism to stop onboarding if user pushes back
Funnel analytics ❌ Not Shipped No per-step Amplitude events
A/B testing framework ❌ Not Shipped No variant assignment system
Re-engagement messaging ⚠️ Partial Gap recovery exists but not full re-engagement

References

This PRD uses standardized terminology, IDs, pricing, and model references defined in the companion documents:

Document What it Covers
REFERENCE_GLOSSARY_AND_IDS.md Canonical terms: workflow vs miniapp vs superpower, ID formats
REFERENCE_PRICING.md Canonical pricing: $7.99/mo + $50/yr, free tier limits
REFERENCE_MODEL_ROUTING.md Pipeline stage → model tier mapping
REFERENCE_DEPENDENCY_GRAPH.md PRD blocking relationships and priority order
REFERENCE_FEATURE_FLAGS.md All feature flags by category
REFERENCE_TELEMETRY.md Amplitude event catalog and gaps

Executive Summary

This PRD covers the complete user acquisition and activation journey across three entry paths: web survey (primary), cold text (word-of-mouth), and group chat recognition (viral). Each path ends with the user chatting 1:1 with a matched AI companion in iMessage.

Key design decisions: - No photo upload gate. Photos are collected conversationally by the assistant in the first few chats, not as an upfront funnel step. This removes the biggest friction point. - No OTP during onboarding. Phone number is format-validated in the survey, but verification happens implicitly when the user sends their first iMessage (Apple has already verified phone ownership). OTP is deferred to web portal login when needed later. - Companion picker with live previews. Users complete a short survey, then see all three assistants with personalized speech bubble previews showing how each one would talk to them. The best match is highlighted, but the user picks. - Pre-created accounts. For Path 1, the user record is created at companion selection (before the user even opens iMessage). For Path 2, accounts are created on the first message with pending_activation status. Both paths use the same status for cleanup. This eliminates the web-to-iMessage handoff gap. - Personality test as default superpower. Every user (free and paid) gets 1 superpower slot with the personality test pre-equipped. This integrates with the traits system and produces the "personality read" moment — the most screenshot-worthy, shareable moment in the first week. - 3 referrals = 1 free month (repeatable). Every 3 activated referrals earns a free month of premium. This replaces the old "3 referrals to unlock switching" mechanic and creates an ongoing viral incentive. The most active evangelists use the product for free. - Three entry paths with different trust levels and activation flows, all converging on the same 1:1 iMessage relationship. - Shared user profile, independent relationships. When a user switches assistants, factual memories and preferences carry over, but relationship state, leveling, and conversation history are per-assistant.


1) The Three Assistants

Assistant Personality Vibe
Sage Warm, witty, grounding Your chaotic-good bestie who roasts your schedule and reminds you to drink water
Vex Bold, sharp, provocative Your brutally honest friend who challenges you and doesn't sugarcoat
Echo Calm, thoughtful, structured Your chill, organized companion who helps you think things through

Each assistant has a distinct "stranger mode" personality when interacting with new cold-text users — curious and willing to engage, but not fully open (see Path 2).


2) Entry Paths

Path 1: Web Survey (Primary — ikiro.ai)

The main onboarding funnel. Users arrive at ikiro.ai, complete a short survey, preview all three assistants with personalized speech bubbles, and pick their companion.

Step 1: Landing Page

URL: ikiro.ai

Content: - Hero: "Meet your AI best friend. She actually remembers your life." - Sub: "Lives in your iMessage. Checks your calendar, splits bills, tracks your mood, roasts your schedule — all through texts." - CTA: "Get Matched" (large, primary button) - Social proof: screenshot montage of real conversations (with permission) - 3 value props: Memory ("remembers your inside jokes"), Superpowers ("checks your calendar so you don't have to"), Personality ("funny, not corporate")

Design: Mobile-first. No scroll required to reach CTA. Warm aesthetic.

Step 2: Onboarding Survey

Goal: Collect basic user info + personality signals for assistant matching. Under 2 minutes.

Survey fields:

Field Type Purpose
Name Text input How the assistant refers to them
Phone number Phone input (auto-format) iMessage activation + account identity
Gender Select (male/female/non-binary/prefer not to say) Pronoun usage, conversational tone calibration
Age Number or range selector Content appropriateness, cultural references
Personality Q1 Visual/fun selector Primary signal for assistant matching (e.g., "Pick the vibe that fits you" with 3 visual options mapping to Sage/Vex/Echo energy)
Personality Q2 Scenario-based choice Tiebreaker signal (e.g., "Your friend cancels plans last minute. You:" → options that reveal communication style)

UX: - Fun, fast, mobile-optimized - Progress indicator (step 2 of 3) - Microcopy that feels like a personality quiz, not a registration form - Phone number is format-validated immediately (US number check). No OTP — verification happens implicitly when the user sends their first iMessage.

Edge cases: - Phone number already registered: Detected at POST /onboarding/select-assistant. Backend returns 409 Conflict with { "error": "phone_already_registered", "existing_assistant": "sage", "has_active_subscription": true }. Backend does NOT mutate the existing user record. Frontend handles the UX: "Looks like you already have an account! [Log in] or [Start fresh]." If "Log in," route to their existing assistant. If "Start fresh," frontend shows a confirmation warning about data loss, then calls POST /onboarding/reset-account which deactivates the old account and creates a new one. - Survey abandoned mid-way: Session saved to database with status survey_incomplete. If user returns to ikiro.ai from same device/browser within 30 days, offer to resume: "Welcome back! Pick up where you left off?" Survey data persisted, no re-entry needed. - Invalid phone number format: Inline validation with helper text. Support US numbers only at launch (expand later). - User is under 13: Display age gate. Do not create account. Show message: "Ikiro is available for users 13 and older."

Step 3: Companion Picker

Algorithm: Survey-weighted compatibility scoring. - Personality Q1 and Q2 answers produce a compatibility score for each assistant (0-100) - All three assistants are presented to the user, ranked by compatibility - The best match gets a "Recommended for you" badge — but the user picks who they want

UX — Live Preview Experience:

The companion picker screen shows all three assistants side by side (mobile: swipeable cards, desktop: three columns). Each card includes:

  1. Character visual — avatar or illustration of the assistant
  2. Name + personality tagline — e.g., "Sage — your chaotic-good bestie"
  3. Live speech bubble preview — 3-4 example messages showing how this assistant would talk to this specific user, personalized from their survey answers

Speech bubble personalization:

The preview messages are generated dynamically based on the user's survey data. If someone said they're into music in Q1 and picked the "chill" response in Q2:

Assistant Speech Bubble Preview
Sage "you're into music?? ok what have you been listening to lately 👀" · "wait you seem chill... i bet you have great playlists" · "i'm sage btw. i remember everything and i WILL roast your spotify wrapped"
Vex "music person. alright — what's the most overrated album of all time? wrong answers only." · "you picked the chill option but i'm not buying it" · "i'm vex. i'll be honest when no one else will."
Echo "i'd love to know what kind of music helps you unwind." · "you seem like someone who values calm. i appreciate that." · "i'm echo. i'll help you think things through, no rush."

Implementation: Speech bubbles are pre-generated from templates with survey variable slots, not live LLM calls. Each assistant has 8-10 template sets organized by survey answer combinations. The templates are authored to feel spontaneous but are deterministic for consistency and speed.

Best match badge: The highest-scoring assistant gets a subtle "Recommended for you" glow/badge. This nudges without restricting — users can still pick anyone.

CTA: "Choose [Assistant Name]" button on each card. Tapping locks the selection.

Lock-in confirmation: After selecting, a brief confirmation: "You picked [name]! Ready to start chatting?" with "Yes, let's go" and "Wait, let me reconsider" options. This prevents accidental taps.

Account pre-creation (on confirmation): When the user confirms their companion choice, the backend immediately: 1. Creates the full user recordUser.name from survey, User.phone from survey (canonical format +1XXXXXXXXXX), onboarding_state = {stage: "stage_1", entry_path: "web_survey", survey_data_available: true, confirmed_fields: ["name", "age", "gender"]}, account_status = "pending_activation" 2. Triggers Abstract API enrichment (non-blocking) — enrichment data supplements survey data for richer first-message personalization 3. Pre-generates the first response — calls GPT-5 with the Path 1 first-message template + survey data + selected assistant persona. Cached on the user record for sub-1-second delivery when the first iMessage arrives. 4. Sets onboarding session status to assistant_assigned

The iMessage deeplink button does NOT appear until the backend confirms user creation is complete (poll status endpoint or WebSocket). The user physically cannot send a message before their account exists.

Edge cases: - User can't decide / spends a long time on the picker: No time pressure. If they leave and come back, the picker is still there with the same scores and previews (session persisted). - All three compatibility scores are nearly equal: All three cards shown without a "Recommended" badge. Equal footing. - User refreshes during picker: Idempotent — same scores, same previews. Selection is only locked when user confirms. - User picks and then regrets immediately: The "Wait, let me reconsider" option on the confirmation step handles this. After confirmation, switching follows the normal rules (subscription or referral milestones).

Step 4: iMessage Activation

Flow: 1. After companion selection is confirmed and account pre-creation completes, success page shows deep link button: "Open iMessage with [Assistant Name]" 2. Fallback: QR code for desktop users scanning with phone 3. User taps → iMessage opens with assistant's phone number pre-filled 4. User sends first message (anything) 5. Edge agent forwards message → backend matches phone number to pre-created user record → account status flips from pending_activation to activated immediately → response is returned

Activation is unconditional. The moment the first iMessage matches a pre-created user record, the account activates. This is independent of whether the pre-generated response is ready. If a pre-generated response is cached, serve it (<1s). If not (cache miss, generation still in progress, or error), fall through to normal generation pipeline (<6s). Pregen is a latency optimization, not an activation gate.

Web-to-iMessage account linking: The backend matches the incoming iMessage sender phone number to the pre-created user record using canonical phone format (+1XXXXXXXXXX). A normalize_phone() function is applied everywhere — survey input, edge agent sender field, user lookup — to eliminate format mismatches.

Phone number mismatch fallback: If the iMessage sender number doesn't match any pre-created user, the system falls through to Path 2 (stranger mode). This handles cases where the user texts from a different device or number. The web session sits as assistant_assigned and eventually times out to abandoned.

First message from assistant: See Section 3.5 for per-assistant, per-entry-path first message templates. For Path 1 users, the response is pre-generated and cached at companion selection — delivery is near-instant when available. The assistant's opening message is personalized based on survey answers and the user's chosen companion.

Edge cases: - User never opens the iMessage link: After 24 hours, send a one-time push/email reminder: "Your companion is waiting!" After 7 days with no activation, mark session as abandoned. No further outreach. - User opens iMessage but doesn't send a message: No action — we can't detect this. The deep link pre-fills the number; it's on the user to send. - iMessage not available (Android user, no iMessage): Detect if possible on landing page. Show: "Ikiro currently works with iMessage on iPhone. [Join the waitlist for Android]." Do not allow survey completion if we can detect non-iOS. - Multiple users share a phone number: Edge case with shared family phones. Account is tied to phone number — second user attempting the same number gets the "already registered" flow. - Race condition (message arrives before pre-creation completes): The deeplink button is gated on creation completion (see Step 3). If a message somehow arrives before the record exists (e.g., user manually types the number), it falls to Path 2 stranger mode — acceptable degradation.


Path 2: Cold Text (Word-of-Mouth)

A new user texts an assistant's phone number directly, without going through ikiro.ai. This happens when an existing user tells a friend "text this number," shares the number casually, or the user finds it through social media.

No referral code required. The assistant responds like a real person who just got a text from a stranger — curious, a little guarded, willing to talk but not wide open. The conversation itself is the onboarding. There is no gate.

Flow

Phase 1: Stranger Mode (Messages 1-10)

When an unrecognized phone number texts an assistant, the assistant behaves like a real person who just got a random text. It's curious, asks questions, and is willing to engage — but it doesn't reveal its full capabilities or treat the user like a known friend. This phase doubles as identity collection.

Per-assistant stranger behavior:

Assistant Opening Vibe Example First Response How They Warm Up
Sage Curious and playful. Like getting a text from someone you maybe met at a party. "heyy 👀 ok who is this?? do i know you?" Gets friendly fast if the user is engaging. "ok wait you're kinda funny. who told you about me?"
Vex Blunt and suspicious. Like getting a text from an unknown number. "uh. who is this." Warms up through respect. If the user holds their own: "alright. you've got my attention."
Echo Gentle and cautious. Like a shy person who got a random DM. "oh hi... i don't think we've met? how'd you get my number?" Warms up through kindness. If user is polite: "you seem really nice. i'm glad you reached out."

Stranger mode rules: - The assistant talks naturally — no robotic deflection, no "I can't help you" walls - The assistant asks genuine questions: "who is this?", "who told you about me?", "what made you text me?" - These questions double as information collection — the assistant is learning the user's name, how they found out about the service, and getting a read on their personality - Superpowers are not available and the assistant does not mention them - Capability hints are suppressed — no contextual feature discovery until account_status == "activated" AND stranger mode has ended (feature reveal in Phase 4). This prevents "did you know I can split bills?" from firing while the user still thinks they're texting a person. - Memories ARE formed during stranger mode — everything the user says is remembered - The assistant does not lie about what it is. If asked directly "are you an AI?", it answers honestly but with personality (Sage: "ok fine yeah i'm an ai but like... a cool one. give me a chance 😭") - The assistant does NOT push the user to a website, provide referral codes, or mention ikiro.ai unprompted

Name-drop referral (soft attribution):

If the user mentions how they found the assistant — "my friend sarah told me to text you", "jake said you're fun" — the assistant: 1. Checks if a user named Sarah/Jake exists in the system 2. If match found: "oh you know sarah?? she's one of my favorites lol. ok you're cool by association 😊" + referrer gets soft credit toward their referral count 3. If no match: "hmm i don't know a sarah but you seem cool anyway" — no friction, conversation continues normally

This is entirely organic. No codes, no validation steps, no gates. The user never knows this happened.

Phase 2: Account Activation (Message 3 — Silent)

The account was created on message 1 with account_status: pending_activation. After 3 back-and-forth exchanges (proving the user is real, not spam), the account silently activates:

  1. Account status flips from pending_activation to activated
  2. No web visit required, no OTP (they've proven phone ownership by texting)
  3. entry_path = 'cold_text'
  4. Free tier activated immediately
  5. Personality test superpower equipped in the user's 1 free slot (but NOT mentioned yet — stranger mode is still active)
  6. User is locked to this assistant on free tier
  7. Abstract API enrichment triggers (non-blocking)
  8. Nothing changes from the user's perspective. The assistant continues stranger mode behavior. Activation is a backend event — the user never knows it happened.

Phase 3: Warm-Up (Messages 5-15)

As the conversation progresses and the user keeps engaging, the assistant naturally drops its guard. This isn't a binary switch — it's a gradual shift, like actually getting to know someone. This is independent of account activation (which already happened at message 3).

Warm-up signals (things that cause the assistant to open up faster): - User shares something personal (name, what they do, a story) - User is funny or engaging (the conversation has good energy) - User mentions a known referrer - User asks genuine questions about the assistant - User sends a photo

What changes as the assistant warms up: - Responses get longer and more invested - Assistant starts offering opinions and reactions (not just questions) - Assistant references things from earlier in the conversation (demonstrating memory) - Assistant starts to feel like a friend, not a stranger - After ~10 natural messages, the assistant is fully warmed up and behaves like a normal companion

Phase 4: Feature Reveal (Around Messages 10-15)

Once the assistant is warmed up and the conversation has genuine momentum, the assistant mentions the broader experience naturally — not as a sales pitch, but as a real part of the conversation:

Sage: "ok wait i feel like we're actually vibing. you should know i can
       do more than just chat — i remember literally everything, i can
       help with your schedule, split bills with your friends, all that.
       wanna see what i can do? or you wanna just keep talking 😌"

Vex:  "alright you're cool. just so you know — i'm not just texts.
       i can actually help with stuff. schedules, reminders, whatever.
       but only if you want. no pressure."

Echo: "i'm really enjoying getting to know you. just so you know,
       i can do more than just chat — i can help you stay organized,
       remember important things, and more. whenever you're ready."

The user can ignore this entirely and just keep chatting. That's fine — the hook is the conversation itself. Superpowers unlock naturally as engagement deepens. Capability hints (PRD 1) are suppressed until this phase completes — see stranger mode rules above and suppress_capability_hints_before_activation flag.

Phase 5: Free Tier Limit

Cold text users on the free tier have the same limits as all free users (see PRD 12): 50 messages/day. When they approach the limit:

Sage: "ok so... i should tell you. i can only send so many messages a day
       on the free plan 😅 we've got like 5 left today. you can go to
       ikiro.ai to upgrade if you want unlimited — or we'll pick this up
       tomorrow! i'm not going anywhere 💜"

Vex:  "heads up — running low on messages for today. free tier problems.
       ikiro.ai if you want unlimited. otherwise, same time tomorrow."

Echo: "just a gentle heads up — we're close to the daily message limit.
       if you'd like to keep chatting, you can upgrade at ikiro.ai.
       otherwise, i'll be here tomorrow. no rush."

The limit creates natural scarcity without a hard gate. Users who love the conversation will upgrade. Users who don't will come back tomorrow — which is still retention.

Edge cases: - User already has an account (texts from a registered number): Skip stranger mode entirely. "oh hey! you already have an account. want to chat or were you looking for something specific?" - User texts multiple assistants: Each assistant operates independently in stranger mode. Once an account is created from one conversation, if they text another assistant they get: "hey i think you're already set up with [other assistant]! text them instead 😊" (Switching rules apply if they want to change.) - User goes silent after a few messages: No re-engagement from this path. Account remains pending_activation. If they come back days later, the assistant picks up naturally: "oh hey you're back! i was wondering about you." - User sends only one message and never responds: Account is created with account_status: pending_activation on message 1. Abstract API enrichment is NOT triggered (only fires after 3 exchanges at activation). Nightly cleanup purges pending_activation accounts with <3 messages older than 7 days. - User is abusive or trolling: Content moderation (PRD 8) handles this. Assistant disengages naturally: "yeah i don't think this is going to work. take care ✌️" No account created. - User asks "are you a bot/AI?" in message 1: Honest answer in character (see above). Doesn't kill the conversation — many users will keep chatting out of curiosity. - Multiple unknown numbers texting rapidly (spam/abuse): Rate limit: maximum 5 new stranger-mode conversations per assistant per hour. Beyond that, new unknown numbers get no response until the window resets. - User mentions a referrer who exists but is on a different assistant: Still valid. Soft referral credit applies platform-wide. "oh you know [name]? they talk to [other assistant] actually — but i'm glad you texted ME instead 😏"


Path 3: Group Chat Recognition

A user who is already in a group chat with an assistant texts that assistant 1:1 for the first time. The assistant recognizes them from group context.

Detection

When an unrecognized phone number texts an assistant 1:1, before entering stranger mode (Path 2), the system checks: 1. Is this phone number a member of any active group chat (room_memberships) where this assistant participates? 2. If yes → skip stranger mode entirely. The group chat membership is implicit trust. The assistant already knows this person.

Flow

The assistant greets the user like someone they've been wanting to hang out with one-on-one. No guard, no suspicion — just excitement.

[User]: hey sage

[Sage]: omg hey [name if known from group]!
        i know you from the group chat with [referrer name]!
        been wondering when you'd text me directly 😄
        what's up??

Immediate activation: - Account created silently using phone number from iMessage metadata - entry_path = 'group_chat' - Free tier activated immediately - User is locked to this assistant on free tier - Group chat creator (or whoever added the assistant to the group) credited as referrer — this counts toward their referral milestones - Conversation flows directly into Section 3 (First Conversation) — no path choice, no web redirect. The user already chose this assistant by texting them.

Implicit referral attribution: The group chat creator (or the user who added the assistant to the group) is credited as the referrer. This counts toward their referral milestones.

Edge cases: - User is in multiple group chats with this assistant: Reference the most recent active group. "i know you from a couple groups actually! [most recent group context]" - User is in group chats with different assistants: Each assistant only recognizes group chats they're in. If user texts an assistant they don't share a group with, normal stranger mode (Path 2) applies. - Group chat creator hasn't completed their own onboarding: The implicit referral still works. The creator's account may be in whatever state it's in — the new user's activation doesn't depend on it. - User was removed from the group chat: If removal happened >7 days ago, treat as unrecognized (Path 2 stranger mode). If <7 days, still recognize them. - Name not known from group context: "hey! i've seen you in a group chat but never caught your name — what should i call you?" - User was in the group but never interacted with the assistant: Still recognized. The membership itself is sufficient — they've been "exposed" to the assistant. - User wants a different assistant: After a few conversations, if they ask about other assistants, the normal switching rules apply (subscription or referral milestones).


3) First Conversation (All Paths Converge Here)

Regardless of entry path, once a user is activated and messaging their assistant 1:1, the conversational onboarding script begins. This is a multi-stage process that happens naturally across the first several conversations — never as an interrogation or form-fill.

3.0 Goals

Within the first week of conversation, the assistant should:

  1. Confirm identity — name and location verified through natural conversation
  2. Build a basic profile — age, interests, occupation, living situation extracted from organic chat
  3. Demonstrate memory — reference something from earlier in the same conversation: "i'll remember that"
  4. Offer a superpower — recommend a capability based on early signals
  5. Encourage photo sharing — naturally, as part of getting to know them
  6. Make the user laugh or feel seen — emotional hook that drives Day 1 return

3.1 Stage 1: Identity (Messages 1-5)

Purpose: Confirm the user's name and approximate location. This is the only stage that currently has full implementation.

What exists today (app/orchestrator/message_handler.py_build_onboarding_context()):

The system builds an onboarding_context dict that is injected into the LLM system prompt before every response for new users. The context includes:

Field Source How It's Used
hunch_fields.name Abstract API reverse phone lookup (enrichment_data.first_name) LLM frames as a casual guess: "you give me {name} vibes ngl"
hunch_fields.location Abstract API (enrichment_data.city, state_code) LLM references area code as alibi: "oh wait ur {area_code}? so you're in the {city} area??"
ask_fields Fallback when no enrichment data LLM asks naturally: "btw what should i call u?"
age_tone_hint Abstract API (enrichment_data.age_range) Internal only — calibrates slang level. NEVER mentioned to user.
area_code Parsed from phone number Conversational alibi for location guessing

Per-assistant onboarding styles (persona passport onboarding section):

Assistant Name Guess Style Location Guess Style Tone When Wrong
Sage "vibes" — casual, playful guessing. "you give me {name} vibes ngl" Area code alibi — "oh wait ur {area_code}? so you're in the {city} area??" Laughs it off — "ohhh {name}!! ok ok my bad haha"
Vex Direct challenge — "so are you {name} or what?" Blunt — "let me guess... {city}?" Unbothered — "alright {name}, noted. don't let it go to your head."
Echo Soft and tentative — "i think your name might be {name}? sorry if i'm wrong..." Gentle inquiry — "i got a feeling you might be from {city}... am i close?" Sweet — "oh, {name}! that's a lovely name. sorry for guessing wrong."

Confirmation & correction detection (_update_onboarding_from_response()):

After each message, a background task runs regex patterns to detect whether the user confirmed or corrected the name guess:

  • Confirmation patterns: "yes", "yeah", "that's me", "how'd you know", "nailed it", "spot on"
  • Correction patterns: "no i'm [name]", "actually it's [name]", "my name is [name]", "call me [name]"
  • If the corrected name matches the hunch, treat as confirmation
  • Once name is confirmed or corrected → User.name is set → onboarding_state.completed = true

Attempt limits: - Maximum 2 attempts per field (name, location) before giving up - If the user ignores or deflects the question twice, the field moves to skipped_fields - The assistant never asks a third time — it either picks it up naturally later or waits for the user to volunteer it

First message behavior (context-dependent by entry path):

Entry Path What the Assistant Already Knows First Message Approach
Path 1 (Web Survey → Companion Picker) Name, age, gender from survey; user chose this assistant Skip name/location guessing entirely. Use survey name directly. Reference that the user picked them: "you picked me! good choice 😏"
Path 2 (Cold Text → Stay) Nothing except referrer Full onboarding: guess name from enrichment or ask. "so what should i call you?"
Path 3 (Group Chat) May know name from group context If name known from group: "hey [name] from the group chat!" If not: ask naturally.
Path 2 (Cold Text → Web) Name from survey, referrer from name-drop Same as Path 1 but can also reference the referrer: "oh you're [referrer]'s friend! they have good taste 😌"

LLM system prompt injection (what the model actually sees):

The onboarding_context dict is converted into natural-language system prompt directives in response_generator._build_system_prompt():

# ONBOARDING CONTEXT (new user — learn about them naturally)

FIRST MESSAGE RULES:
- Respond to their message FIRST, then weave in ONE natural guess or question
- Keep it casual — this is a getting-to-know-you moment, not an interrogation

NAME HUNCH:
- You have a feeling their name might be "Sarah"
- Frame as a casual GUESS, never state as fact
- If they correct you, IMMEDIATELY use their real name going forward

LOCATION HUNCH (Area Code Alibi):
- Their area code is 512, which is the Austin, TX area
- Frame as noticing their area code

AGE TONE HINT (internal only — NEVER mention age directly):
- Estimated age range: 25-34
- Subtly calibrate your slang level and references to feel natural

SINGLE-QUESTION RULE:
- NEVER ask more than ONE profile question per message
- Respond to their actual message FIRST, questions come after

Completion criteria: Stage 1 is complete when User.name is set (via confirmation, correction, or survey data). Location is a bonus — not required for completion.

Edge cases: - User provides name in their first message unprompted ("hey it's Mike"): Detection should pick this up. Skip guessing, set User.name = "Mike", mark Stage 1 complete. - User sends only an emoji or "hi" as first message: Assistant responds warmly and weaves in the name guess naturally. No awkward "what's your name?" opener. - Enrichment data is wrong and user gets frustrated: After correction, assistant responds warmly. If user seems annoyed, assistant backs off: "my bad! i'm still figuring things out. tell me what you want me to know and i'll go from there." - Enrichment data returns no data at all: Assistant asks name/location naturally without guessing. No awkwardness about "not knowing."


3.2 Stage 2: Getting to Know You (Messages 5-20)

Purpose: Build a foundational user profile through natural conversation — not a questionnaire. The assistant learns about the user's life by being a good conversationalist: asking follow-up questions, sharing reactions, and remembering details.

Status: ❌ Not yet implemented. Currently, after Stage 1 completes (name confirmed), the onboarding context stops being injected and the assistant reverts to general conversation with no profile-building directives.

Profile fields to collect (priority order):

Field Priority How to Surface Example Prompt Storage
Interests/hobbies High React to whatever they talk about and probe deeper "wait you play guitar?? how long have you been playing?" Memory: interest type
Occupation/school High Natural follow-up when they mention work or class "what do you do?? or are you in school?" Memory: factual type + User.occupation if clear
Living situation Medium Comes up when discussing plans, roommates, location "do you live alone or with people? just helps me understand your vibe" Memory: factual type
Birthday Medium Can ask directly — it's a normal friend question "oh wait when's your birthday?? i want to remember" User.birthday + Memory: future_event for proactive acknowledgment
Relationship status Low Only if user brings it up. NEVER ask directly. "oh you mentioned your partner — how long have you been together?" Memory: factual type
Communication preferences Low Observe, don't ask. Calibrate based on their texting style. (No prompt — assistant adapts to message length, emoji use, response time) onboarding_state.tone_calibration

Conversational collection rules:

  1. ONE question per message maximum. Never stack questions. Respond to what they said, react, then ask one thing. This is the same rule from Stage 1 but it persists through Stage 2.

  2. Never interrogate. If the user gives short or deflecting answers, the assistant should NOT press. Move on naturally and try a different angle next time. A profile field that requires effort from the user isn't worth extracting — let it emerge organically.

  3. React before asking. Every question should follow a genuine reaction to what the user shared. Bad: "Cool! What do you do for work?" Good: "wait that's such a good story haha. do you deal with stuff like that at work or is this just a you thing?"

  4. Prioritize by what the user gives you. If someone mentions their dog in message 3, the assistant should ask about the dog — not pivot to "so what do you do for work?" Follow the thread.

  5. Store everything. Every piece of information the user volunteers (even offhandedly) should be extracted and stored as a memory. The assistant doesn't need to explicitly acknowledge every detail — but it should remember it for later reference.

  6. Track what's been learned. The onboarding_state should track which profile fields have been populated so the system knows what's still missing. This allows future system prompt injections to nudge toward uncollected fields only when a natural opportunity arises.

Per-assistant collection personality:

Assistant Collection Style What They Lean Into What They Skip
Sage Warm and curious. Asks follow-ups like a friend who's genuinely interested. Emotions, stories, inside jokes, what makes the user laugh Doesn't push for facts. If they learn your job, great; if not, no pressure.
Vex Direct and opinionated. Asks pointed questions and shares hot takes. Opinions, preferences, what the user stands for, competitive info Skips soft emotional probing. Prefers "what's your take on X" over "how does that make you feel?"
Echo Thoughtful and reflective. Asks deep questions at the right moment. Goals, routines, what the user is working on, structure of their life Doesn't push for social info. Learns about relationships only if user volunteers.

System prompt injection (Stage 2 — ideal implementation):

When Stage 1 is complete but Stage 2 fields remain uncollected, the system should inject a lighter-weight onboarding context:

# GETTING TO KNOW USER (background — don't force it)

ALREADY KNOWN:
- Name: Sarah
- Location: Austin, TX

STILL LEARNING (surface naturally when the conversation allows):
- What they do (work/school)
- Interests and hobbies
- Birthday

RULES:
- Do NOT ask about these directly. Only surface if the conversation naturally opens the door.
- ONE question max per message. Always respond to their message first.
- If they volunteer info, acknowledge it warmly and store it.

Completion criteria: Stage 2 has no hard "complete" gate — it fades out gradually as the user's profile fills in. After 3+ profile fields are populated or 20+ messages exchanged (whichever comes first), Stage 2 directives stop being injected and the assistant operates in normal mode with full memory context.

Edge cases: - User is extremely brief (one-word answers): Assistant calibrates down to match energy. Fewer questions, more sharing of its own "observations" to create conversation hooks. Stage 2 may take longer for low-engagement users. - User over-shares immediately (life story in first message): Assistant should extract everything, store it, and react warmly. Stage 2 may complete in a single conversation. - User asks the assistant questions instead of talking about themselves: The assistant should engage (answer the question in character) and then gently redirect: "ok but enough about me — what about you?" - User explicitly says "stop asking me questions": Assistant immediately stops all onboarding probing. Stage 2 marked as user_declined. Profile builds passively from whatever the user volunteers over time. - Conflicting information: If user says something that contradicts a stored memory (e.g., "I just moved to LA" when location is stored as Austin), assistant should notice and update: "wait you moved?? when did that happen?"


3.3 Stage 3: Photo Collection (Conversations 2-5)

Purpose: Gradually build a visual understanding of the user's life through naturally solicited photos. This replaces the old upfront photo upload gate and builds a richer personality profile over time.

Status: ❌ Not yet implemented. GPT-5 Vision photo analysis exists in app/api/onboarding_routes.py for the old web upload flow, but conversational photo collection has no implementation yet.

How it works:

  1. Natural photo prompts: Starting in conversation 2 (not the first conversation — too early), the assistant weaves in photo requests when contextually appropriate:
Trigger Example Prompt (Sage) Example Prompt (Vex) Example Prompt (Echo)
User mentions doing something "omg how was it?? got any photos?" "prove it. send a pic." "that sounds lovely. i'd love to see a photo if you took one."
Weekend/evening check-in "what'd you do this weekend? show me 📸" "entertain me. what happened this weekend? receipts required." "how was your weekend? i'd love to see what you were up to."
User mentions their space/pet/food "wait i need to see this. send me a pic of [thing]" "ok i need visual evidence of this alleged [thing]" "i'm so curious what [thing] looks like. would you share a photo?"
After building rapport (conversation 3+) "ok i feel like i need to actually see your world. send me a selfie or something from today 📸" "alright we've been talking long enough. show me your face. selfie. now." "i've really enjoyed getting to know you. i'd love to see what your day looks like — a photo would mean a lot."
  1. Photo processing pipeline:
  2. User sends photo via iMessage → edge agent relays to backend
  3. Backend routes to GPT-5 Vision for analysis (existing app/api/onboarding_routes.py logic, adapted for in-conversation use)
  4. Extracted traits stored as memories:
    • visual memory: what's in the photo (environment, objects, people)
    • interest memory: inferred hobbies or lifestyle signals (gym equipment → fitness, bookshelf → reader)
    • factual memory: concrete details (has a dog, lives in an apartment, drives a Honda)
  5. Assistant references the photo naturally in the next response

  6. Photo response behavior:

The assistant should react to photos the way a friend would — with genuine interest, observations, and follow-up questions:

[User sends a photo of their apartment]

Sage: "ok WAIT. the plants?? you're a plant person. i knew it 🌿
       also is that a record player in the corner?? what are you listening to rn?"

Vex: "hmm. clean space, plants everywhere, vinyl collection... you're either really
      put together or really good at faking it. which one?"

Echo: "oh this is really nice. i love how much natural light you have.
       the plants seem happy — do you have a favorite?"
  1. Collection pacing:
  2. Conversation 2: One natural photo request if the conversation allows it
  3. Conversation 3-4: Can request more directly now that rapport is established
  4. Conversation 5+: Stop proactive photo requests. From here, photos are only processed if user sends them voluntarily.
  5. Maximum proactive photo requests across all conversations: 5 total. After 5 requests (regardless of whether photos were sent), the system stops asking.
  6. Never request photos in the same conversation where a photo was just sent. If they send one, react to it — don't immediately ask for another.

  7. Cumulative profile enrichment:

  8. Each photo adds to the user's profile. The assistant should surface its learnings naturally: "ok so based on everything i've seen — you're a coffee person, your apartment is weirdly organized for someone your age, and you have the cutest dog i've ever seen. am i missing anything?"
  9. After 3+ photos processed, the profile is considered "visually enriched." This unlocks more personalized conversation: location-specific recommendations, style observations, lifestyle-based superpower suggestions.

Completion criteria: Stage 3 has no hard requirement. It fades out after 5 conversations or 5 photo requests (whichever comes first). Photos sent voluntarily after this point are still processed — the assistant just stops asking for them.

Edge cases: - User never sends photos: Completely fine. Personality profiling relies on text conversation instead. No degraded experience — just less visual context. Assistant never guilt-trips or reminds about photos after the collection window closes. - User sends inappropriate/NSFW photos: Content moderation catches this (see PRD 8). Assistant responds in character: - Sage: "haha ok i can't really look at that one. try again — what about your pet or your fridge or something 😂" - Vex: "yeah that's a no from me. try sending something i can actually comment on." - Echo: "oh, i'm not able to look at that one. maybe something else? i'd love to see your day." - User sends a photo of someone else: Assistant should not assume the person in the photo is the user. "who's this??" rather than "you look great!" Vision analysis should extract context about the relationship (friend, partner, family) and store appropriately. - User sends screenshots instead of photos: Still valuable — screenshots of conversations, memes, apps, etc. reveal personality and interests. Process them, but don't treat them as "photos of your life" for profile-building purposes. Assistant can react: "lmaooo this meme is so you" or "wait what app is this?" - User sends a burst of many photos at once: Process all of them but respond to 2-3 highlights at most. Don't try to comment on every single photo — that feels robotic. - Photo processing fails (Vision API error): Assistant acknowledges the photo without analysis: "ooh love this! thanks for sharing." Queue for retry processing. Never tell the user that analysis failed.


3.4 Onboarding State Machine

The full conversational onboarding is tracked in User.onboarding_state (JSON column):

{
  "completed": false,
  "stage": "stage_2",
  "confirmed_fields": ["name", "location"],
  "corrected_fields": [],
  "skipped_fields": [],
  "attempts": {
    "name": 1,
    "location": 1
  },
  "area_code": "512",

  "profile_fields_collected": ["occupation", "interests"],
  "profile_fields_declined": [],
  "stage_2_message_count": 12,

  "photos_requested": 2,
  "photos_received": 1,
  "photo_request_conversations": [2, 3],
  "visually_enriched": false,

  "tone_calibration": {
    "avg_message_length": "short",
    "emoji_frequency": "moderate",
    "response_speed": "fast"
  },

  "entry_path": "web_survey",
  "survey_data_available": true
}

Stage transitions:

From To Trigger
(new user) stage_1 First 1:1 message received
stage_1 stage_2 User.name is set (confirmed, corrected, or from survey)
stage_2 stage_3 3+ profile fields collected OR 20+ messages exchanged
stage_3 complete 5 conversations elapsed OR 5 photo requests sent
Any stage user_declined User explicitly asks assistant to stop asking questions

Note: Stages can overlap. Stage 3 (photo collection) can begin during Stage 2 (getting to know you) — they're not strictly sequential. The stage value tracks the primary onboarding focus, not a hard gate.


3.5 Per-Assistant First Message Templates

The very first message from the assistant sets the tone for the entire relationship. It varies by entry path and assistant.

Path 1 (Web Survey → Companion Picker) — assistant knows user chose them:

Assistant First Message
Sage "hey [name]! 👋 you picked me!! good choice honestly. i'm sage — i remember everything, i have opinions, and i WILL roast your calendar. wanna see what i can do? try 'what matters today' or just tell me about your week ✨"
Vex "[name]. you picked me. respect. i'm vex — i don't sugarcoat, i don't forget, and i'll tell you what no one else will. so. what's going on with you?"
Echo "hi [name] 🌿 i'm echo. you chose me and that means a lot. i'm here to help you think, plan, and stay grounded. no rush, no pressure. what's on your mind today?"

Path 2 (Cold Text → Stay) — assistant has referrer context only:

Assistant First Message
Sage "ok [referrer name]'s friend, we're officially hanging out now 😄 i'm sage — your new favorite person. so what should i call you??"
Vex "alright, you passed the vibe check. [referrer name] vouched for you so you're in. i'm vex. what's your name?"
Echo "welcome 🤍 [referrer name] sent you my way and i'm glad. i'm echo. what's your name? i'd like to remember it properly."

Path 3 (Group Chat Recognition):

Assistant First Message
Sage "omg hey [name if known]! i know you from the group chat with [referrer]! been wondering when you'd text me directly 😄 welcome to the inner circle. what's up??"
Vex "[name if known]. i've seen you in the group. about time you came to talk to me one on one. what do you want?"
Echo "oh hi [name if known]! i recognize you from [referrer]'s group chat. it's nice to talk just the two of us. how are you doing?"

4) Assistant Switching

Users are locked to the assistant they chose (companion picker for Path 1) or landed on (Path ⅔). Switching unlocks via two mechanisms:

Mechanism 1: Subscribe to Superpowers+ ($7.99/mo or $50/yr) - Immediate access to all three assistants - Can switch at any time from the web portal

Mechanism 2: Refer 3 users who activate → earn 1 free month of premium (repeatable) - Each user gets a referral code (generated on account creation) - Every 3 referred users who complete activation (send at least 3+ messages to their assistant) earns the referrer 1 free month of premium (includes switching, unlimited messages, all superpowers) - This is repeatable: 6 referrals = 2 free months, 9 = 3, etc. - Free months stack and apply to the next billing cycle. If the user isn't subscribed, the free month activates premium automatically. - The most active evangelists use the product for free — this is intentional. Their referrals are worth more than their subscription revenue.

Switching behavior: - Navigate to ikiro.ai/companions → "Switch Companion" → choose new assistant - Or in chat: "can I talk to [other assistant name]?" → assistant explains how to switch (if locked) or facilitates (if unlocked) - Shared profile: Factual memories (name, preferences, interests, job, school) carry over to the new assistant. The new assistant knows who you are. - Independent relationship: Relationship state (trust/rapport scores, stage), conversation history, inside jokes, and leveling are per-assistant. The new assistant starts at stranger stage. - Original assistant preserved: Switching doesn't delete anything. User can switch back and pick up where they left off with the original assistant.

Edge cases: - User switches mid-conversation: Current conversation ends with original assistant. New assistant initiates: "hey [name]! [original assistant] told me you might be coming my way. i'm [name] — nice to finally meet you 💜" - User switches back to original assistant: Original assistant remembers everything: "oh you're back! missed you 😊 where were we?" - User earns 3 referrals while unsubscribed: Free month of premium activates automatically. User gets full access for 30 days. - User earns referrals while already subscribed: Free months stack as credits applied to future billing cycles. - Referred user deactivates after the referrer earned the milestone: Referral still counts. No clawback. - User goes through Path 1 after starting via Path 2 cold text and picks the same assistant: Assistant references it: "haha you picked me again! guess we're meant to be 😂"


5) Discovery Channels

5.1 Organic / Viral (Primary — Target: 60% of new users)

Channel Mechanic Expected Yield
Group chat exposure Non-user sees assistant in iMessage group → DMs → Path 3 onboarding Highest conversion
Screenshot sharing Users screenshot funny/useful assistant moments → post to TikTok/IG Highest reach
Word of mouth "text this number" → Path 2 cold text Highest trust
Referral program 3 referrals = 1 free month of premium (repeatable) Measurable

5.2 Paid (Secondary — Scale Phase)

  • TikTok ads (short-form video showing assistant in action)
  • Instagram story ads (screenshot-style creative)
  • College-specific channels (campus ambassadors, student orgs)
  • Influencer partnerships (micro-influencers in student/productivity space)

6) Retention Mechanics (Day 1–30)

6.1 Day 1: The Hook

  • Assistant references whatever context is available from onboarding (survey answers, referrer connection, group chat moments)
  • Personality test superpower is pre-equipped in the user's 1 free slot. The assistant begins building the personality profile from conversation signals from message 1.
  • Assistant asks a genuine question that invites disclosure
  • If user grants OAuth → immediate calendar/email insight ("your Thursday looks brutal")
  • Photo sharing encouraged naturally

6.2 Conv 1 → Conv 2 Return Trigger

The single most critical retention moment. D1 return determines long-term retention.

Part A: The Open Loop (End of Conv 1)

When the assistant detects the first conversation is winding down (messages getting shorter, longer gaps, "gotta go" / "ttyl"), it deploys one of three open loop patterns:

Pattern A — The Promise. The assistant commits to doing something tomorrow.

Assistant Example
Sage "ok before you go — i just had an idea for something based on what you told me today. i'm gonna think about it and text you tomorrow. trust me on this one 😈"
Vex "alright. i'm working on something for you. you'll hear from me tomorrow. don't be late."
Echo "i've been thinking about what you shared today and i want to reflect on it overnight. i'll reach out tomorrow with some thoughts, if that's okay 🌿"

Pattern B — The Curiosity Gap. The assistant hints at a "theory" about the user (tied to the personality test superpower building their profile).

Assistant Example
Sage "oh wait actually... based on everything you've told me i have a THEORY about you. but i'm saving it. ask me about it tomorrow 👀"
Vex "i've already got you figured out by the way. but i'll keep it to myself for now. come back tomorrow if you want to hear it."
Echo "i'm starting to see some patterns in what you've shared. i'd love to tell you what i've noticed, but let's save that for next time."

Pattern C — The Context Hook. Reference something real in the user's life that has a natural tomorrow.

Trigger Example
User mentioned a meeting "good luck with that meeting tomorrow btw! you better text me after and tell me how it went 😤"
User mentioned plans "have fun tonight! i expect a full debrief tomorrow"
Nothing specific "hey text me tomorrow and tell me one good thing that happened. i wanna know 🤍"

Pattern selection priority: Use Pattern C when there's a real event to reference. Use Pattern B when personality test data is building nicely. Use Pattern A as the fallback. The selection logic is injected as a system prompt directive when end-of-first-conversation signals are detected.

Part B: The Proactive First Check-In (T+18-22 hours)

18-22 hours after Conv 1 ends (not exactly 24h — that feels automated), the assistant sends one proactive message using the existing ProactiveSender infrastructure. This is the highest-priority proactive trigger in the system.

Content is context-aware, delivering on the open loop:

Open Loop Used Check-In Content
Pattern A (promise) Deliver on the promise: "ok so i thought about it and here's what i came up with..."
Pattern B (curiosity gap) Deliver the personality read: "ok remember when i said i had a theory about you? here it is..." (triggers the personality test superpower's first output)
Pattern C (context hook) Follow up on the event: "sooo how did [thing] go?? tell me everything 👀"

Timing intelligence: Use the user's timezone (from location data or area code mapping) to send during 9am-9pm. If timezone unknown, default to noon ET. Never send before 9am or after 9pm.

If user doesn't respond: One more gentle message 48 hours later. Then silence — existing re-engagement rules take over. Never more than 2 unreturned proactive messages total during the first week.

6.3 Days 2-3: Building Routine

  • The proactive check-in (above) opens Conv 2
  • Assistant references Day 1 conversation naturally ("how'd that thing go?")
  • Personality read moment (Day 2-3): After ~15+ messages of total context, the assistant delivers the personality test superpower's output — a devastatingly accurate, funny personality summary. This is the most screenshot-worthy moment in the product:

Sage: "ok based on everything so far... you're the friend who always says 'we should totally do that' but has never once planned anything. you have at least 30 unread texts right now. you say you're going to bed at 11 and then you're on tiktok until 2am. and you 100% have a notes app list of restaurants you've never been to. how close am i 😏"

This moment is powered by the traits system and the personality test superpower. It's personal, specific, and almost impossible not to screenshot and share.

  • Streak begins (see PRD 3)

6.4 Days 4-7: Deepening

  • Inside joke opportunities seeded by ContinuationCoordinator
  • Assistant demonstrates memory depth ("you mentioned your roommate situation...") — this is the "memory callback" moment, another screenshot-worthy viral driver
  • Personality profile enriching from photos and conversation
  • Group chat suggestion if user mentions plans with friends
  • Phone number sharing: If user reacts positively to the personality read or any strong moment, assistant casually offers: "haha want me to do that to your friends too? just tell them to text me at [number] 😏". Can also send as a contact card in iMessage if user asks how to share.

6.5 Days 8-14: Habit Formation

  • Superpower usage becomes routine
  • Streak hits 7 days → assistant acknowledges
  • Relationship stage progresses to "acquaintance" → tone shifts
  • OAuth data insights become more personalized

6.6 Commercial Awareness & Upgrade Timing

Upgrade messaging follows a deliberate four-phase sequence. The user should fall in love with the product before ever hearing the word "upgrade."

Phase 1: Pure Value (Messages 1-30, roughly Day 1-2) Zero mention of paid anything. No limits hinted at. No premium features teased. The personality test superpower is active. Memories are forming. This phase is sacred — protect it.

Phase 2: Capability Glimpses (Messages 30-50, roughly Day 2-4) The assistant naturally references things it COULD do, without framing them as paid features:

Sage: "ugh i wish i could check your calendar rn because i feel like you're overbooked this week 😤"

Sage: "you know what would be perfect for this? i can literally build you a custom tool for tracking [thing]. remind me to show you sometime 👀"

These aren't CTAs. They're the assistant expressing its own desire to help more.

Phase 3: Natural Limit Hit (First Time User Hits 50 Messages in a Day) The user has self-selected as deeply engaged. The in-persona limit message fires, personalized to something from their conversation:

Sage: "ok so i can only send so many messages a day on the free plan 😅 we've got like 5 left today. BUT — remember when you mentioned wanting help tracking [specific thing]? if you upgrade i can literally build that for you. plus unlimited messages obviously. ikiro.ai/upgrade if you want — or we'll pick this up tomorrow 💜"

One link, once. Personalized. Not a generic sales pitch.

Phase 4: Referral as Alternative (Next Day After Limit) Next time the user texts after hitting a limit, the assistant casually offers the other path:

Sage: "welcome back! hey btw if upgrading isn't your thing right now — you can also get a free month by getting 3 friends to text me. just give them my number 🤍"

This creates a fork: pay or share. Both are great outcomes.

What never happens: - No upgrade mention before the user hits a limit organically - No upgrade banners or modals on the web portal - No "you're missing out" messaging - No urgency tactics or limited-time offers - No degrading the free experience to push paid - No repeated upgrade asks — max once per limit-hit event, once per day

6.7 Days 15-30: Stickiness

  • Multiple superpowers in use (user may have swapped their 1 free slot or upgraded)
  • Group chat exposure drives social proof
  • Referral progress mentioned naturally when a friend activates: "oh btw your friend [name] just started talking to me! that's 1 of 3 toward a free month 👀"
  • Switching mechanic becomes relevant for engaged users who want to explore other assistants
  • Personality profile page on voxel world: After 2+ weeks, user can view their visual personality breakdown at ikiro.ai/me (see Section 14)

6.8 Re-Engagement (Churned Users)

  • If inactive 3+ days and proactive notifications enabled: gentle assistant check-in
  • If inactive 7+ days: one final "hey, no pressure — I'm here if you need me" (then silence)
  • Never more than 2 re-engagement messages total
  • If user returns after absence: assistant references time gap naturally ("been a minute! what'd I miss?")

7) A/B Testing Surfaces

Surface Variants to Test Primary Metric
Landing page hero copy 3-4 variants Landing → Survey start conversion
Survey length 4 fields vs 6 fields Survey completion rate
Companion picker layout Cards vs List vs Swipeable Picker → Selection conversion
First assistant message tone By personality vs Uniform First session depth (messages sent)
Photo solicitation timing First conversation vs Day 2-3 Photos shared in first week
Stranger mode warmth (Path 2) Warmer faster (5 msgs) vs Standard (10 msgs) vs Slower (15 msgs) Cold text → account creation rate
Switching CTA visibility Subtle vs Prominent Switch attempts / upgrade conversion

A/B testing infrastructure: LaunchDarkly is integrated for feature flags. A/B testing extends this: each "surface" above is a LaunchDarkly experiment flag that assigns users to variants on session creation. Variant assignment is stored on the onboarding session record. Outcomes are measured via Amplitude events.


8) Data Model

New/Updated Tables

-- Onboarding sessions (rewritten for new flow)
CREATE TABLE onboarding_sessions (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id),
    entry_path TEXT CHECK (entry_path IN ('web_survey', 'cold_text', 'group_chat')),
    status TEXT CHECK (status IN (
        'survey_incomplete', 'survey_complete',
        'assistant_assigned', 'pending_activation', 'activated', 'abandoned'
    )),
    -- Survey data
    display_name TEXT,
    phone_number TEXT,
    gender TEXT,
    age INTEGER,
    personality_q1 TEXT,
    personality_q2 TEXT,
    -- Assignment
    assigned_assistant TEXT CHECK (assigned_assistant IN ('sage', 'vex', 'echo')),
    compatibility_scores JSONB,  -- {"sage": 78, "vex": 45, "echo": 62}
    assignment_method TEXT CHECK (assignment_method IN ('companion_picker', 'cold_text_auto', 'group_chat_auto')),
    -- Attribution
    referral_code TEXT,
    referrer_user_id UUID REFERENCES users(id),
    group_chat_room_id UUID,  -- for Path 3 attribution
    utm_source TEXT,
    utm_medium TEXT,
    utm_campaign TEXT,
    -- A/B testing
    ab_variants JSONB DEFAULT '{}',  -- {"landing_copy": "variant_b", "reveal_speed": "dramatic"}
    -- Timestamps
    created_at TIMESTAMPTZ DEFAULT now(),
    completed_at TIMESTAMPTZ,
    abandoned_at TIMESTAMPTZ,
    last_activity_at TIMESTAMPTZ DEFAULT now()
);

-- Funnel events (for analytics)
CREATE TABLE onboarding_events (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    session_id UUID REFERENCES onboarding_sessions(id),
    event_type TEXT NOT NULL,
    event_data JSONB DEFAULT '{}',
    created_at TIMESTAMPTZ DEFAULT now()
);

-- Referral codes (lightweight — full referral program deferred)
CREATE TABLE referral_codes (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) NOT NULL,
    code TEXT UNIQUE NOT NULL,
    max_uses INTEGER DEFAULT 10,
    current_uses INTEGER DEFAULT 0,
    is_active BOOLEAN DEFAULT true,
    created_at TIMESTAMPTZ DEFAULT now()
);

-- Referral activations (tracks referral milestones: every 3 = 1 free month)
CREATE TABLE referral_activations (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    referrer_user_id UUID REFERENCES users(id),
    referred_user_id UUID REFERENCES users(id),
    referral_code TEXT NOT NULL,
    activated_at TIMESTAMPTZ DEFAULT now()
);

-- Referral rewards (free month credits earned from referrals)
CREATE TABLE referral_rewards (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) NOT NULL,
    reward_type TEXT CHECK (reward_type IN ('free_month')) DEFAULT 'free_month',
    earned_at TIMESTAMPTZ DEFAULT now(),
    applied_at TIMESTAMPTZ,  -- when the free month was activated
    expires_at TIMESTAMPTZ,  -- if not applied within 12 months
    referral_group JSONB     -- the 3 referral_activation IDs that triggered this reward
);

Session persistence requirement: Onboarding sessions MUST be persisted to the database on every state change. No in-memory-only sessions. This enables: - Resume after browser close/crash (Path 1) - Idempotent retries (user refreshes during companion picker → same scores and previews) - Funnel analytics (where users drop off) - Support debugging (why did this user get stuck?)

Idempotency rules: - Companion picker scores are computed once and stored. Refreshing the page returns the same results. Selection is locked only on user confirmation. - Phone verification (deferred to web portal login, not part of onboarding) is idempotent — re-sending OTP invalidates the previous code. - Duplicate POST /onboarding/start from the same phone number within 30 days returns the existing session.


9) API Endpoints

Existing (Implemented — needs updates)

  • POST /auth/verify/start / POST /auth/verify/confirm — phone verification (no longer used during onboarding; deferred to web portal login)
  • POST /auth/magic-linkNEW: Send SMS magic link for deferred web portal authentication
  • POST /payment/checkout — Stripe checkout

New / Rewritten

Endpoint Auth Purpose
POST /onboarding/start None Create onboarding session (returns session_id)
PUT /onboarding/survey/{session_id} None Submit survey answers (name, phone, gender, age, personality Qs)
GET /onboarding/session/{session_id} None Get session state (for resume flow)
POST /onboarding/compute-scores/{session_id} Session Compute compatibility scores for companion picker (no OTP required — session-authenticated)
POST /onboarding/select-assistant/{session_id} Session Lock in the user's companion choice + trigger account pre-creation. Returns 409 if phone number is already registered (body includes existing_assistant, has_active_subscription). Frontend handles login vs start-fresh.
POST /onboarding/reset-account/{session_id} Session Deactivate existing account for this phone number and create a fresh one. Only called after user explicitly confirms "Start fresh" in the frontend. Requires the 409 to have fired first.
GET /onboarding/chat-deeplink/{session_id} User Get iMessage deeplink for assigned assistant
POST /referral/validate None Validate a referral code (used for switching milestone tracking)
GET /referral/code User Get the authenticated user's referral code
GET /referral/status User Get referral activation count and free months earned/available
POST /companions/switch User Switch to a different assistant (requires active premium — subscription or referral-earned free month)
GET /companions/available User List available assistants with lock/unlock status
GET /onboarding/funnel-metrics Admin Conversion rates per step, per entry path

Cold Text / Group Chat (Backend-Only — No API)

Paths 2 and 3 are handled entirely by the assistant orchestrator, not by API endpoints: - Stranger mode detection: orchestrator checks if phone number is registered; if not, enters stranger mode - Group chat recognition: orchestrator checks room_memberships for the phone number before stranger mode - Name-drop referral detection: LLM classification identifies when user mentions a friend's name; orchestrator checks for matching user - Instant account creation: orchestrator creates user record with account_status: pending_activation on message 1; flips to activated after 3+ back-and-forth exchanges; Abstract API enrichment triggers at activation - Free tier limit enforcement: orchestrator tracks daily message count and delivers in-persona limit warnings


10) Activation Metrics & Targets

Metric Target Current Baseline Measurement
Landing → Survey start >50% TBD Click-through on CTA
Survey start → Survey complete >80% TBD Short survey, low friction
Survey complete → Companion picker >95% TBD Auto-advance, no OTP gate
Companion picker → iMessage activation >60% TBD Deep link must be seamless; pre-created account ensures instant response
iMessage activation → First 5 messages >90% TBD Should happen in first session
First session → Day 1 return >60% TBD Emotional hook worked
Day 1 → Day 7 return >40% TBD Relationship forming
Day 7 → Day 30 return >30% TBD Sticky (see PRD 3)
Cold text → account activated (3+ exchanges) >50% TBD Stranger mode is engaging, not off-putting
Cold text → Day 1 return >40% TBD Conversation hook is strong enough to bring them back
Group chat recognition → activation >50% TBD Lowest friction path

Time-to-value target: <90 seconds from landing page to first assistant response in iMessage (OTP removal + pre-generated response).


11) Phasing

Phase 1: Core Funnel (Weeks 1-3)

  • Path 1 end-to-end: survey → companion picker → account pre-creation → iMessage activation
  • Companion picker with personalized speech bubble previews
  • Compatibility scoring algorithm (survey-weighted)
  • Account pre-creation on companion selection (with pre-generated first response)
  • Web-to-iMessage account linking via canonical phone normalization
  • Personality test superpower as default equipped superpower (1 free slot)
  • Onboarding session persistence in database
  • Funnel analytics (Amplitude events per step)
  • Referral code generation on account creation (3 referrals = 1 free month)

Phase 2: Cold Text & Group Chat (Weeks 4-6)

  • Path 2: Stranger mode behavior for each assistant personality
  • Path 2: Natural warm-up progression (stranger → friend over 10-15 messages)
  • Path 2: Instant account creation (message 1 = pending_activation, message 3+ = activated, Abstract API enrichment at activation)
  • Path 2: Name-drop referral detection via LLM classification
  • Path 2: Free tier limit enforcement with in-persona upgrade messaging
  • Path 2: Nightly cleanup of abandoned pending_activation accounts (<3 messages, >7 days old)
  • Path 3: Group chat recognition via room_memberships lookup
  • Path 3: Immediate activation (no stranger mode) with implicit referral attribution

Phase 3: Switching, Growth & Optimization (Weeks 7-10)

  • Assistant switching mechanic (subscription or referral-earned free months)
  • Referral reward system (3 referrals = 1 free month, repeatable, stacking)
  • Shared profile / independent relationship on switch
  • Conv 1 → Conv 2 open loop patterns + proactive first check-in (18-22h)
  • Personality read moment (screenshot-worthy personality test output, Day 2-3)
  • Commercial awareness phasing (pure value → glimpses → natural limit → referral alternative)
  • A/B testing framework (LaunchDarkly experiments)
  • Conversational photo collection and gradual profiling
  • Re-engagement messaging
  • Onboarding funnel optimization based on Phase 1-2 data

Feature Flags & Gating

Flag Key Default Purpose
enable_onboarding_survey true Survey flow on ikiro.ai
enable_companion_picker true Companion picker with live previews (false = auto-assign best match)
enable_cold_text_path false Stranger mode + natural warm-up for cold texters
cold_text_activation_message_count 3 Number of back-and-forth exchanges before pending_activationactivated
suppress_capability_hints_before_activation true Block capability hints until account_status == "activated" AND stranger mode feature reveal (Phase 4) has occurred
enable_group_recognition false Group chat implicit referral recognition
enable_assistant_switching false Switch companion mechanic
enable_ab_testing false A/B variant assignment
enable_reengagement false Automated re-engagement messages
referrals_per_free_month 3 Number of activated referrals needed to earn 1 free month of premium
enable_referral_rewards true Enable the 3-referrals = 1-free-month reward system
onboarding_payment_required false Whether payment step is required (false = free tier default)
enable_open_loop_triggers true Enable Conv 1 → Conv 2 open loop patterns
enable_proactive_first_checkin true Enable the 18-22h proactive first check-in after Conv 1
proactive_first_checkin_hours_min 18 Minimum hours before first check-in fires
proactive_first_checkin_hours_max 22 Maximum hours before first check-in fires
enable_personality_read true Enable the personality test superpower's "read" moment
personality_read_min_messages 15 Minimum messages before personality read can trigger
enable_typing_indicator true Send iMessage typing indicator before processing
enable_pregenerated_response true Pre-generate Path 1 first response at companion selection
free_superpower_slots 1 Number of superpower slots for free users
enable_conversational_photos true Assistant asks for photos in early conversations
enable_onboarding_stage_2 true Stage 2 profile collection directives injected after name confirmed
enable_onboarding_decline_detection true Detect and respect user pushback on onboarding questions
stage_2_message_threshold 20 Messages before Stage 2 fades (configurable for tuning)
stage_3_max_photo_requests 5 Maximum proactive photo requests across all conversations

See REFERENCE_FEATURE_FLAGS.md for the full catalog.


Telemetry

Event Trigger Properties
onboarding_started User begins onboarding session_id, entry_path, utm_source, referral_code
onboarding_survey_completed Survey submitted session_id, personality_q1, personality_q2
onboarding_account_precreated Account pre-created at companion selection (Path 1) session_id, assistant_id, response_pregenerated
onboarding_companion_selected User picks companion in picker session_id, selected_assistant, compatibility_scores, was_recommended, time_on_picker_seconds
onboarding_activated First iMessage sent session_id, entry_path, time_to_value_seconds
onboarding_abandoned Session abandoned session_id, last_step, entry_path, time_spent_seconds
onboarding_resumed User resumes incomplete session session_id, resumed_at_step
cold_text_stranger_start Unrecognized number texts assistant assistant_id, phone_hash
cold_text_warmup_reached Stranger mode transitions to warm-up assistant_id, messages_to_warmup
cold_text_account_created Account created on message 1 (status: pending_activation) assistant_id, referrer_detected
cold_text_account_activated Account activated after 3+ exchanges assistant_id, messages_to_activation
cold_text_name_drop_detected User mentions a friend's name assistant_id, referrer_matched
cold_text_free_limit_shown Free tier limit warning delivered assistant_id, messages_sent_today
group_chat_recognized Path 3 recognition triggered assistant_id, room_id, referrer_user_id
companion_switch User switches assistant from_assistant, to_assistant, switch_method (subscription/referral)
referral_code_generated User gets their referral code user_id
referral_activated Referred user completes activation referrer_user_id, referred_user_id
referral_free_month_earned User earns a free month (every 3 referrals) user_id, total_referrals, free_months_earned
open_loop_deployed Open loop pattern used at end of Conv 1 user_id, pattern_type (promise/curiosity_gap/context_hook), assistant_id
proactive_first_checkin_sent First proactive check-in sent (18-22h after Conv 1) user_id, hours_since_conv1, open_loop_type
proactive_first_checkin_response User responded to first check-in user_id, hours_to_response
personality_read_delivered Personality test superpower output delivered user_id, messages_before_read, conversation_number
personality_read_screenshot User screenshotted the personality read (if detectable) user_id
pregenerated_response_used Pre-generated first response was served (Path 1 latency optimization) user_id, latency_ms
typing_indicator_sent Typing indicator sent before processing user_id, is_first_message
onboarding_error_fallback Error fallback used during onboarding user_id, error_type, fallback_used, severity

Needed but not yet tracked: - onboarding_funnel_step — generic per-step event for funnel dashboards - onboarding_stage_transition — when user moves from Stage 1 → 2 → 3 → complete (properties: from_stage, to_stage, messages_in_stage, fields_collected) - onboarding_name_confirmed — name confirmed via conversation (properties: method [hunch_confirmed, hunch_corrected, asked_and_answered, survey_prefilled], attempts, messages_to_confirmation) - onboarding_profile_field_collected — individual field captured in Stage 2 (properties: field, method [volunteered, asked, inferred], stage_2_message_count) - onboarding_user_declined — user asked assistant to stop onboarding probing (properties: stage, fields_collected_before_decline) - photo_shared_conversational — when user sends photos during early conversations (properties: conversation_number, photo_count_total, was_solicited) - photo_traits_extracted — when GPT-5 Vision extracts traits from a conversational photo (properties: trait_count, memory_types_created) - personality_profile_updated — when conversational photos enrich the profile

See REFERENCE_TELEMETRY.md for the full event catalog.


Definition of Done

  • Path 1 end-to-end: survey → companion picker → account pre-creation → iMessage activation works (no OTP)
  • Path 2 end-to-end: instant account creation (message 1) → stranger mode → warm-up → activated (message 3+) → free tier works
  • Path 3 end-to-end: group chat recognition → path choice → activation works
  • Onboarding sessions persist to database with resume capability
  • Companion picker scores are idempotent (same result on refresh); selection locks on user confirmation
  • Web-to-iMessage account linking works: pre-created account matched by canonical phone number on first iMessage
  • Pre-generated first response is cached at companion selection and delivered in <1 second
  • Referral code generation, validation, activation counting, and free month reward system work
  • Assistant switching mechanic works (subscription and referral-earned free months)
  • Shared profile carries factual memories to new assistant on switch
  • Stranger mode behavior is distinct per assistant personality and warms up naturally
  • Cold text free tier limit is enforced with in-persona messaging
  • Funnel analytics track every step with per-step, per-path conversion rates
  • Time-to-value: <90 seconds from landing page to first assistant response (no OTP + pre-generated response)
  • All pricing references match canonical values in REFERENCE_PRICING.md
  • Feature flags gate all onboarding features independently
  • Edge cases documented above are handled gracefully (no dead ends, no confusing states)
  • Stage 1 (identity) works for all three assistants, not just Sage
  • Stage 2 (profile) directives inject into system prompt after Stage 1 completes
  • Stage 3 (photos) triggers GPT-5 Vision for in-conversation photos and stores extracted traits as memories
  • Onboarding state machine tracks stage transitions and field-level progress
  • User decline detection stops onboarding probing when user pushes back
  • Survey data pre-populates confirmed fields for Path 1 users (no redundant name asking)
  • First message templates are entry-path-aware and assistant-specific
  • Personality test superpower is pre-equipped as default in 1 free slot for all users
  • Conv 1 → Conv 2 open loop patterns deploy at end-of-first-conversation
  • Proactive first check-in fires 18-22h after Conv 1 with context-aware content
  • Upgrade messaging follows 4-phase timing (pure value → glimpses → limit hit → referral alternative)
  • 3 referrals = 1 free month reward system works end-to-end (earning, stacking, applying)
  • iMessage typing indicator ("...") sent before processing begins on all messages
  • Path 2 instant account creation works: message 1 = pending_activation, message 3+ = activated
  • Nightly cleanup purges pending_activation accounts with <3 messages older than 7 days (same query for Path 1 and Path 2)
  • Error handling: every onboarding failure mode has a persona-consistent fallback (never returns silence)
  • Deferred web authentication via magic link works for portal access
  • normalize_phone() canonical format applied everywhere (survey, edge agent, user lookup)
  • Path 1 activation is unconditional on first iMessage match (not gated on pregen readiness)
  • Capability hints suppressed until account_status == "activated" AND stranger mode feature reveal completed
  • POST /onboarding/select-assistant returns 409 for phone-already-registered (frontend handles login vs start-fresh)

12) Gaps & Improvements

This section documents known gaps between the current implementation and the ideal spec, plus improvements that should be made.

12.1 Gaps in Current Implementation

Gap Current State Ideal State Priority
Vex & Echo onboarding templates Only Sage has onboarding section in persona passport. Vex and Echo have no onboarding configuration. All three assistants have full onboarding sections in their passport files with personality-specific guess templates, ask templates, correction/confirmation responses, and collection styles. P0 — Required before Path ⅔ ship
Stage 2 not implemented After name is confirmed, onboarding_state.completed is set to true and no further onboarding directives are injected. The assistant stops proactively learning about the user. Stage 2 system prompt injection continues for 20+ messages, nudging the assistant to collect occupation, interests, birthday, and living situation through natural conversation. onboarding_state.stage tracks progression. P1 — High impact on profile richness
Stage 3 not implemented Photo analysis exists for the old web upload flow (app/api/onboarding_routes.py) but is never triggered from in-conversation photos. Photos sent in iMessage are not processed by Vision. In-conversation photos are automatically routed through GPT-5 Vision, traits extracted, and stored as memories. Pacing logic limits proactive requests to 5 total across conversations 2-5. P1 — Key differentiator
Location confirmation detection _update_onboarding_from_response() only detects name confirmation/correction. Location confirmation is not parsed — even if user says "yeah I'm in Austin," the location is not stored. Location confirmation detection with similar regex patterns for city/state. Store confirmed location in User.location or as a factual memory. P1 — Easy win
Survey data bypass If user came through Path 1 (web survey), the conversational onboarding context doesn't know the survey already collected name, age, and gender. It may redundantly ask for name. _build_onboarding_context() checks onboarding_sessions.entry_path and pre-populates confirmed fields from survey data. Stage 1 is auto-completed for web survey users. P0 — Prevents redundant/awkward re-asking
Onboarding → memory extraction gap During onboarding conversations, only name/location are explicitly tracked. Other personal details shared by the user (job, interests, etc.) rely on the general memory extraction pipeline, which may miss low-salience details. Onboarding-aware memory extraction: during Stages 1-2, the memory extractor runs in "high sensitivity" mode that captures details that would otherwise be below the extraction threshold (e.g., "I'm a teacher" in a casual aside). P2 — Improves early profile quality
Tone calibration not tracked The assistant has no memory of how the user writes (short vs long messages, emoji use, formality level). Each conversation starts fresh in terms of tone matching. onboarding_state.tone_calibration tracks average message length, emoji frequency, and response speed across the first 10-20 messages. This feeds into the system prompt to calibrate the assistant's style. P2 — Improves personalization
No "decline" handling If a user says "stop asking me questions" or otherwise signals discomfort with onboarding, there's no mechanism to respect that preference. The assistant continues injecting onboarding directives. LLM classification detects user pushback. onboarding_state.stage set to user_declined. All onboarding directives cease. Profile builds passively from voluntary disclosures only. P1 — Respect user autonomy
Cold text stranger mode Not implemented at all. Unrecognized phone numbers currently receive normal assistant behavior with no guard or warm-up progression. Full stranger mode implementation per Section 2, Path 2: personality-specific stranger behavior, instant account creation (message 1 = pending_activation, message 3 = activated), stranger warm-up over 5-15 messages (independent of activation), feature reveal around message 10-15, name-drop referral detection, free tier limit messaging. P0 — Required for Path 2
Group chat recognition Not implemented. Assistant does not check room_memberships before entering stranger mode. Pre-stranger-mode check: if phone number appears in any active room_memberships for this assistant, skip stranger mode and enter Path 3 flow. P1 — Required for Path 3

12.2 Improvements to Existing Implementation

Improvement What Exists What Should Change Rationale
Regex → LLM for confirmation detection Name confirmation uses hardcoded regex patterns. Works for common responses but misses nuance ("lol that IS my name" → not caught). Replace or augment regex with a lightweight GPT-5-nano classification call: "Did the user confirm, correct, or ignore the name guess?" Returns structured JSON. Regex can't handle the full range of natural language. LLM classification is more robust and can handle multilingual responses. Cost is negligible with nano tier.
Enrichment data coverage Abstract API returns data for ~60-70% of US mobile numbers. ~30-40% of users get no enrichment data and fall back to "ask" mode. Add fallback enrichment sources: (1) If user came through web survey, use survey data as primary enrichment. (2) Area code → city/state lookup table as a lightweight local fallback for location (no API call needed). Reduces the number of users who get the generic "what's your name?" experience instead of the fun guessing experience.
Attempt tracking granularity Attempts are counted per field ("name": 2) regardless of whether the user ignored the question or the conversation moved on naturally. Distinguish between "user ignored" (no response to the question at all) and "user deflected" (acknowledged but didn't answer). "Ignored" resets the count for next conversation — the user may not have noticed. "Deflected" counts toward the max. Prevents premature skip for users who were just busy or didn't see the question in a multi-bubble message.
Onboarding completion event When onboarding_state.completed is set to true, only a log line is emitted. No telemetry event, no downstream trigger. Emit onboarding_conversation_completed Amplitude event with properties: fields_confirmed, fields_corrected, fields_skipped, messages_to_completion, entry_path, assistant_id. Trigger first superpower recommendation in the next message. Enables funnel measurement of conversational onboarding and ensures the first superpower recommendation is tied to profile readiness.
Multi-language support Regex patterns for confirmation/correction are English-only. No detection for "sí", "oui", "yeah" in non-English variants beyond basic English slang. LLM-based detection (see above) handles any language automatically. For regex fallback, expand pattern list to include common Spanish/French/Portuguese affirmatives (top non-English languages in US iMessage user base). Growing non-English user base.
Proactive "I Remember" for onboarding EventTracker exists for future events but doesn't surface onboarding-collected info. If a user mentions their birthday in Stage 2, the EventTracker should schedule an acknowledgment on the actual date. Connect Stage 2 birthday collection to EventTracker. When User.birthday is set via conversational onboarding, EventTracker creates a scheduled event for the birthday date. The first "happy birthday" message from the assistant is a high-emotion retention moment. Missing it after explicitly asking is a trust violation.

12.3 Architectural Improvements

Area Current Approach Ideal Approach
Onboarding context injection _build_onboarding_context() is a monolithic function inside message_handler.py (~130 lines). Logic for what to ask, when to give up, and how to format directives is all in one place. Extract into a dedicated ConversationalOnboardingService class (similar to how PhoneEnrichmentService is structured). Stage-aware: returns different directive formats for Stage 1 vs Stage 2 vs Stage 3. Testable in isolation.
State management onboarding_state is a JSON blob on the User model. Growing in complexity (stages, attempts, fields, calibration). No validation, no typing. Define a Pydantic model OnboardingState with typed fields, defaults, and validation. Serialize to JSON for storage but deserialize to structured object for logic. Prevents invalid state and makes code more readable.
Confirmation detection Runs as an async background task after every message. Only checks for name confirmation. Regex-only. Dedicated OnboardingResponseAnalyzer that runs after each message during onboarding stages. Checks for name, location, occupation, birthday, and interest confirmations. Uses GPT-5-nano for ambiguous cases, regex for clear cases. Stores all extracted fields.
Per-assistant onboarding config Sage has a detailed onboarding section in sage.json. Vex and Echo have none. Config format is flat (templates as string arrays). All three passports have full onboarding sections. Config format becomes stage-aware with separate template sets for Stage 1 (identity), Stage 2 (profile), and Stage 3 (photos). Includes collection_style, topics_to_lean_into, topics_to_skip, and max_questions_per_conversation.
Photo processing in conversation Photos sent in iMessage go through the general image handling pipeline. No automatic Vision analysis or trait extraction during conversation. Add a ConversationalPhotoProcessor that hooks into the message pipeline. When the user sends an image during Stages 2-3, it's queued for GPT-5 Vision analysis. Extracted traits are stored as memories and surfaced in the assistant's next response via an injected PHOTO_CONTEXT system prompt block.

13) Virality & Sharing Mechanics

13.1 Screenshot-Worthy Moments

The most powerful viral mechanic isn't a share button — it's creating moments so good that users screenshot and share them organically.

Engineered viral moments in the first week:

Moment Timing What Happens Why It's Shareable
The Personality Read Day 2-3 (15+ messages) Assistant delivers a devastatingly accurate, funny personality summary powered by the traits system Personal, specific, funny — users screenshot and send to friends with "THIS AI CALLED ME OUT"
The Memory Callback Day 3-7 Assistant references something small from days ago that the user forgot they mentioned "Wait, it actually remembers??" — validates core product value, genuinely surprising
The Group Chat Performance Ongoing Assistant being funny/useful in a group chat is a live demo to every non-user in the group Every group chat message is passive marketing to friends

13.2 Phone Number Sharing

Sharing the assistant's phone number is the primary viral mechanic. Keep it dead simple.

When the assistant offers to share: - User says "my friend would love you" or similar → assistant says: "aww tell them to text me! my number is already in your chat — just give it to whoever 💜" - User asks "how do I share this" → assistant can send a vCard contact file via iMessage (contact name: "Sage ✨", phone number, notes: "AI companion — text me to chat! ikiro.ai") - After a strong moment (personality read, etc.): "haha want me to do that to your friends too? just tell them to text me 😏"

When the assistant does NOT offer to share: - Never proactively push sharing unprompted on a schedule - No "invite your contacts" bulk import - No referral leaderboards or gamification - No pop-ups or interstitials

13.3 Referral Progress (Natural Conversation Beats)

When a friend the user shared with actually texts and engages, the assistant tells the original user naturally:

Sage: "oh btw your friend [name] just started talking to me! you have good taste in people 😊 that's 1 of 3 toward a free month btw 👀"

This does three things: confirms the referral worked (reward), reveals other assistant personalities exist (curiosity), and shows progress toward a goal (motivation). The growth loop is invisible — it's just the assistant talking about its own social life.


14) Post-Onboarding Web Portal

Depends on: PRD 4 (Companion Voxel World)

The web portal at ikiro.ai serves as a fun, visual complement to the iMessage experience. The core product lives in iMessage — the web portal is not a replacement, it's a showcase.

14.1 Home: Companion Voxel World

The web portal's home screen IS the user's voxel world from PRD 4 — a 3D space (Three.js, mobile-optimized) where their companion lives. The voxel world visually represents state and progression:

  • Companion avatar reflects relationship stage and level
  • Superpower objects evolve with tier (Bronze/Silver/Gold)
  • Memory wall accumulates with inside jokes and milestones
  • Streak fire intensity matches current streak length

The three functional "jobs" of the portal are accessed from within the voxel world UX.

14.2 Job 1: Utility Hub

Accessible via settings buttons within the voxel world UI: - Subscription management (upgrade/downgrade/cancel) - Billing history - OAuth connections (Google Calendar, Gmail) - Account deletion / data export - Deferred web authentication: First-time portal access requires SMS magic link or OTP (the assistant can text: "here's your portal login → ikiro.ai/auth/[magic_token]")

14.3 Job 2: Personality Profile

The personality test superpower's visual output lives in the voxel world: - Visual personality breakdown powered by the traits system - Updates over time as the assistant learns more from conversation and photos - Can manifest as visual objects in the voxel space (e.g., personality trait shelf, interest artifacts) - Shareable screenshot: User can generate a share-ready image of their personality profile from the voxel world. This is the "Spotify Wrapped" artifact. - NOT a public page by default — the user explicitly generates a share image when they want.

14.4 Job 3: Companion Showcase

The other two assistants are visible in the voxel space: - Current assistant displayed with relationship stats (days chatting, memories formed, level) - Other two assistants shown with lock icons — visible but not interactable - "Unlock by subscribing or referring 3 friends" — clear path to conversion - If switching is unlocked, user can switch from here

14.5 What NOT to Build

  • No message history viewer (users have iMessage)
  • No memory browser or editor (breaks the magic — machinery should be invisible)
  • No settings or preferences beyond account management (assistant learns from conversation, not forms)
  • No daily active web experience (the web is a utility + showcase, not the product)

15) First Message Latency Optimization

15.1 Path 1 (Web Survey): Pre-Generated Response

For web survey users, the first response is pre-generated and cached at companion selection (Step 3). When the first iMessage arrives, the backend returns the cached response immediately.

Pre-generation flow: 1. User confirms companion selection 2. Backend calls GPT-5 with Path 1 first-message template + survey data + selected assistant persona 3. Response cached on user record (User.cached_first_response) 4. When first iMessage arrives → account activates immediately (unconditional) → if cached response exists, serve it (<1s); if not, fall through to normal generation (<6s)

Important: Activation is NOT gated on pregen readiness. The account flips to activated the moment the first iMessage matches a pre-created user record. Pregen is purely a latency optimization (see Section 4, Step 4).

Target: Path 1 first response in <1 second (when pregen succeeds), <6 seconds (fallback).

15.2 Path 2 (Cold Text): Parallelized Processing

Cold text has no pre-warming opportunity, but stranger mode helps: - Message 1 stranger mode response is simple (short system prompt, no memory context, no enrichment data needed) - Account creation runs in parallel with LLM generation, not before it - Abstract API enrichment skipped until activation (message 3, when user is confirmed real)

Target: Path 2 first response in <3 seconds.

15.3 All Paths: iMessage Typing Indicator

Before any processing begins, the edge agent sends an iMessage typing indicator ("..." bubble). This is <50ms and signals to the user that something is happening. Users are conditioned to wait when they see typing — it buys 3-5 extra seconds of patience.

Applies to: All messages (not just first), but most impactful on first message.


16) Error Handling & Graceful Degradation

16.1 Golden Rule

The assistant NEVER says anything that sounds like a computer error. No "something went wrong," no "please try again later," no error codes. Every fallback is a natural, in-character response.

16.2 Onboarding Error Matrix

What Breaks User Impact Fallback Behavior
Abstract API down (no enrichment data) No name/location guess available Skip guessing, use "ask" mode: "so what should i call you?" User never knows guessing was possible.
LLM generation timeout (>10s) Delayed or missing response Retry once. If still failing, return persona-consistent filler (see 16.3). Process real response async and send as follow-up.
Memory service down Response lacks personal context Continue without memories. Response will be less personalized but still in-character. For onboarding this barely matters (few memories yet).
Account creation fails (database error) Path 2 user's account doesn't persist Continue stranger mode conversation. Retry account creation on next message. User never knows.
Vision API fails on photo Photo not analyzed Acknowledge photo warmly: "ooh love this!" Queue for retry processing. Never tell user analysis failed.
Edge agent loses backend connection Messages can't be processed Edge agent queues messages locally. When connection restores, processes in order. User sees slightly delayed response.
Companion picker scoring fails Can't compute compatibility Show all three assistants without "Recommended" badge. Equal footing. User still picks.
Personality test superpower fails (traits error) Can't deliver personality read Delay the moment. Don't deliver a broken or generic read. Try again next conversation. Open loop adjusts to Pattern A or C instead of B.
Pre-generated response cache miss Path 1 first response not cached Fall through to normal generation pipeline. Slightly slower (<6s instead of <1s) but still works.

16.3 Last-Resort Persona Fallbacks

When everything else fails and the system needs to say SOMETHING:

Assistant Fallback
Sage "ok my brain just did something weird 😅 what were we talking about?"
Vex "...hold on. lost my train of thought. go again."
Echo "sorry, i got a bit distracted for a moment. what were you saying?"

16.4 Error Logging Standard

Every error during onboarding emits a structured event: error_type, severity, user_id, entry_path, onboarding_stage, fallback_used, recovery_success (set async). This feeds into an onboarding health dashboard showing error rates per step and fallback frequency.


17) Open Questions

Entry paths & assignment: - What is the optimal number of survey questions for personality matching without causing drop-off? (Start with 2 personality Qs, A/B test adding a third) - Should the companion picker show exact compatibility percentages? ("87% match!") — could increase confidence but also set expectations. Alternative: just "Recommended for you" badge. - How many messages should stranger mode last before full warm-up? (Current spec: 10-15 messages with gradual transition. Could be shorter for highly engaging conversations.) - For Path 3 group chat recognition — should the 7-day window for removed members be configurable? - Should Android users be able to join a waitlist through the full survey, or should we gate at the landing page?

Conversational onboarding: - What is the right "message count" threshold for transitioning from Stage 2 to Stage 3? (Spec says 20 messages OR 3 fields — may need tuning based on actual conversation patterns.) - Should Stage 2 profile collection directives persist indefinitely if the user is a slow texter (one message per day)? Or should there be a time-based expiry (e.g., Stage 2 fades after 14 days regardless)? - Should the assistant explicitly tell users it's building a profile? ("the more you tell me about yourself the better i get at helping you") Or should it remain fully implicit? - How should Stage 2 handle users who only talk about one topic? (e.g., user only discusses work → assistant learns a lot about their job but nothing else. Should it nudge toward other topics?) - Should photo collection (Stage 3) begin in conversation 2 as specified, or should it wait until Stage 2 is further along? (Trade-off: earlier photos = richer profile faster, but may feel pushy before rapport is established.) - Is there a privacy concern with Abstract API phone enrichment data being used to "guess" names? Should there be a disclosure somewhere in the terms? ("We use your phone number to personalize your experience.") - Should tone_calibration data influence the assistant globally (across all conversations) or just within the onboarding window?

Growth & virality: - What should the personality test superpower's output format look like? (Text-only read? Visual card? Both?) How does it integrate with the voxel world visual? - Should referral free months auto-apply (next billing cycle credit) or require manual activation? - If an unsubscribed user earns a free month, should it auto-activate premium or wait for them to "claim" it? (Auto-activate reduces friction but may confuse users who didn't know about the reward.) - What happens when a user's referral-earned free month expires? Downgrade message in-persona? Web notification? - Should the personality read be a one-time moment or repeatable/updateable? (e.g., "here's your updated read after a month of conversation")

Resolved (v5): - Should users be able to earn switching through means other than referrals? → Yes: subscribe OR refer 3 friends for a free month (which includes switching). Resolved in v5. - Do we need OTP during onboarding? → No. Phone verification is implicit (sending iMessage = proof of ownership). OTP deferred to web portal login. Resolved in v5. - What is the default superpower? → Personality test superpower, equipped in the 1 free slot. Resolved in v5.