Stop greeting new users with "hey, what's up." Drive them to a language and a first win β in the first thread.
4 min
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.
Three things, asked one at a time β never a form. Each maps to a profile field and changes how the buddy talks.
| Field | Why we need it | How 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 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.
targetLanguage immediately on the answer. If they ghost here, they're still saved correctly for next time.goal.interests[].onboarded = trueTone 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.
β profile now: targetLanguage: Korean, goal: "impress Korean gf", interests: [gaming, food], onboarded: true
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 changeonboarded: false, goal: null, interests: []. Seed targetLanguage: null instead of the Korean default, so onboarding must set it.remember() β accept targetLanguage, goal, interests, onboarded (today it only writes name + level).// 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!profile.onboarded, use an ONBOARDING prompt that instructs the model to drive steps 1β5 and call remember as it learns each answer, ending by teaching one phrase and setting onboarded: true.goal + interests into the buddy prompt so conversations stay on-theme.REMEMBER_TOOL schema β add the four new optional fields so the model can fill them.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.
targetLanguage null vs. default? Recommend null β forces the ask, enables true per-user languages. The .env default becomes a fallback only.onboarded undefined β would trigger onboarding. Either backfill onboarded: true for known handles, or let it re-onboard once (cleaner profile).onboarded + targetLanguage. Easy follow-up, not v1.