claude_swarm 1.0.9 → 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} +10 -0
- data/CLAUDE.md +346 -191
- data/decisions/2025-11-22-001-global-agent-registry.md +172 -0
- data/docs/v2/CHANGELOG.swarm_cli.md +20 -0
- data/docs/v2/CHANGELOG.swarm_memory.md +146 -1
- data/docs/v2/CHANGELOG.swarm_sdk.md +433 -10
- data/docs/v2/README.md +20 -5
- data/docs/v2/guides/complete-tutorial.md +95 -9
- data/docs/v2/guides/getting-started.md +10 -8
- data/docs/v2/guides/memory-adapters.md +41 -0
- data/docs/v2/guides/migrating-to-2.x.md +746 -0
- data/docs/v2/guides/plugins.md +52 -5
- data/docs/v2/guides/rails-integration.md +6 -0
- data/docs/v2/guides/snapshots.md +14 -14
- data/docs/v2/guides/swarm-memory.md +2 -13
- data/docs/v2/reference/architecture-flow.md +3 -3
- 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 +27 -5
- data/docs/v2/reference/ruby-dsl.md +614 -18
- data/docs/v2/reference/swarm_memory_technical_details.md +7 -29
- data/docs/v2/reference/yaml.md +172 -54
- data/examples/snapshot_demo.rb +2 -2
- data/lib/claude_swarm/mcp_generator.rb +8 -21
- data/lib/claude_swarm/orchestrator.rb +8 -1
- data/lib/claude_swarm/version.rb +1 -1
- data/lib/swarm_cli/commands/run.rb +2 -2
- data/lib/swarm_cli/config_loader.rb +11 -11
- data/lib/swarm_cli/formatters/human_formatter.rb +0 -33
- data/lib/swarm_cli/interactive_repl.rb +2 -2
- data/lib/swarm_cli/ui/icons.rb +0 -23
- data/lib/swarm_cli/version.rb +1 -1
- data/lib/swarm_memory/adapters/filesystem_adapter.rb +11 -34
- 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 +201 -28
- 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 +8 -6
- data/lib/swarm_sdk/agent/builder.rb +58 -0
- data/lib/swarm_sdk/agent/chat.rb +527 -1061
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/context_tracker.rb +13 -88
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +204 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/hook_integration.rb +108 -46
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +78 -0
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +267 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/logging_helpers.rb +3 -3
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +83 -0
- data/lib/swarm_sdk/agent/{chat → chat_helpers}/system_reminder_injector.rb +11 -13
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +79 -0
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +146 -0
- data/lib/swarm_sdk/agent/context.rb +1 -2
- data/lib/swarm_sdk/agent/definition.rb +66 -154
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +4 -2
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +161 -0
- data/lib/swarm_sdk/agent_registry.rb +146 -0
- data/lib/swarm_sdk/builders/base_builder.rb +488 -0
- data/lib/swarm_sdk/concerns/cleanupable.rb +39 -0
- data/lib/swarm_sdk/concerns/snapshotable.rb +67 -0
- data/lib/swarm_sdk/concerns/validatable.rb +55 -0
- data/lib/swarm_sdk/config.rb +302 -0
- data/lib/swarm_sdk/configuration/parser.rb +373 -0
- data/lib/swarm_sdk/configuration/translator.rb +255 -0
- data/lib/swarm_sdk/configuration.rb +77 -546
- data/lib/swarm_sdk/context_compactor/token_counter.rb +2 -6
- data/lib/swarm_sdk/context_compactor.rb +6 -11
- data/lib/swarm_sdk/context_management/builder.rb +128 -0
- data/lib/swarm_sdk/context_management/context.rb +328 -0
- data/lib/swarm_sdk/custom_tool_registry.rb +226 -0
- data/lib/swarm_sdk/defaults.rb +196 -0
- data/lib/swarm_sdk/events_to_messages.rb +18 -0
- data/lib/swarm_sdk/hooks/adapter.rb +3 -3
- data/lib/swarm_sdk/hooks/shell_executor.rb +4 -2
- data/lib/swarm_sdk/log_collector.rb +179 -29
- data/lib/swarm_sdk/log_stream.rb +29 -0
- data/lib/swarm_sdk/models.json +4333 -1
- data/lib/swarm_sdk/models.rb +43 -2
- data/lib/swarm_sdk/node_context.rb +1 -1
- data/lib/swarm_sdk/observer/builder.rb +81 -0
- data/lib/swarm_sdk/observer/config.rb +45 -0
- data/lib/swarm_sdk/observer/manager.rb +236 -0
- data/lib/swarm_sdk/patterns/agent_observer.rb +160 -0
- data/lib/swarm_sdk/plugin.rb +95 -5
- data/lib/swarm_sdk/result.rb +52 -0
- data/lib/swarm_sdk/snapshot.rb +6 -6
- data/lib/swarm_sdk/snapshot_from_events.rb +13 -2
- data/lib/swarm_sdk/state_restorer.rb +136 -151
- data/lib/swarm_sdk/state_snapshot.rb +65 -100
- data/lib/swarm_sdk/swarm/agent_initializer.rb +181 -137
- data/lib/swarm_sdk/swarm/builder.rb +44 -578
- data/lib/swarm_sdk/swarm/executor.rb +213 -0
- data/lib/swarm_sdk/swarm/hook_triggers.rb +151 -0
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +341 -0
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +7 -4
- data/lib/swarm_sdk/swarm/tool_configurator.rb +58 -140
- data/lib/swarm_sdk/swarm.rb +203 -683
- data/lib/swarm_sdk/tools/bash.rb +14 -8
- data/lib/swarm_sdk/tools/delegate.rb +61 -43
- data/lib/swarm_sdk/tools/edit.rb +8 -13
- data/lib/swarm_sdk/tools/glob.rb +12 -4
- data/lib/swarm_sdk/tools/grep.rb +7 -0
- data/lib/swarm_sdk/tools/multi_edit.rb +15 -11
- data/lib/swarm_sdk/tools/path_resolver.rb +51 -2
- data/lib/swarm_sdk/tools/read.rb +16 -18
- data/lib/swarm_sdk/tools/registry.rb +122 -10
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +9 -5
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -6
- data/lib/swarm_sdk/tools/todo_write.rb +7 -0
- data/lib/swarm_sdk/tools/web_fetch.rb +20 -17
- data/lib/swarm_sdk/tools/write.rb +8 -13
- data/lib/swarm_sdk/version.rb +1 -1
- data/lib/swarm_sdk/{node → workflow}/agent_config.rb +1 -1
- data/lib/swarm_sdk/workflow/builder.rb +192 -0
- data/lib/swarm_sdk/workflow/executor.rb +497 -0
- data/lib/swarm_sdk/{node/builder.rb → workflow/node_builder.rb} +7 -5
- data/lib/swarm_sdk/{node → workflow}/transformer_executor.rb +5 -3
- data/lib/swarm_sdk/{node_orchestrator.rb → workflow.rb} +152 -456
- data/lib/swarm_sdk.rb +294 -108
- data/rubocop/cop/security/no_reflection_methods.rb +1 -1
- data/swarm_cli.gemspec +1 -1
- data/swarm_memory.gemspec +8 -3
- data/swarm_sdk.gemspec +6 -4
- data/team_full.yml +124 -320
- metadata +42 -14
- data/lib/swarm_memory/chat_extension.rb +0 -34
- data/lib/swarm_memory/tools/memory_multi_edit.rb +0 -281
- data/lib/swarm_sdk/providers/openai_with_responses.rb +0 -589
- /data/lib/swarm_memory/{errors.rb → error.rb} +0 -0
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: claude_swarm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.11
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Paulo Arruda
|
|
@@ -145,7 +145,7 @@ files:
|
|
|
145
145
|
- ".rubocop.yml"
|
|
146
146
|
- ".rubocop_todo.yml"
|
|
147
147
|
- ".ruby-version"
|
|
148
|
-
- CHANGELOG.md
|
|
148
|
+
- CHANGELOG.claude-swarm.md
|
|
149
149
|
- CLAUDE.md
|
|
150
150
|
- CONTRIBUTING.md
|
|
151
151
|
- LICENSE
|
|
@@ -154,6 +154,7 @@ files:
|
|
|
154
154
|
- Rakefile
|
|
155
155
|
- analyze_coverage.rb
|
|
156
156
|
- claude-sdk-docs.md
|
|
157
|
+
- decisions/2025-11-22-001-global-agent-registry.md
|
|
157
158
|
- docs-team-swarm.yml
|
|
158
159
|
- docs/V1_TO_V2_MIGRATION_GUIDE.md
|
|
159
160
|
- docs/v1/README.md
|
|
@@ -167,6 +168,7 @@ files:
|
|
|
167
168
|
- docs/v2/guides/getting-started.md
|
|
168
169
|
- docs/v2/guides/memory-adapters.md
|
|
169
170
|
- docs/v2/guides/memory-defrag-guide.md
|
|
171
|
+
- docs/v2/guides/migrating-to-2.x.md
|
|
170
172
|
- docs/v2/guides/plugins.md
|
|
171
173
|
- docs/v2/guides/quick-start-cli.md
|
|
172
174
|
- docs/v2/guides/rails-integration.md
|
|
@@ -174,6 +176,7 @@ files:
|
|
|
174
176
|
- docs/v2/guides/swarm-memory.md
|
|
175
177
|
- docs/v2/reference/architecture-flow.md
|
|
176
178
|
- docs/v2/reference/cli.md
|
|
179
|
+
- docs/v2/reference/configuration_reference.md
|
|
177
180
|
- docs/v2/reference/event_payload_structures.md
|
|
178
181
|
- docs/v2/reference/execution-flow.md
|
|
179
182
|
- docs/v2/reference/ruby-dsl.md
|
|
@@ -304,7 +307,6 @@ files:
|
|
|
304
307
|
- lib/swarm_memory.rb
|
|
305
308
|
- lib/swarm_memory/adapters/base.rb
|
|
306
309
|
- lib/swarm_memory/adapters/filesystem_adapter.rb
|
|
307
|
-
- lib/swarm_memory/chat_extension.rb
|
|
308
310
|
- lib/swarm_memory/cli/commands.rb
|
|
309
311
|
- lib/swarm_memory/core/entry.rb
|
|
310
312
|
- lib/swarm_memory/core/frontmatter_parser.rb
|
|
@@ -317,7 +319,7 @@ files:
|
|
|
317
319
|
- lib/swarm_memory/dsl/memory_config.rb
|
|
318
320
|
- lib/swarm_memory/embeddings/embedder.rb
|
|
319
321
|
- lib/swarm_memory/embeddings/informers_embedder.rb
|
|
320
|
-
- lib/swarm_memory/
|
|
322
|
+
- lib/swarm_memory/error.rb
|
|
321
323
|
- lib/swarm_memory/integration/cli_registration.rb
|
|
322
324
|
- lib/swarm_memory/integration/configuration.rb
|
|
323
325
|
- lib/swarm_memory/integration/registration.rb
|
|
@@ -339,7 +341,6 @@ files:
|
|
|
339
341
|
- lib/swarm_memory/tools/memory_edit.rb
|
|
340
342
|
- lib/swarm_memory/tools/memory_glob.rb
|
|
341
343
|
- lib/swarm_memory/tools/memory_grep.rb
|
|
342
|
-
- lib/swarm_memory/tools/memory_multi_edit.rb
|
|
343
344
|
- lib/swarm_memory/tools/memory_read.rb
|
|
344
345
|
- lib/swarm_memory/tools/memory_write.rb
|
|
345
346
|
- lib/swarm_memory/utils.rb
|
|
@@ -348,19 +349,38 @@ files:
|
|
|
348
349
|
- lib/swarm_sdk/agent/RETRY_LOGIC.md
|
|
349
350
|
- lib/swarm_sdk/agent/builder.rb
|
|
350
351
|
- lib/swarm_sdk/agent/chat.rb
|
|
351
|
-
- lib/swarm_sdk/agent/
|
|
352
|
-
- lib/swarm_sdk/agent/
|
|
353
|
-
- lib/swarm_sdk/agent/
|
|
354
|
-
- lib/swarm_sdk/agent/
|
|
352
|
+
- lib/swarm_sdk/agent/chat_helpers/context_tracker.rb
|
|
353
|
+
- lib/swarm_sdk/agent/chat_helpers/event_emitter.rb
|
|
354
|
+
- lib/swarm_sdk/agent/chat_helpers/hook_integration.rb
|
|
355
|
+
- lib/swarm_sdk/agent/chat_helpers/instrumentation.rb
|
|
356
|
+
- lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb
|
|
357
|
+
- lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb
|
|
358
|
+
- lib/swarm_sdk/agent/chat_helpers/serialization.rb
|
|
359
|
+
- lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb
|
|
360
|
+
- lib/swarm_sdk/agent/chat_helpers/system_reminders.rb
|
|
361
|
+
- lib/swarm_sdk/agent/chat_helpers/token_tracking.rb
|
|
355
362
|
- lib/swarm_sdk/agent/context.rb
|
|
356
363
|
- lib/swarm_sdk/agent/context_manager.rb
|
|
357
364
|
- lib/swarm_sdk/agent/definition.rb
|
|
358
365
|
- lib/swarm_sdk/agent/llm_instrumentation_middleware.rb
|
|
366
|
+
- lib/swarm_sdk/agent/system_prompt_builder.rb
|
|
367
|
+
- lib/swarm_sdk/agent_registry.rb
|
|
368
|
+
- lib/swarm_sdk/builders/base_builder.rb
|
|
359
369
|
- lib/swarm_sdk/claude_code_agent_adapter.rb
|
|
370
|
+
- lib/swarm_sdk/concerns/cleanupable.rb
|
|
371
|
+
- lib/swarm_sdk/concerns/snapshotable.rb
|
|
372
|
+
- lib/swarm_sdk/concerns/validatable.rb
|
|
373
|
+
- lib/swarm_sdk/config.rb
|
|
360
374
|
- lib/swarm_sdk/configuration.rb
|
|
375
|
+
- lib/swarm_sdk/configuration/parser.rb
|
|
376
|
+
- lib/swarm_sdk/configuration/translator.rb
|
|
361
377
|
- lib/swarm_sdk/context_compactor.rb
|
|
362
378
|
- lib/swarm_sdk/context_compactor/metrics.rb
|
|
363
379
|
- lib/swarm_sdk/context_compactor/token_counter.rb
|
|
380
|
+
- lib/swarm_sdk/context_management/builder.rb
|
|
381
|
+
- lib/swarm_sdk/context_management/context.rb
|
|
382
|
+
- lib/swarm_sdk/custom_tool_registry.rb
|
|
383
|
+
- lib/swarm_sdk/defaults.rb
|
|
364
384
|
- lib/swarm_sdk/events_to_messages.rb
|
|
365
385
|
- lib/swarm_sdk/hooks/adapter.rb
|
|
366
386
|
- lib/swarm_sdk/hooks/context.rb
|
|
@@ -378,11 +398,11 @@ files:
|
|
|
378
398
|
- lib/swarm_sdk/model_aliases.json
|
|
379
399
|
- lib/swarm_sdk/models.json
|
|
380
400
|
- lib/swarm_sdk/models.rb
|
|
381
|
-
- lib/swarm_sdk/node/agent_config.rb
|
|
382
|
-
- lib/swarm_sdk/node/builder.rb
|
|
383
|
-
- lib/swarm_sdk/node/transformer_executor.rb
|
|
384
401
|
- lib/swarm_sdk/node_context.rb
|
|
385
|
-
- lib/swarm_sdk/
|
|
402
|
+
- lib/swarm_sdk/observer/builder.rb
|
|
403
|
+
- lib/swarm_sdk/observer/config.rb
|
|
404
|
+
- lib/swarm_sdk/observer/manager.rb
|
|
405
|
+
- lib/swarm_sdk/patterns/agent_observer.rb
|
|
386
406
|
- lib/swarm_sdk/permissions/config.rb
|
|
387
407
|
- lib/swarm_sdk/permissions/error_formatter.rb
|
|
388
408
|
- lib/swarm_sdk/permissions/path_matcher.rb
|
|
@@ -392,7 +412,6 @@ files:
|
|
|
392
412
|
- lib/swarm_sdk/plugin_registry.rb
|
|
393
413
|
- lib/swarm_sdk/proc_helpers.rb
|
|
394
414
|
- lib/swarm_sdk/prompts/base_system_prompt.md.erb
|
|
395
|
-
- lib/swarm_sdk/providers/openai_with_responses.rb
|
|
396
415
|
- lib/swarm_sdk/restore_result.rb
|
|
397
416
|
- lib/swarm_sdk/result.rb
|
|
398
417
|
- lib/swarm_sdk/snapshot.rb
|
|
@@ -403,6 +422,9 @@ files:
|
|
|
403
422
|
- lib/swarm_sdk/swarm/agent_initializer.rb
|
|
404
423
|
- lib/swarm_sdk/swarm/all_agents_builder.rb
|
|
405
424
|
- lib/swarm_sdk/swarm/builder.rb
|
|
425
|
+
- lib/swarm_sdk/swarm/executor.rb
|
|
426
|
+
- lib/swarm_sdk/swarm/hook_triggers.rb
|
|
427
|
+
- lib/swarm_sdk/swarm/logging_callbacks.rb
|
|
406
428
|
- lib/swarm_sdk/swarm/mcp_configurator.rb
|
|
407
429
|
- lib/swarm_sdk/swarm/swarm_registry_builder.rb
|
|
408
430
|
- lib/swarm_sdk/swarm/tool_configurator.rb
|
|
@@ -440,6 +462,12 @@ files:
|
|
|
440
462
|
- lib/swarm_sdk/utils.rb
|
|
441
463
|
- lib/swarm_sdk/validation_result.rb
|
|
442
464
|
- lib/swarm_sdk/version.rb
|
|
465
|
+
- lib/swarm_sdk/workflow.rb
|
|
466
|
+
- lib/swarm_sdk/workflow/agent_config.rb
|
|
467
|
+
- lib/swarm_sdk/workflow/builder.rb
|
|
468
|
+
- lib/swarm_sdk/workflow/executor.rb
|
|
469
|
+
- lib/swarm_sdk/workflow/node_builder.rb
|
|
470
|
+
- lib/swarm_sdk/workflow/transformer_executor.rb
|
|
443
471
|
- llms.claude-swarm.txt
|
|
444
472
|
- rubocop/cop/security/no_reflection_methods.rb
|
|
445
473
|
- rubocop/cop/security/no_ruby_llm_logger.rb
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module SwarmMemory
|
|
4
|
-
# Extension module for SwarmSDK::Agent::Chat
|
|
5
|
-
#
|
|
6
|
-
# Adds individual tool removal capability needed for:
|
|
7
|
-
# 1. Mode-based tool filtering (retrieval/interactive/researcher)
|
|
8
|
-
# 2. LoadSkill's fine-grained tool swapping
|
|
9
|
-
#
|
|
10
|
-
# This is injected into SwarmSDK::Agent::Chat when SwarmMemory is loaded.
|
|
11
|
-
module ChatExtension
|
|
12
|
-
# Remove a specific tool by name
|
|
13
|
-
#
|
|
14
|
-
# Used by SwarmMemory to filter tools based on memory mode.
|
|
15
|
-
# Unlike remove_mutable_tools (which removes ALL mutable tools),
|
|
16
|
-
# this removes a single tool by name.
|
|
17
|
-
#
|
|
18
|
-
# @param tool_name [String, Symbol] Tool name to remove
|
|
19
|
-
# @return [void]
|
|
20
|
-
def remove_tool(tool_name)
|
|
21
|
-
tool_sym = tool_name.to_sym
|
|
22
|
-
tool_str = tool_name.to_s
|
|
23
|
-
|
|
24
|
-
# Remove from @tools hash (tools are keyed by symbol)
|
|
25
|
-
@tools.delete(tool_sym)
|
|
26
|
-
@tools.delete(tool_str)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Inject into SwarmSDK when both gems are loaded
|
|
32
|
-
if defined?(SwarmSDK::Agent::Chat)
|
|
33
|
-
SwarmSDK::Agent::Chat.include(SwarmMemory::ChatExtension)
|
|
34
|
-
end
|
|
@@ -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
|