claude_swarm 1.0.10 → 1.0.11
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.
- checksums.yaml +4 -4
- data/{CHANGELOG.md → CHANGELOG.claude-swarm.md} +3 -0
- data/CLAUDE.md +0 -1
- data/decisions/2025-11-22-001-global-agent-registry.md +172 -0
- data/docs/v2/CHANGELOG.swarm_cli.md +12 -0
- data/docs/v2/CHANGELOG.swarm_memory.md +139 -0
- data/docs/v2/CHANGELOG.swarm_sdk.md +249 -1
- data/docs/v2/README.md +15 -5
- data/docs/v2/guides/complete-tutorial.md +93 -7
- data/docs/v2/guides/getting-started.md +3 -1
- data/docs/v2/guides/memory-adapters.md +41 -0
- data/docs/v2/guides/{migrating-to-2.3.md → migrating-to-2.x.md} +213 -8
- data/docs/v2/guides/plugins.md +52 -5
- data/docs/v2/guides/rails-integration.md +6 -0
- data/docs/v2/guides/swarm-memory.md +2 -13
- data/docs/v2/reference/cli.md +0 -1
- data/docs/v2/reference/configuration_reference.md +300 -0
- data/docs/v2/reference/event_payload_structures.md +26 -4
- data/docs/v2/reference/ruby-dsl.md +457 -4
- data/docs/v2/reference/swarm_memory_technical_details.md +7 -29
- data/docs/v2/reference/yaml.md +2 -2
- data/lib/claude_swarm/mcp_generator.rb +1 -1
- data/lib/claude_swarm/orchestrator.rb +8 -1
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/swarm_cli/version.rb +1 -1
- data/lib/swarm_memory/core/semantic_index.rb +10 -2
- data/lib/swarm_memory/core/storage.rb +7 -2
- data/lib/swarm_memory/dsl/memory_config.rb +37 -0
- data/lib/swarm_memory/integration/sdk_plugin.rb +120 -27
- data/lib/swarm_memory/optimization/defragmenter.rb +1 -1
- data/lib/swarm_memory/prompts/memory_researcher.md.erb +0 -1
- data/lib/swarm_memory/tools/load_skill.rb +0 -1
- data/lib/swarm_memory/tools/memory_edit.rb +2 -1
- data/lib/swarm_memory/tools/memory_read.rb +1 -1
- data/lib/swarm_memory/version.rb +1 -1
- data/lib/swarm_memory.rb +7 -5
- data/lib/swarm_sdk/agent/chat.rb +1 -1
- data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +4 -0
- data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +1 -1
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +38 -4
- data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +2 -2
- data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +3 -5
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +48 -0
- data/lib/swarm_sdk/agent/context.rb +1 -2
- data/lib/swarm_sdk/agent/definition.rb +3 -3
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +1 -1
- data/lib/swarm_sdk/agent_registry.rb +146 -0
- data/lib/swarm_sdk/builders/base_builder.rb +91 -12
- data/lib/swarm_sdk/config.rb +302 -0
- data/lib/swarm_sdk/configuration/parser.rb +22 -2
- data/lib/swarm_sdk/configuration.rb +13 -4
- data/lib/swarm_sdk/context_compactor/token_counter.rb +2 -6
- data/lib/swarm_sdk/custom_tool_registry.rb +226 -0
- data/lib/swarm_sdk/hooks/adapter.rb +3 -3
- data/lib/swarm_sdk/hooks/shell_executor.rb +4 -3
- data/lib/swarm_sdk/models.json +4333 -1
- data/lib/swarm_sdk/models.rb +43 -2
- data/lib/swarm_sdk/plugin.rb +2 -2
- data/lib/swarm_sdk/result.rb +52 -0
- data/lib/swarm_sdk/swarm/agent_initializer.rb +1 -1
- data/lib/swarm_sdk/swarm/hook_triggers.rb +1 -0
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +1 -0
- data/lib/swarm_sdk/swarm/tool_configurator.rb +18 -4
- data/lib/swarm_sdk/swarm.rb +76 -13
- data/lib/swarm_sdk/tools/bash.rb +7 -9
- data/lib/swarm_sdk/tools/glob.rb +5 -5
- data/lib/swarm_sdk/tools/read.rb +8 -8
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +4 -3
- data/lib/swarm_sdk/tools/web_fetch.rb +20 -18
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk/workflow/builder.rb +49 -0
- data/lib/swarm_sdk/workflow/node_builder.rb +4 -2
- data/lib/swarm_sdk/workflow/transformer_executor.rb +4 -3
- data/lib/swarm_sdk.rb +261 -105
- data/swarm_cli.gemspec +1 -1
- data/swarm_memory.gemspec +8 -3
- data/swarm_sdk.gemspec +4 -4
- data/team_full.yml +104 -300
- metadata +9 -5
- data/lib/swarm_memory/tools/memory_multi_edit.rb +0 -281
- /data/lib/swarm_memory/{errors.rb → error.rb} +0 -0
|
@@ -1,281 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmMemory
|
|
4
|
-
module Tools
|
|
5
|
-
# Tool for performing multiple edits to a memory entry
|
|
6
|
-
#
|
|
7
|
-
# Applies multiple edit operations sequentially to a single memory entry.
|
|
8
|
-
# Each edit sees the result of all previous edits, allowing for
|
|
9
|
-
# coordinated multi-step transformations.
|
|
10
|
-
# Each agent has its own isolated memory storage.
|
|
11
|
-
class MemoryMultiEdit < RubyLLM::Tool
|
|
12
|
-
description <<~DESC
|
|
13
|
-
Perform multiple exact string replacements in a single memory entry (applies edits sequentially).
|
|
14
|
-
|
|
15
|
-
REQUIRED: Provide BOTH parameters - file_path and edits_json.
|
|
16
|
-
|
|
17
|
-
**Required Parameters:**
|
|
18
|
-
- file_path (REQUIRED): Path to memory entry - MUST start with concept/, fact/, skill/, or experience/
|
|
19
|
-
- edits_json (REQUIRED): JSON array of edit operations - each must have old_string, new_string, and optionally replace_all
|
|
20
|
-
|
|
21
|
-
**MEMORY STRUCTURE (4 Fixed Categories Only):**
|
|
22
|
-
- concept/{domain}/** - Abstract ideas
|
|
23
|
-
- fact/{subfolder}/** - Concrete information
|
|
24
|
-
- skill/{domain}/** - Procedures
|
|
25
|
-
- experience/** - Lessons
|
|
26
|
-
INVALID: documentation/, reference/, project/, code/, parallel/
|
|
27
|
-
|
|
28
|
-
**JSON Format:**
|
|
29
|
-
```json
|
|
30
|
-
[
|
|
31
|
-
{"old_string": "text to find", "new_string": "replacement text", "replace_all": false},
|
|
32
|
-
{"old_string": "another find", "new_string": "another replace", "replace_all": true}
|
|
33
|
-
]
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
**CRITICAL - Before Using This Tool:**
|
|
37
|
-
1. You MUST use MemoryRead on the entry first - edits without reading will FAIL
|
|
38
|
-
2. Copy text exactly from MemoryRead output, EXCLUDING the line number prefix
|
|
39
|
-
3. Line number format: " 123→actual content" - only use text AFTER the arrow
|
|
40
|
-
4. Edits are applied SEQUENTIALLY - later edits see results of earlier edits
|
|
41
|
-
5. If ANY edit fails, NO changes are saved (all-or-nothing)
|
|
42
|
-
|
|
43
|
-
**How Sequential Edits Work:**
|
|
44
|
-
```
|
|
45
|
-
Original: "status: pending, priority: low"
|
|
46
|
-
|
|
47
|
-
Edit 1: "pending" → "in-progress"
|
|
48
|
-
Result: "status: in-progress, priority: low"
|
|
49
|
-
|
|
50
|
-
Edit 2: "low" → "high" (sees Edit 1's result)
|
|
51
|
-
Final: "status: in-progress, priority: high"
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
**Use Cases:**
|
|
55
|
-
- Making multiple coordinated changes in one operation
|
|
56
|
-
- Updating several related fields at once
|
|
57
|
-
- Chaining transformations where order matters
|
|
58
|
-
- Bulk find-and-replace operations
|
|
59
|
-
|
|
60
|
-
**Examples:**
|
|
61
|
-
```
|
|
62
|
-
# Update multiple fields in an experience
|
|
63
|
-
MemoryMultiEdit(
|
|
64
|
-
file_path: "experience/api-debugging.md",
|
|
65
|
-
edits_json: '[
|
|
66
|
-
{"old_string": "status: in-progress", "new_string": "status: resolved"},
|
|
67
|
-
{"old_string": "confidence: medium", "new_string": "confidence: high"}
|
|
68
|
-
]'
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
# Rename function and update calls in a concept
|
|
72
|
-
MemoryMultiEdit(
|
|
73
|
-
file_path: "concept/ruby/functions.md",
|
|
74
|
-
edits_json: '[
|
|
75
|
-
{"old_string": "def old_func_name", "new_string": "def new_func_name"},
|
|
76
|
-
{"old_string": "old_func_name()", "new_string": "new_func_name()", "replace_all": true}
|
|
77
|
-
]'
|
|
78
|
-
)
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
**Important Notes:**
|
|
82
|
-
- All edits in the array must be valid JSON objects
|
|
83
|
-
- Each old_string must be different from its new_string
|
|
84
|
-
- Each old_string must be unique in content UNLESS replace_all is true
|
|
85
|
-
- Failed edit shows which previous edits succeeded
|
|
86
|
-
- More efficient than multiple MemoryEdit calls
|
|
87
|
-
DESC
|
|
88
|
-
|
|
89
|
-
param :file_path,
|
|
90
|
-
desc: "Path to memory entry - MUST start with concept/, fact/, skill/, or experience/ (e.g., 'experience/api-debugging.md', 'concept/ruby/functions.md')",
|
|
91
|
-
required: true
|
|
92
|
-
|
|
93
|
-
param :edits_json,
|
|
94
|
-
type: "string",
|
|
95
|
-
desc: <<~DESC.chomp,
|
|
96
|
-
JSON array of edit operations. Each edit must have:
|
|
97
|
-
old_string (exact text to replace),
|
|
98
|
-
new_string (replacement text),
|
|
99
|
-
and optionally replace_all (boolean, default false).
|
|
100
|
-
Example: [{"old_string":"foo","new_string":"bar","replace_all":false}]
|
|
101
|
-
DESC
|
|
102
|
-
required: true
|
|
103
|
-
|
|
104
|
-
# Initialize with storage instance and agent name
|
|
105
|
-
#
|
|
106
|
-
# @param storage [Core::Storage] Storage instance
|
|
107
|
-
# @param agent_name [String, Symbol] Agent identifier
|
|
108
|
-
def initialize(storage:, agent_name:)
|
|
109
|
-
super()
|
|
110
|
-
@storage = storage
|
|
111
|
-
@agent_name = agent_name.to_sym
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Override name to return simple "MemoryMultiEdit"
|
|
115
|
-
def name
|
|
116
|
-
"MemoryMultiEdit"
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Execute the tool
|
|
120
|
-
#
|
|
121
|
-
# @param file_path [String] Path to memory entry
|
|
122
|
-
# @param edits_json [String] JSON array of edit operations
|
|
123
|
-
# @return [String] Success message or error
|
|
124
|
-
def execute(file_path:, edits_json:)
|
|
125
|
-
# Validate inputs
|
|
126
|
-
return validation_error("file_path is required") if file_path.nil? || file_path.to_s.strip.empty?
|
|
127
|
-
|
|
128
|
-
# Parse JSON
|
|
129
|
-
edits = begin
|
|
130
|
-
JSON.parse(edits_json)
|
|
131
|
-
rescue JSON::ParserError
|
|
132
|
-
nil
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
return validation_error("Invalid JSON format. Please provide a valid JSON array of edit operations.") if edits.nil?
|
|
136
|
-
|
|
137
|
-
return validation_error("edits must be an array") unless edits.is_a?(Array)
|
|
138
|
-
return validation_error("edits array cannot be empty") if edits.empty?
|
|
139
|
-
|
|
140
|
-
# Read current content (this will raise ArgumentError if entry doesn't exist)
|
|
141
|
-
content = @storage.read(file_path: file_path)
|
|
142
|
-
|
|
143
|
-
# Enforce read-before-edit with content verification
|
|
144
|
-
unless Core::StorageReadTracker.entry_read?(@agent_name, file_path, @storage)
|
|
145
|
-
return validation_error(
|
|
146
|
-
"Cannot edit memory entry without reading it first. " \
|
|
147
|
-
"You must use MemoryRead on 'memory://#{file_path}' before editing it. " \
|
|
148
|
-
"This ensures you have the current content to match against.",
|
|
149
|
-
)
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
# Validate edit operations
|
|
153
|
-
validated_edits = []
|
|
154
|
-
edits.each_with_index do |edit, index|
|
|
155
|
-
unless edit.is_a?(Hash)
|
|
156
|
-
return validation_error("Edit at index #{index} must be a hash/object with old_string and new_string")
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
# Convert string keys to symbols for consistency
|
|
160
|
-
edit = edit.transform_keys(&:to_sym)
|
|
161
|
-
|
|
162
|
-
unless edit[:old_string]
|
|
163
|
-
return validation_error("Edit at index #{index} missing required field 'old_string'")
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
unless edit[:new_string]
|
|
167
|
-
return validation_error("Edit at index #{index} missing required field 'new_string'")
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
# old_string and new_string must be different
|
|
171
|
-
if edit[:old_string] == edit[:new_string]
|
|
172
|
-
return validation_error("Edit at index #{index}: old_string and new_string must be different")
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
validated_edits << {
|
|
176
|
-
old_string: edit[:old_string].to_s,
|
|
177
|
-
new_string: edit[:new_string].to_s,
|
|
178
|
-
replace_all: edit[:replace_all] == true,
|
|
179
|
-
index: index,
|
|
180
|
-
}
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# Apply edits sequentially
|
|
184
|
-
results = []
|
|
185
|
-
current_content = content
|
|
186
|
-
|
|
187
|
-
validated_edits.each do |edit|
|
|
188
|
-
# Check if old_string exists in current content
|
|
189
|
-
unless current_content.include?(edit[:old_string])
|
|
190
|
-
return error_with_results(
|
|
191
|
-
<<~ERROR.chomp,
|
|
192
|
-
Edit #{edit[:index]}: old_string not found in memory entry.
|
|
193
|
-
Make sure it matches exactly, including all whitespace and indentation.
|
|
194
|
-
Do not include line number prefixes from MemoryRead tool output.
|
|
195
|
-
Note: This edit follows #{edit[:index]} previous edit(s) which may have changed the content.
|
|
196
|
-
ERROR
|
|
197
|
-
results,
|
|
198
|
-
)
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# Count occurrences
|
|
202
|
-
occurrences = current_content.scan(edit[:old_string]).count
|
|
203
|
-
|
|
204
|
-
# If not replace_all and multiple occurrences, error
|
|
205
|
-
if !edit[:replace_all] && occurrences > 1
|
|
206
|
-
return error_with_results(
|
|
207
|
-
<<~ERROR.chomp,
|
|
208
|
-
Edit #{edit[:index]}: Found #{occurrences} occurrences of old_string.
|
|
209
|
-
Either provide more surrounding context to make the match unique, or set replace_all: true to replace all occurrences.
|
|
210
|
-
ERROR
|
|
211
|
-
results,
|
|
212
|
-
)
|
|
213
|
-
end
|
|
214
|
-
|
|
215
|
-
# Perform replacement
|
|
216
|
-
new_content = if edit[:replace_all]
|
|
217
|
-
current_content.gsub(edit[:old_string], edit[:new_string])
|
|
218
|
-
else
|
|
219
|
-
current_content.sub(edit[:old_string], edit[:new_string])
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# Record result
|
|
223
|
-
replaced_count = edit[:replace_all] ? occurrences : 1
|
|
224
|
-
results << {
|
|
225
|
-
index: edit[:index],
|
|
226
|
-
status: "success",
|
|
227
|
-
occurrences: replaced_count,
|
|
228
|
-
message: "Replaced #{replaced_count} occurrence(s)",
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
# Update content for next edit
|
|
232
|
-
current_content = new_content
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
# Get existing entry
|
|
236
|
-
entry = @storage.read_entry(file_path: file_path)
|
|
237
|
-
|
|
238
|
-
# Write updated content back (preserving the title)
|
|
239
|
-
@storage.write(
|
|
240
|
-
file_path: file_path,
|
|
241
|
-
content: current_content,
|
|
242
|
-
title: entry.title,
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
# Build success message
|
|
246
|
-
total_replacements = results.sum { |r| r[:occurrences] }
|
|
247
|
-
message = "Successfully applied #{validated_edits.size} edit(s) to memory://#{file_path}\n"
|
|
248
|
-
message += "Total replacements: #{total_replacements}\n\n"
|
|
249
|
-
message += "Details:\n"
|
|
250
|
-
results.each do |result|
|
|
251
|
-
message += " Edit #{result[:index]}: #{result[:message]}\n"
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
message
|
|
255
|
-
rescue ArgumentError => e
|
|
256
|
-
validation_error(e.message)
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
private
|
|
260
|
-
|
|
261
|
-
def validation_error(message)
|
|
262
|
-
"<tool_use_error>InputValidationError: #{message}</tool_use_error>"
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
def error_with_results(message, results)
|
|
266
|
-
output = "<tool_use_error>InputValidationError: #{message}\n\n"
|
|
267
|
-
|
|
268
|
-
if results.any?
|
|
269
|
-
output += "Previous successful edits before error:\n"
|
|
270
|
-
results.each do |result|
|
|
271
|
-
output += " Edit #{result[:index]}: #{result[:message]}\n"
|
|
272
|
-
end
|
|
273
|
-
output += "\n"
|
|
274
|
-
end
|
|
275
|
-
|
|
276
|
-
output += "Note: The memory entry has NOT been modified. All or nothing approach - if any edit fails, no changes are saved.</tool_use_error>"
|
|
277
|
-
output
|
|
278
|
-
end
|
|
279
|
-
end
|
|
280
|
-
end
|
|
281
|
-
end
|
|
File without changes
|