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.
Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/lib/swarm_memory/version.rb +1 -1
  3. metadata +5 -184
  4. data/lib/claude_swarm/base_executor.rb +0 -133
  5. data/lib/claude_swarm/claude_code_executor.rb +0 -349
  6. data/lib/claude_swarm/claude_mcp_server.rb +0 -78
  7. data/lib/claude_swarm/cli.rb +0 -697
  8. data/lib/claude_swarm/commands/ps.rb +0 -215
  9. data/lib/claude_swarm/commands/show.rb +0 -139
  10. data/lib/claude_swarm/configuration.rb +0 -373
  11. data/lib/claude_swarm/hooks/session_start_hook.rb +0 -42
  12. data/lib/claude_swarm/json_handler.rb +0 -91
  13. data/lib/claude_swarm/mcp_generator.rb +0 -230
  14. data/lib/claude_swarm/openai/chat_completion.rb +0 -256
  15. data/lib/claude_swarm/openai/executor.rb +0 -256
  16. data/lib/claude_swarm/openai/responses.rb +0 -319
  17. data/lib/claude_swarm/orchestrator.rb +0 -878
  18. data/lib/claude_swarm/process_tracker.rb +0 -78
  19. data/lib/claude_swarm/session_cost_calculator.rb +0 -209
  20. data/lib/claude_swarm/session_path.rb +0 -42
  21. data/lib/claude_swarm/settings_generator.rb +0 -77
  22. data/lib/claude_swarm/system_utils.rb +0 -46
  23. data/lib/claude_swarm/templates/generation_prompt.md.erb +0 -230
  24. data/lib/claude_swarm/tools/reset_session_tool.rb +0 -24
  25. data/lib/claude_swarm/tools/session_info_tool.rb +0 -24
  26. data/lib/claude_swarm/tools/task_tool.rb +0 -63
  27. data/lib/claude_swarm/version.rb +0 -5
  28. data/lib/claude_swarm/worktree_manager.rb +0 -475
  29. data/lib/claude_swarm/yaml_loader.rb +0 -22
  30. data/lib/claude_swarm.rb +0 -67
  31. data/lib/swarm_cli/cli.rb +0 -201
  32. data/lib/swarm_cli/command_registry.rb +0 -61
  33. data/lib/swarm_cli/commands/mcp_serve.rb +0 -130
  34. data/lib/swarm_cli/commands/mcp_tools.rb +0 -148
  35. data/lib/swarm_cli/commands/migrate.rb +0 -55
  36. data/lib/swarm_cli/commands/run.rb +0 -173
  37. data/lib/swarm_cli/config_loader.rb +0 -98
  38. data/lib/swarm_cli/formatters/human_formatter.rb +0 -781
  39. data/lib/swarm_cli/formatters/json_formatter.rb +0 -51
  40. data/lib/swarm_cli/interactive_repl.rb +0 -924
  41. data/lib/swarm_cli/mcp_serve_options.rb +0 -44
  42. data/lib/swarm_cli/mcp_tools_options.rb +0 -59
  43. data/lib/swarm_cli/migrate_options.rb +0 -54
  44. data/lib/swarm_cli/migrator.rb +0 -132
  45. data/lib/swarm_cli/options.rb +0 -151
  46. data/lib/swarm_cli/ui/components/agent_badge.rb +0 -33
  47. data/lib/swarm_cli/ui/components/content_block.rb +0 -120
  48. data/lib/swarm_cli/ui/components/divider.rb +0 -57
  49. data/lib/swarm_cli/ui/components/panel.rb +0 -62
  50. data/lib/swarm_cli/ui/components/usage_stats.rb +0 -70
  51. data/lib/swarm_cli/ui/formatters/cost.rb +0 -49
  52. data/lib/swarm_cli/ui/formatters/number.rb +0 -58
  53. data/lib/swarm_cli/ui/formatters/text.rb +0 -77
  54. data/lib/swarm_cli/ui/formatters/time.rb +0 -73
  55. data/lib/swarm_cli/ui/icons.rb +0 -36
  56. data/lib/swarm_cli/ui/renderers/event_renderer.rb +0 -188
  57. data/lib/swarm_cli/ui/state/agent_color_cache.rb +0 -45
  58. data/lib/swarm_cli/ui/state/depth_tracker.rb +0 -40
  59. data/lib/swarm_cli/ui/state/spinner_manager.rb +0 -170
  60. data/lib/swarm_cli/ui/state/usage_tracker.rb +0 -62
  61. data/lib/swarm_cli/version.rb +0 -5
  62. data/lib/swarm_cli.rb +0 -46
  63. data/lib/swarm_sdk/agent/RETRY_LOGIC.md +0 -127
  64. data/lib/swarm_sdk/agent/builder.rb +0 -552
  65. data/lib/swarm_sdk/agent/chat.rb +0 -774
  66. data/lib/swarm_sdk/agent/chat_helpers/context_tracker.rb +0 -268
  67. data/lib/swarm_sdk/agent/chat_helpers/event_emitter.rb +0 -204
  68. data/lib/swarm_sdk/agent/chat_helpers/hook_integration.rb +0 -480
  69. data/lib/swarm_sdk/agent/chat_helpers/instrumentation.rb +0 -78
  70. data/lib/swarm_sdk/agent/chat_helpers/llm_configuration.rb +0 -233
  71. data/lib/swarm_sdk/agent/chat_helpers/logging_helpers.rb +0 -116
  72. data/lib/swarm_sdk/agent/chat_helpers/serialization.rb +0 -83
  73. data/lib/swarm_sdk/agent/chat_helpers/system_reminder_injector.rb +0 -136
  74. data/lib/swarm_sdk/agent/chat_helpers/system_reminders.rb +0 -79
  75. data/lib/swarm_sdk/agent/chat_helpers/token_tracking.rb +0 -98
  76. data/lib/swarm_sdk/agent/context.rb +0 -116
  77. data/lib/swarm_sdk/agent/context_manager.rb +0 -315
  78. data/lib/swarm_sdk/agent/definition.rb +0 -477
  79. data/lib/swarm_sdk/agent/llm_instrumentation_middleware.rb +0 -182
  80. data/lib/swarm_sdk/agent/system_prompt_builder.rb +0 -161
  81. data/lib/swarm_sdk/builders/base_builder.rb +0 -409
  82. data/lib/swarm_sdk/claude_code_agent_adapter.rb +0 -205
  83. data/lib/swarm_sdk/concerns/cleanupable.rb +0 -39
  84. data/lib/swarm_sdk/concerns/snapshotable.rb +0 -67
  85. data/lib/swarm_sdk/concerns/validatable.rb +0 -55
  86. data/lib/swarm_sdk/configuration/parser.rb +0 -353
  87. data/lib/swarm_sdk/configuration/translator.rb +0 -255
  88. data/lib/swarm_sdk/configuration.rb +0 -135
  89. data/lib/swarm_sdk/context_compactor/metrics.rb +0 -147
  90. data/lib/swarm_sdk/context_compactor/token_counter.rb +0 -106
  91. data/lib/swarm_sdk/context_compactor.rb +0 -335
  92. data/lib/swarm_sdk/context_management/builder.rb +0 -128
  93. data/lib/swarm_sdk/context_management/context.rb +0 -328
  94. data/lib/swarm_sdk/defaults.rb +0 -196
  95. data/lib/swarm_sdk/events_to_messages.rb +0 -199
  96. data/lib/swarm_sdk/hooks/adapter.rb +0 -359
  97. data/lib/swarm_sdk/hooks/context.rb +0 -197
  98. data/lib/swarm_sdk/hooks/definition.rb +0 -80
  99. data/lib/swarm_sdk/hooks/error.rb +0 -29
  100. data/lib/swarm_sdk/hooks/executor.rb +0 -146
  101. data/lib/swarm_sdk/hooks/registry.rb +0 -147
  102. data/lib/swarm_sdk/hooks/result.rb +0 -150
  103. data/lib/swarm_sdk/hooks/shell_executor.rb +0 -255
  104. data/lib/swarm_sdk/hooks/tool_call.rb +0 -35
  105. data/lib/swarm_sdk/hooks/tool_result.rb +0 -62
  106. data/lib/swarm_sdk/log_collector.rb +0 -227
  107. data/lib/swarm_sdk/log_stream.rb +0 -127
  108. data/lib/swarm_sdk/markdown_parser.rb +0 -75
  109. data/lib/swarm_sdk/model_aliases.json +0 -8
  110. data/lib/swarm_sdk/models.json +0 -1
  111. data/lib/swarm_sdk/models.rb +0 -120
  112. data/lib/swarm_sdk/node_context.rb +0 -245
  113. data/lib/swarm_sdk/observer/builder.rb +0 -81
  114. data/lib/swarm_sdk/observer/config.rb +0 -45
  115. data/lib/swarm_sdk/observer/manager.rb +0 -236
  116. data/lib/swarm_sdk/patterns/agent_observer.rb +0 -160
  117. data/lib/swarm_sdk/permissions/config.rb +0 -239
  118. data/lib/swarm_sdk/permissions/error_formatter.rb +0 -121
  119. data/lib/swarm_sdk/permissions/path_matcher.rb +0 -35
  120. data/lib/swarm_sdk/permissions/validator.rb +0 -173
  121. data/lib/swarm_sdk/permissions_builder.rb +0 -122
  122. data/lib/swarm_sdk/plugin.rb +0 -309
  123. data/lib/swarm_sdk/plugin_registry.rb +0 -101
  124. data/lib/swarm_sdk/proc_helpers.rb +0 -53
  125. data/lib/swarm_sdk/prompts/base_system_prompt.md.erb +0 -117
  126. data/lib/swarm_sdk/restore_result.rb +0 -65
  127. data/lib/swarm_sdk/result.rb +0 -123
  128. data/lib/swarm_sdk/snapshot.rb +0 -156
  129. data/lib/swarm_sdk/snapshot_from_events.rb +0 -397
  130. data/lib/swarm_sdk/state_restorer.rb +0 -476
  131. data/lib/swarm_sdk/state_snapshot.rb +0 -334
  132. data/lib/swarm_sdk/swarm/agent_initializer.rb +0 -683
  133. data/lib/swarm_sdk/swarm/all_agents_builder.rb +0 -167
  134. data/lib/swarm_sdk/swarm/builder.rb +0 -249
  135. data/lib/swarm_sdk/swarm/executor.rb +0 -213
  136. data/lib/swarm_sdk/swarm/hook_triggers.rb +0 -150
  137. data/lib/swarm_sdk/swarm/logging_callbacks.rb +0 -340
  138. data/lib/swarm_sdk/swarm/mcp_configurator.rb +0 -154
  139. data/lib/swarm_sdk/swarm/swarm_registry_builder.rb +0 -67
  140. data/lib/swarm_sdk/swarm/tool_configurator.rb +0 -358
  141. data/lib/swarm_sdk/swarm.rb +0 -717
  142. data/lib/swarm_sdk/swarm_loader.rb +0 -145
  143. data/lib/swarm_sdk/swarm_registry.rb +0 -136
  144. data/lib/swarm_sdk/tools/bash.rb +0 -282
  145. data/lib/swarm_sdk/tools/clock.rb +0 -44
  146. data/lib/swarm_sdk/tools/delegate.rb +0 -267
  147. data/lib/swarm_sdk/tools/document_converters/base_converter.rb +0 -83
  148. data/lib/swarm_sdk/tools/document_converters/docx_converter.rb +0 -99
  149. data/lib/swarm_sdk/tools/document_converters/html_converter.rb +0 -101
  150. data/lib/swarm_sdk/tools/document_converters/pdf_converter.rb +0 -78
  151. data/lib/swarm_sdk/tools/document_converters/xlsx_converter.rb +0 -194
  152. data/lib/swarm_sdk/tools/edit.rb +0 -145
  153. data/lib/swarm_sdk/tools/glob.rb +0 -166
  154. data/lib/swarm_sdk/tools/grep.rb +0 -235
  155. data/lib/swarm_sdk/tools/image_extractors/docx_image_extractor.rb +0 -43
  156. data/lib/swarm_sdk/tools/image_extractors/pdf_image_extractor.rb +0 -163
  157. data/lib/swarm_sdk/tools/image_formats/tiff_builder.rb +0 -65
  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 -272
  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 -98
  170. data/lib/swarm_sdk/tools/todo_write.rb +0 -235
  171. data/lib/swarm_sdk/tools/web_fetch.rb +0 -262
  172. data/lib/swarm_sdk/tools/write.rb +0 -112
  173. data/lib/swarm_sdk/utils.rb +0 -68
  174. data/lib/swarm_sdk/validation_result.rb +0 -33
  175. data/lib/swarm_sdk/version.rb +0 -5
  176. data/lib/swarm_sdk/workflow/agent_config.rb +0 -79
  177. data/lib/swarm_sdk/workflow/builder.rb +0 -143
  178. data/lib/swarm_sdk/workflow/executor.rb +0 -497
  179. data/lib/swarm_sdk/workflow/node_builder.rb +0 -555
  180. data/lib/swarm_sdk/workflow/transformer_executor.rb +0 -249
  181. data/lib/swarm_sdk/workflow.rb +0 -554
  182. data/lib/swarm_sdk.rb +0 -524
@@ -1,98 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- module ChatHelpers
6
- # Token usage tracking and context limit management
7
- #
8
- # Extracted from Chat to reduce class size and centralize token metrics.
9
- module TokenTracking
10
- # Get context window limit for the current model
11
- #
12
- # @return [Integer, nil] Maximum context tokens
13
- def context_limit
14
- return @explicit_context_window if @explicit_context_window
15
- return @real_model_info.context_window if @real_model_info&.context_window
16
-
17
- model_context_window
18
- rescue StandardError
19
- nil
20
- end
21
-
22
- # Calculate cumulative input tokens for the conversation
23
- #
24
- # Gets input_tokens from the most recent assistant message, which represents
25
- # the total context size sent to the model (not sum of all messages).
26
- #
27
- # @return [Integer] Total input tokens used
28
- def cumulative_input_tokens
29
- find_last_message { |msg| msg.role == :assistant && msg.input_tokens }&.input_tokens || 0
30
- end
31
-
32
- # Calculate cumulative output tokens across all assistant messages
33
- #
34
- # @return [Integer] Total output tokens used
35
- def cumulative_output_tokens
36
- assistant_messages.sum { |msg| msg.output_tokens || 0 }
37
- end
38
-
39
- # Calculate cumulative cached tokens
40
- #
41
- # @return [Integer] Total cached tokens used
42
- def cumulative_cached_tokens
43
- assistant_messages.sum { |msg| msg.cached_tokens || 0 }
44
- end
45
-
46
- # Calculate cumulative cache creation tokens
47
- #
48
- # @return [Integer] Total tokens written to cache
49
- def cumulative_cache_creation_tokens
50
- assistant_messages.sum { |msg| msg.cache_creation_tokens || 0 }
51
- end
52
-
53
- # Calculate effective input tokens (excluding cache hits)
54
- #
55
- # @return [Integer] Actual input tokens charged
56
- def effective_input_tokens
57
- cumulative_input_tokens - cumulative_cached_tokens
58
- end
59
-
60
- # Calculate total tokens used (input + output)
61
- #
62
- # @return [Integer] Total tokens used
63
- def cumulative_total_tokens
64
- cumulative_input_tokens + cumulative_output_tokens
65
- end
66
-
67
- # Calculate percentage of context window used
68
- #
69
- # @return [Float] Percentage (0.0 to 100.0)
70
- def context_usage_percentage
71
- limit = context_limit
72
- return 0.0 if limit.nil? || limit.zero?
73
-
74
- (cumulative_total_tokens.to_f / limit * 100).round(2)
75
- end
76
-
77
- # Calculate remaining tokens in context window
78
- #
79
- # @return [Integer, nil] Tokens remaining
80
- def tokens_remaining
81
- limit = context_limit
82
- return if limit.nil?
83
-
84
- limit - cumulative_total_tokens
85
- end
86
-
87
- # Compact the conversation history to reduce token usage
88
- #
89
- # @param options [Hash] Compression options
90
- # @return [ContextCompactor::Metrics] Compression statistics
91
- def compact_context(**options)
92
- compactor = ContextCompactor.new(self, options)
93
- compactor.compact
94
- end
95
- end
96
- end
97
- end
98
- end
@@ -1,116 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- # AgentContext encapsulates per-agent state and metadata
6
- #
7
- # Each agent has its own context that tracks:
8
- # - Agent identity (name)
9
- # - Delegation relationships (which tool calls are delegations)
10
- # - Context window warnings (which thresholds have been hit)
11
- # - Optional metadata
12
- #
13
- # This class replaces the per-agent hash maps that were previously
14
- # stored in UnifiedLogger.
15
- #
16
- # @example
17
- # context = Agent::Context.new(
18
- # name: :backend,
19
- # delegation_tools: ["DelegateToDatabase", "DelegateToAuth"],
20
- # metadata: { role: "backend" }
21
- # )
22
- #
23
- # # Track a delegation
24
- # context.track_delegation(call_id: "call_123", target: "DelegateToDatabase")
25
- #
26
- # # Check if a tool call is a delegation
27
- # context.delegation?(call_id: "call_123") # => true
28
- class Context
29
- # Thresholds for context limit warnings (in percentage)
30
- # 60% triggers automatic compression, 80%/90% are informational warnings
31
- CONTEXT_WARNING_THRESHOLDS = [60, 80, 90].freeze
32
-
33
- # Backward compatibility alias - use Defaults module for new code
34
- COMPRESSION_THRESHOLD = Defaults::Context::COMPRESSION_THRESHOLD_PERCENT
35
-
36
- attr_reader :name, :delegation_tools, :metadata, :warning_thresholds_hit, :swarm_id, :parent_swarm_id
37
-
38
- # Initialize a new agent context
39
- #
40
- # @param name [Symbol, String] Agent name
41
- # @param swarm_id [String] Swarm ID for event tracking
42
- # @param parent_swarm_id [String, nil] Parent swarm ID (nil for root swarms)
43
- # @param delegation_tools [Array<String>] Names of tools that are delegations
44
- # @param metadata [Hash] Optional metadata about the agent
45
- def initialize(name:, swarm_id:, parent_swarm_id: nil, delegation_tools: [], metadata: {})
46
- @name = name.to_sym
47
- @swarm_id = swarm_id
48
- @parent_swarm_id = parent_swarm_id
49
- @delegation_tools = Set.new(delegation_tools.map(&:to_s))
50
- @metadata = metadata
51
- @delegation_call_ids = Set.new
52
- @delegation_targets = {}
53
- @warning_thresholds_hit = Set.new
54
- end
55
-
56
- # Track a delegation tool call
57
- #
58
- # @param call_id [String] Tool call ID
59
- # @param target [String] Target agent/tool name
60
- # @return [void]
61
- def track_delegation(call_id:, target:)
62
- @delegation_call_ids.add(call_id)
63
- @delegation_targets[call_id] = target
64
- end
65
-
66
- # Check if a tool call is a delegation
67
- #
68
- # @param call_id [String] Tool call ID
69
- # @return [Boolean]
70
- def delegation?(call_id:)
71
- @delegation_call_ids.include?(call_id)
72
- end
73
-
74
- # Get the delegation target for a tool call
75
- #
76
- # @param call_id [String] Tool call ID
77
- # @return [String, nil] Target agent/tool name, or nil if not a delegation
78
- def delegation_target(call_id:)
79
- @delegation_targets[call_id]
80
- end
81
-
82
- # Remove a delegation from tracking (after it completes)
83
- #
84
- # @param call_id [String] Tool call ID
85
- # @return [void]
86
- def clear_delegation(call_id:)
87
- @delegation_targets.delete(call_id)
88
- @delegation_call_ids.delete(call_id)
89
- end
90
-
91
- # Check if a tool name is a delegation tool
92
- #
93
- # @param tool_name [String] Tool name
94
- # @return [Boolean]
95
- def delegation_tool?(tool_name)
96
- @delegation_tools.include?(tool_name.to_s)
97
- end
98
-
99
- # Record that a context warning threshold has been hit
100
- #
101
- # @param threshold [Integer] Threshold percentage (80, 90, etc)
102
- # @return [Boolean] true if this is the first time hitting this threshold
103
- def hit_warning_threshold?(threshold)
104
- !@warning_thresholds_hit.add?(threshold).nil?
105
- end
106
-
107
- # Check if a warning threshold has been hit
108
- #
109
- # @param threshold [Integer] Threshold percentage
110
- # @return [Boolean]
111
- def warning_threshold_hit?(threshold)
112
- @warning_thresholds_hit.include?(threshold)
113
- end
114
- end
115
- end
116
- end
@@ -1,315 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module SwarmSDK
4
- module Agent
5
- # Manages conversation context and message optimization
6
- #
7
- # Responsibilities:
8
- # - Handle ephemeral messages (sent to LLM but not persisted)
9
- # - Extract and strip system reminders
10
- # - Prepare messages for LLM API calls
11
- # - Future: Context window management, summarization, truncation
12
- #
13
- # @example
14
- # manager = ContextManager.new
15
- # manager.add_ephemeral_reminder("<system-reminder>Use caution</system-reminder>")
16
- # messages_for_llm = manager.prepare_for_llm(persistent_messages)
17
- # manager.clear_ephemeral # After LLM call
18
- class ContextManager
19
- SYSTEM_REMINDER_REGEX = %r{<system-reminder>.*?</system-reminder>}m
20
-
21
- # Expose compression state for snapshot/restore
22
- # NOTE: @compression_applied initializes to nil (not false), only set to true when compression runs
23
- attr_reader :compression_applied
24
- attr_writer :compression_applied
25
-
26
- def initialize
27
- # Ephemeral content to append to messages for this turn only
28
- # Format: { message_index => [array of reminder strings] }
29
- @ephemeral_content = {}
30
- # NOTE: @compression_applied is NOT initialized here - starts as nil
31
- end
32
-
33
- # Track ephemeral content to append to a specific message
34
- #
35
- # Reminders will be embedded in the message content when sent to LLM,
36
- # but are NOT persisted in the message history.
37
- #
38
- # @param message_index [Integer] Index of message to append to
39
- # @param content [String] Reminder content to append
40
- # @return [void]
41
- def add_ephemeral_content_for_message(message_index, content)
42
- @ephemeral_content[message_index] ||= []
43
- @ephemeral_content[message_index] << content
44
- end
45
-
46
- # Add ephemeral reminder to the most recent message
47
- #
48
- # This will append the reminder to the last message in the array when
49
- # preparing for LLM, but won't modify the stored message.
50
- #
51
- # @param content [String] Reminder content
52
- # @param messages_array [Array<RubyLLM::Message>] Message array to get index from
53
- # @return [void]
54
- def add_ephemeral_reminder(content, messages_array:)
55
- message_index = messages_array.size - 1
56
- return if message_index < 0
57
-
58
- add_ephemeral_content_for_message(message_index, content)
59
- end
60
-
61
- # Prepare messages for LLM API call
62
- #
63
- # Embeds ephemeral content into messages for this turn only.
64
- # Does NOT modify the persistent messages array.
65
- #
66
- # @param persistent_messages [Array<RubyLLM::Message>] Messages from @messages
67
- # @return [Array<RubyLLM::Message>] Messages with ephemeral content embedded
68
- def prepare_for_llm(persistent_messages)
69
- return persistent_messages.dup if @ephemeral_content.empty?
70
-
71
- # Clone messages and embed ephemeral content
72
- messages_for_llm = persistent_messages.map.with_index do |msg, index|
73
- ephemeral_for_this_msg = @ephemeral_content[index]
74
-
75
- # No ephemeral content for this message - use as-is
76
- next msg unless ephemeral_for_this_msg&.any?
77
-
78
- # Embed ephemeral content in this message
79
- original_content = msg.content.is_a?(RubyLLM::Content) ? msg.content.text : msg.content.to_s
80
- embedded_content = [original_content, *ephemeral_for_this_msg].join("\n\n")
81
-
82
- # Create new message with embedded content
83
- if msg.content.is_a?(RubyLLM::Content)
84
- RubyLLM::Message.new(
85
- role: msg.role,
86
- content: RubyLLM::Content.new(embedded_content, msg.content.attachments),
87
- tool_call_id: msg.tool_call_id,
88
- )
89
- else
90
- RubyLLM::Message.new(
91
- role: msg.role,
92
- content: embedded_content,
93
- tool_call_id: msg.tool_call_id,
94
- )
95
- end
96
- end
97
-
98
- messages_for_llm
99
- end
100
-
101
- # Clear all ephemeral content
102
- #
103
- # Should be called after LLM response is received.
104
- #
105
- # @return [void]
106
- def clear_ephemeral
107
- @ephemeral_content.clear
108
- end
109
-
110
- # Check if there is pending ephemeral content
111
- #
112
- # @return [Boolean] True if ephemeral content exists
113
- def has_ephemeral?
114
- @ephemeral_content.any?
115
- end
116
-
117
- # Get count of messages with ephemeral content
118
- #
119
- # @return [Integer] Number of messages with ephemeral content attached
120
- def ephemeral_count
121
- @ephemeral_content.size
122
- end
123
-
124
- # Extract all <system-reminder> blocks from content
125
- #
126
- # @param content [String] Content to extract from
127
- # @return [Array<String>] Array of system reminder blocks
128
- def extract_system_reminders(content)
129
- return [] if content.nil? || content.empty?
130
-
131
- content.scan(SYSTEM_REMINDER_REGEX)
132
- end
133
-
134
- # Strip all <system-reminder> blocks from content
135
- #
136
- # Returns clean content without system reminders.
137
- #
138
- # @param content [String] Content to strip from
139
- # @return [String] Clean content
140
- def strip_system_reminders(content)
141
- return content if content.nil? || content.empty?
142
-
143
- content.gsub(SYSTEM_REMINDER_REGEX, "").strip
144
- end
145
-
146
- # Check if content contains system reminders
147
- #
148
- # @param content [String] Content to check
149
- # @return [Boolean] True if reminders found
150
- def has_system_reminders?(content)
151
- return false if content.nil? || content.empty?
152
-
153
- SYSTEM_REMINDER_REGEX.match?(content)
154
- end
155
-
156
- # ============================================================================
157
- # FUTURE: Context Optimization Methods (Hooks for Later Implementation)
158
- # ============================================================================
159
-
160
- # Future: Summarize old messages to save context window space
161
- #
162
- # @param messages [Array<RubyLLM::Message>] Messages to potentially summarize
163
- # @param before_index [Integer] Summarize messages before this index
164
- # @param strategy [Symbol] Summarization strategy (:llm, :truncate, :remove)
165
- # @return [Array<RubyLLM::Message>] Optimized message array
166
- def summarize_old_messages(messages, before_index:, strategy: :truncate)
167
- # TODO: Implement when needed
168
- messages
169
- end
170
-
171
- # Future: Truncate messages to fit within context window
172
- #
173
- # @param messages [Array<RubyLLM::Message>] Messages to fit
174
- # @param max_tokens [Integer] Maximum token budget
175
- # @param keep_recent [Integer] Number of recent messages to always keep
176
- # @return [Array<RubyLLM::Message>] Truncated messages
177
- def truncate_to_fit(messages, max_tokens:, keep_recent: 10)
178
- # TODO: Implement when needed
179
- messages
180
- end
181
-
182
- # Compress verbose tool results for older messages
183
- #
184
- # Uses progressive compression: older messages are compressed more aggressively.
185
- # Preserves user/assistant messages at full detail (conversational context).
186
- #
187
- # @param messages [Array<RubyLLM::Message>] Messages to compress
188
- # @param keep_recent [Integer] Number of recent messages to keep at full detail
189
- # @return [Array<RubyLLM::Message>] Compressed messages
190
- def compress_tool_results(messages, keep_recent: 10)
191
- messages.map.with_index do |msg, i|
192
- # Keep recent messages at full detail
193
- next msg if i >= messages.size - keep_recent
194
-
195
- # Keep user/assistant messages (conversational flow is important)
196
- next msg if [:user, :assistant].include?(msg.role)
197
-
198
- # Compress old tool results
199
- if msg.role == :tool
200
- compress_tool_message(msg, age: messages.size - i)
201
- else
202
- msg
203
- end
204
- end
205
- end
206
-
207
- # Compress a single tool message based on age
208
- #
209
- # Progressive compression: older messages get compressed more.
210
- # For re-runnable tools (Read, Grep, Glob, etc.), adds instruction to re-run if needed.
211
- #
212
- # @param msg [RubyLLM::Message] Tool message to compress
213
- # @param age [Integer] How many messages ago (higher = older)
214
- # @return [RubyLLM::Message] Compressed message
215
- def compress_tool_message(msg, age:)
216
- content = msg.content.to_s
217
-
218
- # Progressive compression based on age
219
- max_length = case age
220
- when 0..10 then return msg # Recent: keep full detail
221
- when 11..20 then 1000 # Medium age: light compression
222
- when 21..40 then 500 # Old: moderate compression
223
- when 41..60 then 200 # Very old: heavy compression
224
- else 100 # Ancient: minimal summary
225
- end
226
-
227
- return msg if content.length <= max_length
228
-
229
- # Compress while preserving structure
230
- compressed = content.slice(0, max_length)
231
- truncated_chars = content.length - max_length
232
- compressed += "\n...[#{truncated_chars} chars truncated for context management]"
233
-
234
- # Detect if this is a re-runnable tool and add helpful instruction
235
- tool_name = detect_tool_name(content)
236
- if rerunnable_tool?(tool_name)
237
- compressed += "\n\nšŸ’” If you need the full output, re-run the #{tool_name} tool with the same parameters."
238
- end
239
-
240
- RubyLLM::Message.new(
241
- role: :tool,
242
- content: compressed,
243
- tool_call_id: msg.tool_call_id,
244
- )
245
- end
246
-
247
- # Detect tool name from content
248
- #
249
- # @param content [String] Tool result content
250
- # @return [String, nil] Tool name or nil
251
- def detect_tool_name(content)
252
- # Many tool results start with patterns we can detect
253
- case content
254
- when /^\s*\d+→/ # Line numbers (Read, MemoryRead)
255
- content.include?("memory://") ? "MemoryRead" : "Read"
256
- when /^Memory entries matching/ # MemoryGlob
257
- "MemoryGlob"
258
- when /^Found \d+ files? matching/ # Glob
259
- "Glob"
260
- when /matches in \d+ files?|No matches found/ # Grep, MemoryGrep
261
- content.include?("memory://") ? "MemoryGrep" : "Grep"
262
- when %r{^Stored at memory://} # MemoryWrite (not re-runnable but identifiable)
263
- "MemoryWrite"
264
- when %r{^Deleted memory://} # MemoryDelete
265
- "MemoryDelete"
266
- end
267
- end
268
-
269
- # Check if a tool is re-runnable (idempotent, can get same data again)
270
- #
271
- # @param tool_name [String, nil] Tool name
272
- # @return [Boolean] True if tool can be re-run safely
273
- def rerunnable_tool?(tool_name)
274
- return false if tool_name.nil?
275
-
276
- # These tools are idempotent - re-running gives same/current data
277
- ["Read", "MemoryRead", "Grep", "MemoryGrep", "Glob", "MemoryGlob"].include?(tool_name)
278
- end
279
-
280
- # Automatically compress messages when context threshold is hit
281
- #
282
- # This is called automatically when context usage crosses 60% threshold.
283
- # Returns compressed messages array for immediate use.
284
- #
285
- # @param messages [Array<RubyLLM::Message>] Current message array
286
- # @param keep_recent [Integer] Number of recent messages to keep full
287
- # @return [Array<RubyLLM::Message>] Compressed messages
288
- def auto_compress_on_threshold(messages, keep_recent: 10)
289
- return messages if @compression_applied
290
-
291
- # Mark as applied to avoid compressing multiple times
292
- @compression_applied = true
293
-
294
- compress_tool_results(messages, keep_recent: keep_recent)
295
- end
296
-
297
- # Reset compression flag (when conversation is reset)
298
- #
299
- # @return [void]
300
- def reset_compression
301
- @compression_applied = false
302
- end
303
-
304
- # Future: Detect if context is becoming bloated
305
- #
306
- # @param messages [Array<RubyLLM::Message>] Messages to analyze
307
- # @param threshold [Float] Bloat threshold (0.0-1.0)
308
- # @return [Hash] Bloat analysis with recommendations
309
- def analyze_context_bloat(messages, threshold: 0.7)
310
- # TODO: Implement when needed
311
- { bloated: false, recommendations: [] }
312
- end
313
- end
314
- end
315
- end