robot_lab 0.0.4 → 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 (77) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +64 -6
  4. data/Rakefile +2 -1
  5. data/docs/api/core/index.md +41 -46
  6. data/docs/api/core/memory.md +200 -154
  7. data/docs/api/core/network.md +13 -3
  8. data/docs/api/core/robot.md +38 -26
  9. data/docs/api/core/state.md +55 -73
  10. data/docs/api/index.md +7 -28
  11. data/docs/api/messages/index.md +35 -20
  12. data/docs/api/messages/text-message.md +67 -21
  13. data/docs/api/messages/tool-call-message.md +80 -41
  14. data/docs/api/messages/tool-result-message.md +119 -50
  15. data/docs/api/messages/user-message.md +48 -24
  16. data/docs/architecture/core-concepts.md +10 -15
  17. data/docs/concepts.md +5 -7
  18. data/docs/examples/index.md +2 -2
  19. data/docs/getting-started/configuration.md +80 -0
  20. data/docs/guides/building-robots.md +10 -9
  21. data/docs/guides/creating-networks.md +49 -0
  22. data/docs/guides/index.md +0 -5
  23. data/docs/guides/rails-integration.md +244 -162
  24. data/docs/guides/streaming.md +118 -138
  25. data/docs/index.md +0 -8
  26. data/examples/03_network.rb +10 -7
  27. data/examples/08_llm_config.rb +40 -11
  28. data/examples/09_chaining.rb +45 -6
  29. data/examples/11_network_introspection.rb +30 -7
  30. data/examples/12_message_bus.rb +1 -1
  31. data/examples/14_rusty_circuit/heckler.rb +14 -8
  32. data/examples/14_rusty_circuit/open_mic.rb +5 -3
  33. data/examples/14_rusty_circuit/scout.rb +14 -31
  34. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +1 -1
  35. data/examples/16_writers_room/display.rb +158 -0
  36. data/examples/16_writers_room/output/.gitignore +2 -0
  37. data/examples/16_writers_room/output/opus_001.md +263 -0
  38. data/examples/16_writers_room/output/opus_001_notes.log +470 -0
  39. data/examples/16_writers_room/prompts/writer.md +37 -0
  40. data/examples/16_writers_room/room.rb +150 -0
  41. data/examples/16_writers_room/tools.rb +162 -0
  42. data/examples/16_writers_room/writer.rb +121 -0
  43. data/examples/16_writers_room/writers_room.rb +162 -0
  44. data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -13
  45. data/lib/robot_lab/memory.rb +8 -32
  46. data/lib/robot_lab/network.rb +13 -20
  47. data/lib/robot_lab/robot/bus_messaging.rb +239 -0
  48. data/lib/robot_lab/robot/mcp_management.rb +88 -0
  49. data/lib/robot_lab/robot/template_rendering.rb +130 -0
  50. data/lib/robot_lab/robot.rb +56 -420
  51. data/lib/robot_lab/run_config.rb +184 -0
  52. data/lib/robot_lab/state_proxy.rb +2 -12
  53. data/lib/robot_lab/task.rb +8 -1
  54. data/lib/robot_lab/utils.rb +39 -0
  55. data/lib/robot_lab/version.rb +1 -1
  56. data/lib/robot_lab.rb +29 -8
  57. data/mkdocs.yml +0 -11
  58. metadata +15 -20
  59. data/docs/api/adapters/anthropic.md +0 -121
  60. data/docs/api/adapters/gemini.md +0 -133
  61. data/docs/api/adapters/index.md +0 -104
  62. data/docs/api/adapters/openai.md +0 -134
  63. data/docs/api/history/active-record-adapter.md +0 -275
  64. data/docs/api/history/config.md +0 -284
  65. data/docs/api/history/index.md +0 -128
  66. data/docs/api/history/thread-manager.md +0 -194
  67. data/docs/guides/history.md +0 -359
  68. data/lib/robot_lab/adapters/anthropic.rb +0 -163
  69. data/lib/robot_lab/adapters/base.rb +0 -85
  70. data/lib/robot_lab/adapters/gemini.rb +0 -193
  71. data/lib/robot_lab/adapters/openai.rb +0 -160
  72. data/lib/robot_lab/adapters/registry.rb +0 -81
  73. data/lib/robot_lab/errors.rb +0 -70
  74. data/lib/robot_lab/history/active_record_adapter.rb +0 -146
  75. data/lib/robot_lab/history/config.rb +0 -115
  76. data/lib/robot_lab/history/thread_manager.rb +0 -93
  77. data/lib/robot_lab/robotic_model.rb +0 -324
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8d3bfa06c07301b03b01359a15a945374b135f2055c77396ad456a916917a742
4
- data.tar.gz: 0b9e81d50841bf9a56d4efcc8f4175f34aa0e17702c89cc4bd5d4750631c0f68
3
+ metadata.gz: db26fa8aa3d47eb49face5b344cd1b41ed6e148a0460514b54913b5973ca9cc1
4
+ data.tar.gz: 79f2a71535ddaabd6f2a7c5dceaf3aefad10e3e51b4c0ccbd04fbe82c7804f57
5
5
  SHA512:
6
- metadata.gz: 15649fd320dd84530d49884900d99c2cdc98ddd5841948b052ed6b3a6f3d0fa9cec3e80636883f6d2bcd5b5a8ffe6b61ad681dc907faf5e881f48ac05ebef0fd
7
- data.tar.gz: cdd136aad382247babb5639cf3a2b0b35db5086b33661823a6e00d8099f2a18a57325c3ed57f3474d23ee8f317f65c1f0b70911d349ad04f111d80cac03dd8c9
6
+ metadata.gz: 832389808ec464678849736bb0d9fceca5dd67a5a3fa1e4c0486367bdf20a389257f4bfdeeb3afc9ca8619168274eb2a7cbed2c0376e358c902030ee26b105de
7
+ data.tar.gz: fb57b2e6cb329f6c116cbd7cb81a60bb68a9daeddc6e45a1f859690cc7f44ff3074eb4dfcdf0240b6a95f3786d992fc31c6edc3ed61fd2f838b435a19b550876
data/CHANGELOG.md CHANGED
@@ -11,6 +11,56 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.0.6] - 2026-02-17 [unreleased]
15
+
16
+ ### Added
17
+
18
+ - **Writers' Room example** (`examples/16_writers_room/`) — Self-Organizing Group (SOG) demo where identical writer robots collaborate to produce a 10-chapter fiction novella
19
+ - Writer class with `fresh_chat!` pattern to prevent RubyLLM empty text content block corruption in bus-based robots
20
+ - 7 tools: Broadcast, DirectMessage, ReadMemory, WriteMemory, ListMemory, SpawnWriter, MarkComplete
21
+ - Room class with bus, shared memory, writer roster, heartbeat-based progress nudging, and structured logging
22
+ - Display class with color-coded terminal output, word wrapping, and optional log file
23
+ - CLI with `--premise`, `--writers`, `--log`, `--timeout`, `-h`/`--help` options
24
+ - Shared prompt template (`prompts/writer.md`) — all writers use the same instructions with no hierarchy
25
+ - **Network pipeline tests** (`test/robot_lab/network_pipeline_test.rb`) for sequential robot execution and memory sharing
26
+ - **`dispatch_async` error handling** — exceptions inside async dispatch are now logged and contained instead of propagating
27
+
28
+ ### Changed
29
+
30
+ - Bumped version to 0.0.6
31
+ - **Removed `Errors` module** and related test file — unused error classes cleaned out
32
+ - **Zeitwerk autoloading optimized** — streamlined loader configuration in `lib/robot_lab.rb`
33
+ - Rakefile updated with `16_writers_room` entry point in `SUBDIR_ENTRY_POINTS`
34
+
35
+ ## [0.0.5] - 2026-02-17 [unreleased]
36
+
37
+ ### Added
38
+
39
+ - **`RunConfig` class** (`lib/robot_lab/run_config.rb`) for shared operational defaults
40
+ - Field categories: LLM (`model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop`), tools (`mcp`, `tools`), callbacks (`on_tool_call`, `on_tool_result`), infrastructure (`bus`, `enable_cache`)
41
+ - Keyword construction, block DSL, and method chaining
42
+ - Merge semantics: more-specific config's non-nil values win
43
+ - `apply_to(chat)` applies LLM fields to a RubyLLM chat
44
+ - `from_front_matter(metadata)` extracts config from template YAML front matter
45
+ - `to_h`, `to_json_hash` (skips Procs/IO), `empty?`, `key?`, `==`, `inspect`
46
+ - Full test suite (`test/robot_lab/run_config_test.rb`, 39 tests)
47
+ - **`config:` parameter** on `Robot.new`, `Network.new`, `Network#task`, `RobotLab.build`, and `RobotLab.create_network` for passing RunConfig instances
48
+ - **Configuration inheritance chain**: `RobotLab.config` (global) -> `network.config` -> task `config:` -> `robot.config` -> template front matter -> constructor kwargs
49
+ - **`robot.config` / `network.config` accessors** (`attr_reader`) returning the effective RunConfig
50
+ - **`RobotLab.configure`** block-style configuration method yielding the config object
51
+ - **Bus processing guard** (`handle_incoming_delivery`) serializing message deliveries across bus-connected robots to prevent Async fiber re-entrancy corrupting chat message ordering
52
+ - Documentation for RunConfig across README, configuration guide, network guide, and API reference
53
+ - Updated examples (`03_network`, `08_llm_config`, `09_chaining`, `11_network_introspection`) demonstrating RunConfig usage
54
+
55
+ ### Changed
56
+
57
+ - Bumped version to 0.0.5
58
+ - **Template rendering refactored** to use `RunConfig.from_front_matter` instead of `apply_front_matter_config` — front matter config is now merged with the robot's RunConfig before applying to chat
59
+ - **MCP/tools hierarchy resolution** now accepts `network_config:` parameter instead of directly accessing the network object, enabling RunConfig-driven configuration flow
60
+ - **`dispatch_async`** simplified to exclusively use Async fibers, removing Thread-based fallback
61
+ - **`Memory#get`** improved nil value handling — uses `@backend.key?()` instead of nil check for correct nil value storage and retrieval
62
+ - **`Memory#clone`** optimized — results and messages are referenced directly instead of duplicated
63
+
14
64
  ## [0.0.4] - 2026-02-16 [unreleased]
15
65
 
16
66
  ### Added
data/README.md CHANGED
@@ -187,6 +187,57 @@ robot = RobotLab.build(
187
187
  )
188
188
  ```
189
189
 
190
+ ### Shared Configuration with RunConfig
191
+
192
+ `RunConfig` lets you define operational defaults that flow through the hierarchy: Network -> Robot -> Template -> Task -> Runtime. Use it to share LLM settings across multiple robots or an entire network.
193
+
194
+ ```ruby
195
+ # Create a shared config
196
+ shared = RobotLab::RunConfig.new(
197
+ model: "claude-sonnet-4",
198
+ temperature: 0.7,
199
+ max_tokens: 2000
200
+ )
201
+
202
+ # Apply to individual robots
203
+ robot = RobotLab.build(
204
+ name: "writer",
205
+ system_prompt: "You are a creative writer.",
206
+ config: shared
207
+ )
208
+
209
+ # Apply to an entire network (all robots inherit these defaults)
210
+ network = RobotLab.create_network(name: "pipeline", config: shared) do
211
+ task :analyzer, analyzer_robot, depends_on: :none
212
+ task :writer, writer_robot, depends_on: [:analyzer]
213
+ end
214
+
215
+ # Robot-specific kwargs always override the shared config
216
+ robot = RobotLab.build(
217
+ name: "fast_bot",
218
+ system_prompt: "Be brief.",
219
+ config: shared,
220
+ temperature: 0.3 # overrides shared config's 0.7
221
+ )
222
+ ```
223
+
224
+ RunConfig supports keyword construction, block DSL, and merge semantics:
225
+
226
+ ```ruby
227
+ # Block DSL
228
+ config = RobotLab::RunConfig.new do |c|
229
+ c.model "claude-sonnet-4"
230
+ c.temperature 0.7
231
+ end
232
+
233
+ # Merge (more-specific wins)
234
+ network_config = RobotLab::RunConfig.new(model: "claude-sonnet-4", temperature: 0.5)
235
+ robot_config = RobotLab::RunConfig.new(temperature: 0.9)
236
+ effective = network_config.merge(robot_config)
237
+ effective.temperature #=> 0.9
238
+ effective.model #=> "claude-sonnet-4"
239
+ ```
240
+
190
241
  ### Chaining Configuration
191
242
 
192
243
  Robots support method chaining to adjust configuration after creation:
@@ -375,7 +426,7 @@ class Comedian < RobotLab::Robot
375
426
  super(name: "bob", template: :comedian, bus: bus)
376
427
  on_message do |message|
377
428
  joke = run(message.content.to_s).last_text_content.strip
378
- reply(message, joke)
429
+ send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
379
430
  end
380
431
  end
381
432
  end
@@ -402,7 +453,7 @@ Key features:
402
453
 
403
454
  - **Typed channels** — only `RobotMessage` objects are accepted (type enforcement via `typed_bus`)
404
455
  - **Auto-ACK** — `on_message { |message| }` auto-acknowledges; use `|delivery, message|` for manual ACK/NACK
405
- - **Reply correlation** — `reply(message, content)` tracks conversation threads via `in_reply_to`
456
+ - **Reply correlation** — `send_reply(to:, content:, in_reply_to:)` tracks conversation threads
406
457
  - **Outbox tracking** — sent messages tracked in `robot.outbox` with status and replies
407
458
  - **Independent of Network** — bus communication works without a Network pipeline
408
459
 
@@ -451,17 +502,24 @@ Key features:
451
502
 
452
503
  ## Streaming
453
504
 
454
- Pass a block to `run` to receive real-time events during execution:
505
+ Use a `Streaming::Context` with a publish callback to receive real-time events:
455
506
 
456
507
  ```ruby
457
- result = robot.run("Tell me a story") do |event|
508
+ handler = lambda do |event|
458
509
  case event[:event]
459
- when "text.delta"
510
+ when RobotLab::Streaming::Events::TEXT_DELTA
460
511
  print event[:data][:delta]
461
- when "run.completed"
512
+ when RobotLab::Streaming::Events::RUN_COMPLETED
462
513
  puts "\nDone!"
463
514
  end
464
515
  end
516
+
517
+ context = RobotLab::Streaming::Context.new(
518
+ run_id: SecureRandom.uuid,
519
+ message_id: SecureRandom.uuid,
520
+ scope: "robot",
521
+ publish: handler
522
+ )
465
523
  ```
466
524
 
467
525
  ## Rails Integration
data/Rakefile CHANGED
@@ -48,7 +48,8 @@ namespace :examples do
48
48
  # Map of subdirectory-based demos to their entry point scripts
49
49
  SUBDIR_ENTRY_POINTS = {
50
50
  "14_rusty_circuit" => "open_mic.rb",
51
- "15_memory_network_and_bus" => "editorial_pipeline.rb"
51
+ "15_memory_network_and_bus" => "editorial_pipeline.rb",
52
+ "16_writers_room" => "writers_room.rb"
52
53
  }.freeze
53
54
 
54
55
  desc "Run all examples"
@@ -12,21 +12,21 @@ classDiagram
12
12
  +model: String
13
13
  +template: String
14
14
  +tools: Array~Tool~
15
- +run(state, network)
15
+ +run(message) Message
16
16
  }
17
17
 
18
18
  class Network {
19
19
  +name: String
20
20
  +robots: Hash
21
- +router: Proc
22
- +run(state)
21
+ +config: RunConfig
22
+ +run(message)
23
23
  }
24
24
 
25
- class State {
26
- +data: StateProxy
27
- +results: Array
28
- +messages: Array
29
- +memory: Memory
25
+ class RunConfig {
26
+ +model: String
27
+ +temperature: Float
28
+ +merge(other) RunConfig
29
+ +apply_to(chat)
30
30
  }
31
31
 
32
32
  class Tool {
@@ -37,10 +37,13 @@ classDiagram
37
37
  }
38
38
 
39
39
  class Memory {
40
- +remember(key, value)
41
- +recall(key)
42
- +forget(key)
43
- +scoped(namespace)
40
+ +set(key, value)
41
+ +get(key)
42
+ +delete(key)
43
+ +data: StateProxy
44
+ +results: Array
45
+ +messages: Array
46
+ +session_id: String
44
47
  }
45
48
 
46
49
  class RobotMessage {
@@ -53,9 +56,11 @@ classDiagram
53
56
  }
54
57
 
55
58
  Network --> Robot : contains
59
+ Network --> RunConfig : uses
60
+ Robot --> RunConfig : uses
56
61
  Robot --> Tool : has
57
- State --> Memory : has
58
- Network --> State : uses
62
+ Robot --> Memory : uses
63
+ Network --> Memory : uses
59
64
  Robot ..> RobotMessage : sends/receives
60
65
  ```
61
66
 
@@ -65,10 +70,10 @@ classDiagram
65
70
  |-------|---------|
66
71
  | [Robot](robot.md) | LLM agent with personality, tools, and model configuration |
67
72
  | [Network](network.md) | Container for robots with routing and orchestration |
68
- | [State](state.md) | Conversation state with data, results, and memory |
73
+ | RunConfig | Shared configuration for LLM, tools, callbacks, and infrastructure |
69
74
  | [Tool](tool.md) | Callable function with parameters and handler |
70
75
  | [AskUser](tool.md#built-in-askuser) | Built-in tool for terminal-based user interaction |
71
- | [Memory](memory.md) | Namespaced key-value store for sharing data |
76
+ | [Memory](memory.md) | Reactive key-value store for sharing data |
72
77
  | RobotMessage | Typed envelope for bus-based inter-robot communication |
73
78
 
74
79
  ## Quick Examples
@@ -76,50 +81,40 @@ classDiagram
76
81
  ### Robot
77
82
 
78
83
  ```ruby
79
- robot = RobotLab.build do
80
- name "assistant"
81
- model "claude-sonnet-4"
82
- template "You are helpful."
83
-
84
- tool :greet do
85
- parameter :name, type: :string
86
- handler { |name:, **_| "Hello, #{name}!" }
87
- end
88
- end
84
+ robot = RobotLab.build(
85
+ name: "assistant",
86
+ model: "claude-sonnet-4",
87
+ system_prompt: "You are helpful.",
88
+ local_tools: [greet_tool]
89
+ )
90
+
91
+ result = robot.run("Hello!")
89
92
  ```
90
93
 
91
94
  ### Network
92
95
 
93
96
  ```ruby
94
- network = RobotLab.create_network do
95
- name "my_network"
96
- add_robot robot
97
- router ->(args) { args.call_count.zero? ? :assistant : nil }
97
+ network = RobotLab.create_network(name: "my_network") do
98
+ step :analyzer, analyzer_robot, depends_on: :none
99
+ step :writer, writer_robot, depends_on: [:analyzer]
98
100
  end
101
+
102
+ result = network.run(message: "Process this")
99
103
  ```
100
104
 
101
- ### State
105
+ ### Memory
102
106
 
103
107
  ```ruby
104
- state = RobotLab.create_state(
105
- message: "Hello",
106
- data: { user_id: "123" }
107
- )
108
+ memory = RobotLab.create_memory(data: { user_id: "123" })
109
+ memory.set(:category, "billing")
110
+ memory.get(:category) # => "billing"
108
111
  ```
109
112
 
110
113
  ### Tool
111
114
 
112
115
  ```ruby
113
- tool = RobotLab::Tool.new(
116
+ tool = RobotLab::Tool.create(
114
117
  name: "get_time",
115
- description: "Get current time",
116
- handler: ->(**, _) { Time.now.to_s }
117
- )
118
- ```
119
-
120
- ### Memory
121
-
122
- ```ruby
123
- state.memory.remember("key", "value")
124
- value = state.memory.recall("key")
118
+ description: "Get current time"
119
+ ) { |**_| Time.now.to_s }
125
120
  ```