swarm_memory 2.1.5 → 2.1.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.
- checksums.yaml +4 -4
- data/lib/swarm_memory/version.rb +1 -1
- metadata +5 -184
- data/lib/claude_swarm/base_executor.rb +0 -133
- data/lib/claude_swarm/claude_code_executor.rb +0 -349
- data/lib/claude_swarm/claude_mcp_server.rb +0 -78
- data/lib/claude_swarm/cli.rb +0 -697
- data/lib/claude_swarm/commands/ps.rb +0 -215
- data/lib/claude_swarm/commands/show.rb +0 -139
- data/lib/claude_swarm/configuration.rb +0 -373
- data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
- data/lib/claude_swarm/json_handler.rb +0 -91
- data/lib/claude_swarm/mcp_generator.rb +0 -230
- data/lib/claude_swarm/openai/chat_completion.rb +0 -256
- data/lib/claude_swarm/openai/executor.rb +0 -256
- data/lib/claude_swarm/openai/responses.rb +0 -319
- data/lib/claude_swarm/orchestrator.rb +0 -878
- data/lib/claude_swarm/process_tracker.rb +0 -78
- data/lib/claude_swarm/session_cost_calculator.rb +0 -209
- data/lib/claude_swarm/session_path.rb +0 -42
- data/lib/claude_swarm/settings_generator.rb +0 -77
- data/lib/claude_swarm/system_utils.rb +0 -46
- data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
- data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
- data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
- data/lib/claude_swarm/tools/task_tool.rb +0 -63
- data/lib/claude_swarm/version.rb +0 -5
- data/lib/claude_swarm/worktree_manager.rb +0 -475
- data/lib/claude_swarm/yaml_loader.rb +0 -22
- data/lib/claude_swarm.rb +0 -67
- data/lib/swarm_cli/cli.rb +0 -201
- data/lib/swarm_cli/command_registry.rb +0 -61
- data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
- data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
- data/lib/swarm_cli/commands/migrate.rb +0 -55
- data/lib/swarm_cli/commands/run.rb +0 -173
- data/lib/swarm_cli/config_loader.rb +0 -98
- data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
- data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
- data/lib/swarm_cli/interactive_repl.rb +0 -924
- data/lib/swarm_cli/mcp_serve_options.rb +0 -44
- data/lib/swarm_cli/mcp_tools_options.rb +0 -59
- data/lib/swarm_cli/migrate_options.rb +0 -54
- data/lib/swarm_cli/migrator.rb +0 -132
- data/lib/swarm_cli/options.rb +0 -151
- data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
- data/lib/swarm_cli/ui/components/content_block.rb +0 -120
- data/lib/swarm_cli/ui/components/divider.rb +0 -57
- data/lib/swarm_cli/ui/components/panel.rb +0 -62
- data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
- data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
- data/lib/swarm_cli/ui/formatters/number.rb +0 -58
- data/lib/swarm_cli/ui/formatters/text.rb +0 -77
- data/lib/swarm_cli/ui/formatters/time.rb +0 -73
- data/lib/swarm_cli/ui/icons.rb +0 -36
- data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
- data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
- data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
- data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
- data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
- data/lib/swarm_cli/version.rb +0 -5
- data/lib/swarm_cli.rb +0 -46
- data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
- data/lib/swarm_sdk/agent/builder.rb +0 -552
- data/lib/swarm_sdk/agent/chat.rb +0 -774
- data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
- data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
- data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
- data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
- data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
- data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
- data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
- data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
- data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
- data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
- data/lib/swarm_sdk/agent/context.rb +0 -116
- data/lib/swarm_sdk/agent/context_manager.rb +0 -315
- data/lib/swarm_sdk/agent/definition.rb +0 -477
- data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
- data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
- data/lib/swarm_sdk/builders/base_builder.rb +0 -409
- data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
- data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
- data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
- data/lib/swarm_sdk/concerns/validatable.rb +0 -55
- data/lib/swarm_sdk/configuration/parser.rb +0 -353
- data/lib/swarm_sdk/configuration/translator.rb +0 -255
- data/lib/swarm_sdk/configuration.rb +0 -135
- data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
- data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
- data/lib/swarm_sdk/context_compactor.rb +0 -335
- data/lib/swarm_sdk/context_management/builder.rb +0 -128
- data/lib/swarm_sdk/context_management/context.rb +0 -328
- data/lib/swarm_sdk/defaults.rb +0 -196
- data/lib/swarm_sdk/events_to_messages.rb +0 -199
- data/lib/swarm_sdk/hooks/adapter.rb +0 -359
- data/lib/swarm_sdk/hooks/context.rb +0 -197
- data/lib/swarm_sdk/hooks/definition.rb +0 -80
- data/lib/swarm_sdk/hooks/error.rb +0 -29
- data/lib/swarm_sdk/hooks/executor.rb +0 -146
- data/lib/swarm_sdk/hooks/registry.rb +0 -147
- data/lib/swarm_sdk/hooks/result.rb +0 -150
- data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
- data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
- data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
- data/lib/swarm_sdk/log_collector.rb +0 -227
- data/lib/swarm_sdk/log_stream.rb +0 -127
- data/lib/swarm_sdk/markdown_parser.rb +0 -75
- data/lib/swarm_sdk/model_aliases.json +0 -8
- data/lib/swarm_sdk/models.json +0 -1
- data/lib/swarm_sdk/models.rb +0 -120
- data/lib/swarm_sdk/node_context.rb +0 -245
- data/lib/swarm_sdk/observer/builder.rb +0 -81
- data/lib/swarm_sdk/observer/config.rb +0 -45
- data/lib/swarm_sdk/observer/manager.rb +0 -236
- data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
- data/lib/swarm_sdk/permissions/config.rb +0 -239
- data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
- data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
- data/lib/swarm_sdk/permissions/validator.rb +0 -173
- data/lib/swarm_sdk/permissions_builder.rb +0 -122
- data/lib/swarm_sdk/plugin.rb +0 -309
- data/lib/swarm_sdk/plugin_registry.rb +0 -101
- data/lib/swarm_sdk/proc_helpers.rb +0 -53
- data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
- data/lib/swarm_sdk/restore_result.rb +0 -65
- data/lib/swarm_sdk/result.rb +0 -123
- data/lib/swarm_sdk/snapshot.rb +0 -156
- data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
- data/lib/swarm_sdk/state_restorer.rb +0 -476
- data/lib/swarm_sdk/state_snapshot.rb +0 -334
- data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
- data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
- data/lib/swarm_sdk/swarm/builder.rb +0 -249
- data/lib/swarm_sdk/swarm/executor.rb +0 -213
- data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
- data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
- data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
- data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
- data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
- data/lib/swarm_sdk/swarm.rb +0 -717
- data/lib/swarm_sdk/swarm_loader.rb +0 -145
- data/lib/swarm_sdk/swarm_registry.rb +0 -136
- data/lib/swarm_sdk/tools/bash.rb +0 -282
- data/lib/swarm_sdk/tools/clock.rb +0 -44
- data/lib/swarm_sdk/tools/delegate.rb +0 -267
- data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
- data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
- data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
- data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
- data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
- data/lib/swarm_sdk/tools/edit.rb +0 -145
- data/lib/swarm_sdk/tools/glob.rb +0 -166
- data/lib/swarm_sdk/tools/grep.rb +0 -235
- data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
- data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
- data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
- data/lib/swarm_sdk/tools/multi_edit.rb +0 -236
- data/lib/swarm_sdk/tools/path_resolver.rb +0 -92
- data/lib/swarm_sdk/tools/read.rb +0 -261
- data/lib/swarm_sdk/tools/registry.rb +0 -205
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_list.rb +0 -117
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_read.rb +0 -97
- data/lib/swarm_sdk/tools/scratchpad/scratchpad_write.rb +0 -108
- data/lib/swarm_sdk/tools/stores/read_tracker.rb +0 -96
- data/lib/swarm_sdk/tools/stores/scratchpad_storage.rb +0 -272
- data/lib/swarm_sdk/tools/stores/storage.rb +0 -142
- data/lib/swarm_sdk/tools/stores/todo_manager.rb +0 -65
- data/lib/swarm_sdk/tools/think.rb +0 -98
- data/lib/swarm_sdk/tools/todo_write.rb +0 -235
- data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
- data/lib/swarm_sdk/tools/write.rb +0 -112
- data/lib/swarm_sdk/utils.rb +0 -68
- data/lib/swarm_sdk/validation_result.rb +0 -33
- data/lib/swarm_sdk/version.rb +0 -5
- data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
- data/lib/swarm_sdk/workflow/builder.rb +0 -143
- data/lib/swarm_sdk/workflow/executor.rb +0 -497
- data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
- data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
- data/lib/swarm_sdk/workflow.rb +0 -554
- data/lib/swarm_sdk.rb +0 -524
|
@@ -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
|