swarm_sdk 2.7.14 → 3.0.0.alpha2

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +16 -0
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +4 -1
  4. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  5. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  6. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  7. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  8. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  9. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  10. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  11. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  12. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  13. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  14. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  15. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  16. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  17. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  18. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  19. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  20. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  24. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  25. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  26. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  27. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  28. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  29. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  30. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  31. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  32. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  33. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  34. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  35. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  36. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  37. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  38. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  39. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  40. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  41. data/lib/swarm_sdk/v3/tools/document_converters/base.rb +84 -0
  42. data/lib/swarm_sdk/v3/tools/document_converters/docx_converter.rb +120 -0
  43. data/lib/swarm_sdk/v3/tools/document_converters/pdf_converter.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/document_converters/xlsx_converter.rb +128 -0
  45. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  46. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  47. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  48. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  49. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  50. data/lib/swarm_sdk/v3/tools/read.rb +213 -0
  51. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  52. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  53. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  54. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  55. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  56. data/lib/swarm_sdk/v3.rb +145 -0
  57. metadata +88 -149
  58. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  59. data/lib/swarm_sdk/agent/builder.rb +0 -705
  60. data/lib/swarm_sdk/agent/chat.rb +0 -1438
  61. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  62. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  63. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  64. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  65. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  66. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  67. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  68. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  69. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  70. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  71. data/lib/swarm_sdk/agent/context.rb +0 -115
  72. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  73. data/lib/swarm_sdk/agent/definition.rb +0 -588
  74. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  75. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -173
  76. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  77. data/lib/swarm_sdk/agent_registry.rb +0 -146
  78. data/lib/swarm_sdk/builders/base_builder.rb +0 -558
  79. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  80. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -42
  81. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  82. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  83. data/lib/swarm_sdk/config.rb +0 -368
  84. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  85. data/lib/swarm_sdk/configuration/translator.rb +0 -285
  86. data/lib/swarm_sdk/configuration.rb +0 -165
  87. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  88. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  89. data/lib/swarm_sdk/context_compactor.rb +0 -335
  90. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  91. data/lib/swarm_sdk/context_management/context.rb +0 -328
  92. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  93. data/lib/swarm_sdk/defaults.rb +0 -251
  94. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  95. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  96. data/lib/swarm_sdk/hooks/context.rb +0 -197
  97. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  98. data/lib/swarm_sdk/hooks/error.rb +0 -29
  99. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  100. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  101. data/lib/swarm_sdk/hooks/result.rb +0 -150
  102. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  103. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  104. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  105. data/lib/swarm_sdk/log_collector.rb +0 -227
  106. data/lib/swarm_sdk/log_stream.rb +0 -127
  107. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  108. data/lib/swarm_sdk/model_aliases.json +0 -8
  109. data/lib/swarm_sdk/models.json +0 -44002
  110. data/lib/swarm_sdk/models.rb +0 -161
  111. data/lib/swarm_sdk/node_context.rb +0 -245
  112. data/lib/swarm_sdk/observer/builder.rb +0 -81
  113. data/lib/swarm_sdk/observer/config.rb +0 -45
  114. data/lib/swarm_sdk/observer/manager.rb +0 -248
  115. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  116. data/lib/swarm_sdk/permissions/config.rb +0 -239
  117. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  118. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  119. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  120. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  121. data/lib/swarm_sdk/plugin.rb +0 -309
  122. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  123. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  124. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -119
  125. data/lib/swarm_sdk/restore_result.rb +0 -65
  126. data/lib/swarm_sdk/result.rb +0 -241
  127. data/lib/swarm_sdk/snapshot.rb +0 -156
  128. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  129. data/lib/swarm_sdk/state_restorer.rb +0 -476
  130. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  131. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  132. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -204
  133. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  134. data/lib/swarm_sdk/swarm/executor.rb +0 -446
  135. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -162
  136. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -361
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -290
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  141. data/lib/swarm_sdk/swarm.rb +0 -973
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/base.rb +0 -63
  145. data/lib/swarm_sdk/tools/bash.rb +0 -280
  146. data/lib/swarm_sdk/tools/clock.rb +0 -46
  147. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  148. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  149. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  150. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  151. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  152. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  153. data/lib/swarm_sdk/tools/edit.rb +0 -145
  154. data/lib/swarm_sdk/tools/glob.rb +0 -166
  155. data/lib/swarm_sdk/tools/grep.rb +0 -235
  156. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  157. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  158. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  159. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  160. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  161. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  162. data/lib/swarm_sdk/tools/read.rb +0 -261
  163. data/lib/swarm_sdk/tools/registry.rb +0 -205
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  165. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  166. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  167. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  168. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  169. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  170. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  171. data/lib/swarm_sdk/tools/think.rb +0 -100
  172. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  173. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  174. data/lib/swarm_sdk/tools/write.rb +0 -112
  175. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  176. data/lib/swarm_sdk/utils.rb +0 -68
  177. data/lib/swarm_sdk/validation_result.rb +0 -33
  178. data/lib/swarm_sdk/version.rb +0 -5
  179. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  180. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  181. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  182. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  183. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  184. data/lib/swarm_sdk/workflow.rb +0 -589
  185. data/lib/swarm_sdk.rb +0 -721
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module V3
5
+ module Tools
6
+ # SubTask tool for spawning focused subtask agents
7
+ #
8
+ # Allows an agent to spawn a copy of itself for a focused subtask.
9
+ # The subtask agent shares the parent's memory (read-only), MCP
10
+ # connections, and skill access. It runs in the same process.
11
+ #
12
+ # SubTask is opt-in — agents must include :SubTask in their tools list.
13
+ # This prevents accidental cost explosion from unintended spawning.
14
+ #
15
+ # ## Depth control
16
+ #
17
+ # Subtasks track nesting depth. The maximum depth is controlled by
18
+ # {Configuration#max_subtask_depth} (default: 1, meaning the parent
19
+ # can spawn subtasks, but subtasks cannot spawn further subtasks).
20
+ #
21
+ # @example Agent definition with SubTask
22
+ # AgentDefinition.new(
23
+ # name: :researcher,
24
+ # description: "Research agent",
25
+ # tools: [:Read, :Grep, :Glob, :Think, :SubTask],
26
+ # )
27
+ class SubTask < Base
28
+ class << self
29
+ # @return [Array<Symbol>] Constructor requirements
30
+ def creation_requirements
31
+ [:agent_definition, :memory_store, :directory, :subtask_depth]
32
+ end
33
+ end
34
+
35
+ # @param agent_definition [AgentDefinition] Parent agent's definition
36
+ # @param memory_store [Memory::Store, nil] Parent's memory store
37
+ # @param directory [String] Working directory
38
+ # @param subtask_depth [Integer] Current nesting depth
39
+ def initialize(agent_definition:, memory_store: nil, directory: ".", subtask_depth: 0)
40
+ super()
41
+ @agent_definition = agent_definition
42
+ @memory_store = memory_store
43
+ @directory = directory
44
+ @subtask_depth = subtask_depth
45
+ end
46
+
47
+ description <<~DESC
48
+ Spawn a focused subtask to handle a specific piece of work.
49
+
50
+ The subtask runs as a copy of you with the same tools, skills, and MCP
51
+ connections, but in an **isolated context window**. Results are returned
52
+ as a summary, keeping your main context clean.
53
+
54
+ ## When to use SubTask
55
+
56
+ **Strongly prefer SubTask when:**
57
+ - Reading or analyzing multiple files (3+ files)
58
+ - Making multiple MCP tool calls (MCP operations are context-heavy)
59
+ - Searching across a codebase (grep/glob + read patterns)
60
+ - Performing any operation that would return large outputs
61
+ - Investigating tangential questions before returning to main task
62
+
63
+ **Use direct tools when:**
64
+ - Single, quick operations (one file read, one simple command)
65
+ - You need the raw output for immediate follow-up in the same turn
66
+
67
+ ## Context efficiency
68
+
69
+ Each subtask gets a fresh context window. Instead of accumulating
70
+ 50k+ tokens of tool outputs in your context, you receive a focused
71
+ summary. This keeps you effective over long conversations.
72
+
73
+ ## Example patterns
74
+
75
+ Instead of: Read file1, Read file2, Read file3, Grep for X, analyze
76
+ Do: SubTask("Analyze auth system", "Read the auth files and grep for
77
+ login patterns, summarize the authentication flow")
78
+
79
+ Instead of: Multiple MCP tool calls to external services
80
+ Do: SubTask("Gather external data", "Use the MCP tools to fetch X, Y, Z
81
+ and return the relevant findings")
82
+
83
+ ## Memory access
84
+
85
+ The subtask has read-only access to your memory — it can retrieve
86
+ context but cannot write new memories.
87
+
88
+ Be specific in your instructions. The subtask sees only what you
89
+ provide in the instructions field, plus any memory it retrieves.
90
+ DESC
91
+
92
+ param :title,
93
+ type: "string",
94
+ desc: "Short title for the subtask (used in logging)",
95
+ required: true
96
+
97
+ param :instructions,
98
+ type: "string",
99
+ desc: "Detailed instructions for the subtask. Be specific — the subtask only sees these instructions and its retrieved memory.",
100
+ required: true
101
+
102
+ # Execute the SubTask tool
103
+ #
104
+ # Spawns a SubTaskAgent, runs the instructions, and returns the result.
105
+ # The subtask agent is always cleaned up via ensure block.
106
+ #
107
+ # @param title [String] Short title for logging
108
+ # @param instructions [String] Detailed instructions for the subtask
109
+ # @return [String] Subtask result or error message
110
+ def execute(title:, instructions:, **_kwargs)
111
+ return validation_error("title is required") if title.to_s.strip.empty?
112
+ return validation_error("instructions are required") if instructions.to_s.strip.empty?
113
+
114
+ next_depth = @subtask_depth + 1
115
+ max_depth = Configuration.instance.max_subtask_depth
116
+
117
+ if next_depth > max_depth
118
+ return error(
119
+ "Maximum subtask depth (#{max_depth}) exceeded. " \
120
+ "Cannot spawn nested subtask at depth #{next_depth}.",
121
+ )
122
+ end
123
+
124
+ agent = SubTaskAgent.new(
125
+ @agent_definition,
126
+ parent_memory_store: @memory_store,
127
+ subtask_depth: next_depth,
128
+ )
129
+
130
+ EventStream.emit(
131
+ type: "subtask_spawned",
132
+ agent: @agent_definition.name,
133
+ subtask_agent: agent.id,
134
+ title: title,
135
+ depth: next_depth,
136
+ )
137
+
138
+ prompt = <<~PROMPT.strip
139
+ # SubTask: #{title}
140
+
141
+ #{instructions}
142
+ PROMPT
143
+
144
+ response = agent.ask(prompt)
145
+ format_result(title, response)
146
+ rescue StandardError => e
147
+ EventStream.emit(
148
+ type: "subtask_failed",
149
+ agent: @agent_definition.name,
150
+ subtask_agent: agent&.id,
151
+ title: title,
152
+ error: "#{e.class}: #{e.message}",
153
+ )
154
+ error("Subtask '#{title}' failed: #{e.class}: #{e.message}")
155
+ ensure
156
+ agent&.clear
157
+ end
158
+
159
+ private
160
+
161
+ # Format a successful subtask result
162
+ #
163
+ # @param title [String] Subtask title
164
+ # @param response [RubyLLM::Message] LLM response
165
+ # @return [String] Formatted result
166
+ def format_result(title, response)
167
+ EventStream.emit(
168
+ type: "subtask_completed",
169
+ agent: @agent_definition.name,
170
+ title: title,
171
+ success: true,
172
+ )
173
+
174
+ <<~RESULT.strip
175
+ ## SubTask Result: #{title}
176
+
177
+ #{response&.content || "(no response)"}
178
+ RESULT
179
+ end
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module V3
5
+ module Tools
6
+ # Think tool for explicit reasoning and planning
7
+ #
8
+ # Allows the agent to write down thoughts, plans, and intermediate
9
+ # calculations. These thoughts become part of the conversation context,
10
+ # enabling better reasoning through complex problems.
11
+ #
12
+ # When the agent has memory enabled, Think retrieves relevant memories
13
+ # for the thought content and returns them alongside "Thought noted."
14
+ # This gives the agent access to its long-term knowledge while reasoning.
15
+ # Without memory, Think behaves as a simple acknowledgment.
16
+ #
17
+ # Think does NOT write to memory — the existing turn-level async
18
+ # ingestion pipeline already captures tool call arguments (including
19
+ # Think's thoughts), so Think content is automatically persisted.
20
+ class Think < Base
21
+ class << self
22
+ # @return [Array<Symbol>] Constructor requirements
23
+ def creation_requirements
24
+ [:memory_store]
25
+ end
26
+ end
27
+
28
+ # @param memory_store [Memory::Store, nil] Memory store for retrieval
29
+ def initialize(memory_store: nil)
30
+ super()
31
+ @memory_store = memory_store
32
+ end
33
+
34
+ description <<~DESC
35
+ Use this tool to think through problems step by step before acting.
36
+
37
+ This is your working memory — record your thoughts, plans, strategies,
38
+ and intermediate calculations. Using this tool leads to significantly
39
+ better outcomes and more accurate solutions.
40
+
41
+ Use it frequently:
42
+ - Before starting any task
43
+ - When doing arithmetic or counting
44
+ - After reading files to process what you learned
45
+ - Between steps to track progress
46
+ - When debugging to trace issues
47
+
48
+ Your thoughts persist throughout the session as conversation history.
49
+ DESC
50
+
51
+ param :thoughts,
52
+ type: "string",
53
+ desc: "Your thoughts, plans, calculations, or any notes you want to record",
54
+ required: true
55
+
56
+ # Execute the Think tool
57
+ #
58
+ # When memory is available, searches for cards relevant to the thought
59
+ # content and returns them as context for the agent's reasoning.
60
+ #
61
+ # @param thoughts [String] The agent's thoughts
62
+ # @return [String] Acknowledgment with optional related memories
63
+ def execute(thoughts:, **_kwargs)
64
+ return "Thought noted." unless @memory_store
65
+
66
+ cards = @memory_store.search(thoughts, top_k: 5)
67
+ format_response(cards)
68
+ end
69
+
70
+ private
71
+
72
+ # Format the response with optional related memories
73
+ #
74
+ # @param cards [Array<Card>] Retrieved memory cards
75
+ # @return [String] Formatted response
76
+ def format_response(cards)
77
+ return "Thought noted." if cards.empty?
78
+
79
+ lines = ["Thought noted.", "", "Related memories:"]
80
+ cards.each do |card|
81
+ lines << "- [#{card.type.to_s.capitalize}] #{card.text}"
82
+ end
83
+ lines.join("\n")
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SwarmSDK
4
+ module V3
5
+ module Tools
6
+ # Write tool for writing content to files
7
+ #
8
+ # Creates new files or overwrites existing files.
9
+ # Enforces read-before-write for existing files via the Read tool's tracking.
10
+ class Write < Base
11
+ class << self
12
+ # @return [Array<Symbol>] Constructor requirements
13
+ def creation_requirements
14
+ [:agent_name, :directory, :read_tracker]
15
+ end
16
+ end
17
+
18
+ description <<~DESC
19
+ Writes a file to the local filesystem.
20
+ Overwrites existing files. You MUST use Read first on existing files.
21
+ ALWAYS prefer editing existing files. NEVER write new files unless required.
22
+
23
+ Path handling:
24
+ - Relative paths resolve against your working directory
25
+ - Absolute paths (starting with /) are used as-is
26
+ DESC
27
+
28
+ param :file_path,
29
+ type: "string",
30
+ desc: "Path to the file to write",
31
+ required: true
32
+
33
+ param :content,
34
+ type: "string",
35
+ desc: "The content to write to the file",
36
+ required: true
37
+
38
+ # @param agent_name [Symbol, String] Agent identifier
39
+ # @param directory [String] Agent's working directory
40
+ # @param read_tracker [ReadTracker] Shared read tracker for enforcement
41
+ def initialize(agent_name:, directory:, read_tracker:)
42
+ super()
43
+ @agent_name = agent_name.to_sym
44
+ @directory = File.expand_path(directory)
45
+ @read_tracker = read_tracker
46
+ end
47
+
48
+ # Execute file write
49
+ #
50
+ # @param file_path [String] Path to the file
51
+ # @param content [String] Content to write
52
+ # @return [String] Success or error message
53
+ def execute(file_path:, content:)
54
+ return validation_error("file_path is required") if file_path.nil? || file_path.to_s.strip.empty?
55
+ return validation_error("content is required") if content.nil?
56
+
57
+ resolved_path = resolve_path(file_path)
58
+ file_exists = File.exist?(resolved_path)
59
+
60
+ if file_exists && !@read_tracker.file_read?(@agent_name, resolved_path)
61
+ return validation_error(
62
+ "Cannot write to existing file without reading it first. " \
63
+ "Use the Read tool on '#{file_path}' before overwriting.",
64
+ )
65
+ end
66
+
67
+ parent_dir = File.dirname(resolved_path)
68
+ FileUtils.mkdir_p(parent_dir) unless File.directory?(parent_dir)
69
+
70
+ File.write(resolved_path, content, encoding: "UTF-8")
71
+
72
+ byte_size = content.bytesize
73
+ line_count = content.lines.count
74
+ action = file_exists ? "overwrote" : "created"
75
+
76
+ "Successfully #{action} file: #{file_path} (#{line_count} lines, #{byte_size} bytes)"
77
+ rescue Errno::EACCES
78
+ error("Permission denied: Cannot write to file '#{file_path}'")
79
+ rescue Errno::EISDIR
80
+ error("Path is a directory, not a file.")
81
+ rescue StandardError => e
82
+ error("Unexpected error writing file: #{e.class.name} - #{e.message}")
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+ require "cgi"
5
+ require "digest"
6
+ require "English"
7
+ require "fileutils"
8
+ require "json"
9
+ require "open3"
10
+ require "pathname"
11
+ require "securerandom"
12
+ require "set"
13
+ require "time"
14
+ require "timeout"
15
+
16
+ require "async"
17
+ require "async/barrier"
18
+ require "async/semaphore"
19
+ require "mcp"
20
+ require "ruby_llm"
21
+
22
+ # Load ruby_llm compatibility patches (shared gem infrastructure)
23
+ require_relative "ruby_llm_patches/init"
24
+
25
+ require "zeitwerk"
26
+
27
+ module SwarmSDK
28
+ # V3 is the next-generation agent primitive with built-in memory.
29
+ #
30
+ # Self-contained module with its own Zeitwerk loader and zero dependencies
31
+ # on V2 code. The core insight: the LLM's context window is a staging area,
32
+ # not the whole brain. Older turns get consolidated into memory cards, and
33
+ # retrieval brings relevant memory back into working context on demand.
34
+ #
35
+ # @example Basic usage
36
+ # require "swarm_sdk/v3"
37
+ #
38
+ # definition = SwarmSDK::V3::AgentDefinition.new(
39
+ # name: :assistant,
40
+ # description: "A helpful assistant",
41
+ # model: "claude-sonnet-4",
42
+ # tools: [:Read, :Write, :Edit, :Bash, :Grep, :Glob],
43
+ # memory_directory: ".swarm/memory",
44
+ # )
45
+ #
46
+ # agent = SwarmSDK::V3::Agent.new(definition)
47
+ # response = agent.ask("Build a login page")
48
+ #
49
+ # @example Without memory (pure conversation)
50
+ # definition = SwarmSDK::V3::AgentDefinition.new(
51
+ # name: :chat,
52
+ # description: "Simple chat agent",
53
+ # model: "claude-sonnet-4",
54
+ # )
55
+ #
56
+ # agent = SwarmSDK::V3::Agent.new(definition)
57
+ # response = agent.ask("Hello!")
58
+ module V3
59
+ class Error < StandardError; end
60
+ class ConfigurationError < Error; end
61
+ class ToolExecutionError < Error; end
62
+ class MemoryError < Error; end
63
+
64
+ class << self
65
+ # Build a single V3 Agent from a DSL block
66
+ #
67
+ # Convenience entry point that delegates to {AgentBuilder.build}.
68
+ # Returns an initialized Agent ready for use with {Agent#ask}.
69
+ #
70
+ # @yield DSL block evaluated on an {AgentBuilder} instance
71
+ # @return [Agent] Initialized agent
72
+ # @raise [ConfigurationError] If required fields are missing
73
+ #
74
+ # @example
75
+ # agent = SwarmSDK::V3.agent do
76
+ # name :assistant
77
+ # description "A helpful assistant"
78
+ # tools :Read, :Write, :Edit
79
+ # streaming true
80
+ #
81
+ # memory do
82
+ # directory ".swarm/memory"
83
+ # end
84
+ # end
85
+ #
86
+ # agent.ask("Hello!")
87
+ def agent(&block)
88
+ AgentBuilder.build(&block)
89
+ end
90
+
91
+ # Configure V3 global settings
92
+ #
93
+ # Convenience wrapper around {Configuration.configure}. Yields the
94
+ # configuration instance and applies provider settings to RubyLLM.
95
+ #
96
+ # @yield [Configuration] The configuration instance
97
+ # @return [Configuration]
98
+ #
99
+ # @example
100
+ # SwarmSDK::V3.configure do |config|
101
+ # config.default_model = "claude-sonnet-4"
102
+ # config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
103
+ # config.default_tools = [:Think, :Clock]
104
+ # end
105
+ def configure(&block)
106
+ Configuration.configure(&block)
107
+ end
108
+
109
+ # Access the global configuration instance
110
+ #
111
+ # @return [Configuration]
112
+ #
113
+ # @example
114
+ # model = SwarmSDK::V3.configuration.default_model
115
+ def configuration
116
+ Configuration.instance
117
+ end
118
+
119
+ # Reset configuration to defaults
120
+ #
121
+ # @return [void]
122
+ def reset_configuration!
123
+ Configuration.reset!
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ # V3 Zeitwerk setup:
130
+ # When loaded via V2 (require "swarm_sdk"), the V2 loader already manages
131
+ # lib/swarm_sdk/ including v3/, so we skip to avoid conflicts.
132
+ # When loaded standalone (require "swarm_sdk/v3"), creates its own loader.
133
+ v3_dir = File.join(__dir__, "v3")
134
+ already_managed = false
135
+ Zeitwerk::Registry.loaders.each do |loader|
136
+ loader.dirs.each { |dir| already_managed = true if v3_dir.start_with?(dir) }
137
+ end
138
+
139
+ unless already_managed
140
+ v3_loader = Zeitwerk::Loader.new
141
+ v3_loader.tag = "swarm_sdk_v3"
142
+ v3_loader.push_dir(v3_dir, namespace: SwarmSDK::V3)
143
+ v3_loader.inflector.inflect("mcp" => "MCP")
144
+ v3_loader.setup
145
+ end