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
@@ -4,240 +4,293 @@ This page details how a robot processes messages and generates responses.
4
4
 
5
5
  ## Execution Overview
6
6
 
7
- When you call `robot.run(state:, network:)`, several steps occur:
7
+ When you call `robot.run("message")`, several steps occur:
8
8
 
9
9
  ```mermaid
10
10
  sequenceDiagram
11
11
  participant App as Application
12
12
  participant Robot
13
- participant Model as RoboticModel
14
- participant Adapter
13
+ participant Memory
14
+ participant Chat as @chat (RubyLLM)
15
15
  participant LLM
16
16
 
17
- App->>Robot: run(state, network)
18
- Robot->>Robot: resolve_tools()
19
- Robot->>Model: infer(messages, tools)
20
- Model->>Adapter: format_messages()
21
- Model->>LLM: API Request
17
+ App->>Robot: run("message")
18
+ Robot->>Memory: resolve_active_memory()
19
+ Robot->>Robot: resolve_mcp_hierarchy()
20
+ Robot->>Robot: resolve_tools_hierarchy()
21
+ Robot->>Robot: ensure_mcp_clients()
22
+ Robot->>Robot: filtered_tools()
23
+ Robot->>Chat: with_tools(*filtered)
24
+ Robot->>Chat: ask("message")
25
+ Chat->>LLM: API Request
22
26
 
23
27
  loop Tool Calls
24
- LLM-->>Model: tool_call response
25
- Model->>Robot: execute_tool()
26
- Robot->>Model: tool_result
27
- Model->>LLM: continue
28
+ LLM-->>Chat: tool_call response
29
+ Chat->>Chat: execute tool
30
+ Chat->>LLM: tool result
28
31
  end
29
32
 
30
- LLM-->>Model: final response
31
- Model->>Adapter: parse_response()
32
- Model-->>Robot: InferenceResponse
33
+ LLM-->>Chat: final response
34
+ Chat-->>Robot: RubyLLM::Response
35
+ Robot->>Robot: build_result(response)
33
36
  Robot-->>App: RobotResult
34
37
  ```
35
38
 
36
39
  ## Step-by-Step Flow
37
40
 
38
- ### 1. Tool Resolution
41
+ ### 1. Memory Resolution
39
42
 
40
- Before making any LLM call, the robot resolves available tools:
43
+ The robot determines which memory to use for this run:
41
44
 
42
45
  ```ruby
43
- # Internal process
44
- tools = []
45
- tools += local_tools # Tools defined on robot
46
- tools += mcp_tools # Tools from MCP servers
47
- tools = apply_whitelist(tools) # Filter by allowed tools
46
+ # Priority order:
47
+ # 1. Explicit network_memory: parameter
48
+ # 2. Network's memory (if running in a network)
49
+ # 3. Robot's inherent @memory (standalone mode)
50
+ run_memory = resolve_active_memory(network: network, network_memory: network_memory)
51
+
52
+ # Merge runtime memory if provided
53
+ case memory
54
+ when Memory then run_memory = memory
55
+ when Hash then run_memory.merge!(memory)
56
+ end
57
+
58
+ # Track who is writing to memory
59
+ run_memory.current_writer = @name
48
60
  ```
49
61
 
50
- ### 2. Message Preparation
62
+ ### 2. MCP Hierarchy Resolution
51
63
 
52
- The robot prepares messages from state:
64
+ MCP servers are resolved through a hierarchy: **runtime > robot build-time > network > global config**.
53
65
 
54
66
  ```ruby
55
- messages = []
56
- messages << system_message # From template
57
- messages += state.messages # Conversation history
58
- messages << user_message # Current input
67
+ # Resolve build-time config against network/global
68
+ parent_value = network&.network&.mcp || RobotLab.config.mcp
69
+ build_resolved = ToolConfig.resolve_mcp(@mcp_config, parent_value: parent_value)
70
+
71
+ # Then resolve runtime override against build-time
72
+ resolved_mcp = ToolConfig.resolve_mcp(runtime_mcp, parent_value: build_resolved)
59
73
  ```
60
74
 
61
- ### 3. LLM Inference
75
+ Values at each level:
62
76
 
63
- Messages are sent to the LLM via `RoboticModel`:
77
+ - `:none` -- no MCP servers at this level
78
+ - `:inherit` -- use parent level's MCP config
79
+ - `Array` -- explicit list of server configurations
80
+
81
+ ### 3. MCP Client Initialization
82
+
83
+ If MCP servers need to be connected (or reconnected), the robot initializes clients:
64
84
 
65
85
  ```ruby
66
- response = model.infer(
67
- messages,
68
- tools,
69
- tool_choice: "auto",
70
- streaming: streaming_callback
71
- )
86
+ # Connect to each MCP server
87
+ mcp_servers.each do |server_config|
88
+ client = MCP::Client.new(server_config)
89
+ client.connect
90
+
91
+ if client.connected?
92
+ @mcp_clients[client.server.name] = client
93
+ discover_mcp_tools(client, server_name) # Auto-discover tools
94
+ end
95
+ end
72
96
  ```
73
97
 
74
- ### 4. Tool Execution Loop
98
+ ### 4. Tools Resolution
75
99
 
76
- If the LLM requests tool calls:
100
+ Tools are resolved through the same hierarchy and filtered:
77
101
 
78
102
  ```ruby
79
- loop do
80
- if response.wants_tools?
81
- response.tool_calls.each do |tool_call|
82
- result = execute_tool(tool_call)
83
- # Result sent back to LLM
84
- end
85
- response = model.continue_with_results(results)
86
- else
87
- break
88
- end
89
- end
103
+ # Collect all available tools
104
+ available = @local_tools + @mcp_tools
105
+
106
+ # Apply whitelist if specified
107
+ filtered = ToolConfig.filter_tools(available, allowed_names: resolved_tools)
108
+
109
+ # Apply tools to the persistent chat
110
+ @chat.with_tools(*filtered) if filtered.any?
90
111
  ```
91
112
 
92
- ### 5. Result Construction
113
+ ### 5. LLM Inference
93
114
 
94
- Finally, a `RobotResult` is created:
115
+ The message is sent to the LLM via `Agent#ask`, which delegates to `@chat.ask`:
95
116
 
96
117
  ```ruby
97
- RobotResult.new(
98
- robot_name: name,
99
- output: response.output,
100
- tool_calls: executed_tools,
101
- stop_reason: response.stop_reason
102
- )
118
+ # Robot#run calls Agent#ask
119
+ response = ask(message, **kwargs)
120
+
121
+ # Internally, Agent#ask calls:
122
+ # @chat.ask(message)
103
123
  ```
104
124
 
105
- ## Tool Execution
125
+ The persistent `@chat` (a `RubyLLM::Chat` instance) handles:
106
126
 
107
- ### Tool Call Processing
127
+ - Maintaining conversation history
128
+ - Sending the system prompt
129
+ - Formatting messages for the provider
130
+ - Executing the tool call loop automatically
108
131
 
109
- When the LLM requests a tool:
132
+ ### 6. Tool Execution Loop
110
133
 
111
- 1. **Identify Tool**: Match tool name to registered tools
112
- 2. **Validate Input**: Check parameters against schema
113
- 3. **Execute Handler**: Call the tool's handler function
114
- 4. **Capture Result**: Wrap response in ToolResultMessage
115
- 5. **Return to LLM**: Send result for continued processing
134
+ RubyLLM's `@chat` handles the tool loop automatically. When the LLM requests a tool call:
116
135
 
117
- ### Execution Context
136
+ 1. `@chat` identifies the tool from its registered tools
137
+ 2. Calls the tool's `execute` method (for `RubyLLM::Tool` subclasses) or `call` method (for `RobotLab::Tool`)
138
+ 3. Sends the result back to the LLM
139
+ 4. Repeats until the LLM produces a final text response
118
140
 
119
- Tools receive context about their execution environment:
141
+ The `on_tool_call` and `on_tool_result` callbacks fire during this loop if configured:
120
142
 
121
143
  ```ruby
122
- tool.handler.call(
123
- **tool_call.input, # User-provided arguments
124
- robot: self, # The executing robot
125
- network: network, # Network context
126
- state: state # Current state
127
- )
144
+ # These callbacks are registered on @chat during Robot#initialize
145
+ @chat.on_tool_call(&@on_tool_call) if @on_tool_call
146
+ @chat.on_tool_result(&@on_tool_result) if @on_tool_result
128
147
  ```
129
148
 
130
- ### Error Handling
149
+ ### 7. Result Construction
131
150
 
132
- Tool errors are captured and returned to the LLM:
151
+ After the LLM responds, a `RobotResult` is built:
133
152
 
134
153
  ```ruby
135
- begin
136
- result = tool.handler.call(**args)
137
- ToolResultMessage.new(tool: tool_call, content: { data: result })
138
- rescue StandardError => e
139
- ToolResultMessage.new(tool: tool_call, content: { error: e.message })
154
+ def build_result(response, _memory)
155
+ output = if response.respond_to?(:content) && response.content
156
+ [TextMessage.new(role: 'assistant', content: response.content)]
157
+ else
158
+ []
159
+ end
160
+
161
+ tool_calls = response.respond_to?(:tool_calls) ? (response.tool_calls || []) : []
162
+
163
+ RobotResult.new(
164
+ robot_name: @name,
165
+ output: output,
166
+ tool_calls: normalize_tool_calls(tool_calls),
167
+ stop_reason: response.respond_to?(:stop_reason) ? response.stop_reason : nil
168
+ )
140
169
  end
141
170
  ```
142
171
 
143
- ## Iteration Limits
172
+ ## RobotResult
144
173
 
145
- Robot execution has safeguards:
174
+ The result object from a `robot.run` call:
146
175
 
147
- | Limit | Default | Purpose |
148
- |-------|---------|---------|
149
- | `max_tool_iterations` | 10 | Max tool calls per robot run |
150
-
151
- When limits are reached, execution stops with the current state.
176
+ ```ruby
177
+ result = robot.run("Hello!")
178
+
179
+ result.robot_name # => "assistant"
180
+ result.output # => [TextMessage, ...]
181
+ result.tool_calls # => [ToolResultMessage, ...]
182
+ result.stop_reason # => "stop" or nil
183
+ result.created_at # => Time
184
+ result.id # => UUID string
185
+
186
+ # Convenience methods
187
+ result.last_text_content # => "Hi there!" (last text message content)
188
+ result.has_tool_calls? # => false
189
+ result.stopped? # => true
190
+ ```
152
191
 
153
192
  ## Streaming
154
193
 
155
- Robots support streaming responses:
194
+ Robots support streaming by passing a block to `run`:
156
195
 
157
196
  ```ruby
158
- robot.run(
159
- state: state,
160
- network: network,
161
- streaming: ->(event) {
162
- case event.type
163
- when :delta then print event.content
164
- when :tool_call then puts "Calling: #{event.tool_name}"
165
- end
166
- }
167
- )
197
+ result = robot.run("Tell me a story") do |event|
198
+ print event.text if event.respond_to?(:text)
199
+ end
168
200
  ```
169
201
 
170
- ### Streaming Events
202
+ The block is forwarded to `Agent#ask` which passes it to `@chat.ask`. Streaming events are provider-specific but typically include text deltas.
171
203
 
172
- | Event Type | Description |
173
- |------------|-------------|
174
- | `run.started` | Robot execution began |
175
- | `delta` | Text content chunk |
176
- | `tool_call` | Tool execution starting |
177
- | `tool_result` | Tool execution complete |
178
- | `run.completed` | Robot execution finished |
179
- | `run.failed` | Error occurred |
204
+ ## Template Resolution
180
205
 
181
- ## Model Selection
206
+ When a robot has a `template:`, it is resolved during initialization:
182
207
 
183
- The model is determined by:
208
+ ```ruby
209
+ # 1. Parse the template via prompt_manager
210
+ parsed = PM.parse(@template)
184
211
 
185
- 1. Robot's explicit `model` setting
186
- 2. Network's `default_model`
187
- 3. Global `RobotLab.configuration.default_model`
212
+ # 2. Extract and apply front matter config
213
+ # (model, temperature, top_p, etc.)
214
+ apply_front_matter_config(parsed.metadata)
188
215
 
189
- ```ruby
190
- robot = RobotLab.build do
191
- model "claude-sonnet-4" # Takes precedence
192
- end
216
+ # 3. Render the template body with context
217
+ rendered = parsed.to_s(**resolved_context)
193
218
 
194
- network = RobotLab.create_network do
195
- default_model "gpt-4" # Fallback for robots without model
196
- end
219
+ # 4. Set as system instructions on @chat
220
+ @chat.with_instructions(rendered)
197
221
  ```
198
222
 
199
- ## Provider Detection
223
+ ### Front Matter Config Keys
224
+
225
+ Templates can configure the chat via YAML front matter:
200
226
 
201
- If no provider is specified, it's detected from model name:
227
+ | Key | Effect |
228
+ |-----|--------|
229
+ | `model` | Sets the LLM model |
230
+ | `temperature` | Sets randomness |
231
+ | `top_p` | Sets nucleus sampling |
232
+ | `top_k` | Sets top-k sampling |
233
+ | `max_tokens` | Sets max response tokens |
234
+ | `presence_penalty` | Sets presence penalty |
235
+ | `frequency_penalty` | Sets frequency penalty |
236
+ | `stop` | Sets stop sequences |
202
237
 
203
- | Model Pattern | Provider |
204
- |--------------|----------|
205
- | `claude-*`, `anthropic-*` | `:anthropic` |
206
- | `gpt-*`, `o1-*`, `chatgpt-*` | `:openai` |
207
- | `gemini-*` | `:gemini` |
208
- | `llama-*`, `mistral-*` | `:ollama` |
238
+ ## Model Selection
209
239
 
210
- ## RoboticModel
240
+ The model is determined by:
211
241
 
212
- The `RoboticModel` class handles LLM communication:
242
+ 1. Robot's explicit `model:` parameter
243
+ 2. Front matter `model` from template
244
+ 3. Global `RobotLab.config.ruby_llm.model`
213
245
 
214
246
  ```ruby
215
- model = RoboticModel.new("claude-sonnet-4", provider: :anthropic)
216
-
217
- # Full inference
218
- response = model.infer(messages, tools)
247
+ robot = RobotLab.build(
248
+ name: "bot",
249
+ model: "claude-sonnet-4" # Takes precedence
250
+ )
219
251
 
220
- # Quick ask
221
- response = model.ask("What is 2+2?", system: "You are a math tutor")
252
+ # Or configure globally via config files / environment variables
253
+ # ROBOT_LAB_RUBY_LLM__MODEL=gpt-4o
222
254
  ```
223
255
 
224
- ### InferenceResponse
256
+ ## SimpleFlow Integration
225
257
 
226
- The response object provides:
258
+ When a robot runs inside a network, the `call` method is invoked by SimpleFlow:
259
+
260
+ ```mermaid
261
+ sequenceDiagram
262
+ participant SF as SimpleFlow
263
+ participant Task as Task Wrapper
264
+ participant Robot
265
+ participant Chat as @chat
266
+
267
+ SF->>Task: call(result)
268
+ Task->>Task: deep_merge(run_params, task_context)
269
+ Task->>Robot: call(enhanced_result)
270
+ Robot->>Robot: extract_run_context(result)
271
+ Robot->>Robot: message = context.delete(:message)
272
+ Robot->>Robot: run(message, **context)
273
+ Robot->>Chat: ask(message)
274
+ Chat-->>Robot: response
275
+ Robot-->>SF: result.continue(robot_result)
276
+ ```
277
+
278
+ The `Task` wrapper deep-merges per-task configuration (context, mcp, tools) before delegating to the robot's `call`. The base `Robot#call` extracts the message and calls `run`:
227
279
 
228
280
  ```ruby
229
- response.output # Array<Message> - parsed output
230
- response.raw # Original LLM response
231
- response.stop_reason # "stop", "tool", etc.
232
- response.stopped? # true if naturally completed
233
- response.wants_tools? # true if tool calls pending
234
- response.tool_calls # Array<ToolMessage>
235
- response.text_content # Combined text from output
236
- response.captured_tool_results # Auto-executed tool results
281
+ def call(result)
282
+ run_context = extract_run_context(result)
283
+ message = run_context.delete(:message)
284
+ robot_result = run(message, **run_context)
285
+
286
+ result
287
+ .with_context(@name.to_sym, robot_result)
288
+ .continue(robot_result)
289
+ end
237
290
  ```
238
291
 
239
292
  ## Next Steps
240
293
 
241
294
  - [Network Orchestration](network-orchestration.md) - Multi-robot coordination
242
- - [State Management](state-management.md) - Managing state across robots
295
+ - [Core Concepts](core-concepts.md) - Fundamental building blocks
243
296
  - [Using Tools](../guides/using-tools.md) - Creating and using tools