Skip to content

Smart Prompt Suggestions

How LucidPal generates the four suggestion chips shown on the empty-chat screen.

Overview

When a user opens a new chat, four suggestion chips appear. Rather than static generic prompts ("What does tomorrow look like?"), LucidPal analyses the user's actual message history to surface prompts ranked by what they genuinely use the app for.

The entire pipeline runs on-device, synchronously, with no LLM call — typical runtime is under 1 ms.

Pipeline

SessionManager.loadIndex()        ← last 10 sessions (meta only, fast)
    └── loadSession(id:)          ← full messages loaded per session


    UsageAnalyzer.analyze()

            ├── Thread continuation detector
            ├── Domain classifier (per user message)
            └── Recency-weighted domain scorer


            UsageProfile { totalMessages, rankedDomains, unresolvedThread }


    SuggestedPromptsProvider.build()

            ├── 1. Thread continuation prompt  (if unresolved thread found)
            ├── 2. Calendar prompt             (always, time-aware)
            ├── 3–4. Top-ranked domain prompts (from usage history)
            └── Fallback pad to 4             (for new users / sparse history)

Domain scoring

Each user message is classified into one or more of eight domains:

DomainSignal keywords
calendarschedule, event, meeting, today, tomorrow, agenda, free slot
emailemail, gmail, inbox, reply, compose, unread
writingwrite, draft, message, text, letter, post, caption
habitshabit, streak, workout, exercise, log, track, gym, steps
taskstodo, task, list, priority, organize, plan, remind, checklist
notesnote, notes, summarize, capture, remember, jot, memo
researchwhat is, explain, how does, search, look up, tell me about
decisionsdecide, decision, should i, think through, advice, compare

A single message can match multiple domains. Unclassified messages default to .decisions (general thinking) so every message contributes signal.

Recency weighting

Each message is assigned a weight based on how old it is before adding to its domain score:

age < 1 hour   → 5.0×   (very hot signal)
age < 1 day    → 3.0×
age < 1 week   → 2.0×
age < 30 days  → 1.0×
older          → 0.3×

Final score per domain = Σ(weight) across all messages in that domain. Domains are ranked descending by score. Domains with no score appear at the end in a fixed fallback order so the prompt builder always has options.

Thread continuation

A session is considered unresolved if:

  • It has fewer than 3 user messages (barely started)
  • Its updatedAt is within the last 48 hours

When an unresolved session is detected, the first user message is extracted as an excerpt (capped at 38 characters, stripped of trivial greetings like "hi", "hello"). The suggestion reads:

Still on: [excerpt]…

This mirrors the pattern used by Pi and Replika — picking up active threads the user hasn't finished — and is the single highest-signal suggestion the system can make.

Thread continuation is inserted at position 0, before the calendar prompt, because it is the most personally relevant signal available.

Prompt slot assignment

Slot 0  Thread continuation   (only when unresolved thread detected)
Slot 1  Calendar prompt       (always — time-aware variant)
Slot 2  Top domain #1         (highest score, calendar excluded since slot 1 covers it)
Slot 3  Top domain #2 / fallback

Total is always capped at 4.

Calendar prompt variants

The calendar slot adapts to time of day and actual event data:

ConditionPrompt
Evening, tomorrow has events"Prep me for tomorrow"
Evening, tomorrow clear"What's on tomorrow?"
Morning, events today"Walk me through today's agenda"
Morning, nothing today"Do I have anything today?"
Midday, next event has title"Prep me for [event name]"
Midday, no upcoming events"When's my next free slot?"
Calendar not authorised"What should I focus on today?" / "Help me plan my time"

Domain prompt variants

Each domain maps to one or two time-aware prompts:

DomainMorningAfternoon/Evening
email"Draft an email for me""Help me write a reply"
writing"Help me write something""Help me write something"
habits"Log today's habits""How's my streak looking?"
tasks"Build my to-do list""What should I focus on next?"
notes"Summarize my recent notes""Summarize my recent notes"
research"Look something up for me""Look something up for me"
decisions"Help me decide""Help me think through something"

habits and notes are gated on FeatureContext.hasHabits / hasNotes — if the feature is off, the slot is skipped and the next ranked domain fills it.

New user experience

A user is considered new when totalMessages < 5. In this state, no history analysis is meaningful, so the system shows fixed discovery prompts:

  1. Calendar prompt (time-aware)
  2. "Log a habit for me" (if habits enabled)
  3. "Draft a quick message"
  4. "Help me decide something"

After 5 messages the full personalization pipeline activates on the next new-chat open.

Source files

FileRole
Services/SuggestedPromptsProvider.swiftFull pipeline: UsageAnalyzer, SuggestedPromptsProvider, UsageProfile
ViewModels/SessionListViewModel.swiftWires sessionManager into the provider factory
ViewModels/ChatViewModel.swiftCalls generateSuggestedPrompts() on model ready
Views/ChatView+Banners.swiftRenders EmptyStateContent with the four prompts
Views/SuggestedPromptsView.swiftChip UI with icon inference, skeleton loader, animations

Extending the algorithm

Add a new domain: add a case to UsageDomain, add keyword list to classify(), add a prompt variant to prompt(for:hour:features:).

Tap feedback signal (not yet implemented): when a user taps a suggestion, record (domain, timestamp) in UserDefaults. Multiply that domain's score by a feedbackBoost factor (suggested: 2×) during the next analysis pass. This would close the explicit feedback loop and reduce reliance on implicit usage inference.

Time-slot bucketing (not yet implemented): instead of binary isMorning / isEvening, bucket usage by 3-hour slot (0–3, 3–6, … 21–24) and track per-slot domain scores. Surfaces the right prompt at the right hour rather than just time-of-day heuristics.

Internal — not for distribution