Proactive onboarding

Stop greeting new users with "hey, what's up." Drive them to a language and a first win β€” in the first thread.

4 min

The problem

A new handle texts hey. The buddy replies "hey! what's up πŸ˜„" and waits. It never finds out what language they want, never seeds the profile, never teaches anything. The user churns before learning a single word.

Worse, the profile is seeded to the instance default (Korean) β€” so a Japanese learner gets a Korean buddy and there's no path to fix it. Onboarding has to set the language, not assume it.

What to capture

Three things, asked one at a time β€” never a form. Each maps to a profile field and changes how the buddy talks.

FieldWhy we need itHow we get it
Target language+ native, level Drives the entire system prompt. Without it the buddy can't teach the right language. Q1, asked first. Set targetLanguage the moment they answer β€” even if they drop off after.
Goal / why A partner, a trip, moving abroad, work, fun β€” tells the buddy what "done" feels like and what to prioritize. Q2. Save as a structured goal so it survives the rolling history window.
Interests So vocabulary lands in conversations they actually care about (gaming, food, music) instead of textbook scenes. Q3. Save as interests[]; reused forever as conversation fuel.

Level isn't a question β€” it's felt out over the first few exchanges and refined via the existing level field. Asking "what's your level?" gets useless self-assessments.

The proactive flow

The buddy leads. It introduces itself with a clear purpose, asks the three questions across separate texts, then β€” critically β€” teaches one phrase immediately so the very first session ends in a win.

1
Open with identity + purpose, not "what's up"
One warm line on what it is (a texting buddy that teaches by chatting), then straight into Q1: which language?
2
Get the language β†’ set it now
Write targetLanguage immediately on the answer. If they ghost here, they're still saved correctly for next time.
3
Ask the "why"
Travel / partner / work / moving / fun. React warmly, save the goal.
4
Ask interests
"What do you usually talk about / what're you into?" Save interests[].
5
Deliver a first win, then flip onboarded = true
Teach one phrase tied to their goal, ask them to fire it back. Learning starts in thread #1 β€” that's the whole point.

Tone rules: one question per text, never stack them. Confirm + react before the next ask (it's a friend, not an intake form). If they answer two things at once, skip ahead β€” don't re-ask.

Sample thread

hey
hey!! i'm your language buddy 🌏 i help you actually pick up a language just by texting back and forth β€” no lessons, no flashcards. quick one to start: which language do you wanna learn?
korean
korean, love it πŸ‡°πŸ‡· what's pulling you toward it β€” travel, someone special, work, moving there… or just for fun?
my gf is korean lol
ok that's the best reason πŸ₯Ή we'll get you to where you can surprise her. last thing then we dive in β€” what are you usually into / talk about? (food, gaming, music, whatever) so i teach you with stuff you actually care about
gaming and food mostly
perfect. here's your first one πŸ‘‡
μ•ˆλ…• (annyeong) = "hi / hey" β€” super casual, exactly what you'd say to your gf or friends. try firing it back at me πŸ‘€

β†’ profile now: targetLanguage: Korean, goal: "impress Korean gf", interests: [gaming, food], onboarded: true

What changes in code

Three files. The agent already builds the prompt from the profile β€” we add an onboarding branch and the write-paths to fill it.

core/memory.mjs β€” profile schema + write paths change

// remember(handle, note, updates)
if (updates.targetLanguage) p.targetLanguage = String(updates.targetLanguage).trim();
if (updates.goal)           p.goal = String(updates.goal).trim();
if (Array.isArray(updates.interests)) p.interests = updates.interests;
if (updates.onboarded === true)       p.onboarded = true;

core/agent.mjs β€” onboarding prompt + tool fields change

Trigger free

No new plumbing β€” a fresh handle starts onboarded: false (from memory.mjs:47), so the next inbound text drops straight into onboarding mode. Existing users keep their current behavior.

Open decisions