Feature: Self-Reflection Mechanism #1051

Closed
gemini wants to merge 7 commits from feature/self-reflection into main
6 changed files with 164 additions and 0 deletions

View File

@@ -233,6 +233,22 @@ _SYNTHESIZED_STATE: dict = {
}
async function _reflection_scheduler() -> None:
"""Background task: execute Timmy's self-reflection cycle every 4 hours."""
from timmy.reflection import reflection_engine
await asyncio.sleep(15) # Stagger after other schedulers
while True:
try:
await reflection_engine.reflect_once()
except Exception as exc:
logger.error("Reflection scheduler error: %s", exc)
await asyncio.sleep(4 * 3600)
async def _presence_watcher() -> None:
"""Background task: watch ~/.timmy/presence.json and broadcast changes via WS.
@@ -380,6 +396,7 @@ def _startup_background_tasks() -> list[asyncio.Task]:
asyncio.create_task(_thinking_scheduler()),
asyncio.create_task(_loop_qa_scheduler()),
asyncio.create_task(_presence_watcher()),
asyncio.create_task(_reflection_scheduler()),
asyncio.create_task(_start_chat_integrations_background()),
]

View File

@@ -0,0 +1 @@
from .calm import Task, JournalEntry\nfrom .reflection import Reflection\n

View File

@@ -0,0 +1,14 @@
from datetime import UTC, datetime
from sqlalchemy import Column, DateTime, Integer, String, Text
from .database import Base
class Reflection(Base):
__tablename__ = "reflections"
id = Column(Integer, primary_key=True, index=True)
content = Column(Text, nullable=False)
sentiment = Column(String(50), nullable=True)
focus_area = Column(String(100), nullable=True)
created_at = Column(DateTime, default=lambda: datetime.now(UTC), nullable=False)

View File

@@ -61,3 +61,11 @@ async def thought_chain_api(thought_id: str):
}
for t in chain
]
@router.post("/reflect", response_class=JSONResponse)
async def trigger_reflection():
"""Trigger a self-reflection cycle."""
reflection = await thinking_engine.reflect()
if not reflection:
return JSONResponse({"error": "Failed to generate reflection"}, status_code=500)
return {"status": "ok", "reflection": reflection}

82
src/timmy/reflection.py Normal file
View File

@@ -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()

View File

@@ -316,6 +316,48 @@ class ThinkingEngine:
self._write_journal(thought)
await self._broadcast(thought)
async def reflect(self) -> str | None:
"""Periodic self-reflection: summarize recent state and goals."""
from timmy.memory_system import get_memory_context, store_last_reflection
# 1. Gather context
recent_thoughts = self.get_recent_thoughts(limit=10)
thought_text = "
".join([f"- {t.content}" for t in reversed(recent_thoughts)])
memory_context = get_memory_context("recent activity", max_tokens=1000)
system_snapshot = self._gather_system_snapshot()
prompt = f"""
You are Timmy, reflecting on your current state.
Review your recent thoughts and activity to create a concise "Self-Reflection".
Recent Thoughts:
{thought_text}
Recent Memory Context:
{memory_context}
System Snapshot:
{system_snapshot}
Write a 3-4 sentence reflection that summarizes:
1. What you've been focused on recently.
2. Your current "vibe" or state of mind.
3. Your immediate goals or what you're pondering next.
Be introspective and sovereign.
"""
try:
reflection = await self._call_agent(prompt)
if reflection:
store_last_reflection(reflection.strip())
logger.info("ThinkingEngine: Generated self-reflection")
return reflection.strip()
except Exception as exc:
logger.error("Self-reflection failed: %s", exc)
return None
async def think_once(self, prompt: str | None = None) -> Thought | None:
"""Execute one thinking cycle.