robot_lab 0.0.1 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (187) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/deploy-github-pages.yml +9 -9
  3. data/.irbrc +6 -0
  4. data/CHANGELOG.md +140 -0
  5. data/README.md +263 -48
  6. data/Rakefile +71 -1
  7. data/docs/api/core/index.md +53 -46
  8. data/docs/api/core/memory.md +200 -154
  9. data/docs/api/core/network.md +13 -3
  10. data/docs/api/core/robot.md +490 -130
  11. data/docs/api/core/state.md +55 -73
  12. data/docs/api/core/tool.md +205 -209
  13. data/docs/api/index.md +7 -28
  14. data/docs/api/mcp/client.md +119 -48
  15. data/docs/api/mcp/index.md +75 -60
  16. data/docs/api/mcp/server.md +120 -136
  17. data/docs/api/mcp/transports.md +172 -184
  18. data/docs/api/messages/index.md +35 -20
  19. data/docs/api/messages/text-message.md +67 -21
  20. data/docs/api/messages/tool-call-message.md +80 -41
  21. data/docs/api/messages/tool-result-message.md +119 -50
  22. data/docs/api/messages/user-message.md +48 -24
  23. data/docs/api/streaming/context.md +157 -74
  24. data/docs/api/streaming/events.md +114 -166
  25. data/docs/api/streaming/index.md +74 -72
  26. data/docs/architecture/core-concepts.md +360 -116
  27. data/docs/architecture/index.md +97 -59
  28. data/docs/architecture/message-flow.md +138 -129
  29. data/docs/architecture/network-orchestration.md +197 -50
  30. data/docs/architecture/robot-execution.md +199 -146
  31. data/docs/architecture/state-management.md +255 -187
  32. data/docs/concepts.md +311 -49
  33. data/docs/examples/basic-chat.md +89 -77
  34. data/docs/examples/index.md +222 -47
  35. data/docs/examples/mcp-server.md +207 -203
  36. data/docs/examples/multi-robot-network.md +129 -35
  37. data/docs/examples/rails-application.md +159 -160
  38. data/docs/examples/tool-usage.md +295 -204
  39. data/docs/getting-started/configuration.md +347 -154
  40. data/docs/getting-started/index.md +1 -1
  41. data/docs/getting-started/installation.md +22 -13
  42. data/docs/getting-started/quick-start.md +166 -121
  43. data/docs/guides/building-robots.md +418 -212
  44. data/docs/guides/creating-networks.md +143 -24
  45. data/docs/guides/index.md +0 -5
  46. data/docs/guides/mcp-integration.md +152 -113
  47. data/docs/guides/memory.md +220 -164
  48. data/docs/guides/rails-integration.md +244 -162
  49. data/docs/guides/streaming.md +137 -187
  50. data/docs/guides/using-tools.md +259 -212
  51. data/docs/index.md +46 -41
  52. data/examples/01_simple_robot.rb +6 -9
  53. data/examples/02_tools.rb +6 -9
  54. data/examples/03_network.rb +19 -17
  55. data/examples/04_mcp.rb +5 -8
  56. data/examples/05_streaming.rb +5 -8
  57. data/examples/06_prompt_templates.rb +42 -37
  58. data/examples/07_network_memory.rb +13 -14
  59. data/examples/08_llm_config.rb +169 -0
  60. data/examples/09_chaining.rb +262 -0
  61. data/examples/10_memory.rb +331 -0
  62. data/examples/11_network_introspection.rb +253 -0
  63. data/examples/12_message_bus.rb +74 -0
  64. data/examples/13_spawn.rb +90 -0
  65. data/examples/14_rusty_circuit/comic.rb +143 -0
  66. data/examples/14_rusty_circuit/display.rb +203 -0
  67. data/examples/14_rusty_circuit/heckler.rb +63 -0
  68. data/examples/14_rusty_circuit/open_mic.rb +123 -0
  69. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +20 -0
  70. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +23 -0
  71. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +20 -0
  72. data/examples/14_rusty_circuit/scout.rb +156 -0
  73. data/examples/14_rusty_circuit/scout_notes.md +89 -0
  74. data/examples/14_rusty_circuit/show.log +234 -0
  75. data/examples/15_memory_network_and_bus/editor_in_chief.rb +24 -0
  76. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +206 -0
  77. data/examples/15_memory_network_and_bus/linux_writer.rb +80 -0
  78. data/examples/15_memory_network_and_bus/os_editor.rb +46 -0
  79. data/examples/15_memory_network_and_bus/os_writer.rb +46 -0
  80. data/examples/15_memory_network_and_bus/output/combined_article.md +13 -0
  81. data/examples/15_memory_network_and_bus/output/final_article.md +15 -0
  82. data/examples/15_memory_network_and_bus/output/linux_draft.md +5 -0
  83. data/examples/15_memory_network_and_bus/output/mac_draft.md +7 -0
  84. data/examples/15_memory_network_and_bus/output/memory.json +13 -0
  85. data/examples/15_memory_network_and_bus/output/revision_1.md +19 -0
  86. data/examples/15_memory_network_and_bus/output/revision_2.md +15 -0
  87. data/examples/15_memory_network_and_bus/output/windows_draft.md +7 -0
  88. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +13 -0
  89. data/examples/15_memory_network_and_bus/prompts/os_chief.md +13 -0
  90. data/examples/15_memory_network_and_bus/prompts/os_editor.md +13 -0
  91. data/examples/16_writers_room/display.rb +158 -0
  92. data/examples/16_writers_room/output/.gitignore +2 -0
  93. data/examples/16_writers_room/output/opus_001.md +263 -0
  94. data/examples/16_writers_room/output/opus_001_notes.log +470 -0
  95. data/examples/16_writers_room/prompts/writer.md +37 -0
  96. data/examples/16_writers_room/room.rb +150 -0
  97. data/examples/16_writers_room/tools.rb +162 -0
  98. data/examples/16_writers_room/writer.rb +121 -0
  99. data/examples/16_writers_room/writers_room.rb +162 -0
  100. data/examples/README.md +197 -0
  101. data/examples/prompts/{assistant/system.txt.erb → assistant.md} +3 -0
  102. data/examples/prompts/{billing/system.txt.erb → billing.md} +3 -0
  103. data/examples/prompts/{classifier/system.txt.erb → classifier.md} +3 -0
  104. data/examples/prompts/comedian.md +6 -0
  105. data/examples/prompts/comedy_critic.md +10 -0
  106. data/examples/prompts/configurable.md +9 -0
  107. data/examples/prompts/dispatcher.md +12 -0
  108. data/examples/prompts/{entity_extractor/system.txt.erb → entity_extractor.md} +3 -0
  109. data/examples/prompts/{escalation/system.txt.erb → escalation.md} +7 -0
  110. data/examples/prompts/frontmatter_mcp_test.md +9 -0
  111. data/examples/prompts/frontmatter_named_test.md +5 -0
  112. data/examples/prompts/frontmatter_tools_test.md +6 -0
  113. data/examples/prompts/{general/system.txt.erb → general.md} +3 -0
  114. data/examples/prompts/{github_assistant/system.txt.erb → github_assistant.md} +8 -0
  115. data/examples/prompts/{helper/system.txt.erb → helper.md} +3 -0
  116. data/examples/prompts/{keyword_extractor/system.txt.erb → keyword_extractor.md} +3 -0
  117. data/examples/prompts/llm_config_demo.md +20 -0
  118. data/examples/prompts/{order_support/system.txt.erb → order_support.md} +8 -0
  119. data/examples/prompts/os_advocate.md +13 -0
  120. data/examples/prompts/os_chief.md +13 -0
  121. data/examples/prompts/os_editor.md +13 -0
  122. data/examples/prompts/{product_support/system.txt.erb → product_support.md} +7 -0
  123. data/examples/prompts/{sentiment_analyzer/system.txt.erb → sentiment_analyzer.md} +3 -0
  124. data/examples/prompts/{synthesizer/system.txt.erb → synthesizer.md} +3 -0
  125. data/examples/prompts/{technical/system.txt.erb → technical.md} +3 -0
  126. data/examples/prompts/{triage/system.txt.erb → triage.md} +6 -0
  127. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
  128. data/lib/robot_lab/ask_user.rb +75 -0
  129. data/lib/robot_lab/config/defaults.yml +121 -0
  130. data/lib/robot_lab/config.rb +183 -0
  131. data/lib/robot_lab/error.rb +6 -0
  132. data/lib/robot_lab/mcp/client.rb +1 -1
  133. data/lib/robot_lab/memory.rb +10 -34
  134. data/lib/robot_lab/network.rb +13 -20
  135. data/lib/robot_lab/robot/bus_messaging.rb +239 -0
  136. data/lib/robot_lab/robot/mcp_management.rb +88 -0
  137. data/lib/robot_lab/robot/template_rendering.rb +130 -0
  138. data/lib/robot_lab/robot.rb +240 -330
  139. data/lib/robot_lab/robot_message.rb +44 -0
  140. data/lib/robot_lab/robot_result.rb +1 -0
  141. data/lib/robot_lab/run_config.rb +184 -0
  142. data/lib/robot_lab/state_proxy.rb +2 -12
  143. data/lib/robot_lab/streaming/context.rb +1 -1
  144. data/lib/robot_lab/task.rb +8 -1
  145. data/lib/robot_lab/tool.rb +108 -172
  146. data/lib/robot_lab/tool_config.rb +1 -1
  147. data/lib/robot_lab/tool_manifest.rb +2 -18
  148. data/lib/robot_lab/utils.rb +39 -0
  149. data/lib/robot_lab/version.rb +1 -1
  150. data/lib/robot_lab.rb +89 -57
  151. data/mkdocs.yml +0 -11
  152. metadata +121 -135
  153. data/docs/api/adapters/anthropic.md +0 -121
  154. data/docs/api/adapters/gemini.md +0 -133
  155. data/docs/api/adapters/index.md +0 -104
  156. data/docs/api/adapters/openai.md +0 -134
  157. data/docs/api/history/active-record-adapter.md +0 -195
  158. data/docs/api/history/config.md +0 -191
  159. data/docs/api/history/index.md +0 -132
  160. data/docs/api/history/thread-manager.md +0 -144
  161. data/docs/guides/history.md +0 -359
  162. data/examples/prompts/assistant/user.txt.erb +0 -1
  163. data/examples/prompts/billing/user.txt.erb +0 -1
  164. data/examples/prompts/classifier/user.txt.erb +0 -1
  165. data/examples/prompts/entity_extractor/user.txt.erb +0 -3
  166. data/examples/prompts/escalation/user.txt.erb +0 -34
  167. data/examples/prompts/general/user.txt.erb +0 -1
  168. data/examples/prompts/github_assistant/user.txt.erb +0 -1
  169. data/examples/prompts/helper/user.txt.erb +0 -1
  170. data/examples/prompts/keyword_extractor/user.txt.erb +0 -3
  171. data/examples/prompts/order_support/user.txt.erb +0 -22
  172. data/examples/prompts/product_support/user.txt.erb +0 -32
  173. data/examples/prompts/sentiment_analyzer/user.txt.erb +0 -3
  174. data/examples/prompts/synthesizer/user.txt.erb +0 -15
  175. data/examples/prompts/technical/user.txt.erb +0 -1
  176. data/examples/prompts/triage/user.txt.erb +0 -17
  177. data/lib/robot_lab/adapters/anthropic.rb +0 -163
  178. data/lib/robot_lab/adapters/base.rb +0 -85
  179. data/lib/robot_lab/adapters/gemini.rb +0 -193
  180. data/lib/robot_lab/adapters/openai.rb +0 -159
  181. data/lib/robot_lab/adapters/registry.rb +0 -81
  182. data/lib/robot_lab/configuration.rb +0 -143
  183. data/lib/robot_lab/errors.rb +0 -70
  184. data/lib/robot_lab/history/active_record_adapter.rb +0 -146
  185. data/lib/robot_lab/history/config.rb +0 -115
  186. data/lib/robot_lab/history/thread_manager.rb +0 -93
  187. data/lib/robot_lab/robotic_model.rb +0 -324
@@ -0,0 +1,169 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example 8: LLM Configuration via MywayConfig
5
+ #
6
+ # Demonstrates how RobotLab uses MywayConfig for environment-specific
7
+ # configuration (similar to Rails database.yml).
8
+ #
9
+ # Configuration is loaded from multiple sources in priority order:
10
+ # 1. Bundled defaults (lib/robot_lab/config/defaults.yml)
11
+ # 2. Environment-specific overrides (development, test, production)
12
+ # 3. XDG user config (~/.config/robot_lab/config.yml)
13
+ # 4. Project config (./config/robot_lab.yml)
14
+ # 5. Environment variables (ROBOT_LAB_*)
15
+ # 6. Template front matter (model, temperature, etc. in .md YAML header)
16
+ # 7. Constructor parameters (model:, temperature:, etc. passed to Robot.new)
17
+ # 8. with_* methods (runtime chaining, e.g. robot.with_temperature(0.9))
18
+ # 9. Run-time context (kwargs passed to robot.run that re-render the template)
19
+ #
20
+ # Environment is determined by ROBOT_LAB_ENV, RAILS_ENV, or RACK_ENV.
21
+ #
22
+ # Environment variable examples:
23
+ # ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
24
+ # ROBOT_LAB_RUBY_LLM__MODEL=gpt-4
25
+ # ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=180
26
+ #
27
+ # Usage:
28
+ # ANTHROPIC_API_KEY=your_key ruby examples/08_llm_config.rb
29
+ # ROBOT_LAB_ENV=test ANTHROPIC_API_KEY=your_key ruby examples/08_llm_config.rb
30
+ # ROBOT_LAB_ENV=production ANTHROPIC_API_KEY=your_key ruby examples/08_llm_config.rb
31
+
32
+ # Configure template path before loading (MywayConfig reads env vars on init)
33
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
34
+
35
+ require_relative "../lib/robot_lab"
36
+
37
+ # =============================================================================
38
+ # Demonstration
39
+ # =============================================================================
40
+
41
+ puts "=" * 70
42
+ puts "Example 8: LLM Configuration via MywayConfig"
43
+ puts "=" * 70
44
+ puts
45
+
46
+ # --- Gather environment from the user via AskUser ---
47
+ ask = RobotLab::AskUser.new
48
+ env = ask.call(
49
+ "question" => "Which environment should the demo run in?",
50
+ "choices" => %w[development test production],
51
+ "default" => ENV['ROBOT_LAB_ENV'] || ENV['RAILS_ENV'] || ENV['RACK_ENV'] || "development"
52
+ )
53
+ puts
54
+
55
+ # Display configuration values (hiding sensitive keys)
56
+ config = RobotLab.config
57
+
58
+ puts "Core Settings:"
59
+ puts " max_iterations: #{config.max_iterations}"
60
+ puts " streaming_enabled: #{config.streaming_enabled}"
61
+ puts " template_path: #{config.template_path || '(default)'}"
62
+ puts
63
+
64
+ puts "RubyLLM Settings:"
65
+ puts " model: #{config.ruby_llm.model}"
66
+ puts " provider: #{config.ruby_llm.provider}"
67
+ puts " request_timeout: #{config.ruby_llm.request_timeout}s"
68
+ puts " max_retries: #{config.ruby_llm.max_retries}"
69
+ puts " log_level: #{config.ruby_llm.log_level}"
70
+
71
+ # Show API key status without revealing values
72
+ api_key = config.ruby_llm.anthropic_api_key
73
+ puts " anthropic_api_key: #{api_key ? '[SET via config]' : (ENV['ANTHROPIC_API_KEY'] ? '[SET via env]' : '(not set)')}"
74
+ puts
75
+
76
+ puts "-" * 70
77
+ puts "Configuration hierarchy (highest priority first):"
78
+ puts
79
+ puts " Per-Robot (override global settings for a specific robot):"
80
+ puts " 11. Run-time context: kwargs to robot.run re-render template"
81
+ puts " 10. with_* methods: robot.with_temperature(0.9).ask(...)"
82
+ puts " 9. Constructor params: Robot.new(model: ..., temperature: ...)"
83
+ puts " 8. Template front matter: model, temperature, etc. in .md YAML header"
84
+ puts " 7. Robot RunConfig: config: passed to Robot constructor"
85
+ puts
86
+ puts " Network/Task (shared defaults for a group of robots):"
87
+ puts " 6. Task RunConfig: config: passed to task() in network"
88
+ puts " 5. Network RunConfig: config: passed to create_network()"
89
+ puts
90
+ puts " Global (apply to all robots unless overridden):"
91
+ puts " 4. Environment variables: ROBOT_LAB_RUBY_LLM__MODEL, etc."
92
+ puts " 3. Project config: ./config/robot_lab.yml"
93
+ puts " 2. User config: ~/.config/robot_lab/config.yml"
94
+ puts " 1. Bundled defaults: lib/robot_lab/config/defaults.yml + env overrides"
95
+ puts "-" * 70
96
+ puts
97
+
98
+ # --- RunConfig demonstration ---
99
+ puts "-" * 70
100
+ puts "RunConfig: Shared Operational Defaults"
101
+ puts "-" * 70
102
+ puts
103
+
104
+ shared = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
105
+ puts " shared = RunConfig.new(model: \"claude-sonnet-4\", temperature: 0.5)"
106
+ puts " shared.to_h => #{shared.to_h.inspect}"
107
+ puts
108
+
109
+ creative = RobotLab::RunConfig.new(temperature: 0.9)
110
+ merged = shared.merge(creative)
111
+ puts " creative = RunConfig.new(temperature: 0.9)"
112
+ puts " merged = shared.merge(creative)"
113
+ puts " merged.to_h => #{merged.to_h.inspect}"
114
+ puts " (model inherited from shared, temperature overridden by creative)"
115
+ puts
116
+
117
+ # Create a robot using the configuration
118
+ robot = RobotLab.build(
119
+ name: "config_demo",
120
+ template: :llm_config_demo,
121
+ local_tools: [RobotLab::AskUser],
122
+ context: {
123
+ environment: env,
124
+ model: config.ruby_llm.model,
125
+ provider: config.ruby_llm.provider.to_s
126
+ }
127
+ )
128
+
129
+ puts "Running robot with #{env} configuration..."
130
+ puts "Model: #{robot.model}"
131
+ puts
132
+
133
+ # Run the robot
134
+ result = robot.run(
135
+ "Briefly explain how environment-specific LLM configuration works " \
136
+ "and why it's useful."
137
+ )
138
+
139
+ # Display the result
140
+ puts "Response:"
141
+ result.output.each do |message|
142
+ puts message.content if message.respond_to?(:content)
143
+ end
144
+
145
+ puts <<~FOOTER
146
+
147
+ #{"=" * 70}
148
+ Configuration demonstrated successfully!
149
+
150
+ Configuration flows through three layers:
151
+ Global (MywayConfig): YAML defaults, env overrides, XDG, env vars
152
+ Network/Task (RunConfig): shared defaults for groups of robots
153
+ Per-Robot: RunConfig, template front matter, constructor params, with_*
154
+
155
+ RunConfig lets you express shared defaults that flow through the hierarchy:
156
+ shared = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
157
+ network = RobotLab.create_network(name: "team", config: shared) { ... }
158
+ robot = RobotLab.build(name: "bot", config: shared, temperature: 0.9)
159
+
160
+ Example environment variable overrides:
161
+ ROBOT_LAB_RUBY_LLM__MODEL=gpt-4
162
+ ROBOT_LAB_RUBY_LLM__REQUEST_TIMEOUT=180
163
+ ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
164
+
165
+ Try running with different environments:
166
+ ROBOT_LAB_ENV=test ruby examples/08_llm_config.rb
167
+ ROBOT_LAB_ENV=production ruby examples/08_llm_config.rb
168
+ #{"=" * 70}
169
+ FOOTER
@@ -0,0 +1,262 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Example 9: Robot Chaining & Reconfiguration
5
+ #
6
+ # Demonstrates the Robot API surface for runtime configuration without
7
+ # making any LLM calls. Covers:
8
+ # - with_* method chaining
9
+ # - update() for reconfiguration
10
+ # - to_h introspection (via amazing_print)
11
+ # - Config diffs between steps (via hashdiff)
12
+ # - Template front matter config keys
13
+ # - Constructor params overriding front matter
14
+ # - Config hierarchy in action
15
+ # - AskUser tool for gathering template parameters interactively
16
+ #
17
+ # Usage:
18
+ # bundle exec ruby examples/09_chaining.rb
19
+
20
+ # Configure template path before loading
21
+ ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
22
+
23
+ require_relative "../lib/robot_lab"
24
+ require "amazing_print"
25
+ require "hashdiff"
26
+ require "stringio"
27
+
28
+ # Show a config snapshot and the diff from the previous snapshot.
29
+ # Returns the current hash for use as the next "previous" snapshot.
30
+ def show_config(robot, previous_config = nil)
31
+ current = robot.to_h
32
+ ap current
33
+
34
+ if previous_config
35
+ diff = Hashdiff.diff(previous_config, current)
36
+ if diff.empty?
37
+ puts " (no changes to to_h)"
38
+ else
39
+ puts
40
+ puts " Diff from previous:"
41
+ diff.each do |change|
42
+ op, path, *values = change
43
+ case op
44
+ when "+"
45
+ puts " + #{path}: #{values.first.inspect}"
46
+ when "-"
47
+ puts " - #{path}: #{values.first.inspect}"
48
+ when "~"
49
+ puts " ~ #{path}: #{values.first.inspect} -> #{values.last.inspect}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ current
56
+ end
57
+
58
+ puts "=" * 70
59
+ puts "Example 9: Robot Chaining & Reconfiguration"
60
+ puts "=" * 70
61
+ puts
62
+
63
+ # =============================================================================
64
+ # Section 1: Template front matter config
65
+ # =============================================================================
66
+
67
+ puts "--- Section 1: Template Front Matter Config ---"
68
+ puts
69
+
70
+ # The 'configurable' template sets temperature: 0.3 and max_tokens: 200
71
+ # via YAML front matter. These are applied automatically.
72
+ robot = RobotLab.build(
73
+ name: "chameleon",
74
+ template: :configurable,
75
+ context: { task_type: "analysis" }
76
+ )
77
+
78
+ puts "Robot created with :configurable template"
79
+ prev = show_config(robot)
80
+ puts
81
+
82
+ # =============================================================================
83
+ # Section 2: with_* method chaining
84
+ # =============================================================================
85
+
86
+ puts "--- Section 2: with_* Method Chaining ---"
87
+ puts
88
+
89
+ # with_* methods return self, enabling fluent chaining.
90
+ # These override whatever the template set.
91
+ robot.with_temperature(0.9)
92
+
93
+ puts "After chaining with_temperature(0.9):"
94
+ prev = show_config(robot, prev)
95
+ puts
96
+
97
+ # =============================================================================
98
+ # Section 3: update() for reconfiguration
99
+ # =============================================================================
100
+
101
+ puts "--- Section 3: update() for Reconfiguration ---"
102
+ puts
103
+
104
+ # update() can swap the template, model, temperature, and other settings.
105
+ robot.update(template: :assistant, temperature: 0.5)
106
+
107
+ puts "After update(template: :assistant, temperature: 0.5):"
108
+ prev = show_config(robot, prev)
109
+ puts
110
+
111
+ # Swap back with context
112
+ robot.update(template: :configurable, context: { task_type: "creative" })
113
+
114
+ puts "After update(template: :configurable, context: { task_type: 'creative' }):"
115
+ prev = show_config(robot, prev)
116
+ puts
117
+
118
+ # =============================================================================
119
+ # Section 4: Constructor params override front matter
120
+ # =============================================================================
121
+
122
+ puts "--- Section 4: Constructor Params Override Front Matter ---"
123
+ puts
124
+
125
+ # The configurable template sets temperature: 0.3 in front matter.
126
+ # Passing temperature: 0.9 to the constructor overrides it.
127
+ robot2 = RobotLab.build(
128
+ name: "override_demo",
129
+ template: :configurable,
130
+ context: { task_type: "creative" },
131
+ temperature: 0.9
132
+ )
133
+
134
+ puts "Template sets temperature: 0.3, constructor passes temperature: 0.9"
135
+ prev2 = show_config(robot2)
136
+ puts
137
+
138
+ # =============================================================================
139
+ # Section 5: RunConfig as alternative to individual kwargs
140
+ # =============================================================================
141
+
142
+ puts "--- Section 5: RunConfig as Alternative to Individual kwargs ---"
143
+ puts
144
+
145
+ # Instead of passing model:, temperature:, etc. individually,
146
+ # use a RunConfig to express shared defaults.
147
+ shared = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
148
+
149
+ puts "RunConfig: #{shared.to_h.inspect}"
150
+ puts
151
+
152
+ # Robot inherits from RunConfig; constructor kwargs still override
153
+ robot3 = RobotLab.build(
154
+ name: "runconfig_demo",
155
+ template: :configurable,
156
+ context: { task_type: "analysis" },
157
+ config: shared,
158
+ temperature: 0.8 # overrides RunConfig's 0.5
159
+ )
160
+
161
+ puts "Robot with config: shared, temperature: 0.8"
162
+ puts "(RunConfig sets 0.5, constructor overrides to 0.8)"
163
+ prev3 = show_config(robot3)
164
+ puts
165
+
166
+ # RunConfig merge semantics
167
+ creative = RobotLab::RunConfig.new(temperature: 0.9, max_tokens: 2000)
168
+ merged = shared.merge(creative)
169
+
170
+ puts "Merge: shared.merge(creative)"
171
+ puts " shared: #{shared.to_h.inspect}"
172
+ puts " creative: #{creative.to_h.inspect}"
173
+ puts " merged: #{merged.to_h.inspect}"
174
+ puts " (model inherited, temperature overridden, max_tokens added)"
175
+ puts
176
+
177
+ # =============================================================================
178
+ # Section 6: Bare robot with chaining
179
+ # =============================================================================
180
+
181
+ puts "--- Section 6: Bare Robot with Chaining ---"
182
+ puts
183
+
184
+ # A bare robot has no template. Configure entirely via chaining.
185
+ bare = RobotLab.build(name: "bare")
186
+
187
+ puts "Bare robot before chaining:"
188
+ prev_bare = show_config(bare)
189
+ puts
190
+
191
+ bare
192
+ .with_instructions("You are a helpful assistant.")
193
+ .with_temperature(0.5)
194
+
195
+ puts "After with_instructions(...).with_temperature(0.5):"
196
+ prev_bare = show_config(bare, prev_bare)
197
+ puts
198
+
199
+ # =============================================================================
200
+ # Section 7: with_template() on an existing robot
201
+ # =============================================================================
202
+
203
+ puts "--- Section 7: with_template() on Existing Robot ---"
204
+ puts
205
+
206
+ # You can apply a template to a robot after creation.
207
+ bare.with_template(:helper)
208
+
209
+ puts "After bare.with_template(:helper):"
210
+ show_config(bare, prev_bare)
211
+ puts
212
+
213
+ # =============================================================================
214
+ # Section 8: AskUser tool for gathering template parameters
215
+ # =============================================================================
216
+
217
+ puts "--- Section 8: AskUser for Template Parameters ---"
218
+ puts
219
+ puts "The :configurable template declares `task_type: general` in its front"
220
+ puts "matter. That default is offered to the user — they can accept it by"
221
+ puts "pressing Enter or type something else. Parameters with null values"
222
+ puts "have no default and require user input."
223
+ puts
224
+
225
+ # Build a robot with AskUser — the template's task_type default ("general")
226
+ # is offered to the user, who can accept or override it.
227
+ interactive = RobotLab.build(
228
+ name: "interactive_demo",
229
+ template: :configurable,
230
+ local_tools: [RobotLab::AskUser]
231
+ )
232
+
233
+ puts "Robot 'interactive_demo' has AskUser in its tools."
234
+ puts "The template renders with the default: task_type = \"general\""
235
+ puts "When run, the robot can call ask_user to let the user confirm"
236
+ puts "or change the value."
237
+ puts
238
+
239
+ # Simulate what the user would see (no LLM call needed)
240
+ output = StringIO.new
241
+ demo_tool = RobotLab::AskUser.new(robot: interactive)
242
+ interactive.output = output
243
+ interactive.input = StringIO.new("2\n")
244
+
245
+ result = demo_tool.call(
246
+ "question" => "What type of task should I optimize for?",
247
+ "choices" => %w[general analysis creative coding research],
248
+ "default" => "general"
249
+ )
250
+
251
+ puts output.string
252
+ puts "User selected: #{result}"
253
+ puts
254
+ puts "The robot would re-render the template with task_type: \"#{result}\""
255
+ puts "producing: \"You are a precise assistant optimized for #{result} tasks.\""
256
+ puts
257
+ puts "If the user had pressed Enter, the default \"general\" would be kept."
258
+ puts
259
+
260
+ puts "=" * 70
261
+ puts "All sections completed without any LLM calls."
262
+ puts "=" * 70