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,69 @@
1
+ """Cheap social-addressing inference.
2
+
3
+ This is not a semantic world model. It only answers:
4
+ "is this message plausibly aimed at her?"
5
+ Explicit @/name is handled by the bridge; this fills the implicit gap:
6
+ continuations after her reply, second-person questions, backchannels.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import re
11
+
12
+ ALIASES = ("灰羽", "能天使", "阿能", "小乐", "蕾缪乐")
13
+ _AT = re.compile(r"@[^\s((]+")
14
+ _SECOND_PERSON = ("你", "您")
15
+ _QUESTION = ("吗", "呢", "怎么", "为什么", "啥", "什么", "哪", "谁", "吗?", "?", "?")
16
+ _CONTINUE = ("然后", "所以", "那", "可是", "但是", "刚才", "继续", "你说", "你觉得", "你看")
17
+
18
+
19
+ def infer_addressing(rows, text: str, speaker: str = "") -> dict:
20
+ t = str(text or "").strip()
21
+ low = t.lower()
22
+ score = 0.0
23
+ reasons = []
24
+ for a in ALIASES:
25
+ if a in t:
26
+ score = 1.0
27
+ reasons.append("name:" + a)
28
+ break
29
+ if score >= 1.0:
30
+ return {"score": 1.0, "addressed": True, "reasons": reasons}
31
+ ats = _AT.findall(t)
32
+ if ats:
33
+ # @ someone else is a strong negative unless an alias is also present
34
+ score -= 0.35
35
+ reasons.append("at_other")
36
+ prev = {}
37
+ try:
38
+ if len(rows) >= 2:
39
+ prev = rows[-2]
40
+ except Exception:
41
+ prev = {}
42
+ prev_reply = str(prev.get("reply") or "").strip() if isinstance(prev, dict) else ""
43
+ if prev_reply:
44
+ score += 0.35
45
+ reasons.append("after_her_reply")
46
+ if any(w in t for w in _SECOND_PERSON):
47
+ score += 0.20
48
+ reasons.append("second_person")
49
+ if any(w in t for w in _QUESTION):
50
+ score += 0.15
51
+ reasons.append("question")
52
+ if any(w in t for w in _CONTINUE):
53
+ score += 0.12
54
+ reasons.append("continuation")
55
+ if len(t) <= 8 and t and not ats:
56
+ score += 0.08
57
+ reasons.append("short")
58
+ # previous row spoken by someone else, current speaker is a different person:
59
+ # this is often a dyadic exchange, not aimed at her.
60
+ try:
61
+ if len(rows) >= 2:
62
+ psp = str(rows[-2].get("speaker") or "")
63
+ if psp and speaker and psp != speaker:
64
+ score -= 0.10
65
+ reasons.append("other_speaker")
66
+ except Exception:
67
+ pass
68
+ score = max(0.0, min(1.0, score))
69
+ return {"score": round(score, 3), "addressed": bool(score >= 0.55), "reasons": reasons}
@@ -0,0 +1,86 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ from typing import Dict
5
+
6
+ from .config import MindConfig
7
+ from .noise import clip, ou
8
+ from .types import Appraisal
9
+
10
+
11
+ class Affect:
12
+ """Fast affect (V/A/stress) + slow mood + neuromodulator scalars."""
13
+
14
+ def __init__(self, cfg: MindConfig, rng: random.Random):
15
+ self.cfg = cfg
16
+ self.rng = rng
17
+ self.v = 0.0
18
+ self.a = 0.5
19
+ self.dominance = 0.0
20
+ self.stress = 0.0
21
+ self.scar = 0.0
22
+ self.mood = 0.0
23
+ self.fatigue = 0.0
24
+ self.da = 0.5
25
+ self.oa = 0.5
26
+ self.ht = 0.5
27
+
28
+ def update(self, ap: Appraisal, drives, dt: float) -> None:
29
+ v_target = clip(0.60 * ap.valence + 0.20 * self.mood + 0.20 * ap.social, -1.0, 1.0)
30
+ a_target = clip(
31
+ 0.40 + 0.30 * ap.novelty + 0.20 * ap.threat + 0.10 * (1.0 - drives.get("energy")),
32
+ 0.0,
33
+ 1.0,
34
+ )
35
+ stress_target = clip(
36
+ 0.50 * ap.threat + 0.30 * max(0.0, -ap.valence) + 0.20 * (1.0 - drives.get("safety")),
37
+ 0.0,
38
+ 1.0,
39
+ )
40
+ theta_fast = 1.0 / max(self.cfg.affect_tau, 1e-6)
41
+ theta_stress = 1.0 / max(self.cfg.stress_tau, 1e-6)
42
+ theta_mood = 1.0 / max(self.cfg.mood_tau, 1e-6)
43
+ self.v = ou(self.v, v_target, theta_fast, self.cfg.noise_sigma, dt, self.rng)
44
+ self.a = ou(self.a, a_target, theta_fast, self.cfg.noise_sigma, dt, self.rng)
45
+ self.stress = ou(self.stress, stress_target, theta_stress, self.cfg.noise_sigma, dt, self.rng)
46
+ dominance_target = clip(0.5 * ap.agency + 0.25 * ap.control + 0.25 * (1.0 - ap.threat) - 0.2 * self.stress, -1.0, 1.0)
47
+ self.dominance = ou(self.dominance, dominance_target, theta_fast, self.cfg.noise_sigma * 0.5, dt, self.rng)
48
+ self.dominance = clip(self.dominance, -1.0, 1.0)
49
+ mood_target = clip(0.70 * self.v + 0.30 * self.mood, -1.0, 1.0)
50
+ self.mood = ou(self.mood, mood_target, theta_mood, self.cfg.noise_sigma * 0.5, dt, self.rng)
51
+ self.v = clip(self.v, -1.0, 1.0)
52
+ self.a = clip(self.a, 0.0, 1.0)
53
+ self.stress = clip(self.stress, 0.0, 1.0)
54
+ # acute load can recover; a slow scar accumulates under chronic stress
55
+ grow = 0.00002 * dt * max(0.0, self.stress - self.scar)
56
+ decay = 0.0000002 * dt * self.scar
57
+ self.scar = clip(self.scar + grow - decay, 0.0, 1.0)
58
+ self.mood = clip(self.mood, -1.0, 1.0)
59
+ # neuromodulators: DA tracks RPE, OA tracks arousal, 5-HT tracks patience
60
+ self.da = clip(self.da + 0.08 * (ap.rpe - 0.10 * self.da), 0.0, 1.0)
61
+ self.oa = clip(self.oa + 0.08 * (self.a - self.oa), 0.0, 1.0)
62
+ self.ht = clip(self.ht + 0.03 * ((1.0 - self.stress) - self.ht), 0.0, 1.0)
63
+
64
+ def update_structural_load(self, load: float, dt: float) -> None:
65
+ # crowded/overloaded circuitry adds metabolic fatigue
66
+ self.fatigue = clip(self.fatigue + 0.002 * max(0.0, float(load)) * (max(0.0, float(dt)) / 3600.0), 0.0, 1.0)
67
+
68
+ def update_self_efficacy(self, ase: float, dt: float) -> None:
69
+ # low allostatic self-efficacy accumulates fatigue
70
+ target = clip(1.0 - float(ase), 0.0, 1.0)
71
+ rate = min(1.0, max(0.0, dt) / (6.0 * 3600.0))
72
+ self.fatigue = clip(self.fatigue + (target - self.fatigue) * rate, 0.0, 1.0)
73
+
74
+ def snapshot(self) -> Dict[str, float]:
75
+ return {
76
+ "v": round(self.v, 4),
77
+ "a": round(self.a, 4),
78
+ "dominance": round(self.dominance, 4),
79
+ "stress": round(self.stress, 4),
80
+ "scar": round(self.scar, 4),
81
+ "mood": round(self.mood, 4),
82
+ "fatigue": round(self.fatigue, 3),
83
+ "da": round(self.da, 4),
84
+ "oa": round(self.oa, 4),
85
+ "ht": round(self.ht, 4),
86
+ }
@@ -0,0 +1,111 @@
1
+ """Biological affect injection and empathy hooks.
2
+
3
+ Emotions can be injected as internal states (not text labels). A user-emotion
4
+ signal can be mirrored with a learnable empathy gain. The base remains the
5
+ source of state; the LLM only expresses it.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from typing import Dict, Tuple
10
+
11
+ from .noise import clip
12
+ from .emotion_schema import PAD_PROTOTYPES, pad_for_emotion, emotion_readout
13
+
14
+
15
+ # prototype affect targets: valence, arousal, stress, oxytocin, cortisol
16
+ EMOTION_PROFILES: Dict[str, Dict[str, float]] = {
17
+ "joy": {"v": 0.8, "a": 0.6, "stress": -0.2, "oxy": 0.2, "cortisol": -0.1},
18
+ "sadness": {"v": -0.7, "a": 0.25, "stress": 0.3, "oxy": -0.1, "cortisol": 0.1},
19
+ "fear": {"v": -0.6, "a": 0.8, "stress": 0.7, "oxy": -0.1, "cortisol": 0.3},
20
+ "anger": {"v": -0.5, "a": 0.75, "stress": 0.6, "oxy": -0.1, "cortisol": 0.2},
21
+ "surprise": {"v": 0.1, "a": 0.8, "stress": 0.2, "oxy": 0.0, "cortisol": 0.0},
22
+ "disgust": {"v": -0.6, "a": 0.5, "stress": 0.4, "oxy": -0.1, "cortisol": 0.1},
23
+ "love": {"v": 0.8, "a": 0.4, "stress": -0.2, "oxy": 0.4, "cortisol": -0.1},
24
+ "loneliness": {"v": -0.5, "a": 0.3, "stress": 0.4, "oxy": -0.2, "cortisol": 0.1},
25
+ "calm": {"v": 0.4, "a": 0.2, "stress": -0.4, "oxy": 0.2, "cortisol": -0.2},
26
+ "anxiety": {"v": -0.4, "a": 0.7, "stress": 0.6, "oxy": -0.1, "cortisol": 0.2},
27
+ "shame": {"v": -0.5, "a": 0.4, "stress": 0.5, "oxy": -0.1, "cortisol": 0.2},
28
+ "guilt": {"v": -0.4, "a": 0.4, "stress": 0.4, "oxy": 0.0, "cortisol": 0.15},
29
+ "pride": {"v": 0.6, "a": 0.5, "stress": -0.1, "oxy": 0.1, "cortisol": 0.0},
30
+ "gratitude": {"v": 0.7, "a": 0.35, "stress": -0.2, "oxy": 0.3, "cortisol": -0.1},
31
+ "hope": {"v": 0.5, "a": 0.45, "stress": -0.1, "oxy": 0.1, "cortisol": 0.0},
32
+ "boredom": {"v": -0.2, "a": 0.15, "stress": 0.1, "oxy": -0.1, "cortisol": 0.0},
33
+ }
34
+
35
+
36
+ def inject_emotion(mind, emotion: str, intensity: float = 0.5, blend: float = 0.6) -> Dict[str, object]:
37
+ """Inject a biological emotion into affect/physiology, not just a label."""
38
+ name = str(emotion).lower()
39
+ prof = EMOTION_PROFILES.get(name)
40
+ if prof is None:
41
+ return {"injected": None, "emotion": name}
42
+ k = clip(float(intensity), 0.0, 1.0) * clip(float(blend), 0.0, 1.0)
43
+ a = mind.affect
44
+ pad = pad_for_emotion(name)
45
+ a.v = clip(a.v + k * (pad[0] - a.v), -1.0, 1.0)
46
+ a.a = clip(a.a + k * (pad[1] - a.a), 0.0, 1.0)
47
+ if hasattr(a, "dominance"):
48
+ a.dominance = clip(a.dominance + k * (pad[2] - a.dominance), -1.0, 1.0)
49
+ a.stress = clip(a.stress + k * (prof["stress"] - a.stress), 0.0, 1.0)
50
+ try:
51
+ p = mind.physiology
52
+ p.oxytocin = clip(p.oxytocin + k * prof["oxy"], 0.0, 1.0)
53
+ p.cortisol = clip(p.cortisol + k * prof["cortisol"], 0.0, 1.0)
54
+ except Exception:
55
+ pass
56
+ mind.last_injected_emotion = {"emotion": name, "intensity": round(k, 4)}
57
+ return {"injected": name, "intensity": round(k, 4),
58
+ "affect": {"v": round(a.v, 4), "a": round(a.a, 4),
59
+ "dominance": round(float(getattr(a, "dominance", 0.0)), 4),
60
+ "stress": round(a.stress, 4)}}
61
+
62
+
63
+ def empathize(mind, user_emotion: str, intensity: float = 0.5,
64
+ empathy_gain: float = 0.35) -> Dict[str, object]:
65
+ """Mirror a user emotion with a learnable gain; not a hard rule."""
66
+ return inject_emotion(mind, user_emotion, intensity=float(intensity) * float(empathy_gain), blend=0.7)
67
+
68
+
69
+ def perceive_emotion_lexicon(text: str) -> Tuple[str, float]:
70
+ """Small deterministic fallback sensor; an LLM/classifier can replace it."""
71
+ t = str(text or "")
72
+ table = [
73
+ ("sadness", ("难过", "伤心", "累", "崩溃", "哭", "失落")),
74
+ ("anxiety", ("焦虑", "紧张", "担心", "害怕", "慌")),
75
+ ("anger", ("生气", "愤怒", "烦", "滚", "讨厌")),
76
+ ("joy", ("开心", "高兴", "笑", "太好了", "喜欢")),
77
+ ("loneliness", ("孤独", "没人", "一个人", "寂寞")),
78
+ ("love", ("爱你", "想你", "抱抱", "亲亲")),
79
+ ("gratitude", ("谢谢", "感谢", "麻烦你")),
80
+ ]
81
+ for emotion, words in table:
82
+ for w in words:
83
+ if w in t:
84
+ return emotion, 0.6
85
+ return "neutral", 0.0
86
+
87
+
88
+ def inject_affect(mind, valence: float, arousal: float, dominance: float = 0.0,
89
+ intensity: float = 0.8, blend: float = 0.7) -> Dict[str, object]:
90
+ """Inject a numeric PAD reading. No categorical label is involved."""
91
+ v = max(-1.0, min(1.0, float(valence)))
92
+ a = max(0.0, min(1.0, float(arousal)))
93
+ d = max(-1.0, min(1.0, float(dominance)))
94
+ k = clip(float(intensity), 0.0, 1.0) * clip(float(blend), 0.0, 1.0)
95
+ # negativity bias: negative events have a stronger pull than positive ones
96
+ k *= 1.25 if v < 0 else 0.90
97
+ aff = mind.affect
98
+ aff.v = clip(aff.v + k * (v - aff.v), -1.0, 1.0)
99
+ aff.a = clip(aff.a + k * (a - aff.a), 0.0, 1.0)
100
+ if hasattr(aff, "dominance"):
101
+ aff.dominance = clip(aff.dominance + k * (d - aff.dominance), -1.0, 1.0)
102
+ aff.stress = clip(aff.stress + k * max(0.0, -v) * 0.45, 0.0, 1.0)
103
+ try:
104
+ p = mind.physiology
105
+ p.oxytocin = clip(p.oxytocin + k * max(0.0, v) * 0.03, 0.0, 1.0)
106
+ p.cortisol = clip(p.cortisol + k * max(0.0, -v) * 0.03, 0.0, 1.0)
107
+ except Exception:
108
+ pass
109
+ return {"valence": round(aff.v, 4), "arousal": round(aff.a, 4),
110
+ "dominance": round(float(getattr(aff, "dominance", 0.0)), 4),
111
+ "stress": round(aff.stress, 4)}
@@ -0,0 +1,28 @@
1
+ """Slow allostatic load: chronic stress accumulates and recovers."""
2
+ from __future__ import annotations
3
+
4
+ from dataclasses import dataclass
5
+
6
+
7
+ def _clip(x, lo=0.0, hi=1.0):
8
+ return max(lo, min(hi, float(x)))
9
+
10
+
11
+ @dataclass
12
+ class AllostaticLoad:
13
+ load: float = 0.0
14
+ tau_recovery_s: float = 12 * 3600.0
15
+
16
+ def update(self, dt_s: float, stress: float, fatigue: float,
17
+ sleep_debt: float, sleep_quality: float = 0.5) -> float:
18
+ dt = max(0.0, float(dt_s))
19
+ if dt <= 0:
20
+ return self.load
21
+ drive = 0.5 * _clip(stress) + 0.3 * _clip(fatigue) + 0.2 * _clip(sleep_debt)
22
+ load_up = 0.0025 * max(0.0, drive - 0.25) * (dt / 60.0)
23
+ recover = (1.0 - _clip(sleep_quality)) * (dt / max(1.0, self.tau_recovery_s)) * 0.20
24
+ self.load = _clip(self.load + load_up - recover)
25
+ return round(self.load, 4)
26
+
27
+ def stress_bias(self) -> float:
28
+ return round(0.10 * self.load, 4)
@@ -0,0 +1,101 @@
1
+ from __future__ import annotations
2
+
3
+ import random
4
+ from collections import deque
5
+ from typing import Deque
6
+
7
+ from .config import MindConfig
8
+ from .lexicon import text_affect
9
+ from .noise import clip, jaccard
10
+ from .types import Appraisal, Event
11
+
12
+
13
+ def occ_emotion(goal_congruence: float, certainty: float, agency: float, coping: float, norm: float) -> str:
14
+ """OCC-style label; heuristic, not a full OCC implementation."""
15
+ if goal_congruence >= 0.0:
16
+ if agency > 0.25:
17
+ return "pride"
18
+ if agency < -0.25:
19
+ return "gratitude" if coping >= 0.45 else "relief"
20
+ return "joy" if certainty >= 0.55 else "hope"
21
+ if agency < -0.25:
22
+ return "anger" if certainty >= 0.60 else "fear"
23
+ if agency > 0.25:
24
+ return "shame" if norm < 0.45 else "guilt"
25
+ return "distress" if coping < 0.40 else "sadness"
26
+
27
+
28
+ class Appraiser:
29
+ """Turns an event into appraisal: novelty, relevance, control, RPE, threat.
30
+
31
+ This is deliberately a heuristic. The calibration step later replaces
32
+ text_affect() with ANEW / NRC-VAD / language-specific norms.
33
+ """
34
+
35
+ def __init__(self, cfg: MindConfig, rng: random.Random):
36
+ self.cfg = cfg
37
+ self.rng = rng
38
+ self.expectation = 0.0
39
+ self.recent: Deque[str] = deque(maxlen=24)
40
+
41
+ def _novelty(self, text: str) -> float:
42
+ if not text:
43
+ return 0.3
44
+ now = set(text)
45
+ if not self.recent:
46
+ return 0.55
47
+ best = 0.0
48
+ for old in self.recent:
49
+ best = max(best, jaccard(now, set(old)))
50
+ return clip(1.0 - best, 0.0, 1.0)
51
+
52
+ def appraise(self, ev: Event, state=None, social=None) -> Appraisal:
53
+ v, a = text_affect(ev.text)
54
+ if ev.valence is not None:
55
+ v = clip(ev.valence, -1.0, 1.0)
56
+ if ev.arousal is not None:
57
+ a = clip(ev.arousal, 0.0, 1.0)
58
+ novelty = self._novelty(ev.text)
59
+ relevance = 0.5 if ev.relevance is None else clip(ev.relevance, 0.0, 1.0)
60
+ control = 0.5 if ev.control is None else clip(ev.control, 0.0, 1.0)
61
+ social_sig = 0.0 if ev.social is None else clip(ev.social, -1.0, 1.0)
62
+ rpe = clip(v - self.expectation, -2.0, 2.0)
63
+ threat = clip(max(0.0, -v) * (1.0 - control), 0.0, 1.0)
64
+ goal_relevance = relevance
65
+ goal_congruence = clip(v, -1.0, 1.0)
66
+ if ev.kind == "self_action" or ev.speaker in ("self", "me"):
67
+ agency = 1.0
68
+ elif ev.kind in ("tick", "observation"):
69
+ agency = 0.0
70
+ else:
71
+ agency = -1.0
72
+ certainty = clip(1.0 - 0.6 * novelty, 0.0, 1.0)
73
+ willpower = 0.5
74
+ energy = 0.5
75
+ if state is not None:
76
+ will = getattr(state, "will", None)
77
+ willpower = float(getattr(will, "willpower", 0.5))
78
+ drives = getattr(state, "drives", {})
79
+ energy = float(getattr(drives, "energy", 0.5))
80
+ coping = clip(0.5 * control + 0.3 * willpower + 0.2 * energy, 0.0, 1.0)
81
+ norm = clip(1.0 - 0.5 * threat - 0.3 * max(0.0, -social_sig), 0.0, 1.0)
82
+ self.expectation = 0.85 * self.expectation + 0.15 * v
83
+ if ev.text:
84
+ self.recent.append(ev.text)
85
+ return Appraisal(
86
+ novelty=novelty,
87
+ relevance=relevance,
88
+ control=control,
89
+ rpe=rpe,
90
+ social=social_sig,
91
+ threat=threat,
92
+ valence=v,
93
+ arousal=a,
94
+ goal_relevance=goal_relevance,
95
+ goal_congruence=goal_congruence,
96
+ agency=agency,
97
+ certainty=certainty,
98
+ coping_potential=coping,
99
+ norm_compliance=norm,
100
+ occ_emotion=occ_emotion(goal_congruence, certainty, agency, coping, norm),
101
+ )
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict
4
+
5
+ from .noise import clip
6
+
7
+
8
+ class Attachment:
9
+ """Two-axis attachment: anxiety (fear of abandonment) and avoidance (distance)."""
10
+
11
+ def __init__(self):
12
+ self.trait_anxiety = 0.30
13
+ self.trait_avoidance = 0.30
14
+ self.anxiety = 0.30
15
+ self.avoidance = 0.30
16
+ self.lr = 0.02
17
+
18
+ def update(self, social_signal: float, prediction_error: float, dt: float, fatigue: float = 0.0, scar: float = 0.0) -> None:
19
+ sig = float(social_signal)
20
+ pe = abs(float(prediction_error))
21
+ rate = min(1.0, max(0.0, dt) / 600.0)
22
+ # negative social signal and unpredictable care raise anxiety
23
+ self.anxiety = clip(self.anxiety + self.lr * rate * (max(0.0, -sig) + 0.20 * pe + 0.10 * float(fatigue) + 0.10 * float(scar)), 0.0, 1.0)
24
+ # repeated negative/unpredictable care also raises avoidance
25
+ self.avoidance = clip(self.avoidance + self.lr * rate * (max(0.0, -sig) + 0.10 * pe + 0.08 * float(fatigue) + 0.15 * float(scar)), 0.0, 1.0)
26
+ # positive, predictable care slowly repairs both
27
+ if sig > 0:
28
+ self.anxiety = clip(self.anxiety - self.lr * rate * sig, self.trait_anxiety, 1.0)
29
+ self.avoidance = clip(self.avoidance - self.lr * rate * sig, self.trait_avoidance, 1.0)
30
+
31
+ def action_kind(self) -> str:
32
+ return {"secure": "communicate", "anxious": "seek_help", "avoidant": "avoid", "fearful": "protect"}[self.style()]
33
+
34
+ def memory_bias(self) -> float:
35
+ # anxious attachment prioritizes relational/threat cues
36
+ return 1.0 + 0.30 * float(self.anxiety)
37
+
38
+ def initiation_bias(self) -> float:
39
+ return {"secure": 1.0, "anxious": 1.20, "avoidant": 0.70, "fearful": 0.85}[self.style()]
40
+
41
+ def style(self) -> str:
42
+ a, v = self.anxiety >= 0.55, self.avoidance >= 0.55
43
+ if a and v: return "fearful"
44
+ if a: return "anxious"
45
+ if v: return "avoidant"
46
+ return "secure"
47
+
48
+ def snapshot(self) -> Dict[str, object]:
49
+ return {"anxiety": round(self.anxiety, 3), "avoidance": round(self.avoidance, 3), "style": self.style()}
@@ -0,0 +1,30 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict
4
+
5
+ from .noise import clip
6
+ from .types import Appraisal, Event
7
+
8
+
9
+ class Attention:
10
+ """A minimal salience gate: what reaches working memory / focus."""
11
+
12
+ def __init__(self):
13
+ self.focus: str | None = None
14
+ self.last_score = 0.0
15
+
16
+ def update(self, ev: Event, ap: Appraisal, drives) -> float:
17
+ score = (
18
+ 0.25 * ap.novelty
19
+ + 0.25 * ap.relevance
20
+ + 0.20 * ap.threat
21
+ + 0.20 * abs(ap.valence)
22
+ + 0.10 * abs(ap.social)
23
+ )
24
+ self.last_score = clip(score, 0.0, 1.0)
25
+ if self.last_score >= 0.45 and ev.speaker:
26
+ self.focus = ev.speaker
27
+ return self.last_score
28
+
29
+ def snapshot(self) -> Dict[str, object]:
30
+ return {"focus": self.focus, "last_score": round(self.last_score, 4)}
@@ -0,0 +1,81 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ import random
5
+ from typing import Dict
6
+
7
+ from .config import MindConfig
8
+ from .noise import clip
9
+
10
+
11
+ class Autonomy:
12
+ """Endogenous accumulator: no external trigger required to act."""
13
+
14
+ def __init__(self, cfg: MindConfig, rng: random.Random):
15
+ self.cfg = cfg
16
+ self.rng = rng
17
+ self.h = 0.0
18
+ self.last_spoke = -1e9
19
+ self.gain = 1.0
20
+ # learned meta-gain, not a hand-tuned rule
21
+ self.allostatic_gain = 0.05
22
+ self.gain_lr = 0.01
23
+ # learned impulse threshold, initialized from config
24
+ self.threshold = cfg.autonomy_threshold
25
+ self.threshold_lr = 0.002
26
+ self.tau = cfg.autonomy_tau
27
+ self.cooldown = cfg.autonomy_cooldown
28
+ self.tau_lr = 0.005
29
+ self.activity_ema = 0.0
30
+ self.adapt_lr = float(getattr(cfg, "autonomy_adapt_lr", 0.004))
31
+ self.viability_target = float(getattr(cfg, "autonomy_viability_target", 0.90))
32
+ self.social_target = float(getattr(cfg, "autonomy_social_target", 0.50))
33
+ self.activity_target = float(getattr(cfg, "autonomy_activity_target", 0.30))
34
+
35
+ def tick(self, dt: float, clock, drives, mood: float, allostatic: float = 0.0, fatigue: float = 0.0, attachment_bias: float = 1.0) -> None:
36
+ if dt <= 0:
37
+ return
38
+ brake = max(0.2, 1.0 - 0.5 * float(fatigue))
39
+ drive = brake * float(attachment_bias) * self.gain * (clock.drive + 0.20 * drives.get("social") + 0.20 * max(0.0, mood) + self.allostatic_gain * float(allostatic))
40
+ tau = max(self.tau, 1e-6)
41
+ noise = self.rng.gauss(0.0, 0.01) * math.sqrt(dt / tau)
42
+ self.h = self.h * math.exp(-dt / tau) + drive * (dt / tau) + noise
43
+
44
+ def due(self, now: float, asleep: bool) -> bool:
45
+ if asleep:
46
+ return False
47
+ return self.h > self.threshold and (now - self.last_spoke) > self.cooldown
48
+
49
+ def adapt(self, now: float, viability: float, social_need: float, stress: float,
50
+ spoke: bool, dt: float) -> float:
51
+ """Intrinsic homeostatic adaptation of the impulse threshold. No external labels."""
52
+ if dt <= 0:
53
+ return self.threshold
54
+ spoke_f = 1.0 if spoke else 0.0
55
+ self.activity_ema += 0.02 * (spoke_f - self.activity_ema)
56
+ # low viability -> raise threshold (be conservative); high social need -> lower
57
+ err = (self.viability_target - float(viability)) - 0.5 * (float(social_need) - self.social_target)
58
+ err += 0.15 * (self.activity_target - self.activity_ema)
59
+ rate = self.adapt_lr * min(1.0, max(0.1, float(dt) / 60.0))
60
+ self.threshold = clip(self.threshold + rate * err, 0.20, 2.0)
61
+ return self.threshold
62
+
63
+ def on_speak(self, now: float) -> None:
64
+ self.h *= 0.30
65
+ self.last_spoke = now
66
+
67
+ def learn_from_feedback(self, rpe: float, allostatic: float) -> None:
68
+ # RPE meta-gradient: raise/lower allostatic sensitivity from outcome
69
+ self.allostatic_gain = clip(
70
+ self.allostatic_gain + self.gain_lr * float(rpe) * float(allostatic), 0.0, 0.5
71
+ )
72
+ # outcome learning of the impulse threshold
73
+ self.threshold = clip(
74
+ self.threshold - self.threshold_lr * float(rpe) * min(1.0, abs(self.h)), 0.35, 2.0
75
+ )
76
+ # positive outcome -> faster accumulation / shorter cooldown
77
+ self.tau = clip(self.tau * (1.0 - self.tau_lr * float(rpe)), 60.0, 7200.0)
78
+ self.cooldown = clip(self.cooldown - 20.0 * float(rpe), 10.0, 3600.0)
79
+
80
+ def snapshot(self) -> Dict[str, float]:
81
+ return {"h": round(self.h, 4), "last_spoke": round(self.last_spoke, 1), "allostatic_gain": round(self.allostatic_gain, 4), "threshold": round(self.threshold, 4), "tau": round(self.tau, 1), "cooldown": round(self.cooldown, 1), "viability_target": round(self.viability_target, 4), "social_target": round(self.social_target, 4), "activity_target": round(self.activity_target, 4), "activity_ema": round(self.activity_ema, 4)}