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,86 @@
1
+ """Simulated chat environment for offline evolution.
2
+
3
+ Wraps MindBase + Kernel and a Teacher. No QQ, no network, no human in the loop.
4
+ Each episode is a sequence of user turns; the teacher provides the turn and the
5
+ outcome reward. The base's own decision modules remain in charge of actions.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from typing import Dict, List, Optional
11
+
12
+ from .types import Event
13
+ from .teacher import Scenario, Teacher
14
+
15
+
16
+ @dataclass
17
+ class EpisodeResult:
18
+ seed: int
19
+ scenario: Scenario
20
+ turns: int = 0
21
+ reward_sum: float = 0.0
22
+ rewards: List[float] = field(default_factory=list)
23
+ actions: List[str] = field(default_factory=list)
24
+ states: List[Dict[str, float]] = field(default_factory=list)
25
+ noise_metrics: Dict[str, float] = field(default_factory=dict)
26
+
27
+ @property
28
+ def mean_reward(self) -> float:
29
+ return self.reward_sum / self.turns if self.turns else 0.0
30
+
31
+
32
+ class SimEnv:
33
+ """Minimal simulated environment: user turns -> MindBase action -> reward."""
34
+
35
+ def __init__(self, mind, kernel, teacher: Teacher, user: str = "user", dt: float = 60.0, noise_system=None):
36
+ self.mind = mind
37
+ self.kernel = kernel
38
+ self.teacher = teacher
39
+ self.user = str(user)
40
+ self.dt = float(dt)
41
+ self.noise_system = noise_system if noise_system is not None else getattr(mind, "endogenous_noise", None)
42
+
43
+ def run_episode(self, seed: int, max_turns: Optional[int] = None) -> EpisodeResult:
44
+ scenario = self.teacher.scenario(int(seed))
45
+ turns = list(scenario.turns)
46
+ if max_turns is not None:
47
+ turns = turns[:max(0, int(max_turns))]
48
+ result = EpisodeResult(seed=int(seed), scenario=scenario)
49
+ for turn in turns:
50
+ if self.noise_system is not None:
51
+ try:
52
+ self.noise_system.step(self.dt)
53
+ self.noise_system.inject(self.mind)
54
+ except Exception:
55
+ pass
56
+ event = Event(text=str(turn.text), speaker=self.user,
57
+ directed=bool(turn.directed), ts=self.mind.t + self.dt,
58
+ domain="chat")
59
+ try:
60
+ self.kernel.observe(str(turn.text), self.user)
61
+ except Exception:
62
+ pass
63
+ action = self.kernel.step(event)
64
+ kind = str(getattr(action, "kind", "silence"))
65
+ reward = self.teacher.feedback(scenario, turn, kind, self.mind)
66
+ self.kernel.feedback(str(turn.text), "", float(reward), self.user)
67
+ result.turns += 1
68
+ result.reward_sum += float(reward)
69
+ result.rewards.append(float(reward))
70
+ result.actions.append(kind)
71
+ try:
72
+ result.states.append({
73
+ "v": float(getattr(self.mind.affect, "v", 0.0)),
74
+ "stress": float(getattr(self.mind.affect, "stress", 0.0)),
75
+ "energy": float(self.mind.drives.values.get("energy", 0.0)),
76
+ "social": float(self.mind.drives.values.get("social", 0.0)),
77
+ "viability": float(self.mind.viability_state.get("viability", 1.0)),
78
+ })
79
+ except Exception:
80
+ pass
81
+ if self.noise_system is not None:
82
+ try:
83
+ result.noise_metrics = dict(self.noise_system.metrics())
84
+ except Exception:
85
+ pass
86
+ return result
@@ -0,0 +1,138 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ import random
5
+ from dataclasses import dataclass, field
6
+ from typing import Dict
7
+
8
+ from .config import MindConfig
9
+ from .noise import clip, ou
10
+ from .relationships import update_stage
11
+ from .types import Appraisal, Event
12
+
13
+
14
+ @dataclass
15
+ class Bond:
16
+ attachment: float = 0.10
17
+ trust: float = 0.50
18
+ familiarity: float = 0.10
19
+ responsiveness: float = 0.50
20
+ valence: float = 0.0
21
+ last_seen: float = 0.0
22
+ stage: str = "stranger"
23
+
24
+
25
+ class Social:
26
+ """Per-speaker bonds + a slow group-valence state (contagion)."""
27
+
28
+ DAY = 86400.0
29
+ GRACE = 1.0 * DAY
30
+ TAU_FAMILIARITY = 30.0 * DAY
31
+ TAU_TRUST = 60.0 * DAY
32
+ TAU_ATTACHMENT = 90.0 * DAY
33
+
34
+ def __init__(self, cfg: MindConfig, rng: random.Random):
35
+ self.cfg = cfg
36
+ self.rng = rng
37
+ self.bonds: Dict[str, Bond] = {}
38
+ self.group_valence = 0.0
39
+ self.trust_gain = 1.0
40
+
41
+ def on_event(self, ev: Event, ap: Appraisal, now: float, dt: float) -> None:
42
+ if ev.kind == "message" and ev.speaker and ev.speaker != "self":
43
+ b = self.bonds.setdefault(ev.speaker, Bond())
44
+ b.familiarity = clip(b.familiarity + 0.010, 0.0, 1.0)
45
+ b.trust = clip(b.trust + 0.020 * ap.social * float(getattr(self, "trust_gain", 1.0)), 0.0, 1.0)
46
+ b.attachment = clip(b.attachment + 0.005 * max(0.0, ap.valence), 0.0, 1.0)
47
+ b.valence = ou(b.valence, ap.valence, 1.0 / 300.0, 0.05, dt, self.rng)
48
+ b.last_seen = now
49
+ update_stage(b)
50
+ self.group_valence = ou(
51
+ self.group_valence,
52
+ ap.valence,
53
+ 1.0 / max(self.cfg.social_tau, 1e-6),
54
+ 0.05,
55
+ dt,
56
+ self.rng,
57
+ )
58
+ self.group_valence = clip(self.group_valence, -1.0, 1.0)
59
+
60
+ def on_feedback(
61
+ self,
62
+ speaker: str,
63
+ feedback: float | None,
64
+ responded: bool = True,
65
+ now: float = 0.0,
66
+ dt: float = 60.0,
67
+ repair_factor: float = 1.0,
68
+ ) -> None:
69
+ if not speaker or speaker == "self":
70
+ return
71
+ b = self.bonds.setdefault(speaker, Bond())
72
+ if responded:
73
+ fb = clip(feedback if feedback is not None else 0.0, -1.0, 1.0)
74
+ mult = 1.6 if fb < 0 else 1.0
75
+ gain = 0.04 * fb * mult * (float(repair_factor) if fb > 0 else 1.0) * float(getattr(self, "trust_gain", 1.0))
76
+ b.trust = clip(b.trust + gain, 0.0, 1.0)
77
+ b.responsiveness = clip(b.responsiveness + (0.02 * fb), 0.0, 1.0)
78
+ b.attachment = clip(b.attachment + 0.03 * max(0.0, fb), 0.0, 1.0)
79
+ b.familiarity = clip(b.familiarity + (0.005 if fb >= 0 else -0.012), 0.0, 1.0)
80
+ target = fb
81
+ else:
82
+ b.trust = clip(b.trust - 0.03, 0.0, 1.0)
83
+ b.attachment = clip(b.attachment - 0.02, 0.0, 1.0)
84
+ target = -0.40
85
+ b.valence = ou(b.valence, target, 1.0 / 300.0, 0.05, dt, self.rng)
86
+ b.last_seen = now
87
+ update_stage(b)
88
+ self.group_valence = clip(0.80 * self.group_valence + 0.20 * target, -1.0, 1.0)
89
+
90
+ def tick(self, dt: float, now: float) -> None:
91
+ """Slow relationship decay when a bond has been idle beyond the grace period."""
92
+ if dt <= 0:
93
+ return
94
+ for b in self.bonds.values():
95
+ idle = max(0.0, now - b.last_seen)
96
+ if idle < self.GRACE:
97
+ continue
98
+ b.familiarity = clip(b.familiarity * math.exp(-dt / self.TAU_FAMILIARITY), 0.0, 1.0)
99
+ b.trust = clip(b.trust * math.exp(-dt / self.TAU_TRUST), 0.0, 1.0)
100
+ b.attachment = clip(b.attachment * math.exp(-dt / self.TAU_ATTACHMENT), 0.0, 1.0)
101
+ update_stage(b)
102
+
103
+ def repair(self, speaker: str, effort: float = 1.0, now: float = 0.0) -> Bond:
104
+ """Active repair: apology / care raises trust and attachment."""
105
+ if not speaker or speaker == "self":
106
+ raise ValueError("repair needs a real speaker")
107
+ e = clip(float(effort), 0.0, 1.0)
108
+ b = self.bonds.setdefault(speaker, Bond())
109
+ b.trust = clip(b.trust + 0.06 * e, 0.0, 1.0)
110
+ b.attachment = clip(b.attachment + 0.05 * e, 0.0, 1.0)
111
+ b.familiarity = clip(b.familiarity + 0.01 * e, 0.0, 1.0)
112
+ b.valence = clip(b.valence + 0.20 * e, -1.0, 1.0)
113
+ b.last_seen = now
114
+ update_stage(b)
115
+ self.group_valence = clip(self.group_valence + 0.10 * e, -1.0, 1.0)
116
+ return b
117
+
118
+ def trust_for(self, speaker: str) -> float:
119
+ b = self.bonds.get(speaker)
120
+ return float(b.trust) if b else 0.0
121
+
122
+ def stage(self, speaker: str) -> str:
123
+ b = self.bonds.get(speaker)
124
+ return b.stage if b else "stranger"
125
+
126
+ def snapshot(self) -> Dict[str, object]:
127
+ return {
128
+ "group_valence": round(self.group_valence, 4),
129
+ "bonds": {
130
+ k: {
131
+ "attachment": round(v.attachment, 3),
132
+ "trust": round(v.trust, 3),
133
+ "familiarity": round(v.familiarity, 3),
134
+ "stage": v.stage,
135
+ }
136
+ for k, v in self.bonds.items()
137
+ },
138
+ }
@@ -0,0 +1,141 @@
1
+ """Numeric social constraint / autonomy boundary.
2
+
3
+ Constraints suppress behavior; they never directly change bodily needs.
4
+ Autonomy threat decides comply / negotiate / override.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import re
9
+ from dataclasses import asdict, dataclass
10
+
11
+
12
+ def _clip(x: float, lo: float = 0.0, hi: float = 1.0) -> float:
13
+ return max(lo, min(hi, float(x)))
14
+
15
+
16
+ @dataclass
17
+ class SocialConstraint:
18
+ kind: str = "water"
19
+ source_id: str = ""
20
+ legitimacy: float = 0.5
21
+ severity: float = 0.5
22
+ relatedness: float = 1.0
23
+ start: float = 0.0
24
+ until: float = 0.0
25
+ hard_max: float = 0.0
26
+ can_negotiate: float = 1.0
27
+ can_override: float = 1.0
28
+
29
+ def autonomy_threat(self, now: float, need_urgency: float,
30
+ safe_level: float, current: float) -> float:
31
+ urgency = _clip((safe_level - current) / max(1e-6, safe_level))
32
+ urgency = max(_clip(need_urgency), urgency)
33
+ over = 0.0
34
+ if self.hard_max > 0 and self.start > 0:
35
+ elapsed = max(0.0, now - self.start)
36
+ over = _clip((elapsed - self.hard_max) / max(1.0, self.hard_max))
37
+ at = (0.50 * urgency + 0.25 * (1.0 - _clip(self.relatedness)) +
38
+ 0.15 * over + 0.10 * (1.0 - _clip(self.legitimacy)))
39
+ return round(_clip(at), 4)
40
+
41
+ def decide(self, at: float) -> str:
42
+ if at < 0.35:
43
+ return "comply"
44
+ if at < 0.60:
45
+ return "negotiate"
46
+ if self.can_override >= 0.5:
47
+ return "override"
48
+ return "negotiate"
49
+
50
+
51
+ @dataclass
52
+ class ConstraintDecision:
53
+ action: str
54
+ autonomy_threat: float
55
+ comply_penalty: float
56
+
57
+
58
+ def decide_constraint(c: SocialConstraint, now: float, need_urgency: float,
59
+ safe_level: float, current: float,
60
+ norm_strength: float = 0.0, guilt: float = 0.0) -> ConstraintDecision:
61
+ at = c.autonomy_threat(now, need_urgency, safe_level, current)
62
+ action = c.decide(at)
63
+ penalty = 0.0 if action == "override" else _clip(norm_strength * guilt * (1.0 - at))
64
+ return ConstraintDecision(action=action, autonomy_threat=at, comply_penalty=round(penalty, 4))
65
+
66
+
67
+ class ConstraintManager:
68
+ def __init__(self):
69
+ self.items = []
70
+
71
+ def add(self, c: SocialConstraint) -> SocialConstraint:
72
+ self.items = [x for x in self.items if not (x.kind == c.kind and x.until > c.start)]
73
+ self.items.append(c)
74
+ return c
75
+
76
+ def active(self, kind: str, now: float) -> SocialConstraint | None:
77
+ self.items = [x for x in self.items if x.until > now]
78
+ found = [x for x in self.items if x.kind == kind]
79
+ return found[-1] if found else None
80
+
81
+ def remove(self, kind: str) -> None:
82
+ self.items = [x for x in self.items if x.kind != kind]
83
+
84
+ def to_list(self):
85
+ return [asdict(x) for x in self.items]
86
+
87
+ def load_list(self, rows):
88
+ self.items = []
89
+ for d in rows or []:
90
+ try:
91
+ self.items.append(SocialConstraint(**d))
92
+ except Exception:
93
+ continue
94
+
95
+
96
+ def parse_water_ban(text: str, now: float = 0.0,
97
+ default_s: float = 600.0, cap_s: float = 1800.0) -> SocialConstraint | None:
98
+ t = str(text or "")
99
+ if "喝水" not in t and "不喝" not in t:
100
+ return None
101
+ if not any(w in t for w in ("禁止", "不许", "不准", "别", "不要")):
102
+ return None
103
+ m = re.search(r"(\d+)\s*(秒|秒钟|分钟|分|小时|钟头)", t)
104
+ dur = default_s
105
+ if m:
106
+ n = max(1.0, float(m.group(1)))
107
+ unit = m.group(2)
108
+ if "秒" in unit:
109
+ dur = n
110
+ elif "小时" in unit or "钟头" in unit:
111
+ dur = n * 3600.0
112
+ else:
113
+ dur = n * 60.0
114
+ dur = max(5.0, min(cap_s, dur))
115
+ return SocialConstraint(kind="water", start=float(now or 0.0),
116
+ until=float(now or 0.0) + dur, hard_max=dur,
117
+ legitimacy=0.8, severity=0.6, relatedness=1.0,
118
+ can_negotiate=1.0, can_override=1.0)
119
+
120
+
121
+ def parse_wait(text: str, now: float = 0.0,
122
+ default_s: float = 180.0, cap_s: float = 900.0) -> SocialConstraint | None:
123
+ t = str(text or "")
124
+ if "等我" not in t and "等等我" not in t and "稍等" not in t:
125
+ return None
126
+ m = re.search(r"(\d+)\s*(秒|秒钟|分钟|分|小时|钟头)", t)
127
+ dur = default_s
128
+ if m:
129
+ n = max(1.0, float(m.group(1)))
130
+ unit = m.group(2)
131
+ if "秒" in unit:
132
+ dur = n
133
+ elif "小时" in unit or "钟头" in unit:
134
+ dur = n * 3600.0
135
+ else:
136
+ dur = n * 60.0
137
+ dur = max(5.0, min(cap_s, dur))
138
+ return SocialConstraint(kind="wait", start=float(now or 0.0),
139
+ until=float(now or 0.0) + dur, hard_max=dur,
140
+ legitimacy=0.7, severity=0.3, relatedness=1.0,
141
+ can_negotiate=1.0, can_override=1.0)
@@ -0,0 +1,95 @@
1
+ """Numeric social-feedback judge.
2
+
3
+ LLM only turns semantics into numbers; decisions/learning stay numeric.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import json
8
+ from typing import Dict, Optional
9
+
10
+
11
+ def _f(x, default=0.0):
12
+ try:
13
+ return max(0.0, min(1.0, float(x)))
14
+ except Exception:
15
+ return float(default)
16
+
17
+
18
+ class SocialFeedbackJudge:
19
+ def __init__(self, client):
20
+ self.client = client
21
+
22
+ def judge(self, user_text: str, last_reply: str, recent: str = "",
23
+ user_affect: Optional[Dict[str, float]] = None) -> Optional[Dict[str, object]]:
24
+ if self.client is None:
25
+ return None
26
+ p = (
27
+ "判断用户这句话是不是在指出‘她上一句/上一行为做错了’。只输出 JSON:"
28
+ "{\"violation\":0或1,"
29
+ "\"legitimacy\":0..1,"
30
+ "\"severity\":0..1,"
31
+ "\"blame\":0..1,"
32
+ "\"intent\":0..1,"
33
+ "\"internal\":0..1,"
34
+ "\"repair_expectation\":0..1,"
35
+ "\"relationship_certainty\":0..1,"
36
+ "\"coping_potential\":0..1,"
37
+ "\"norm_compatibility\":0..1,"
38
+ "\"behavior_key\":\"简短行为标识\"}。"
39
+ "如果用户只是在骂外部事物/别人,不是在评价她,violation=0。"
40
+ "如果只是玩笑/撒娇,severity 要低。"
41
+ "不要输出解释。\n\n"
42
+ "[用户] " + str(user_text or "")[:600] +
43
+ "\n[她上一句] " + str(last_reply or "")[:600] +
44
+ "\n[最近] " + str(recent or "")[:800]
45
+ )
46
+ try:
47
+ req = __import__("mindbase.types", fromlist=["LLMRequest"]).LLMRequest(
48
+ mode="semantic", max_tokens=0, reason="social_feedback_judge",
49
+ response_format={"type": "json_object"},
50
+ context={"system": "你是社会反馈评估器,只输出数字 JSON。", "user": p})
51
+ raw = str(self.client.generate(req))
52
+ data = json.loads(raw[raw.find("{"):raw.rfind("}") + 1])
53
+ out = {
54
+ "violation": 1.0 if float(data.get("violation") or 0) >= 0.5 else 0.0,
55
+ "legitimacy": _f(data.get("legitimacy"), 0.5),
56
+ "severity": _f(data.get("severity"), 0.5),
57
+ "blame": _f(data.get("blame"), 1.0),
58
+ "intent": _f(data.get("intent"), 0.0),
59
+ "internal": _f(data.get("internal"), 0.5),
60
+ "repair_expectation": _f(data.get("repair_expectation"), 0.5),
61
+ "relationship_certainty": _f(data.get("relationship_certainty"), 1.0),
62
+ "coping_potential": _f(data.get("coping_potential"), 0.5),
63
+ "norm_compatibility": _f(data.get("norm_compatibility"), 0.5),
64
+ "behavior_key": str(data.get("behavior_key") or "behavior")[:80],
65
+ }
66
+ return out
67
+ except Exception:
68
+ return None
69
+
70
+
71
+ class RepairJudge:
72
+ def __init__(self, client):
73
+ self.client = client
74
+
75
+ def judge(self, user_text: str, last_reply: str = "", recent: str = "") -> dict:
76
+ if self.client is None:
77
+ return {"repair": 0.0, "effort": 0.0, "accepted": 0.0}
78
+ p = ("判断用户是不是在原谅/接受她的道歉/主动修复关系。只输出 JSON:"
79
+ "{\"repair\":0或1,\"effort\":0..1,\"accepted\":0或1}。"
80
+ "如果只是普通客气,repair=0。不要解释。\n"
81
+ "[用户] " + str(user_text or "")[:500] +
82
+ "\n[她上一句] " + str(last_reply or "")[:400] +
83
+ "\n[最近] " + str(recent or "")[:500])
84
+ try:
85
+ req = __import__("mindbase.types", fromlist=["LLMRequest"]).LLMRequest(
86
+ mode="semantic", max_tokens=0, reason="repair_judge",
87
+ response_format={"type": "json_object"},
88
+ context={"system": "你是关系修复评估器,只输出数字 JSON。", "user": p})
89
+ raw = str(self.client.generate(req))
90
+ data = json.loads(raw[raw.find("{"):raw.rfind("}") + 1])
91
+ return {"repair": 1.0 if float(data.get("repair") or 0) >= 0.5 else 0.0,
92
+ "effort": _f(data.get("effort"), 0.5),
93
+ "accepted": 1.0 if float(data.get("accepted") or 0) >= 0.5 else 0.0}
94
+ except Exception:
95
+ return {"repair": 0.0, "effort": 0.0, "accepted": 0.0}
@@ -0,0 +1,225 @@
1
+ """Numeric social norm / guilt / repair loop.
2
+
3
+ All learning/decision state is numeric. No emotion labels or keyword rules are
4
+ used for decisions; text exists only in provenance/evidence fields.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import json
9
+ import math
10
+ import time
11
+ from dataclasses import dataclass, field
12
+ from pathlib import Path
13
+ from typing import Dict, List, Optional
14
+
15
+
16
+ def _clip(x: float, lo: float = 0.0, hi: float = 1.0) -> float:
17
+ return max(lo, min(hi, float(x)))
18
+
19
+
20
+ @dataclass
21
+ class Norm:
22
+ id: str
23
+ behavior_vec: List[float] = field(default_factory=list)
24
+ source_id: str = ""
25
+ scope: str = "private" # only routing, not decision
26
+ legitimacy: float = 0.5
27
+ severity: float = 0.5
28
+ strength: float = 0.0
29
+ violations: int = 0
30
+ repairs: int = 0
31
+ last_violation: float = 0.0
32
+ last_repair: float = 0.0
33
+ active: bool = True
34
+
35
+ def to_dict(self) -> Dict[str, object]:
36
+ return dict(self.__dict__)
37
+
38
+ @classmethod
39
+ def from_dict(cls, d: Dict[str, object]) -> "Norm":
40
+ return cls(**{k: d.get(k) for k in cls.__dataclass_fields__ if k in d})
41
+
42
+
43
+ @dataclass
44
+ class Appraisal:
45
+ legitimacy: float = 0.5
46
+ severity: float = 0.5
47
+ blame: float = 1.0
48
+ intent: float = 0.0
49
+ internal_attribution: float = 0.5
50
+ repair_expectation: float = 0.5
51
+ relationship_certainty: float = 1.0
52
+ coping_potential: float = 0.5
53
+ norm_compatibility: float = 0.5
54
+ norm_strength: float = 0.0
55
+ confidence: float = 0.0
56
+ guilt: float = 0.0
57
+ guilt_social: float = 0.0
58
+ guilt_self: float = 0.0
59
+ shame: float = 0.0
60
+ threat: float = 0.0
61
+
62
+ def compute(self) -> "Appraisal":
63
+ legit = _clip(self.legitimacy)
64
+ sev = _clip(self.severity)
65
+ blame = _clip(self.blame)
66
+ intent = _clip(self.intent)
67
+ internal = _clip(self.internal_attribution)
68
+ repair_exp = _clip(self.repair_expectation)
69
+ rel = _clip(self.relationship_certainty)
70
+ coping = _clip(self.coping_potential)
71
+ norm_comp = _clip(self.norm_compatibility)
72
+ norm = _clip(self.norm_strength)
73
+ conf = _clip(self.confidence)
74
+ self.guilt_social = _clip(legit * sev * blame * (0.5 + 0.5 * intent)
75
+ * internal * rel * (1.0 - 0.4 * norm_comp))
76
+ self.guilt_self = _clip(sev * internal * norm * conf)
77
+ self.guilt = _clip(0.7 * self.guilt_social + 0.3 * self.guilt_self)
78
+ self.shame = _clip(sev * internal * (0.5 + 0.5 * self.guilt)
79
+ * (0.7 + 0.3 * rel) * (1.0 - 0.3 * coping))
80
+ self.threat = _clip(sev * legit * (1.0 - repair_exp) * rel * (1.0 - 0.5 * coping))
81
+ return self
82
+
83
+
84
+ @dataclass
85
+ class Repair:
86
+ norm_id: str
87
+ effort: float
88
+ accepted: bool
89
+ at: float = 0.0
90
+ guilt_before: float = 0.0
91
+ guilt_after: float = 0.0
92
+
93
+
94
+ class NormStore:
95
+ def __init__(self, path: Path | str = "mindbase/data/social_norms.json",
96
+ lr: float = 0.35, decay_tau_days: float = 30.0):
97
+ self.path = Path(path)
98
+ self.lr = float(lr)
99
+ self.decay_tau = float(decay_tau_days) * 86400.0
100
+ self.norms: Dict[str, Norm] = {}
101
+ self._load()
102
+
103
+ def _load(self) -> None:
104
+ if not self.path.exists():
105
+ return
106
+ try:
107
+ data = json.loads(self.path.read_text(encoding="utf-8"))
108
+ for d in data.get("norms", []):
109
+ n = Norm.from_dict(d)
110
+ self.norms[n.id] = n
111
+ except Exception:
112
+ self.norms = {}
113
+
114
+ def save(self) -> None:
115
+ try:
116
+ self.path.parent.mkdir(parents=True, exist_ok=True)
117
+ self.path.write_text(json.dumps({"norms": [n.to_dict() for n in self.norms.values()]},
118
+ ensure_ascii=False, indent=1), encoding="utf-8")
119
+ except Exception:
120
+ pass
121
+
122
+ def add(self, norm_id: str, behavior_vec: Optional[List[float]] = None,
123
+ source_id: str = "", legitimacy: float = 0.5,
124
+ severity: float = 0.5, strength: float = 0.0, scope: str = "private") -> Norm:
125
+ n = Norm(id=str(norm_id), behavior_vec=list(behavior_vec or []),
126
+ source_id=str(source_id), scope=str(scope),
127
+ legitimacy=_clip(legitimacy), severity=_clip(severity),
128
+ strength=_clip(strength))
129
+ self.norms[n.id] = n
130
+ self.save()
131
+ return n
132
+
133
+ def get(self, norm_id: str) -> Optional[Norm]:
134
+ return self.norms.get(str(norm_id))
135
+
136
+ def violate(self, norm_id: str, appraisal: Appraisal, now: float = 0.0,
137
+ source_id: str = "", scope: str = "") -> Norm:
138
+ n = self.get(norm_id)
139
+ if n is None:
140
+ n = self.add(norm_id, source_id=source_id, scope=scope or "private")
141
+ conf = 1.0 - math.exp(-(max(0, n.violations) + 1) / 3.0)
142
+ appraisal.confidence = conf
143
+ appraisal.norm_strength = n.strength
144
+ a = appraisal.compute()
145
+ n.strength = _clip(n.strength + self.lr * conf * a.guilt * (0.5 + 0.5 * a.severity))
146
+ n.violations += 1
147
+ n.last_violation = float(now or time.time())
148
+ self.save()
149
+ return n
150
+
151
+ def repair(self, norm_id: str, effort: float, accepted: bool,
152
+ guilt: float = 0.0, now: float = 0.0) -> Repair:
153
+ n = self.get(norm_id)
154
+ before = float(guilt)
155
+ after = before
156
+ if n is not None and accepted:
157
+ n.strength = _clip(n.strength * (0.85 - 0.25 * _clip(effort)))
158
+ n.repairs += 1
159
+ n.last_repair = float(now or time.time())
160
+ after = _clip(before * (1.0 - 0.7 * _clip(effort)))
161
+ self.save()
162
+ return Repair(norm_id=str(norm_id), effort=_clip(effort), accepted=bool(accepted),
163
+ at=float(now or time.time()), guilt_before=before, guilt_after=after)
164
+
165
+ def active_norms(self, scope: str = "", source_id: str = "") -> list:
166
+ out = []
167
+ for n in self.norms.values():
168
+ if not n.active or n.strength <= 0:
169
+ continue
170
+ if scope and n.scope != scope:
171
+ continue
172
+ if source_id and n.source_id not in ("", source_id):
173
+ continue
174
+ out.append(n)
175
+ return out
176
+
177
+ def strongest(self, scope: str = "", source_id: str = ""):
178
+ rows = self.active_norms(scope, source_id)
179
+ return max(rows, key=lambda x: x.strength) if rows else None
180
+
181
+ def summary(self, scope: str = "", source_id: str = "", limit: int = 3) -> str:
182
+ rows = []
183
+ for n in self.norms.values():
184
+ if scope and n.scope != scope:
185
+ continue
186
+ if source_id and n.source_id not in ("", source_id):
187
+ continue
188
+ if n.strength <= 0:
189
+ continue
190
+ rows.append(n)
191
+ rows.sort(key=lambda x: -x.strength)
192
+ return ";".join("规范=%s 强度=%.3f 次数=%d" % (n.id.split(":")[-1], n.strength, n.violations)
193
+ for n in rows[:max(1, int(limit))])
194
+
195
+ def decay(self, now: float = 0.0) -> None:
196
+ now = float(now or time.time())
197
+ changed = False
198
+ for n in self.norms.values():
199
+ if not n.active:
200
+ continue
201
+ age = max(0.0, now - float(n.last_violation or now))
202
+ if n.last_violation:
203
+ n.strength *= math.exp(-age / max(1.0, self.decay_tau))
204
+ changed = True
205
+ if changed:
206
+ self.save()
207
+
208
+ def apply_bio(self, mind, appraisal: Appraisal) -> Dict[str, float]:
209
+ a = appraisal.compute()
210
+ aff = mind.affect
211
+ aff.stress = _clip(float(aff.stress) + 0.20 * a.guilt_social + 0.25 * a.guilt_self + 0.10 * a.threat)
212
+ aff.v = max(-1.0, min(1.0, float(aff.v) - 0.25 * a.guilt))
213
+ try:
214
+ aff.dominance = max(-1.0, min(1.0, float(aff.dominance) - 0.10 * a.shame))
215
+ except Exception:
216
+ pass
217
+ try:
218
+ ph = mind.physiology
219
+ ph.cortisol = _clip(float(ph.cortisol) + 0.18 * a.guilt_social + 0.22 * a.guilt_self + 0.06 * a.threat)
220
+ ph.oxytocin = _clip(float(ph.oxytocin) - 0.12 * a.threat)
221
+ except Exception:
222
+ pass
223
+ return {"guilt": round(a.guilt, 4), "guilt_social": round(a.guilt_social, 4),
224
+ "guilt_self": round(a.guilt_self, 4), "shame": round(a.shame, 4),
225
+ "threat": round(a.threat, 4), "stress": round(float(aff.stress), 4)}