dsh-biocore 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/lib/index.js +170 -27
  2. package/package.json +1 -1
  3. package/python/biocore_runtime/pipeline.py +42 -0
  4. package/python/biocore_runtime/rerank.py +201 -0
  5. package/python/mindbase/__init__.py +18 -0
  6. package/python/mindbase/__main__.py +3 -0
  7. package/python/mindbase/actions.py +56 -0
  8. package/python/mindbase/active_inference.py +54 -0
  9. package/python/mindbase/addressing.py +69 -0
  10. package/python/mindbase/affect.py +86 -0
  11. package/python/mindbase/affective.py +111 -0
  12. package/python/mindbase/allostasis.py +28 -0
  13. package/python/mindbase/appraisal.py +101 -0
  14. package/python/mindbase/attachment.py +49 -0
  15. package/python/mindbase/attention.py +30 -0
  16. package/python/mindbase/autonomy.py +81 -0
  17. package/python/mindbase/autonomy_bench.py +251 -0
  18. package/python/mindbase/autonomy_evolve.py +342 -0
  19. package/python/mindbase/autonomy_loop.py +65 -0
  20. package/python/mindbase/autonomy_meta.py +91 -0
  21. package/python/mindbase/base_only.py +28 -0
  22. package/python/mindbase/benchmark.py +61 -0
  23. package/python/mindbase/bio_adaptation.py +274 -0
  24. package/python/mindbase/bio_noise.py +39 -0
  25. package/python/mindbase/bio_semantic.py +138 -0
  26. package/python/mindbase/bm25.py +46 -0
  27. package/python/mindbase/bootstrap.py +256 -0
  28. package/python/mindbase/bootstrap_real.py +231 -0
  29. package/python/mindbase/brain.py +178 -0
  30. package/python/mindbase/care.py +78 -0
  31. package/python/mindbase/chaos_search.py +79 -0
  32. package/python/mindbase/character_profile.py +33 -0
  33. package/python/mindbase/circadian.py +38 -0
  34. package/python/mindbase/circuit.py +235 -0
  35. package/python/mindbase/clock.py +53 -0
  36. package/python/mindbase/companion_runtime.py +366 -0
  37. package/python/mindbase/companion_train.py +126 -0
  38. package/python/mindbase/config.py +66 -0
  39. package/python/mindbase/connection_state.py +76 -0
  40. package/python/mindbase/connectome_prior.py +35 -0
  41. package/python/mindbase/connectome_world.py +158 -0
  42. package/python/mindbase/constructs.py +88 -0
  43. package/python/mindbase/context_builder.py +58 -0
  44. package/python/mindbase/critical.py +36 -0
  45. package/python/mindbase/criticality.py +123 -0
  46. package/python/mindbase/defaults.py +27 -0
  47. package/python/mindbase/demo.py +163 -0
  48. package/python/mindbase/diary.py +116 -0
  49. package/python/mindbase/diary_service.py +40 -0
  50. package/python/mindbase/digital_prior.py +37 -0
  51. package/python/mindbase/diversity.py +110 -0
  52. package/python/mindbase/drives.py +106 -0
  53. package/python/mindbase/embedding.py +132 -0
  54. package/python/mindbase/embedding_memory.py +74 -0
  55. package/python/mindbase/embodiment.py +83 -0
  56. package/python/mindbase/emotion_calibration.py +39 -0
  57. package/python/mindbase/emotion_calibrator.py +50 -0
  58. package/python/mindbase/emotion_core.py +113 -0
  59. package/python/mindbase/emotion_dynamics.py +104 -0
  60. package/python/mindbase/emotion_intent.py +164 -0
  61. package/python/mindbase/emotion_layer.py +200 -0
  62. package/python/mindbase/emotion_schema.py +92 -0
  63. package/python/mindbase/engine.py +781 -0
  64. package/python/mindbase/episode_log.py +90 -0
  65. package/python/mindbase/evaluation.py +493 -0
  66. package/python/mindbase/event_appraisal.py +84 -0
  67. package/python/mindbase/evolution.py +77 -0
  68. package/python/mindbase/evolve_offline.py +232 -0
  69. package/python/mindbase/experiments.py +358 -0
  70. package/python/mindbase/expression.py +54 -0
  71. package/python/mindbase/external_brain.py +157 -0
  72. package/python/mindbase/fly_mb.py +159 -0
  73. package/python/mindbase/fly_tasks.py +45 -0
  74. package/python/mindbase/forage.py +179 -0
  75. package/python/mindbase/goal_generator.py +98 -0
  76. package/python/mindbase/goals.py +153 -0
  77. package/python/mindbase/grid_world.py +123 -0
  78. package/python/mindbase/group_autonomy.py +92 -0
  79. package/python/mindbase/group_context.py +67 -0
  80. package/python/mindbase/homeostatic.py +71 -0
  81. package/python/mindbase/homeostatic_action.py +38 -0
  82. package/python/mindbase/human_standard.py +53 -0
  83. package/python/mindbase/identity_suite.py +50 -0
  84. package/python/mindbase/image_context.py +27 -0
  85. package/python/mindbase/impression.py +119 -0
  86. package/python/mindbase/initiative_quota.py +145 -0
  87. package/python/mindbase/insight.py +51 -0
  88. package/python/mindbase/intelligence_search.py +108 -0
  89. package/python/mindbase/intention.py +76 -0
  90. package/python/mindbase/interaction_log.py +30 -0
  91. package/python/mindbase/interoception.py +67 -0
  92. package/python/mindbase/invariants.py +53 -0
  93. package/python/mindbase/iteration.py +29 -0
  94. package/python/mindbase/layered_memory.py +170 -0
  95. package/python/mindbase/layers/__init__.py +18 -0
  96. package/python/mindbase/layers/l0_substrate.py +39 -0
  97. package/python/mindbase/layers/l1_homeostasis.py +6 -0
  98. package/python/mindbase/layers/l2_affect.py +6 -0
  99. package/python/mindbase/layers/l3_attention.py +6 -0
  100. package/python/mindbase/layers/l4_learning.py +17 -0
  101. package/python/mindbase/layers/l5_self_social.py +35 -0
  102. package/python/mindbase/layers/l6_expression.py +23 -0
  103. package/python/mindbase/layers/l7_evaluation.py +45 -0
  104. package/python/mindbase/learner.py +196 -0
  105. package/python/mindbase/lexicon.py +33 -0
  106. package/python/mindbase/life_state.py +169 -0
  107. package/python/mindbase/lifecycle.py +87 -0
  108. package/python/mindbase/liquid_readout.py +48 -0
  109. package/python/mindbase/llm_adapter.py +233 -0
  110. package/python/mindbase/llm_cache.py +77 -0
  111. package/python/mindbase/llm_gate.py +85 -0
  112. package/python/mindbase/llm_openai.py +130 -0
  113. package/python/mindbase/llm_setup.py +99 -0
  114. package/python/mindbase/longrun.py +83 -0
  115. package/python/mindbase/longterm_memory.py +606 -0
  116. package/python/mindbase/mb/__init__.py +14 -0
  117. package/python/mindbase/mb/apl.py +22 -0
  118. package/python/mindbase/mb/dan.py +14 -0
  119. package/python/mindbase/mb/habituation.py +27 -0
  120. package/python/mindbase/mb/kc.py +28 -0
  121. package/python/mindbase/mb/mbon.py +23 -0
  122. package/python/mindbase/mb/mushroom_body.py +116 -0
  123. package/python/mindbase/memory.py +201 -0
  124. package/python/mindbase/memory_framework.py +158 -0
  125. package/python/mindbase/memory_retriever.py +110 -0
  126. package/python/mindbase/memory_summarizer.py +177 -0
  127. package/python/mindbase/memory_task.py +32 -0
  128. package/python/mindbase/memory_trace.py +46 -0
  129. package/python/mindbase/meta.py +32 -0
  130. package/python/mindbase/motivation.py +31 -0
  131. package/python/mindbase/multi_session.py +19 -0
  132. package/python/mindbase/narrative.py +87 -0
  133. package/python/mindbase/needs.py +77 -0
  134. package/python/mindbase/neural.py +45 -0
  135. package/python/mindbase/neuron_models.py +134 -0
  136. package/python/mindbase/noise.py +309 -0
  137. package/python/mindbase/one_on_one.py +60 -0
  138. package/python/mindbase/online_iteration.py +63 -0
  139. package/python/mindbase/ostracism.py +35 -0
  140. package/python/mindbase/parallel_eval.py +60 -0
  141. package/python/mindbase/parallel_qd.py +64 -0
  142. package/python/mindbase/persona.py +221 -0
  143. package/python/mindbase/persona_ab.py +64 -0
  144. package/python/mindbase/persona_bridge.py +129 -0
  145. package/python/mindbase/persona_compile.py +34 -0
  146. package/python/mindbase/persona_dsh.py +120 -0
  147. package/python/mindbase/persona_learner.py +157 -0
  148. package/python/mindbase/physiology.py +197 -0
  149. package/python/mindbase/population_lifecycle.py +83 -0
  150. package/python/mindbase/preferences.py +50 -0
  151. package/python/mindbase/proactive_agent.py +274 -0
  152. package/python/mindbase/proactive_gate.py +53 -0
  153. package/python/mindbase/proactive_group.py +119 -0
  154. package/python/mindbase/proactive_policy.py +422 -0
  155. package/python/mindbase/proactive_profile.py +196 -0
  156. package/python/mindbase/proactive_timing.py +28 -0
  157. package/python/mindbase/pyramid_stages.py +61 -0
  158. package/python/mindbase/quantum.py +132 -0
  159. package/python/mindbase/quantum_chaos.py +98 -0
  160. package/python/mindbase/reflection.py +68 -0
  161. package/python/mindbase/regulation_posture.py +50 -0
  162. package/python/mindbase/relationship_session.py +431 -0
  163. package/python/mindbase/relationship_suite.py +45 -0
  164. package/python/mindbase/relationships.py +70 -0
  165. package/python/mindbase/replay.py +22 -0
  166. package/python/mindbase/reply_diversity.py +53 -0
  167. package/python/mindbase/reply_gate.py +52 -0
  168. package/python/mindbase/rerank.py +41 -0
  169. package/python/mindbase/rerank_local.py +46 -0
  170. package/python/mindbase/ripple.py +187 -0
  171. package/python/mindbase/robust_search.py +68 -0
  172. package/python/mindbase/runtime.py +113 -0
  173. package/python/mindbase/scoped_identity.py +116 -0
  174. package/python/mindbase/self_insight.py +51 -0
  175. package/python/mindbase/self_model.py +116 -0
  176. package/python/mindbase/selftest.py +118 -0
  177. package/python/mindbase/semantic_memory.py +108 -0
  178. package/python/mindbase/session_store.py +175 -0
  179. package/python/mindbase/session_window.py +31 -0
  180. package/python/mindbase/shadow_eval.py +42 -0
  181. package/python/mindbase/sim_env.py +86 -0
  182. package/python/mindbase/social.py +138 -0
  183. package/python/mindbase/social_constraint.py +141 -0
  184. package/python/mindbase/social_feedback.py +95 -0
  185. package/python/mindbase/social_norm.py +225 -0
  186. package/python/mindbase/spiking_reservoir.py +204 -0
  187. package/python/mindbase/spontaneity.py +40 -0
  188. package/python/mindbase/story_loop.py +203 -0
  189. package/python/mindbase/survival_gauntlet.py +128 -0
  190. package/python/mindbase/symbolic.py +170 -0
  191. package/python/mindbase/teacher.py +244 -0
  192. package/python/mindbase/theory.py +42 -0
  193. package/python/mindbase/theory_of_mind.py +98 -0
  194. package/python/mindbase/translation_layer.py +80 -0
  195. package/python/mindbase/turn_eval.py +52 -0
  196. package/python/mindbase/types.py +78 -0
  197. package/python/mindbase/uncertainty_field.py +61 -0
  198. package/python/mindbase/user_loop.py +45 -0
  199. package/python/mindbase/user_profile.py +73 -0
  200. package/python/mindbase/value_model.py +64 -0
  201. package/python/mindbase/values.py +102 -0
  202. package/python/mindbase/vector_store.py +78 -0
  203. package/python/mindbase/viability.py +41 -0
  204. package/python/mindbase/virtual_body.py +90 -0
  205. package/python/mindbase/will.py +60 -0
  206. package/python/mindbase/working_memory.py +32 -0
  207. package/python/mindbase/workspace.py +55 -0
  208. package/ui/panel.css +16 -5
  209. package/ui/panel.html +25 -1
  210. package/ui/panel.js +57 -15
@@ -0,0 +1,251 @@
1
+ """Autonomy benchmark: free-running base with no chat and no LLM.
2
+
3
+ The product goal is autonomy, not chat. This bench runs only tick events:
4
+ - no user messages
5
+ - no LLM calls
6
+ - optional perturbation (energy crash / stress spike) to test recovery
7
+
8
+ Metrics:
9
+ - spontaneous action rate / action entropy
10
+ - viability/energy/stress/sleep
11
+ - drive volatility
12
+ - recovery steps after perturbation
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import math
17
+ import random
18
+ from collections import Counter
19
+ from typing import Dict, List, Optional
20
+
21
+ from .config import MindConfig
22
+ from .engine import MindBase
23
+ from .types import Event
24
+
25
+
26
+ def _entropy(counts: Dict[str, int]) -> float:
27
+ total = sum(counts.values())
28
+ if total <= 0:
29
+ return 0.0
30
+ h = 0.0
31
+ for c in counts.values():
32
+ if c > 0:
33
+ p = c / total
34
+ h -= p * math.log(p)
35
+ return h / math.log(max(2, len(counts)))
36
+
37
+
38
+ def run_autonomy(steps: int = 240, dt: float = 3600.0, seed: int = 42,
39
+ perturb: bool = True, cfg: Optional[MindConfig] = None,
40
+ collect_series: bool = False, mortality: bool = False,
41
+ starvation: float = 0.0, initial_energy: Optional[float] = None,
42
+ damage_threshold: Optional[float] = None, energy_policy=None,
43
+ explore_success: float = 0.45, predator_risk: float = 0.0,
44
+ thirst_rate: float = 0.0, inflammation_rate: float = 0.0,
45
+ pain_rate: float = 0.0, physiology_params=None, emotion_weights=None,
46
+ regime_shift: int = 0, env_chaos: float = 0.0,
47
+ chaos_r: float = 3.9, env_drift: float = 0.0,
48
+ env_noise: float = 0.05, effect_delay: int = 0) -> Dict[str, object]:
49
+ mind = MindBase(cfg if cfg is not None else MindConfig(seed=int(seed)))
50
+ if initial_energy is not None:
51
+ mind.drives.values["energy"] = max(0.0, min(1.0, float(initial_energy)))
52
+ if energy_policy is not None:
53
+ mind.heads["energy_policy"] = energy_policy
54
+ if physiology_params:
55
+ mind.physiology.set_coefficients(physiology_params)
56
+ if emotion_weights:
57
+ for emotion, features in dict(emotion_weights).items():
58
+ for feat, value in dict(features).items():
59
+ mind.physiology.set_emotion_weight(emotion, feat, value)
60
+ threshold_start = float(getattr(mind.autonomy, "threshold", 0.0))
61
+ damage = 0.0
62
+ known_food = 0.0
63
+ regime = 0
64
+ shifts = 0
65
+ chaos_x = 0.5
66
+ chaos_sum = 0.0
67
+ food_env = 0.60
68
+ water_env = 0.70
69
+ predator_env = 0.50
70
+ env_food_sum = 0.0
71
+ env_water_sum = 0.0
72
+ env_pred_sum = 0.0
73
+ pending_damage = []
74
+ pending_energy = []
75
+ delayed_applied = 0
76
+ mind.env = {"known_food": known_food, "food_uncertainty": 1.0 - known_food, "regime": float(regime),
77
+ "food_env": food_env, "water_env": water_env, "predator_env": predator_env}
78
+ env_rng = random.Random(int(seed) + 9173)
79
+ actions: Counter = Counter()
80
+ reasons: Counter = Counter()
81
+ rows = []
82
+ perturb_at = max(1, int(steps) // 2) if perturb else -1
83
+ pre_stress = 0.0
84
+ for i in range(max(1, int(steps))):
85
+ # apply effects that were queued earlier
86
+ due_d = [v for t, v in pending_damage if t <= i]
87
+ pending_damage = [(t, v) for t, v in pending_damage if t > i]
88
+ due_e = [v for t, v in pending_energy if t <= i]
89
+ pending_energy = [(t, v) for t, v in pending_energy if t > i]
90
+ if due_d:
91
+ damage += sum(due_d)
92
+ delayed_applied += len(due_d)
93
+ if due_e:
94
+ mind.drives.values["energy"] = max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) + sum(due_e)))
95
+ chaos_x = float(chaos_r) * chaos_x * (1.0 - chaos_x)
96
+ chaos_x = max(0.0, min(1.0, chaos_x))
97
+ chaos_sum += chaos_x
98
+ food_env = max(0.0, min(1.0, food_env + float(env_drift) * (env_rng.gauss(0.0, float(env_noise)) + 0.05 * (0.5 - food_env))))
99
+ water_env = max(0.0, min(1.0, water_env + float(env_drift) * (env_rng.gauss(0.0, float(env_noise)) + 0.05 * (0.5 - water_env))))
100
+ predator_env = max(0.0, min(1.0, predator_env + float(env_drift) * (env_rng.gauss(0.0, float(env_noise)) + 0.05 * (0.5 - predator_env))))
101
+ env_food_sum += food_env
102
+ env_water_sum += water_env
103
+ env_pred_sum += predator_env
104
+ risk_eff = float(predator_risk) * (0.5 + float(env_chaos) * 1.5 * chaos_x) * (0.5 + float(env_drift) * predator_env)
105
+ if regime_shift and i > 0 and i % int(regime_shift) == 0:
106
+ regime = 1 - regime
107
+ shifts += 1
108
+ known_food = 0.0
109
+ mind.env = {"known_food": known_food, "food_uncertainty": 1.0, "regime": float(regime)}
110
+ if i == perturb_at:
111
+ pre_stress = float(mind.affect.stress)
112
+ mind.drives.values["energy"] = 0.05
113
+ mind.affect.stress = 0.80
114
+ mind.affect.v = -0.50
115
+ if env_chaos > 0.0:
116
+ damage += float(env_chaos) * 0.008 * chaos_x
117
+ if starvation > 0.0:
118
+ mind.drives.values["energy"] = max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) - float(starvation)))
119
+ if thirst_rate > 0.0:
120
+ mind.physiology.hydration = max(0.0, float(mind.physiology.hydration) - float(thirst_rate))
121
+ if inflammation_rate > 0.0:
122
+ mind.physiology.inflammation = min(1.0, float(mind.physiology.inflammation) + float(inflammation_rate))
123
+ if pain_rate > 0.0:
124
+ mind.physiology.pain = min(1.0, float(mind.physiology.pain) + float(pain_rate))
125
+ out = mind.step(Event(kind="tick", ts=mind.t + float(dt)))
126
+ action = str(getattr(out, "action", "silence"))
127
+ kind = str(getattr(getattr(out, "act", None), "kind", ""))
128
+ if kind == "forage":
129
+ if risk_eff > 0.0 and env_rng.random() < float(risk_eff):
130
+ mind.drives.values["energy"] = max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) - 0.05))
131
+ mind.affect.stress = min(1.0, float(mind.affect.stress) + 0.10)
132
+ (pending_damage.append((i + int(effect_delay), 0.12)) if effect_delay > 0 else None)
133
+ damage = damage if effect_delay > 0 else damage + 0.12
134
+ gain = (0.10 if known_food > 0.10 else 0.04) * (1.0 if env_drift <= 0.0 else (0.5 + food_env))
135
+ (pending_energy.append((i + int(effect_delay), gain)) if effect_delay > 0 else None)
136
+ mind.drives.values["energy"] = (float(mind.drives.values.get("energy", 0.0)) if effect_delay > 0 else max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) + gain)))
137
+ known_food = max(0.0, known_food - 0.05)
138
+ (pending_damage.append((i + int(effect_delay), 0.02)) if effect_delay > 0 else None)
139
+ damage = damage if effect_delay > 0 else damage + 0.02
140
+ elif kind == "explore":
141
+ if risk_eff > 0.0 and env_rng.random() < float(risk_eff) * 0.7:
142
+ mind.drives.values["energy"] = max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) - 0.05))
143
+ mind.affect.stress = min(1.0, float(mind.affect.stress) + 0.12)
144
+ (pending_damage.append((i + int(effect_delay), 0.14)) if effect_delay > 0 else None)
145
+ damage = damage if effect_delay > 0 else damage + 0.14
146
+ elif env_rng.random() < float(explore_success):
147
+ (pending_energy.append((i + int(effect_delay), 0.18)) if effect_delay > 0 else None)
148
+ mind.drives.values["energy"] = (float(mind.drives.values.get("energy", 0.0)) if effect_delay > 0 else max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) + 0.18)))
149
+ known_food = min(1.0, known_food + 0.25)
150
+ else:
151
+ mind.drives.values["energy"] = max(0.0, min(1.0, float(mind.drives.values.get("energy", 0.0)) - 0.02))
152
+ (pending_damage.append((i + int(effect_delay), 0.03)) if effect_delay > 0 else None)
153
+ damage = damage if effect_delay > 0 else damage + 0.03
154
+ elif kind == "drink":
155
+ gain_d = 0.18 * (1.0 if env_drift <= 0.0 else (0.5 + water_env))
156
+ mind.physiology.hydration = min(1.0, float(mind.physiology.hydration) + gain_d)
157
+ elif kind == "heal":
158
+ mind.physiology.inflammation = max(0.0, float(mind.physiology.inflammation) - 0.18)
159
+ mind.physiology.pain = max(0.0, float(mind.physiology.pain) - 0.06)
160
+ elif kind == "rest":
161
+ mind.physiology.sleep_pressure = max(0.0, float(mind.physiology.sleep_pressure) - 0.12)
162
+ elif kind == "socialize":
163
+ mind.drives.values["social"] = max(0.0, min(1.0, float(mind.drives.values.get("social", 0.0)) + 0.05))
164
+ actions[action] += 1
165
+ reasons[str(getattr(out, "reason", ""))] += 1
166
+ rows.append({
167
+ "i": i,
168
+ "action": action,
169
+ "viability": float(mind.viability_state.get("viability", 1.0)),
170
+ "energy": float(mind.drives.values.get("energy", 0.0)),
171
+ "social": float(mind.drives.values.get("social", 0.0)),
172
+ "stress": float(mind.affect.stress),
173
+ "asleep": bool(mind.clock.asleep),
174
+ "needs": float(mind.needs.snapshot().get("satisfaction", 0.0)) if hasattr(mind, "needs") else 0.0,
175
+ "damage": float(damage),
176
+ "pain": float(mind.physiology.pain),
177
+ "viability": float(mind.viability_state.get("viability", 1.0)),
178
+ })
179
+ damage += max(0.0, 0.20 - float(mind.drives.values.get("energy", 0.0)))
180
+ if mortality and (float(mind.drives.values.get("energy", 0.0)) <= 0.001 or float(mind.viability_state.get("viability", 1.0)) <= 0.001
181
+ or (damage_threshold is not None and damage >= float(damage_threshold))
182
+ or float(mind.physiology.hydration) <= 0.03
183
+ or float(mind.physiology.inflammation) >= 0.95
184
+ or float(mind.physiology.pain) >= 0.95
185
+ or float(mind.physiology.sleep_pressure) >= 1.40):
186
+ break
187
+ dead = bool(mortality and rows and (float(mind.drives.values.get("energy", 0.0)) <= 0.001
188
+ or float(mind.viability_state.get("viability", 1.0)) <= 0.001
189
+ or (damage_threshold is not None and damage >= float(damage_threshold))
190
+ or float(mind.physiology.hydration) <= 0.03
191
+ or float(mind.physiology.inflammation) >= 0.95
192
+ or float(mind.physiology.pain) >= 0.95
193
+ or float(mind.physiology.sleep_pressure) >= 1.40))
194
+ viability = [r["viability"] for r in rows]
195
+ energy = [r["energy"] for r in rows]
196
+ stress = [r["stress"] for r in rows]
197
+ social = [r["social"] for r in rows]
198
+ vol = 0.0
199
+ if len(energy) > 1:
200
+ vol = sum(abs(energy[i] - energy[i - 1]) + abs(social[i] - social[i - 1]) for i in range(1, len(energy))) / (len(energy) - 1)
201
+ recovery = -1
202
+ if perturb and perturb_at > 0:
203
+ target_stress = min(0.30, pre_stress + 0.10)
204
+ for r in rows[perturb_at:]:
205
+ if r["viability"] >= 0.90 and r["stress"] <= target_stress:
206
+ recovery = int(r["i"] - perturb_at)
207
+ break
208
+ half = steps // 2
209
+ return {
210
+ "steps": len(rows),
211
+ "actions": dict(actions),
212
+ "reasons": dict(reasons.most_common(8)),
213
+ "spontaneous_rate": round(1.0 - actions.get("silence", 0) / max(1, len(rows)), 4),
214
+ "action_entropy": round(_entropy(dict(actions)), 4),
215
+ "viability_min": round(min(viability), 4),
216
+ "viability_mean": round(sum(viability) / len(viability), 4),
217
+ "energy_mean": round(sum(energy) / len(energy), 4),
218
+ "stress_mean": round(sum(stress) / len(stress), 4),
219
+ "social_mean": round(sum(social) / len(social), 4),
220
+ "sleep_fraction": round(sum(1 for r in rows if r["asleep"]) / max(1, len(rows)), 4),
221
+ "drive_volatility": round(vol, 5),
222
+ "recovery_steps": recovery,
223
+ "perturbed": bool(perturb),
224
+ "pre_perturb_stress": round(pre_stress, 4),
225
+ "threshold_start": round(threshold_start, 4),
226
+ "threshold_end": round(float(getattr(mind.autonomy, "threshold", 0.0)), 4),
227
+ "dead": bool(dead),
228
+ "lifespan": len(rows),
229
+ "death_reason": ("damage" if dead and damage_threshold is not None and damage >= float(damage_threshold) else ("starvation" if dead and float(mind.drives.values.get("energy", 0.0)) <= 0.001 else ("viability" if dead else "alive"))),
230
+ "damage": round(damage, 4),
231
+ "known_food": round(known_food, 4),
232
+ "regime": int(regime),
233
+ "regime_shifts": int(shifts),
234
+ "env_chaos": round(float(env_chaos), 4),
235
+ "chaos_mean": round(chaos_sum / max(1, len(rows)), 4),
236
+ "chaos_final": round(chaos_x, 4),
237
+ "env_food_mean": round(env_food_sum / max(1, len(rows)), 4),
238
+ "env_water_mean": round(env_water_sum / max(1, len(rows)), 4),
239
+ "env_predator_mean": round(env_pred_sum / max(1, len(rows)), 4),
240
+ "effect_delay": int(effect_delay),
241
+ "delayed_pending": len(pending_damage) + len(pending_energy),
242
+ "delayed_applied": int(delayed_applied),
243
+ "pending_damage_sum": round(sum(v for _, v in pending_damage), 4),
244
+ "pending_energy_sum": round(sum(v for _, v in pending_energy), 4),
245
+ "hydration": round(float(mind.physiology.hydration), 4),
246
+ "inflammation": round(float(mind.physiology.inflammation), 4),
247
+ "pain": round(float(mind.physiology.pain), 4),
248
+ "sleep_pressure": round(float(mind.physiology.sleep_pressure), 4),
249
+ "needs": mind.needs.snapshot() if hasattr(mind, "needs") else {},
250
+ "series": rows if collect_series else None,
251
+ }
@@ -0,0 +1,342 @@
1
+ """Quality-diversity over autonomy configurations.
2
+
3
+ No chat. Each genome is a small set of mutable parameters:
4
+ autonomy_threshold, volition_temperature, autonomy_adapt_lr, noise_sigma
5
+
6
+ Fitness combines viability, recovery, action entropy and spontaneous rate.
7
+ The archive is behavior-indexed (spontaneous rate × action entropy), so the
8
+ loop preserves different behavioral modes instead of collapsing to one.
9
+ """
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import math
14
+ import random
15
+ from dataclasses import dataclass, asdict, replace
16
+ from typing import Dict, List, Tuple
17
+
18
+ from .autonomy_bench import run_autonomy
19
+ from .forage import ForagePolicy
20
+ from .symbolic import mutate as symbolic_mutate, random_expr, formula_stability, formula_complexity
21
+ from .config import MindConfig
22
+
23
+
24
+ @dataclass
25
+ class AutonomyGenome:
26
+ autonomy_threshold: float = 0.90
27
+ volition_temperature: float = 0.0
28
+ autonomy_adapt_lr: float = 0.004
29
+ noise_sigma: float = 0.03
30
+ forage_weight: float = 1.0
31
+ rest_weight: float = 0.7
32
+ social_weight: float = 0.5
33
+ forage_temperature: float = 0.3
34
+ fear_weight: float = 0.5
35
+ curiosity_weight: float = 0.3
36
+ explore_weight: float = 0.5
37
+ drink_weight: float = 0.6
38
+ heal_weight: float = 0.5
39
+ hydration_decay: float = 0.015
40
+ sleep_accum: float = 0.035
41
+ inflammation_gain: float = 0.002
42
+ cortisol_gain: float = 0.60
43
+ fear_stress: float = 0.35
44
+ joy_v: float = 0.45
45
+ need_physio_gate: float = 0.8
46
+ need_safety_gate: float = 0.8
47
+ need_belong_gate: float = 0.8
48
+ need_actual_gate: float = 0.8
49
+ planning: float = 0.5
50
+ quantum_weight: float = 0.5
51
+ quantum_gamma: float = 0.05
52
+ quantum_dephase: float = 0.10
53
+ world_weight: float = 0.5
54
+ world_horizon: int = 1
55
+ world_beam: int = 1
56
+ emotion_social_weight: float = 0.5
57
+ emotion_avoid_weight: float = 0.5
58
+ emotion_explore_weight: float = 0.5
59
+ emotion_recovery_weight: float = 0.5
60
+ formula_expr: object = None
61
+
62
+
63
+ def _clip(x: float, lo: float, hi: float) -> float:
64
+ return lo if x < lo else hi if x > hi else x
65
+
66
+
67
+ def mutate(g: AutonomyGenome, rng: random.Random, scale: float = 0.15) -> AutonomyGenome:
68
+ return AutonomyGenome(
69
+ autonomy_threshold=_clip(g.autonomy_threshold + rng.gauss(0, scale), 0.30, 1.20),
70
+ volition_temperature=_clip(g.volition_temperature + rng.gauss(0, scale), 0.0, 1.0),
71
+ autonomy_adapt_lr=_clip(g.autonomy_adapt_lr * math.exp(rng.gauss(0, 0.4)), 0.0005, 0.02),
72
+ noise_sigma=_clip(g.noise_sigma + rng.gauss(0, 0.02), 0.0, 0.20),
73
+ forage_weight=_clip(g.forage_weight + rng.gauss(0, 0.20), 0.0, 3.0),
74
+ rest_weight=_clip(g.rest_weight + rng.gauss(0, 0.20), 0.0, 3.0),
75
+ social_weight=_clip(g.social_weight + rng.gauss(0, 0.20), 0.0, 3.0),
76
+ forage_temperature=_clip(g.forage_temperature * math.exp(rng.gauss(0, 0.3)), 0.05, 1.0),
77
+ fear_weight=_clip(g.fear_weight + rng.gauss(0, 0.20), 0.0, 3.0),
78
+ curiosity_weight=_clip(g.curiosity_weight + rng.gauss(0, 0.20), 0.0, 3.0),
79
+ explore_weight=_clip(g.explore_weight + rng.gauss(0, 0.20), 0.0, 3.0),
80
+ drink_weight=_clip(g.drink_weight + rng.gauss(0, 0.20), 0.0, 3.0),
81
+ heal_weight=_clip(g.heal_weight + rng.gauss(0, 0.20), 0.0, 3.0),
82
+ hydration_decay=_clip(g.hydration_decay + rng.gauss(0, 0.003), 0.0, 0.05),
83
+ sleep_accum=_clip(g.sleep_accum + rng.gauss(0, 0.006), 0.0, 0.08),
84
+ inflammation_gain=_clip(g.inflammation_gain + rng.gauss(0, 0.0005), 0.0, 0.01),
85
+ cortisol_gain=_clip(g.cortisol_gain + rng.gauss(0, 0.08), 0.0, 1.5),
86
+ fear_stress=_clip(g.fear_stress + rng.gauss(0, 0.04), 0.0, 1.0),
87
+ joy_v=_clip(g.joy_v + rng.gauss(0, 0.04), 0.0, 1.0),
88
+ need_physio_gate=_clip(g.need_physio_gate + rng.gauss(0, 0.15), 0.0, 2.5),
89
+ need_safety_gate=_clip(g.need_safety_gate + rng.gauss(0, 0.15), 0.0, 2.5),
90
+ need_belong_gate=_clip(g.need_belong_gate + rng.gauss(0, 0.15), 0.0, 2.5),
91
+ need_actual_gate=_clip(g.need_actual_gate + rng.gauss(0, 0.15), 0.0, 2.5),
92
+ planning=_clip(g.planning + rng.gauss(0, 0.08), 0.0, 1.0),
93
+ quantum_weight=_clip(g.quantum_weight + rng.gauss(0, 0.10), 0.0, 2.0),
94
+ quantum_gamma=_clip(g.quantum_gamma + rng.gauss(0, 0.02), 0.0, 0.5),
95
+ quantum_dephase=_clip(g.quantum_dephase + rng.gauss(0, 0.03), 0.0, 0.8),
96
+ emotion_social_weight=_clip(g.emotion_social_weight + rng.gauss(0, 0.12), 0.0, 2.0),
97
+ emotion_avoid_weight=_clip(g.emotion_avoid_weight + rng.gauss(0, 0.12), 0.0, 2.0),
98
+ emotion_explore_weight=_clip(g.emotion_explore_weight + rng.gauss(0, 0.12), 0.0, 2.0),
99
+ emotion_recovery_weight=_clip(g.emotion_recovery_weight + rng.gauss(0, 0.12), 0.0, 2.0),
100
+ world_weight=_clip(g.world_weight + rng.gauss(0, 0.12), 0.0, 2.0),
101
+ world_horizon=max(1, min(5, int(round(g.world_horizon + rng.gauss(0, 0.5))))),
102
+ world_beam=max(1, min(6, int(round(g.world_beam + rng.gauss(0, 0.5))))),
103
+ formula_expr=(
104
+ symbolic_mutate(g.formula_expr, rng, ("energy", "social", "stress", "hunger", "thirst", "pain", "inflammation", "sleep_pressure", "known_food", "food_uncertainty"))
105
+ if g.formula_expr is not None and rng.random() < 0.8
106
+ else (random_expr(rng, ("energy", "social", "stress", "hunger", "thirst", "pain", "inflammation", "sleep_pressure", "known_food", "food_uncertainty"), 2)
107
+ if rng.random() < 0.15 else g.formula_expr)
108
+ ),
109
+ )
110
+
111
+
112
+ def crossover(a: AutonomyGenome, b: AutonomyGenome, rng: random.Random) -> AutonomyGenome:
113
+ """Sexual recombination of low-level genes; missing from mutation-only QD."""
114
+ out = {}
115
+ for field_name in AutonomyGenome.__dataclass_fields__:
116
+ out[field_name] = getattr(a if rng.random() < 0.5 else b, field_name)
117
+ child = AutonomyGenome(**out)
118
+ # light mutation keeps crossover from being a pure copy
119
+ return mutate(child, rng, scale=0.06)
120
+
121
+ def evaluate(g: AutonomyGenome, steps: int = 120, seed: int = 1,
122
+ weights: Dict[str, float] | None = None, mortality: bool = False,
123
+ starvation: float = 0.0, damage_threshold: float | None = None,
124
+ predator_risk: float = 0.0, thirst_rate: float = 0.0,
125
+ inflammation_rate: float = 0.0, pain_rate: float = 0.0,
126
+ regime_shift: int = 0, env_chaos: float = 0.0, chaos_r: float = 3.9,
127
+ effect_delay: int = 0) -> Dict[str, object]:
128
+ cfg = MindConfig(seed=int(seed), autonomy_threshold=g.autonomy_threshold,
129
+ volition_temperature=g.volition_temperature,
130
+ autonomy_adapt_lr=g.autonomy_adapt_lr,
131
+ noise_sigma=g.noise_sigma)
132
+ policy = ForagePolicy(forage_weight=g.forage_weight, rest_weight=g.rest_weight,
133
+ social_weight=g.social_weight, temperature=g.forage_temperature,
134
+ fear_weight=g.fear_weight, curiosity_weight=g.curiosity_weight,
135
+ explore_weight=g.explore_weight, drink_weight=g.drink_weight,
136
+ heal_weight=g.heal_weight, need_physio_gate=g.need_physio_gate,
137
+ need_safety_gate=g.need_safety_gate, need_belong_gate=g.need_belong_gate,
138
+ need_actual_gate=g.need_actual_gate, planning=g.planning,
139
+ quantum_weight=g.quantum_weight, quantum_gamma=g.quantum_gamma,
140
+ quantum_dephase=g.quantum_dephase,
141
+ emotion_social_weight=g.emotion_social_weight,
142
+ emotion_avoid_weight=g.emotion_avoid_weight,
143
+ emotion_explore_weight=g.emotion_explore_weight,
144
+ emotion_recovery_weight=g.emotion_recovery_weight,
145
+ formula_expr=g.formula_expr, world_weight=g.world_weight,
146
+ world_horizon=g.world_horizon, world_beam=g.world_beam,
147
+ rng=random.Random(int(seed)))
148
+ phys = {"hydration_decay": g.hydration_decay, "sleep_accum": g.sleep_accum,
149
+ "inflammation_gain": g.inflammation_gain, "cortisol_gain": g.cortisol_gain}
150
+ emo = {"fear": {"stress": g.fear_stress}, "joy": {"vpos": g.joy_v}}
151
+ r = run_autonomy(steps=steps, cfg=cfg, perturb=True, mortality=mortality, collect_series=True,
152
+ starvation=starvation, damage_threshold=damage_threshold,
153
+ energy_policy=policy, predator_risk=predator_risk,
154
+ thirst_rate=thirst_rate, inflammation_rate=inflammation_rate, pain_rate=pain_rate,
155
+ physiology_params=phys, emotion_weights=emo, regime_shift=regime_shift,
156
+ env_chaos=env_chaos, chaos_r=chaos_r, effect_delay=effect_delay)
157
+ recovery = float(r.get("recovery_steps", -1))
158
+ rec_score = 0.0 if recovery < 0 else 1.0 / (1.0 + recovery)
159
+ viability = float(r.get("viability_mean", 0.0))
160
+ vmin = float(r.get("viability_min", 0.0))
161
+ spont = float(r.get("spontaneous_rate", 0.0))
162
+ entropy = float(r.get("action_entropy", 0.0))
163
+ w = weights or {"viability": 1.0, "recovery": 0.5, "entropy": 0.3, "spont": 0.2, "penalty": 0.5}
164
+ fitness = (float(w.get("viability", 1.0)) * viability
165
+ + float(w.get("recovery", 0.5)) * rec_score
166
+ + float(w.get("entropy", 0.3)) * entropy
167
+ + float(w.get("spont", 0.2)) * spont
168
+ - float(w.get("penalty", 0.5)) * (1.0 if vmin < 0.5 else 0.0))
169
+ died = bool(r.get("dead", False))
170
+ lifespan = float(r.get("lifespan", steps)) / max(1.0, float(steps))
171
+ fitness += 0.30 * lifespan - (0.50 if died else 0.0)
172
+ needs = r.get("needs") or {}
173
+ deficits = needs.get("deficits") or {}
174
+ phys = float(deficits.get("physiological", 0.0))
175
+ safety = float(deficits.get("safety", 0.0))
176
+ belonging = float(deficits.get("belonging", 0.0))
177
+ esteem = float(deficits.get("esteem", 0.0))
178
+ actual = float(deficits.get("actualization", 0.0))
179
+ lower = max(phys, safety)
180
+ higher = max(belonging, esteem, actual)
181
+ # stepwise gating: lower unmet needs suppress higher-level reward
182
+ fitness += 0.25 * (1.0 - lower) - 0.35 * lower - 0.15 * higher
183
+ # entropy cannot be farmed when lower needs are unmet
184
+ fitness += 0.25 * float(needs.get("satisfaction", 0.0)) - 0.30 * entropy * lower
185
+ formula = g.formula_expr
186
+ f_complexity = formula_complexity(formula) if formula is not None else 0
187
+ f_stability = formula_stability(formula) if formula is not None else 1.0
188
+ if formula is not None:
189
+ fitness -= 0.002 * f_complexity + 0.25 * (1.0 - f_stability)
190
+ # discounted future effects: pending harm/help is not free
191
+ fitness -= 0.35 * float(r.get("pending_damage_sum", 0.0))
192
+ fitness += 0.12 * float(r.get("pending_energy_sum", 0.0))
193
+ # multi-horizon: late needs matter more than early transient needs
194
+ series = r.get("series") or []
195
+ early_needs = late_needs = 0.0
196
+ future_trend = 0.0
197
+ if len(series) >= 5:
198
+ k = max(1, len(series) // 5)
199
+ early_needs = sum(float(x.get("needs", 0.0)) for x in series[:k]) / k
200
+ late_needs = sum(float(x.get("needs", 0.0)) for x in series[-k:]) / k
201
+ future_trend = late_needs - early_needs
202
+ fitness += 0.20 * late_needs - 0.20 * max(0.0, -future_trend)
203
+ vbin = max(0, min(3, int(viability * 4)))
204
+ rbin = 0 if recovery < 0 else max(0, min(2, int(recovery)))
205
+ descriptor = (min(4, int(spont * 5)), min(4, int(entropy * 5)), vbin, rbin)
206
+ return {"genome": asdict(g), "fitness": round(fitness, 5), "descriptor": descriptor,
207
+ "viability": round(viability, 4), "viability_min": round(vmin, 4),
208
+ "spontaneous": round(spont, 4), "entropy": round(entropy, 4),
209
+ "recovery_steps": int(recovery), "threshold_end": r.get("threshold_end"),
210
+ "dead": died, "lifespan": int(r.get("lifespan", steps)), "damage": r.get("damage", 0.0),
211
+ "needs_dominant": needs.get("dominant"), "needs_satisfaction": needs.get("satisfaction"),
212
+ "formula_complexity": f_complexity, "formula_stability": round(float(f_stability), 3),
213
+ "pending_damage_sum": r.get("pending_damage_sum", 0.0),
214
+ "pending_energy_sum": r.get("pending_energy_sum", 0.0),
215
+ "early_needs": round(float(early_needs), 4), "late_needs": round(float(late_needs), 4),
216
+ "future_trend": round(float(future_trend), 4)}
217
+
218
+
219
+ def run_qd(generations: int = 10, population: int = 10, steps: int = 120, seed: int = 1,
220
+ weights: Dict[str, float] | None = None, mortality: bool = False,
221
+ starvation: float = 0.0, damage_threshold: float | None = None,
222
+ archive_path=None, predator_risk: float = 0.0, thirst_rate: float = 0.0,
223
+ inflammation_rate: float = 0.0, pain_rate: float = 0.0,
224
+ regime_shift: int = 0, initials: List[AutonomyGenome] | None = None,
225
+ env_chaos: float = 0.0, chaos_r: float = 3.9,
226
+ effect_delay: int = 0, burst_prob: float = 0.0) -> Dict[str, object]:
227
+ rng = random.Random(int(seed))
228
+ archive: Dict[Tuple[int, ...], Dict[str, object]] = {}
229
+ if archive_path:
230
+ try:
231
+ from pathlib import Path
232
+ rows = json.loads(Path(archive_path).read_text(encoding="utf-8"))
233
+ for row in rows if isinstance(rows, list) else []:
234
+ key = tuple(int(x) for x in row.get("descriptor", []))
235
+ if key:
236
+ archive[key] = row
237
+ except Exception:
238
+ archive = {}
239
+ initial = list(initials) if initials else [
240
+ AutonomyGenome(0.9, 0.0, 0.004, 0.03),
241
+ AutonomyGenome(0.8, 0.0, 0.004, 0.03),
242
+ AutonomyGenome(0.7, 0.1, 0.004, 0.03),
243
+ AutonomyGenome(0.9, 0.2, 0.006, 0.05),
244
+ AutonomyGenome(0.6, 0.3, 0.004, 0.05),
245
+ AutonomyGenome(0.75, 0.0, 0.008, 0.08),
246
+ ]
247
+ for g in initial[:max(1, population)]:
248
+ r = evaluate(g, steps=steps, seed=seed, weights=weights, mortality=mortality,
249
+ starvation=starvation, damage_threshold=damage_threshold,
250
+ predator_risk=predator_risk, thirst_rate=thirst_rate,
251
+ inflammation_rate=inflammation_rate, pain_rate=pain_rate,
252
+ regime_shift=regime_shift, env_chaos=env_chaos, chaos_r=chaos_r, effect_delay=effect_delay)
253
+ k = tuple(r["descriptor"]) # type: ignore[arg-type]
254
+ if k not in archive or r["fitness"] > archive[k]["fitness"]:
255
+ archive[k] = r
256
+ history = []
257
+ for gen in range(max(1, int(generations))):
258
+ elites = list(archive.values())
259
+ candidates: List[AutonomyGenome] = []
260
+ for e in elites:
261
+ if rng.random() < float(burst_prob):
262
+ candidates.append(burst_mutate(AutonomyGenome(**e["genome"]), rng)) # type: ignore[arg-type]
263
+ elif rng.random() < 0.35 and len(elites) >= 2:
264
+ other = rng.choice(elites)
265
+ candidates.append(crossover(AutonomyGenome(**e["genome"]), # type: ignore[arg-type]
266
+ AutonomyGenome(**other["genome"]), rng)) # type: ignore[arg-type]
267
+ else:
268
+ candidates.append(mutate(AutonomyGenome(**e["genome"]), rng)) # type: ignore[arg-type]
269
+ while len(candidates) < max(1, population):
270
+ base = AutonomyGenome(**rng.choice(elites)["genome"]) if elites else AutonomyGenome()
271
+ candidates.append(mutate(base, rng, scale=0.25))
272
+ for g in candidates:
273
+ r = evaluate(g, steps=steps, seed=seed, weights=weights, mortality=mortality,
274
+ starvation=starvation, damage_threshold=damage_threshold,
275
+ predator_risk=predator_risk, thirst_rate=thirst_rate,
276
+ inflammation_rate=inflammation_rate, pain_rate=pain_rate,
277
+ regime_shift=regime_shift, env_chaos=env_chaos, chaos_r=chaos_r, effect_delay=effect_delay)
278
+ k = tuple(r["descriptor"]) # type: ignore[arg-type]
279
+ if k not in archive or r["fitness"] > archive[k]["fitness"]:
280
+ archive[k] = r
281
+ best_now = max(archive.values(), key=lambda e: e["fitness"]) if archive else {}
282
+ history.append({
283
+ "generation": gen + 1,
284
+ "archive": len(archive),
285
+ "best": round(float(best_now.get("fitness", 0.0)), 5),
286
+ "best_planning": round(float((best_now.get("genome") or {}).get("planning", 0.0)), 3),
287
+ "best_lifespan": int(best_now.get("lifespan", 0)),
288
+ "best_needs": best_now.get("needs_satisfaction"),
289
+ "descriptors": [list(k) for k in archive.keys()],
290
+ })
291
+ best = max(archive.values(), key=lambda e: e["fitness"]) if archive else None
292
+ if archive_path:
293
+ try:
294
+ from pathlib import Path
295
+ rows = sorted(archive.values(), key=lambda e: float(e.get("fitness", 0.0)), reverse=True)
296
+ Path(archive_path).parent.mkdir(parents=True, exist_ok=True)
297
+ Path(archive_path).write_text(json.dumps(rows, ensure_ascii=False, indent=2), encoding="utf-8")
298
+ except Exception:
299
+ pass
300
+ return {"archive_size": len(archive), "best": best, "history": history,
301
+ "descriptors": [list(k) for k in archive.keys()], "archive_path": str(archive_path) if archive_path else ""}
302
+
303
+
304
+
305
+ def burst_mutate(g: AutonomyGenome, rng: random.Random, k: int | None = None) -> AutonomyGenome:
306
+ """Saltation / punctuated mutation: a few genes take large jumps.
307
+
308
+ Normal mutate() is local; this adds heavy-tailed structural variation and
309
+ occasional formula replacement. It is a mutation operator, not a rule.
310
+ """
311
+ bounds = {
312
+ "autonomy_threshold": (0.30, 1.20), "volition_temperature": (0.0, 1.0),
313
+ "autonomy_adapt_lr": (0.0005, 0.02), "noise_sigma": (0.0, 0.20),
314
+ "forage_weight": (0.0, 3.0), "rest_weight": (0.0, 3.0), "social_weight": (0.0, 3.0),
315
+ "forage_temperature": (0.05, 1.0), "fear_weight": (0.0, 3.0),
316
+ "curiosity_weight": (0.0, 3.0), "explore_weight": (0.0, 3.0),
317
+ "drink_weight": (0.0, 3.0), "heal_weight": (0.0, 3.0),
318
+ "hydration_decay": (0.0, 0.05), "sleep_accum": (0.0, 0.08),
319
+ "inflammation_gain": (0.0, 0.01), "cortisol_gain": (0.0, 1.5),
320
+ "fear_stress": (0.0, 1.0), "joy_v": (0.0, 1.0),
321
+ "need_physio_gate": (0.0, 2.5), "need_safety_gate": (0.0, 2.5),
322
+ "need_belong_gate": (0.0, 2.5), "need_actual_gate": (0.0, 2.5),
323
+ "planning": (0.0, 1.0), "quantum_weight": (0.0, 2.0),
324
+ "quantum_gamma": (0.0, 0.5), "quantum_dephase": (0.0, 0.8),
325
+ "emotion_social_weight": (0.0, 2.0), "emotion_avoid_weight": (0.0, 2.0),
326
+ "emotion_explore_weight": (0.0, 2.0), "emotion_recovery_weight": (0.0, 2.0),
327
+ "world_weight": (0.0, 2.0),
328
+ }
329
+ d = {name: getattr(g, name) for name in AutonomyGenome.__dataclass_fields__}
330
+ names = list(bounds)
331
+ n = int(k) if k is not None else rng.randint(3, 8)
332
+ for name in rng.sample(names, min(n, len(names))):
333
+ lo, hi = bounds[name]
334
+ if rng.random() < 0.45:
335
+ d[name] = rng.uniform(lo, hi)
336
+ else:
337
+ d[name] = _clip(float(d[name]) + rng.gauss(0.0, (hi - lo) * 0.35), lo, hi)
338
+ d["world_horizon"] = max(1, min(5, int(round(g.world_horizon + rng.gauss(0, 1.5)))))
339
+ d["world_beam"] = max(1, min(6, int(round(g.world_beam + rng.gauss(0, 1.5)))))
340
+ if rng.random() < 0.35:
341
+ d["formula_expr"] = random_expr(rng, ("energy", "social", "stress", "hunger", "thirst", "pain", "inflammation", "sleep_pressure", "known_food", "food_uncertainty"), rng.choice([2, 3]))
342
+ return AutonomyGenome(**d)
@@ -0,0 +1,65 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import Counter
4
+ from typing import Dict
5
+
6
+ from .types import Event
7
+
8
+
9
+ class AutonomyLoop:
10
+ """One loop over structure, allostasis, sleep, attachment and memory."""
11
+
12
+ def __init__(self, mind):
13
+ self.mind = mind
14
+
15
+ def run_with_messages(self, messages, ticks: int = 300, dt: float = 3600.0, every: int = 10) -> Dict[str, object]:
16
+ actions, reasons = Counter(), Counter()
17
+ viab, stress = [], []
18
+ msgs = list(messages)
19
+ mi = 0
20
+ for i in range(ticks):
21
+ if i % every == 0 and mi < len(msgs):
22
+ out = self.mind.step(Event(text=str(msgs[mi]), speaker="A", directed=True, ts=self.mind.t + dt))
23
+ self.mind.post_reply("嗯", feedback=0.2, speaker="A")
24
+ mi += 1
25
+ else:
26
+ out = self.mind.step(Event(kind="tick", ts=self.mind.t + dt))
27
+ actions[out.action] += 1
28
+ reasons[out.reason] += 1
29
+ viab.append(self.mind.viability_state["viability"])
30
+ stress.append(self.mind.affect.stress)
31
+ return {
32
+ "ticks": ticks, "messages": mi, "actions": dict(actions), "reasons": dict(reasons),
33
+ "viability_min": round(min(viab), 3) if viab else 1.0,
34
+ "stress_max": round(max(stress), 3) if stress else 0.0,
35
+ "fatigue": round(self.mind.affect.fatigue, 3),
36
+ "scar": round(self.mind.circuit.chronic_scar, 3),
37
+ "sleep_debt": round(self.mind.lifecycle.sleep_debt, 3),
38
+ "identity": round(self.mind.self_model.identity, 3),
39
+ "attachment": self.mind.attachment.style(),
40
+ "memory": self.mind.memory.snapshot(),
41
+ }
42
+
43
+ def run(self, ticks: int = 300, dt: float = 3600.0) -> Dict[str, object]:
44
+ actions, reasons = Counter(), Counter()
45
+ viab, stress = [], []
46
+ for _ in range(ticks):
47
+ out = self.mind.step(Event(kind="tick", ts=self.mind.t + dt))
48
+ actions[out.action] += 1
49
+ reasons[out.reason] += 1
50
+ viab.append(self.mind.viability_state["viability"])
51
+ stress.append(self.mind.affect.stress)
52
+ return {
53
+ "ticks": ticks,
54
+ "actions": dict(actions),
55
+ "reasons": dict(reasons),
56
+ "viability_min": round(min(viab), 3) if viab else 1.0,
57
+ "stress_max": round(max(stress), 3) if stress else 0.0,
58
+ "fatigue": round(self.mind.affect.fatigue, 3),
59
+ "scar": round(self.mind.circuit.chronic_scar, 3),
60
+ "sleep_debt": round(self.mind.lifecycle.sleep_debt, 3),
61
+ "identity": round(self.mind.self_model.identity, 3),
62
+ "attachment": self.mind.attachment.style(),
63
+ "memory": self.mind.memory.snapshot(),
64
+ "circuit": {"sparsity": round(self.mind.circuit.sparsity, 3), "lesions": sorted(self.mind.circuit.lesions)},
65
+ }