/ xpresso / states.py
states.py
  1  """
  2  DreamTalk State Machines
  3  
  4  Support for agentic holons with discrete states.
  5  
  6  States represent relational configurations - specific parameter values
  7  that define a mode of being. Transitions between states are animations.
  8  
  9  Usage:
 10      class MindVirus(Holon):
 11          fold: Bipolar = 0
 12  
 13          class States:
 14              idle = State(fold=1)
 15              hunting = State(fold=0.5)
 16              attached = State(fold=-1)
 17  
 18      # In a Dream:
 19      virus = MindVirus()
 20      self.play(virus.transition_to(virus.States.hunting), run_time=0.5)
 21  """
 22  
 23  
 24  class State:
 25      """
 26      A discrete state configuration.
 27  
 28      Defines specific parameter values for a mode of being.
 29  
 30      Args:
 31          **param_values: Parameter name → value mappings
 32      """
 33  
 34      def __init__(self, **param_values):
 35          self.param_values = param_values
 36          self.name = None  # Set when collected from States class
 37  
 38      def __repr__(self):
 39          if self.name:
 40              return f"State.{self.name}({self.param_values})"
 41          return f"State({self.param_values})"
 42  
 43  
 44  class StateMachine:
 45      """
 46      Manages state transitions for a holon.
 47  
 48      Created automatically when a Holon has a States class defined.
 49      """
 50  
 51      def __init__(self, holon, states_class):
 52          """
 53          Initialize state machine from a States class.
 54  
 55          Args:
 56              holon: The holon this state machine controls
 57              states_class: The class containing State definitions
 58          """
 59          self.holon = holon
 60          self.states = {}
 61          self.current_state = None
 62  
 63          # Collect states from class attributes
 64          for name in dir(states_class):
 65              if name.startswith('_'):
 66                  continue
 67              value = getattr(states_class, name)
 68              if isinstance(value, State):
 69                  value.name = name
 70                  self.states[name] = value
 71  
 72      def get_state(self, name):
 73          """Get a state by name."""
 74          return self.states.get(name)
 75  
 76      def transition_to(self, state):
 77          """
 78          Create animation to transition to a state.
 79  
 80          Args:
 81              state: State instance or state name
 82  
 83          Returns:
 84              AnimationGroup animating all parameters to their state values
 85          """
 86          from DreamTalk.animation.animation import ScalarAnimation
 87          from DreamTalk.animation.abstract_animators import AnimationGroup
 88  
 89          # Handle state name string
 90          if isinstance(state, str):
 91              state = self.states.get(state)
 92              if state is None:
 93                  raise ValueError(f"Unknown state: {state}")
 94  
 95          animations = []
 96  
 97          for param_name, target_value in state.param_values.items():
 98              # Try to find the parameter on the holon
 99              param = None
100  
101              # Check for parameter object (e.g., fold_parameter)
102              param_attr = f"{param_name}_parameter"
103              if hasattr(self.holon, param_attr):
104                  param = getattr(self.holon, param_attr)
105  
106              # Check for parameter in parameters list
107              if param is None and hasattr(self.holon, 'parameters'):
108                  for p in self.holon.parameters:
109                      if hasattr(p, 'name') and p.name.lower() == param_name.lower():
110                          param = p
111                          break
112  
113              if param is not None and hasattr(param, 'desc_id'):
114                  # Determine the correct target object
115                  # In generator_mode, parameters may need to target child objects
116                  target_obj = self.holon
117  
118                  anim = ScalarAnimation(
119                      target=target_obj,
120                      descriptor=param.desc_id,
121                      value_fin=target_value
122                  )
123                  animations.append(anim)
124  
125                  # Update the actual value
126                  target_obj.obj[param.desc_id] = target_value
127  
128          self.current_state = state
129  
130          if len(animations) == 0:
131              return None
132          elif len(animations) == 1:
133              return animations[0]
134          else:
135              return AnimationGroup(*animations)
136  
137  
138  def collect_states(holon):
139      """
140      Collect states from a holon's States class.
141  
142      Called during holon initialization.
143  
144      Args:
145          holon: The holon instance
146  
147      Returns:
148          StateMachine if States class exists, None otherwise
149      """
150      states_class = getattr(holon.__class__, 'States', None)
151      if states_class is None:
152          return None
153  
154      return StateMachine(holon, states_class)