robot_lab 0.0.1

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 (153) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/.github/workflows/deploy-github-pages.yml +52 -0
  4. data/.github/workflows/deploy-yard-docs.yml +52 -0
  5. data/CHANGELOG.md +55 -0
  6. data/COMMITS.md +196 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +332 -0
  9. data/Rakefile +67 -0
  10. data/docs/api/adapters/anthropic.md +121 -0
  11. data/docs/api/adapters/gemini.md +133 -0
  12. data/docs/api/adapters/index.md +104 -0
  13. data/docs/api/adapters/openai.md +134 -0
  14. data/docs/api/core/index.md +113 -0
  15. data/docs/api/core/memory.md +314 -0
  16. data/docs/api/core/network.md +291 -0
  17. data/docs/api/core/robot.md +273 -0
  18. data/docs/api/core/state.md +273 -0
  19. data/docs/api/core/tool.md +353 -0
  20. data/docs/api/history/active-record-adapter.md +195 -0
  21. data/docs/api/history/config.md +191 -0
  22. data/docs/api/history/index.md +132 -0
  23. data/docs/api/history/thread-manager.md +144 -0
  24. data/docs/api/index.md +82 -0
  25. data/docs/api/mcp/client.md +221 -0
  26. data/docs/api/mcp/index.md +111 -0
  27. data/docs/api/mcp/server.md +225 -0
  28. data/docs/api/mcp/transports.md +264 -0
  29. data/docs/api/messages/index.md +67 -0
  30. data/docs/api/messages/text-message.md +102 -0
  31. data/docs/api/messages/tool-call-message.md +144 -0
  32. data/docs/api/messages/tool-result-message.md +154 -0
  33. data/docs/api/messages/user-message.md +171 -0
  34. data/docs/api/streaming/context.md +174 -0
  35. data/docs/api/streaming/events.md +237 -0
  36. data/docs/api/streaming/index.md +108 -0
  37. data/docs/architecture/core-concepts.md +243 -0
  38. data/docs/architecture/index.md +138 -0
  39. data/docs/architecture/message-flow.md +320 -0
  40. data/docs/architecture/network-orchestration.md +216 -0
  41. data/docs/architecture/robot-execution.md +243 -0
  42. data/docs/architecture/state-management.md +323 -0
  43. data/docs/assets/css/custom.css +56 -0
  44. data/docs/assets/images/robot_lab.jpg +0 -0
  45. data/docs/concepts.md +216 -0
  46. data/docs/examples/basic-chat.md +193 -0
  47. data/docs/examples/index.md +129 -0
  48. data/docs/examples/mcp-server.md +290 -0
  49. data/docs/examples/multi-robot-network.md +312 -0
  50. data/docs/examples/rails-application.md +420 -0
  51. data/docs/examples/tool-usage.md +310 -0
  52. data/docs/getting-started/configuration.md +230 -0
  53. data/docs/getting-started/index.md +56 -0
  54. data/docs/getting-started/installation.md +179 -0
  55. data/docs/getting-started/quick-start.md +203 -0
  56. data/docs/guides/building-robots.md +376 -0
  57. data/docs/guides/creating-networks.md +366 -0
  58. data/docs/guides/history.md +359 -0
  59. data/docs/guides/index.md +68 -0
  60. data/docs/guides/mcp-integration.md +356 -0
  61. data/docs/guides/memory.md +309 -0
  62. data/docs/guides/rails-integration.md +432 -0
  63. data/docs/guides/streaming.md +314 -0
  64. data/docs/guides/using-tools.md +394 -0
  65. data/docs/index.md +160 -0
  66. data/examples/01_simple_robot.rb +38 -0
  67. data/examples/02_tools.rb +106 -0
  68. data/examples/03_network.rb +103 -0
  69. data/examples/04_mcp.rb +219 -0
  70. data/examples/05_streaming.rb +124 -0
  71. data/examples/06_prompt_templates.rb +324 -0
  72. data/examples/07_network_memory.rb +329 -0
  73. data/examples/prompts/assistant/system.txt.erb +2 -0
  74. data/examples/prompts/assistant/user.txt.erb +1 -0
  75. data/examples/prompts/billing/system.txt.erb +7 -0
  76. data/examples/prompts/billing/user.txt.erb +1 -0
  77. data/examples/prompts/classifier/system.txt.erb +4 -0
  78. data/examples/prompts/classifier/user.txt.erb +1 -0
  79. data/examples/prompts/entity_extractor/system.txt.erb +11 -0
  80. data/examples/prompts/entity_extractor/user.txt.erb +3 -0
  81. data/examples/prompts/escalation/system.txt.erb +35 -0
  82. data/examples/prompts/escalation/user.txt.erb +34 -0
  83. data/examples/prompts/general/system.txt.erb +4 -0
  84. data/examples/prompts/general/user.txt.erb +1 -0
  85. data/examples/prompts/github_assistant/system.txt.erb +6 -0
  86. data/examples/prompts/github_assistant/user.txt.erb +1 -0
  87. data/examples/prompts/helper/system.txt.erb +1 -0
  88. data/examples/prompts/helper/user.txt.erb +1 -0
  89. data/examples/prompts/keyword_extractor/system.txt.erb +8 -0
  90. data/examples/prompts/keyword_extractor/user.txt.erb +3 -0
  91. data/examples/prompts/order_support/system.txt.erb +27 -0
  92. data/examples/prompts/order_support/user.txt.erb +22 -0
  93. data/examples/prompts/product_support/system.txt.erb +30 -0
  94. data/examples/prompts/product_support/user.txt.erb +32 -0
  95. data/examples/prompts/sentiment_analyzer/system.txt.erb +9 -0
  96. data/examples/prompts/sentiment_analyzer/user.txt.erb +3 -0
  97. data/examples/prompts/synthesizer/system.txt.erb +14 -0
  98. data/examples/prompts/synthesizer/user.txt.erb +15 -0
  99. data/examples/prompts/technical/system.txt.erb +7 -0
  100. data/examples/prompts/technical/user.txt.erb +1 -0
  101. data/examples/prompts/triage/system.txt.erb +16 -0
  102. data/examples/prompts/triage/user.txt.erb +17 -0
  103. data/lib/generators/robot_lab/install_generator.rb +78 -0
  104. data/lib/generators/robot_lab/robot_generator.rb +55 -0
  105. data/lib/generators/robot_lab/templates/initializer.rb.tt +41 -0
  106. data/lib/generators/robot_lab/templates/migration.rb.tt +32 -0
  107. data/lib/generators/robot_lab/templates/result_model.rb.tt +52 -0
  108. data/lib/generators/robot_lab/templates/robot.rb.tt +46 -0
  109. data/lib/generators/robot_lab/templates/robot_test.rb.tt +32 -0
  110. data/lib/generators/robot_lab/templates/routing_robot.rb.tt +53 -0
  111. data/lib/generators/robot_lab/templates/thread_model.rb.tt +40 -0
  112. data/lib/robot_lab/adapters/anthropic.rb +163 -0
  113. data/lib/robot_lab/adapters/base.rb +85 -0
  114. data/lib/robot_lab/adapters/gemini.rb +193 -0
  115. data/lib/robot_lab/adapters/openai.rb +159 -0
  116. data/lib/robot_lab/adapters/registry.rb +81 -0
  117. data/lib/robot_lab/configuration.rb +143 -0
  118. data/lib/robot_lab/error.rb +32 -0
  119. data/lib/robot_lab/errors.rb +70 -0
  120. data/lib/robot_lab/history/active_record_adapter.rb +146 -0
  121. data/lib/robot_lab/history/config.rb +115 -0
  122. data/lib/robot_lab/history/thread_manager.rb +93 -0
  123. data/lib/robot_lab/mcp/client.rb +210 -0
  124. data/lib/robot_lab/mcp/server.rb +84 -0
  125. data/lib/robot_lab/mcp/transports/base.rb +56 -0
  126. data/lib/robot_lab/mcp/transports/sse.rb +117 -0
  127. data/lib/robot_lab/mcp/transports/stdio.rb +133 -0
  128. data/lib/robot_lab/mcp/transports/streamable_http.rb +139 -0
  129. data/lib/robot_lab/mcp/transports/websocket.rb +108 -0
  130. data/lib/robot_lab/memory.rb +882 -0
  131. data/lib/robot_lab/memory_change.rb +123 -0
  132. data/lib/robot_lab/message.rb +357 -0
  133. data/lib/robot_lab/network.rb +350 -0
  134. data/lib/robot_lab/rails/engine.rb +29 -0
  135. data/lib/robot_lab/rails/railtie.rb +42 -0
  136. data/lib/robot_lab/robot.rb +560 -0
  137. data/lib/robot_lab/robot_result.rb +205 -0
  138. data/lib/robot_lab/robotic_model.rb +324 -0
  139. data/lib/robot_lab/state_proxy.rb +188 -0
  140. data/lib/robot_lab/streaming/context.rb +144 -0
  141. data/lib/robot_lab/streaming/events.rb +95 -0
  142. data/lib/robot_lab/streaming/sequence_counter.rb +48 -0
  143. data/lib/robot_lab/task.rb +117 -0
  144. data/lib/robot_lab/tool.rb +223 -0
  145. data/lib/robot_lab/tool_config.rb +112 -0
  146. data/lib/robot_lab/tool_manifest.rb +234 -0
  147. data/lib/robot_lab/user_message.rb +118 -0
  148. data/lib/robot_lab/version.rb +5 -0
  149. data/lib/robot_lab/waiter.rb +73 -0
  150. data/lib/robot_lab.rb +195 -0
  151. data/mkdocs.yml +214 -0
  152. data/sig/robot_lab.rbs +4 -0
  153. metadata +442 -0
@@ -0,0 +1,314 @@
1
+ # Streaming Responses
2
+
3
+ Stream LLM responses in real-time for better user experience.
4
+
5
+ ## Basic Streaming
6
+
7
+ Pass a callback to receive streaming events:
8
+
9
+ ```ruby
10
+ robot.run(
11
+ state: state,
12
+ network: network,
13
+ streaming: ->(event) {
14
+ puts event.inspect
15
+ }
16
+ )
17
+ ```
18
+
19
+ ## Event Types
20
+
21
+ ### Text Deltas
22
+
23
+ Receive text as it's generated:
24
+
25
+ ```ruby
26
+ streaming: ->(event) {
27
+ if event[:event] == "delta"
28
+ print event[:data][:content]
29
+ end
30
+ }
31
+ ```
32
+
33
+ ### Tool Calls
34
+
35
+ Know when tools are being called:
36
+
37
+ ```ruby
38
+ streaming: ->(event) {
39
+ case event[:event]
40
+ when "tool_call.start"
41
+ puts "\nCalling: #{event[:data][:name]}"
42
+ when "tool_call.complete"
43
+ puts "Done: #{event[:data][:result]}"
44
+ end
45
+ }
46
+ ```
47
+
48
+ ### Lifecycle Events
49
+
50
+ Track execution lifecycle:
51
+
52
+ ```ruby
53
+ streaming: ->(event) {
54
+ case event[:event]
55
+ when "run.started"
56
+ puts "Starting run #{event[:data][:run_id]}"
57
+ when "run.completed"
58
+ puts "Completed!"
59
+ when "run.failed"
60
+ puts "Failed: #{event[:data][:error]}"
61
+ end
62
+ }
63
+ ```
64
+
65
+ ## Event Reference
66
+
67
+ | Event | Description | Data |
68
+ |-------|-------------|------|
69
+ | `run.started` | Network run began | `run_id`, `network` |
70
+ | `run.completed` | Network run finished | `run_id`, `robot_count` |
71
+ | `run.failed` | Error occurred | `run_id`, `error` |
72
+ | `delta` | Text content chunk | `content` |
73
+ | `tool_call.start` | Tool execution starting | `name`, `input` |
74
+ | `tool_call.complete` | Tool execution done | `name`, `result` |
75
+
76
+ ## Streaming Context
77
+
78
+ For advanced control, use `Streaming::Context`:
79
+
80
+ ```ruby
81
+ context = RobotLab::Streaming::Context.new(
82
+ run_id: SecureRandom.uuid,
83
+ message_id: SecureRandom.uuid,
84
+ scope: "network",
85
+ publish: ->(event) { broadcast_to_client(event) }
86
+ )
87
+ ```
88
+
89
+ ### Context Properties
90
+
91
+ ```ruby
92
+ context.run_id # Unique run identifier
93
+ context.message_id # Unique message identifier
94
+ context.scope # "network" or "robot"
95
+ ```
96
+
97
+ ### Publishing Events
98
+
99
+ ```ruby
100
+ context.publish_event(
101
+ event: "custom.event",
102
+ data: { key: "value" }
103
+ )
104
+ ```
105
+
106
+ ## Web Integration
107
+
108
+ ### Rails Action Cable
109
+
110
+ ```ruby
111
+ class ChatChannel < ApplicationCable::Channel
112
+ def receive(data)
113
+ state = RobotLab.create_state(message: data["message"])
114
+
115
+ network.run(
116
+ state: state,
117
+ streaming: ->(event) {
118
+ transmit(event)
119
+ }
120
+ )
121
+ end
122
+ end
123
+ ```
124
+
125
+ ### Server-Sent Events
126
+
127
+ ```ruby
128
+ class StreamController < ApplicationController
129
+ include ActionController::Live
130
+
131
+ def create
132
+ response.headers["Content-Type"] = "text/event-stream"
133
+
134
+ state = RobotLab.create_state(message: params[:message])
135
+
136
+ network.run(
137
+ state: state,
138
+ streaming: ->(event) {
139
+ response.stream.write("data: #{event.to_json}\n\n")
140
+ }
141
+ )
142
+ ensure
143
+ response.stream.close
144
+ end
145
+ end
146
+ ```
147
+
148
+ ### WebSocket
149
+
150
+ ```ruby
151
+ # Using Faye WebSocket
152
+ ws.on :message do |msg|
153
+ state = RobotLab.create_state(message: msg.data)
154
+
155
+ network.run(
156
+ state: state,
157
+ streaming: ->(event) {
158
+ ws.send(event.to_json)
159
+ }
160
+ )
161
+ end
162
+ ```
163
+
164
+ ## Event Filtering
165
+
166
+ ### Check Event Type
167
+
168
+ ```ruby
169
+ streaming: ->(event) {
170
+ return unless RobotLab::Streaming::Events.delta?(event)
171
+ print event[:data][:content]
172
+ }
173
+ ```
174
+
175
+ ### Available Predicates
176
+
177
+ ```ruby
178
+ Streaming::Events.lifecycle?(event) # run.started, run.completed, etc.
179
+ Streaming::Events.delta?(event) # Text content
180
+ Streaming::Events.valid?(event) # Has required fields
181
+ ```
182
+
183
+ ## Buffering
184
+
185
+ Buffer content for batch processing:
186
+
187
+ ```ruby
188
+ buffer = []
189
+
190
+ streaming: ->(event) {
191
+ if event[:event] == "delta"
192
+ buffer << event[:data][:content]
193
+
194
+ # Flush every 10 chunks
195
+ if buffer.size >= 10
196
+ process_batch(buffer.join)
197
+ buffer.clear
198
+ end
199
+ end
200
+ }
201
+
202
+ # Don't forget final flush
203
+ process_batch(buffer.join) if buffer.any?
204
+ ```
205
+
206
+ ## Progress Tracking
207
+
208
+ Track streaming progress:
209
+
210
+ ```ruby
211
+ class StreamProgress
212
+ def initialize
213
+ @chars = 0
214
+ @tools = 0
215
+ end
216
+
217
+ def handle(event)
218
+ case event[:event]
219
+ when "delta"
220
+ @chars += event[:data][:content].length
221
+ puts "\rReceived #{@chars} characters..."
222
+ when "tool_call.start"
223
+ @tools += 1
224
+ puts "\nTool call ##{@tools}: #{event[:data][:name]}"
225
+ end
226
+ end
227
+ end
228
+
229
+ progress = StreamProgress.new
230
+ network.run(state: state, streaming: progress.method(:handle))
231
+ ```
232
+
233
+ ## Error Handling
234
+
235
+ Handle streaming errors gracefully:
236
+
237
+ ```ruby
238
+ streaming: ->(event) {
239
+ case event[:event]
240
+ when "run.failed"
241
+ log_error(event[:data][:error])
242
+ notify_user("An error occurred")
243
+ when "delta"
244
+ begin
245
+ broadcast(event)
246
+ rescue BroadcastError => e
247
+ # Client disconnected, but continue processing
248
+ logger.warn "Broadcast failed: #{e.message}"
249
+ end
250
+ end
251
+ }
252
+ ```
253
+
254
+ ## Disabling Streaming
255
+
256
+ Disable streaming when not needed:
257
+
258
+ ```ruby
259
+ RobotLab.configure do |config|
260
+ config.streaming_enabled = false
261
+ end
262
+
263
+ # Or per-run
264
+ network.run(state: state, streaming: nil)
265
+ ```
266
+
267
+ ## Best Practices
268
+
269
+ ### 1. Handle All Event Types
270
+
271
+ ```ruby
272
+ streaming: ->(event) {
273
+ case event[:event]
274
+ when "delta" then handle_delta(event)
275
+ when "tool_call.start" then show_tool_indicator(event)
276
+ when "tool_call.complete" then hide_tool_indicator(event)
277
+ when "run.completed" then finalize_response
278
+ when "run.failed" then show_error(event)
279
+ end
280
+ }
281
+ ```
282
+
283
+ ### 2. Provide User Feedback
284
+
285
+ ```ruby
286
+ streaming: ->(event) {
287
+ case event[:event]
288
+ when "run.started"
289
+ show_typing_indicator
290
+ when "delta"
291
+ update_message(event[:data][:content])
292
+ when "tool_call.start"
293
+ show_status("Looking up information...")
294
+ when "run.completed"
295
+ hide_typing_indicator
296
+ end
297
+ }
298
+ ```
299
+
300
+ ### 3. Clean Up Resources
301
+
302
+ ```ruby
303
+ begin
304
+ network.run(state: state, streaming: callback)
305
+ ensure
306
+ close_stream_connection
307
+ end
308
+ ```
309
+
310
+ ## Next Steps
311
+
312
+ - [Building Robots](building-robots.md) - Robot creation
313
+ - [Creating Networks](creating-networks.md) - Network patterns
314
+ - [API Reference: Streaming](../api/streaming/index.md) - Complete API
@@ -0,0 +1,394 @@
1
+ # Using Tools
2
+
3
+ Tools give robots the ability to interact with external systems.
4
+
5
+ ## Defining Tools
6
+
7
+ ### In Robot Builder
8
+
9
+ ```ruby
10
+ robot = RobotLab.build do
11
+ name "assistant"
12
+
13
+ tool :get_weather do
14
+ description "Get current weather for a location"
15
+ parameter :location, type: :string, required: true
16
+ handler { |location:, **_| WeatherService.current(location) }
17
+ end
18
+ end
19
+ ```
20
+
21
+ ### Standalone Tool
22
+
23
+ ```ruby
24
+ weather_tool = RobotLab::Tool.new(
25
+ name: "get_weather",
26
+ description: "Get current weather for a location",
27
+ parameters: {
28
+ location: {
29
+ type: "string",
30
+ description: "City name",
31
+ required: true
32
+ }
33
+ },
34
+ handler: ->(location:, **_context) {
35
+ WeatherService.current(location)
36
+ }
37
+ )
38
+ ```
39
+
40
+ ## Parameter Types
41
+
42
+ ### String
43
+
44
+ ```ruby
45
+ parameter :name, type: :string, required: true
46
+ ```
47
+
48
+ ### Integer
49
+
50
+ ```ruby
51
+ parameter :count, type: :integer, default: 10
52
+ ```
53
+
54
+ ### Number (Float)
55
+
56
+ ```ruby
57
+ parameter :price, type: :number
58
+ ```
59
+
60
+ ### Boolean
61
+
62
+ ```ruby
63
+ parameter :active, type: :boolean, default: true
64
+ ```
65
+
66
+ ### Array
67
+
68
+ ```ruby
69
+ parameter :tags, type: :array
70
+ ```
71
+
72
+ ### Enum
73
+
74
+ ```ruby
75
+ parameter :status, type: :string, enum: %w[pending active completed]
76
+ ```
77
+
78
+ ### With Description
79
+
80
+ ```ruby
81
+ parameter :query,
82
+ type: :string,
83
+ required: true,
84
+ description: "Search query (supports wildcards)"
85
+ ```
86
+
87
+ ## Handler Patterns
88
+
89
+ ### Simple Handler
90
+
91
+ ```ruby
92
+ handler { |param:, **_| do_something(param) }
93
+ ```
94
+
95
+ ### With Context Access
96
+
97
+ ```ruby
98
+ handler do |param:, robot:, network:, state:|
99
+ user_id = state.data[:user_id]
100
+ result = perform_action(param, user_id)
101
+ state.memory.remember("last_action", result[:id])
102
+ result
103
+ end
104
+ ```
105
+
106
+ ### Error Handling
107
+
108
+ ```ruby
109
+ handler do |id:, **_|
110
+ record = Record.find_by(id: id)
111
+ if record
112
+ { success: true, data: record.to_h }
113
+ else
114
+ { success: false, error: "Record not found" }
115
+ end
116
+ rescue StandardError => e
117
+ { success: false, error: e.message }
118
+ end
119
+ ```
120
+
121
+ ### Async Operations
122
+
123
+ ```ruby
124
+ handler do |url:, **_|
125
+ # Long-running operation
126
+ response = HTTP.timeout(30).get(url)
127
+ { status: response.status, body: response.body.to_s[0..1000] }
128
+ end
129
+ ```
130
+
131
+ ## Tool Return Values
132
+
133
+ ### Structured Data
134
+
135
+ ```ruby
136
+ handler do |user_id:, **_|
137
+ user = User.find(user_id)
138
+ {
139
+ id: user.id,
140
+ name: user.name,
141
+ email: user.email,
142
+ created_at: user.created_at.iso8601
143
+ }
144
+ end
145
+ ```
146
+
147
+ ### Simple Values
148
+
149
+ ```ruby
150
+ handler { |**_| Time.now.to_s }
151
+ handler { |**_| 42 }
152
+ handler { |**_| true }
153
+ ```
154
+
155
+ ### Lists
156
+
157
+ ```ruby
158
+ handler do |query:, **_|
159
+ results = Search.query(query)
160
+ results.map { |r| { id: r.id, title: r.title, score: r.score } }
161
+ end
162
+ ```
163
+
164
+ ## Tool Manifests
165
+
166
+ Wrap existing tools with modified metadata:
167
+
168
+ ```ruby
169
+ # Original tool
170
+ base_tool = RobotLab::Tool.new(
171
+ name: "search",
172
+ description: "General search",
173
+ handler: ->(q:, **_) { Search.query(q) }
174
+ )
175
+
176
+ # Customized version
177
+ product_search = RobotLab::ToolManifest.new(
178
+ tool: base_tool,
179
+ name: "search_products",
180
+ description: "Search the product catalog"
181
+ )
182
+
183
+ code_search = RobotLab::ToolManifest.new(
184
+ tool: base_tool,
185
+ name: "search_code",
186
+ description: "Search source code"
187
+ )
188
+ ```
189
+
190
+ ## Tool Whitelisting
191
+
192
+ ### At Robot Level
193
+
194
+ ```ruby
195
+ robot = RobotLab.build do
196
+ tools %w[read_file list_directory] # Only these tools
197
+ tools :inherit # Use network's tools
198
+ tools :none # No inherited tools
199
+ end
200
+ ```
201
+
202
+ ### At Network Level
203
+
204
+ ```ruby
205
+ network = RobotLab.create_network do
206
+ tools %w[search create_issue] # Global whitelist
207
+ end
208
+ ```
209
+
210
+ ### Configuration Hierarchy
211
+
212
+ ```
213
+ Global (RobotLab.configure)
214
+ └── Network (tools: [...])
215
+ └── Robot (tools: :inherit | :none | [...])
216
+ ```
217
+
218
+ ## MCP Tools
219
+
220
+ Use tools from MCP servers:
221
+
222
+ ```ruby
223
+ network = RobotLab.create_network do
224
+ mcp [
225
+ {
226
+ name: "github",
227
+ transport: { type: "stdio", command: "mcp-server-github" }
228
+ }
229
+ ]
230
+
231
+ # MCP tools automatically available
232
+ # e.g., search_repositories, create_issue, etc.
233
+ end
234
+ ```
235
+
236
+ ### Filtering MCP Tools
237
+
238
+ ```ruby
239
+ robot = RobotLab.build do
240
+ mcp :inherit # Use network's MCP servers
241
+ tools %w[search_repositories create_issue] # Only these MCP tools
242
+ end
243
+ ```
244
+
245
+ ## Common Tool Patterns
246
+
247
+ ### Database Lookup
248
+
249
+ ```ruby
250
+ tool :find_user do
251
+ description "Find user by email or ID"
252
+ parameter :identifier, type: :string, required: true
253
+ handler do |identifier:, **_|
254
+ user = User.find_by(id: identifier) || User.find_by(email: identifier)
255
+ user ? user.to_h : { error: "User not found" }
256
+ end
257
+ end
258
+ ```
259
+
260
+ ### API Integration
261
+
262
+ ```ruby
263
+ tool :get_stock_price do
264
+ description "Get current stock price"
265
+ parameter :symbol, type: :string, required: true
266
+ handler do |symbol:, **_|
267
+ response = HTTP.get("https://api.stocks.example/quote/#{symbol}")
268
+ JSON.parse(response.body)
269
+ rescue HTTP::Error => e
270
+ { error: "Failed to fetch stock price: #{e.message}" }
271
+ end
272
+ end
273
+ ```
274
+
275
+ ### File Operations
276
+
277
+ ```ruby
278
+ tool :read_file do
279
+ description "Read contents of a file"
280
+ parameter :path, type: :string, required: true
281
+ handler do |path:, **_|
282
+ if File.exist?(path) && File.readable?(path)
283
+ { content: File.read(path), size: File.size(path) }
284
+ else
285
+ { error: "File not found or not readable" }
286
+ end
287
+ end
288
+ end
289
+ ```
290
+
291
+ ### State Modification
292
+
293
+ ```ruby
294
+ tool :update_preference do
295
+ description "Update user preference"
296
+ parameter :key, type: :string, required: true
297
+ parameter :value, type: :string, required: true
298
+ handler do |key:, value:, state:, **_|
299
+ state.memory.remember("pref:#{key}", value)
300
+ { success: true, key: key, value: value }
301
+ end
302
+ end
303
+ ```
304
+
305
+ ### Multi-Step Operations
306
+
307
+ ```ruby
308
+ tool :process_order do
309
+ description "Process a customer order"
310
+ parameter :order_id, type: :string, required: true
311
+ handler do |order_id:, state:, **_|
312
+ order = Order.find(order_id)
313
+
314
+ # Validate
315
+ return { error: "Invalid order" } unless order.valid?
316
+
317
+ # Process
318
+ result = PaymentProcessor.charge(order)
319
+ return { error: result[:error] } unless result[:success]
320
+
321
+ # Update
322
+ order.update!(status: "paid")
323
+
324
+ # Store for later reference
325
+ state.memory.remember("processed_order", order.id)
326
+
327
+ { success: true, order_id: order.id, amount: order.total }
328
+ end
329
+ end
330
+ ```
331
+
332
+ ## Best Practices
333
+
334
+ ### 1. Clear Descriptions
335
+
336
+ ```ruby
337
+ # Good: Specific and actionable
338
+ tool :search_orders do
339
+ description "Search customer orders by date range, status, or customer email. Returns up to 50 matching orders."
340
+ end
341
+
342
+ # Bad: Vague
343
+ tool :search do
344
+ description "Searches stuff"
345
+ end
346
+ ```
347
+
348
+ ### 2. Validate Inputs
349
+
350
+ ```ruby
351
+ handler do |email:, **_|
352
+ unless email.match?(/\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i)
353
+ return { error: "Invalid email format" }
354
+ end
355
+ # ... rest of handler
356
+ end
357
+ ```
358
+
359
+ ### 3. Handle Errors Gracefully
360
+
361
+ ```ruby
362
+ handler do |id:, **_|
363
+ result = ExternalAPI.fetch(id)
364
+ { success: true, data: result }
365
+ rescue ExternalAPI::NotFound
366
+ { success: false, error: "Resource not found", id: id }
367
+ rescue ExternalAPI::RateLimited => e
368
+ { success: false, error: "Rate limited", retry_after: e.retry_after }
369
+ rescue StandardError => e
370
+ { success: false, error: "Unexpected error: #{e.message}" }
371
+ end
372
+ ```
373
+
374
+ ### 4. Return Structured Data
375
+
376
+ ```ruby
377
+ # Good: Structured and consistent
378
+ handler do |**_|
379
+ {
380
+ success: true,
381
+ data: { id: 1, name: "Item" },
382
+ metadata: { fetched_at: Time.now.iso8601 }
383
+ }
384
+ end
385
+
386
+ # Bad: Unstructured
387
+ handler { |**_| "Found item with id 1 named Item" }
388
+ ```
389
+
390
+ ## Next Steps
391
+
392
+ - [MCP Integration](mcp-integration.md) - External tool servers
393
+ - [Building Robots](building-robots.md) - Robot creation patterns
394
+ - [API Reference: Tool](../api/core/tool.md) - Complete API