Feature: Self-Reflection Mechanism #1051
@@ -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()),
|
||||
]
|
||||
|
||||
|
||||
1
src/dashboard/models/__init__.py
Normal file
1
src/dashboard/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .calm import Task, JournalEntry\nfrom .reflection import Reflection\n
|
||||
14
src/dashboard/models/reflection.py
Normal file
14
src/dashboard/models/reflection.py
Normal 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)
|
||||
@@ -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
82
src/timmy/reflection.py
Normal 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()
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user