diff --git a/app/agent/orchestrator.py b/app/agent/orchestrator.py index 570f102..f3a81ba 100644 --- a/app/agent/orchestrator.py +++ b/app/agent/orchestrator.py @@ -36,6 +36,9 @@ class AgentOrchestrator: """Initialize the orchestrator and all modes.""" self.llm_client = LLMClient() self.loop_interval_seconds = loop_interval_seconds + self.hearthkeeper_prompt_interval = timedelta( + minutes=settings.HEARTHKEEPER_PROMPT_INTERVAL_MINUTES + ) # Initialize modes self.hearthkeeper = HearthkeeperMode(self.llm_client) @@ -93,7 +96,10 @@ class AgentOrchestrator: sessions = await repo.get_active_sessions() for session in sessions: - recent_messages = await repo.get_recent_messages(session.id, limit=1) + recent_messages = await repo.get_recent_human_messages( + session.id, + limit=1, + ) message_count = await repo.count_messages(session.id) last_activity_at = ( recent_messages[0].timestamp if recent_messages else session.started_at @@ -189,7 +195,7 @@ class AgentOrchestrator: recent_messages = [] async for db_session in get_session(): repo = Repository(db_session) - recent_messages = await repo.get_messages_since( + recent_messages = await repo.get_human_messages_since( session_id=session_id, since=datetime.utcnow() - timedelta(minutes=1), ) @@ -285,19 +291,32 @@ class AgentOrchestrator: session_id=session_id, mode="hearthkeeper", ) + session_info["last_hearthkeeper_prompt_at"] = ( + datetime.utcnow() - self.hearthkeeper_prompt_interval + ) + third_tick = await self._tick_session(session_id) + final_count = await self._count_response_actions( + session_id=session_id, + mode="hearthkeeper", + ) prompts_created = after_count - before_count + prompts_created_after_interval = final_count - before_count return { "passed": ( prompts_created == 1 and first_tick is not None and second_tick is None + and third_tick is not None + and prompts_created_after_interval == 2 ), "session_id": session_id, "inactive_minutes": inactive_minutes, "prompts_created": prompts_created, + "prompts_created_after_interval": prompts_created_after_interval, "first_tick": first_tick, "second_tick": second_tick, + "third_tick_after_interval": third_tick, } async def _count_response_actions(self, session_id: str, mode: str) -> int: @@ -323,6 +342,9 @@ class AgentOrchestrator: """Get background loop configuration and current session count.""" return { "interval_seconds": self.loop_interval_seconds, + "hearthkeeper_prompt_interval_minutes": int( + self.hearthkeeper_prompt_interval.total_seconds() / 60 + ), "active_session_count": len(self.active_sessions), } @@ -344,7 +366,7 @@ class AgentOrchestrator: recent_messages = [] async for db_session in get_session(): repo = Repository(db_session) - recent_messages = await repo.get_messages_since( + recent_messages = await repo.get_human_messages_since( session_id=session_id, since=datetime.utcnow() - timedelta(minutes=1), ) @@ -362,7 +384,13 @@ class AgentOrchestrator: last_activity_at = self.chat_activity.last_activity_at(session_id) last_prompt_at = session_info.get("last_hearthkeeper_prompt_at") - if last_prompt_at and last_activity_at and last_prompt_at >= last_activity_at: + if last_activity_at and last_prompt_at and last_activity_at > last_prompt_at: + session_info["last_hearthkeeper_prompt_at"] = None + last_prompt_at = None + if ( + last_prompt_at + and datetime.utcnow() - last_prompt_at < self.hearthkeeper_prompt_interval + ): return None try: diff --git a/app/config.py b/app/config.py index 6d3a03e..08b2c38 100644 --- a/app/config.py +++ b/app/config.py @@ -55,6 +55,7 @@ class Settings(BaseSettings): # Agent loop AGENT_LOOP_INTERVAL_SECONDS: float = 60.0 + HEARTHKEEPER_PROMPT_INTERVAL_MINUTES: int = 15 # Export EXPORT_PATH: str = "exports" diff --git a/app/memory/repository.py b/app/memory/repository.py index fdaef41..9022e1e 100644 --- a/app/memory/repository.py +++ b/app/memory/repository.py @@ -105,6 +105,22 @@ class Repository: result = await self.session.execute(stmt) return list(result.scalars().all()) + async def get_recent_human_messages( + self, session_id: str, limit: int = 50 + ) -> list[ChatMessage]: + """Get recent non-bot chat messages from a session.""" + stmt = ( + select(ChatMessage) + .where( + ChatMessage.session_id == session_id, + ChatMessage.is_bot.is_(False), + ) + .order_by(ChatMessage.timestamp.desc()) + .limit(limit) + ) + result = await self.session.execute(stmt) + return list(result.scalars().all()) + async def count_messages(self, session_id: str) -> int: """Count chat messages stored for a session.""" stmt = select(func.count()).select_from(ChatMessage).where( @@ -128,6 +144,22 @@ class Repository: result = await self.session.execute(stmt) return list(result.scalars().all()) + async def get_human_messages_since( + self, session_id: str, since: datetime + ) -> list[ChatMessage]: + """Get non-bot messages recorded since a specific timestamp.""" + stmt = ( + select(ChatMessage) + .where( + ChatMessage.session_id == session_id, + ChatMessage.is_bot.is_(False), + ChatMessage.timestamp >= since, + ) + .order_by(ChatMessage.timestamp.desc()) + ) + result = await self.session.execute(stmt) + return list(result.scalars().all()) + # Agent Action operations async def record_action( diff --git a/docker-compose.yml b/docker-compose.yml index 90b3859..184ff8d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -43,6 +43,7 @@ services: LLM_API_KEY: ${LLM_API_KEY:-} LLM_MODEL: ${LLM_MODEL:-gpt-3.5-turbo} AGENT_LOOP_INTERVAL_SECONDS: ${AGENT_LOOP_INTERVAL_SECONDS:-60} + HEARTHKEEPER_PROMPT_INTERVAL_MINUTES: ${HEARTHKEEPER_PROMPT_INTERVAL_MINUTES:-15} EXPORT_PATH: /app/exports volumes: - ./exports:/app/exports