swarm_sdk 2.7.13 → 3.0.0.alpha1

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 (183) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_sdk/ruby_llm_patches/chat_callbacks_patch.rb +43 -22
  3. data/lib/swarm_sdk/ruby_llm_patches/init.rb +6 -0
  4. data/lib/swarm_sdk/ruby_llm_patches/mcp_ssl_patch.rb +144 -0
  5. data/lib/swarm_sdk/ruby_llm_patches/tool_concurrency_patch.rb +3 -4
  6. data/lib/swarm_sdk/v3/agent.rb +1165 -0
  7. data/lib/swarm_sdk/v3/agent_builder.rb +533 -0
  8. data/lib/swarm_sdk/v3/agent_definition.rb +330 -0
  9. data/lib/swarm_sdk/v3/configuration.rb +490 -0
  10. data/lib/swarm_sdk/v3/debug_log.rb +86 -0
  11. data/lib/swarm_sdk/v3/event_stream.rb +130 -0
  12. data/lib/swarm_sdk/v3/hooks/context.rb +112 -0
  13. data/lib/swarm_sdk/v3/hooks/result.rb +115 -0
  14. data/lib/swarm_sdk/v3/hooks/runner.rb +128 -0
  15. data/lib/swarm_sdk/v3/mcp/connector.rb +183 -0
  16. data/lib/swarm_sdk/v3/mcp/mcp_error.rb +15 -0
  17. data/lib/swarm_sdk/v3/mcp/server_definition.rb +125 -0
  18. data/lib/swarm_sdk/v3/mcp/ssl_http_transport.rb +103 -0
  19. data/lib/swarm_sdk/v3/mcp/stdio_transport.rb +135 -0
  20. data/lib/swarm_sdk/v3/mcp/tool_proxy.rb +53 -0
  21. data/lib/swarm_sdk/v3/memory/adapters/base.rb +297 -0
  22. data/lib/swarm_sdk/v3/memory/adapters/faiss_support.rb +194 -0
  23. data/lib/swarm_sdk/v3/memory/adapters/filesystem_adapter.rb +212 -0
  24. data/lib/swarm_sdk/v3/memory/adapters/sqlite_adapter.rb +507 -0
  25. data/lib/swarm_sdk/v3/memory/adapters/vector_utils.rb +88 -0
  26. data/lib/swarm_sdk/v3/memory/card.rb +206 -0
  27. data/lib/swarm_sdk/v3/memory/cluster.rb +146 -0
  28. data/lib/swarm_sdk/v3/memory/compressor.rb +496 -0
  29. data/lib/swarm_sdk/v3/memory/consolidator.rb +427 -0
  30. data/lib/swarm_sdk/v3/memory/context_builder.rb +339 -0
  31. data/lib/swarm_sdk/v3/memory/edge.rb +105 -0
  32. data/lib/swarm_sdk/v3/memory/embedder.rb +185 -0
  33. data/lib/swarm_sdk/v3/memory/exposure_tracker.rb +104 -0
  34. data/lib/swarm_sdk/v3/memory/ingestion_pipeline.rb +394 -0
  35. data/lib/swarm_sdk/v3/memory/retriever.rb +289 -0
  36. data/lib/swarm_sdk/v3/memory/store.rb +489 -0
  37. data/lib/swarm_sdk/v3/skills/loader.rb +147 -0
  38. data/lib/swarm_sdk/v3/skills/manifest.rb +45 -0
  39. data/lib/swarm_sdk/v3/sub_task_agent.rb +248 -0
  40. data/lib/swarm_sdk/v3/tools/base.rb +80 -0
  41. data/lib/swarm_sdk/v3/tools/bash.rb +174 -0
  42. data/lib/swarm_sdk/v3/tools/clock.rb +32 -0
  43. data/lib/swarm_sdk/v3/tools/edit.rb +111 -0
  44. data/lib/swarm_sdk/v3/tools/glob.rb +96 -0
  45. data/lib/swarm_sdk/v3/tools/grep.rb +200 -0
  46. data/lib/swarm_sdk/v3/tools/message_teammate.rb +15 -0
  47. data/lib/swarm_sdk/v3/tools/message_user.rb +15 -0
  48. data/lib/swarm_sdk/v3/tools/read.rb +181 -0
  49. data/lib/swarm_sdk/v3/tools/read_tracker.rb +40 -0
  50. data/lib/swarm_sdk/v3/tools/registry.rb +208 -0
  51. data/lib/swarm_sdk/v3/tools/sub_task.rb +183 -0
  52. data/lib/swarm_sdk/v3/tools/think.rb +88 -0
  53. data/lib/swarm_sdk/v3/tools/write.rb +87 -0
  54. data/lib/swarm_sdk/v3.rb +145 -0
  55. metadata +84 -148
  56. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -175
  57. data/lib/swarm_sdk/agent/builder.rb +0 -680
  58. data/lib/swarm_sdk/agent/chat.rb +0 -1432
  59. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -375
  60. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  61. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  62. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -85
  63. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -290
  64. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  65. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  66. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -134
  67. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  68. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -146
  69. data/lib/swarm_sdk/agent/context.rb +0 -115
  70. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  71. data/lib/swarm_sdk/agent/definition.rb +0 -581
  72. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -226
  73. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  74. data/lib/swarm_sdk/agent/tool_registry.rb +0 -189
  75. data/lib/swarm_sdk/agent_registry.rb +0 -146
  76. data/lib/swarm_sdk/builders/base_builder.rb +0 -553
  77. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  78. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  79. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  80. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  81. data/lib/swarm_sdk/config.rb +0 -367
  82. data/lib/swarm_sdk/configuration/parser.rb +0 -397
  83. data/lib/swarm_sdk/configuration/translator.rb +0 -283
  84. data/lib/swarm_sdk/configuration.rb +0 -165
  85. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  86. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -102
  87. data/lib/swarm_sdk/context_compactor.rb +0 -335
  88. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  89. data/lib/swarm_sdk/context_management/context.rb +0 -328
  90. data/lib/swarm_sdk/custom_tool_registry.rb +0 -226
  91. data/lib/swarm_sdk/defaults.rb +0 -251
  92. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  93. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  94. data/lib/swarm_sdk/hooks/context.rb +0 -197
  95. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  96. data/lib/swarm_sdk/hooks/error.rb +0 -29
  97. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  98. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  99. data/lib/swarm_sdk/hooks/result.rb +0 -150
  100. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -256
  101. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  102. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  103. data/lib/swarm_sdk/log_collector.rb +0 -227
  104. data/lib/swarm_sdk/log_stream.rb +0 -127
  105. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  106. data/lib/swarm_sdk/model_aliases.json +0 -8
  107. data/lib/swarm_sdk/models.json +0 -44002
  108. data/lib/swarm_sdk/models.rb +0 -161
  109. data/lib/swarm_sdk/node_context.rb +0 -245
  110. data/lib/swarm_sdk/observer/builder.rb +0 -81
  111. data/lib/swarm_sdk/observer/config.rb +0 -45
  112. data/lib/swarm_sdk/observer/manager.rb +0 -236
  113. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  114. data/lib/swarm_sdk/permissions/config.rb +0 -239
  115. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  116. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  117. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  118. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  119. data/lib/swarm_sdk/plugin.rb +0 -309
  120. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  121. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  122. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  123. data/lib/swarm_sdk/restore_result.rb +0 -65
  124. data/lib/swarm_sdk/result.rb +0 -212
  125. data/lib/swarm_sdk/snapshot.rb +0 -156
  126. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  127. data/lib/swarm_sdk/state_restorer.rb +0 -476
  128. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  129. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -648
  130. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -195
  131. data/lib/swarm_sdk/swarm/builder.rb +0 -256
  132. data/lib/swarm_sdk/swarm/executor.rb +0 -290
  133. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -151
  134. data/lib/swarm_sdk/swarm/lazy_delegate_chat.rb +0 -372
  135. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -360
  136. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -270
  137. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  138. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -392
  139. data/lib/swarm_sdk/swarm.rb +0 -843
  140. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  141. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  142. data/lib/swarm_sdk/tools/base.rb +0 -63
  143. data/lib/swarm_sdk/tools/bash.rb +0 -280
  144. data/lib/swarm_sdk/tools/clock.rb +0 -46
  145. data/lib/swarm_sdk/tools/delegate.rb +0 -389
  146. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  147. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  148. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  149. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  150. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  151. data/lib/swarm_sdk/tools/edit.rb +0 -145
  152. data/lib/swarm_sdk/tools/glob.rb +0 -166
  153. data/lib/swarm_sdk/tools/grep.rb +0 -235
  154. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  155. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -167
  156. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  157. data/lib/swarm_sdk/tools/mcp_tool_stub.rb +0 -198
  158. data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
  159. data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
  160. data/lib/swarm_sdk/tools/read.rb +0 -261
  161. data/lib/swarm_sdk/tools/registry.rb +0 -205
  162. data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
  163. data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
  164. data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
  165. data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
  166. data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -273
  167. data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
  168. data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
  169. data/lib/swarm_sdk/tools/think.rb +0 -100
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -237
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -264
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/transcript_builder.rb +0 -278
  174. data/lib/swarm_sdk/utils.rb +0 -68
  175. data/lib/swarm_sdk/validation_result.rb +0 -33
  176. data/lib/swarm_sdk/version.rb +0 -5
  177. data/lib/swarm_sdk/workflow/agent_config.rb +0 -95
  178. data/lib/swarm_sdk/workflow/builder.rb +0 -227
  179. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  180. data/lib/swarm_sdk/workflow/node_builder.rb +0 -593
  181. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -250
  182. data/lib/swarm_sdk/workflow.rb +0 -589
  183. data/lib/swarm_sdk.rb +0 -718
@@ -1,335 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- # ContextCompactor implements intelligent conversation history compression
5
- #
6
- # The Hybrid Production Strategy combines three compression techniques:
7
- # 1. Tool result pruning - Aggressively truncate tool outputs (80% of tokens!)
8
- # 2. Checkpoint creation - LLM-generated summaries of conversation chunks
9
- # 3. Sliding window - Keep recent messages in full detail
10
- #
11
- # ## Usage
12
- #
13
- # # From Agent::Chat
14
- # metrics = chat.compact_context
15
- #
16
- # # With options
17
- # metrics = chat.compact_context(
18
- # tool_result_max_length: 500,
19
- # checkpoint_threshold: 50,
20
- # sliding_window_size: 20,
21
- # summarization_model: "claude-3-haiku-20240307"
22
- # )
23
- #
24
- # ## Metrics
25
- #
26
- # Returns a Metrics object with compression stats:
27
- # - original_message_count / compressed_message_count
28
- # - original_tokens / compressed_tokens
29
- # - compression_ratio (e.g., 0.15 = 15% of original)
30
- # - messages_removed / messages_summarized
31
- # - time_taken
32
- #
33
- class ContextCompactor
34
- # Default configuration
35
- DEFAULT_OPTIONS = {
36
- tool_result_max_length: 500, # Truncate tool results to N chars
37
- checkpoint_threshold: 50, # Create checkpoint after N messages
38
- sliding_window_size: 20, # Keep last N messages in full
39
- summarization_model: "claude-3-haiku-20240307", # Fast model for summaries
40
- preserve_system_messages: true, # Always keep system messages
41
- preserve_error_messages: true, # Always keep error messages
42
- }.freeze
43
-
44
- # Initialize compactor for a chat instance
45
- #
46
- # @param chat [Agent::Chat] The chat instance to compact
47
- # @param options [Hash] Configuration options (see DEFAULT_OPTIONS)
48
- def initialize(chat, options = {})
49
- @chat = chat
50
- @options = DEFAULT_OPTIONS.merge(options)
51
- @agent_name = chat.provider.respond_to?(:agent_name) ? chat.provider.agent_name : :unknown
52
- end
53
-
54
- # Compact the conversation history using hybrid production strategy
55
- #
56
- # Returns metrics about the compression operation.
57
- #
58
- # @return [ContextCompactor::Metrics] Compression metrics
59
- def compact
60
- start_time = Time.now
61
- original_messages = @chat.messages
62
-
63
- # Emit compression_started event
64
- LogStream.emit(
65
- type: "compression_started",
66
- agent: @agent_name,
67
- message_count: original_messages.size,
68
- estimated_tokens: TokenCounter.estimate_messages(original_messages),
69
- )
70
-
71
- # Step 1: Prune tool results
72
- pruned = prune_tool_results(original_messages)
73
-
74
- # Step 2: Create checkpoint if needed
75
- checkpointed = create_checkpoint_if_needed(pruned)
76
-
77
- # Step 3: Apply sliding window
78
- final_messages = apply_sliding_window(checkpointed)
79
-
80
- # Replace messages in chat
81
- replace_messages(final_messages)
82
-
83
- # Calculate metrics
84
- time_taken = Time.now - start_time
85
- metrics = ContextCompactor::Metrics.new(
86
- original_messages: original_messages,
87
- compressed_messages: final_messages,
88
- time_taken: time_taken,
89
- )
90
-
91
- # Emit compression_completed event
92
- LogStream.emit(
93
- type: "compression_completed",
94
- agent: @agent_name,
95
- original_message_count: metrics.original_message_count,
96
- compressed_message_count: metrics.compressed_message_count,
97
- original_tokens: metrics.original_tokens,
98
- compressed_tokens: metrics.compressed_tokens,
99
- compression_ratio: metrics.compression_ratio,
100
- messages_removed: metrics.messages_removed,
101
- messages_summarized: metrics.messages_summarized,
102
- time_taken: metrics.time_taken.round(3),
103
- )
104
-
105
- metrics
106
- end
107
-
108
- private
109
-
110
- # Step 1: Prune tool results to reduce token count
111
- #
112
- # Tool results often contain 80%+ of conversation tokens.
113
- # We truncate them aggressively while preserving errors.
114
- #
115
- # @param messages [Array<RubyLLM::Message>] Original messages
116
- # @return [Array<RubyLLM::Message>] Messages with pruned tool results
117
- def prune_tool_results(messages)
118
- max_length = @options[:tool_result_max_length]
119
-
120
- messages.map do |msg|
121
- # Only prune tool result messages
122
- next msg unless msg.role == :tool
123
-
124
- # Preserve error messages
125
- if @options[:preserve_error_messages] && msg.is_error
126
- next msg
127
- end
128
-
129
- # Truncate long tool results
130
- if msg.content.is_a?(String) && msg.content.length > max_length
131
- truncated_content = msg.content[0...max_length] + "\n\n[... truncated by context compaction ...]"
132
-
133
- # Create new message with truncated content
134
- # We can't modify messages in place, so we create a new one
135
- RubyLLM::Message.new(
136
- role: :tool,
137
- content: truncated_content,
138
- tool_call_id: msg.tool_call_id,
139
- )
140
- else
141
- msg
142
- end
143
- end
144
- end
145
-
146
- # Step 2: Create checkpoint if conversation is long enough
147
- #
148
- # Checkpoints are LLM-generated summaries that preserve context
149
- # while drastically reducing token count. We keep recent messages
150
- # in full detail and checkpoint older conversation.
151
- #
152
- # @param messages [Array<RubyLLM::Message>] Pruned messages
153
- # @return [Array<RubyLLM::Message>] Messages with checkpoint
154
- def create_checkpoint_if_needed(messages)
155
- threshold = @options[:checkpoint_threshold]
156
- window_size = @options[:sliding_window_size]
157
-
158
- # Only checkpoint if we have enough messages
159
- return messages if messages.size <= threshold
160
-
161
- # Separate system messages, old messages, and recent messages
162
- system_messages = messages.select { |m| m.role == :system }
163
- non_system_messages = messages.reject { |m| m.role == :system }
164
-
165
- # Keep recent messages, checkpoint the rest
166
- recent_messages = non_system_messages.last(window_size)
167
- old_messages = non_system_messages[0...-window_size]
168
-
169
- # Create checkpoint summary of old messages
170
- checkpoint_message = create_checkpoint_summary(old_messages)
171
-
172
- # Reconstruct: system messages + checkpoint + recent messages
173
- system_messages + [checkpoint_message] + recent_messages
174
- end
175
-
176
- # Step 3: Apply sliding window to keep conversation size bounded
177
- #
178
- # After checkpointing, we still apply a sliding window to ensure
179
- # the conversation doesn't grow unbounded.
180
- #
181
- # @param messages [Array<RubyLLM::Message>] Checkpointed messages
182
- # @return [Array<RubyLLM::Message>] Final messages
183
- def apply_sliding_window(messages)
184
- window_size = @options[:sliding_window_size]
185
-
186
- # Separate system messages from others
187
- system_messages = messages.select { |m| m.role == :system }
188
- non_system_messages = messages.reject { |m| m.role == :system }
189
-
190
- # Keep only the sliding window of non-system messages
191
- recent_messages = non_system_messages.last(window_size)
192
-
193
- # Always include system messages
194
- system_messages + recent_messages
195
- end
196
-
197
- # Create a checkpoint summary using an LLM
198
- #
199
- # Uses a fast model (Haiku) to generate a concise summary of
200
- # the conversation chunk that preserves critical context.
201
- #
202
- # @param messages [Array<RubyLLM::Message>] Messages to summarize
203
- # @return [RubyLLM::Message] Checkpoint message
204
- def create_checkpoint_summary(messages)
205
- # Extract key information for summarization
206
- user_messages = messages.select { |m| m.role == :user }.map(&:content).compact
207
- assistant_messages = messages.select { |m| m.role == :assistant }.map(&:content).compact
208
- tool_calls = messages.select { |m| m.role == :assistant && m.tool_calls&.any? }
209
-
210
- # Build summarization prompt
211
- prompt = build_summarization_prompt(
212
- user_messages: user_messages,
213
- assistant_messages: assistant_messages,
214
- tool_calls: tool_calls,
215
- message_count: messages.size,
216
- )
217
-
218
- # Generate summary using fast model
219
- summary = generate_summary(prompt)
220
-
221
- # Create checkpoint message
222
- checkpoint_content = <<~CHECKPOINT
223
- [CONVERSATION CHECKPOINT - #{Time.now.utc.iso8601}]
224
-
225
- #{summary}
226
-
227
- --- Continuing conversation from this point ---
228
- CHECKPOINT
229
-
230
- RubyLLM::Message.new(
231
- role: :system,
232
- content: checkpoint_content,
233
- )
234
- end
235
-
236
- # Build the summarization prompt for the LLM
237
- #
238
- # @param user_messages [Array<String>] User message contents
239
- # @param assistant_messages [Array<String>] Assistant message contents
240
- # @param tool_calls [Array<RubyLLM::Message>] Messages with tool calls
241
- # @param message_count [Integer] Total messages being summarized
242
- # @return [String] Summarization prompt
243
- def build_summarization_prompt(user_messages:, assistant_messages:, tool_calls:, message_count:)
244
- # Format tool calls for context
245
- tools_used = tool_calls.flat_map do |msg|
246
- msg.tool_calls.map { |_id, tc| tc.name }
247
- end.uniq
248
-
249
- # Get last few user messages for context
250
- recent_user_messages = user_messages.last(5).join("\n---\n")
251
-
252
- <<~PROMPT
253
- You are a conversation summarization specialist. Create a concise summary of this conversation
254
- that preserves all critical information needed for the assistant to continue working effectively.
255
-
256
- CONVERSATION STATS:
257
- - Total messages: #{message_count}
258
- - User messages: #{user_messages.size}
259
- - Assistant responses: #{assistant_messages.size}
260
- - Tools used: #{tools_used.join(", ")}
261
-
262
- RECENT USER REQUESTS (last 5):
263
- #{recent_user_messages}
264
-
265
- INSTRUCTIONS:
266
- Create a structured summary with these sections:
267
-
268
- ## Summary
269
- Brief overview of what has been discussed and accomplished (2-3 sentences)
270
-
271
- ## Key Facts Discovered
272
- - List important facts, findings, or observations
273
- - Include file paths, variable names, configurations discussed
274
- - Note any errors or issues encountered
275
-
276
- ## Decisions Made
277
- - List key decisions or approaches agreed upon
278
- - Include rationale if relevant
279
-
280
- ## Current State
281
- - What is the current state of the work?
282
- - What files or systems have been modified?
283
- - What is working / what needs work?
284
-
285
- ## Tools & Actions Completed
286
- - Summarize major tool calls and their outcomes
287
- - Focus on successful operations and their results
288
-
289
- Be concise but comprehensive. Preserve all information the assistant will need to continue
290
- the conversation seamlessly. Use bullet points for clarity.
291
- PROMPT
292
- end
293
-
294
- # Generate summary using a fast LLM model
295
- #
296
- # @param prompt [String] Summarization prompt
297
- # @return [String] Generated summary
298
- def generate_summary(prompt)
299
- # Create a temporary chat for summarization
300
- summary_chat = RubyLLM::Chat.new(
301
- model: @options[:summarization_model],
302
- context: @chat.provider.client.context, # Use same context (API keys, etc.)
303
- )
304
-
305
- summary_chat.with_instructions("You are a precise conversation summarization assistant.")
306
-
307
- response = summary_chat.ask(prompt)
308
- response.content
309
- rescue StandardError => e
310
- # If summarization fails, create a simple fallback summary
311
- LogStream.emit_error(e, source: "context_compactor", context: "generate_summary", agent: @agent_name)
312
- RubyLLM.logger.debug("ContextCompactor: Summarization failed: #{e.message}")
313
-
314
- <<~FALLBACK
315
- ## Summary
316
- Previous conversation involved multiple exchanges. Conversation compacted due to context limits.
317
-
318
- ## Note
319
- Summarization failed - continuing with reduced context. If critical information was lost,
320
- please ask the user to provide it again.
321
- FALLBACK
322
- end
323
-
324
- # Replace messages in the chat
325
- #
326
- # Delegates to the Chat's replace_messages method which provides
327
- # a safe abstraction over the internal message array.
328
- #
329
- # @param new_messages [Array<RubyLLM::Message>] New message array
330
- # @return [void]
331
- def replace_messages(new_messages)
332
- @chat.replace_messages(new_messages)
333
- end
334
- end
335
- end
@@ -1,128 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module ContextManagement
5
- # DSL for defining context management handlers
6
- #
7
- # This builder provides a clean, idiomatic way to register handlers for
8
- # context warning thresholds. Handlers receive a rich context object
9
- # with message manipulation methods.
10
- #
11
- # @example Basic usage
12
- # context_management do
13
- # on :warning_60 do |ctx|
14
- # ctx.compress_tool_results(keep_recent: 10)
15
- # end
16
- #
17
- # on :warning_80 do |ctx|
18
- # ctx.prune_old_messages(keep_recent: 20)
19
- # end
20
- # end
21
- #
22
- # @example Progressive compression
23
- # context_management do
24
- # on :warning_60 do |ctx|
25
- # ctx.compress_tool_results(keep_recent: 15, truncate_to: 500)
26
- # end
27
- #
28
- # on :warning_80 do |ctx|
29
- # ctx.prune_old_messages(keep_recent: 30)
30
- # ctx.compress_tool_results(keep_recent: 5, truncate_to: 200)
31
- # end
32
- #
33
- # on :warning_90 do |ctx|
34
- # ctx.log_action("emergency_pruning", tokens_remaining: ctx.tokens_remaining)
35
- # ctx.prune_old_messages(keep_recent: 15)
36
- # end
37
- # end
38
- class Builder
39
- # Map semantic event names to threshold percentages
40
- EVENT_MAP = {
41
- warning_60: 60,
42
- warning_80: 80,
43
- warning_90: 90,
44
- }.freeze
45
-
46
- def initialize
47
- @handlers = {} # { threshold => block }
48
- end
49
-
50
- # Register a handler for a context warning threshold
51
- #
52
- # Handlers take full responsibility for managing context at their threshold.
53
- # When a handler is registered for a threshold, automatic compression is disabled
54
- # for that threshold.
55
- #
56
- # @param event [Symbol] Event name (:warning_60, :warning_80, :warning_90)
57
- # @yield [ContextManagement::Context] Context with message manipulation methods
58
- # @return [void]
59
- #
60
- # @raise [ArgumentError] If event is unknown or block is missing
61
- #
62
- # @example Compress tool results at 60%
63
- # on :warning_60 do |ctx|
64
- # ctx.compress_tool_results(keep_recent: 10)
65
- # end
66
- #
67
- # @example Custom logic at 80%
68
- # on :warning_80 do |ctx|
69
- # if ctx.usage_percentage > 85
70
- # ctx.prune_old_messages(keep_recent: 10)
71
- # else
72
- # ctx.summarize_old_exchanges(older_than: 20)
73
- # end
74
- # end
75
- #
76
- # @example Log and prune at 90%
77
- # on :warning_90 do |ctx|
78
- # ctx.log_action("critical_threshold", remaining: ctx.tokens_remaining)
79
- # ctx.prune_old_messages(keep_recent: 10)
80
- # end
81
- def on(event, &block)
82
- threshold = EVENT_MAP[event]
83
- raise ArgumentError, "Unknown event: #{event}. Valid events: #{EVENT_MAP.keys.join(", ")}" unless threshold
84
- raise ArgumentError, "Block required for #{event}" unless block
85
-
86
- @handlers[threshold] = block
87
- end
88
-
89
- # Build hook definitions from handlers
90
- #
91
- # Creates Hooks::Definition objects that wrap user blocks to provide
92
- # rich context objects instead of raw Hooks::Context. Each handler
93
- # becomes a hook for the :context_warning event.
94
- #
95
- # @return [Array<Hooks::Definition>] Hook definitions for :context_warning event
96
- def build
97
- @handlers.map do |threshold, user_block|
98
- # Create a hook that filters by threshold and wraps context
99
- Hooks::Definition.new(
100
- event: :context_warning,
101
- matcher: nil, # No tool matching needed
102
- priority: 0,
103
- proc: create_threshold_matcher(threshold, user_block),
104
- )
105
- end
106
- end
107
-
108
- private
109
-
110
- # Create a proc that matches threshold and wraps context
111
- #
112
- # @param target_threshold [Integer] Threshold to match (60, 80, 90)
113
- # @param user_block [Proc] User's handler block
114
- # @return [Proc] Hook proc
115
- def create_threshold_matcher(target_threshold, user_block)
116
- proc do |hooks_context|
117
- # Only execute for matching threshold
118
- current_threshold = hooks_context.metadata[:threshold]
119
- next unless current_threshold == target_threshold
120
-
121
- # Wrap in rich context object
122
- rich_context = Context.new(hooks_context)
123
- user_block.call(rich_context)
124
- end
125
- end
126
- end
127
- end
128
- end