Building Isolyne (Part 5): How the Offline Fallback Parser Handles LLM Outages

Wait 5 sec.

What happens when an LLM call fails on flaky Wi-Fi? How we engineered a deterministic fallback engine that keeps our mobile app 100% functional offline.The dirty secret of many modern "AI-native" mobile apps is that they are completely bricked the moment the phone loses internet connectivity.If an app routes every single user tap and text submission through a cloud LLM:A subway ride with zero signal causes infinite spinners.A spotty conference Wi-Fi network turns a 90-second demo pitch into an awkward crash.An unexpected 429 Too Many Requests rate limit halts team collaboration.When we built Isolyne for Shipaton 2026, we adhered to an offline-first rule: An AI outage must never stop developers from recording decisions and detecting alignment drift.Here is how we built a layered fallback architecture that degrades gracefully from Gemini 1.5 Flash to local heuristics without the UI skipping a beat.The Fallback CascadeWhenever a developer types a statement into Isolyne's Statement Channel, the input travels through a multi-tier resilience pipeline:[ User Statement ] │ ▼"We're using PostgreSQL for the database" │ ▼┌────────────────────────────────────────────────────────────┐│ TIER 1: CLOUD LLM (Gemini 1.5 Flash / Groq) ││ • Fast structured extraction (< 400ms) ││ • 8-second circuit breaker timeout ││ • Exponential backoff on 429 rate limits │└────────────────────────────┬───────────────────────────────┘ │ If network fails, times out, or no API key exists │ ▼┌────────────────────────────────────────────────────────────┐│ TIER 2: LOCAL HEURISTIC FALLBACK PARSER (Pure TypeScript) ││ • Local regex/keyword engine ││ • Domain and scope boundary classification ││ • Filters out casual banter by returning null │└────────────────────────────┬───────────────────────────────┘ │ ▼[ Structured Decision Signal ]{ topic: "Database", choice: "PostgreSQL" }Building the Local Fallback EngineOur local fallback engine (fallbackParser) is designed to run directly on the device without a network round trip or external dependencies.It doesn't just look for naive keywords—it uses contextual precedence to differentiate between technical choices and project scope boundaries:export function fallbackParser(input: string): ParsedStatement | null { const lowerInput = input.toLowerCase(); let topic = ''; let choice = input.trim(); const hasSpecificDb = lowerInput.includes('postgres') || lowerInput.includes('mongo') || lowerInput.includes('supabase') || lowerInput.includes('firebase'); const hasScopeKeyword = lowerInput.includes('scope') || lowerInput.includes('mvp') || lowerInput.includes('mock') || lowerInput.includes('prototype') || lowerInput.includes('full crud') || lowerInput.includes('landing page'); // Rule 1: Scope and MVP boundaries take priority over generic words if (hasScopeKeyword && !hasSpecificDb) { topic = 'Scope'; if ( lowerInput.includes('mock') || lowerInput.includes('prototype') || lowerInput.includes('demo') ) { choice = 'Mock / Prototype Only'; } else if ( lowerInput.includes('full') || lowerInput.includes('crud') || lowerInput.includes('auth') ) { choice = 'Full Feature Build'; } } // Rule 2: Database layer else if ( hasSpecificDb || lowerInput.includes('db') || lowerInput.includes('database') ) { topic = 'Database'; } // Rule 3: Frontend frameworks else if ( lowerInput.includes('react') || lowerInput.includes('vue') || lowerInput.includes('react native') ) { topic = 'Frontend Framework'; } // Rule 4: Architecture else if ( lowerInput.includes('monolith') || lowerInput.includes('microservices') || lowerInput.includes('architecture') ) { topic = 'Architecture'; choice = lowerInput.includes('microservices') ? 'Microservices' : 'Monolith'; } // Rule 5: Non-decisions and casual banter return null else { return null; } return { topic, choice };}Seamless Failover in PracticeIn interpretStatement(), if the API key is missing, such as in local CI test environments, or if the network fetch throws an AbortError, the fallback catches the error and immediately returns the locally parsed result:export async function interpretWithGemini( sanitizedInput: string, recentTopics: string[]): Promise { const apiKey = process.env.EXPO_PUBLIC_GEMINI_API_KEY; // Immediate failover if running offline or in keyless CI if (!apiKey) { console.warn( 'No EXPO_PUBLIC_GEMINI_API_KEY found. Falling back to keyword parser.' ); return fallbackParser(sanitizedInput); } try { const res = await fetchWithRetry(endpoint, options); // ... parse structured JSON from Gemini ... } catch (err: any) { console.warn( 'LLM parsing failed or timed out. Falling back to local engine.', err ); return fallbackParser(sanitizedInput); }}Proving Graceful Degradation in VitestTo ensure that our fallback layer never regresses, we created a dedicated test suite in Vitest that simulates API outages, missing keys, and edge-case phrasing:describe('LLM Parser Boundary & Offline Fallback', () => { it('gracefully degrades to fallback parser when API key is missing', async () => { delete process.env.EXPO_PUBLIC_GEMINI_API_KEY; const result = await interpretStatement( 'We are using postgres', [] ); expect(result).toEqual({ topic: 'Database', choice: 'We are using postgres' }); }); it('fallback parser correctly extracts scope decisions', () => { const mockStmt = fallbackParser( "let's just do a mock login for the demo" ); expect(mockStmt).toEqual({ topic: 'Scope', choice: 'Mock / Prototype Only' }); }); it('returns null on casual banter without polluting the state stream', () => { const chatStmt = fallbackParser( 'hey team, see you in 10 minutes' ); expect(chatStmt).toBeNull(); });});The RevenueCat Connection: Offline Value PreservationWhy is offline resilience essential for our RevenueCat monetization strategy?Subscribers paying for Isolyne Pro rely on the app as an immutable audit trail of how their team shipped.If an engineer is working on a plane or from an offline hackathon venue:They can continue logging decisions offline.The local CQRS kernel records every signal to AsyncStorage.The local fallback parser allows decisions to be categorized using local heuristics without cloud connectivity.When the device reconnects, RevenueCat verifies their cached entitlement, and the full timeline synchronizes seamlessly.Never allow third-party API availability to become a single point of failure for your core user experience.In Part 6...How do we design a monetization model that feels honest, sustainable, and genuinely aligned with small hackathon teams?In Part 6, we’ll explore Monetization Philosophy—why our free tier keeps teams safe while Pro monetizes the accumulated timeline through RevenueCat.