[TERMINAL · SKILLS]
> mounting /skills...
> indexing skill manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Use Cases/Build an AI Chat App with Persistent Memory and Multi-Model Routing

Build an AI Chat App with Persistent Memory and Multi-Model Routing

Build a production AI chat application using Mem0 for persistent user memory across sessions, OpenRouter for multi-model routing and fallbacks, Langfuse for LLM observability and cost tracking, and Convex for a reactive real-time backend — creating a personalized AI assistant that remembers users and optimizes model costs.

Data & AI#memory#ai-agents#personalization#rag#long-term-memory
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 4 skills

Avg quality 91/100·All SAFE
>

mem0

v1.1.0

Adds persistent, personalized long-term memory to LLM apps and agents with Mem0, the open-source memory layer: it extracts facts from conversations, stores them in a vector database and retrieves them in later sessions. Use when a user asks to make a chatbot or agent remember user preferences across sessions, add memory to an AI app, or search, update and delete stored memories per user.

93/100 quality
1.91× impact
SAFE· stale score
View skill
>

openrouter

v1.1.0

OpenRouter is a unified API gateway that reaches hundreds of LLMs from OpenAI, Anthropic, Google, Meta and others through one OpenAI-compatible endpoint and one API key. Use when a user wants to call several models from one codebase, add model fallbacks, route to the cheapest or fastest provider, compare model cost and latency, use free models, or cap spend per key.

100/100 quality
9.30× impact
SAFE· stale score
View skill
>

langfuse

v1.1.0

Langfuse is an open-source LLM engineering platform that records traces of LLM calls, versions prompts, scores outputs and runs experiments on datasets. Use when asked to add observability to an LLM or RAG app, trace OpenAI or Vercel AI SDK calls, track token cost and latency, manage prompts outside the code, score outputs, build an evaluation dataset, or self-host Langfuse.

93/100 quality
10.00× impact
SAFE· stale score
View skill
>

convex

v1.1.0

Convex is a reactive backend where the database, server functions and file storage are written in TypeScript and query results update live in every connected client. Use when building a Convex app: schema and indexes, queries, mutations and actions, useQuery and useMutation in React, auth, pagination, file uploads, scheduled functions and cron jobs, full-text and vector search. Trigger words: convex, reactive backend, convex schema, convex functions, npx convex dev.

77/100 quality
1.50× impact
SAFE· stale score
View skill
$

Kai is building an AI customer success platform where each customer gets a personalized AI assistant. The assistant must remember past conversations, customer preferences, and account details across sessions — not just within a single chat thread. It should route simple questions to cheap models and complex ones to powerful models, while tracking quality and cost.

Kai uses Mem0 for persistent memory, OpenRouter for multi-model routing, Langfuse for observability, and Convex for the real-time backend.

Step 1: Convex Backend with Real-Time Chat

typescript
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";

export default defineSchema({
  conversations: defineTable({
    userId: v.string(),
    title: v.string(),
    lastMessageAt: v.number(),
  }).index("by_user", ["userId"]),

  messages: defineTable({
    conversationId: v.id("conversations"),
    role: v.union(v.literal("user"), v.literal("assistant")),
    content: v.string(),
    model: v.optional(v.string()),
    cost: v.optional(v.number()),
    latencyMs: v.optional(v.number()),
  }).index("by_conversation", ["conversationId"]),
});
typescript
// convex/chat.ts — Real-time chat with AI
import { action, mutation, query } from "./_generated/server";
import { v } from "convex/values";

export const sendMessage = action({
  args: { conversationId: v.id("conversations"), content: v.string(), userId: v.string() },
  handler: async (ctx, args) => {
    // Save user message
    await ctx.runMutation(api.chat.addMessage, {
      conversationId: args.conversationId,
      role: "user",
      content: args.content,
    });

    // Step 1: Retrieve relevant memories
    const memoriesResponse = await fetch(`${process.env.API_URL}/api/memories/search`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ userId: args.userId, query: args.content }),
    });
    const memories = await memoriesResponse.json();
    const memoryContext = memories.map((m: any) => `- ${m.memory}`).join("\n");

    // Step 2: Get recent messages for conversation context
    const recentMessages = await ctx.runQuery(api.chat.listMessages, {
      conversationId: args.conversationId,
      limit: 10,
    });

    // Step 3: Classify complexity for model routing
    const complexity = classifyComplexity(args.content, recentMessages);
    const model = complexity === "simple"
      ? "openai/gpt-4o-mini"              // $0.15/M tokens
      : complexity === "complex"
        ? "anthropic/claude-sonnet-4-20250514"       // $3/M tokens
        : "openai/gpt-4o";                // $2.50/M tokens (default)

    // Step 4: Generate AI response via OpenRouter
    const start = Date.now();
    const aiResponse = await fetch("https://openrouter.ai/api/v1/chat/completions", {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${process.env.OPENROUTER_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        model,
        messages: [
          { role: "system", content: `You are a helpful customer success assistant.

What you know about this customer:
${memoryContext || "No prior context available."}

Be personalized and reference what you know about them naturally.` },
          ...recentMessages.map(m => ({ role: m.role, content: m.content })),
          { role: "user", content: args.content },
        ],
      }),
    });

    const data = await aiResponse.json();
    const assistantContent = data.choices[0].message.content;
    const latencyMs = Date.now() - start;

    // Step 5: Save assistant message
    await ctx.runMutation(api.chat.addMessage, {
      conversationId: args.conversationId,
      role: "assistant",
      content: assistantContent,
      model,
      latencyMs,
    });

    // Step 6: Store new memories from this exchange
    await fetch(`${process.env.API_URL}/api/memories/add`, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        userId: args.userId,
        messages: [
          { role: "user", content: args.content },
          { role: "assistant", content: assistantContent },
        ],
      }),
    });

    // Step 7: Log to Langfuse for observability
    await fetch(`${process.env.API_URL}/api/trace`, {
      method: "POST",
      body: JSON.stringify({
        userId: args.userId,
        model,
        complexity,
        latencyMs,
        inputTokens: data.usage?.prompt_tokens,
        outputTokens: data.usage?.completion_tokens,
        memoriesUsed: memories.length,
      }),
    });

    return { content: assistantContent, model, latencyMs };
  },
});

function classifyComplexity(content: string, history: any[]): "simple" | "medium" | "complex" {
  const wordCount = content.split(" ").length;
  const hasCode = /```|function|const |import |class /.test(content);
  const hasAnalysis = /analyze|compare|explain why|debug|architect/i.test(content);

  if (hasCode || hasAnalysis || wordCount > 100) return "complex";
  if (wordCount > 30 || history.length > 5) return "medium";
  return "simple";
}

Step 2: Memory API with Mem0

python
# api/memories.py — Memory service
from mem0 import Memory
from langfuse.decorators import observe

memory = Memory.from_config({
    "llm": {"provider": "openai", "config": {"model": "gpt-4o-mini"}},
    "vector_store": {"provider": "qdrant", "config": {"host": "localhost", "port": 6333}},
})

@observe()
async def search_memories(user_id: str, query: str):
    results = memory.search(query, user_id=user_id, limit=5)
    return [{"memory": r["memory"], "score": r.get("score", 0)} for r in results]

@observe()
async def add_memories(user_id: str, messages: list):
    memory.add(messages, user_id=user_id)

Step 3: Langfuse Observability

python
# api/trace.py — Track LLM usage and quality
from langfuse import Langfuse

langfuse = Langfuse()

def log_trace(data):
    trace = langfuse.trace(
        name="chat-response",
        user_id=data["userId"],
        metadata={"model": data["model"], "complexity": data["complexity"]},
    )
    trace.generation(
        name="llm-call",
        model=data["model"],
        usage={"input": data["inputTokens"], "output": data["outputTokens"]},
        metadata={"latency_ms": data["latencyMs"], "memories_used": data["memoriesUsed"]},
    )

Results

After 90 days, the platform serves 2,000 customers with personalized AI assistants.

  • Memory recall: 89% of responses reference relevant customer context from past sessions
  • Model cost optimization: 62% of requests routed to gpt-4o-mini ($0.15/M), 28% to gpt-4o ($2.50/M), 10% to Claude ($3/M)
  • Average cost per conversation: $0.08 (vs $0.31 with always-GPT-4o)
  • Response latency: p50 = 1.2s (mini), p50 = 2.8s (Claude) — tracked in Langfuse
  • Real-time UX: Messages appear instantly for all session participants via Convex reactive queries
  • Memory growth: Average 47 memories per customer after 90 days; auto-deduplication prevents bloat
  • Quality scores: 4.2/5 user satisfaction (Langfuse evaluation), up from 3.6 without memory

Related use cases