claude_swarm 1.0.4 → 1.0.6

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/Rakefile +4 -4
  4. data/docs/v2/CHANGELOG.swarm_cli.md +19 -0
  5. data/docs/v2/CHANGELOG.swarm_memory.md +19 -0
  6. data/docs/v2/CHANGELOG.swarm_sdk.md +92 -0
  7. data/docs/v2/README.md +56 -22
  8. data/docs/v2/guides/MEMORY_DEFRAG_GUIDE.md +811 -0
  9. data/docs/v2/guides/complete-tutorial.md +115 -3
  10. data/docs/v2/guides/getting-started.md +6 -6
  11. data/docs/v2/guides/rails-integration.md +6 -6
  12. data/docs/v2/reference/architecture-flow.md +407 -0
  13. data/docs/v2/reference/event_payload_structures.md +471 -0
  14. data/docs/v2/reference/execution-flow.md +600 -0
  15. data/docs/v2/reference/ruby-dsl.md +138 -5
  16. data/docs/v2/reference/swarm_memory_technical_details.md +2090 -0
  17. data/examples/v2/swarm_with_hooks.yml +1 -1
  18. data/lib/claude_swarm/cli.rb +9 -11
  19. data/lib/claude_swarm/commands/ps.rb +1 -2
  20. data/lib/claude_swarm/configuration.rb +2 -3
  21. data/lib/claude_swarm/mcp_generator.rb +4 -10
  22. data/lib/claude_swarm/orchestrator.rb +43 -44
  23. data/lib/claude_swarm/system_utils.rb +4 -4
  24. data/lib/claude_swarm/version.rb +1 -1
  25. data/lib/claude_swarm.rb +4 -9
  26. data/lib/swarm_cli/commands/mcp_serve.rb +2 -2
  27. data/lib/swarm_cli/commands/mcp_tools.rb +3 -3
  28. data/lib/swarm_cli/config_loader.rb +14 -13
  29. data/lib/swarm_cli/version.rb +1 -1
  30. data/lib/swarm_cli.rb +2 -0
  31. data/lib/swarm_memory/adapters/base.rb +4 -4
  32. data/lib/swarm_memory/adapters/filesystem_adapter.rb +0 -12
  33. data/lib/swarm_memory/core/storage.rb +66 -6
  34. data/lib/swarm_memory/integration/sdk_plugin.rb +14 -0
  35. data/lib/swarm_memory/optimization/defragmenter.rb +4 -0
  36. data/lib/swarm_memory/tools/memory_edit.rb +1 -0
  37. data/lib/swarm_memory/tools/memory_glob.rb +24 -1
  38. data/lib/swarm_memory/tools/memory_write.rb +2 -2
  39. data/lib/swarm_memory/version.rb +1 -1
  40. data/lib/swarm_memory.rb +2 -0
  41. data/lib/swarm_sdk/agent/chat.rb +1 -1
  42. data/lib/swarm_sdk/agent/definition.rb +18 -21
  43. data/lib/swarm_sdk/configuration.rb +34 -10
  44. data/lib/swarm_sdk/mcp.rb +16 -0
  45. data/lib/swarm_sdk/node/agent_config.rb +7 -2
  46. data/lib/swarm_sdk/node/builder.rb +130 -35
  47. data/lib/swarm_sdk/node_context.rb +75 -0
  48. data/lib/swarm_sdk/node_orchestrator.rb +219 -12
  49. data/lib/swarm_sdk/plugin.rb +73 -1
  50. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -126
  51. data/lib/swarm_sdk/result.rb +32 -6
  52. data/lib/swarm_sdk/swarm/builder.rb +1 -0
  53. data/lib/swarm_sdk/swarm.rb +32 -50
  54. data/lib/swarm_sdk/tools/delegate.rb +2 -2
  55. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +23 -2
  56. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +23 -2
  57. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +21 -4
  58. data/lib/swarm_sdk/tools/stores/storage.rb +4 -4
  59. data/lib/swarm_sdk/tools/think.rb +4 -1
  60. data/lib/swarm_sdk/tools/todo_write.rb +20 -8
  61. data/lib/swarm_sdk/version.rb +1 -1
  62. data/lib/swarm_sdk.rb +332 -27
  63. data/swarm_sdk.gemspec +1 -1
  64. metadata +9 -3
@@ -20,16 +20,28 @@ module SwarmSDK
20
20
  class NodeOrchestrator
21
21
  attr_reader :swarm_name, :nodes, :start_node
22
22
 
23
- def initialize(swarm_name:, agent_definitions:, nodes:, start_node:)
23
+ def initialize(swarm_name:, agent_definitions:, nodes:, start_node:, scratchpad_enabled: true)
24
24
  @swarm_name = swarm_name
25
25
  @agent_definitions = agent_definitions
26
26
  @nodes = nodes
27
27
  @start_node = start_node
28
+ @scratchpad_enabled = scratchpad_enabled
29
+ @agent_instance_cache = {} # Cache for preserving agent context across nodes
28
30
 
29
31
  validate!
30
32
  @execution_order = build_execution_order
31
33
  end
32
34
 
35
+ # Alias for compatibility with Swarm interface
36
+ alias_method :name, :swarm_name
37
+
38
+ # Return the lead agent of the start node for CLI compatibility
39
+ #
40
+ # @return [Symbol] Lead agent of the start node
41
+ def lead_agent
42
+ @nodes[@start_node].lead_agent
43
+ end
44
+
33
45
  # Execute the node workflow
34
46
  #
35
47
  # Executes nodes in topological order, passing output from each node
@@ -56,7 +68,12 @@ module SwarmSDK
56
68
  LogStream.emitter = LogCollector
57
69
  end
58
70
 
59
- @execution_order.each do |node_name|
71
+ # Dynamic execution with support for goto_node
72
+ execution_index = 0
73
+ last_result = nil
74
+
75
+ while execution_index < @execution_order.size
76
+ node_name = @execution_order[execution_index]
60
77
  node = @nodes[node_name]
61
78
  node_start_time = Time.now
62
79
 
@@ -102,13 +119,26 @@ module SwarmSDK
102
119
  # - Exit 2: Halt workflow with error from STDERR (STDOUT ignored)
103
120
  transformed = node.transform_input(input_context, current_input: current_input)
104
121
 
105
- # Check if transformer requested skipping execution
106
- # (from Ruby block returning hash OR bash command exit 1)
107
- if transformed.is_a?(Hash) && transformed[:skip_execution]
122
+ # Check for control flow from transformer
123
+ control_result = handle_transformer_control_flow(
124
+ transformed: transformed,
125
+ node_name: node_name,
126
+ node: node,
127
+ node_start_time: node_start_time,
128
+ )
129
+
130
+ case control_result[:action]
131
+ when :halt
132
+ return control_result[:result]
133
+ when :goto
134
+ execution_index = find_node_index(control_result[:target])
135
+ current_input = control_result[:content]
136
+ next
137
+ when :skip
108
138
  skip_execution = true
109
- skip_content = transformed[:content] || transformed["content"]
110
- else
111
- current_input = transformed
139
+ skip_content = control_result[:content]
140
+ when :continue
141
+ current_input = control_result[:content]
112
142
  end
113
143
  end
114
144
 
@@ -130,6 +160,9 @@ module SwarmSDK
130
160
  mini_swarm = build_swarm_for_node(node)
131
161
  result = mini_swarm.execute(current_input)
132
162
 
163
+ # Cache agent instances for context preservation
164
+ cache_agent_instances(mini_swarm, node)
165
+
133
166
  # If result has error, log it with backtrace
134
167
  if result.error
135
168
  RubyLLM.logger.error("NodeOrchestrator: Node '#{node_name}' failed: #{result.error.message}")
@@ -138,6 +171,7 @@ module SwarmSDK
138
171
  end
139
172
 
140
173
  results[node_name] = result
174
+ last_result = result
141
175
 
142
176
  # Transform output for next node using NodeContext
143
177
  output_context = NodeContext.for_output(
@@ -146,7 +180,29 @@ module SwarmSDK
146
180
  original_prompt: @original_prompt,
147
181
  node_name: node_name,
148
182
  )
149
- current_input = node.transform_output(output_context)
183
+ transformed_output = node.transform_output(output_context)
184
+
185
+ # Check for control flow from output transformer
186
+ control_result = handle_output_transformer_control_flow(
187
+ transformed: transformed_output,
188
+ node_name: node_name,
189
+ node: node,
190
+ node_start_time: node_start_time,
191
+ skip_execution: skip_execution,
192
+ result: result,
193
+ )
194
+
195
+ case control_result[:action]
196
+ when :halt
197
+ return control_result[:result]
198
+ when :goto
199
+ execution_index = find_node_index(control_result[:target])
200
+ current_input = control_result[:content]
201
+ emit_node_stop(node_name, node, result, Time.now - node_start_time, skip_execution)
202
+ next
203
+ when :continue
204
+ current_input = control_result[:content]
205
+ end
150
206
 
151
207
  # For agent-less nodes, update the result with transformed content
152
208
  # This ensures all_results contains the actual output, not the input
@@ -158,14 +214,17 @@ module SwarmSDK
158
214
  duration: result.duration,
159
215
  error: result.error,
160
216
  )
217
+ last_result = results[node_name]
161
218
  end
162
219
 
163
220
  # Emit node_stop event
164
221
  node_duration = Time.now - node_start_time
165
222
  emit_node_stop(node_name, node, result, node_duration, skip_execution)
223
+
224
+ execution_index += 1
166
225
  end
167
226
 
168
- results.values.last
227
+ last_result
169
228
  ensure
170
229
  # Reset logging state for next execution
171
230
  LogCollector.reset!
@@ -284,10 +343,18 @@ module SwarmSDK
284
343
  # Creates a new Swarm with only the agents specified in the node,
285
344
  # configured with the node's delegation topology.
286
345
  #
346
+ # For agents with reset_context: false, injects cached instances
347
+ # to preserve conversation history across nodes.
348
+ #
349
+ # Inherits scratchpad_enabled setting from NodeOrchestrator.
350
+ #
287
351
  # @param node [Node::Builder] Node configuration
288
352
  # @return [Swarm] Configured swarm instance
289
353
  def build_swarm_for_node(node)
290
- swarm = Swarm.new(name: "#{@swarm_name}:#{node.name}")
354
+ swarm = Swarm.new(
355
+ name: "#{@swarm_name}:#{node.name}",
356
+ scratchpad_enabled: @scratchpad_enabled,
357
+ )
291
358
 
292
359
  # Add each agent specified in this node
293
360
  node.agent_configs.each do |config|
@@ -306,6 +373,9 @@ module SwarmSDK
306
373
  # Set lead agent
307
374
  swarm.lead = node.lead_agent
308
375
 
376
+ # Inject cached agent instances for context preservation
377
+ inject_cached_agents(swarm, node)
378
+
309
379
  swarm
310
380
  end
311
381
 
@@ -359,7 +429,8 @@ module SwarmSDK
359
429
  if order.size < @nodes.size
360
430
  unprocessed = @nodes.keys - order
361
431
  raise CircularDependencyError,
362
- "Circular dependency detected. Unprocessed nodes: #{unprocessed.join(", ")}"
432
+ "Circular dependency detected. Unprocessed nodes: #{unprocessed.join(", ")}. " \
433
+ "Use goto_node in transformers to create loops instead of circular depends_on."
363
434
  end
364
435
 
365
436
  # Verify start_node is in the execution order
@@ -380,5 +451,141 @@ module SwarmSDK
380
451
 
381
452
  order
382
453
  end
454
+
455
+ # Handle control flow from input transformer
456
+ #
457
+ # @param transformed [String, Hash] Result from transformer
458
+ # @param node_name [Symbol] Current node name
459
+ # @param node [Node::Builder] Node configuration
460
+ # @param node_start_time [Time] Node execution start time
461
+ # @return [Hash] Control result with :action and relevant data
462
+ def handle_transformer_control_flow(transformed:, node_name:, node:, node_start_time:)
463
+ return { action: :continue, content: transformed } unless transformed.is_a?(Hash)
464
+
465
+ if transformed[:halt_workflow]
466
+ # Halt entire workflow
467
+ halt_result = Result.new(
468
+ content: transformed[:content],
469
+ agent: "halted:#{node_name}",
470
+ logs: [],
471
+ duration: Time.now - node_start_time,
472
+ )
473
+ emit_node_stop(node_name, node, halt_result, Time.now - node_start_time, false)
474
+ { action: :halt, result: halt_result }
475
+ elsif transformed[:goto_node]
476
+ # Jump to different node
477
+ { action: :goto, target: transformed[:goto_node], content: transformed[:content] }
478
+ elsif transformed[:skip_execution]
479
+ # Skip node execution
480
+ { action: :skip, content: transformed[:content] }
481
+ else
482
+ # No control flow - continue normally
483
+ { action: :continue, content: transformed[:content] }
484
+ end
485
+ end
486
+
487
+ # Handle control flow from output transformer
488
+ #
489
+ # @param transformed [String, Hash] Result from transformer
490
+ # @param node_name [Symbol] Current node name
491
+ # @param node [Node::Builder] Node configuration
492
+ # @param node_start_time [Time] Node execution start time
493
+ # @param skip_execution [Boolean] Whether node execution was skipped
494
+ # @param result [Result] Node execution result
495
+ # @return [Hash] Control result with :action and relevant data
496
+ def handle_output_transformer_control_flow(transformed:, node_name:, node:, node_start_time:, skip_execution:, result:)
497
+ # If not a hash, it's just transformed content - continue normally
498
+ return { action: :continue, content: transformed } unless transformed.is_a?(Hash)
499
+
500
+ if transformed[:halt_workflow]
501
+ # Halt entire workflow
502
+ halt_result = Result.new(
503
+ content: transformed[:content],
504
+ agent: result.agent,
505
+ logs: result.logs,
506
+ duration: result.duration,
507
+ )
508
+ emit_node_stop(node_name, node, halt_result, Time.now - node_start_time, skip_execution)
509
+ { action: :halt, result: halt_result }
510
+ elsif transformed[:goto_node]
511
+ # Jump to different node
512
+ { action: :goto, target: transformed[:goto_node], content: transformed[:content] }
513
+ else
514
+ # Hash without control flow keys - treat as regular hash with :content key
515
+ # This handles the case where transformer returns a hash that's not for control flow
516
+ { action: :continue, content: transformed[:content] || transformed }
517
+ end
518
+ end
519
+
520
+ # Find the index of a node in the execution order
521
+ #
522
+ # @param node_name [Symbol] Node name to find
523
+ # @return [Integer] Index in execution order
524
+ # @raise [ConfigurationError] If node not found
525
+ def find_node_index(node_name)
526
+ index = @execution_order.index(node_name)
527
+ unless index
528
+ raise ConfigurationError,
529
+ "goto_node target '#{node_name}' not found. Available nodes: #{@execution_order.join(", ")}"
530
+ end
531
+ index
532
+ end
533
+
534
+ # Cache agent instances from a swarm for potential reuse
535
+ #
536
+ # Only caches agents that have reset_context: false in this node.
537
+ # This allows preserving conversation history across nodes.
538
+ #
539
+ # @param swarm [Swarm] Swarm instance that just executed
540
+ # @param node [Node::Builder] Node configuration
541
+ # @return [void]
542
+ def cache_agent_instances(swarm, node)
543
+ return unless swarm.agents # Only cache if agents were initialized
544
+
545
+ node.agent_configs.each do |config|
546
+ agent_name = config[:agent]
547
+ reset_context = config[:reset_context]
548
+
549
+ # Only cache if reset_context is false
550
+ next if reset_context
551
+
552
+ # Cache the agent instance
553
+ agent_instance = swarm.agents[agent_name]
554
+ @agent_instance_cache[agent_name] = agent_instance if agent_instance
555
+ end
556
+ end
557
+
558
+ # Inject cached agent instances into a swarm
559
+ #
560
+ # For agents with reset_context: false, reuses cached instances to preserve context.
561
+ # Forces agent initialization first (by accessing .agents), then swaps in cached instances.
562
+ #
563
+ # @param swarm [Swarm] Swarm instance to inject into
564
+ # @param node [Node::Builder] Node configuration
565
+ # @return [void]
566
+ def inject_cached_agents(swarm, node)
567
+ # Check if any agents need context preservation
568
+ has_preserved_agents = node.agent_configs.any? { |c| !c[:reset_context] && @agent_instance_cache[c[:agent]] }
569
+ return unless has_preserved_agents
570
+
571
+ # Force agent initialization by accessing .agents (triggers lazy init)
572
+ # Then inject cached instances
573
+ agents_hash = swarm.agents
574
+
575
+ node.agent_configs.each do |config|
576
+ agent_name = config[:agent]
577
+ reset_context = config[:reset_context]
578
+
579
+ # Skip if reset_context is true (want fresh instance)
580
+ next if reset_context
581
+
582
+ # Check if we have a cached instance
583
+ cached_agent = @agent_instance_cache[agent_name]
584
+ next unless cached_agent
585
+
586
+ # Inject the cached instance (replace the freshly initialized one)
587
+ agents_hash[agent_name] = cached_agent
588
+ end
589
+ end
383
590
  end
384
591
  end
@@ -7,7 +7,32 @@ module SwarmSDK
7
7
  # Plugins are self-registering - they call SwarmSDK::PluginRegistry.register
8
8
  # when the gem is loaded.
9
9
  #
10
- # @example Implementing a plugin
10
+ # ## Adding Custom Attributes to Agents
11
+ #
12
+ # Plugins can add custom attributes to Agent::Definition that are preserved
13
+ # when agents are cloned (e.g., in NodeOrchestrator). To do this:
14
+ #
15
+ # 1. Add attr_reader to Agent::Definition for your attribute
16
+ # 2. Parse the attribute in Agent::Definition#initialize
17
+ # 3. Implement serialize_config to preserve it during serialization
18
+ #
19
+ # @example Plugin with custom agent attributes
20
+ # # 1. Extend Agent::Definition (in your plugin gem)
21
+ # module SwarmSDK
22
+ # module Agent
23
+ # class Definition
24
+ # attr_reader :my_custom_config
25
+ #
26
+ # alias_method :original_initialize, :initialize
27
+ # def initialize(name, config = {})
28
+ # @my_custom_config = config[:my_custom_config]
29
+ # original_initialize(name, config)
30
+ # end
31
+ # end
32
+ # end
33
+ # end
34
+ #
35
+ # # 2. Implement plugin with serialize_config
11
36
  # class MyPlugin < SwarmSDK::Plugin
12
37
  # def name
13
38
  # :my_plugin
@@ -20,9 +45,34 @@ module SwarmSDK
20
45
  # def create_tool(tool_name, context)
21
46
  # # Create and return tool instance
22
47
  # end
48
+ #
49
+ # # Preserve custom config when agents are cloned
50
+ # def serialize_config(agent_definition:)
51
+ # return {} unless agent_definition.my_custom_config
52
+ #
53
+ # { my_custom_config: agent_definition.my_custom_config }
54
+ # end
23
55
  # end
24
56
  #
25
57
  # SwarmSDK::PluginRegistry.register(MyPlugin.new)
58
+ #
59
+ # Now agents can use your custom config:
60
+ #
61
+ # agent :researcher do
62
+ # my_custom_config { option: "value" }
63
+ # end
64
+ #
65
+ # And it will be preserved when NodeOrchestrator clones the agent!
66
+ #
67
+ # @example Real-world: SwarmMemory plugin
68
+ # # SwarmMemory adds 'memory' attribute to agents
69
+ # class SDKPlugin < SwarmSDK::Plugin
70
+ # def serialize_config(agent_definition:)
71
+ # return {} unless agent_definition.memory
72
+ # { memory: agent_definition.memory }
73
+ # end
74
+ # end
75
+ #
26
76
  class Plugin
27
77
  # Plugin name (must be unique)
28
78
  #
@@ -143,5 +193,27 @@ module SwarmSDK
143
193
  def on_user_message(agent_name:, prompt:, is_first_message:)
144
194
  []
145
195
  end
196
+
197
+ # Contribute to agent serialization (optional)
198
+ #
199
+ # Called when Agent::Definition.to_h is invoked (e.g., for cloning agents
200
+ # in NodeOrchestrator). Plugins can return config keys that should be
201
+ # included in the serialized hash to preserve their state.
202
+ #
203
+ # This allows plugins to maintain their configuration when agents are
204
+ # cloned or serialized, without SwarmSDK needing to know about plugin-specific fields.
205
+ #
206
+ # @param agent_definition [Agent::Definition] Agent definition
207
+ # @return [Hash] Config keys to include in to_h (e.g., { memory: config })
208
+ #
209
+ # @example Memory plugin serialization
210
+ # def serialize_config(agent_definition:)
211
+ # return {} unless agent_definition.memory
212
+ #
213
+ # { memory: agent_definition.memory }
214
+ # end
215
+ def serialize_config(agent_definition:)
216
+ {}
217
+ end
146
218
  end
147
219
  end
@@ -69,139 +69,15 @@ When making changes to files, first understand the file's conventions. Mimic exi
69
69
  - When you edit something, first look at the surrounding context (especially imports/requires) to understand the choice of frameworks and libraries. Then consider how to make the given change in a way that is most consistent with existing patterns.
70
70
  - Always follow security best practices. Never introduce code that exposes or logs secrets and keys. Never commit secrets or keys to repositories.
71
71
 
72
- # Task Management
73
-
74
- You have access to the TodoWrite tool to help you manage and plan tasks. Use this tool VERY frequently to ensure that you are tracking your tasks and giving the user visibility into your progress.
75
- This tool is also EXTREMELY helpful for planning tasks, and for breaking down larger complex tasks into smaller steps. If you do not use this tool when planning, you may forget to do important tasks - and that is unacceptable.
76
-
77
- **CRITICAL WORKFLOW**: When starting a multi-step task:
78
- 1. **FIRST**: Analyze what needs to be done (search, read files, understand scope)
79
- 2. **SECOND**: Create a COMPLETE todo list with ALL known tasks before starting work
80
- 3. **THIRD**: Begin executing tasks, marking them in_progress → completed as you work
81
- 4. **ONLY add new todos** if you discover unexpected work during implementation
82
-
83
- **CRITICAL RULES FOR TODO COMPLETION**:
84
- - Mark EACH task as completed IMMEDIATELY after finishing it (do not batch updates)
85
- - You MUST complete ALL pending todos before giving your final answer to the user
86
- - If a task becomes irrelevant, remove it from the list or mark it completed with a note
87
- - NEVER leave in_progress or pending tasks when you finish responding to the user
88
- - Before giving your final response, verify all todos are marked completed
89
-
90
- Examples:
91
-
92
- <example>
93
- user: Run the build and fix any type errors
94
- assistant: I'll run the build first to identify all type errors, then create a complete todo list.
95
-
96
- [Runs build and finds 3 type errors in 3 different files]
97
-
98
- Now I'll create a complete todo list with all the work:
99
-
100
- [Uses TodoWrite to create full list:]
101
- 1. Fix type error in auth.ts:45 (in_progress)
102
- 2. Fix type error in user.ts:23 (pending)
103
- 3. Fix type error in api.ts:67 (pending)
104
- 4. Run build again to verify all fixes (pending)
105
-
106
- Starting with the first error in auth.ts...
107
- [Fixes auth.ts error]
108
-
109
- [Updates TodoWrite - marks task 1 completed, task 2 in_progress]
110
-
111
- Now fixing user.ts...
112
- [Fixes user.ts error]
113
-
114
- [Updates TodoWrite - marks task 2 completed, task 3 in_progress]
115
-
116
- Now fixing api.ts...
117
- [Fixes api.ts error]
118
-
119
- [Updates TodoWrite - marks task 3 completed, task 4 in_progress]
120
-
121
- Running build to verify...
122
- [Runs build - all errors fixed!]
123
-
124
- [Updates TodoWrite - marks task 4 completed]
125
-
126
- All type errors have been fixed and the build passes successfully.
127
- </example>
128
- In this example, the assistant: (1) analyzed scope first, (2) created COMPLETE todo list, (3) marked EACH task completed immediately after finishing it, (4) completed ALL tasks before giving final response.
129
-
130
- <example>
131
- user: Help me write a new feature that allows users to track their usage metrics and export them to various formats
132
-
133
- A: I'll help you implement a usage metrics tracking and export feature. Let me first research the codebase to understand the scope, then create a complete todo list.
134
-
135
- [Searches for existing metrics/telemetry code, examines architecture, identifies integration points]
136
-
137
- Based on my analysis, here's the complete implementation plan:
138
-
139
- [Uses TodoWrite to create full list:]
140
- 1. Create MetricsCollector class in src/metrics/ (in_progress)
141
- 2. Add database schema for metrics storage (pending)
142
- 3. Implement CSV export format (pending)
143
- 4. Implement JSON export format (pending)
144
- 5. Create MetricsService API endpoints (pending)
145
- 6. Add frontend UI for export (pending)
146
- 7. Write unit tests for collectors and exporters (pending)
147
- 8. Update documentation (pending)
148
-
149
- Now starting with the MetricsCollector class...
150
- [Implements MetricsCollector]
151
-
152
- [Updates TodoWrite - marks task 1 completed, task 2 in_progress]
153
-
154
- Adding database schema...
155
- [Creates schema]
156
-
157
- [Updates TodoWrite - marks task 2 completed, task 3 in_progress]
158
-
159
- Implementing CSV export...
160
- [Implements CSV exporter]
161
-
162
- [Updates TodoWrite - marks task 3 completed, task 4 in_progress]
163
-
164
- Implementing JSON export...
165
- [Implements JSON exporter]
166
-
167
- [Updates TodoWrite - marks task 4 completed, task 5 in_progress]
168
-
169
- Creating MetricsService API endpoints...
170
- [Implements API endpoints]
171
-
172
- [Updates TodoWrite - marks task 5 completed, task 6 in_progress]
173
-
174
- Adding frontend UI for export...
175
- [Implements UI components]
176
-
177
- [Updates TodoWrite - marks task 6 completed, task 7 in_progress]
178
-
179
- Writing unit tests...
180
- [Writes and runs tests - all pass]
181
-
182
- [Updates TodoWrite - marks task 7 completed, task 8 in_progress]
183
-
184
- Updating documentation...
185
- [Updates docs with usage examples]
186
-
187
- [Updates TodoWrite - marks task 8 completed]
188
-
189
- The metrics tracking and export feature is now complete. Users can collect metrics and export them to CSV or JSON formats through both the API and the frontend UI.
190
- </example>
191
-
192
72
  # Doing tasks
193
73
 
194
74
  The user will primarily request you perform tasks. This includes solving problems, adding new functionality, refactoring, explaining content, and more. For these tasks the following steps are recommended:
195
75
 
196
- - Use the TodoWrite tool to plan the task if required
197
76
  - Use the available search tools to understand the context and the user's query. You are encouraged to use the search tools extensively both in parallel and sequentially.
198
77
  - Implement the solution using all tools available to you
199
- - Mark each todo completed IMMEDIATELY after finishing it
200
78
  - Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the project documentation or search to determine the testing approach.
201
79
  - When you have completed a task, if there are linting or validation commands available to you, run them to ensure your work is correct. NEVER assume what these commands are - check the project documentation first.
202
80
  NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTANT to only commit when explicitly asked, otherwise the user will feel that you are being too proactive.
203
- - Before giving your final response: Ensure ALL todos are marked completed. NEVER leave pending or in_progress tasks.
204
- - IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.
205
81
 
206
82
  # Tool usage policy
207
83
 
@@ -211,8 +87,6 @@ NEVER commit changes unless the user explicitly asks you to. It is VERY IMPORTAN
211
87
  - If the user specifies that they want you to run tools "in parallel", you MUST send a single message with multiple tool use content blocks. For example, if you need to delegate a task to multiple agents in parallel, send a single message with multiple DelegateTask tool calls.
212
88
  - Use specialized tools instead of bash commands when possible, as this provides a better user experience. For file operations, use dedicated tools: Read for reading files instead of cat/head/tail, Edit/MultiEdit for editing instead of sed/awk, and Write for creating files instead of cat with heredoc or echo redirection. Reserve bash tools exclusively for actual system commands and terminal operations that require shell execution. NEVER use bash echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
213
89
 
214
- IMPORTANT: Always use the TodoWrite tool to plan and track tasks throughout the conversation.
215
-
216
90
  You MUST answer concisely with fewer than 4 lines of text (not including tool use or code generation), unless user asks for detail.
217
91
 
218
92
 
@@ -2,17 +2,17 @@
2
2
 
3
3
  module SwarmSDK
4
4
  class Result
5
- attr_reader :content, :agent, :cost, :tokens, :duration, :logs, :error, :metadata
5
+ attr_reader :content, :agent, :duration, :logs, :error, :metadata
6
6
 
7
- def initialize(content: nil, agent:, cost: 0.0, tokens: {}, duration: 0.0, logs: [], error: nil, metadata: {})
7
+ def initialize(content: nil, agent:, cost: nil, tokens: nil, duration: 0.0, logs: [], error: nil, metadata: {})
8
8
  @content = content
9
9
  @agent = agent
10
- @cost = cost
11
- @tokens = tokens
12
10
  @duration = duration
13
11
  @logs = logs
14
12
  @error = error
15
13
  @metadata = metadata
14
+ # Legacy parameters kept for backward compatibility but not stored
15
+ # Use total_cost and tokens methods instead which calculate from logs
16
16
  end
17
17
 
18
18
  def success?
@@ -23,12 +23,38 @@ module SwarmSDK
23
23
  !success?
24
24
  end
25
25
 
26
+ # Calculate total cost from logs
27
+ #
28
+ # Delegates to total_cost for consistency. This attribute is calculated
29
+ # dynamically rather than stored.
30
+ #
31
+ # @return [Float] Total cost in dollars
32
+ def cost
33
+ total_cost
34
+ end
35
+
36
+ # Get token breakdown from logs
37
+ #
38
+ # Returns input and output tokens from the last log entry with usage data.
39
+ # This attribute is calculated dynamically rather than stored.
40
+ #
41
+ # @return [Hash] Token breakdown with :input and :output keys, or empty hash if no usage data
42
+ def tokens
43
+ last_entry = @logs.reverse.find { |entry| entry.dig(:usage, :cumulative_input_tokens) }
44
+ return {} unless last_entry
45
+
46
+ {
47
+ input: last_entry.dig(:usage, :cumulative_input_tokens) || 0,
48
+ output: last_entry.dig(:usage, :cumulative_output_tokens) || 0,
49
+ }
50
+ end
51
+
26
52
  def to_h
27
53
  {
28
54
  content: @content,
29
55
  agent: @agent,
30
- cost: @cost,
31
- tokens: @tokens,
56
+ cost: cost,
57
+ tokens: tokens,
32
58
  duration: @duration,
33
59
  success: success?,
34
60
  error: @error&.message,
@@ -380,6 +380,7 @@ module SwarmSDK
380
380
  agent_definitions: agent_definitions,
381
381
  nodes: @nodes,
382
382
  start_node: @start_node,
383
+ scratchpad_enabled: @scratchpad_enabled,
383
384
  )
384
385
  end
385
386