robot_lab 0.2.8 → 0.3.0

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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/.envrc +2 -2
  3. data/CHANGELOG.md +53 -0
  4. data/CLAUDE.md +4 -2
  5. data/README.md +15 -17
  6. data/docs/architecture/core-concepts.md +1 -1
  7. data/docs/architecture/state-management.md +4 -4
  8. data/docs/concepts.md +1 -1
  9. data/docs/getting-started/installation.md +1 -4
  10. data/docs/guides/memory.md +2 -2
  11. data/examples/.envrc +2 -0
  12. data/examples/02_tools.rb +8 -8
  13. data/examples/03_network.rb +1 -1
  14. data/examples/04_mcp.rb +7 -5
  15. data/examples/08_llm_config.rb +5 -5
  16. data/examples/09_chaining.rb +3 -3
  17. data/examples/14_rusty_circuit/comic.rb +8 -8
  18. data/examples/14_rusty_circuit/scout.rb +4 -4
  19. data/examples/15_memory_network_and_bus/README.md +66 -0
  20. data/examples/15_memory_network_and_bus/output/combined_article.md +5 -7
  21. data/examples/15_memory_network_and_bus/output/final_article.md +5 -10
  22. data/examples/15_memory_network_and_bus/output/linux_draft.md +3 -3
  23. data/examples/15_memory_network_and_bus/output/mac_draft.md +3 -3
  24. data/examples/15_memory_network_and_bus/output/memory.json +6 -6
  25. data/examples/15_memory_network_and_bus/output/revision_1.md +21 -10
  26. data/examples/15_memory_network_and_bus/output/revision_2.md +43 -6
  27. data/examples/15_memory_network_and_bus/output/revision_3.md +8 -0
  28. data/examples/15_memory_network_and_bus/output/windows_draft.md +3 -3
  29. data/examples/16_writers_room/tools.rb +14 -14
  30. data/examples/19_token_tracking.rb +2 -2
  31. data/examples/20_circuit_breaker.rb +3 -3
  32. data/examples/22_context_compression.rb +1 -1
  33. data/examples/27_incident_response/README.md +65 -0
  34. data/examples/28_mcp_discovery.rb +2 -2
  35. data/examples/29_ractor_tools.rb +4 -4
  36. data/examples/30_ractor_network.rb +2 -2
  37. data/examples/33_stock_predictor.rb +8 -8
  38. data/examples/35_hooks.rb +3 -3
  39. data/examples/README.md +17 -0
  40. data/examples/common.rb +55 -23
  41. data/examples/run_all.rb +60 -0
  42. data/lib/robot_lab/ask_user.rb +3 -3
  43. data/lib/robot_lab/config/defaults.yml +5 -5
  44. data/lib/robot_lab/config.rb +2 -2
  45. data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -3
  46. data/lib/robot_lab/memory.rb +16 -7
  47. data/lib/robot_lab/robot/hooking.rb +26 -0
  48. data/lib/robot_lab/robot/result_building.rb +119 -0
  49. data/lib/robot_lab/robot.rb +37 -120
  50. data/lib/robot_lab/run_config.rb +52 -20
  51. data/lib/robot_lab/tool.rb +7 -12
  52. data/lib/robot_lab/version.rb +1 -1
  53. data/lib/robot_lab.rb +3 -3
  54. metadata +18 -27
@@ -7,6 +7,7 @@ require_relative 'robot/history_search'
7
7
  require_relative 'robot/agent_skill_matching'
8
8
  require_relative 'robot/budget'
9
9
  require_relative 'robot/hooking'
10
+ require_relative 'robot/result_building'
10
11
 
11
12
  module RobotLab
12
13
  # LLM-powered robot built on RubyLLM::Agent
@@ -54,6 +55,7 @@ module RobotLab
54
55
  include Robot::HistorySearch
55
56
  include Robot::Budget
56
57
  include Robot::Hooking
58
+ include Robot::ResultBuilding
57
59
  include Runnable
58
60
  prepend Robot::AgentSkillMatching
59
61
 
@@ -367,9 +369,7 @@ module RobotLab
367
369
  # access to conversation state.
368
370
  #
369
371
  # @return [RubyLLM::Chat]
370
- def chat
371
- @chat
372
- end
372
+ attr_reader :chat
373
373
 
374
374
  # Return the conversation messages from the underlying chat.
375
375
  #
@@ -386,10 +386,10 @@ module RobotLab
386
386
  def clear_messages(keep_system: true)
387
387
  if keep_system
388
388
  system_msg = @chat.messages.find { |m| m.role == :system }
389
- @chat.reset_messages!
389
+ @chat.messages = []
390
390
  @chat.add_message(system_msg) if system_msg
391
391
  else
392
- @chat.reset_messages!
392
+ @chat.messages = []
393
393
  end
394
394
  self
395
395
  end
@@ -399,8 +399,8 @@ module RobotLab
399
399
  # @param messages [Array<RubyLLM::Message>] the messages to restore
400
400
  # @return [self]
401
401
  def replace_messages(messages)
402
- @chat.reset_messages!
403
- messages.each { |m| @chat.add_message(m) }
402
+ @chat.messages = []
403
+ messages.each { |m| @chat.add_message(coerce_replacement_message(m)) }
404
404
  self
405
405
  end
406
406
 
@@ -665,6 +665,8 @@ module RobotLab
665
665
  @learnings = []
666
666
  @hooks = HookRegistry.new
667
667
  @budget_ledger = build_budget_ledger
668
+ @circuit_breaker_armed = false
669
+ @circuit_breaker_call_count = 0
668
670
  end
669
671
 
670
672
  def initialize_memory
@@ -716,20 +718,33 @@ module RobotLab
716
718
 
717
719
  def apply_chat_params
718
720
  @chat.with_temperature(@config.temperature) if @config.temperature
721
+ @chat.with_max_output_tokens(@config.max_tokens) if @config.max_tokens
719
722
 
723
+ # ruby_llm 2.0 has no with_params; provider-specific sampling knobs are
724
+ # merged into the request payload via with_provider_options instead.
725
+ # with_provider_options replaces prior options, so merge with whatever
726
+ # the chat already carries (e.g. from template front matter).
720
727
  extra_params = {
721
728
  top_p: @config.top_p, top_k: @config.top_k,
722
- max_tokens: @config.max_tokens,
723
729
  presence_penalty: @config.presence_penalty,
724
730
  frequency_penalty: @config.frequency_penalty,
725
731
  stop: @config.stop
726
732
  }.compact
727
- @chat.with_params(**extra_params) if extra_params.any?
733
+ @chat.with_provider_options(@chat.provider_options.to_h.merge(extra_params)) if extra_params.any?
728
734
  end
729
735
 
736
+ # ruby_llm 2.0 callbacks (before_tool_call/after_tool_result) are additive
737
+ # and cannot be removed, so register a single dispatcher per chat that
738
+ # consults the robot's current state. The per-run circuit breaker arms and
739
+ # disarms a flag instead of swapping callbacks.
730
740
  def register_chat_callbacks
731
- @chat.on_tool_call(&@on_tool_call) if @on_tool_call
732
- @chat.on_tool_result(&@on_tool_result) if @on_tool_result
741
+ @chat.before_tool_call do |tool_call|
742
+ enforce_circuit_breaker! if @circuit_breaker_armed
743
+ @on_tool_call&.call(tool_call)
744
+ end
745
+ @chat.after_tool_result do |result|
746
+ @on_tool_result&.call(result)
747
+ end
733
748
  setup_bus_channel if @bus
734
749
  end
735
750
 
@@ -761,6 +776,7 @@ module RobotLab
761
776
  end
762
777
  end
763
778
 
779
+ # :reek:TooManyStatements -- resolve/discover/connect/filter/attach is one linear per-turn tool pipeline.
764
780
  def prepare_tools(message:, mcp:, tools:, network:, network_config:)
765
781
  resolved_mcp = resolve_mcp_hierarchy(mcp, network: network, network_config: network_config)
766
782
  resolved_tools = resolve_tools_hierarchy(tools, network: network, network_config: network_config)
@@ -779,12 +795,16 @@ module RobotLab
779
795
  # runtime value before resolution to honor the zero-tools intent.
780
796
  filtered = explicit_none_tools?(tools) ? [] : cap_tools(filtered_tools(resolved_tools))
781
797
 
782
- # replace: true so the chat holds EXACTLY this turn's resolved+capped set.
783
- # RubyLLM's with_tools appends by default; on a persistent chat that lets
784
- # tools accumulate across turns, so a capped per-turn addition could still
785
- # push the chat's total past the provider limit. An explicit none clears
786
- # the chat's tools to zero (with_tools(replace: true) with no tools).
787
- @chat.with_tools(*filtered, replace: true) if filtered.any? || explicit_none_tools?(tools)
798
+ # Clear-then-add so the chat holds EXACTLY this turn's resolved+capped
799
+ # set. RubyLLM's with_tools appends; on a persistent chat that lets tools
800
+ # accumulate across turns, so a capped per-turn addition could still push
801
+ # the chat's total past the provider limit. with_tools(nil) is ruby_llm
802
+ # 2.0's "clear the tool set"; an explicit none stops there, sending zero
803
+ # tools this turn.
804
+ return unless filtered.any? || explicit_none_tools?(tools)
805
+
806
+ @chat.with_tools(nil)
807
+ @chat.with_tools(*filtered) if filtered.any?
788
808
  end
789
809
 
790
810
  # True when the runtime tools value explicitly requests zero tools — `:none`
@@ -902,85 +922,6 @@ module RobotLab
902
922
  merged
903
923
  end
904
924
 
905
- # :reek:TooManyStatements :reek:FeatureEnvy -- adapting a provider response's many optional fields into
906
- # a RobotResult is inherently response-centric.
907
- def build_result(response, _memory)
908
- text = result_text(response)
909
- output = text ? [TextMessage.new(role: 'assistant', content: text)] : []
910
-
911
- tool_calls = response.respond_to?(:tool_calls) ? (response.tool_calls || []) : []
912
-
913
- # Extract token usage from the response
914
- input_toks = output_toks = 0
915
- if response.respond_to?(:tokens) && (tokens = response.tokens)
916
- input_toks = tokens.input.to_i
917
- output_toks = tokens.output.to_i
918
- elsif response.respond_to?(:input_tokens)
919
- input_toks = response.input_tokens.to_i
920
- output_toks = response.respond_to?(:output_tokens) ? response.output_tokens.to_i : 0
921
- end
922
-
923
- @total_input_tokens += input_toks
924
- @total_output_tokens += output_toks
925
-
926
- RobotResult.new(
927
- robot_name: @name,
928
- output: output,
929
- tool_calls: normalize_tool_calls(tool_calls),
930
- stop_reason: response.respond_to?(:stop_reason) ? response.stop_reason : nil,
931
- raw: response,
932
- input_tokens: input_toks,
933
- output_tokens: output_toks
934
- )
935
- end
936
-
937
- # Text for the result's output. Prefers the final response's content, then
938
- # falls back in order to: (1) thinking text for models that route all output
939
- # through reasoning_content (e.g. qwen3 on Ollama), (2) the most recent
940
- # assistant text within the current turn for models that end on a tool call
941
- # with no trailing text.
942
- #
943
- # The chat-history fallback is scoped to messages AFTER the last user message
944
- # (the current turn) to prevent a previous turn's response from being returned
945
- # when a thinking-mode model emits nothing in response.content.
946
- # :reek:TooManyStatements :reek:FeatureEnvy -- documented fallback chain over the response's optional content/thinking/history fields.
947
- def result_text(response)
948
- content = response.content if response.respond_to?(:content)
949
- return content if content && !content.to_s.empty?
950
-
951
- # Ollama routes qwen3's reasoning to reasoning_content, which ruby_llm
952
- # surfaces as response.thinking (a RubyLLM::Thinking object). When content
953
- # is nil and thinking is present, the thinking IS the response for that turn.
954
- if response.respond_to?(:thinking) && (thinking = response.thinking)
955
- thinking_text = thinking.respond_to?(:text) ? thinking.text.to_s : thinking.to_s
956
- return thinking_text unless thinking_text.empty?
957
- end
958
-
959
- return nil unless @chat.respond_to?(:messages)
960
-
961
- messages = @chat.messages
962
- last_user_idx = messages.rindex { |m| m.role == :user } || -1
963
- current_turn = messages[(last_user_idx + 1)..]
964
-
965
- last = current_turn.rfind { |m| m.role == :assistant && m.content && !m.content.to_s.empty? }
966
- last&.content
967
- end
968
-
969
- def normalize_tool_calls(tool_calls)
970
- return [] unless tool_calls
971
-
972
- tool_calls.map do |tc|
973
- if tc.is_a?(Hash)
974
- ToolResultMessage.new(
975
- tool: tc,
976
- content: tc[:result] || tc['result']
977
- )
978
- else
979
- tc
980
- end
981
- end
982
- end
983
-
984
925
  # Merge the stored on_content callback with a runtime streaming block.
985
926
  # If both exist, both fire (stored first, then runtime block).
986
927
  #
@@ -1124,29 +1065,5 @@ module RobotLab
1124
1065
  RobotLab.config.logger.warn("[#{@name}] auto_compact: #{e.message}; skipping compaction")
1125
1066
  end
1126
1067
  end
1127
-
1128
- # Install a per-run circuit breaker on the chat's on_tool_call hook.
1129
- # Raises ToolLoopError if tool calls exceed @config.max_tool_rounds.
1130
- # Stores the previous callback so restore_tool_call_callback can undo it.
1131
- def install_circuit_breaker
1132
- @circuit_breaker_call_count = 0
1133
- max = @config.max_tool_rounds
1134
- original = @on_tool_call
1135
-
1136
- @chat.on_tool_call do |tool_call|
1137
- @circuit_breaker_call_count += 1
1138
- if @circuit_breaker_call_count > max
1139
- raise ToolLoopError,
1140
- "Circuit breaker triggered: #{@circuit_breaker_call_count} tool calls exceeded " \
1141
- "max_tool_rounds (#{max})"
1142
- end
1143
- original&.call(tool_call)
1144
- end
1145
- end
1146
-
1147
- # Restore the original on_tool_call callback after a circuit-breaker run.
1148
- def restore_tool_call_callback
1149
- @chat.on_tool_call(&@on_tool_call)
1150
- end
1151
1068
  end
1152
1069
  end
@@ -12,20 +12,20 @@ module RobotLab
12
12
  # config's non-nil values win over the less-specific config.
13
13
  #
14
14
  # @example Keyword construction
15
- # config = RunConfig.new(model: "claude-sonnet-4", temperature: 0.7)
15
+ # config = RunConfig.new(model: "claude-sonnet-4-6", temperature: 0.7)
16
16
  #
17
17
  # @example Block DSL
18
18
  # config = RunConfig.new do |c|
19
- # c.model "claude-sonnet-4"
19
+ # c.model "claude-sonnet-4-6"
20
20
  # c.temperature 0.7
21
21
  # end
22
22
  #
23
23
  # @example Merge (more-specific wins)
24
- # network_config = RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
24
+ # network_config = RunConfig.new(model: "claude-sonnet-4-6", temperature: 0.5)
25
25
  # robot_config = RunConfig.new(temperature: 0.9)
26
26
  # effective = network_config.merge(robot_config)
27
27
  # effective.temperature #=> 0.9
28
- # effective.model #=> "claude-sonnet-4"
28
+ # effective.model #=> "claude-sonnet-4-6"
29
29
  #
30
30
  # :reek:RepeatedConditional -- `if value` guards in independent field loops; each skips unset fields.
31
31
  class RunConfig
@@ -106,6 +106,10 @@ module RobotLab
106
106
  self.class.new(**merged)
107
107
  end
108
108
 
109
+ # Fields that ruby_llm 2.0 accepts only through with_provider_options
110
+ # (merged into the request payload in the provider's own vocabulary).
111
+ PROVIDER_OPTION_FIELDS = %i[top_p top_k presence_penalty frequency_penalty stop].freeze
112
+
109
113
  # Applies LLM fields to a chat object via its with_* methods.
110
114
  #
111
115
  # +provider+/+assume_model_exists+ are threaded through to +with_model+
@@ -117,27 +121,26 @@ module RobotLab
117
121
  # raises ModelNotFoundError for any local-provider model (Ollama, etc.)
118
122
  # not in RubyLLM's bundled registry.
119
123
  #
124
+ # ruby_llm 2.0 mapping: +max_tokens+ applies via with_max_output_tokens,
125
+ # and the sampling knobs (top_p, top_k, penalties, stop) go through
126
+ # with_provider_options, merged with any options the chat already has.
127
+ #
120
128
  # @param chat [Object] a RubyLLM::Chat (or similar) that responds to with_model, with_temperature, etc.
121
129
  # @param provider [String, Symbol, nil] passed through to chat.with_model's provider: kwarg
122
- # @param assume_model_exists [Boolean] passed through to chat.with_model's assume_exists: kwarg
130
+ # @param assume_model_exists [Boolean] passed through to chat.with_model's assume_model_exists: kwarg
123
131
  # :reek:BooleanParameter -- assume_model_exists is a pass-through to RubyLLM's with_model kwarg.
124
132
  # :reek:FeatureEnvy -- configuring the chat object handed in is exactly what apply_to is for.
133
+ # :reek:TooManyStatements -- one guarded application per LLM field group.
125
134
  def apply_to(chat, provider: nil, assume_model_exists: false)
126
- LLM_FIELDS.each do |field|
127
- value = @fields[field]
128
- next unless value
129
-
130
- if field == :model && provider
131
- # Only take the provider-aware path when a provider was actually
132
- # given -- preserves the original single-arg call (and thus
133
- # compatibility with any chat-like object exposing only
134
- # `with_model(value)`) for the common case.
135
- chat.with_model(value, provider:, assume_exists: assume_model_exists) if chat.respond_to?(:with_model)
136
- else
137
- method = :"with_#{field}"
138
- chat.public_send(method, value) if chat.respond_to?(method)
139
- end
140
- end
135
+ apply_model_to(chat, provider: provider, assume_model_exists: assume_model_exists)
136
+
137
+ temperature = @fields[:temperature]
138
+ chat.with_temperature(temperature) if temperature && chat.respond_to?(:with_temperature)
139
+
140
+ max_tokens = @fields[:max_tokens]
141
+ chat.with_max_output_tokens(max_tokens) if max_tokens && chat.respond_to?(:with_max_output_tokens)
142
+
143
+ apply_provider_options_to(chat)
141
144
  end
142
145
 
143
146
  # Build a RunConfig from prompt_manager front matter metadata.
@@ -187,6 +190,35 @@ module RobotLab
187
190
 
188
191
  private
189
192
 
193
+ # Apply the model field, threading provider/assume_model_exists through
194
+ # only when a provider was actually given -- preserves the original
195
+ # single-arg call (and thus compatibility with any chat-like object
196
+ # exposing only `with_model(value)`) for the common case.
197
+ # :reek:BooleanParameter -- pass-through to RubyLLM's with_model kwarg.
198
+ # :reek:FeatureEnvy -- configuring the chat handed in is exactly what apply_to delegates here.
199
+ def apply_model_to(chat, provider:, assume_model_exists:)
200
+ model = @fields[:model]
201
+ return unless model && chat.respond_to?(:with_model)
202
+
203
+ if provider
204
+ chat.with_model(model, provider: provider, assume_model_exists: assume_model_exists)
205
+ else
206
+ chat.with_model(model)
207
+ end
208
+ end
209
+
210
+ # Merge the sampling knobs into the chat's provider options.
211
+ # with_provider_options replaces prior options wholesale, so merge with
212
+ # the chat's current set to keep options applied earlier.
213
+ # :reek:FeatureEnvy -- configuring the chat handed in is exactly what apply_to delegates here.
214
+ def apply_provider_options_to(chat)
215
+ options = @fields.slice(*PROVIDER_OPTION_FIELDS)
216
+ return if options.empty? || !chat.respond_to?(:with_provider_options)
217
+
218
+ current = chat.respond_to?(:provider_options) ? chat.provider_options.to_h : {}
219
+ chat.with_provider_options(current.merge(options))
220
+ end
221
+
190
222
  # Validates and stores a field value. Nil removes the key.
191
223
  def set(field, value)
192
224
  field = field.to_sym
@@ -9,7 +9,7 @@ module RobotLab
9
9
  #
10
10
  # class GetWeather < RobotLab::Tool
11
11
  # description "Get weather for a location"
12
- # param :location, type: "string", desc: "City name"
12
+ # parameter :location, type: "string", description: "City name"
13
13
  #
14
14
  # def execute(location:)
15
15
  # WeatherService.fetch(location)
@@ -93,10 +93,11 @@ module RobotLab
93
93
  #
94
94
  # For non-Ractor-safe tools: runs execute directly in the calling thread.
95
95
  #
96
+ # @param tool_call [RubyLLM::ToolCall, nil] the originating tool call (supplied by the chat)
96
97
  # @param args [Hash] the tool arguments from the LLM
97
98
  # @return [Object] the tool result or an error string
98
99
  # :reek:TooManyStatements -- hook-wrapped dispatch with per-error-class handling; the rescue clauses are the method.
99
- def call(args)
100
+ def call(tool_call: nil, **args)
100
101
  context = ToolCallHookContext.new(tool: self, tool_args: args, robot: @robot)
101
102
 
102
103
  RobotLab::Hooks.run(:tool_call, context, **tool_hook_options) do
@@ -157,15 +158,9 @@ module RobotLab
157
158
  tool_class = Class.new(self) do
158
159
  description(desc_text) if desc_text
159
160
 
160
- if params_hash.is_a?(Hash) && params_hash[:properties]
161
- required_list = Array(params_hash[:required]).map(&:to_s)
162
- params_hash[:properties].each do |pname, pdef|
163
- param pname.to_sym,
164
- type: pdef[:type] || "string",
165
- desc: pdef[:description],
166
- required: required_list.include?(pname.to_s)
167
- end
168
- end
161
+ # ruby_llm 2.0's parameters() accepts a raw JSON Schema hash, so the
162
+ # full schema (including nested items/objects) survives verbatim.
163
+ parameters(params_hash) if params_hash.is_a?(Hash) && params_hash[:properties]
169
164
 
170
165
  define_method(:execute) do |**args|
171
166
  block.call(args)
@@ -183,7 +178,7 @@ module RobotLab
183
178
  #
184
179
  # @return [Hash] JSON Schema representation
185
180
  def to_json_schema
186
- schema = params_schema || { "type" => "object", "properties" => {}, "required" => [] }
181
+ schema = parameters_schema || { "type" => "object", "properties" => {}, "required" => [] }
187
182
  {
188
183
  name: name,
189
184
  description: description,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RobotLab
4
- VERSION = "0.2.8"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/robot_lab.rb CHANGED
@@ -39,7 +39,7 @@ require 'typed_bus'
39
39
  # # See lib/robot_lab/config/defaults.yml for all options
40
40
  #
41
41
  # # Access configuration values:
42
- # RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
42
+ # RobotLab.config.ruby_llm.model #=> "claude-sonnet-4-6"
43
43
  # RobotLab.config.ruby_llm.request_timeout #=> 120
44
44
  #
45
45
  module RobotLab
@@ -159,7 +159,7 @@ module RobotLab
159
159
  # @return [Config] the config instance
160
160
  #
161
161
  # @example
162
- # RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
162
+ # RobotLab.config.ruby_llm.model #=> "claude-sonnet-4-6"
163
163
  # RobotLab.config.ruby_llm.request_timeout #=> 120
164
164
  # RobotLab.config.development? #=> true
165
165
  def config
@@ -173,7 +173,7 @@ module RobotLab
173
173
  #
174
174
  # @example
175
175
  # RobotLab.configure do |c|
176
- # c.default_model = "claude-sonnet-4"
176
+ # c.default_model = "claude-sonnet-4-6"
177
177
  # end
178
178
  def configure
179
179
  yield config
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: robot_lab
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.8
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dewayne VanHoozer
@@ -43,70 +43,56 @@ dependencies:
43
43
  requirements:
44
44
  - - "~>"
45
45
  - !ruby/object:Gem::Version
46
- version: '1.12'
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '1.12'
54
- - !ruby/object:Gem::Dependency
55
- name: ruby_llm-mcp
56
- requirement: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '1.0'
46
+ version: '2.0'
61
47
  type: :runtime
62
48
  prerelease: false
63
49
  version_requirements: !ruby/object:Gem::Requirement
64
50
  requirements:
65
51
  - - "~>"
66
52
  - !ruby/object:Gem::Version
67
- version: '1.0'
53
+ version: '2.0'
68
54
  - !ruby/object:Gem::Dependency
69
- name: prompt_manager
55
+ name: ruby_llm-providers-apfel
70
56
  requirement: !ruby/object:Gem::Requirement
71
57
  requirements:
72
58
  - - "~>"
73
59
  - !ruby/object:Gem::Version
74
- version: '1.0'
60
+ version: 0.2.1
75
61
  type: :runtime
76
62
  prerelease: false
77
63
  version_requirements: !ruby/object:Gem::Requirement
78
64
  requirements:
79
65
  - - "~>"
80
66
  - !ruby/object:Gem::Version
81
- version: '1.0'
67
+ version: 0.2.1
82
68
  - !ruby/object:Gem::Dependency
83
- name: ruby_llm-schema
69
+ name: ruby_llm-providers-lms
84
70
  requirement: !ruby/object:Gem::Requirement
85
71
  requirements:
86
72
  - - "~>"
87
73
  - !ruby/object:Gem::Version
88
- version: '0.3'
74
+ version: 0.2.1
89
75
  type: :runtime
90
76
  prerelease: false
91
77
  version_requirements: !ruby/object:Gem::Requirement
92
78
  requirements:
93
79
  - - "~>"
94
80
  - !ruby/object:Gem::Version
95
- version: '0.3'
81
+ version: 0.2.1
96
82
  - !ruby/object:Gem::Dependency
97
- name: ruby_llm-semantic_cache
83
+ name: prompt_manager
98
84
  requirement: !ruby/object:Gem::Requirement
99
85
  requirements:
100
86
  - - "~>"
101
87
  - !ruby/object:Gem::Version
102
- version: '0.1'
88
+ version: '1.0'
103
89
  type: :runtime
104
90
  prerelease: false
105
91
  version_requirements: !ruby/object:Gem::Requirement
106
92
  requirements:
107
93
  - - "~>"
108
94
  - !ruby/object:Gem::Version
109
- version: '0.1'
95
+ version: '1.0'
110
96
  - !ruby/object:Gem::Dependency
111
97
  name: async
112
98
  requirement: !ruby/object:Gem::Requirement
@@ -312,6 +298,7 @@ files:
312
298
  - examples/14_rusty_circuit/scout.rb
313
299
  - examples/14_rusty_circuit/show.log
314
300
  - examples/15_memory_network_and_bus/.envrc
301
+ - examples/15_memory_network_and_bus/README.md
315
302
  - examples/15_memory_network_and_bus/editor_in_chief.rb
316
303
  - examples/15_memory_network_and_bus/editorial_pipeline.rb
317
304
  - examples/15_memory_network_and_bus/linux_writer.rb
@@ -324,6 +311,7 @@ files:
324
311
  - examples/15_memory_network_and_bus/output/memory.json
325
312
  - examples/15_memory_network_and_bus/output/revision_1.md
326
313
  - examples/15_memory_network_and_bus/output/revision_2.md
314
+ - examples/15_memory_network_and_bus/output/revision_3.md
327
315
  - examples/15_memory_network_and_bus/output/windows_draft.md
328
316
  - examples/15_memory_network_and_bus/prompts/os_advocate.md
329
317
  - examples/15_memory_network_and_bus/prompts/os_chief.md
@@ -387,6 +375,7 @@ files:
387
375
  - examples/26_document_store/postgres_runbook.md
388
376
  - examples/26_document_store/redis_caching_guide.md
389
377
  - examples/26_document_store/sidekiq_guide.md
378
+ - examples/27_incident_response/README.md
390
379
  - examples/27_incident_response/incident_response.rb
391
380
  - examples/28_mcp_discovery.rb
392
381
  - examples/29_ractor_tools.rb
@@ -448,6 +437,7 @@ files:
448
437
  - examples/prompts/template_with_skills_test.md
449
438
  - examples/prompts/triage.md
450
439
  - examples/ruboruby.md
440
+ - examples/run_all.rb
451
441
  - examples/xyzzy.rb
452
442
  - lib/robot_lab.rb
453
443
  - lib/robot_lab/agent_skill.rb
@@ -490,6 +480,7 @@ files:
490
480
  - lib/robot_lab/robot/history_search.rb
491
481
  - lib/robot_lab/robot/hooking.rb
492
482
  - lib/robot_lab/robot/mcp_management.rb
483
+ - lib/robot_lab/robot/result_building.rb
493
484
  - lib/robot_lab/robot/template_rendering.rb
494
485
  - lib/robot_lab/robot_message.rb
495
486
  - lib/robot_lab/robot_result.rb
@@ -541,7 +532,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
541
532
  - !ruby/object:Gem::Version
542
533
  version: '0'
543
534
  requirements: []
544
- rubygems_version: 4.0.20
535
+ rubygems_version: 4.0.21
545
536
  specification_version: 4
546
537
  summary: Ruby framework for building and orchestrating multi-robot LLM workflows
547
538
  test_files: []