| | """Scheduler — manages agent turn order and batching for LLM calls.""" |
| |
|
| | from __future__ import annotations |
| |
|
| | import asyncio |
| | import logging |
| | from typing import Optional, TYPE_CHECKING |
| |
|
| | if TYPE_CHECKING: |
| | from soci.agents.agent import Agent |
| | from soci.agents.routine import DailyRoutine |
| | from soci.world.clock import SimClock |
| |
|
| | logger = logging.getLogger(__name__) |
| |
|
| |
|
| | def prioritize_agents(agents: list[Agent], clock: SimClock) -> list[Agent]: |
| | """Sort agents by priority for this tick. Agents with urgent needs go first.""" |
| | def priority_score(agent: Agent) -> float: |
| | score = 0.0 |
| | |
| | if agent.needs.is_critical: |
| | score += 10.0 |
| | urgent = agent.needs.urgent_needs |
| | score += len(urgent) * 2.0 |
| | |
| | if not agent.is_busy: |
| | score += 5.0 |
| | |
| | if agent.state.value == "sleeping": |
| | score -= 5.0 |
| | |
| | if agent.is_player: |
| | score += 20.0 |
| | return score |
| |
|
| | return sorted(agents, key=priority_score, reverse=True) |
| |
|
| |
|
| | async def batch_llm_calls( |
| | coros: list, |
| | max_concurrent: int = 10, |
| | ) -> list: |
| | """Run multiple LLM coroutines concurrently with a concurrency limit.""" |
| | semaphore = asyncio.Semaphore(max_concurrent) |
| |
|
| | async def limited(coro): |
| | async with semaphore: |
| | return await coro |
| |
|
| | results = await asyncio.gather( |
| | *[limited(c) for c in coros], |
| | return_exceptions=True, |
| | ) |
| |
|
| | |
| | for i, r in enumerate(results): |
| | if isinstance(r, Exception): |
| | logger.error(f"LLM call {i} failed: {r}") |
| | results[i] = None |
| |
|
| | return results |
| |
|
| |
|
| | def should_skip_llm( |
| | agent: Agent, |
| | clock: SimClock, |
| | routine: Optional[DailyRoutine] = None, |
| | ) -> bool: |
| | """Determine if we can skip the LLM call for this agent (habit caching).""" |
| | |
| | if agent.is_player: |
| | return False |
| |
|
| | |
| | if agent.is_busy: |
| | return True |
| |
|
| | |
| | if routine: |
| | if not routine.is_awake_at(clock.hour) and agent.state.value == "sleeping": |
| | return True |
| | elif agent.state.value == "sleeping" and clock.is_sleeping_hours: |
| | |
| | return True |
| |
|
| | return False |
| |
|