An AI that greets every user like a stranger every time they open the app is an AI that gets deleted.
One of the reasons ChatGPT retains users is that it remembers the thread of past conversations. But most AI features in Rork-built apps reset completely between sessions. Every launch is "Hello, nice to meet you" — and users notice.
This article covers a practical design pattern for building an AI that remembers: persisting user context locally in your Rork app and injecting it into LLM calls, so the AI feels like it actually knows the person it's talking to.
Why Context-Free AI Features Fail
Consider a travel planning feature in a Rork app. Day one, the user says "I want to plan a trip." The AI asks "Where to?" — reasonable.
Two days later, the same user comes back: "Can you add a few more ideas?" Without context, the AI replies: "Sure, what are we planning?" The user has to explain the whole situation from scratch.
Each repetition of that friction erodes trust. Users don't think "the app doesn't have memory." They think "this app is annoying." That's the behavioral outcome you're designing against.
Architecture Overview
Three components make this work:
- User Context Storage — persist conversations, preferences, and inferred facts on-device
- Context Builder — transform stored data into natural language a system prompt can use
- Injected LLM Call — pass the built context as system context in each API call
Implementation 1: Store User Context with MMKV
MMKV is faster than AsyncStorage and well-suited for this. With Rork Max, enable the native MMKV module in your Expo config first.
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV({ id: 'user-context' });
interface UserContext {
preferences: {
language: string;
topics: string[];
tone: 'casual' | 'formal';
};
recentConversations: Array<{
summary: string;
timestamp: number;
}>;
profileFacts: string[]; // e.g. "Lives in Tokyo", "Has a 3-year-old"
}
function saveUserContext(ctx: UserContext) {
storage.set('user_context', JSON.stringify(ctx));
}
function getUserContext(): UserContext | null {
const raw = storage.getString('user_context');
return raw ? JSON.parse(raw) : null;
}Implementation 2: Auto-Extract User Facts From Conversation
Rather than asking users to fill out a profile, extract personal facts from their messages automatically. Over time, this builds a lightweight model of who the user is.
async function extractAndSaveUserFacts(
userMessage: string,
existingFacts: string[]
): Promise<string[]> {
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: {
'x-api-key': process.env.EXPO_PUBLIC_ANTHROPIC_KEY!,
'anthropic-version': '2023-06-01',
'content-type': 'application/json',
},
body: JSON.stringify({
model: 'claude-haiku-4-5-20251001',
max_tokens: 256,
system: `Extract any personal facts from the user message that would be useful in future conversations.
Known facts already: ${existingFacts.join(', ')}
Return ONLY new facts as a JSON array. Return empty array if nothing new.
Example: ["Lives in Seattle", "Has two dogs", "Works in design"]`,
messages: [{ role: 'user', content: userMessage }],
}),
});
const data = await response.json();
try {
const newFacts = JSON.parse(data.content[0].text);
return [...new Set([...existingFacts, ...newFacts])];
} catch {
return existingFacts;
}
}Use Haiku here — not Opus or Sonnet. Fact extraction is a lightweight task, and at tens of thousands of calls, model choice matters for cost. Haiku handles this well at a fraction of the price.
Implementation 3: Inject Context Into Every LLM Call
Now use the stored context to construct a system prompt that makes the AI feel informed.
function buildSystemPrompt(ctx: UserContext | null): string {
if (!ctx) return 'You are a helpful assistant.';
const parts = ['You are a helpful assistant.'];
if (ctx.profileFacts.length > 0) {
parts.push(`\n[What you know about this user]\n${ctx.profileFacts.join('\n')}`);
}
if (ctx.recentConversations.length > 0) {
const recent = ctx.recentConversations
.slice(-3)
.map(c => `- ${c.summary}`)
.join('\n');
parts.push(`\n[Recent conversation context]\n${recent}`);
}
if (ctx.preferences.tone === 'casual') {
parts.push('\nUse a friendly, conversational tone.');
}
return parts.join('');
}
async function chat(userMessage: string): Promise<string> {
const ctx = getUserContext();
const systemPrompt = buildSystemPrompt(ctx);
const response = await fetch('https://api.anthropic.com/v1/messages', {
method: 'POST',
headers: { /* ... */ },
body: JSON.stringify({
model: 'claude-haiku-4-5-20251001',
max_tokens: 1024,
system: systemPrompt,
messages: [{ role: 'user', content: userMessage }],
}),
});
// Update context after each exchange
if (ctx) {
const newFacts = await extractAndSaveUserFacts(userMessage, ctx.profileFacts);
saveUserContext({ ...ctx, profileFacts: newFacts });
}
const data = await response.json();
return data.content[0].text;
}Privacy Matters — Give Users Control
Storing personal information on-device means your privacy policy needs to reflect it. More importantly, users need a way to clear it.
function clearUserContext() {
storage.delete('user_context');
}Add a "Reset AI memory" button in your settings screen. It takes five minutes to implement, and it turns a potential privacy concern into a trust signal. After adding this to one of my apps, I started seeing reviews that mentioned "respects privacy" — not something I'd seen before.
Cost vs. Impact
The additional API cost for this pattern is one Haiku call per conversation turn for fact extraction — roughly $0.0001 per exchange. That's essentially free.
The user experience gain is real: an AI that accumulates knowledge about the person using it. That shift from "generic tool" to "knows me" drives retention in ways that are hard to replicate with UI polish alone.
For indie developers building AI-powered apps with Rork, this is one of the highest-leverage improvements available — low implementation cost, meaningful differentiation, measurable retention impact.