lex-agentic-self 0.1.0

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 (249) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +13 -0
  6. data/lex-agentic-self.gemspec +31 -0
  7. data/lib/legion/extensions/agentic/self/agency/client.rb +21 -0
  8. data/lib/legion/extensions/agentic/self/agency/helpers/constants.rb +77 -0
  9. data/lib/legion/extensions/agentic/self/agency/helpers/efficacy_model.rb +136 -0
  10. data/lib/legion/extensions/agentic/self/agency/helpers/outcome_event.rb +52 -0
  11. data/lib/legion/extensions/agentic/self/agency/runners/agency.rb +117 -0
  12. data/lib/legion/extensions/agentic/self/agency/version.rb +13 -0
  13. data/lib/legion/extensions/agentic/self/agency.rb +19 -0
  14. data/lib/legion/extensions/agentic/self/anchor/client.rb +15 -0
  15. data/lib/legion/extensions/agentic/self/anchor/helpers/anchor.rb +92 -0
  16. data/lib/legion/extensions/agentic/self/anchor/helpers/anchor_engine.rb +123 -0
  17. data/lib/legion/extensions/agentic/self/anchor/helpers/chain.rb +93 -0
  18. data/lib/legion/extensions/agentic/self/anchor/helpers/constants.rb +46 -0
  19. data/lib/legion/extensions/agentic/self/anchor/runners/cognitive_anchor.rb +70 -0
  20. data/lib/legion/extensions/agentic/self/anchor/version.rb +13 -0
  21. data/lib/legion/extensions/agentic/self/anchor.rb +22 -0
  22. data/lib/legion/extensions/agentic/self/anosognosia/client.rb +28 -0
  23. data/lib/legion/extensions/agentic/self/anosognosia/helpers/anosognosia_engine.rb +153 -0
  24. data/lib/legion/extensions/agentic/self/anosognosia/helpers/cognitive_deficit.rb +71 -0
  25. data/lib/legion/extensions/agentic/self/anosognosia/helpers/constants.rb +29 -0
  26. data/lib/legion/extensions/agentic/self/anosognosia/runners/anosognosia.rb +98 -0
  27. data/lib/legion/extensions/agentic/self/anosognosia/version.rb +13 -0
  28. data/lib/legion/extensions/agentic/self/anosognosia.rb +19 -0
  29. data/lib/legion/extensions/agentic/self/architecture/client.rb +19 -0
  30. data/lib/legion/extensions/agentic/self/architecture/helpers/architecture_engine.rb +167 -0
  31. data/lib/legion/extensions/agentic/self/architecture/helpers/connection.rb +57 -0
  32. data/lib/legion/extensions/agentic/self/architecture/helpers/constants.rb +37 -0
  33. data/lib/legion/extensions/agentic/self/architecture/helpers/subsystem.rb +80 -0
  34. data/lib/legion/extensions/agentic/self/architecture/runners/cognitive_architecture.rb +125 -0
  35. data/lib/legion/extensions/agentic/self/architecture/version.rb +13 -0
  36. data/lib/legion/extensions/agentic/self/architecture.rb +20 -0
  37. data/lib/legion/extensions/agentic/self/default_mode_network/actors/idle.rb +45 -0
  38. data/lib/legion/extensions/agentic/self/default_mode_network/client.rb +28 -0
  39. data/lib/legion/extensions/agentic/self/default_mode_network/helpers/constants.rb +53 -0
  40. data/lib/legion/extensions/agentic/self/default_mode_network/helpers/dmn_engine.rb +221 -0
  41. data/lib/legion/extensions/agentic/self/default_mode_network/helpers/wandering_thought.rb +60 -0
  42. data/lib/legion/extensions/agentic/self/default_mode_network/runners/default_mode_network.rb +122 -0
  43. data/lib/legion/extensions/agentic/self/default_mode_network/version.rb +13 -0
  44. data/lib/legion/extensions/agentic/self/default_mode_network.rb +20 -0
  45. data/lib/legion/extensions/agentic/self/fingerprint/client.rb +28 -0
  46. data/lib/legion/extensions/agentic/self/fingerprint/helpers/cognitive_trait.rb +73 -0
  47. data/lib/legion/extensions/agentic/self/fingerprint/helpers/constants.rb +60 -0
  48. data/lib/legion/extensions/agentic/self/fingerprint/helpers/fingerprint_engine.rb +169 -0
  49. data/lib/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint.rb +86 -0
  50. data/lib/legion/extensions/agentic/self/fingerprint/version.rb +13 -0
  51. data/lib/legion/extensions/agentic/self/fingerprint.rb +19 -0
  52. data/lib/legion/extensions/agentic/self/identity/actors/credential_refresh.rb +49 -0
  53. data/lib/legion/extensions/agentic/self/identity/actors/orphan_check.rb +52 -0
  54. data/lib/legion/extensions/agentic/self/identity/client.rb +27 -0
  55. data/lib/legion/extensions/agentic/self/identity/helpers/dimensions.rb +75 -0
  56. data/lib/legion/extensions/agentic/self/identity/helpers/fingerprint.rb +170 -0
  57. data/lib/legion/extensions/agentic/self/identity/helpers/graph_client.rb +29 -0
  58. data/lib/legion/extensions/agentic/self/identity/helpers/graph_token.rb +36 -0
  59. data/lib/legion/extensions/agentic/self/identity/helpers/token_cache.rb +59 -0
  60. data/lib/legion/extensions/agentic/self/identity/helpers/vault_secrets.rb +80 -0
  61. data/lib/legion/extensions/agentic/self/identity/local_migrations/20260316000030_create_fingerprint.rb +20 -0
  62. data/lib/legion/extensions/agentic/self/identity/runners/entra.rb +402 -0
  63. data/lib/legion/extensions/agentic/self/identity/runners/identity.rb +90 -0
  64. data/lib/legion/extensions/agentic/self/identity/version.rb +13 -0
  65. data/lib/legion/extensions/agentic/self/identity.rb +28 -0
  66. data/lib/legion/extensions/agentic/self/metacognition/client.rb +27 -0
  67. data/lib/legion/extensions/agentic/self/metacognition/helpers/constants.rb +377 -0
  68. data/lib/legion/extensions/agentic/self/metacognition/helpers/narrator_bridge.rb +85 -0
  69. data/lib/legion/extensions/agentic/self/metacognition/helpers/registry_store.rb +70 -0
  70. data/lib/legion/extensions/agentic/self/metacognition/helpers/self_model.rb +160 -0
  71. data/lib/legion/extensions/agentic/self/metacognition/helpers/snapshot_store.rb +82 -0
  72. data/lib/legion/extensions/agentic/self/metacognition/runners/metacognition.rb +116 -0
  73. data/lib/legion/extensions/agentic/self/metacognition/runners/registry.rb +180 -0
  74. data/lib/legion/extensions/agentic/self/metacognition/version.rb +13 -0
  75. data/lib/legion/extensions/agentic/self/metacognition.rb +22 -0
  76. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/client.rb +25 -0
  77. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/calibration_tracker.rb +96 -0
  78. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/constants.rb +47 -0
  79. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_engine.rb +141 -0
  80. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_judgment.rb +79 -0
  81. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/runners/metacognitive_monitoring.rb +151 -0
  82. data/lib/legion/extensions/agentic/self/metacognitive_monitoring/version.rb +13 -0
  83. data/lib/legion/extensions/agentic/self/metacognitive_monitoring.rb +20 -0
  84. data/lib/legion/extensions/agentic/self/narrative_arc/client.rb +29 -0
  85. data/lib/legion/extensions/agentic/self/narrative_arc/helpers/arc.rb +137 -0
  86. data/lib/legion/extensions/agentic/self/narrative_arc/helpers/arc_engine.rb +119 -0
  87. data/lib/legion/extensions/agentic/self/narrative_arc/helpers/beat_event.rb +59 -0
  88. data/lib/legion/extensions/agentic/self/narrative_arc/helpers/constants.rb +66 -0
  89. data/lib/legion/extensions/agentic/self/narrative_arc/runners/narrative.rb +101 -0
  90. data/lib/legion/extensions/agentic/self/narrative_arc/version.rb +13 -0
  91. data/lib/legion/extensions/agentic/self/narrative_arc.rb +20 -0
  92. data/lib/legion/extensions/agentic/self/narrative_identity/actors/narrative_decay.rb +45 -0
  93. data/lib/legion/extensions/agentic/self/narrative_identity/client.rb +22 -0
  94. data/lib/legion/extensions/agentic/self/narrative_identity/helpers/chapter.rb +48 -0
  95. data/lib/legion/extensions/agentic/self/narrative_identity/helpers/constants.rb +62 -0
  96. data/lib/legion/extensions/agentic/self/narrative_identity/helpers/episode.rb +67 -0
  97. data/lib/legion/extensions/agentic/self/narrative_identity/helpers/narrative_engine.rb +187 -0
  98. data/lib/legion/extensions/agentic/self/narrative_identity/helpers/theme.rb +50 -0
  99. data/lib/legion/extensions/agentic/self/narrative_identity/runners/narrative_identity.rb +158 -0
  100. data/lib/legion/extensions/agentic/self/narrative_identity/version.rb +13 -0
  101. data/lib/legion/extensions/agentic/self/narrative_identity.rb +21 -0
  102. data/lib/legion/extensions/agentic/self/narrative_self/client.rb +27 -0
  103. data/lib/legion/extensions/agentic/self/narrative_self/helpers/autobiography.rb +187 -0
  104. data/lib/legion/extensions/agentic/self/narrative_self/helpers/constants.rb +42 -0
  105. data/lib/legion/extensions/agentic/self/narrative_self/helpers/episode.rb +81 -0
  106. data/lib/legion/extensions/agentic/self/narrative_self/helpers/narrative_thread.rb +65 -0
  107. data/lib/legion/extensions/agentic/self/narrative_self/runners/narrative_self.rb +86 -0
  108. data/lib/legion/extensions/agentic/self/narrative_self/version.rb +13 -0
  109. data/lib/legion/extensions/agentic/self/narrative_self.rb +20 -0
  110. data/lib/legion/extensions/agentic/self/personality/client.rb +21 -0
  111. data/lib/legion/extensions/agentic/self/personality/helpers/constants.rb +84 -0
  112. data/lib/legion/extensions/agentic/self/personality/helpers/personality_store.rb +126 -0
  113. data/lib/legion/extensions/agentic/self/personality/helpers/trait_model.rb +147 -0
  114. data/lib/legion/extensions/agentic/self/personality/runners/personality.rb +102 -0
  115. data/lib/legion/extensions/agentic/self/personality/version.rb +13 -0
  116. data/lib/legion/extensions/agentic/self/personality.rb +19 -0
  117. data/lib/legion/extensions/agentic/self/reflection/client.rb +27 -0
  118. data/lib/legion/extensions/agentic/self/reflection/helpers/constants.rb +66 -0
  119. data/lib/legion/extensions/agentic/self/reflection/helpers/llm_enhancer.rb +166 -0
  120. data/lib/legion/extensions/agentic/self/reflection/helpers/monitors.rb +186 -0
  121. data/lib/legion/extensions/agentic/self/reflection/helpers/reflection.rb +54 -0
  122. data/lib/legion/extensions/agentic/self/reflection/helpers/reflection_store.rb +99 -0
  123. data/lib/legion/extensions/agentic/self/reflection/runners/reflection.rb +199 -0
  124. data/lib/legion/extensions/agentic/self/reflection/version.rb +13 -0
  125. data/lib/legion/extensions/agentic/self/reflection.rb +21 -0
  126. data/lib/legion/extensions/agentic/self/self_model/client.rb +19 -0
  127. data/lib/legion/extensions/agentic/self/self_model/helpers/capability.rb +93 -0
  128. data/lib/legion/extensions/agentic/self/self_model/helpers/constants.rb +46 -0
  129. data/lib/legion/extensions/agentic/self/self_model/helpers/knowledge_domain.rb +82 -0
  130. data/lib/legion/extensions/agentic/self/self_model/helpers/self_model.rb +150 -0
  131. data/lib/legion/extensions/agentic/self/self_model/runners/self_model.rb +82 -0
  132. data/lib/legion/extensions/agentic/self/self_model/version.rb +13 -0
  133. data/lib/legion/extensions/agentic/self/self_model.rb +21 -0
  134. data/lib/legion/extensions/agentic/self/self_talk/actors/volume_decay.rb +45 -0
  135. data/lib/legion/extensions/agentic/self/self_talk/client.rb +30 -0
  136. data/lib/legion/extensions/agentic/self/self_talk/helpers/constants.rb +63 -0
  137. data/lib/legion/extensions/agentic/self/self_talk/helpers/dialogue.rb +114 -0
  138. data/lib/legion/extensions/agentic/self/self_talk/helpers/dialogue_turn.rb +43 -0
  139. data/lib/legion/extensions/agentic/self/self_talk/helpers/inner_voice.rb +77 -0
  140. data/lib/legion/extensions/agentic/self/self_talk/helpers/llm_enhancer.rb +135 -0
  141. data/lib/legion/extensions/agentic/self/self_talk/helpers/self_talk_engine.rb +160 -0
  142. data/lib/legion/extensions/agentic/self/self_talk/runners/self_talk.rb +172 -0
  143. data/lib/legion/extensions/agentic/self/self_talk/version.rb +13 -0
  144. data/lib/legion/extensions/agentic/self/self_talk.rb +22 -0
  145. data/lib/legion/extensions/agentic/self/version.rb +11 -0
  146. data/lib/legion/extensions/agentic/self.rb +33 -0
  147. data/spec/legion/extensions/agentic/self/agency/client_spec.rb +67 -0
  148. data/spec/legion/extensions/agentic/self/agency/helpers/constants_spec.rb +73 -0
  149. data/spec/legion/extensions/agentic/self/agency/helpers/efficacy_model_spec.rb +190 -0
  150. data/spec/legion/extensions/agentic/self/agency/helpers/outcome_event_spec.rb +85 -0
  151. data/spec/legion/extensions/agentic/self/agency/runners/agency_spec.rb +132 -0
  152. data/spec/legion/extensions/agentic/self/anchor/client_spec.rb +30 -0
  153. data/spec/legion/extensions/agentic/self/anchor/helpers/anchor_engine_spec.rb +109 -0
  154. data/spec/legion/extensions/agentic/self/anchor/helpers/anchor_spec.rb +124 -0
  155. data/spec/legion/extensions/agentic/self/anchor/helpers/chain_spec.rb +106 -0
  156. data/spec/legion/extensions/agentic/self/anchor/helpers/constants_spec.rb +53 -0
  157. data/spec/legion/extensions/agentic/self/anchor/runners/cognitive_anchor_spec.rb +70 -0
  158. data/spec/legion/extensions/agentic/self/anosognosia/anosognosia_spec.rb +15 -0
  159. data/spec/legion/extensions/agentic/self/anosognosia/client_spec.rb +50 -0
  160. data/spec/legion/extensions/agentic/self/anosognosia/helpers/anosognosia_engine_spec.rb +266 -0
  161. data/spec/legion/extensions/agentic/self/anosognosia/helpers/cognitive_deficit_spec.rb +150 -0
  162. data/spec/legion/extensions/agentic/self/anosognosia/helpers/constants_spec.rb +58 -0
  163. data/spec/legion/extensions/agentic/self/anosognosia/runners/anosognosia_spec.rb +225 -0
  164. data/spec/legion/extensions/agentic/self/architecture/client_spec.rb +51 -0
  165. data/spec/legion/extensions/agentic/self/architecture/helpers/architecture_engine_spec.rb +321 -0
  166. data/spec/legion/extensions/agentic/self/architecture/helpers/connection_spec.rb +118 -0
  167. data/spec/legion/extensions/agentic/self/architecture/helpers/subsystem_spec.rb +189 -0
  168. data/spec/legion/extensions/agentic/self/architecture/runners/cognitive_architecture_spec.rb +181 -0
  169. data/spec/legion/extensions/agentic/self/default_mode_network/client_spec.rb +69 -0
  170. data/spec/legion/extensions/agentic/self/default_mode_network/helpers/constants_spec.rb +76 -0
  171. data/spec/legion/extensions/agentic/self/default_mode_network/helpers/dmn_engine_spec.rb +321 -0
  172. data/spec/legion/extensions/agentic/self/default_mode_network/helpers/wandering_thought_spec.rb +145 -0
  173. data/spec/legion/extensions/agentic/self/default_mode_network/runners/default_mode_network_spec.rb +269 -0
  174. data/spec/legion/extensions/agentic/self/fingerprint/client_spec.rb +54 -0
  175. data/spec/legion/extensions/agentic/self/fingerprint/helpers/cognitive_trait_spec.rb +180 -0
  176. data/spec/legion/extensions/agentic/self/fingerprint/helpers/constants_spec.rb +108 -0
  177. data/spec/legion/extensions/agentic/self/fingerprint/helpers/fingerprint_engine_spec.rb +318 -0
  178. data/spec/legion/extensions/agentic/self/fingerprint/runners/cognitive_fingerprint_spec.rb +232 -0
  179. data/spec/legion/extensions/agentic/self/identity/actors/orphan_check_spec.rb +104 -0
  180. data/spec/legion/extensions/agentic/self/identity/client_spec.rb +32 -0
  181. data/spec/legion/extensions/agentic/self/identity/helpers/dimensions_spec.rb +51 -0
  182. data/spec/legion/extensions/agentic/self/identity/helpers/fingerprint_spec.rb +66 -0
  183. data/spec/legion/extensions/agentic/self/identity/helpers/graph_client_spec.rb +19 -0
  184. data/spec/legion/extensions/agentic/self/identity/helpers/graph_token_spec.rb +31 -0
  185. data/spec/legion/extensions/agentic/self/identity/helpers/token_cache_spec.rb +50 -0
  186. data/spec/legion/extensions/agentic/self/identity/local_persistence_spec.rb +329 -0
  187. data/spec/legion/extensions/agentic/self/identity/runners/entra_spec.rb +655 -0
  188. data/spec/legion/extensions/agentic/self/identity/runners/identity_spec.rb +61 -0
  189. data/spec/legion/extensions/agentic/self/metacognition/client_spec.rb +20 -0
  190. data/spec/legion/extensions/agentic/self/metacognition/helpers/constants_spec.rb +31 -0
  191. data/spec/legion/extensions/agentic/self/metacognition/helpers/narrator_bridge_spec.rb +102 -0
  192. data/spec/legion/extensions/agentic/self/metacognition/helpers/registry_store_spec.rb +227 -0
  193. data/spec/legion/extensions/agentic/self/metacognition/helpers/self_model_spec.rb +117 -0
  194. data/spec/legion/extensions/agentic/self/metacognition/helpers/snapshot_store_spec.rb +128 -0
  195. data/spec/legion/extensions/agentic/self/metacognition/runners/metacognition_spec.rb +110 -0
  196. data/spec/legion/extensions/agentic/self/metacognition/runners/registry_spec.rb +281 -0
  197. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/client_spec.rb +59 -0
  198. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/calibration_tracker_spec.rb +143 -0
  199. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/constants_spec.rb +91 -0
  200. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_engine_spec.rb +198 -0
  201. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/helpers/monitoring_judgment_spec.rb +172 -0
  202. data/spec/legion/extensions/agentic/self/metacognitive_monitoring/runners/metacognitive_monitoring_spec.rb +244 -0
  203. data/spec/legion/extensions/agentic/self/narrative_arc/client_spec.rb +22 -0
  204. data/spec/legion/extensions/agentic/self/narrative_arc/helpers/arc_engine_spec.rb +183 -0
  205. data/spec/legion/extensions/agentic/self/narrative_arc/helpers/arc_spec.rb +177 -0
  206. data/spec/legion/extensions/agentic/self/narrative_arc/helpers/beat_event_spec.rb +96 -0
  207. data/spec/legion/extensions/agentic/self/narrative_arc/helpers/constants_spec.rb +75 -0
  208. data/spec/legion/extensions/agentic/self/narrative_arc/runners/narrative_spec.rb +142 -0
  209. data/spec/legion/extensions/agentic/self/narrative_identity/client_spec.rb +69 -0
  210. data/spec/legion/extensions/agentic/self/narrative_identity/helpers/chapter_spec.rb +85 -0
  211. data/spec/legion/extensions/agentic/self/narrative_identity/helpers/constants_spec.rb +83 -0
  212. data/spec/legion/extensions/agentic/self/narrative_identity/helpers/episode_spec.rb +180 -0
  213. data/spec/legion/extensions/agentic/self/narrative_identity/helpers/narrative_engine_spec.rb +307 -0
  214. data/spec/legion/extensions/agentic/self/narrative_identity/helpers/theme_spec.rb +107 -0
  215. data/spec/legion/extensions/agentic/self/narrative_identity/runners/narrative_identity_spec.rb +240 -0
  216. data/spec/legion/extensions/agentic/self/narrative_self/client_spec.rb +67 -0
  217. data/spec/legion/extensions/agentic/self/narrative_self/helpers/autobiography_spec.rb +155 -0
  218. data/spec/legion/extensions/agentic/self/narrative_self/helpers/constants_spec.rb +28 -0
  219. data/spec/legion/extensions/agentic/self/narrative_self/helpers/episode_spec.rb +144 -0
  220. data/spec/legion/extensions/agentic/self/narrative_self/helpers/narrative_thread_spec.rb +87 -0
  221. data/spec/legion/extensions/agentic/self/narrative_self/runners/narrative_self_spec.rb +118 -0
  222. data/spec/legion/extensions/agentic/self/personality/client_spec.rb +20 -0
  223. data/spec/legion/extensions/agentic/self/personality/helpers/constants_spec.rb +41 -0
  224. data/spec/legion/extensions/agentic/self/personality/helpers/personality_store_spec.rb +66 -0
  225. data/spec/legion/extensions/agentic/self/personality/helpers/trait_model_spec.rb +148 -0
  226. data/spec/legion/extensions/agentic/self/personality/runners/personality_spec.rb +67 -0
  227. data/spec/legion/extensions/agentic/self/reflection/client_spec.rb +24 -0
  228. data/spec/legion/extensions/agentic/self/reflection/helpers/llm_enhancer_spec.rb +191 -0
  229. data/spec/legion/extensions/agentic/self/reflection/helpers/monitors_spec.rb +120 -0
  230. data/spec/legion/extensions/agentic/self/reflection/helpers/reflection_spec.rb +49 -0
  231. data/spec/legion/extensions/agentic/self/reflection/helpers/reflection_store_spec.rb +93 -0
  232. data/spec/legion/extensions/agentic/self/reflection/runners/reflection_spec.rb +204 -0
  233. data/spec/legion/extensions/agentic/self/self_model/client_spec.rb +55 -0
  234. data/spec/legion/extensions/agentic/self/self_model/helpers/capability_spec.rb +160 -0
  235. data/spec/legion/extensions/agentic/self/self_model/helpers/knowledge_domain_spec.rb +128 -0
  236. data/spec/legion/extensions/agentic/self/self_model/helpers/self_model_spec.rb +238 -0
  237. data/spec/legion/extensions/agentic/self/self_model/runners/self_model_spec.rb +143 -0
  238. data/spec/legion/extensions/agentic/self/self_talk/actors/volume_decay_spec.rb +46 -0
  239. data/spec/legion/extensions/agentic/self/self_talk/client_spec.rb +26 -0
  240. data/spec/legion/extensions/agentic/self/self_talk/helpers/constants_spec.rb +110 -0
  241. data/spec/legion/extensions/agentic/self/self_talk/helpers/dialogue_spec.rb +191 -0
  242. data/spec/legion/extensions/agentic/self/self_talk/helpers/dialogue_turn_spec.rb +78 -0
  243. data/spec/legion/extensions/agentic/self/self_talk/helpers/inner_voice_spec.rb +172 -0
  244. data/spec/legion/extensions/agentic/self/self_talk/helpers/llm_enhancer_spec.rb +206 -0
  245. data/spec/legion/extensions/agentic/self/self_talk/helpers/self_talk_engine_spec.rb +239 -0
  246. data/spec/legion/extensions/agentic/self/self_talk/runners/self_talk_llm_spec.rb +169 -0
  247. data/spec/legion/extensions/agentic/self/self_talk/runners/self_talk_spec.rb +196 -0
  248. data/spec/spec_helper.rb +46 -0
  249. metadata +347 -0
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::Architecture::Runners::CognitiveArchitecture do
4
+ subject(:runner) do
5
+ obj = Object.new
6
+ obj.extend(described_class)
7
+ obj
8
+ end
9
+
10
+ describe '#register_architecture_subsystem' do
11
+ it 'returns registered status and subsystem hash' do
12
+ result = runner.register_architecture_subsystem(name: :perception_hub, subsystem_type: :perception)
13
+ expect(result[:status]).to eq(:registered)
14
+ expect(result[:subsystem][:name]).to eq(:perception_hub)
15
+ end
16
+
17
+ it 'returns error status for duplicate subsystem' do
18
+ runner.register_architecture_subsystem(name: :dup_sub, subsystem_type: :cognition)
19
+ result = runner.register_architecture_subsystem(name: :dup_sub, subsystem_type: :memory)
20
+ expect(result[:status]).to eq(:error)
21
+ expect(result[:message]).to include('already registered')
22
+ end
23
+ end
24
+
25
+ describe '#create_architecture_connection' do
26
+ before do
27
+ runner.register_architecture_subsystem(name: :src, subsystem_type: :cognition)
28
+ runner.register_architecture_subsystem(name: :tgt, subsystem_type: :memory)
29
+ end
30
+
31
+ it 'returns created status and connection hash' do
32
+ result = runner.create_architecture_connection(source_name: :src, target_name: :tgt)
33
+ expect(result[:status]).to eq(:created)
34
+ expect(result[:connection][:connection_type]).to eq(:informational)
35
+ end
36
+
37
+ it 'accepts custom connection_type and weight' do
38
+ result = runner.create_architecture_connection(
39
+ source_name: :src, target_name: :tgt, connection_type: :excitatory, weight: 0.9
40
+ )
41
+ expect(result[:connection][:connection_type]).to eq(:excitatory)
42
+ expect(result[:connection][:weight]).to eq(0.9)
43
+ end
44
+
45
+ it 'returns error for unknown subsystem' do
46
+ result = runner.create_architecture_connection(source_name: :ghost, target_name: :tgt)
47
+ expect(result[:status]).to eq(:error)
48
+ end
49
+ end
50
+
51
+ describe '#activate_architecture_subsystem' do
52
+ before { runner.register_architecture_subsystem(name: :act_sub, subsystem_type: :cognition) }
53
+
54
+ it 'returns activated status' do
55
+ result = runner.activate_architecture_subsystem(name: :act_sub)
56
+ expect(result[:status]).to eq(:activated)
57
+ end
58
+
59
+ it 'returns error for unknown subsystem' do
60
+ result = runner.activate_architecture_subsystem(name: :missing)
61
+ expect(result[:status]).to eq(:error)
62
+ end
63
+ end
64
+
65
+ describe '#degrade_architecture_subsystem' do
66
+ before { runner.register_architecture_subsystem(name: :deg_sub, subsystem_type: :safety) }
67
+
68
+ it 'returns degraded status' do
69
+ result = runner.degrade_architecture_subsystem(name: :deg_sub)
70
+ expect(result[:status]).to eq(:degraded)
71
+ end
72
+
73
+ it 'returns error for unknown subsystem' do
74
+ result = runner.degrade_architecture_subsystem(name: :missing)
75
+ expect(result[:status]).to eq(:error)
76
+ end
77
+ end
78
+
79
+ describe '#subsystem_status_report' do
80
+ before { runner.register_architecture_subsystem(name: :status_sub, subsystem_type: :introspection) }
81
+
82
+ it 'returns subsystem hash' do
83
+ result = runner.subsystem_status_report(name: :status_sub)
84
+ expect(result[:name]).to eq(:status_sub)
85
+ expect(result[:subsystem_type]).to eq(:introspection)
86
+ end
87
+
88
+ it 'returns error hash for unknown subsystem' do
89
+ result = runner.subsystem_status_report(name: :nope)
90
+ expect(result[:status]).to eq(:error)
91
+ end
92
+ end
93
+
94
+ describe '#bottleneck_report' do
95
+ it 'returns a hash with bottlenecked_count and subsystems list' do
96
+ result = runner.bottleneck_report
97
+ expect(result).to include(:bottlenecked_count, :subsystems)
98
+ end
99
+
100
+ it 'returns 0 bottlenecked when subsystems are healthy' do
101
+ runner.register_architecture_subsystem(name: :healthy_sub, subsystem_type: :cognition)
102
+ expect(runner.bottleneck_report[:bottlenecked_count]).to eq(0)
103
+ end
104
+ end
105
+
106
+ describe '#architecture_health_report' do
107
+ it 'returns health, health_label, and active_count' do
108
+ result = runner.architecture_health_report
109
+ expect(result).to include(:health, :health_label, :active_count)
110
+ end
111
+
112
+ it 'reports 0.0 health when no subsystems' do
113
+ expect(runner.architecture_health_report[:health]).to eq(0.0)
114
+ end
115
+
116
+ it 'reports correct health_label' do
117
+ runner.register_architecture_subsystem(name: :good_sub, subsystem_type: :cognition, health: 0.9)
118
+ result = runner.architecture_health_report
119
+ expect(result[:health_label]).to eq(:excellent)
120
+ end
121
+ end
122
+
123
+ describe '#architecture_graph_report' do
124
+ it 'returns nodes and edges' do
125
+ runner.register_architecture_subsystem(name: :graph_sub, subsystem_type: :coordination)
126
+ result = runner.architecture_graph_report
127
+ expect(result).to include(:nodes, :edges)
128
+ expect(result[:nodes].size).to eq(1)
129
+ end
130
+ end
131
+
132
+ describe '#downstream_subsystems' do
133
+ before do
134
+ runner.register_architecture_subsystem(name: :root, subsystem_type: :cognition)
135
+ runner.register_architecture_subsystem(name: :child, subsystem_type: :memory)
136
+ runner.create_architecture_connection(source_name: :root, target_name: :child)
137
+ end
138
+
139
+ it 'returns reachable subsystems' do
140
+ result = runner.downstream_subsystems(name: :root)
141
+ names = result[:reachable].map { |s| s[:name] }
142
+ expect(names).to include(:child)
143
+ end
144
+
145
+ it 'returns count of reachable' do
146
+ result = runner.downstream_subsystems(name: :root)
147
+ expect(result[:count]).to eq(1)
148
+ end
149
+
150
+ it 'returns error for unknown source' do
151
+ result = runner.downstream_subsystems(name: :ghost)
152
+ expect(result[:status]).to eq(:error)
153
+ end
154
+ end
155
+
156
+ describe '#update_cognitive_architecture' do
157
+ it 'returns updated status' do
158
+ result = runner.update_cognitive_architecture
159
+ expect(result[:status]).to eq(:updated)
160
+ expect(result).to include(:decayed)
161
+ end
162
+
163
+ it 'decays subsystems health' do
164
+ runner.register_architecture_subsystem(name: :decay_sub, subsystem_type: :cognition)
165
+ before_health = runner.subsystem_status_report(name: :decay_sub)[:health]
166
+ runner.update_cognitive_architecture
167
+ after_health = runner.subsystem_status_report(name: :decay_sub)[:health]
168
+ expect(after_health).to be < before_health
169
+ end
170
+ end
171
+
172
+ describe '#cognitive_architecture_stats' do
173
+ it 'returns summary stats' do
174
+ runner.register_architecture_subsystem(name: :stats_sub, subsystem_type: :coordination)
175
+ result = runner.cognitive_architecture_stats
176
+ expect(result).to include(:subsystem_count, :connection_count, :active_count,
177
+ :bottlenecked_count, :architecture_health)
178
+ expect(result[:subsystem_count]).to eq(1)
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::DefaultModeNetwork::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'includes Runners::DefaultModeNetwork' do
7
+ expect(described_class.ancestors).to include(
8
+ Legion::Extensions::Agentic::Self::DefaultModeNetwork::Runners::DefaultModeNetwork
9
+ )
10
+ end
11
+
12
+ it 'responds to all runner methods' do
13
+ %i[
14
+ register_external_stimulus
15
+ generate_idle_thought
16
+ trigger_self_reflection
17
+ trigger_social_replay
18
+ trigger_spontaneous_plan
19
+ trigger_wandering
20
+ salient_thoughts
21
+ dmn_mode_status
22
+ update_dmn
23
+ dmn_stats
24
+ ].each do |method|
25
+ expect(client).to respond_to(method)
26
+ end
27
+ end
28
+
29
+ it 'accepts a custom dmn_engine' do
30
+ engine = Legion::Extensions::Agentic::Self::DefaultModeNetwork::Helpers::DmnEngine.new
31
+ c = described_class.new(dmn_engine: engine)
32
+ expect(c).to respond_to(:dmn_stats)
33
+ end
34
+
35
+ describe 'full DMN lifecycle scenario' do
36
+ it 'goes from active through idle and generates thoughts' do
37
+ # Start active — register stimulus
38
+ stimulus_result = client.register_external_stimulus(source: :test_harness)
39
+ expect(stimulus_result[:current_mode]).to eq(:active)
40
+
41
+ # Explicitly trigger various thought types
42
+ self_thought = client.trigger_self_reflection
43
+ social_thought = client.trigger_social_replay(interaction: :pair_programming)
44
+ plan_thought = client.trigger_spontaneous_plan(goal: :optimize_query)
45
+ wander_thought = client.trigger_wandering(seed: :pattern)
46
+
47
+ expect(self_thought[:thought][:thought_type]).to eq(:self_referential)
48
+ expect(social_thought[:thought][:thought_type]).to eq(:social_replay)
49
+ expect(plan_thought[:thought][:thought_type]).to eq(:spontaneous_plan)
50
+ expect(wander_thought[:thought][:thought_type]).to eq(:wandering)
51
+
52
+ # Check salient thoughts includes our injected thoughts
53
+ salient = client.salient_thoughts(count: 10)
54
+ expect(salient[:count]).to eq(4)
55
+
56
+ # Run several update ticks — thoughts decay
57
+ 5.times { client.update_dmn }
58
+
59
+ # Stats should still be coherent
60
+ stats = client.dmn_stats
61
+ expect(stats[:stats][:thought_count]).to be_a(Integer)
62
+
63
+ # Mode status
64
+ status = client.dmn_mode_status
65
+ expect(status[:success]).to be true
66
+ expect(status[:idle_duration]).to be >= 0
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::DefaultModeNetwork::Helpers::Constants do
4
+ subject(:mod) { Class.new { include Legion::Extensions::Agentic::Self::DefaultModeNetwork::Helpers::Constants } }
5
+
6
+ describe 'idle thresholds' do
7
+ it 'IDLE_THRESHOLD is 30' do
8
+ expect(described_module::IDLE_THRESHOLD).to eq(30)
9
+ end
10
+
11
+ it 'DEEP_IDLE_THRESHOLD is 300' do
12
+ expect(described_module::DEEP_IDLE_THRESHOLD).to eq(300)
13
+ end
14
+ end
15
+
16
+ describe 'thought limits' do
17
+ it 'MAX_WANDERING_THOUGHTS is 100' do
18
+ expect(described_module::MAX_WANDERING_THOUGHTS).to eq(100)
19
+ end
20
+
21
+ it 'MAX_THOUGHT_HISTORY is 200' do
22
+ expect(described_module::MAX_THOUGHT_HISTORY).to eq(200)
23
+ end
24
+
25
+ it 'MAX_ASSOCIATION_CHAIN is 5' do
26
+ expect(described_module::MAX_ASSOCIATION_CHAIN).to eq(5)
27
+ end
28
+ end
29
+
30
+ describe 'salience parameters' do
31
+ it 'THOUGHT_SALIENCE_FLOOR is positive' do
32
+ expect(described_module::THOUGHT_SALIENCE_FLOOR).to be > 0
33
+ end
34
+
35
+ it 'DEFAULT_SALIENCE is between floor and 1' do
36
+ expect(described_module::DEFAULT_SALIENCE).to be_between(
37
+ described_module::THOUGHT_SALIENCE_FLOOR, 1.0
38
+ )
39
+ end
40
+ end
41
+
42
+ describe 'ACTIVITY_LABELS' do
43
+ it 'maps all four modes' do
44
+ labels = described_module::ACTIVITY_LABELS
45
+ expect(labels[:active]).to eq(:task_focused)
46
+ expect(labels[:transitioning]).to eq(:shifting)
47
+ expect(labels[:idle]).to eq(:daydreaming)
48
+ expect(labels[:deep_idle]).to eq(:deep_reflection)
49
+ end
50
+
51
+ it 'is frozen' do
52
+ expect(described_module::ACTIVITY_LABELS).to be_frozen
53
+ end
54
+ end
55
+
56
+ describe 'SALIENCE_LABELS' do
57
+ it 'maps ranges to quality labels' do
58
+ labels = described_module::SALIENCE_LABELS
59
+ expect(labels.values).to include(:breakthrough, :significant, :notable, :passing, :fleeting)
60
+ end
61
+
62
+ it 'covers 0.9 as breakthrough' do
63
+ result = described_module::SALIENCE_LABELS.each { |range, lbl| break lbl if range.cover?(0.9) }
64
+ expect(result).to eq(:breakthrough)
65
+ end
66
+
67
+ it 'covers 0.1 as fleeting' do
68
+ result = described_module::SALIENCE_LABELS.each { |range, lbl| break lbl if range.cover?(0.1) }
69
+ expect(result).to eq(:fleeting)
70
+ end
71
+ end
72
+
73
+ def described_module
74
+ Legion::Extensions::Agentic::Self::DefaultModeNetwork::Helpers::Constants
75
+ end
76
+ end
@@ -0,0 +1,321 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Self::DefaultModeNetwork::Helpers::DmnEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:const) { Legion::Extensions::Agentic::Self::DefaultModeNetwork::Helpers::Constants }
7
+
8
+ describe '#initialize' do
9
+ it 'starts in :active mode' do
10
+ expect(engine.mode).to eq(:active)
11
+ end
12
+
13
+ it 'has an empty thought list' do
14
+ expect(engine.thoughts).to be_empty
15
+ end
16
+
17
+ it 'has an empty thought history' do
18
+ expect(engine.thought_history).to be_empty
19
+ end
20
+
21
+ it 'records last_stimulus_at as recent Time' do
22
+ expect(engine.last_stimulus_at).to be_a(Time)
23
+ expect(Time.now.utc - engine.last_stimulus_at).to be < 1
24
+ end
25
+ end
26
+
27
+ describe '#register_stimulus' do
28
+ it 'resets idle timer and returns mode info' do
29
+ result = engine.register_stimulus(source: :api_call)
30
+ expect(result[:current_mode]).to eq(:active)
31
+ expect(result[:source]).to eq(:api_call)
32
+ expect(result).to have_key(:previous_mode)
33
+ end
34
+
35
+ it 'switches mode back to active' do
36
+ allow(engine).to receive(:idle_duration).and_return(60.0)
37
+ engine.tick_mode
38
+ engine.register_stimulus
39
+ expect(engine.mode).to eq(:active)
40
+ end
41
+
42
+ it 'updates last_stimulus_at' do
43
+ before = engine.last_stimulus_at
44
+ sleep(0.01)
45
+ engine.register_stimulus
46
+ expect(engine.last_stimulus_at).to be > before
47
+ end
48
+ end
49
+
50
+ describe '#tick_mode' do
51
+ it 'remains active when recently stimulated' do
52
+ result = engine.tick_mode
53
+ # idle_duration is near 0, mode stays active or becomes transitioning
54
+ expect(%i[active transitioning]).to include(result[:current_mode])
55
+ end
56
+
57
+ it 'transitions to :idle after IDLE_THRESHOLD seconds' do
58
+ allow(engine).to receive(:idle_duration).and_return(const::IDLE_THRESHOLD.to_f + 1)
59
+ result = engine.tick_mode
60
+ expect(result[:current_mode]).to eq(:idle)
61
+ end
62
+
63
+ it 'transitions to :deep_idle after DEEP_IDLE_THRESHOLD seconds' do
64
+ allow(engine).to receive(:idle_duration).and_return(const::DEEP_IDLE_THRESHOLD.to_f + 1)
65
+ result = engine.tick_mode
66
+ expect(result[:current_mode]).to eq(:deep_idle)
67
+ end
68
+
69
+ it 'returns previous and current mode' do
70
+ result = engine.tick_mode
71
+ expect(result).to have_key(:previous_mode)
72
+ expect(result).to have_key(:current_mode)
73
+ end
74
+
75
+ it 'returns idle_duration in result' do
76
+ result = engine.tick_mode
77
+ expect(result[:idle_duration]).to be_a(Float)
78
+ end
79
+ end
80
+
81
+ describe '#idle_duration' do
82
+ it 'returns elapsed seconds since last stimulus' do
83
+ expect(engine.idle_duration).to be >= 0
84
+ expect(engine.idle_duration).to be < 5
85
+ end
86
+ end
87
+
88
+ describe '#generate_thought' do
89
+ it 'returns a WanderingThought' do
90
+ thought = engine.generate_thought
91
+ expect(thought).to be_a(Legion::Extensions::Agentic::Self::DefaultModeNetwork::Helpers::WanderingThought)
92
+ end
93
+
94
+ it 'stores the generated thought' do
95
+ engine.generate_thought
96
+ expect(engine.thought_count).to eq(1)
97
+ end
98
+
99
+ it 'increments thought count each call' do
100
+ 3.times { engine.generate_thought }
101
+ expect(engine.thought_count).to eq(3)
102
+ end
103
+
104
+ it 'generates valid thought types' do
105
+ valid_types = %i[self_referential social_replay spontaneous_plan wandering]
106
+ 10.times do
107
+ t = engine.generate_thought
108
+ expect(valid_types).to include(t.thought_type)
109
+ end
110
+ end
111
+ end
112
+
113
+ describe '#self_reflect' do
114
+ it 'creates a self_referential thought' do
115
+ thought = engine.self_reflect
116
+ expect(thought.thought_type).to eq(:self_referential)
117
+ end
118
+
119
+ it 'sets domain to :self' do
120
+ expect(engine.self_reflect.domain).to eq(:self)
121
+ end
122
+
123
+ it 'builds a non-empty association chain' do
124
+ expect(engine.self_reflect.association_chain).not_to be_empty
125
+ end
126
+
127
+ it 'accepts a custom topic' do
128
+ thought = engine.self_reflect(topic: :values)
129
+ expect(thought.seed).to eq(:values)
130
+ end
131
+
132
+ it 'has positive salience' do
133
+ expect(engine.self_reflect.salience).to be > 0
134
+ end
135
+ end
136
+
137
+ describe '#social_replay' do
138
+ it 'creates a social_replay thought' do
139
+ thought = engine.social_replay(interaction: :recent_chat)
140
+ expect(thought.thought_type).to eq(:social_replay)
141
+ end
142
+
143
+ it 'sets domain to :social' do
144
+ expect(engine.social_replay.domain).to eq(:social)
145
+ end
146
+
147
+ it 'uses provided interaction as seed' do
148
+ thought = engine.social_replay(interaction: :team_standup)
149
+ expect(thought.seed).to eq(:team_standup)
150
+ end
151
+
152
+ it 'uses a default seed when interaction is nil' do
153
+ thought = engine.social_replay
154
+ expect(thought.seed).not_to be_nil
155
+ end
156
+ end
157
+
158
+ describe '#plan_spontaneously' do
159
+ it 'creates a spontaneous_plan thought' do
160
+ thought = engine.plan_spontaneously(goal: :improve_latency)
161
+ expect(thought.thought_type).to eq(:spontaneous_plan)
162
+ end
163
+
164
+ it 'sets domain to :planning' do
165
+ expect(engine.plan_spontaneously.domain).to eq(:planning)
166
+ end
167
+
168
+ it 'uses provided goal as seed' do
169
+ thought = engine.plan_spontaneously(goal: :reduce_errors)
170
+ expect(thought.seed).to eq(:reduce_errors)
171
+ end
172
+
173
+ it 'generates higher salience than wandering (on average)' do
174
+ plans = 20.times.map { engine.plan_spontaneously }
175
+ wanders = 20.times.map { engine.wander }
176
+ expect(plans.sum(&:salience) / plans.size).to be > wanders.sum(&:salience) / wanders.size
177
+ end
178
+ end
179
+
180
+ describe '#wander' do
181
+ it 'creates a wandering thought' do
182
+ thought = engine.wander(seed: :curiosity)
183
+ expect(thought.thought_type).to eq(:wandering)
184
+ end
185
+
186
+ it 'sets domain to :associative' do
187
+ expect(engine.wander.domain).to eq(:associative)
188
+ end
189
+
190
+ it 'uses provided seed' do
191
+ thought = engine.wander(seed: :efficiency)
192
+ expect(thought.seed).to eq(:efficiency)
193
+ end
194
+
195
+ it 'builds an association chain with the seed as first element' do
196
+ thought = engine.wander(seed: :pattern)
197
+ expect(thought.association_chain.first).to eq(:pattern)
198
+ end
199
+
200
+ it 'chain does not exceed MAX_ASSOCIATION_CHAIN' do
201
+ thought = engine.wander(seed: :root)
202
+ expect(thought.association_chain.size).to be <= const::MAX_ASSOCIATION_CHAIN + 1
203
+ end
204
+ end
205
+
206
+ describe '#salient_thoughts' do
207
+ before do
208
+ engine.wander(seed: :a).tap { |t| t.salience = 0.8 }.tap { |t| engine.send(:store_thought, t) }
209
+ engine.wander(seed: :b).tap { |t| t.salience = 0.3 }.tap { |t| engine.send(:store_thought, t) }
210
+ engine.wander(seed: :c).tap { |t| t.salience = 0.9 }.tap { |t| engine.send(:store_thought, t) }
211
+ end
212
+
213
+ it 'returns thoughts sorted by descending salience' do
214
+ thoughts = engine.salient_thoughts(count: 3)
215
+ saliences = thoughts.map(&:salience)
216
+ expect(saliences).to eq(saliences.sort.reverse)
217
+ end
218
+
219
+ it 'limits results by count' do
220
+ expect(engine.salient_thoughts(count: 2).size).to eq(2)
221
+ end
222
+
223
+ it 'returns top thought with highest salience' do
224
+ expect(engine.salient_thoughts(count: 1).first.seed).to eq(:c)
225
+ end
226
+ end
227
+
228
+ describe '#thoughts_of_type' do
229
+ before do
230
+ engine.self_reflect.tap { |t| engine.send(:store_thought, t) }
231
+ engine.social_replay.tap { |t| engine.send(:store_thought, t) }
232
+ engine.wander.tap { |t| engine.send(:store_thought, t) }
233
+ end
234
+
235
+ it 'filters by thought_type' do
236
+ results = engine.thoughts_of_type(type: :self_referential)
237
+ expect(results.all? { |t| t.thought_type == :self_referential }).to be true
238
+ end
239
+
240
+ it 'returns empty array for unknown type' do
241
+ expect(engine.thoughts_of_type(type: :unknown_type)).to be_empty
242
+ end
243
+
244
+ it 'accepts string type and coerces to symbol' do
245
+ results = engine.thoughts_of_type(type: 'wandering')
246
+ expect(results).not_to be_empty
247
+ end
248
+ end
249
+
250
+ describe '#decay_all' do
251
+ it 'decays all stored thoughts' do
252
+ 3.times { engine.generate_thought }
253
+ before_saliences = engine.thoughts.map(&:salience).dup
254
+ engine.decay_all
255
+ # All thoughts should have lower or equal salience
256
+ engine.thoughts.zip(before_saliences).each do |t, b|
257
+ expect(t.salience).to be <= b
258
+ end
259
+ end
260
+
261
+ it 'prunes thoughts that fade below floor' do
262
+ thought = engine.wander(seed: :test)
263
+ thought.salience = const::THOUGHT_SALIENCE_FLOOR + 0.001
264
+ engine.send(:store_thought, thought)
265
+ engine.decay_all
266
+ expect(engine.thoughts).not_to include(thought)
267
+ end
268
+
269
+ it 'archives faded thoughts in thought_history' do
270
+ thought = engine.wander(seed: :archive_me)
271
+ thought.salience = const::THOUGHT_SALIENCE_FLOOR + 0.001
272
+ engine.send(:store_thought, thought)
273
+ engine.decay_all
274
+ ids = engine.thought_history.map { |h| h[:id] }
275
+ expect(ids).to include(thought.id)
276
+ end
277
+
278
+ it 'returns the count of faded thoughts removed' do
279
+ thought = engine.wander(seed: :doomed)
280
+ thought.salience = const::THOUGHT_SALIENCE_FLOOR + 0.001
281
+ engine.send(:store_thought, thought)
282
+ removed = engine.decay_all
283
+ expect(removed).to eq(1)
284
+ end
285
+ end
286
+
287
+ describe '#thought_count' do
288
+ it 'returns 0 initially' do
289
+ expect(engine.thought_count).to eq(0)
290
+ end
291
+
292
+ it 'increments with each stored thought' do
293
+ 5.times { engine.generate_thought }
294
+ expect(engine.thought_count).to eq(5)
295
+ end
296
+ end
297
+
298
+ describe 'thought pruning' do
299
+ it 'does not exceed MAX_WANDERING_THOUGHTS' do
300
+ (const::MAX_WANDERING_THOUGHTS + 10).times { engine.generate_thought }
301
+ expect(engine.thought_count).to be <= const::MAX_WANDERING_THOUGHTS
302
+ end
303
+ end
304
+
305
+ describe '#to_h' do
306
+ it 'returns a hash with expected keys' do
307
+ h = engine.to_h
308
+ expect(h).to include(:mode, :mode_label, :idle_duration, :thought_count, :history_count, :last_stimulus_at)
309
+ end
310
+
311
+ it 'includes the mode label from ACTIVITY_LABELS' do
312
+ h = engine.to_h
313
+ expect(const::ACTIVITY_LABELS.values).to include(h[:mode_label])
314
+ end
315
+
316
+ it 'reflects current thought count' do
317
+ 3.times { engine.generate_thought }
318
+ expect(engine.to_h[:thought_count]).to eq(3)
319
+ end
320
+ end
321
+ end