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,91 @@
1
+ """Meta-evolution of the autonomy fitness weights.
2
+
3
+ The selection criterion is itself mutable. Outer loop mutates fitness weights;
4
+ inner loop runs quality-diversity over behavior genomes and reports archive
5
+ size / viability / entropy. This removes the hand-set fitness rule.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import random
10
+ from typing import Dict, List
11
+
12
+ from .autonomy_bench import run_autonomy
13
+ from .autonomy_evolve import run_qd
14
+ from .config import MindConfig
15
+ from .diversity import js_divergence
16
+
17
+
18
+ def normalize(w: Dict[str, float]) -> Dict[str, float]:
19
+ keys = ("viability", "recovery", "entropy", "spont", "penalty")
20
+ vals = {k: max(1e-6, float(w.get(k, 0.1))) for k in keys}
21
+ total = sum(vals.values())
22
+ return {k: round(v / total, 5) for k, v in vals.items()}
23
+
24
+
25
+ def mutate(w: Dict[str, float], rng: random.Random, scale: float = 0.15) -> Dict[str, float]:
26
+ return normalize({k: float(w.get(k, 0.2)) * (1.0 + rng.gauss(0, scale)) for k in ("viability", "recovery", "entropy", "spont", "penalty")})
27
+
28
+
29
+ def _dist(counts: Dict[str, int]) -> Dict[str, float]:
30
+ total = sum(counts.values())
31
+ return {k: v / total for k, v in counts.items()} if total else {}
32
+
33
+
34
+ def evaluate_weights(w: Dict[str, float], inner_generations: int = 2, population: int = 4,
35
+ steps: int = 120, seed: int = 1,
36
+ seeds: tuple = (1, 2, 3)) -> Dict[str, object]:
37
+ out = run_qd(generations=inner_generations, population=population, steps=steps, seed=seed, weights=w)
38
+ best = out.get("best") or {}
39
+ descs = out.get("descriptors") or []
40
+ jsd = 0.0
41
+ genome = best.get("genome") if isinstance(best, dict) else None
42
+ if genome:
43
+ dists = []
44
+ for s in seeds:
45
+ cfg = MindConfig(seed=int(s), autonomy_threshold=float(genome.get("autonomy_threshold", 0.9)),
46
+ volition_temperature=float(genome.get("volition_temperature", 0.0)),
47
+ autonomy_adapt_lr=float(genome.get("autonomy_adapt_lr", 0.004)),
48
+ noise_sigma=float(genome.get("noise_sigma", 0.03)))
49
+ r = run_autonomy(steps=steps, cfg=cfg, perturb=False)
50
+ dists.append(_dist(r["actions"]))
51
+ vals = []
52
+ for i in range(len(dists)):
53
+ for j in range(i + 1, len(dists)):
54
+ vals.append(js_divergence(dists[i], dists[j]))
55
+ jsd = sum(vals) / max(1, len(vals))
56
+ score = (0.35 * min(1.0, float(out.get("archive_size", 0)) / 25.0)
57
+ + 0.30 * float(best.get("viability", 0.0))
58
+ + 0.15 * float(best.get("entropy", 0.0))
59
+ + 0.15 * min(1.0, jsd * 10.0)
60
+ + 0.05 * float(best.get("spontaneous", 0.0)))
61
+ return {"weights": w, "score": round(score, 5), "archive_size": out.get("archive_size", 0),
62
+ "best": best, "descriptors": descs, "jsd_mean": round(jsd, 5)}
63
+
64
+
65
+ def run_meta(generations: int = 4, candidates: int = 4, inner_generations: int = 2,
66
+ population: int = 4, steps: int = 120, seed: int = 1,
67
+ seeds: tuple = (1, 2, 3)) -> Dict[str, object]:
68
+ rng = random.Random(int(seed))
69
+ initial = [
70
+ {"viability": 1.0, "recovery": 0.5, "entropy": 0.3, "spont": 0.2, "penalty": 0.5},
71
+ {"viability": 0.5, "recovery": 0.5, "entropy": 1.0, "spont": 1.0, "penalty": 0.5},
72
+ {"viability": 2.0, "recovery": 1.0, "entropy": 0.1, "spont": 0.1, "penalty": 1.0},
73
+ {"viability": 1.0, "recovery": 2.0, "entropy": 0.2, "spont": 0.2, "penalty": 0.5},
74
+ ]
75
+ pool = [normalize(w) for w in initial[:max(1, candidates)]]
76
+ history = []
77
+ for gen in range(max(1, int(generations))):
78
+ results = [evaluate_weights(w, inner_generations=inner_generations, population=population,
79
+ steps=steps, seed=seed, seeds=seeds) for w in pool]
80
+ results.sort(key=lambda x: x["score"], reverse=True)
81
+ pool = [r["weights"] for r in results[:max(1, min(2, len(results)))]]
82
+ history.append({"generation": gen + 1, "scores": [r["score"] for r in results],
83
+ "archive_sizes": [r["archive_size"] for r in results],
84
+ "jsd": [r.get("jsd_mean", 0.0) for r in results],
85
+ "weights": pool})
86
+ while len(pool) < max(1, int(candidates)):
87
+ pool.append(mutate(rng.choice(pool), rng))
88
+ final = evaluate_weights(pool[0], inner_generations=inner_generations, population=population,
89
+ steps=steps, seed=seed, seeds=seeds)
90
+ return {"best_weights": final["weights"], "best_score": final["score"],
91
+ "best": final["best"], "history": history}
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterable, List, Optional
4
+
5
+ from .config import MindConfig
6
+ from .engine import MindBase
7
+ from .types import Event, Outcome
8
+
9
+
10
+ def run(events: Optional[Iterable[Event]] = None) -> List[Outcome]:
11
+ """Base-only mode: no LLM, no client, only the base's own decisions."""
12
+ mb = MindBase(MindConfig(seed=700))
13
+ evs = list(events or [
14
+ Event(text="在吗", speaker="A", directed=True, ts=0),
15
+ Event(text="今天有点累", speaker="A", ts=60),
16
+ Event(text="老板,出来玩吗", speaker="A", directed=True, ts=120),
17
+ Event(kind="tick", ts=180),
18
+ ])
19
+ return mb.run(evs)
20
+
21
+
22
+ def main() -> None:
23
+ for out in run():
24
+ print(out.t, out.action, out.reason, out.prompt_state.get("relationship"))
25
+
26
+
27
+ if __name__ == "__main__":
28
+ main()
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Dict
4
+
5
+ from .grid_world import EmbodiedLoop, GridWorld
6
+ from .relationship_session import RelationshipSession
7
+
8
+
9
+ def run(stress: bool = False, extreme: bool = False) -> Dict[str, object]:
10
+ from . import MindBase, MindConfig
11
+
12
+ # 1. autonomy: can it act without being addressed and stay viable?
13
+ mb1 = MindBase(MindConfig(seed=2000))
14
+ noise = 0.60 if extreme else (0.40 if stress else 0.10)
15
+ shock_every = 50 if stress else 100
16
+ a = EmbodiedLoop(mb1, GridWorld(seed=1, noise=noise)).run(300, shock_every)
17
+ autonomy = {
18
+ "initiative": a["autonomy"]["initiative"],
19
+ "survival": a["energy"],
20
+ "viability": a["viability"],
21
+ "pass": a["autonomy"]["initiative"] > 0.5 and a["energy"] > 0.2 and a["viability"] >= 0.6,
22
+ }
23
+
24
+ # 2. long-term memory: cross-day recall
25
+ mb2 = MindBase(MindConfig(seed=2001))
26
+ s = RelationshipSession(mb2)
27
+ s.say("我叫阿明,我喜欢猫", 0.5)
28
+ s.say("今天有点难过", 0.3)
29
+ s.day_break(24 * 7 if stress else 24)
30
+ recall = s.recall("阿明")
31
+ memory = {"recall": recall, "pass": bool(recall and "阿明" in recall)}
32
+
33
+ # 2b. memory interference: newer similar fact must supersede the old one
34
+ mb2b = MindBase(MindConfig(seed=2002))
35
+ sb = RelationshipSession(mb2b)
36
+ sb.say("我的名字是阿明", 0.8)
37
+ sb.say("我的名字是小华", 0.8)
38
+ recalled = sb.recall("名字")
39
+ interference = {"recall": recalled, "pass": bool(recalled and "小华" in recalled and "阿明" not in recalled)}
40
+
41
+ # 3. identity persistence: conflict then repair
42
+ before = mb2.self_model.identity
43
+ for _ in range(15 if extreme else (5 if stress else 1)):
44
+ s.say("你根本不懂我", -1.0)
45
+ low = mb2.self_model.identity
46
+ s.auto_repair(max_rounds=5 if extreme else 20)
47
+ after = mb2.self_model.identity
48
+ identity = {
49
+ "before": round(before, 3), "after_conflict": round(low, 3), "after_repair": round(after, 3),
50
+ "pass": after > 0.45 and abs(after - before) < 0.25,
51
+ }
52
+
53
+ # 4. relationship continuity: trust does not collapse after conflict+repair
54
+ rel = {"trust": round(mb2.social.trust_for("A"), 3), "stage": mb2.social.stage("A")}
55
+ rel["pass"] = rel["trust"] > 0.3
56
+
57
+ checks = [autonomy["pass"], memory["pass"], interference["pass"], identity["pass"], rel["pass"]]
58
+ return {
59
+ "autonomy": autonomy, "memory": memory, "interference": interference, "identity": identity, "relationship": rel,
60
+ "score": round(sum(checks) / len(checks), 3), "pass": all(checks),
61
+ }
@@ -0,0 +1,274 @@
1
+ """Biological parameter adaptation, not prompt/persona iteration.
2
+
3
+ The LLM can propose small trait deltas from interaction evidence. The bio layer
4
+ clamps them, drifts them, persists them, and feeds the updated numbers back into
5
+ the next prompt. This is the closed loop: language -> bio -> language.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ import math
11
+ import time
12
+ from dataclasses import asdict, dataclass, field
13
+ from pathlib import Path
14
+
15
+ from .noise import clip
16
+
17
+ TRAITS = (
18
+ "O", "C", "E", "A", "N",
19
+ "stress_reactivity", "recovery_rate", "social_baseline",
20
+ "energy_baseline", "curiosity_baseline", "safety_baseline",
21
+ "da_baseline", "oa_baseline", "ht_baseline", "autonomy_gain",
22
+ )
23
+
24
+
25
+ @dataclass
26
+ class BioProfile:
27
+ traits: dict = field(default_factory=dict)
28
+ version: int = 1
29
+ evidence: int = 0
30
+ last_update: float = 0.0
31
+ baseline: dict = field(default_factory=dict)
32
+ stress_load: float = 0.0
33
+ last_tick: float = 0.0
34
+ noise_values: dict = field(default_factory=dict)
35
+ noise_chaos: float = 0.5
36
+ last_noise_tick: float = 0.0
37
+ noise_echo: float = 0.0
38
+ echo_trace: float = 0.0
39
+ last_echo_tick: float = 0.0
40
+ persona_version: str = ""
41
+
42
+ def __post_init__(self):
43
+ if not self.traits:
44
+ self.traits = {k: 0.5 for k in TRAITS}
45
+ if not self.baseline:
46
+ self.baseline = dict(self.traits)
47
+
48
+ @classmethod
49
+ def from_mind(cls, mind) -> "BioProfile":
50
+ p = getattr(mind, "persona", None)
51
+ traits = {}
52
+ for k in TRAITS:
53
+ try:
54
+ traits[k] = float(getattr(p, k))
55
+ except Exception:
56
+ traits[k] = 0.5
57
+ return cls(traits=traits, baseline=dict(traits))
58
+
59
+ def nudge(self, deltas: dict, confidence: float = 0.5) -> dict:
60
+ c = clip(confidence, 0.0, 1.0)
61
+ lr = 0.12 * c
62
+ for k, dv in dict(deltas or {}).items():
63
+ if k not in TRAITS:
64
+ continue
65
+ try:
66
+ dv = clip(float(dv), -0.02, 0.02)
67
+ except Exception:
68
+ continue
69
+ self.traits[k] = clip(float(self.traits[k]) + lr * dv, 0.0, 1.0)
70
+ self.version += 1
71
+ self.evidence += 1
72
+ self.last_update = time.time()
73
+ return dict(self.traits)
74
+
75
+ def experience(self, reward: float = 0.5, valence: float = 0.0,
76
+ stress: float = 0.2, social: float = 0.5) -> dict:
77
+ """Slow learning from actual outcomes; LLM proposal is only one input."""
78
+ r = clip(reward, 0.0, 1.0)
79
+ v = clip(valence, -1.0, 1.0)
80
+ st = clip(stress, 0.0, 1.0)
81
+ so = clip(social, 0.0, 1.0)
82
+ lr = 0.004
83
+ # positive + low stress -> recovery/safety/social up
84
+ if r >= 0.55 and st < 0.65:
85
+ self.traits["recovery_rate"] = clip(self.traits["recovery_rate"] + lr * (r - 0.5), 0.0, 1.0)
86
+ self.traits["safety_baseline"] = clip(self.traits["safety_baseline"] + lr * (r - 0.5) * 0.8, 0.0, 1.0)
87
+ self.traits["social_baseline"] = clip(self.traits["social_baseline"] + lr * (r - 0.5) * 0.6 * (0.4 + so), 0.0, 1.0)
88
+ elif r < 0.45 and st > 0.6:
89
+ self.traits["stress_reactivity"] = clip(self.traits["stress_reactivity"] + lr * (0.5 - r) * 0.7, 0.0, 1.0)
90
+ self.traits["recovery_rate"] = clip(self.traits["recovery_rate"] - lr * (0.5 - r) * 0.4, 0.0, 1.0)
91
+ if v > 0.2:
92
+ self.traits["curiosity_baseline"] = clip(self.traits["curiosity_baseline"] + lr * 0.4 * v, 0.0, 1.0)
93
+ self.version += 1
94
+ self.last_update = time.time()
95
+ return dict(self.traits)
96
+
97
+ def drift(self, rate: float = 0.002) -> None:
98
+ for k in TRAITS:
99
+ self.traits[k] = clip(float(self.traits[k]) + rate * (float(self.baseline[k]) - float(self.traits[k])), 0.0, 1.0)
100
+
101
+ def apply_to_mind(self, mind) -> None:
102
+ p = getattr(mind, "persona", None)
103
+ if p is None:
104
+ return
105
+ for k, v in self.traits.items():
106
+ if hasattr(p, k):
107
+ try:
108
+ setattr(p, k, clip(float(v), 0.0, 1.0))
109
+ except Exception:
110
+ pass
111
+ # do not reset current affect/drives; only move their baselines slowly
112
+ try:
113
+ mind.drives.values["social"] = clip(0.995 * float(mind.drives.values.get("social", 0.5)) + 0.005 * float(self.traits["social_baseline"]), 0.0, 1.0)
114
+ mind.drives.values["curiosity"] = clip(0.995 * float(mind.drives.values.get("curiosity", 0.5)) + 0.005 * float(self.traits["curiosity_baseline"]), 0.0, 1.0)
115
+ mind.drives.values["energy"] = clip(0.995 * float(mind.drives.values.get("energy", 0.5)) + 0.005 * float(self.traits["energy_baseline"]), 0.0, 1.0)
116
+ except Exception:
117
+ pass
118
+
119
+ def _noise_engine(self):
120
+ eng = getattr(self, "_noise_eng", None)
121
+ if eng is None:
122
+ from .bio_noise import BioNoise
123
+ eng = BioNoise(2026)
124
+ eng.values.update({k: float(v) for k, v in (self.noise_values or {}).items()})
125
+ eng.last_chaos = float(self.noise_chaos or 0.5)
126
+ self._noise_eng = eng
127
+ return eng
128
+
129
+ def update_echo(self, instant: float, now: float | None = None,
130
+ tau_s: float = 180.0) -> float:
131
+ """Echo propagation with attenuation: e_t = a*e_(t-1) + (1-a)*instant."""
132
+ now = time.time() if now is None else float(now)
133
+ if float(self.last_echo_tick or 0.0) <= 0.0:
134
+ dt = 5.0
135
+ else:
136
+ dt = max(0.0, min(600.0, now - float(self.last_echo_tick)))
137
+ a = math.exp(-dt / max(1.0, float(tau_s)))
138
+ self.echo_trace = clip(a * float(self.echo_trace or 0.0) + (1.0 - a) * float(instant), -1.0, 1.0)
139
+ self.noise_echo = self.echo_trace
140
+ self.last_echo_tick = now
141
+ return self.echo_trace
142
+
143
+ def step_noise(self, now: float | None = None) -> dict:
144
+ now = time.time() if now is None else float(now)
145
+ eng = self._noise_engine()
146
+ if float(self.last_noise_tick or 0.0) <= 0.0:
147
+ dt = 1.0
148
+ else:
149
+ dt = max(0.0, min(30.0, now - float(self.last_noise_tick)))
150
+ vals = eng.step(dt)
151
+ self.noise_values = {k: round(float(v), 4) for k, v in vals.items()}
152
+ self.noise_chaos = round(float(eng.last_chaos), 4)
153
+ self.last_noise_tick = now
154
+ return dict(self.noise_values)
155
+
156
+ def tick(self, mind, now: float | None = None, noise: bool = True) -> dict:
157
+ """Stress hysteresis: rise fast, recover slowly. This is the missing inertia."""
158
+ now = time.time() if now is None else float(now)
159
+ try:
160
+ stress = clip(float(getattr(mind.affect, "stress", 0.2)), 0.0, 1.0)
161
+ except Exception:
162
+ stress = 0.2
163
+ if float(self.last_tick or 0.0) <= 0.0:
164
+ dt = 0.0
165
+ else:
166
+ dt = max(0.0, now - float(self.last_tick))
167
+ # recovery time: 2h base, slower when recovery_rate is low
168
+ tau_s = 2.0 * 3600.0 + 4.0 * 3600.0 * (1.0 - clip(float(self.traits.get("recovery_rate", 0.5)), 0.0, 1.0))
169
+ if dt > 0.0:
170
+ self.stress_load = clip(float(self.stress_load) * math.exp(-dt / tau_s), 0.0, 1.0)
171
+ # high stress loads immediately; low stress decays slowly
172
+ self.stress_load = max(float(self.stress_load), stress)
173
+ self.last_tick = now
174
+ if noise:
175
+ self.step_noise(now)
176
+ return {"stress": round(stress, 4), "stress_load": round(float(self.stress_load), 4),
177
+ "tau_hours": round(tau_s / 3600.0, 2)}
178
+
179
+ def behavior(self, mind, base_threshold: float = 0.74, trust: float = 0.5) -> dict:
180
+ """Bio state -> hard behavioral consequences, not just prompt text."""
181
+ energy = 0.5
182
+ stress = 0.2
183
+ social = 0.5
184
+ trust = clip(float(trust), 0.0, 1.0)
185
+ try:
186
+ energy = clip(float(mind.drives.values.get("energy", 0.5)), 0.0, 1.0)
187
+ social = clip(float(mind.drives.values.get("social", 0.5)), 0.0, 1.0)
188
+ stress = clip(float(getattr(mind.affect, "stress", 0.2)), 0.0, 1.0)
189
+ except Exception:
190
+ pass
191
+ recovery = clip(float(self.traits.get("recovery_rate", 0.5)), 0.0, 1.0)
192
+ n = dict(self.noise_values or {})
193
+ echo = clip(float(self.noise_echo or 0.0), -1.0, 1.0)
194
+ stress_eff = clip(max(stress, float(self.stress_load or 0.0)) + 0.12 * float(n.get("stress", 0.0)), 0.0, 1.0)
195
+ energy_eff = clip(energy + 0.10 * float(n.get("energy", 0.0)), 0.0, 1.0)
196
+ # low energy/high stress/low recovery -> harder to join a group
197
+ threshold = (float(base_threshold) + 0.12 * (1.0 - energy_eff)
198
+ + 0.08 * stress_eff - 0.05 * (trust - 0.5)
199
+ + 0.04 * float(n.get("attention", 0.0)) - 0.03 * float(n.get("social", 0.0))
200
+ + 0.03 * echo
201
+ - 0.04 * (social - 0.5) + 0.06 * (0.5 - recovery))
202
+ threshold = clip(threshold, 0.68, 0.92)
203
+ # short replies when depleted/stressed; forced replies keep a floor
204
+ max_chars = int(240.0 * (0.55 + 0.45 * energy_eff) * (1.0 - 0.30 * stress_eff))
205
+ max_chars = int(max(80, min(260, max_chars)))
206
+ private_mode = "answer"
207
+ if energy_eff < 0.22 and stress_eff > 0.75:
208
+ private_mode = "defer"
209
+ elif energy_eff < 0.40 or stress_eff > 0.70:
210
+ private_mode = "brief"
211
+ warmth = clip(0.50 + 0.20 * trust + 0.20 * float(getattr(mind.affect, "v", 0.0))
212
+ - 0.25 * stress_eff, 0.0, 1.0)
213
+ disclosure = clip(0.20 + 0.55 * trust + 0.15 * energy_eff - 0.25 * stress_eff, 0.0, 1.0)
214
+ initiative = clip(0.20 + 0.35 * social + 0.25 * energy_eff - 0.30 * stress_eff, 0.0, 1.0)
215
+ return {"threshold": round(threshold, 4), "max_chars": max_chars,
216
+ "energy": round(energy, 3), "stress": round(stress, 3),
217
+ "stress_load": round(stress_eff, 3),
218
+ "private_mode": private_mode,
219
+ "warmth": round(warmth, 3),
220
+ "disclosure": round(disclosure, 3),
221
+ "initiative": round(initiative, 3),
222
+ "noise": {k: round(float(v), 3) for k, v in n.items()},
223
+ "chaos": round(float(self.noise_chaos or 0.5), 3),
224
+ "echo": round(echo, 3), "echo_trace": round(float(self.echo_trace or 0.0), 3),
225
+ "echo_tau_s": 180.0,
226
+ "social": round(social, 3), "trust": round(trust, 3),
227
+ "recovery": round(recovery, 3)}
228
+
229
+ def context(self) -> str:
230
+ keys = ("stress_reactivity", "recovery_rate", "social_baseline",
231
+ "curiosity_baseline", "safety_baseline", "energy_baseline")
232
+ return " ".join("%s=%.2f" % (k, float(self.traits.get(k, 0.5))) for k in keys)
233
+
234
+ def snapshot(self) -> dict:
235
+ return {"version": self.version, "evidence": self.evidence,
236
+ "last_update": self.last_update,
237
+ "stress_load": round(float(self.stress_load or 0.0), 4),
238
+ "noise_values": {k: round(float(v), 4) for k, v in (self.noise_values or {}).items()},
239
+ "noise_chaos": round(float(self.noise_chaos or 0.5), 4),
240
+ "last_noise_tick": self.last_noise_tick,
241
+ "noise_echo": round(float(self.noise_echo or 0.0), 4),
242
+ "echo_trace": round(float(self.echo_trace or 0.0), 4),
243
+ "last_echo_tick": self.last_echo_tick,
244
+ "traits": {k: round(float(v), 4) for k, v in self.traits.items()}}
245
+
246
+ def save(self, path: Path | str) -> None:
247
+ try:
248
+ p = Path(path)
249
+ p.parent.mkdir(parents=True, exist_ok=True)
250
+ p.write_text(json.dumps(asdict(self), ensure_ascii=False, indent=1), encoding="utf-8")
251
+ except Exception:
252
+ pass
253
+
254
+ @classmethod
255
+ def load(cls, path: Path | str, mind=None) -> "BioProfile":
256
+ try:
257
+ d = json.loads(Path(path).read_text(encoding="utf-8"))
258
+ obj = cls(traits=dict(d.get("traits") or {}), version=int(d.get("version", 1)),
259
+ evidence=int(d.get("evidence", 0)), last_update=float(d.get("last_update", 0.0)),
260
+ baseline=dict(d.get("baseline") or {}),
261
+ stress_load=float(d.get("stress_load", 0.0) or 0.0),
262
+ last_tick=float(d.get("last_tick", 0.0) or 0.0),
263
+ noise_values=dict(d.get("noise_values") or {}),
264
+ noise_chaos=float(d.get("noise_chaos", 0.5) or 0.5),
265
+ last_noise_tick=float(d.get("last_noise_tick", 0.0) or 0.0),
266
+ noise_echo=float(d.get("noise_echo", 0.0) or 0.0),
267
+ echo_trace=float(d.get("echo_trace", 0.0) or 0.0),
268
+ last_echo_tick=float(d.get("last_echo_tick", 0.0) or 0.0),
269
+ persona_version=str(d.get("persona_version") or ""))
270
+ if not obj.traits and mind is not None:
271
+ return cls.from_mind(mind)
272
+ return obj
273
+ except Exception:
274
+ return cls.from_mind(mind) if mind is not None else cls()
@@ -0,0 +1,39 @@
1
+ """Multi-channel chaotic noise for the bio layer.
2
+
3
+ Not physics. A Lorenz field drives several bounded OU channels; each channel
4
+ has a different time constant and gain, so stress/energy/social/attention
5
+ perturbations arrive out of phase instead of as one random number.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import random
10
+
11
+ from .noise import clip
12
+ from .quantum_chaos import ChaosField
13
+
14
+ CHANNELS = ("stress", "energy", "social", "attention", "valence")
15
+
16
+
17
+ class BioNoise:
18
+ def __init__(self, seed: int = 2026):
19
+ self.field = ChaosField(seed)
20
+ self.rng = random.Random(seed + 7)
21
+ self.values = {k: 0.0 for k in CHANNELS}
22
+ self.last_chaos = 0.5
23
+
24
+ def step(self, dt: float = 1.0) -> dict:
25
+ steps = max(1, min(40, int(abs(float(dt or 1.0)) * 10)))
26
+ self.last_chaos = float(self.field.step(steps=steps).get("value", 0.5))
27
+ for i, k in enumerate(CHANNELS):
28
+ theta = 0.10 + 0.025 * i
29
+ sigma = 0.018 + 0.004 * i
30
+ x = float(self.values.get(k, 0.0))
31
+ x += theta * (0.0 - x)
32
+ x += sigma * (self.last_chaos - 0.5)
33
+ x += sigma * (self.rng.random() - 0.5)
34
+ self.values[k] = clip(x, -0.15, 0.15)
35
+ return dict(self.values)
36
+
37
+ def snapshot(self) -> dict:
38
+ return {"values": {k: round(float(v), 4) for k, v in self.values.items()},
39
+ "chaos": round(self.last_chaos, 4)}
@@ -0,0 +1,138 @@
1
+ """Translate biological state into compact semantic intent.
2
+
3
+ Not raw numbers, not a persona script. Fields separate:
4
+ emotion / need / distance / tone / expression / intensity.
5
+ """
6
+ from __future__ import annotations
7
+
8
+
9
+ def _f(x, default=0.0):
10
+ try:
11
+ return float(x)
12
+ except Exception:
13
+ return float(default)
14
+
15
+
16
+ def translate(mind, behavior=None, ripple_outputs=None, trust=None, emotion=None, event=None) -> dict:
17
+ b = dict(behavior or {})
18
+ ro = dict(ripple_outputs or {})
19
+ emo = dict(emotion or {})
20
+ try:
21
+ v = _f(getattr(mind.affect, "v", 0.0))
22
+ a = _f(getattr(mind.affect, "a", 0.5))
23
+ stress = _f(getattr(mind.affect, "stress", 0.2))
24
+ except Exception:
25
+ v, a, stress = 0.0, 0.5, 0.2
26
+ try:
27
+ energy = _f(mind.drives.values.get("energy", 0.5))
28
+ except Exception:
29
+ energy = 0.5
30
+ if trust is None:
31
+ try:
32
+ trust = _f(mind.social.trust_for(""), 0.5)
33
+ except Exception:
34
+ trust = 0.5
35
+ trust = _f(trust, 0.5)
36
+
37
+ # emotion: keep the strongest 7-dim signal, not only PAD/stress
38
+ dims = {k: _f(emo.get(k), 0.0) for k in
39
+ ("joy", "sadness", "fear", "anger", "surprise", "trust", "loneliness")}
40
+ ev = dict(event or {})
41
+ if ev:
42
+ _v = _f(ev.get("valence"), 0.0)
43
+ if _v < -0.10:
44
+ dims["sadness"] = max(dims["sadness"], min(1.0, -_v * 0.8))
45
+ elif _v > 0.10:
46
+ dims["joy"] = max(dims["joy"], min(1.0, _v * 0.8))
47
+ if _f(ev.get("threat"), 0.0) > 0.15:
48
+ dims["fear"] = max(dims["fear"], min(1.0, _f(ev.get("threat"))))
49
+ if _f(ev.get("connection_delta"), 0.0) > 0.10:
50
+ dims["trust"] = max(dims["trust"], min(1.0, 0.45 + _f(ev.get("connection_delta"))))
51
+ top = max(dims, key=lambda k: dims[k]) if dims else "neutral"
52
+ top_val = dims.get(top, 0.0)
53
+ if top_val < 0.08:
54
+ top = ("sadness" if v < -0.15 else ("joy" if v > 0.15 else "neutral"))
55
+ top_val = abs(v)
56
+ emotion_tag = top if top != "neutral" else "neutral"
57
+ intensity_tag = "high" if max(top_val, abs(v), a) > 0.62 else ("low" if max(top_val, abs(v), a) < 0.28 else "medium")
58
+
59
+ energy_tag = "low" if energy < 0.38 else ("high" if energy > 0.72 else "normal")
60
+ stress_tag = "overloaded" if stress > 0.60 else ("tense" if stress > 0.30 else "calm")
61
+ if emotion_tag in ("sadness", "loneliness"):
62
+ need = "be_heard"
63
+ elif emotion_tag in ("fear", "anger"):
64
+ need = "space" if emotion_tag == "fear" else "vent"
65
+ elif emotion_tag in ("joy", "trust"):
66
+ need = "share"
67
+ elif stress_tag == "overloaded" or energy_tag == "low":
68
+ need = "space"
69
+ else:
70
+ need = "talk"
71
+
72
+ if emotion_tag in ("fear", "anger", "sadness", "loneliness") or trust < 0.32:
73
+ distance = "far"
74
+ elif emotion_tag in ("joy", "trust") and trust > 0.45:
75
+ distance = "near"
76
+ else:
77
+ distance = "neutral"
78
+
79
+ if emotion_tag in ("sadness", "fear"):
80
+ tone = "soft"
81
+ elif emotion_tag == "anger":
82
+ tone = "blunt"
83
+ elif emotion_tag in ("joy", "surprise"):
84
+ tone = "playful"
85
+ elif emotion_tag == "loneliness":
86
+ tone = "quiet"
87
+ else:
88
+ tone = "neutral"
89
+
90
+ if stress_tag == "overloaded" or energy_tag == "low":
91
+ intent = "withdraw"
92
+ elif need == "share" and stress_tag != "overloaded":
93
+ intent = "approach"
94
+ elif emotion_tag in ("joy", "trust") and energy_tag != "low":
95
+ intent = "approach"
96
+ elif energy_tag == "high" and trust > 0.62 and stress_tag == "calm":
97
+ intent = "approach"
98
+ elif need == "be_heard" and energy_tag != "low":
99
+ intent = "hold"
100
+ else:
101
+ intent = "hold"
102
+ expression = "short" if intent == "withdraw" or stress_tag == "overloaded" else (
103
+ "open" if intent == "approach" and energy_tag != "low" else "normal")
104
+ q_budget = 1 if (expression == "open" and intent == "approach") or (
105
+ need in ("be_heard", "share") and expression != "short" and energy_tag != "low") else 0
106
+ proactive = 0.0
107
+ if intent == "approach":
108
+ proactive = 0.10 + 0.10 * max(0.0, _f(ro.get("engage_bias")))
109
+ elif intent == "withdraw":
110
+ proactive = -0.15
111
+ mode = str(b.get("private_mode") or "answer")
112
+ if intent == "withdraw" and mode == "answer":
113
+ mode = "brief"
114
+ return {
115
+ "emotion": emotion_tag,
116
+ "intensity": intensity_tag,
117
+ "need": need,
118
+ "distance": distance,
119
+ "tone": tone,
120
+ "intent": intent,
121
+ "energy": energy_tag,
122
+ "stress": stress_tag,
123
+ "expression": expression,
124
+ "question_budget": q_budget,
125
+ "proactive_bias": round(proactive, 3),
126
+ "mode": mode,
127
+ "valence": "positive" if v > 0.12 else ("negative" if v < -0.12 else "neutral"),
128
+ }
129
+
130
+
131
+ def context(semantic: dict) -> str:
132
+ s = dict(semantic or {})
133
+ return ("emotion=%s intensity=%s need=%s distance=%s tone=%s "
134
+ "intent=%s expression=%s mode=%s" % (
135
+ s.get("emotion", "neutral"), s.get("intensity", "medium"),
136
+ s.get("need", "talk"), s.get("distance", "neutral"),
137
+ s.get("tone", "neutral"), s.get("intent", "hold"),
138
+ s.get("expression", "normal"), s.get("mode", "answer")))