refactor(agent-office): drop the random idle→break→idle cycle
The pixel-office game UI is gone, so simulating coffee-break / nap / walk states no longer serves any purpose. Remove: - scheduler's _check_idle_breaks job (no more 60s idle scan) - BaseAgent.check_idle_break() and _break_until field - 'break' from VALID_STATES and from transition() branches - IDLE_BREAK_THRESHOLD / BREAK_DURATION_MIN / BREAK_DURATION_MAX config knobs - 'idle/break' guard in each agent's on_schedule (now just 'idle') Agents now sit in 'idle' between scheduled jobs and explicit commands. Display reads 'Idle' instead of churning between idle and break. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,9 @@
|
|||||||
import asyncio
|
|
||||||
import random
|
|
||||||
import time
|
import time
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from ..config import IDLE_BREAK_THRESHOLD, BREAK_DURATION_MIN, BREAK_DURATION_MAX
|
|
||||||
from ..db import add_log
|
from ..db import add_log
|
||||||
|
|
||||||
VALID_STATES = ("idle", "working", "waiting", "reporting", "break")
|
VALID_STATES = ("idle", "working", "waiting", "reporting")
|
||||||
|
|
||||||
class BaseAgent:
|
class BaseAgent:
|
||||||
agent_id: str = ""
|
agent_id: str = ""
|
||||||
@@ -14,7 +11,6 @@ class BaseAgent:
|
|||||||
state: str = "idle"
|
state: str = "idle"
|
||||||
state_detail: str = ""
|
state_detail: str = ""
|
||||||
_idle_since: float = 0.0
|
_idle_since: float = 0.0
|
||||||
_break_until: float = 0.0
|
|
||||||
_ws_manager = None
|
_ws_manager = None
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -32,9 +28,6 @@ class BaseAgent:
|
|||||||
|
|
||||||
if new_state == "idle":
|
if new_state == "idle":
|
||||||
self._idle_since = time.time()
|
self._idle_since = time.time()
|
||||||
elif new_state == "break":
|
|
||||||
duration = random.randint(BREAK_DURATION_MIN, BREAK_DURATION_MAX)
|
|
||||||
self._break_until = time.time() + duration
|
|
||||||
|
|
||||||
add_log(self.agent_id, f"State: {old} -> {new_state} ({detail})")
|
add_log(self.agent_id, f"State: {old} -> {new_state} ({detail})")
|
||||||
|
|
||||||
@@ -48,19 +41,6 @@ class BaseAgent:
|
|||||||
await self._ws_manager.send_notification(
|
await self._ws_manager.send_notification(
|
||||||
self.agent_id, "task_completed", task_id, detail or "작업 완료"
|
self.agent_id, "task_completed", task_id, detail or "작업 완료"
|
||||||
)
|
)
|
||||||
if new_state == "break":
|
|
||||||
await self._ws_manager.send_agent_move(self.agent_id, "break_room")
|
|
||||||
elif old == "break" and new_state == "idle":
|
|
||||||
await self._ws_manager.send_agent_move(self.agent_id, "desk")
|
|
||||||
|
|
||||||
async def check_idle_break(self) -> None:
|
|
||||||
now = time.time()
|
|
||||||
if self.state == "idle" and (now - self._idle_since) > IDLE_BREAK_THRESHOLD:
|
|
||||||
if random.random() < 0.5:
|
|
||||||
break_type = random.choice(["커피 타임", "잠깐 산책", "졸고 있음"])
|
|
||||||
await self.transition("break", break_type)
|
|
||||||
elif self.state == "break" and now > self._break_until:
|
|
||||||
await self.transition("idle", "휴식 완료")
|
|
||||||
|
|
||||||
async def on_schedule(self) -> None:
|
async def on_schedule(self) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ class InstaAgent(BaseAgent):
|
|||||||
async def on_schedule(self) -> None:
|
async def on_schedule(self) -> None:
|
||||||
"""09:30 매일: 뉴스 수집 → 키워드 추출 → 텔레그램 후보 푸시.
|
"""09:30 매일: 뉴스 수집 → 키워드 추출 → 텔레그램 후보 푸시.
|
||||||
custom_config.auto_select=True면 카테고리당 1위 키워드 자동 슬레이트 생성."""
|
custom_config.auto_select=True면 카테고리당 1위 키워드 자동 슬레이트 생성."""
|
||||||
if self.state not in ("idle", "break"):
|
if self.state != "idle":
|
||||||
return
|
return
|
||||||
config = get_agent_config(self.agent_id) or {}
|
config = get_agent_config(self.agent_id) or {}
|
||||||
custom = config.get("custom_config", {}) or {}
|
custom = config.get("custom_config", {}) or {}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class LottoAgent(BaseAgent):
|
|||||||
display_name = "로또 큐레이터"
|
display_name = "로또 큐레이터"
|
||||||
|
|
||||||
async def on_schedule(self) -> None:
|
async def on_schedule(self) -> None:
|
||||||
if self.state not in ("idle", "break"):
|
if self.state != "idle":
|
||||||
return
|
return
|
||||||
await self._run(source="auto")
|
await self._run(source="auto")
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class StockAgent(BaseAgent):
|
|||||||
display_name = "주식 트레이더"
|
display_name = "주식 트레이더"
|
||||||
|
|
||||||
async def on_schedule(self) -> None:
|
async def on_schedule(self) -> None:
|
||||||
if self.state not in ("idle", "break"):
|
if self.state != "idle":
|
||||||
return
|
return
|
||||||
|
|
||||||
task_id = create_task(self.agent_id, "news_summary", {"limit": 15})
|
task_id = create_task(self.agent_id, "news_summary", {"limit": 15})
|
||||||
@@ -129,7 +129,7 @@ class StockAgent(BaseAgent):
|
|||||||
4) status=='success' → telegram_payload.text 를 parse_mode 그대로 전송
|
4) status=='success' → telegram_payload.text 를 parse_mode 그대로 전송
|
||||||
5) 예외/실패 → 운영자에게 별도 텔레그램 알림 (HTML)
|
5) 예외/실패 → 운영자에게 별도 텔레그램 알림 (HTML)
|
||||||
"""
|
"""
|
||||||
if self.state not in ("idle", "break"):
|
if self.state != "idle":
|
||||||
return
|
return
|
||||||
|
|
||||||
task_id = create_task(self.agent_id, "screener_run", {"mode": "auto"})
|
task_id = create_task(self.agent_id, "screener_run", {"mode": "auto"})
|
||||||
@@ -243,7 +243,7 @@ class StockAgent(BaseAgent):
|
|||||||
4) failures > 30% → 경고 알림 후 메인 메시지 발송
|
4) failures > 30% → 경고 알림 후 메인 메시지 발송
|
||||||
5) 정상 → Top 5 호재/악재 메시지 발송 (MarkdownV2)
|
5) 정상 → Top 5 호재/악재 메시지 발송 (MarkdownV2)
|
||||||
"""
|
"""
|
||||||
if self.state not in ("idle", "break"):
|
if self.state != "idle":
|
||||||
return
|
return
|
||||||
|
|
||||||
task_id = create_task(self.agent_id, "ai_news_sentiment", {})
|
task_id = create_task(self.agent_id, "ai_news_sentiment", {})
|
||||||
|
|||||||
@@ -26,11 +26,6 @@ CORS_ALLOW_ORIGINS = os.getenv(
|
|||||||
"CORS_ALLOW_ORIGINS", "http://localhost:3007,http://localhost:8080"
|
"CORS_ALLOW_ORIGINS", "http://localhost:3007,http://localhost:8080"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Idle break threshold (seconds)
|
|
||||||
IDLE_BREAK_THRESHOLD = int(os.getenv("IDLE_BREAK_THRESHOLD", "300")) # 5 min
|
|
||||||
BREAK_DURATION_MIN = int(os.getenv("BREAK_DURATION_MIN", "60")) # 1 min
|
|
||||||
BREAK_DURATION_MAX = int(os.getenv("BREAK_DURATION_MAX", "180")) # 3 min
|
|
||||||
|
|
||||||
# Lotto Curator
|
# Lotto Curator
|
||||||
LOTTO_BACKEND_URL = os.getenv("LOTTO_BACKEND_URL", "http://lotto:8000")
|
LOTTO_BACKEND_URL = os.getenv("LOTTO_BACKEND_URL", "http://lotto:8000")
|
||||||
LOTTO_CURATOR_MODEL = os.getenv("LOTTO_CURATOR_MODEL", "claude-sonnet-4-5")
|
LOTTO_CURATOR_MODEL = os.getenv("LOTTO_CURATOR_MODEL", "claude-sonnet-4-5")
|
||||||
|
|||||||
@@ -5,10 +5,6 @@ from .agents import AGENT_REGISTRY
|
|||||||
|
|
||||||
scheduler = AsyncIOScheduler(timezone="Asia/Seoul")
|
scheduler = AsyncIOScheduler(timezone="Asia/Seoul")
|
||||||
|
|
||||||
async def _check_idle_breaks():
|
|
||||||
for agent in AGENT_REGISTRY.values():
|
|
||||||
await agent.check_idle_break()
|
|
||||||
|
|
||||||
async def _run_stock_schedule():
|
async def _run_stock_schedule():
|
||||||
agent = AGENT_REGISTRY.get("stock")
|
agent = AGENT_REGISTRY.get("stock")
|
||||||
if agent:
|
if agent:
|
||||||
@@ -78,6 +74,5 @@ def init_scheduler():
|
|||||||
scheduler.add_job(_run_lotto_schedule, "cron", day_of_week="mon", hour=9, minute=0, id="lotto_curate")
|
scheduler.add_job(_run_lotto_schedule, "cron", day_of_week="mon", hour=9, minute=0, id="lotto_curate")
|
||||||
scheduler.add_job(_run_youtube_research, "cron", hour=9, minute=0, id="youtube_research")
|
scheduler.add_job(_run_youtube_research, "cron", hour=9, minute=0, id="youtube_research")
|
||||||
scheduler.add_job(_send_youtube_weekly_report, "cron", day_of_week="mon", hour=8, minute=0, id="youtube_weekly_report")
|
scheduler.add_job(_send_youtube_weekly_report, "cron", day_of_week="mon", hour=8, minute=0, id="youtube_weekly_report")
|
||||||
scheduler.add_job(_check_idle_breaks, "interval", seconds=60, id="idle_check")
|
|
||||||
scheduler.add_job(_poll_pipelines, "interval", seconds=30, id="pipeline_poll")
|
scheduler.add_job(_poll_pipelines, "interval", seconds=30, id="pipeline_poll")
|
||||||
scheduler.start()
|
scheduler.start()
|
||||||
|
|||||||
Reference in New Issue
Block a user