| """ |
| Agent System — Core Knowledge of Agents |
| |
| Infants distinguish agents from objects by detecting: |
| 1. Self-propulsion: Agents can initiate motion without external contact |
| 2. Goal-directedness: Agents take efficient paths toward goals |
| 3. Contingency: Agents respond to other agents' behavior |
| |
| This module provides innate priors for identifying and reasoning about |
| intentional agents in the environment. |
| |
| Author: Algorembrant, Rembrant Oyangoren Albeos (2026) |
| """ |
|
|
| import numpy as np |
| from typing import Optional |
|
|
|
|
| class TrackedAgent: |
| """An entity being evaluated for agency.""" |
| |
| __slots__ = ['agent_id', 'position_history', 'agency_score', |
| 'self_propelled', 'goal_position', 'efficiency_history'] |
| |
| def __init__(self, agent_id: int): |
| self.agent_id = agent_id |
| self.position_history: list[np.ndarray] = [] |
| self.agency_score = 0.0 |
| self.self_propelled = False |
| self.goal_position: Optional[np.ndarray] = None |
| self.efficiency_history: list[float] = [] |
|
|
|
|
| class AgentSystem: |
| """ |
| Innate agency detection system. |
| |
| Evaluates whether tracked entities are intentional agents based on |
| three core cues: self-propulsion, goal-directedness, and contingency. |
| This is an innate prior — infants as young as 3 months make these |
| distinctions. |
| """ |
| |
| def __init__(self, |
| self_propulsion_threshold: float = 0.3, |
| efficiency_threshold: float = 0.6, |
| history_window: int = 20): |
| """ |
| Args: |
| self_propulsion_threshold: Min velocity change without contact to flag self-propulsion. |
| efficiency_threshold: Min path efficiency to flag goal-directedness. |
| history_window: Number of frames to consider for agency evaluation. |
| """ |
| self.self_propulsion_threshold = self_propulsion_threshold |
| self.efficiency_threshold = efficiency_threshold |
| self.history_window = history_window |
| self.agents: dict[int, TrackedAgent] = {} |
| |
| def update_entity(self, entity_id: int, position: np.ndarray, |
| was_contacted: bool = False): |
| """ |
| Update an entity's trajectory and evaluate agency cues. |
| |
| Args: |
| entity_id: Unique identifier for this entity. |
| position: Current position. |
| was_contacted: Whether another object contacted this entity this frame. |
| """ |
| if entity_id not in self.agents: |
| self.agents[entity_id] = TrackedAgent(entity_id) |
| |
| agent = self.agents[entity_id] |
| pos = np.asarray(position, dtype=np.float64) |
| agent.position_history.append(pos) |
| |
| |
| if len(agent.position_history) > self.history_window: |
| agent.position_history = agent.position_history[-self.history_window:] |
| |
| |
| if len(agent.position_history) >= 3: |
| |
| v_prev = agent.position_history[-2] - agent.position_history[-3] |
| v_curr = agent.position_history[-1] - agent.position_history[-2] |
| accel = np.linalg.norm(v_curr - v_prev) |
| |
| if accel > self.self_propulsion_threshold and not was_contacted: |
| agent.self_propelled = True |
| |
| |
| self._evaluate_goal_directedness(agent) |
| |
| |
| self._compute_agency_score(agent) |
| |
| def _evaluate_goal_directedness(self, agent: TrackedAgent): |
| """ |
| Evaluate whether the entity takes efficient paths toward a goal. |
| |
| Efficiency = direct_distance / path_length |
| Agents take short, efficient paths. Objects follow ballistic arcs. |
| """ |
| if len(agent.position_history) < 5: |
| return |
| |
| |
| start = agent.position_history[0] |
| end = agent.position_history[-1] |
| |
| direct_dist = np.linalg.norm(end - start) |
| if direct_dist < 0.01: |
| return |
| |
| |
| path_length = 0.0 |
| for i in range(1, len(agent.position_history)): |
| path_length += np.linalg.norm( |
| agent.position_history[i] - agent.position_history[i-1] |
| ) |
| |
| if path_length < 0.01: |
| return |
| |
| efficiency = direct_dist / path_length |
| agent.efficiency_history.append(efficiency) |
| |
| |
| if len(agent.efficiency_history) > 10: |
| agent.efficiency_history = agent.efficiency_history[-10:] |
| |
| def _compute_agency_score(self, agent: TrackedAgent): |
| """Combine cues into a single agency belief.""" |
| score = 0.0 |
| |
| |
| if agent.self_propelled: |
| score += 0.5 |
| |
| |
| if agent.efficiency_history: |
| avg_eff = np.mean(agent.efficiency_history) |
| if avg_eff > self.efficiency_threshold: |
| score += 0.3 |
| else: |
| score += 0.1 * avg_eff |
| |
| |
| if len(agent.position_history) >= 3: |
| velocities = [] |
| for i in range(1, len(agent.position_history)): |
| v = agent.position_history[i] - agent.position_history[i-1] |
| velocities.append(v) |
| if len(velocities) >= 2: |
| vel_array = np.array(velocities) |
| direction_changes = 0 |
| for i in range(1, len(vel_array)): |
| dot = np.dot(vel_array[i], vel_array[i-1]) |
| if dot < 0: |
| direction_changes += 1 |
| variability = direction_changes / len(vel_array) |
| score += 0.2 * variability |
| |
| agent.agency_score = np.clip(score, 0.0, 1.0) |
| |
| def is_agent(self, entity_id: int) -> bool: |
| """Check if an entity is believed to be an intentional agent.""" |
| agent = self.agents.get(entity_id) |
| if agent is None: |
| return False |
| return agent.agency_score > 0.5 |
| |
| def get_agency_score(self, entity_id: int) -> float: |
| """Get the agency belief for an entity (0=object, 1=agent).""" |
| agent = self.agents.get(entity_id) |
| if agent is None: |
| return 0.0 |
| return agent.agency_score |
| |
| def evaluate_contingency(self, id_a: int, id_b: int) -> float: |
| """ |
| Evaluate contingency between two entities. |
| |
| Contingency = one entity's actions correlate with another's. |
| This is a strong cue for social interaction. |
| |
| Returns: |
| Contingency score (0 to 1). |
| """ |
| a = self.agents.get(id_a) |
| b = self.agents.get(id_b) |
| if a is None or b is None: |
| return 0.0 |
| |
| min_len = min(len(a.position_history), len(b.position_history)) |
| if min_len < 3: |
| return 0.0 |
| |
| |
| va = np.diff(np.array(a.position_history[-min_len:]), axis=0) |
| vb = np.diff(np.array(b.position_history[-min_len:]), axis=0) |
| |
| |
| mag_a = np.linalg.norm(va, axis=1) |
| mag_b = np.linalg.norm(vb, axis=1) |
| |
| if np.std(mag_a) < 1e-8 or np.std(mag_b) < 1e-8: |
| return 0.0 |
| |
| correlation = np.corrcoef(mag_a, mag_b)[0, 1] |
| return float(np.clip(abs(correlation), 0.0, 1.0)) |
|
|