"""react — deterministic 0-LLM "keep going or replan?" filter. Given the agent's current action and fresh observations, returns a decision: no action -> replan, action finished -> replan, mid-action with no novelty -> continue. The LLM variant of this decision lives in brain.decide_tick and fires only on novelty. Architecture: retained as the cheap reflex layer; the production engine currently routes decisions through brain.py and WorldEngine phases. Design: every path has a fixed answer — this step must never stall the simulation on a model call. """ from __future__ import annotations from typing import Any, Dict, List, Optional from pydantic import BaseModel from src.core.log import get_logger from src.core.world_state import CurrentAction from src.core.perceive import Observation logger = get_logger(__name__) class ReactionDecision(BaseModel): should_replan: bool reason: str def decide_reaction( agent_id: str, persona: Dict[str, Any], current_action: Optional[CurrentAction], observations: List[Observation], memories: List[str], tick: int, ) -> ReactionDecision: """Cheap heuristic decision: no LLM involved.""" if current_action is None: return ReactionDecision(should_replan=True, reason="no current action to continue") if current_action.is_finished(tick): return ReactionDecision(should_replan=True, reason="current action has finished") if not observations: return ReactionDecision(should_replan=False, reason="mid-action, nothing new perceived") # Mid-action with observations: cheap heuristic — always continue. # The brain's decide_tick (LLM) will handle novel observation decisions. return ReactionDecision(should_replan=False, reason="mid-action, continuing current action") # Standalone sanity check (doesn't hit the network) if __name__ == "__main__": action = CurrentAction(description="sleeping", start_tick=0, end_tick=60) r1 = decide_reaction("a", {}, None, [], [], tick=0) assert r1.should_replan is True r2 = decide_reaction("a", {}, action, [], [], tick=61) assert r2.should_replan is True r3 = decide_reaction("a", {}, action, [], [], tick=30) assert r3.should_replan is False print("react.py cheap-path sanity checks passed (LLM branch not exercised here).")