File size: 2,339 Bytes
d660fa9
 
 
 
 
 
 
 
 
 
 
f4542ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
"""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).")