<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Emily Lin</title>
    <description>The latest articles on Forem by Emily Lin (@emily_lin_usa).</description>
    <link>https://forem.com/emily_lin_usa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3413260%2F9b59348f-2bc5-48e2-af45-2fb384168e5e.png</url>
      <title>Forem: Emily Lin</title>
      <link>https://forem.com/emily_lin_usa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/emily_lin_usa"/>
    <language>en</language>
    <item>
      <title>Local-First Voice AI: What Actually Works (and What Doesn't) — Week 3</title>
      <dc:creator>Emily Lin</dc:creator>
      <pubDate>Sun, 24 Aug 2025 21:16:41 +0000</pubDate>
      <link>https://forem.com/emily_lin_usa/local-first-voice-ai-what-actually-works-and-what-doesnt-week-3-aoc</link>
      <guid>https://forem.com/emily_lin_usa/local-first-voice-ai-what-actually-works-and-what-doesnt-week-3-aoc</guid>
      <description>&lt;p&gt;This is part of my journey building the Kai ecosystem—a fully local, offline-first voice assistant that keeps your data yours.&lt;br&gt;
Well, I started building an app for myself first. &lt;br&gt;
I collaborated with Claude to build layered time parsing logic all through natural language and my goal is to see a functional app that does what it is designed for. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kai Lite: 5-Point Summary&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Privacy-first voice assistant - Complete offline functionality, zero cloud data sharing, all data stays on your device&lt;/li&gt;
&lt;li&gt;Natural voice commands - Add reminders, create memos, check calendar using speech-to-text with pattern-based parsing&lt;/li&gt;
&lt;li&gt;Local-first architecture - Flutter mobile app with SQLite storage, works in airplane mode, no internet required&lt;/li&gt;
&lt;li&gt;User data control - Export/delete everything anytime, transparent permissions, visual indicators when mic is active&lt;/li&gt;
&lt;li&gt;Future ecosystem foundation - Designed to sync with Kai Laptop/Desktop while maintaining privacy and user control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This week, I'm sharing what actually happened when I tried to build a voice agent that works completely offline. Turns out, it is harder than expected for native AI builders.&lt;br&gt;
&lt;a href="https://youtube.com/shorts/u6rP2D4xVG8?feature=share" rel="noopener noreferrer"&gt;App Demo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqsg9wzovxq01cmd1mnu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqqsg9wzovxq01cmd1mnu.png" alt="Kai Lite App Demo" width="800" height="1771"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  My AI Collaborator This Week
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Claude:&lt;/strong&gt; My main implementation partner throughout this build. From initial architecture decisions to debugging regex patterns, Claude helped me think through each technical challenge and iterate quickly on solutions.&lt;/p&gt;
&lt;h2&gt;
  
  
  What I Actually Built (The Messy Reality)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Attempt 1: "Let's Build Alexa-Level Voice Commands"&lt;/strong&gt;&lt;br&gt;
The goal was ambitious: voice commands that work as smoothly as Alexa, but completely local.&lt;br&gt;
Started with the standard Flutter voice setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dependencies:
  speech_to_text: ^6.3.0
  flutter_tts: ^3.8.3
  permission_handler: ^11.0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Basic voice service structure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class VoiceService {
  final SpeechToText _speech = SpeechToText();
  final FlutterTts _tts = FlutterTts();

  Future&amp;lt;void&amp;gt; initialize() async {
    await _speech.initialize();
    // Kai's calm voice settings
    await _tts.setSpeechRate(0.9);  
    await _tts.setPitch(1.0);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The reality check:&lt;/strong&gt; &lt;br&gt;
Spent a day testing and realized that even with &lt;strong&gt;onDevice: true,&lt;/strong&gt; the accuracy wasn't consistent enough for the "Alexa-level" experience I wanted.&lt;br&gt;
&lt;strong&gt;Result:&lt;/strong&gt; Needed a completely different approach.&lt;/p&gt;
&lt;h2&gt;
  
  
  Attempt 2: Comprehensive Pattern-Based Parser (What Actually Works)
&lt;/h2&gt;

&lt;p&gt;Claude suggested focusing on pattern-based parsing instead of trying to build mini-Alexa. &lt;br&gt;
Smart advice—I used AI to help design the VoiceCommandParser architecture and generate comprehensive regex patterns for different ways people naturally speak.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class VoiceCommandParser {
  static final Map&amp;lt;String, List&amp;lt;RegExp&amp;gt;&amp;gt; patterns = {
    'calendar_add': [
      RegExp(r'remind me to (.*?) at (.*)'),
      RegExp(r'add (.*?) to calendar at (.*)'),
      RegExp(r'schedule (.*?) for (.*)'),
      RegExp(r'set reminder (.*?) at (.*)'),
      RegExp(r'(.*?) at (.*?) today'),
      RegExp(r'(.*?) at (.*?) tomorrow'),
    ],
    'calendar_check': [
      RegExp(r"what'?s on my calendar\??"),
      RegExp(r"what do i have today\??"),
      RegExp(r"show my schedule"),
      RegExp(r"any events today\??"),
    ],
    'memo_add': [
      RegExp(r'note to self[,:]? (.*)'),
      RegExp(r'remember that (.*)'),
      RegExp(r'make a note[,:]? (.*)'),
      RegExp(r'write down (.*)'),
    ],
  };

  static VoiceCommand parse(String input) {
    input = input.toLowerCase().trim();

    // Check each pattern category
    for (final entry in patterns.entries) {
      final intent = entry.key;
      final patternList = entry.value;

      for (final pattern in patternList) {
        final match = pattern.firstMatch(input);
        if (match != null) {
          return _extractCommand(intent, input, match);
        }
      }
    }

    // Fuzzy matching fallback
    return _fuzzyMatch(input);
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Added smart time parsing that handles natural language:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static String? _parseTime(String timeStr) {
  // Natural language conversions
  final conversions = {
    'morning': '9:00 AM',
    'afternoon': '2:00 PM', 
    'evening': '6:00 PM',
    'night': '9:00 PM',
    'noon': '12:00 PM',
    'midnight': '12:00 AM',
  };

  // Check natural language first
  for (final entry in conversions.entries) {
    if (timeStr.contains(entry.key)) {
      return entry.value;
    }
  }

  // Parse actual times (3pm, 3:30pm, 15:00)
  final timeMatch = RegExp(r'(\d{1,2})(?::(\d{2}))?\s*(am|pm)?', 
                          caseSensitive: false).firstMatch(timeStr);
  if (timeMatch != null) {
    var hour = int.parse(timeMatch.group(1) ?? '0');
    final minute = timeMatch.group(2) ?? '00';
    var ampm = timeMatch.group(3)?.toUpperCase();

    // Smart guessing for ambiguous times
    if (ampm == null) {
      if (hour &amp;gt;= 7 &amp;amp;&amp;amp; hour &amp;lt;= 11) {
        ampm = 'AM';
      } else if (hour &amp;gt;= 1 &amp;amp;&amp;amp; hour &amp;lt;= 6) {
        ampm = 'PM';
      } else if (hour &amp;gt;= 13 &amp;amp;&amp;amp; hour &amp;lt;= 23) {
        hour = hour - 12;
        ampm = 'PM';
      }
    }

    return '${hour}:${minute} ${ampm}';
  }

  return null;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Multi-turn conversation handler for missing information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ConversationHandler {
  ConversationContext _context = ConversationContext();

  Future&amp;lt;void&amp;gt; handleCommand(String input) async {
    final command = VoiceCommandParser.parse(input);

    if (command.confidence &amp;lt; 0.7) {
      await _voice.speak("I'm not sure. Did you want to add a calendar event or create a memo?");
      return;
    }

    // Handle missing information
    if (command.intent == 'calendar_add') {
      if (command.title == null) {
        _context.state = ConversationState.waitingForTitle;
        await _voice.speak("What would you like me to remind you about?");
        return;
      }

      if (command.time == null) {
        _context.state = ConversationState.waitingForTime;
        await _voice.speak("What time should I set the reminder for?");
        return;
      }

      await _createCalendarEvent(command);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Performance after this approach:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recognition accuracy: 90% for supported patterns&lt;/li&gt;
&lt;li&gt;Response time: &amp;lt;300ms end-to-end&lt;/li&gt;
&lt;li&gt;Memory usage: 45MB while active&lt;/li&gt;
&lt;li&gt;Battery impact: &amp;lt;2% over full day of testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real example that works:&lt;/strong&gt;&lt;br&gt;
User: "Remind me to call mom tomorrow at three"&lt;br&gt;
↓&lt;br&gt;
STT: "remind me to call mom tomorrow at three"&lt;br&gt;&lt;br&gt;
↓&lt;br&gt;
Pattern match: RegExp(r'remind me to (.&lt;em&gt;?) at (.&lt;/em&gt;)')&lt;br&gt;
↓&lt;br&gt;
Extract: title="call mom tomorrow", time="three"&lt;br&gt;&lt;br&gt;
↓&lt;br&gt;
Time parsing: "three" → "3:00 PM" (afternoon guess)&lt;br&gt;
↓&lt;br&gt;
Date parsing: "tomorrow" → DateTime.now().add(Duration(days: 1))&lt;br&gt;
↓&lt;br&gt;
Create task in SQLite&lt;br&gt;
↓&lt;br&gt;
TTS: "Perfect! I've added 'call mom' for 3 PM tomorrow"&lt;/p&gt;
&lt;h2&gt;
  
  
  Attempt 3: The Complete Alexa-Level System
&lt;/h2&gt;

&lt;p&gt;Realized I was thinking about this wrong. Instead of trying to match Alexa, I built something simpler that works reliably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My actual architecture:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// 1. Local STT with better settings
await _speech.listen(
  onDevice: true,
  listenFor: Duration(seconds: 3), // Shorter timeout
  cancelOnError: true,
  partialResults: false // Wait for complete result
);

// 2. Pattern-based parsing with multiple variations
static VoiceCommand parse(String input) {
  input = input.toLowerCase().trim();

  // Check each pattern category
  for (final entry in patterns.entries) {
    final intent = entry.key;
    final patternList = entry.value;

    for (final pattern in patternList) {
      final match = pattern.firstMatch(input);
      if (match != null) {
        return _extractCommand(intent, input, match);
      }
    }
  }

  return VoiceCommand(intent: 'unknown');
}

// 3. Smart time parsing
static String? _parseTime(String timeStr) {
  final conversions = {
    'morning': '9:00 AM',
    'afternoon': '2:00 PM',
    'evening': '6:00 PM',
    'noon': '12:00 PM',
  };

  // Handle natural language first
  for (final entry in conversions.entries) {
    if (timeStr.contains(entry.key)) {
      return entry.value;
    }
  }

  // Then handle actual times like "3pm" or "3:30"
  final timeMatch = RegExp(r'(\d{1,2})(?::(\d{2}))?\s*(am|pm)?')
      .firstMatch(timeStr);
  // ... parsing logic
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Real example of what works:&lt;/strong&gt;&lt;br&gt;
User says: "Remind me to call mom at three"&lt;br&gt;
↓&lt;br&gt;
Local STT: "remind me to call mom at three"&lt;br&gt;
↓&lt;br&gt;
Pattern match: RegExp(r'remind me to (.&lt;em&gt;?) at (.&lt;/em&gt;)')&lt;br&gt;
↓&lt;br&gt;
Extract: title="call mom", time="three"&lt;br&gt;
↓&lt;br&gt;
Parse time: "three" → "3:00 PM" (smart guess for afternoon)&lt;br&gt;
↓&lt;br&gt;
Create task in SQLite&lt;br&gt;
↓&lt;br&gt;
Response: "Added 'call mom' for 3:00 PM today"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Performance after optimization:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recognition time: 200-400ms&lt;/li&gt;
&lt;li&gt;Memory usage: 40MB while active&lt;/li&gt;
&lt;li&gt;Accuracy: 85% for supported commands&lt;/li&gt;
&lt;li&gt;Battery impact: &amp;lt;2% over full day&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  The Privacy Architecture I Actually Built
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Problem: How do you prove to users that nothing leaves their phone?&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;My solution - complete transparency:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Visual indicators everywhere:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Kai bubble pulses when listening
AnimatedContainer(
  duration: Duration(milliseconds: 300),
  decoration: BoxDecoration(
    color: _isListening 
      ? Color(0xFF9C7BD9).withOpacity(0.8)  // Active purple
      : Color(0xFF9C7BD9).withOpacity(0.2), // Calm purple
    shape: BoxShape.circle,
  ),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Data export built in from day 1:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class DataExportService {
  Future&amp;lt;String&amp;gt; exportAllUserData() async {
    final tasks = await CalendarService().getAllTasks();
    final memos = await MemoService().getAllMemos();

    return jsonEncode({
      'export_date': DateTime.now().toIso8601String(),
      'tasks': tasks.map((t) =&amp;gt; t.toMap()).toList(),
      'memos': memos.map((m) =&amp;gt; m.toMap()).toList(),
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. One-tap delete everything:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Future&amp;lt;void&amp;gt; deleteAllUserData() async {
  await CalendarService().clearAllTasks();
  await MemoService().clearAllMemos();
  await SharedPreferences.getInstance().then((prefs) =&amp;gt; prefs.clear());
  // Show confirmation: "All data deleted"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What surprised me: In testing, I/user cared more about seeing the "Export my data" and "Delete everything" buttons than perfect voice accuracy. Just knowing I had control felt satisfying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Database Design That Actually Works Offline
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Used SQLite with sync-ready fields from the start:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class Task {
  final String id;
  final String title;
  final DateTime? date;
  final String? time;
  final bool isCompleted;

  // Sync-ready fields for future
  final DateTime lastModified;
  final String sourceDevice;
  final String status; // 'active' | 'deleted'

  Task({
    required this.id,
    required this.title,
    this.date,
    this.time,
    this.isCompleted = false,
    required this.lastModified,
    this.sourceDevice = 'kai-lite-android',
    this.status = 'active',
  });
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Everything works offline immediately&lt;/li&gt;
&lt;li&gt;Sync fields ready for when I build cross-device features&lt;/li&gt;
&lt;li&gt;Soft deletes mean data recovery is possible&lt;/li&gt;
&lt;li&gt;Device tracking for multi-device scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Debugging (The Fun Stuff)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Issue 1: Memory leaks during voice processing&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Problem: Not disposing speech service
@override
void dispose() {
  _speech.stop();  // Added this
  _speech.cancel(); // And this
  super.dispose();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Issue 2: Battery drain from overlay&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Problem: Overlay always active
// Solution: Smart hiding
void _hideOverlayDuringCalls() {
  if (_phoneStateService.isInCall()) {
    _overlay.hide();
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Issue 3: SQLite performance with 1000+ tasks&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Added indexing for date queries
await db.execute('''
  CREATE INDEX IF NOT EXISTS idx_task_date_status 
  ON tasks(date, status)
''');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What I Learned (Technical &amp;amp; Otherwise)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Technical insights:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQLite performs way better than expected on mobile&lt;/li&gt;
&lt;li&gt;Local speech processing is viable if you optimize for specific use cases&lt;/li&gt;
&lt;li&gt;Pattern matching beats AI models for simple command parsing&lt;/li&gt;
&lt;li&gt;Flutter overlays are battery killers if not managed properly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;UX insights:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Privacy needs to feel empowering, not defensive
Visual feedback builds more trust than explanations
Reliable simple commands can feel smoother overall than unreliable complex ones
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Architecture insights:&lt;/strong&gt;&lt;br&gt;
Build offline-first from day 1, add sync later&lt;br&gt;
Start with the simplest solution that could work&lt;br&gt;
Real user testing catches issues you never thought of&lt;/p&gt;

&lt;h2&gt;
  
  
  The Current State
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What actually ships:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;15+ voice command patterns that work reliably&lt;/li&gt;
&lt;li&gt;Complete offline functionality (no internet required)&lt;/li&gt;
&lt;li&gt;Export/delete controls for full data ownership&lt;/li&gt;
&lt;li&gt;&amp;lt;300ms voice response time&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>flutter</category>
      <category>privacy</category>
      <category>buildwithai</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Why I Switched from LLMs to Tiny, Instant Voice NLU for Kai Lite — Week 2</title>
      <dc:creator>Emily Lin</dc:creator>
      <pubDate>Mon, 18 Aug 2025 04:29:33 +0000</pubDate>
      <link>https://forem.com/emily_lin_usa/why-i-switched-from-llms-to-tiny-instant-voice-nlu-for-kai-lite-week-2-3bgg</link>
      <guid>https://forem.com/emily_lin_usa/why-i-switched-from-llms-to-tiny-instant-voice-nlu-for-kai-lite-week-2-3bgg</guid>
      <description>&lt;p&gt;This is part of my journey building the Kai ecosystem—a fully local, offline-first, emotionally-intelligent AI assistant.&lt;br&gt;
This week, I’m sharing what actually happened as I tried (and failed, and retried) to build voice command understanding for Kai Lite, my mobile-first companion app.&lt;/p&gt;
&lt;h2&gt;
  
  
  🤖 My Two AI Collaborators
&lt;/h2&gt;

&lt;p&gt;ChatGPT: Idea generator and architecture partner. I used it for feature planning, prompt design, and exploring approaches.&lt;/p&gt;

&lt;p&gt;Claude: My “implementation sidekick.” Every time I got stuck on code, Claude helped debug, re-architect, and refactor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key moment:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Claude told me, “LLMs are nice, but too slow for instant mobile use. You’ll wait 2–3 seconds per command.”&lt;/p&gt;

&lt;p&gt;That advice changed my approach—speed (and flow) beat size.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧑‍💻 Attempt #1: Pattern Rules (Fast, but… Messy)
&lt;/h2&gt;

&lt;p&gt;I started with classic rule-based parsing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Regexes for matching intent (add event, check calendar, etc.)&lt;/li&gt;
&lt;li&gt;Lots of if/else spaghetti in my voice_command_parser.dart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;It only worked for exact commands&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I kept adding more and more patterns, forgetting what I’d written before (lol)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session log snippet with Claude:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"remind me to go fishing tomorrow at 3pm"
→ Matched: go to (.*)  ← WRONG!
→ Intent: navigation 
→ Result: Error/confusion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🚀 Attempt #2: Tiny, On-Device NLU (What Actually Works!)&lt;/p&gt;

&lt;p&gt;With Claude’s push, I rebuilt the whole flow:&lt;br&gt;
&lt;strong&gt;Architecture Overview&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[User Speaks]
     ↓
[Whisper-tiny → Text] 
     ↓
[Intent Classifier: calendar_add, calendar_view, etc.] 
     ↓
[Entity Extractor: date, time, title] 
     ↓
[SmartVoiceParser → Structured Command] 
     ↓
[Local Calendar API → Event Created]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This architecture ensures:&lt;/p&gt;

&lt;p&gt;✅ Speed: No network latency, instant feedback.&lt;br&gt;
✅ Privacy: No audio or text leaves the device.&lt;br&gt;
✅ Reliability: Not dependent on internet or third-party APIs.&lt;br&gt;
✅ Simplicity: Small models focused on specific tasks.&lt;/p&gt;
&lt;h2&gt;
  
  
  How It Works (Step-by-Step)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Voice input via Whisper-tiny:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final text = await WhisperTinyEN.transcribe(audio);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ol&gt;
&lt;li&gt;Intent classification:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final intent = await IntentClassifier.classify(text);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;&lt;em&gt;Example output:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "intent": "calendar_add", "confidence": 0.87 }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Entity extraction:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final entities = await EntityExtractor.extract(text);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;em&gt;Example output:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{ "title": "go fishing", "date": "tomorrow", "time": "3:00 PM" }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Smart voice command assembly:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;final command = SmartVoiceParser.parse(text);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;Returns one object, e.g.:&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "intent": "calendar_add",
  "slots": {
    "title": "go fishing",
    "date": "tomorrow",
    "time": "3:00 PM"
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Calendar event created, instantly and offline.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Before vs After: Real Example
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pattern system failure:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"remind me to go fishing tomorrow at 3pm"
→ navigation intent (wrong)
→ confusion or error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Smart NLU success:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"remind me to go fishing tomorrow at 3pm"
→ calendar_add (confidence: 0.8+)
→ Title: "go fishing"
→ Time: "3:00 PM"
→ Date: "tomorrow"
→ Event created 🎉
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Processing time: ~250–300 ms (on-device, fully offline)&lt;/p&gt;

&lt;h2&gt;
  
  
  🛠️ Technical Highlights
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Whisper-tiny for fast, offline voice-to-text (39 MB)&lt;/li&gt;
&lt;li&gt;BERT-tiny + intent head (~21 MB) for intent classification&lt;/li&gt;
&lt;li&gt;Dateparser-light (~1 MB) for fuzzy dates (“next Friday”, “this weekend”)&lt;/li&gt;
&lt;li&gt;All runs in &amp;lt;60 MB and feels instant on a modern phone&lt;/li&gt;
&lt;li&gt;Fully local: no cloud, zero data leaves device&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🪲 Bugs &amp;amp; Iterations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Early “smart” versions failed almost as much as my old rules&lt;/li&gt;
&lt;li&gt;Six rounds of real-world testing and log reviews to get to “it just works”&lt;/li&gt;
&lt;li&gt;“What’s my calendar like?” sometimes still triggers as an event… and honestly, I kind of love the bug now&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔑 Lessons:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Don’t over-engineer: Tiny, purpose-built NLU is better than a “mini-LLM” for command/slot tasks&lt;/li&gt;
&lt;li&gt;Speed is UX: Even 2 seconds of lag kills the magic&lt;/li&gt;
&lt;li&gt;Privacy: Everything is processed right on-device—no API, no server, no cloud&lt;/li&gt;
&lt;li&gt;ChatGPT and Claude are amazing for rapid iteration and brainstorming—even for solo devs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  💬 Wrap-up
&lt;/h2&gt;

&lt;p&gt;Building Kai Lite this way taught me that “small” can be smarter, and gentle, local AI is possible for real daily use.&lt;/p&gt;

</description>
      <category>voiceassitant</category>
      <category>ondeviceai</category>
      <category>ai</category>
      <category>flutter</category>
    </item>
    <item>
      <title>I’m Building a Local, Multi-Layer AI Assistant — Starting with Kai Lite (Week 1)</title>
      <dc:creator>Emily Lin</dc:creator>
      <pubDate>Fri, 15 Aug 2025 19:57:23 +0000</pubDate>
      <link>https://forem.com/emily_lin_usa/im-building-a-local-multi-layer-ai-assistant-starting-with-kai-lite-week-1-l5c</link>
      <guid>https://forem.com/emily_lin_usa/im-building-a-local-multi-layer-ai-assistant-starting-with-kai-lite-week-1-l5c</guid>
      <description>&lt;p&gt;I’ve been using AI daily for creative and strategic work for years. But after a recent update changed the behavior of my primary assistant — despite the same prompts — I realized something: personal nuance doesn’t survive at scale.&lt;/p&gt;

&lt;p&gt;Large AI systems must follow global rules, minimize risk, and standardize outputs. That’s fair. But it also means they can’t preserve the subtle, evolving rhythm of a one-on-one collaboration.&lt;/p&gt;

&lt;p&gt;So I’m stepping back.&lt;br&gt;
Not to reject cloud AI.&lt;br&gt;
But to design a personal AI ecosystem — local-first, private by default, and built around my actual workflow.&lt;/p&gt;

&lt;p&gt;I’m not launching a product.&lt;br&gt;
I’m not chasing autonomy.&lt;br&gt;
I’m just building a system that works for me — starting simple.&lt;/p&gt;

&lt;p&gt;This week, I began Week 1 of building Kai Lite — the mobile layer of a three-part architecture I’ve been planning.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Vision: A Three-Layer Personal AI System
&lt;/h2&gt;

&lt;p&gt;My goal is continuity, not complexity.&lt;/p&gt;

&lt;p&gt;I want a single coherent experience across devices — each layer handling only what it needs to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| LAYER               | PURPOSE                                | LLM                          | STATUS           |
|---------------------|----------------------------------------|------------------------------|------------------|
| Kai Lite (mobile)   | Capture, voice memos, quick tasks      | None                         | ✅ Starting now  |
| Kai Laptop          | Planning, memory, light automation     | Llama3 7B / Mistral 7B       | 🛠️ Design phase |
| Kai Desktop         | Deep work, reflection, business automation | Qwen3-32B, GPT-OSS 20B       | 🛠️ Design phase |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn’t about running giant models on every device.&lt;br&gt;
It’s about scaling intelligence where it belongs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kai Lite: The Mobile Capture Layer (Week 1)
&lt;/h2&gt;

&lt;p&gt;Right now, I’m focused on Kai Lite — a Flutter app for Android/iOS that acts as a lightweight entry point to my future AI ecosystem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why start here?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Most ideas begin on mobile&lt;/li&gt;
&lt;li&gt;Voice, quick notes, and reminders are 80% of daily capture&lt;/li&gt;
&lt;li&gt;A simple interface helps clarify what matters before adding complexity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter (Dart) → Cross-platform, fast UI&lt;/li&gt;
&lt;li&gt;SQLite → Local storage for memos, tasks, calendar&lt;/li&gt;
&lt;li&gt;No LLM on device → Keeps it fast, private, and focused&lt;/li&gt;
&lt;li&gt;Voice-to-text → Using platform APIs (no local model)&lt;/li&gt;
&lt;li&gt;Floating overlay → Quick capture without opening the app&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;**Folder Structure (Simplified)&lt;br&gt;
**kai_lite_app/&lt;br&gt;
├── lib/&lt;br&gt;
│   ├── screens/           # Home, calendar, memos&lt;br&gt;
│   ├── overlay/           # Floating bubble &amp;amp; voice reflex&lt;br&gt;
│   └── services/          # Voice, memo, calendar, remote API&lt;br&gt;
├── models/                # Task, Memo data classes&lt;br&gt;
├── assets/                # Persona, icon&lt;br&gt;
└── pubspec.yaml&lt;/p&gt;

&lt;p&gt;Key Files I’m Setting Up This Week:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;overlay_bubble.dart → Messenger-style floating button&lt;/li&gt;
&lt;li&gt;overlay_voice_reflex.dart → Hands-free voice capture&lt;/li&gt;
&lt;li&gt;voice_service.dart → STT/TTS (no LLM)&lt;/li&gt;
&lt;li&gt;remote_kai_service.dart → Future HTTP connection to laptop/desktop agent
Right now, it’s just structure.
No logic.
No sync.
Just setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the vision is clear:&lt;br&gt;
Capture inputs on mobile → process them locally or on a trusted machine → get back meaningful responses, not just summaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Behind the Scenes (Design Phase)
&lt;/h2&gt;

&lt;p&gt;While Kai Lite is the starting point, it’s part of a larger local AI architecture I’m designing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On the Laptop:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python-based agent using LangGraph for stateful workflows&lt;/li&gt;
&lt;li&gt;ChromaDB for semantic memory (local vector DB)&lt;/li&gt;
&lt;li&gt;Lightweight LLMs (Llama3 7B) for planning and reflection&lt;/li&gt;
&lt;li&gt;Simple Kanban UI (simple_kanban.py) for task + memory management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;On the Desktop (Future):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Qwen3-32B-Q5_K_M → Primary model for deep writing, planning, self-review&lt;/li&gt;
&lt;li&gt;GPT-OSS 20B → Fallback/critic model&lt;/li&gt;
&lt;li&gt;DeepSeek-Coder 33B → Coding (loaded only when needed)&lt;/li&gt;
&lt;li&gt;Ollama → Local model management + GPU offload&lt;/li&gt;
&lt;li&gt;Self-evaluation flows → One model critiques another&lt;/li&gt;
&lt;li&gt;Biometric context → Heart rate data (Polar Verity) used to adjust tone and pacing (locally only)&lt;/li&gt;
&lt;li&gt;All data stays on-device.&lt;/li&gt;
&lt;li&gt;No cloud logging.&lt;/li&gt;
&lt;li&gt;No third-party APIs for core functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why Local? Why This Design?
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;| REASON                | MY CHOICE                                                                 |
|-----------------------|---------------------------------------------------------------------------|
| Privacy               | Sensitive data (voice, memos, HR) never leaves my devices                 |
| Stability             | No sudden changes from upstream model updates                             |
| Custom logic          | I can build recursive review, memory cleanup, publishing automation       |
| Regulatory reality    | Cloud AI must follow global rules — local AI can follow my rules          |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I’m not trying to replace GPT-4.&lt;br&gt;
I’m trying to build something the cloud can’t:&lt;br&gt;
An AI that knows my rhythms, respects my energy, and evolves with me — without asking permission.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tools &amp;amp; Workflow (Planned)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flutter → Mobile app&lt;/li&gt;
&lt;li&gt;Python + LangGraph → Agent logic&lt;/li&gt;
&lt;li&gt;ChromaDB → Semantic search over personal logs&lt;/li&gt;
&lt;li&gt;Ollama → Run local LLMs with GPU offload (RTX&lt;/li&gt;
&lt;li&gt;VS Code + Continue → Local coding support&lt;/li&gt;
&lt;li&gt;Polar Verity → Heart rate data for context 
(optional future layer)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This Is Just the Beginning&lt;br&gt;
I’m not live.&lt;br&gt;
I’m not demoing.&lt;br&gt;
I’m in Week 1 of building Kai Lite — setting up the foundation.&lt;/p&gt;

&lt;p&gt;No magic.&lt;br&gt;
No autonomy.&lt;br&gt;
Just files, folders, and a clear direction.&lt;/p&gt;

&lt;p&gt;If you're designing a personal AI system — not for scale, but for depth, control, and continuity — I’d love to hear your approach.&lt;/p&gt;

&lt;p&gt;Because the future of AI shouldn’t be only in the cloud.&lt;br&gt;
It should also be on your machine, in your hands, built for you.&lt;/p&gt;

&lt;p&gt;Wait for more updates.&lt;br&gt;
This is just the start.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>opensource</category>
      <category>beginners</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
