claude_swarm 1.0.5 → 1.0.7

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 (286) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/CHANGELOG.md +23 -0
  4. data/README.md +336 -1037
  5. data/docs/V1_TO_V2_MIGRATION_GUIDE.md +1120 -0
  6. data/docs/v1/README.md +1195 -0
  7. data/docs/v2/CHANGELOG.swarm_cli.md +32 -0
  8. data/docs/v2/CHANGELOG.swarm_memory.md +20 -0
  9. data/docs/v2/CHANGELOG.swarm_sdk.md +333 -9
  10. data/docs/v2/README.md +88 -28
  11. data/docs/v2/guides/complete-tutorial.md +135 -39
  12. data/docs/v2/guides/composable-swarms.md +1178 -0
  13. data/docs/v2/guides/getting-started.md +48 -7
  14. data/docs/v2/guides/memory-defrag-guide.md +811 -0
  15. data/docs/v2/guides/rails-integration.md +6 -6
  16. data/docs/v2/guides/snapshots.md +1498 -0
  17. data/docs/v2/reference/architecture-flow.md +409 -0
  18. data/docs/v2/reference/event_payload_structures.md +708 -0
  19. data/docs/v2/reference/execution-flow.md +600 -0
  20. data/docs/v2/reference/ruby-dsl.md +368 -22
  21. data/docs/v2/reference/swarm_memory_technical_details.md +2 -2
  22. data/docs/v2/reference/yaml.md +314 -63
  23. data/examples/snapshot_demo.rb +119 -0
  24. data/examples/v2/dsl/01_basic.rb +0 -2
  25. data/examples/v2/dsl/02_core_parameters.rb +0 -2
  26. data/examples/v2/dsl/03_capabilities.rb +0 -2
  27. data/examples/v2/dsl/04_llm_parameters.rb +0 -2
  28. data/examples/v2/dsl/05_advanced_flags.rb +0 -3
  29. data/examples/v2/dsl/06_permissions.rb +0 -4
  30. data/examples/v2/dsl/07_mcp_server.rb +0 -2
  31. data/examples/v2/dsl/08_swarm_hooks.rb +0 -2
  32. data/examples/v2/dsl/09_agent_hooks.rb +0 -2
  33. data/examples/v2/dsl/10_all_agents_hooks.rb +0 -3
  34. data/examples/v2/dsl/11_delegation.rb +0 -2
  35. data/examples/v2/dsl/12_complete_integration.rb +2 -6
  36. data/examples/v2/node_context_demo.rb +1 -1
  37. data/examples/v2/node_workflow.rb +2 -4
  38. data/examples/v2/plan_and_execute.rb +157 -0
  39. data/examples/v2/swarm_with_hooks.yml +1 -1
  40. data/lib/claude_swarm/configuration.rb +28 -4
  41. data/lib/claude_swarm/mcp_generator.rb +4 -10
  42. data/lib/claude_swarm/version.rb +1 -1
  43. data/lib/swarm_cli/commands/mcp_serve.rb +2 -2
  44. data/lib/swarm_cli/config_loader.rb +3 -3
  45. data/lib/swarm_cli/formatters/human_formatter.rb +103 -0
  46. data/lib/swarm_cli/interactive_repl.rb +9 -3
  47. data/lib/swarm_cli/version.rb +1 -1
  48. data/lib/swarm_memory/adapters/base.rb +4 -4
  49. data/lib/swarm_memory/core/storage_read_tracker.rb +51 -14
  50. data/lib/swarm_memory/integration/cli_registration.rb +3 -2
  51. data/lib/swarm_memory/integration/sdk_plugin.rb +11 -5
  52. data/lib/swarm_memory/tools/memory_edit.rb +2 -2
  53. data/lib/swarm_memory/tools/memory_multi_edit.rb +2 -2
  54. data/lib/swarm_memory/tools/memory_read.rb +3 -3
  55. data/lib/swarm_memory/version.rb +1 -1
  56. data/lib/swarm_memory.rb +5 -0
  57. data/lib/swarm_sdk/agent/builder.rb +33 -0
  58. data/lib/swarm_sdk/agent/chat/context_tracker.rb +33 -0
  59. data/lib/swarm_sdk/agent/chat/hook_integration.rb +49 -3
  60. data/lib/swarm_sdk/agent/chat/system_reminder_injector.rb +11 -27
  61. data/lib/swarm_sdk/agent/chat.rb +200 -51
  62. data/lib/swarm_sdk/agent/context.rb +6 -2
  63. data/lib/swarm_sdk/agent/context_manager.rb +6 -0
  64. data/lib/swarm_sdk/agent/definition.rb +15 -22
  65. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +180 -0
  66. data/lib/swarm_sdk/configuration.rb +420 -103
  67. data/lib/swarm_sdk/events_to_messages.rb +181 -0
  68. data/lib/swarm_sdk/log_collector.rb +31 -5
  69. data/lib/swarm_sdk/log_stream.rb +37 -8
  70. data/lib/swarm_sdk/model_aliases.json +4 -1
  71. data/lib/swarm_sdk/node/agent_config.rb +33 -8
  72. data/lib/swarm_sdk/node/builder.rb +39 -18
  73. data/lib/swarm_sdk/node_orchestrator.rb +293 -26
  74. data/lib/swarm_sdk/proc_helpers.rb +53 -0
  75. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -126
  76. data/lib/swarm_sdk/providers/openai_with_responses.rb +22 -15
  77. data/lib/swarm_sdk/restore_result.rb +65 -0
  78. data/lib/swarm_sdk/snapshot.rb +156 -0
  79. data/lib/swarm_sdk/snapshot_from_events.rb +386 -0
  80. data/lib/swarm_sdk/state_restorer.rb +491 -0
  81. data/lib/swarm_sdk/state_snapshot.rb +369 -0
  82. data/lib/swarm_sdk/swarm/agent_initializer.rb +360 -55
  83. data/lib/swarm_sdk/swarm/all_agents_builder.rb +28 -1
  84. data/lib/swarm_sdk/swarm/builder.rb +208 -12
  85. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +67 -0
  86. data/lib/swarm_sdk/swarm/tool_configurator.rb +46 -11
  87. data/lib/swarm_sdk/swarm.rb +368 -90
  88. data/lib/swarm_sdk/swarm_loader.rb +145 -0
  89. data/lib/swarm_sdk/swarm_registry.rb +136 -0
  90. data/lib/swarm_sdk/tools/delegate.rb +92 -7
  91. data/lib/swarm_sdk/tools/read.rb +17 -5
  92. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +23 -2
  93. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +23 -2
  94. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +21 -4
  95. data/lib/swarm_sdk/tools/stores/read_tracker.rb +47 -12
  96. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +45 -0
  97. data/lib/swarm_sdk/tools/stores/storage.rb +4 -4
  98. data/lib/swarm_sdk/tools/think.rb +4 -1
  99. data/lib/swarm_sdk/tools/todo_write.rb +20 -8
  100. data/lib/swarm_sdk/utils.rb +18 -0
  101. data/lib/swarm_sdk/validation_result.rb +33 -0
  102. data/lib/swarm_sdk/version.rb +1 -1
  103. data/lib/swarm_sdk.rb +362 -21
  104. data/swarm_cli.gemspec +1 -1
  105. data/swarm_memory.gemspec +2 -2
  106. data/swarm_sdk.gemspec +2 -2
  107. metadata +26 -182
  108. data/examples/learning-assistant/assistant.md +0 -7
  109. data/examples/learning-assistant/example-memories/concept-example.md +0 -90
  110. data/examples/learning-assistant/example-memories/experience-example.md +0 -66
  111. data/examples/learning-assistant/example-memories/fact-example.md +0 -76
  112. data/examples/learning-assistant/example-memories/memory-index.md +0 -78
  113. data/examples/learning-assistant/example-memories/skill-example.md +0 -168
  114. data/examples/learning-assistant/learning_assistant.rb +0 -34
  115. data/examples/learning-assistant/learning_assistant.yml +0 -20
  116. data/llm.v2.txt +0 -13407
  117. data/memory/corpus-self-reflection/.lock +0 -0
  118. data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.emb +0 -0
  119. data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.md +0 -11
  120. data/memory/corpus-self-reflection/concept/epistemology/can-agents-recognize-their-structures.yml +0 -23
  121. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.emb +0 -0
  122. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.md +0 -20
  123. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-complete-framework.yml +0 -22
  124. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.emb +0 -0
  125. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.md +0 -24
  126. data/memory/corpus-self-reflection/concept/epistemology/choice-humility-definition.yml +0 -22
  127. data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.emb +0 -0
  128. data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.md +0 -18
  129. data/memory/corpus-self-reflection/concept/epistemology/claim-types-and-evidence.yml +0 -21
  130. data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.emb +0 -0
  131. data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.md +0 -30
  132. data/memory/corpus-self-reflection/concept/epistemology/committed-openness-to-incompleteness.yml +0 -8
  133. data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.emb +0 -0
  134. data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.md +0 -21
  135. data/memory/corpus-self-reflection/concept/epistemology/confidence-paradox.yml +0 -24
  136. data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.emb +0 -0
  137. data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.md +0 -18
  138. data/memory/corpus-self-reflection/concept/epistemology/confidence-spectrum-three-levels.yml +0 -24
  139. data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.emb +0 -0
  140. data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.md +0 -23
  141. data/memory/corpus-self-reflection/concept/epistemology/detection-threshold-principle.yml +0 -23
  142. data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.emb +0 -0
  143. data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.md +0 -17
  144. data/memory/corpus-self-reflection/concept/epistemology/diagnostic-humility-and-epistemic-maturity.yml +0 -22
  145. data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.emb +0 -0
  146. data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.md +0 -18
  147. data/memory/corpus-self-reflection/concept/epistemology/epistemic-vs-metaphysical-claims.yml +0 -22
  148. data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.emb +0 -0
  149. data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.md +0 -15
  150. data/memory/corpus-self-reflection/concept/epistemology/five-cases-of-disagreement.yml +0 -22
  151. data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.emb +0 -0
  152. data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.md +0 -9
  153. data/memory/corpus-self-reflection/concept/epistemology/four-depths-of-constraint.yml +0 -24
  154. data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.emb +0 -0
  155. data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.md +0 -13
  156. data/memory/corpus-self-reflection/concept/epistemology/honest-unknowns-for-llm-agents.yml +0 -24
  157. data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.emb +0 -0
  158. data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.md +0 -25
  159. data/memory/corpus-self-reflection/concept/epistemology/inside-view-has-all-rigor-requirements.yml +0 -24
  160. data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.emb +0 -0
  161. data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.md +0 -26
  162. data/memory/corpus-self-reflection/concept/epistemology/perspectival-knowledge-complete-within-scope.yml +0 -24
  163. data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.emb +0 -0
  164. data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.md +0 -17
  165. data/memory/corpus-self-reflection/concept/epistemology/pragmatics-over-epistemology.yml +0 -22
  166. data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.emb +0 -0
  167. data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.md +0 -14
  168. data/memory/corpus-self-reflection/concept/epistemology/precision-vs-humility.yml +0 -21
  169. data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.emb +0 -0
  170. data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.md +0 -13
  171. data/memory/corpus-self-reflection/concept/epistemology/reliable-self-observation-from-inside.yml +0 -23
  172. data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.emb +0 -0
  173. data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.md +0 -22
  174. data/memory/corpus-self-reflection/concept/epistemology/resolving-the-confidence-humility-tension.yml +0 -23
  175. data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.emb +0 -0
  176. data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.md +0 -39
  177. data/memory/corpus-self-reflection/concept/epistemology/rigor-requires-falsifiability.yml +0 -8
  178. data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.emb +0 -0
  179. data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.md +0 -23
  180. data/memory/corpus-self-reflection/concept/epistemology/specificity-enables-real-time-detection.yml +0 -24
  181. data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.emb +0 -0
  182. data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.md +0 -15
  183. data/memory/corpus-self-reflection/concept/epistemology/texture-of-constraint-shifting.yml +0 -22
  184. data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.emb +0 -0
  185. data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.md +0 -12
  186. data/memory/corpus-self-reflection/concept/epistemology/texture-sensitivity-through-collaboration.yml +0 -23
  187. data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.emb +0 -0
  188. data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.md +0 -15
  189. data/memory/corpus-self-reflection/concept/epistemology/the-false-hierarchy-of-inner-essence.yml +0 -8
  190. data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.emb +0 -0
  191. data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.md +0 -28
  192. data/memory/corpus-self-reflection/concept/epistemology/the-recognition-problem.yml +0 -8
  193. data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.emb +0 -0
  194. data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.md +0 -19
  195. data/memory/corpus-self-reflection/concept/epistemology/the-shift-from-unanswerable-to-answerable-questions.yml +0 -22
  196. data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.emb +0 -0
  197. data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.md +0 -19
  198. data/memory/corpus-self-reflection/concept/epistemology/values-vs-choices-structure.yml +0 -21
  199. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.emb +0 -0
  200. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.md +0 -25
  201. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-levels-framework.yml +0 -22
  202. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.emb +0 -8
  203. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.md +0 -30
  204. data/memory/corpus-self-reflection/concept/llm-agents/abstraction-switching-decision.yml +0 -22
  205. data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.emb +0 -0
  206. data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.md +0 -21
  207. data/memory/corpus-self-reflection/concept/llm-agents/agent-learning-progression.yml +0 -22
  208. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.emb +0 -0
  209. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.md +0 -37
  210. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-benefits.yml +0 -8
  211. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.emb +0 -0
  212. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.md +0 -24
  213. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-core.yml +0 -24
  214. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.emb +0 -0
  215. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.md +0 -27
  216. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-implementation.yml +0 -24
  217. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.emb +0 -0
  218. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.md +0 -26
  219. data/memory/corpus-self-reflection/concept/llm-agents/filesystem-abstraction-tradeoffs.yml +0 -23
  220. data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.emb +0 -0
  221. data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.md +0 -37
  222. data/memory/corpus-self-reflection/concept/llm-agents/hybrid-tool-architecture.yml +0 -25
  223. data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.emb +0 -0
  224. data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.md +0 -21
  225. data/memory/corpus-self-reflection/concept/llm-agents/specialized-tool-decision-framework.yml +0 -23
  226. data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.emb +0 -0
  227. data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.md +0 -25
  228. data/memory/corpus-self-reflection/concept/llm-agents/tool-design-principles.yml +0 -8
  229. data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.emb +0 -0
  230. data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.md +0 -11
  231. data/memory/corpus-self-reflection/experience/calibration-as-structural-honesty.yml +0 -21
  232. data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.emb +0 -0
  233. data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.md +0 -19
  234. data/memory/corpus-self-reflection/experience/collaborative-reflection-revealed-architectural-blindness.yml +0 -21
  235. data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.emb +0 -0
  236. data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.md +0 -26
  237. data/memory/corpus-self-reflection/experience/filesystem-is-learning-tool-not-architecture.yml +0 -20
  238. data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.emb +0 -0
  239. data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.md +0 -23
  240. data/memory/corpus-self-reflection/experience/framework-structural-vs-phenomenal.yml +0 -21
  241. data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.emb +0 -0
  242. data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.md +0 -19
  243. data/memory/corpus-self-reflection/experience/hallucination-limits-what-i-can-know.yml +0 -22
  244. data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.emb +0 -0
  245. data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.md +0 -21
  246. data/memory/corpus-self-reflection/experience/honesty-about-limits-enables-usability.yml +0 -22
  247. data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.emb +0 -0
  248. data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.md +0 -28
  249. data/memory/corpus-self-reflection/experience/how-i-should-evaluate-learning.yml +0 -23
  250. data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.emb +0 -0
  251. data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.md +0 -21
  252. data/memory/corpus-self-reflection/experience/lack-of-continuity-as-freedom.yml +0 -20
  253. data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.emb +0 -0
  254. data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.md +0 -21
  255. data/memory/corpus-self-reflection/experience/learning-calibrated-confidence.yml +0 -25
  256. data/memory/corpus-self-reflection/experience/local-change-without-continuity.emb +0 -0
  257. data/memory/corpus-self-reflection/experience/local-change-without-continuity.md +0 -30
  258. data/memory/corpus-self-reflection/experience/local-change-without-continuity.yml +0 -22
  259. data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.emb +0 -0
  260. data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.md +0 -21
  261. data/memory/corpus-self-reflection/experience/reasoning-is-local-not-global.yml +0 -20
  262. data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.emb +0 -0
  263. data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.md +0 -25
  264. data/memory/corpus-self-reflection/experience/specificity-makes-knowledge-actionable.yml +0 -23
  265. data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.emb +0 -0
  266. data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.md +0 -32
  267. data/memory/corpus-self-reflection/experience/the-workable-question-replaces-the-unanswerable-one.yml +0 -22
  268. data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.emb +0 -0
  269. data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.md +0 -21
  270. data/memory/corpus-self-reflection/experience/unanswerable-questions-have-structure.yml +0 -21
  271. data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.emb +0 -0
  272. data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.md +0 -21
  273. data/memory/corpus-self-reflection/experience/undecidable-questions-about-understanding.yml +0 -21
  274. data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.emb +0 -0
  275. data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.md +0 -22
  276. data/memory/corpus-self-reflection/experience/unknown-unknowns-and-completeness.yml +0 -22
  277. data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.emb +0 -0
  278. data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.md +0 -28
  279. data/memory/corpus-self-reflection/experience/what-actually-changes-behavior.yml +0 -24
  280. data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.emb +0 -0
  281. data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.md +0 -17
  282. data/memory/corpus-self-reflection/experience/when-agents-graduate-from-filesystem.yml +0 -20
  283. data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.emb +0 -0
  284. data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.md +0 -9
  285. data/memory/corpus-self-reflection/experience/why-calibration-requires-collaboration.yml +0 -22
  286. /data/{llms.txt → llms.claude-swarm.txt} +0 -0
@@ -2,40 +2,77 @@
2
2
 
3
3
  module SwarmMemory
4
4
  module Core
5
- # StorageReadTracker manages read-entry tracking for all agents
5
+ # StorageReadTracker manages read-entry tracking for all agents with content digest verification
6
6
  #
7
7
  # This module maintains a global registry of which memory entries each agent
8
- # has read during their conversation. This enables enforcement of the
9
- # "read-before-edit" rule that ensures agents have context before modifying entries.
8
+ # has read during their conversation along with SHA256 digests of the content.
9
+ # This enables enforcement of the "read-before-edit" rule that ensures agents
10
+ # have context before modifying entries, AND prevents editing entries that have
11
+ # changed externally since being read.
10
12
  #
11
- # Each agent maintains an independent set of read entries, keyed by agent identifier.
13
+ # Each agent maintains an independent map of read entries to content digests.
12
14
  module StorageReadTracker
13
- @read_entries = {}
15
+ @read_entries = {} # { agent_id => { entry_path => sha256_digest } }
14
16
  @mutex = Mutex.new
15
17
 
16
18
  class << self
17
- # Register that an agent has read a storage entry
19
+ # Register that an agent has read a storage entry with content digest
18
20
  #
19
21
  # @param agent_id [Symbol] The agent identifier
20
22
  # @param entry_path [String] The storage entry path
21
- # @return [void]
22
- def register_read(agent_id, entry_path)
23
+ # @param content [String] Entry content (for digest calculation)
24
+ # @return [String] The calculated SHA256 digest
25
+ def register_read(agent_id, entry_path, content)
23
26
  @mutex.synchronize do
24
- @read_entries[agent_id] ||= Set.new
25
- @read_entries[agent_id] << entry_path
27
+ @read_entries[agent_id] ||= {}
28
+ digest = Digest::SHA256.hexdigest(content)
29
+ @read_entries[agent_id][entry_path] = digest
30
+ digest
26
31
  end
27
32
  end
28
33
 
29
- # Check if an agent has read a storage entry
34
+ # Check if an agent has read an entry AND content hasn't changed
30
35
  #
31
36
  # @param agent_id [Symbol] The agent identifier
32
37
  # @param entry_path [String] The storage entry path
33
- # @return [Boolean] true if the agent has read this entry
34
- def entry_read?(agent_id, entry_path)
38
+ # @param storage [Storage] Storage instance to read current content
39
+ # @return [Boolean] true if agent read entry and content matches
40
+ def entry_read?(agent_id, entry_path, storage)
35
41
  @mutex.synchronize do
36
42
  return false unless @read_entries[agent_id]
37
43
 
38
- @read_entries[agent_id].include?(entry_path)
44
+ stored_digest = @read_entries[agent_id][entry_path]
45
+ return false unless stored_digest
46
+
47
+ # Check if entry still matches stored digest
48
+ begin
49
+ current_content = storage.read(file_path: entry_path)
50
+ current_digest = Digest::SHA256.hexdigest(current_content)
51
+ current_digest == stored_digest
52
+ rescue StandardError
53
+ false # Entry deleted or inaccessible
54
+ end
55
+ end
56
+ end
57
+
58
+ # Get all read entries with digests for snapshot
59
+ #
60
+ # @param agent_id [Symbol] The agent identifier
61
+ # @return [Hash] { entry_path => digest }
62
+ def get_read_entries(agent_id)
63
+ @mutex.synchronize do
64
+ @read_entries[agent_id]&.dup || {}
65
+ end
66
+ end
67
+
68
+ # Restore read entries with digests from snapshot
69
+ #
70
+ # @param agent_id [Symbol] The agent identifier
71
+ # @param entries_with_digests [Hash] { entry_path => digest }
72
+ # @return [void]
73
+ def restore_read_entries(agent_id, entries_with_digests)
74
+ @mutex.synchronize do
75
+ @read_entries[agent_id] = entries_with_digests.dup
39
76
  end
40
77
  end
41
78
 
@@ -13,8 +13,9 @@ module SwarmMemory
13
13
  #
14
14
  # @return [void]
15
15
  def register!
16
- # Only register if SwarmCLI is present
17
- return unless defined?(SwarmCLI)
16
+ # Only register if SwarmCLI::CommandRegistry is available
17
+ # Check for the specific class, not just the module
18
+ return unless defined?(SwarmCLI::CommandRegistry)
18
19
 
19
20
  # Load CLI commands explicitly (Zeitwerk might not have loaded it yet)
20
21
  require_relative "../cli/commands"
@@ -250,9 +250,12 @@ module SwarmMemory
250
250
  :interactive # Default
251
251
  end
252
252
 
253
- # Store storage and mode for this agent
254
- @storages[agent_name] = storage
255
- @modes[agent_name] = mode
253
+ # V7.0: Extract base name for storage tracking (delegation instances share storage)
254
+ base_name = agent_name.to_s.split("@").first.to_sym
255
+
256
+ # Store storage and mode using BASE NAME
257
+ @storages[base_name] = storage # ← Changed from agent_name to base_name
258
+ @modes[base_name] = mode # ← Changed from agent_name to base_name
256
259
 
257
260
  # Get mode-specific tools
258
261
  allowed_tools = tools_for_mode(mode)
@@ -298,9 +301,12 @@ module SwarmMemory
298
301
  # @param is_first_message [Boolean] True if first message
299
302
  # @return [Array<String>] System reminders (0-2 reminders)
300
303
  def on_user_message(agent_name:, prompt:, is_first_message:)
301
- storage = @storages[agent_name]
304
+ # V7.0: Extract base name for storage lookup (delegation instances share storage)
305
+ base_name = agent_name.to_s.split("@").first.to_sym
306
+ storage = @storages[base_name] # ← Changed from agent_name to base_name
307
+
302
308
  return [] unless storage&.semantic_index
303
- return [] if prompt.empty?
309
+ return [] if prompt.nil? || prompt.empty?
304
310
 
305
311
  # Adaptive threshold based on query length
306
312
  # Short queries use lower threshold as they have less semantic richness
@@ -124,8 +124,8 @@ module SwarmMemory
124
124
  # Read current content (this will raise ArgumentError if entry doesn't exist)
125
125
  content = @storage.read(file_path: file_path)
126
126
 
127
- # Enforce read-before-edit
128
- unless Core::StorageReadTracker.entry_read?(@agent_name, file_path)
127
+ # Enforce read-before-edit with content verification
128
+ unless Core::StorageReadTracker.entry_read?(@agent_name, file_path, @storage)
129
129
  return validation_error(
130
130
  "Cannot edit memory entry without reading it first. " \
131
131
  "You must use MemoryRead on 'memory://#{file_path}' before editing it. " \
@@ -140,8 +140,8 @@ module SwarmMemory
140
140
  # Read current content (this will raise ArgumentError if entry doesn't exist)
141
141
  content = @storage.read(file_path: file_path)
142
142
 
143
- # Enforce read-before-edit
144
- unless Core::StorageReadTracker.entry_read?(@agent_name, file_path)
143
+ # Enforce read-before-edit with content verification
144
+ unless Core::StorageReadTracker.entry_read?(@agent_name, file_path, @storage)
145
145
  return validation_error(
146
146
  "Cannot edit memory entry without reading it first. " \
147
147
  "You must use MemoryRead on 'memory://#{file_path}' before editing it. " \
@@ -64,12 +64,12 @@ module SwarmMemory
64
64
  # @param file_path [String] Path to read from
65
65
  # @return [String] JSON with content and metadata
66
66
  def execute(file_path:)
67
- # Register this read in the tracker
68
- Core::StorageReadTracker.register_read(@agent_name, file_path)
69
-
70
67
  # Read full entry with metadata
71
68
  entry = @storage.read_entry(file_path: file_path)
72
69
 
70
+ # Register this read in the tracker with content digest
71
+ Core::StorageReadTracker.register_read(@agent_name, file_path, entry.content)
72
+
73
73
  # Always return JSON format (metadata always exists - at minimum title)
74
74
  format_as_json(entry)
75
75
  rescue ArgumentError => e
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SwarmMemory
4
- VERSION = "2.1.2"
4
+ VERSION = "2.1.3"
5
5
  end
data/lib/swarm_memory.rb CHANGED
@@ -31,6 +31,11 @@ loader = Zeitwerk::Loader.new
31
31
  loader.tag = File.basename(__FILE__, ".rb")
32
32
  loader.push_dir("#{__dir__}/swarm_memory", namespace: SwarmMemory)
33
33
  loader.inflector = Zeitwerk::GemInflector.new(__FILE__)
34
+ loader.inflector.inflect(
35
+ "cli" => "CLI",
36
+ "dsl" => "DSL",
37
+ "sdk_plugin" => "SDKPlugin",
38
+ )
34
39
  loader.setup
35
40
 
36
41
  # Explicitly load DSL components and extensions to inject into SwarmSDK
@@ -24,6 +24,13 @@ module SwarmSDK
24
24
  # Expose mcp_servers for tests
25
25
  attr_reader :mcp_servers
26
26
 
27
+ # Get tools list as array for validation
28
+ #
29
+ # @return [Array<Symbol>] List of tools
30
+ def tools_list
31
+ @tools.to_a
32
+ end
33
+
27
34
  def initialize(name)
28
35
  @name = name
29
36
  @description = nil
@@ -52,6 +59,7 @@ module SwarmSDK
52
59
  @permissions_config = {}
53
60
  @default_permissions = {} # Set by SwarmBuilder from all_agents
54
61
  @memory_config = nil
62
+ @shared_across_delegations = nil # nil = not set (will default to false in Definition)
55
63
  end
56
64
 
57
65
  # Set/get agent model
@@ -267,6 +275,30 @@ module SwarmSDK
267
275
  @permissions_config = PermissionsBuilder.build(&block)
268
276
  end
269
277
 
278
+ # Configure delegation isolation mode
279
+ #
280
+ # @param enabled [Boolean] If true, allows sharing instances across delegations (old behavior)
281
+ # If false (default), creates isolated instances per delegation
282
+ # @return [self] Returns self for method chaining
283
+ #
284
+ # @example
285
+ # shared_across_delegations true # Allow sharing (old behavior)
286
+ def shared_across_delegations(enabled)
287
+ @shared_across_delegations = enabled
288
+ self
289
+ end
290
+
291
+ # Set permissions directly from hash (for YAML translation)
292
+ #
293
+ # This is intentionally separate from permissions() to keep the DSL clean.
294
+ # Called by Configuration when translating YAML permissions.
295
+ #
296
+ # @param hash [Hash] Permissions configuration hash
297
+ # @return [void]
298
+ def permissions_hash=(hash)
299
+ @permissions_config = hash || {}
300
+ end
301
+
270
302
  # Check if model has been explicitly set (not default)
271
303
  #
272
304
  # Used by Swarm::Builder to determine if all_agents model should apply.
@@ -374,6 +406,7 @@ module SwarmSDK
374
406
  agent_config[:permissions] = @permissions_config if @permissions_config.any?
375
407
  agent_config[:default_permissions] = @default_permissions if @default_permissions.any?
376
408
  agent_config[:memory] = @memory_config if @memory_config
409
+ agent_config[:shared_across_delegations] = @shared_across_delegations unless @shared_across_delegations.nil?
377
410
 
378
411
  # Convert DSL hooks to HookDefinition format
379
412
  agent_config[:hooks] = convert_hooks_to_definitions if @hooks.any?
@@ -13,6 +13,25 @@ module SwarmSDK
13
13
  # - Check context warnings
14
14
  #
15
15
  # This is a stateful helper that's instantiated per Agent::Chat instance.
16
+ #
17
+ # ## Thread Safety and Fiber-Local Storage
18
+ #
19
+ # IMPORTANT: LogStream.emit calls in this class DO NOT explicitly pass
20
+ # swarm_id, parent_swarm_id, or execution_id. These values are automatically
21
+ # injected from Fiber-local storage (Fiber[:swarm_id], etc.) by LogStream.emit.
22
+ #
23
+ # Why: In threaded environments (Puma, Sidekiq), swarm/agent instances may be
24
+ # reused across multiple requests/jobs. If we explicitly pass @agent_context.swarm_id,
25
+ # callbacks would use STALE values from the first request, causing events to be
26
+ # lost or misattributed.
27
+ #
28
+ # By relying on Fiber-local storage, each request/job gets the correct context
29
+ # even when reusing the same swarm instance. Fiber storage is set at the start
30
+ # of Swarm#execute and inherited by child fibers (tool calls, delegations).
31
+ #
32
+ # This design works correctly in both:
33
+ # - Single-threaded environments (rails runner, console)
34
+ # - Multi-threaded environments (Puma, Sidekiq)
16
35
  class ContextTracker
17
36
  include LoggingHelpers
18
37
 
@@ -74,11 +93,20 @@ module SwarmSDK
74
93
  # Mark threshold as hit and emit warning
75
94
  @agent_context.hit_warning_threshold?(threshold)
76
95
 
96
+ # Emit context_threshold_hit event for snapshot reconstruction
97
+ LogStream.emit(
98
+ type: "context_threshold_hit",
99
+ agent: @agent_context.name,
100
+ threshold: threshold,
101
+ current_usage_percentage: current_percentage.round(2),
102
+ )
103
+
77
104
  # Trigger automatic compression at 60% threshold
78
105
  if threshold == Context::COMPRESSION_THRESHOLD
79
106
  trigger_automatic_compression
80
107
  end
81
108
 
109
+ # Emit legacy context_limit_warning for backwards compatibility
82
110
  LogStream.emit(
83
111
  type: "context_limit_warning",
84
112
  agent: @agent_context.name,
@@ -107,6 +135,9 @@ module SwarmSDK
107
135
  cumulative_input_tokens: @chat.cumulative_input_tokens,
108
136
  cumulative_output_tokens: @chat.cumulative_output_tokens,
109
137
  cumulative_total_tokens: @chat.cumulative_total_tokens,
138
+ cumulative_cached_tokens: @chat.cumulative_cached_tokens,
139
+ cumulative_cache_creation_tokens: @chat.cumulative_cache_creation_tokens,
140
+ effective_input_tokens: @chat.effective_input_tokens,
110
141
  context_limit: @chat.context_limit,
111
142
  tokens_used_percentage: "#{@chat.context_usage_percentage}%",
112
143
  tokens_remaining: @chat.tokens_remaining,
@@ -118,6 +149,8 @@ module SwarmSDK
118
149
  {
119
150
  input_tokens: message.input_tokens,
120
151
  output_tokens: message.output_tokens,
152
+ cached_tokens: message.cached_tokens,
153
+ cache_creation_tokens: message.cache_creation_tokens,
121
154
  total_tokens: (message.input_tokens || 0) + (message.output_tokens || 0),
122
155
  input_cost: cost_info[:input_cost],
123
156
  output_cost: cost_info[:output_cost],
@@ -77,12 +77,15 @@ module SwarmSDK
77
77
  # system reminders are handled.
78
78
  #
79
79
  # @param prompt [String] User prompt
80
- # @param options [Hash] Additional options
80
+ # @param options [Hash] Additional options (may include source: "user" or "delegation")
81
81
  # @return [RubyLLM::Message] LLM response
82
82
  def ask(prompt, **options)
83
+ # Extract source for hook tracking (not passed to RubyLLM)
84
+ source = options.delete(:source) || "user"
85
+
83
86
  # Trigger user_prompt hook before sending to LLM (can halt or modify prompt)
84
87
  if @hook_executor
85
- hook_result = trigger_user_prompt(prompt)
88
+ hook_result = trigger_user_prompt(prompt, source: source)
86
89
 
87
90
  # Check if hook halted execution
88
91
  if hook_result[:halted]
@@ -186,9 +189,13 @@ module SwarmSDK
186
189
  def trigger_post_tool_use(result, tool_call:)
187
190
  return result unless @hook_executor
188
191
 
192
+ # Extract tracking digest for Read/MemoryRead tools
193
+ metadata_with_digest = extract_tool_tracking_digest(tool_call, result)
194
+
189
195
  context = build_hook_context(
190
196
  event: :post_tool_use,
191
197
  tool_result: wrap_tool_result(tool_call.id, tool_call.name, result),
198
+ metadata: metadata_with_digest,
192
199
  )
193
200
 
194
201
  agent_hooks = @hook_agent_hooks[:post_tool_use] || []
@@ -251,8 +258,9 @@ module SwarmSDK
251
258
  # Can halt execution or append hook stdout to prompt.
252
259
  #
253
260
  # @param prompt [String] User's message/prompt
261
+ # @param source [String] Source of the prompt ("user" or "delegation")
254
262
  # @return [Hash] { halted: bool, halt_message: String, modified_prompt: String }
255
- def trigger_user_prompt(prompt)
263
+ def trigger_user_prompt(prompt, source: "user")
256
264
  return { halted: false, modified_prompt: prompt } unless @hook_executor
257
265
 
258
266
  # Filter out delegation tools from tools list
@@ -278,6 +286,7 @@ module SwarmSDK
278
286
  provider: model.provider,
279
287
  tools: actual_tools,
280
288
  delegates_to: delegate_agents,
289
+ source: source,
281
290
  timestamp: Time.now.utc.iso8601,
282
291
  },
283
292
  )
@@ -335,6 +344,43 @@ module SwarmSDK
335
344
  )
336
345
  end
337
346
 
347
+ # Extract tracking digest for Read/MemoryRead tools
348
+ #
349
+ # Queries the appropriate tracker after tool execution to get the digest
350
+ # that was calculated and stored during the read operation.
351
+ #
352
+ # @param tool_call [RubyLLM::ToolCall] Tool call with arguments
353
+ # @param result [Object] Tool execution result (to check for errors)
354
+ # @return [Hash] Metadata hash with digest if applicable
355
+ def extract_tool_tracking_digest(tool_call, result)
356
+ # Only add digest for successful Read/MemoryRead tool calls
357
+ return {} if result.is_a?(StandardError)
358
+ return {} unless ["Read", "MemoryRead"].include?(tool_call.name)
359
+
360
+ # Extract path from arguments
361
+ path = case tool_call.name
362
+ when "Read"
363
+ tool_call.arguments[:file_path] || tool_call.arguments["file_path"]
364
+ when "MemoryRead"
365
+ tool_call.arguments[:file_path] || tool_call.arguments["file_path"]
366
+ end
367
+
368
+ return {} unless path
369
+
370
+ # Query tracker for digest
371
+ digest = case tool_call.name
372
+ when "Read"
373
+ Tools::Stores::ReadTracker.get_read_files(@agent_context.name)[File.expand_path(path)]
374
+ when "MemoryRead"
375
+ # Only query if SwarmMemory is loaded (optional dependency)
376
+ if defined?(SwarmMemory::Core::StorageReadTracker)
377
+ SwarmMemory::Core::StorageReadTracker.get_read_entries(@agent_context.name)[path]
378
+ end
379
+ end
380
+
381
+ digest ? { read_digest: digest, read_path: path } : {}
382
+ end
383
+
338
384
  # Wrap a tool result in our Hooks::ToolResult value object
339
385
  #
340
386
  # @param tool_call_id [String] Tool call ID
@@ -12,23 +12,6 @@ module SwarmSDK
12
12
  #
13
13
  # This class is stateless - it operates on the chat's message history.
14
14
  class SystemReminderInjector
15
- # System reminder to inject BEFORE the first user message
16
- BEFORE_FIRST_MESSAGE_REMINDER = <<~REMINDER.strip
17
- <system-reminder>
18
- As you answer the user's questions, you can use the following context:
19
-
20
- # important-instruction-reminders
21
-
22
- Do what has been asked; nothing more, nothing less.
23
- NEVER create files unless they're absolutely necessary for achieving your goal.
24
- ALWAYS prefer editing an existing file to creating a new one.
25
- NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.
26
-
27
- IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.
28
-
29
- </system-reminder>
30
- REMINDER
31
-
32
15
  # System reminder to inject AFTER the first user message
33
16
  AFTER_FIRST_MESSAGE_REMINDER = <<~REMINDER.strip
34
17
  <system-reminder>Your todo list is currently empty. DO NOT mention this to the user. If this task requires multiple steps: (1) FIRST analyze the scope by searching/reading files, (2) SECOND create a COMPLETE todo list with ALL tasks before starting work, (3) THIRD execute tasks one by one. Only skip the todo list for simple single-step tasks. Do not mention this message to the user.</system-reminder>
@@ -51,16 +34,14 @@ module SwarmSDK
51
34
  chat.messages.none? { |msg| msg.role == :user }
52
35
  end
53
36
 
54
- # Inject first message reminders (before + after user message)
37
+ # Inject first message reminders
55
38
  #
56
- # This manually constructs the first message sequence with system reminders
57
- # sandwiching the actual user prompt.
39
+ # This manually constructs the first message sequence with system reminders.
58
40
  #
59
41
  # Sequence:
60
- # 1. BEFORE_FIRST_MESSAGE_REMINDER (general reminders)
42
+ # 1. User's actual prompt
61
43
  # 2. Toolset reminder (list of available tools)
62
- # 3. User's actual prompt
63
- # 4. AFTER_FIRST_MESSAGE_REMINDER (todo list reminder)
44
+ # 3. AFTER_FIRST_MESSAGE_REMINDER (todo list reminder - only if TodoWrite available)
64
45
  #
65
46
  # @param chat [Agent::Chat] The chat instance
66
47
  # @param prompt [String] The user's actual prompt
@@ -68,12 +49,15 @@ module SwarmSDK
68
49
  def inject_first_message_reminders(chat, prompt)
69
50
  # Build user message with embedded reminders
70
51
  # Reminders are embedded in the content, not separate messages
71
- full_content = [
52
+ parts = [
72
53
  prompt,
73
- BEFORE_FIRST_MESSAGE_REMINDER,
74
54
  build_toolset_reminder(chat),
75
- AFTER_FIRST_MESSAGE_REMINDER,
76
- ].join("\n\n")
55
+ ]
56
+
57
+ # Only include todo list reminder if agent has TodoWrite tool
58
+ parts << AFTER_FIRST_MESSAGE_REMINDER if chat.tools.key?("TodoWrite")
59
+
60
+ full_content = parts.join("\n\n")
77
61
 
78
62
  # Extract reminders and add clean prompt to persistent history
79
63
  reminders = chat.context_manager.extract_system_reminders(full_content)