robot_lab 0.2.6 → 0.2.8

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 +4 -4
  2. data/.envrc +4 -0
  3. data/.loki +5 -0
  4. data/Archspec.rb +44 -0
  5. data/CHANGELOG.md +17 -1
  6. data/README.md +124 -64
  7. data/Rakefile +6 -111
  8. data/_typos.toml +21 -0
  9. data/docs/api/core/index.md +41 -15
  10. data/docs/api/core/memory.md +247 -29
  11. data/docs/api/core/network.md +255 -33
  12. data/docs/api/core/result.md +120 -32
  13. data/docs/api/core/robot.md +551 -61
  14. data/docs/api/core/state.md +87 -197
  15. data/docs/api/core/tool.md +165 -20
  16. data/docs/api/errors.md +110 -17
  17. data/docs/api/hooks.md +469 -0
  18. data/docs/api/index.md +80 -7
  19. data/docs/api/mcp/client.md +129 -35
  20. data/docs/api/mcp/index.md +164 -23
  21. data/docs/api/mcp/server.md +27 -3
  22. data/docs/api/mcp/transports.md +94 -22
  23. data/docs/api/messages/index.md +26 -3
  24. data/docs/api/messages/text-message.md +33 -11
  25. data/docs/api/messages/tool-call-message.md +27 -4
  26. data/docs/api/messages/tool-result-message.md +23 -4
  27. data/docs/api/messages/user-message.md +45 -8
  28. data/docs/api/skills.md +423 -0
  29. data/docs/api/streaming/context.md +28 -5
  30. data/docs/api/streaming/index.md +57 -11
  31. data/docs/api/support.md +846 -0
  32. data/docs/architecture/core-concepts.md +79 -31
  33. data/docs/architecture/index.md +87 -11
  34. data/docs/architecture/message-flow.md +66 -29
  35. data/docs/architecture/network-orchestration.md +145 -38
  36. data/docs/architecture/robot-execution.md +172 -90
  37. data/docs/architecture/state-management.md +31 -12
  38. data/docs/concepts.md +176 -21
  39. data/docs/examples/basic-chat.md +72 -19
  40. data/docs/examples/index.md +117 -31
  41. data/docs/examples/mcp-server.md +154 -45
  42. data/docs/examples/multi-robot-network.md +91 -21
  43. data/docs/examples/tool-usage.md +104 -37
  44. data/docs/getting-started/configuration.md +288 -98
  45. data/docs/getting-started/installation.md +53 -41
  46. data/docs/getting-started/quick-start.md +51 -6
  47. data/docs/guides/building-robots.md +258 -50
  48. data/docs/guides/creating-networks.md +214 -30
  49. data/docs/guides/hooks.md +263 -54
  50. data/docs/guides/knowledge.md +35 -4
  51. data/docs/guides/mcp-integration.md +211 -44
  52. data/docs/guides/memory.md +103 -12
  53. data/docs/guides/observability.md +95 -47
  54. data/docs/guides/streaming.md +184 -125
  55. data/docs/guides/using-tools.md +247 -18
  56. data/docs/index.md +36 -4
  57. data/examples/01_simple_robot.rb +2 -2
  58. data/examples/02_tools.rb +14 -4
  59. data/examples/03_network.rb +12 -7
  60. data/examples/04_mcp.rb +11 -4
  61. data/examples/05_streaming.rb +8 -5
  62. data/examples/06_prompt_templates.rb +13 -9
  63. data/examples/07_network_memory.rb +5 -5
  64. data/examples/08_llm_config.rb +20 -15
  65. data/examples/09_chaining.rb +4 -4
  66. data/examples/11_network_introspection.rb +4 -4
  67. data/examples/12_message_bus.rb +2 -2
  68. data/examples/13_spawn.rb +2 -2
  69. data/examples/14_rusty_circuit/README.md +1 -0
  70. data/examples/14_rusty_circuit/comic.rb +7 -3
  71. data/examples/14_rusty_circuit/display.rb +14 -0
  72. data/examples/14_rusty_circuit/heckler.rb +8 -6
  73. data/examples/14_rusty_circuit/open_mic.rb +17 -6
  74. data/examples/14_rusty_circuit/scout.rb +17 -10
  75. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +14 -10
  76. data/examples/15_memory_network_and_bus/linux_writer.rb +2 -2
  77. data/examples/15_memory_network_and_bus/os_editor.rb +3 -1
  78. data/examples/15_memory_network_and_bus/os_writer.rb +4 -1
  79. data/examples/16_writers_room/writer.rb +22 -22
  80. data/examples/16_writers_room/writers_room.rb +2 -0
  81. data/examples/17_skills.rb +14 -13
  82. data/examples/18_rails/README.md +20 -1
  83. data/examples/18_rails/app/controllers/chat_controller.rb +5 -1
  84. data/examples/18_rails/app/jobs/robot_run_job.rb +11 -5
  85. data/examples/18_rails/app/robots/chat_robot.rb +11 -0
  86. data/examples/18_rails/config/initializers/robot_lab.rb +8 -0
  87. data/examples/19_token_tracking.rb +25 -9
  88. data/examples/20_circuit_breaker.rb +10 -7
  89. data/examples/21_learning_loop.rb +42 -16
  90. data/examples/22_context_compression.rb +23 -23
  91. data/examples/23_convergence.rb +24 -17
  92. data/examples/24_structured_delegation.rb +13 -8
  93. data/examples/25_history_search.rb +12 -8
  94. data/examples/27_incident_response/incident_response.rb +31 -13
  95. data/examples/28_mcp_discovery.rb +17 -13
  96. data/examples/29_ractor_tools.rb +4 -2
  97. data/examples/30_ractor_network.rb +22 -17
  98. data/examples/31_launch_assessment.rb +20 -9
  99. data/examples/32_newsletter_reader.rb +7 -2
  100. data/examples/33_stock_predictor.rb +34 -13
  101. data/examples/34_agentskills.rb +7 -3
  102. data/examples/35_hooks.rb +18 -8
  103. data/examples/README.md +199 -45
  104. data/examples/common.rb +79 -11
  105. data/examples/xyzzy.rb +8 -1
  106. data/lib/robot_lab/agent_skill_catalog.rb +1 -0
  107. data/lib/robot_lab/ask_user.rb +2 -0
  108. data/lib/robot_lab/bus_poller.rb +2 -0
  109. data/lib/robot_lab/capabilities.rb +4 -0
  110. data/lib/robot_lab/config.rb +18 -5
  111. data/lib/robot_lab/doom_loop_detector.rb +6 -3
  112. data/lib/robot_lab/history_compressor.rb +5 -0
  113. data/lib/robot_lab/hook.rb +1 -0
  114. data/lib/robot_lab/hook_context.rb +4 -0
  115. data/lib/robot_lab/hook_registry.rb +1 -0
  116. data/lib/robot_lab/hooks.rb +6 -3
  117. data/lib/robot_lab/mcp/client.rb +2 -2
  118. data/lib/robot_lab/mcp/connection_poller.rb +16 -8
  119. data/lib/robot_lab/mcp/server_discovery.rb +1 -0
  120. data/lib/robot_lab/mcp/transports/sse.rb +3 -0
  121. data/lib/robot_lab/mcp/transports/stdio.rb +5 -0
  122. data/lib/robot_lab/mcp/transports/streamable_http.rb +4 -0
  123. data/lib/robot_lab/mcp/transports/websocket.rb +3 -0
  124. data/lib/robot_lab/memory.rb +23 -6
  125. data/lib/robot_lab/memory_change.rb +1 -0
  126. data/lib/robot_lab/message.rb +3 -0
  127. data/lib/robot_lab/names.rb +400 -0
  128. data/lib/robot_lab/network.rb +13 -6
  129. data/lib/robot_lab/robot/agent_skill_matching.rb +3 -3
  130. data/lib/robot_lab/robot/bus_messaging.rb +19 -8
  131. data/lib/robot_lab/robot/history_search.rb +2 -0
  132. data/lib/robot_lab/robot/hooking.rb +3 -0
  133. data/lib/robot_lab/robot/mcp_management.rb +11 -3
  134. data/lib/robot_lab/robot/template_rendering.rb +29 -7
  135. data/lib/robot_lab/robot.rb +81 -19
  136. data/lib/robot_lab/robot_result.rb +3 -0
  137. data/lib/robot_lab/run_config.rb +5 -0
  138. data/lib/robot_lab/script_tool.rb +20 -39
  139. data/lib/robot_lab/state_proxy.rb +1 -0
  140. data/lib/robot_lab/streaming/context.rb +1 -0
  141. data/lib/robot_lab/streaming/events.rb +1 -0
  142. data/lib/robot_lab/task.rb +2 -0
  143. data/lib/robot_lab/tool.rb +4 -0
  144. data/lib/robot_lab/user_message.rb +1 -0
  145. data/lib/robot_lab/utils.rb +2 -0
  146. data/lib/robot_lab/version.rb +1 -1
  147. data/lib/robot_lab/waiter.rb +3 -0
  148. data/lib/robot_lab.rb +27 -18
  149. data/mkdocs.yml +6 -1
  150. metadata +12 -7
  151. data/lib/robot_lab/sandbox/null.rb +0 -13
  152. data/lib/robot_lab/sandbox/seatbelt.rb +0 -104
  153. data/lib/robot_lab/sandbox.rb +0 -52
@@ -0,0 +1,400 @@
1
+ # lib/robot_lab/names.rb
2
+
3
+ # A large curated word list, not application logic — kept as one inline
4
+ # module for lookup speed, so the size/length cops below are false positives.
5
+ # rubocop:disable-next Metrics/ModuleLength
6
+ module RobotLab
7
+ # Fictional robot names
8
+ # rubocop:disable-next Metrics/CollectionLiteralLength
9
+ NAMES = %w[
10
+ R_7723
11
+ R_790
12
+ AXL
13
+ Adam
14
+ Adam_Link
15
+ Alfie
16
+ Alojzy_Kukuryk
17
+ Alpha_5
18
+ Alpha_6
19
+ Alsatia_Zevo
20
+ AMEE
21
+ Ancient_Ore_Crusher
22
+ Andrew
23
+ Andrew_Martin
24
+ Andromon
25
+ Aniel
26
+ Aqua
27
+ Arbeitsmaschine
28
+ Arthur
29
+ Artificial_Friend
30
+ Ash
31
+ Astor
32
+ Astro_Boy
33
+ Athena
34
+ Atmophytes
35
+ AUTO
36
+ Ava
37
+ B_4
38
+ BATs
39
+ BEN
40
+ BOB
41
+ B166ER
42
+ Baymax
43
+ BB
44
+ BB_8
45
+ Becker
46
+ Bender
47
+ Bénédict_Masson
48
+ Bennie
49
+ Beta
50
+ Beta_Machine
51
+ Billybot
52
+ Bishop
53
+ Black_Beauty
54
+ Blue_Senturion
55
+ Boilerplate
56
+ Boppers
57
+ Bors
58
+ Box
59
+ Brackenridge
60
+ Brainbots
61
+ BRL_A
62
+ Bruno
63
+ Bubo
64
+ Buffybot
65
+ BURN_E
66
+ Byron
67
+ C_3PO
68
+ CHOMPS
69
+ Calculon
70
+ Caliban
71
+ Call
72
+ Cambot
73
+ Candy_Droober
74
+ Carl
75
+ Casandra_Kresnov
76
+ CASE
77
+ Cassandra_One
78
+ Chani
79
+ CHAPPiE
80
+ Charlie
81
+ Cherry_2000
82
+ Chip
83
+ CHIP
84
+ Chitti
85
+ Chuck_the_Robot
86
+ Chucky
87
+ Chumblies
88
+ Claire
89
+ Clamps
90
+ Clunk
91
+ Coconuts
92
+ Coldfire
93
+ Coldstone
94
+ Colossus
95
+ Conky_2000
96
+ Coppélia
97
+ Coyote
98
+ Crimson
99
+ Crow_T_Robot
100
+ Cutie
101
+ Cybots
102
+ Cylons
103
+ D_FIB
104
+ DARYL
105
+ Damon
106
+ Data
107
+ David
108
+ David_8
109
+ Dee_Model
110
+ Dewey
111
+ DOR_15
112
+ Dorfl
113
+ Dot_Matrix
114
+ DV_8
115
+ ED_209
116
+ EDI
117
+ Elio
118
+ Elle
119
+ Emma_2
120
+ Enforcer_Drone
121
+ Engimals
122
+ EVAs
123
+ Eve
124
+ EVE
125
+ Evolver
126
+ Ez_27
127
+ false_Maria
128
+ Fembots
129
+ Flexo
130
+ Frank_Saunders
131
+ Franklin_Droober
132
+ Freya
133
+ Freysa
134
+ Frost
135
+ G2
136
+ Galaxina
137
+ GERTY_3000
138
+ Giant_Robot
139
+ Giddy
140
+ Gigantor
141
+ Gnut
142
+ GO_4
143
+ Goddard
144
+ Gog
145
+ Gort
146
+ Gort
147
+ Gris
148
+ Grounder
149
+ Guardromon
150
+ Gypsy
151
+ Hadaly
152
+ HAN_S
153
+ Haro
154
+ HCR_328
155
+ Hector
156
+ Helen_OLoy
157
+ Helena
158
+ Hermes
159
+ Huey
160
+ Hymie
161
+ Ilia
162
+ Intergalactic_Advocate_Bob
163
+ Jaegers
164
+ Janice_Em
165
+ Jay_Score
166
+ Jay_Dub
167
+ Jenkins
168
+ Jenny_Wakeman
169
+ Jessica
170
+ Jet
171
+ Jet_Jaguar
172
+ Jinx
173
+ Johnny_5
174
+ Johnny_Cab
175
+ Juliana_Tainer
176
+ K
177
+ K_2SO
178
+ K9
179
+ Kampfmaschin
180
+ KARR
181
+ Kay_Em_14
182
+ Kei
183
+ Kikaider
184
+ Killbots
185
+ Kirby
186
+ Kit
187
+ KITT
188
+ Klapaucius
189
+ Kronos
190
+ Kryten
191
+ Kwanzaa_Bot
192
+ Kyoko
193
+ L_76
194
+ L3_37
195
+ Lal
196
+ Lenny
197
+ Leon_Kowalski
198
+ Lomax
199
+ Lore
200
+ Louie
201
+ Luv
202
+ M_O
203
+ M3GAN
204
+ Mac
205
+ Magog
206
+ Mami
207
+ Manders
208
+ Mandroid
209
+ Marcus_Wright
210
+ Maria
211
+ Mariette
212
+ Marius
213
+ Mark_1
214
+ MARK13
215
+ Marvin
216
+ Mary_25
217
+ Maschinenmensch
218
+ Mata_Nui
219
+ Maureen_Droober
220
+ MAX
221
+ Max_404
222
+ Maximillian
223
+ May
224
+ MD_63
225
+ Mech_Eagles
226
+ Mecha_King_Ghidorah
227
+ Mechagodzilla
228
+ Mechagodzilla
229
+ Mechani_Kong
230
+ Mechano
231
+ Mechonoids
232
+ Mec_Willy
233
+ Melfina
234
+ Merlin_Athrawes
235
+ Micro_Managers
236
+ MM7
237
+ Moguera
238
+ MOOSE
239
+ Moravecs
240
+ Mordel
241
+ Morgan
242
+ Mr_Split
243
+ Mr_Static
244
+ Muranian
245
+ Necron_99
246
+ Neotnia
247
+ Newman
248
+ Nimani
249
+ Nimue_Alban
250
+ Norby
251
+ NS4
252
+ Okra
253
+ Olimpia
254
+ Oliver
255
+ Olympia
256
+ Omega
257
+ Omnidroid
258
+ Otis
259
+ POPS
260
+ Paws
261
+ Peace
262
+ Phatasaur
263
+ Phil
264
+ Pino_Petto
265
+ PR_T
266
+ Primus
267
+ Pris
268
+ Project_2501
269
+ Proteus_IV
270
+ Quinn
271
+ R_Daneel_Olivaw
272
+ R_Giskard_Reventlov
273
+ RALF
274
+ ROTOR
275
+ R2_D2
276
+ R4_P17
277
+ Rabbot
278
+ Rachael
279
+ Radius
280
+ Rena
281
+ Replicators
282
+ Rex
283
+ RIA
284
+ Richard_Daniel
285
+ Rick_Deckard
286
+ Ro_Man_Extension_XJ_2
287
+ Rob_Monkey
288
+ Robbie
289
+ Robby
290
+ Robo
291
+ Robo_Clowns
292
+ RoboDoc
293
+ RoboGadget
294
+ Robot_7
295
+ Robot_B_9
296
+ Robot_Devil
297
+ Robot_Santa
298
+ Robot_ZX29B
299
+ Robotoid
300
+ Roderick
301
+ Rosie
302
+ Roy_Batty
303
+ Roz
304
+ ROZZUM_unit_7134
305
+ SAR_003
306
+ S1MONE
307
+ Samantha
308
+ Sapper_Morton
309
+ Sara
310
+ Scratch
311
+ Sentinels
312
+ Setaur
313
+ Sheen
314
+ Sheriff_Not_a_Robot
315
+ SHROUD
316
+ SI_9
317
+ Sico
318
+ SID_67
319
+ SIMON
320
+ Skutters
321
+ Solo
322
+ Sonny
323
+ Sox
324
+ Sparks
325
+ Speedy
326
+ Spofforth
327
+ Spot
328
+ Stitchpunks
329
+ Sulla
330
+ T_1000
331
+ T_600
332
+ T_800
333
+ T_850
334
+ T_Bob
335
+ T_X
336
+ Talkie_Toaster
337
+ TARS
338
+ Terminus
339
+ Tet
340
+ The_Automaton
341
+ The_Cast_Iron_Man
342
+ The_Electric_Grandmother
343
+ The_Fury
344
+ The_Golden_Army
345
+ The_Gunslinger
346
+ The_Hangman
347
+ The_Iron_Giant
348
+ The_Iron_Man
349
+ The_Juggernaut
350
+ The_Mechanical_Cow
351
+ The_Mechanical_Dummy
352
+ The_Mechanical_Horse
353
+ The_Mechanical_Hound
354
+ The_One_Who_Waits
355
+ Tin_Man
356
+ Tik_Tok
357
+ Tilk
358
+ Tilly
359
+ Tima
360
+ Toaster
361
+ Tobor
362
+ Tom
363
+ Tom_Servo
364
+ Tony
365
+ Torg
366
+ Trace_Mayter
367
+ Trurl
368
+ Twonky
369
+ Ultron
370
+ Ulyssses
371
+ UniBlab
372
+ US_47
373
+ VINCENT
374
+ Vahki
375
+ Val
376
+ Valerie_23
377
+ Vanessa_Kensington
378
+ VAQ_M
379
+ Victor
380
+ Vision
381
+ VN_GO
382
+ Voltron
383
+ WALL_E
384
+ Walter_One
385
+ Weebo
386
+ Wilmer
387
+ XJ_9
388
+ Yod
389
+ Z_1
390
+ Z_2
391
+ Z_3
392
+ Zane_Gort
393
+ Zat
394
+ Zhora
395
+ Zoromes
396
+ ].freeze
397
+
398
+ def self.name = names.first
399
+ def self.names(how_many = 1) = NAMES.sample(how_many).sort
400
+ end
@@ -87,6 +87,7 @@ module RobotLab
87
87
  # task :billing, billing_robot, context: { dept: "billing" }, depends_on: :optional
88
88
  # end
89
89
  #
90
+ # :reek:ControlParameter -- `memory || ...` and `config || ...` are nil-safe defaults, not behavior selection.
90
91
  def initialize(name:, concurrency: :auto, memory: nil, config: nil, parallel_mode: :async, &)
91
92
  @name = name.to_s
92
93
  @robots = {}
@@ -125,6 +126,7 @@ module RobotLab
125
126
  # @example Task with dependencies
126
127
  # task :writer, writer_robot, depends_on: [:analyst]
127
128
  #
129
+ # :reek:LongParameterList -- one keyword per documented per-task option.
128
130
  def task(name, robot, context: {}, mcp: :none, tools: :none, memory: nil, config: nil, depends_on: :none,
129
131
  poller_group: :default)
130
132
  task_wrapper = Task.new(
@@ -180,6 +182,7 @@ module RobotLab
180
182
  # result.value # => RobotResult from last robot
181
183
  # result.context[:classifier] # => RobotResult from classifier
182
184
  #
185
+ # :reek:TooManyStatements -- linear assembly of run params and hook context before dispatch.
183
186
  def run(message = nil, **run_context)
184
187
  # Runnable protocol: accept a positional message like Robot#run does, so
185
188
  # callers can `run(msg, ...)` uniformly. `run(message: msg)` still works.
@@ -200,12 +203,13 @@ module RobotLab
200
203
  )
201
204
 
202
205
  RobotLab::Hooks.run(:network_run, context, registries: [RobotLab.hooks, @hooks]) do
206
+ run_params = context.context
203
207
  if @parallel_mode == :ractor
204
- run_with_ractor_scheduler(context.context)
208
+ run_with_ractor_scheduler(run_params)
205
209
  else
206
210
  initial_result = SimpleFlow::Result.new(
207
- context.context,
208
- context: { run_params: context.context }
211
+ run_params,
212
+ context: { run_params: run_params }
209
213
  )
210
214
  @pipeline.call_parallel(initial_result, max_concurrent: @config.max_concurrent_robots)
211
215
  end
@@ -329,11 +333,12 @@ module RobotLab
329
333
  # @raise [ArgumentError] if a robot with the same name already exists
330
334
  #
331
335
  def add_robot(robot)
332
- if @robots.key?(robot.name)
333
- raise ArgumentError, "Robot '#{robot.name}' already exists in network '#{@name}'"
336
+ name = robot.name
337
+ if @robots.key?(name)
338
+ raise ArgumentError, "Robot '#{name}' already exists in network '#{@name}'"
334
339
  end
335
340
 
336
- @robots[robot.name] = robot
341
+ @robots[name] = robot
337
342
  self
338
343
  end
339
344
 
@@ -398,6 +403,7 @@ module RobotLab
398
403
 
399
404
  private
400
405
 
406
+ # :reek:TooManyStatements -- linear build-specs/run/shutdown sequence for the ractor scheduler.
401
407
  def run_with_ractor_scheduler(run_context)
402
408
  unless RobotLab.extension_loaded?(:ractor)
403
409
  raise RobotLab::DependencyError,
@@ -419,6 +425,7 @@ module RobotLab
419
425
  results
420
426
  end
421
427
 
428
+ # :reek:FeatureEnvy -- snapshotting a robot's identity fields into a Ractor-shareable spec is this method's job.
422
429
  def build_robot_spec(task_wrapper)
423
430
  robot = task_wrapper.robot
424
431
  RobotSpec.new(
@@ -40,6 +40,8 @@ module RobotLab
40
40
  # @param message [String]
41
41
  # @param threshold [Float] cosine similarity cutoff (default SIMILARITY_THRESHOLD)
42
42
  # @return [Array<AgentSkill>]
43
+ # :reek:TooManyStatements -- linear search/filter with a best-effort rescue fallback.
44
+ # :reek:NestedIterators -- 2-deep filter_map/find joining search hits back to pending skills.
43
45
  def match_agent_skills(message, threshold: SIMILARITY_THRESHOLD)
44
46
  return [] if @pending_agent_skills.nil? || @pending_agent_skills.empty?
45
47
 
@@ -90,9 +92,7 @@ module RobotLab
90
92
  #
91
93
  # @return [String, nil]
92
94
  def current_agent_skill_instructions
93
- messages = @chat.instance_variable_get(:@messages)
94
- sys = messages&.find { |m| m.role.to_s == "system" }
95
- sys&.content
95
+ current_system_instructions
96
96
  end
97
97
  end
98
98
  end
@@ -12,9 +12,15 @@ module RobotLab
12
12
  # == Delivery Serialization
13
13
  #
14
14
  # TypedBus delivers messages in concurrent Async fibers. Robots
15
- # enqueue deliveries into a BusPoller rather than handling them
16
- # inline. The BusPoller drains each group's queue sequentially on
17
- # a dedicated OS thread, so robot.run() calls never interleave.
15
+ # enqueue deliveries into a shared BusPoller rather than dispatching
16
+ # them straight to the handler. Despite the name, BusPoller runs no
17
+ # background thread: `enqueue` processes the delivery inline in the
18
+ # caller's own execution context (Async fiber or OS thread) when the
19
+ # robot is idle, and otherwise queues it behind the delivery already
20
+ # in flight and drains it once that one returns. A mutex therefore
21
+ # serializes deliveries per robot, so a robot's run() calls never
22
+ # interleave — but nothing makes progress while the calling fiber is
23
+ # parked.
18
24
  #
19
25
  # Owns: @bus, @bus_poller, @private_bus_poller, @bus_poller_group, @bus_subscriber_id, @message_counter, @outbox, @message_handler
20
26
  # Reads: @name
@@ -78,12 +84,14 @@ module RobotLab
78
84
  # counterpart to how a Cyborg answers its human — one call makes any bus
79
85
  # member a first-class responder instead of hand-wiring {#on_message}.
80
86
  #
81
- # The responder runs on the poller drain thread, so deliveries to this member
82
- # are handled one at a time (a long turn delays the next inbound message).
87
+ # The responder runs inline in the caller's execution context (BusPoller has
88
+ # no drain thread), and deliveries to this member are serialized, so a long
89
+ # turn blocks both the sender and the next inbound message.
83
90
  #
84
91
  # @param auto_reply [Boolean] send the responder's result back to the sender
85
92
  # @yield [message] the inbound task; return the reply content (nil => no reply)
86
93
  # @return [self]
94
+ # :reek:BooleanParameter :reek:ControlParameter -- auto_reply is a documented public API toggle for reply behavior.
87
95
  def respond_to_tasks(auto_reply: true, &responder)
88
96
  on_message do |message|
89
97
  next if message.reply?
@@ -100,6 +108,7 @@ module RobotLab
100
108
  #
101
109
  # @param auto_reply [Boolean]
102
110
  # @return [self]
111
+ # :reek:BooleanParameter -- auto_reply is a documented public API toggle, forwarded to respond_to_tasks.
103
112
  def serve(auto_reply: true)
104
113
  respond_to_tasks(auto_reply: auto_reply) { |message| run(bus_task_content(message)).reply }
105
114
  end
@@ -154,6 +163,7 @@ module RobotLab
154
163
  # @param bus [TypedBus::MessageBus, nil] bus to join (creates one if nil)
155
164
  # @return [self]
156
165
  #
166
+ # :reek:ControlParameter -- `bus || @bus || new` implements the documented join-or-create semantics.
157
167
  def with_bus(bus = nil)
158
168
  return self if bus && @bus == bus
159
169
 
@@ -165,8 +175,9 @@ module RobotLab
165
175
 
166
176
  # Assign a shared BusPoller from a Network.
167
177
  #
168
- # Stops any private poller this robot auto-created, then adopts
169
- # the network's shared poller for the given group.
178
+ # Drops any private poller this robot auto-created, then adopts
179
+ # the network's shared poller for the given group. Called by
180
+ # Network#task; groups are informational labels only.
170
181
  #
171
182
  # @param poller [BusPoller] the network's shared poller
172
183
  # @param group [Symbol] poller group for this robot (default: :default)
@@ -223,7 +234,7 @@ module RobotLab
223
234
  @bus_poller.enqueue(robot: self, delivery: delivery, group: @bus_poller_group)
224
235
  end
225
236
 
226
- # Process a single delivery (called by BusPoller drain thread).
237
+ # Process a single delivery (called by BusPoller during its inline drain).
227
238
  def process_delivery(delivery)
228
239
  message = delivery.message
229
240
  correlate_reply(message) if message.reply?
@@ -32,6 +32,8 @@ module RobotLab
32
32
  # @param limit [Integer] maximum number of results to return (default 5)
33
33
  # @return [Array<HistoryResult>] results sorted by score descending
34
34
  # @raise [RobotLab::DependencyError] if the 'classifier' gem is not installed
35
+ # :reek:TooManyStatements -- linear vectorize/score/collect scan over the chat history.
36
+ # :reek:FeatureEnvy -- scoring each message's extracted text against the query is the search itself.
35
37
  def search_history(query, limit: 5)
36
38
  TextAnalysis.require_classifier!
37
39
 
@@ -3,6 +3,9 @@
3
3
  module RobotLab
4
4
  class Robot < RubyLLM::Agent
5
5
  module Hooking
6
+ # :reek:LongParameterList -- the documented Robot#run public API: each keyword is a distinct run-scoped override.
7
+ # :reek:TooManyStatements -- the run lifecycle (memory writer swap, hook wrap, budget, cleanup) is one
8
+ # deliberate orchestrator; flog gates its complexity.
6
9
  def run(message = nil, network: nil, task: nil, network_memory: nil, network_config: nil,
7
10
  memory: nil, mcp: :none, tools: :none, hooks: nil, **kwargs, &block)
8
11
  run_memory = resolve_run_memory(memory, network: network, network_memory: network_memory)
@@ -11,6 +11,7 @@ module RobotLab
11
11
  private
12
12
 
13
13
  # Resolve MCP hierarchy: runtime -> robot build -> network -> config
14
+ # :reek:ControlParameter -- `network_config&.mcp || ...` is the documented fallback cascade, not a mode switch.
14
15
  def resolve_mcp_hierarchy(runtime_value, network: nil, network_config: nil)
15
16
  parent_value = network_config&.mcp || network_parent_config(network)&.mcp || RobotLab.config.mcp
16
17
  build_resolved = ToolConfig.resolve_mcp(@mcp_config, parent_value: parent_value)
@@ -18,6 +19,7 @@ module RobotLab
18
19
  end
19
20
 
20
21
  # Resolve tools hierarchy: runtime -> robot build -> network -> config
22
+ # :reek:ControlParameter -- `network_config&.tools || ...` is the documented fallback cascade, not a mode switch.
21
23
  def resolve_tools_hierarchy(runtime_value, network: nil, network_config: nil)
22
24
  parent_value = network_config&.tools || network_parent_config(network)&.tools || RobotLab.config.tools
23
25
  build_resolved = ToolConfig.resolve_tools(@tools_config, parent_value: parent_value)
@@ -32,6 +34,7 @@ module RobotLab
32
34
 
33
35
  # Ensure MCP clients are initialized for the given server configs.
34
36
  # On subsequent calls, retries any servers that previously failed to connect.
37
+ # :reek:TooManyStatements -- first-run init vs retry paths share the needed-server list; linear either way.
35
38
  def ensure_mcp_clients(mcp_servers)
36
39
  return if mcp_servers.empty?
37
40
 
@@ -54,6 +57,7 @@ module RobotLab
54
57
  @mcp_initialized = true
55
58
  end
56
59
 
60
+ # :reek:TooManyStatements -- connect success/failure bookkeeping plus the rescue path for one server.
57
61
  def init_mcp_client(server_config)
58
62
  client = MCP::Client.new(server_config)
59
63
  client.connect
@@ -78,6 +82,7 @@ module RobotLab
78
82
  end
79
83
 
80
84
  # Retry connecting to servers that previously failed
85
+ # :reek:TooManyStatements -- per-server retry with success bookkeeping and a best-effort rescue.
81
86
  def retry_failed_servers(_mcp_servers, needed_servers)
82
87
  return if @failed_mcp_configs.nil? || @failed_mcp_configs.empty?
83
88
 
@@ -85,8 +90,9 @@ module RobotLab
85
90
  to_retry = @failed_mcp_configs.slice(*needed_servers)
86
91
  return if to_retry.empty?
87
92
 
93
+ logger = RobotLab.config.logger
88
94
  to_retry.each do |name, server_config|
89
- RobotLab.config.logger.info(
95
+ logger.info(
90
96
  "Robot '#{@name}' retrying MCP server: #{name}"
91
97
  )
92
98
 
@@ -97,17 +103,19 @@ module RobotLab
97
103
  @mcp_clients[name] = client
98
104
  @failed_mcp_configs.delete(name)
99
105
  discover_mcp_tools(client, name)
100
- RobotLab.config.logger.info(
106
+ logger.info(
101
107
  "Robot '#{@name}' successfully connected to MCP server '#{name}' on retry"
102
108
  )
103
109
  end
104
110
  rescue StandardError => e
105
- RobotLab.config.logger.warn(
111
+ logger.warn(
106
112
  "Robot '#{@name}' retry failed for MCP server '#{name}': #{e.message}"
107
113
  )
108
114
  end
109
115
  end
110
116
 
117
+ # :reek:FeatureEnvy -- adapting each MCP tool definition into a local Tool is exactly this method's purpose.
118
+ # :reek:NestedIterators -- the inner block is the tool's execution closure, not an iteration.
111
119
  def discover_mcp_tools(client, server_name)
112
120
  tools = client.list_tools
113
121