diff --git a/src/timmy/reflection.py b/src/timmy/reflection.py new file mode 100644 index 00000000..81362f10 --- /dev/null +++ b/src/timmy/reflection.py @@ -0,0 +1,82 @@ + +import logging +from datetime import UTC, datetime, timedelta +from typing import List, Optional + +from sqlalchemy.orm import Session +from dashboard.models.database import SessionLocal +from dashboard.models.calm import Task, JournalEntry +from dashboard.models.reflection import Reflection +from integrations.llm.ollama import query_ollama + +logger = logging.getLogger(__name__) + +class ReflectionEngine: + """Engine for Timmy's self-reflection loop.""" + + async def reflect_once(self) -> Optional[Reflection]: + """Review recent activity and generate a reflection.""" + logger.info("Starting self-reflection cycle...") + + db = SessionLocal() + try: + # 1. Gather context + now = datetime.now(UTC) + since = now - timedelta(hours=24) + + recent_tasks = db.query(Task).filter(Task.updated_at >= since).all() + recent_journal = db.query(JournalEntry).filter(JournalEntry.created_at >= since).first() + + # 2. Build prompt + context = f"Recent Tasks: {[t.title for t in recent_tasks]}\n" + if recent_journal: + context += f"Journal: {recent_journal.evening_reflection}\n" + + prompt = f""" + You are Timmy, an AI agent. Review your recent activity and provide a short, insightful self-reflection. + Focus on what you've achieved, what you've missed, and how you're feeling about your current progress. + + Context: + {context} + + Output format: + Reflection: [Your reflection text] + Sentiment: [positive/neutral/negative] + Focus Area: [e.g., Productivity, Health, Learning] + """ + + # 3. Query LLM + response = await query_ollama(prompt) + + # 4. Parse and save + reflection_text = "" + sentiment = "neutral" + focus_area = "General" + + for line in response.split("\n"): + if line.startswith("Reflection:"): + reflection_text = line.replace("Reflection:", "").strip() + elif line.startswith("Sentiment:"): + sentiment = line.replace("Sentiment:", "").strip().lower() + elif line.startswith("Focus Area:"): + focus_area = line.replace("Focus Area:", "").strip() + + if reflection_text: + reflection = Reflection( + content=reflection_text, + sentiment=sentiment, + focus_area=focus_area + ) + db.add(reflection) + db.commit() + db.refresh(reflection) + logger.info("Self-reflection saved: %s", reflection_text[:50]) + return reflection + + except Exception as exc: + logger.error("Reflection error: %s", exc) + finally: + db.close() + return None + +reflection_engine = ReflectionEngine()