// ========================================== // MEEZAN - ميزان Self-Reflection Engine // After each session, Meezan reviews itself // and generates lessons to improve // Based on the "Reflexion" AI technique // ========================================== import Anthropic from '@anthropic-ai/sdk'; import * as fs from 'fs'; import * as path from 'path'; import { ReflectionNote, LearnedLesson, FeedbackSignal, KnowledgeGap } from '../types'; const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }); interface ConversationToReflect { userId: string; messages: Array<{ role: string; content: string }>; feedbackSignals: FeedbackSignal[]; knowledgeGaps: KnowledgeGap[]; } export class SelfReflectionEngine { private dbPath: string; private reflections: ReflectionNote[]; private lessons: LearnedLesson[]; private reflectionQueue: ConversationToReflect[]; constructor(dbPath: string = './data/reflections.json') { this.dbPath = dbPath; this.reflections = []; this.lessons = []; this.reflectionQueue = []; this.load(); // Run reflection every 30 minutes on queued conversations setInterval(() => this.processQueue(), 30 * 60 * 1000); } /** * Queue a conversation for later reflection * (non-blocking — runs in background) */ queueForReflection(conversation: ConversationToReflect): void { this.reflectionQueue.push(conversation); // Process immediately if queue gets large if (this.reflectionQueue.length >= 5) { this.processQueue(); } } /** * Process queued conversations — runs in background */ async processQueue(): Promise { if (this.reflectionQueue.length === 0) return; const toProcess = this.reflectionQueue.splice(0, 3); // Process 3 at a time for (const conversation of toProcess) { try { await this.reflectOnConversation(conversation); } catch (error) { console.error('Reflection error:', error); } } } /** * Core reflection — Claude analyzes its own conversation */ async reflectOnConversation(conversation: ConversationToReflect): Promise { if (conversation.messages.length < 2) return null; const sessionId = `session_${Date.now()}`; // Format conversation for analysis const convText = conversation.messages .map(m => `${m.role.toUpperCase()}: ${m.content}`) .join('\n\n'); const feedbackSummary = conversation.feedbackSignals.length > 0 ? conversation.feedbackSignals.map(f => `- Signal: ${f.signal} | Reason: ${f.reason}` ).join('\n') : 'No explicit feedback signals detected.'; const gapsSummary = conversation.knowledgeGaps.length > 0 ? conversation.knowledgeGaps.map(g => `- Topic: ${g.topic} (${g.category})` ).join('\n') : 'No knowledge gaps detected.'; const reflectionPrompt = `You are Meezan's internal quality reviewer. Analyze this conversation and provide honest, structured feedback for self-improvement. CONVERSATION: ${convText} USER FEEDBACK SIGNALS: ${feedbackSummary} DETECTED KNOWLEDGE GAPS: ${gapsSummary} Analyze this conversation across these dimensions: 1. SHARIAH ACCURACY: Were all responses Shariah-compliant? Any errors or near-misses? 2. ARABIC QUALITY: Was the Arabic natural, appropriate register, correct Gulf dialect? 3. HELPFULNESS: Did Meezan actually solve the user's problem? 4. CLARITY: Were responses clear and well-structured? 5. ISLAMIC ADAB: Was the tone warm, respectful, Islamic in character? 6. MISSED OPPORTUNITIES: What could Meezan have proactively offered that it didn't? Respond ONLY with this exact JSON format: { "strengths": ["list of 2-3 things done well"], "weaknesses": ["list of 1-3 specific weaknesses"], "missedOpportunities": ["things Meezan could have offered but didn't"], "shariahErrors": ["any Shariah compliance issues found, empty array if none"], "languageQuality": "excellent|good|poor", "overallScore": 7, "lessonsLearned": [ "Specific actionable lesson 1", "Specific actionable lesson 2" ] }`; try { const response = await client.messages.create({ model: 'claude-sonnet-4-6', max_tokens: 800, messages: [{ role: 'user', content: reflectionPrompt }] }); const rawText = response.content[0].type === 'text' ? response.content[0].text : ''; // Parse JSON response const jsonMatch = rawText.match(/\{[\s\S]*\}/); if (!jsonMatch) return null; const parsed = JSON.parse(jsonMatch[0]); const note: ReflectionNote = { id: `ref_${Date.now()}`, sessionId, userId: conversation.userId, timestamp: new Date().toISOString(), strengths: parsed.strengths || [], weaknesses: parsed.weaknesses || [], missedOpportunities: parsed.missedOpportunities || [], shariahErrors: parsed.shariahErrors || [], languageQuality: parsed.languageQuality || 'good', overallScore: Math.min(10, Math.max(1, parsed.overallScore || 7)), lessonsLearned: parsed.lessonsLearned || [] }; this.reflections.push(note); // Extract and store lessons for (const lesson of note.lessonsLearned) { this.storelesson(lesson, 'reflection', note.id); } // Flag Shariah errors urgently if (note.shariahErrors.length > 0) { console.warn(`⚠️ SHARIAH REVIEW NEEDED: ${note.shariahErrors.join('; ')}`); this.storeShariahErrorLesson(note.shariahErrors); } this.save(); console.log(`✅ Reflection complete for session ${sessionId}. Score: ${note.overallScore}/10`); return note; } catch (error) { console.error('Self-reflection API error:', error); return null; } } /** * Generate a comprehensive improvement summary * Used to update the system prompt with learned lessons */ async generateImprovementContext(): Promise { if (this.lessons.length === 0) return ''; // Get top lessons by confidence and reinforcement const topLessons = this.lessons .filter(l => l.confidence >= 0.6) .sort((a, b) => (b.confidence * b.reinforcementCount) - (a.confidence * a.reinforcementCount)) .slice(0, 10); if (topLessons.length === 0) return ''; const lessonsList = topLessons .map(l => `• ${l.lesson}`) .join('\n'); return `\nLEARNED IMPROVEMENTS (from ${this.reflections.length} self-reflections):\n${lessonsList}\n`; } /** * Get recent Shariah errors to always keep in system prompt */ getShariahErrorAlerts(): string[] { return this.lessons .filter(l => l.category === 'shariah_error' && l.confidence >= 0.8) .map(l => l.lesson) .slice(-5); } /** * Get improvement stats for dashboard */ getStats(): { totalReflections: number; averageScore: number; totalLessons: number; shariahErrors: number; trend: 'improving' | 'stable' | 'declining'; } { const recent = this.reflections.slice(-10); const older = this.reflections.slice(-20, -10); const avgRecent = recent.length > 0 ? recent.reduce((s, r) => s + r.overallScore, 0) / recent.length : 0; const avgOlder = older.length > 0 ? older.reduce((s, r) => s + r.overallScore, 0) / older.length : avgRecent; const trend = avgRecent > avgOlder + 0.5 ? 'improving' : avgRecent < avgOlder - 0.5 ? 'declining' : 'stable'; return { totalReflections: this.reflections.length, averageScore: Math.round(avgRecent * 10) / 10, totalLessons: this.lessons.length, shariahErrors: this.lessons.filter(l => l.category === 'shariah_error').length, trend }; } getRecentReflections(limit: number = 5): ReflectionNote[] { return this.reflections.slice(-limit).reverse(); } getLessons(): LearnedLesson[] { return this.lessons.sort((a, b) => b.confidence - a.confidence); } private storelesson(lessonText: string, source: LearnedLesson['source'], sourceId: string): void { // Check if similar lesson already exists const existing = this.lessons.find(l => this.similarity(l.lesson, lessonText) > 0.7 ); if (existing) { existing.reinforcementCount++; existing.confidence = Math.min(1, existing.confidence + 0.1); existing.lastReinforced = new Date().toISOString(); } else { this.lessons.push({ id: `lesson_${Date.now()}_${Math.random().toString(36).slice(2)}`, lesson: lessonText, lessonAr: lessonText, // TODO: translate category: 'general', confidence: 0.5, reinforcementCount: 1, firstLearned: new Date().toISOString(), lastReinforced: new Date().toISOString(), source }); } } private storeShariahErrorLesson(errors: string[]): void { for (const error of errors) { const lesson: LearnedLesson = { id: `shariah_err_${Date.now()}`, lesson: `SHARIAH ALERT: ${error}`, lessonAr: `تنبيه شرعي: ${error}`, category: 'shariah_error', confidence: 0.9, // High confidence for Shariah errors reinforcementCount: 1, firstLearned: new Date().toISOString(), lastReinforced: new Date().toISOString(), source: 'reflection' }; this.lessons.push(lesson); } } private similarity(a: string, b: string): number { const aWords = new Set(a.toLowerCase().split(/\s+/)); const bWords = new Set(b.toLowerCase().split(/\s+/)); const intersection = new Set([...aWords].filter(w => bWords.has(w))); const union = new Set([...aWords, ...bWords]); return intersection.size / union.size; } private load(): void { const dir = path.dirname(this.dbPath); if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true }); if (fs.existsSync(this.dbPath)) { try { const data = JSON.parse(fs.readFileSync(this.dbPath, 'utf-8')); this.reflections = data.reflections || []; this.lessons = data.lessons || []; } catch { /* start fresh */ } } } private save(): void { fs.writeFileSync( this.dbPath, JSON.stringify({ reflections: this.reflections, lessons: this.lessons }, null, 2), 'utf-8' ); } } export const selfReflection = new SelfReflectionEngine( process.env.DB_PATH?.replace('memory.json', 'reflections.json') || './data/reflections.json' );