theapemachine commited on
Commit
5c08b2a
·
verified ·
1 Parent(s): f4d9997

V3 cleanup: tensegrity/engine/agent.py

Browse files
Files changed (1) hide show
  1. tensegrity/engine/agent.py +217 -0
tensegrity/engine/agent.py ADDED
@@ -0,0 +1,217 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ CognitiveAgent — V3 clean agent without legacy V1 baggage.
3
+
4
+ Replaces TensegrityAgent (legacy/v1/agent.py). Composes:
5
+ - UnifiedField (SBERT-native NGC + Hopfield)
6
+ - FreeEnergyEngine (discrete active inference)
7
+ - CausalArena (competing SCMs)
8
+ - EpisodicMemory (cross-item recall)
9
+
10
+ No Morton codes. No MarkovBlanket. No associative memory random projections.
11
+ """
12
+
13
+ import hashlib
14
+ import numpy as np
15
+ from typing import Optional, Dict, List, Any
16
+ import logging
17
+
18
+ from tensegrity.engine.unified_field import UnifiedField
19
+ from tensegrity.inference.free_energy import FreeEnergyEngine
20
+ from tensegrity.causal.arena import CausalArena
21
+ from tensegrity.causal.scm import StructuralCausalModel
22
+ from tensegrity.memory.episodic import EpisodicMemory
23
+
24
+ logger = logging.getLogger(__name__)
25
+
26
+ DEFAULT_MEDIATED_SCM_NAME = "mediated_causal"
27
+
28
+
29
+ class CognitiveAgent:
30
+ """
31
+ V3 cognitive agent operating in SBERT embedding space.
32
+
33
+ Provides the same interface that CognitiveController expects
34
+ (field, perceive(), arena, episodic, experience_replay, n_states)
35
+ without any V1 legacy code.
36
+ """
37
+
38
+ def __init__(
39
+ self,
40
+ n_states: int = 16,
41
+ n_observations: int = 32,
42
+ n_actions: int = 4,
43
+ planning_horizon: int = 3,
44
+ precision: float = 4.0,
45
+ context_dim: int = 32,
46
+ # UnifiedField parameters
47
+ obs_dim: int = 256,
48
+ hidden_dims: Optional[List[int]] = None,
49
+ fhrr_dim: int = 2048,
50
+ hopfield_beta: float = 0.05,
51
+ ngc_settle_steps: int = 20,
52
+ ngc_learning_rate: float = 0.005,
53
+ sbert_dim: Optional[int] = None,
54
+ # Legacy compat: these are accepted but ignored
55
+ sensory_dims: int = 4,
56
+ sensory_bits: int = 4,
57
+ associative_dim: int = 64,
58
+ ):
59
+ self.n_states = n_states
60
+ self.n_obs = n_observations
61
+ self.n_actions = n_actions
62
+
63
+ # === Unified Field (SBERT-native NGC + Hopfield) ===
64
+ self.field = UnifiedField(
65
+ obs_dim=obs_dim,
66
+ hidden_dims=hidden_dims or [128, 32],
67
+ fhrr_dim=fhrr_dim,
68
+ hopfield_beta=hopfield_beta,
69
+ ngc_settle_steps=ngc_settle_steps,
70
+ ngc_learning_rate=ngc_learning_rate,
71
+ sbert_dim=sbert_dim,
72
+ )
73
+
74
+ # === Free Energy Engine (discrete active inference) ===
75
+ self.engine = FreeEnergyEngine(
76
+ n_states=n_states,
77
+ n_observations=n_observations,
78
+ n_actions=n_actions,
79
+ planning_horizon=planning_horizon,
80
+ precision=precision,
81
+ policy_depth=min(planning_horizon, 3),
82
+ )
83
+
84
+ # === Causal Arena ===
85
+ self.arena = CausalArena(
86
+ prior_concentration=1.0,
87
+ falsification_threshold=-100.0,
88
+ min_models=2,
89
+ )
90
+ self._init_default_models()
91
+
92
+ # === Episodic Memory ===
93
+ self.episodic = EpisodicMemory(
94
+ context_dim=context_dim,
95
+ capacity=10000,
96
+ drift_rate=0.95,
97
+ encoding_strength=0.3,
98
+ )
99
+
100
+ # Agent state
101
+ self._step_count = 0
102
+ self._prev_belief: Optional[np.ndarray] = None
103
+
104
+ def _init_default_models(self):
105
+ """Initialize causal arena with default competing SCMs."""
106
+ model_a = StructuralCausalModel(name="direct_causal")
107
+ model_a.add_variable("state", n_values=self.n_states)
108
+ model_a.add_variable("observation", n_values=self.n_obs, parents=["state"])
109
+
110
+ model_b = StructuralCausalModel(name=DEFAULT_MEDIATED_SCM_NAME)
111
+ model_b.add_variable("cause", n_values=self.n_states)
112
+ model_b.add_variable("state", n_values=self.n_states, parents=["cause"])
113
+ model_b.add_variable("observation", n_values=self.n_obs, parents=["state"])
114
+
115
+ self.arena.register_model(model_a)
116
+ self.arena.register_model(model_b)
117
+
118
+ def perceive(self, raw_observation: np.ndarray) -> Dict[str, Any]:
119
+ """
120
+ Perception: observation → UnifiedField → active inference → causal arena.
121
+ """
122
+ self._step_count += 1
123
+ raw = np.asarray(raw_observation, dtype=np.float64).ravel()
124
+
125
+ # Run through unified field (SBERT-native settling)
126
+ cycle = self.field.observe(raw, input_type="numeric")
127
+ obs_vec = cycle["observation"]
128
+ decomp = cycle["energy"]
129
+ surprise = float(decomp.surprise)
130
+
131
+ # Discrete observation index for the FEE
132
+ h = hashlib.sha256(obs_vec.astype(np.float64).tobytes()).digest()
133
+ obs_idx = int.from_bytes(h[:8], byteorder="big", signed=False) % max(self.n_obs, 1)
134
+
135
+ # Active inference step
136
+ A = getattr(self.engine, '_A', None)
137
+ if A is None:
138
+ # Use epistemic memory-style matrices if available,
139
+ # otherwise use engine defaults
140
+ from tensegrity.memory.epistemic import EpistemicMemory
141
+ em = EpistemicMemory(
142
+ n_states=self.n_states,
143
+ n_observations=self.n_obs,
144
+ n_actions=self.n_actions,
145
+ )
146
+ self._epistemic = em
147
+ A, B, C, D = em.A, em.B, em.C, em.D
148
+ log_A = em.log_A
149
+ self.engine._A = A # cache
150
+ else:
151
+ em = self._epistemic
152
+ A, B, C, D = em.A, em.B, em.C, em.D
153
+ log_A = em.log_A
154
+
155
+ previous_action = self.engine.prev_action
156
+ inference_result = self.engine.step(obs_idx, A, B, C, D, log_A)
157
+ q_states = inference_result["belief_state"]
158
+ F = float(inference_result["free_energy"])
159
+
160
+ # Update epistemic memory
161
+ em.update_likelihood(obs_idx, q_states)
162
+ if previous_action is not None and self._prev_belief is not None:
163
+ em.update_transition(self._prev_belief, q_states, previous_action)
164
+ self._prev_belief = q_states.copy()
165
+
166
+ # Causal arena competition
167
+ causal_obs = {
168
+ "state": int(np.argmax(q_states)),
169
+ "observation": obs_idx,
170
+ }
171
+ if DEFAULT_MEDIATED_SCM_NAME in self.arena.models:
172
+ causal_obs["cause"] = int(np.argmax(q_states))
173
+ arena_result = self.arena.compete(causal_obs)
174
+
175
+ # Episodic memory encoding
176
+ self.episodic.encode(
177
+ observation=raw,
178
+ morton_code=np.array([obs_idx], dtype=np.int64),
179
+ belief_state=q_states,
180
+ action=int(inference_result["action"]),
181
+ surprise=surprise,
182
+ free_energy=F,
183
+ metadata={
184
+ "obs_idx": obs_idx,
185
+ "field_energy": float(decomp.total),
186
+ },
187
+ )
188
+
189
+ return {
190
+ "step": self._step_count,
191
+ "observation_index": obs_idx,
192
+ "belief_state": q_states,
193
+ "free_energy": F,
194
+ "surprise": surprise,
195
+ "action": inference_result["action"],
196
+ "action_confidence": inference_result["action_confidence"],
197
+ "arena": arena_result,
198
+ "epistemic_value": self.engine.epistemic_value,
199
+ "field_cycle": cycle,
200
+ }
201
+
202
+ def experience_replay(self, n_episodes: int = 10) -> Dict[str, Any]:
203
+ """Replay past episodes to strengthen beliefs."""
204
+ episodes = self.episodic.replay(n_episodes)
205
+ em = getattr(self, '_epistemic', None)
206
+ if em is not None:
207
+ for ep in episodes:
208
+ obs_idx = ep.metadata.get('obs_idx', 0)
209
+ em.update_likelihood(obs_idx, ep.belief_state)
210
+ return {
211
+ 'episodes_replayed': len(episodes),
212
+ 'mean_surprise': np.mean([ep.surprise for ep in episodes]) if episodes else 0,
213
+ }
214
+
215
+ def add_causal_model(self, model: StructuralCausalModel):
216
+ """Add a competing causal model to the arena."""
217
+ self.arena.register_model(model)