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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a73bb0d9c45aa5f2f904e744774ecb3396027fa17a7c099a1ede7ac57598929
4
- data.tar.gz: 8ff58ce299752cb0ff12365637070d71f35b011613a62b01a62d2fe43523a87c
3
+ metadata.gz: db26fa8aa3d47eb49face5b344cd1b41ed6e148a0460514b54913b5973ca9cc1
4
+ data.tar.gz: 79f2a71535ddaabd6f2a7c5dceaf3aefad10e3e51b4c0ccbd04fbe82c7804f57
5
5
  SHA512:
6
- metadata.gz: afa598bbaea859d6f70f6bef21c936fc4f6260cf15c8af786dbaee5ce4d71c4d24e9f4e63a243e9a3321d2659f04cd6fab86f19568852b3cbd659eb9f4b339a1
7
- data.tar.gz: 77ea74d0251124766263e3baf64f80889a56ecffa248a1ac612cd0ea1ea495ee090a3830a575090fb5f13dfac55d3c10a0097c182f57cc5a47c3167b5cb4e29f
6
+ metadata.gz: 832389808ec464678849736bb0d9fceca5dd67a5a3fa1e4c0486367bdf20a389257f4bfdeeb3afc9ca8619168274eb2a7cbed2c0376e358c902030ee26b105de
7
+ data.tar.gz: fb57b2e6cb329f6c116cbd7cb81a60bb68a9daeddc6e45a1f859690cc7f44ff3074eb4dfcdf0240b6a95f3786d992fc31c6edc3ed61fd2f838b435a19b550876
@@ -1,4 +1,4 @@
1
- name: Deploy HTM Documentation to GitHub Pages
1
+ name: Deploy Documentation to GitHub Pages
2
2
  on:
3
3
  push:
4
4
  branches:
@@ -41,12 +41,12 @@ jobs:
41
41
  git config --local user.email "action@github.com"
42
42
  git config --local user.name "GitHub Action"
43
43
 
44
+ - name: Build MkDocs site
45
+ run: mkdocs build
46
+
44
47
  - name: Deploy to GitHub Pages
45
- run: |
46
- if [ "${{ github.ref }}" = "refs/heads/main" ]; then
47
- echo "Deploying from main branch"
48
- mkdocs gh-deploy --force --clean
49
- else
50
- echo "Deploying from develop branch"
51
- mkdocs gh-deploy --force --clean
52
- fi
48
+ uses: peaceiris/actions-gh-pages@v4
49
+ with:
50
+ github_token: ${{ secrets.GITHUB_TOKEN }}
51
+ publish_dir: ./site
52
+ keep_files: true
data/.irbrc ADDED
@@ -0,0 +1,6 @@
1
+ begin
2
+ load File.expand_path('lib/robot_lab.rb', __dir__)
3
+ rescue LoadError => e
4
+ $stderr.puts "[robot_lab] #{e.message}"
5
+ $stderr.puts "[robot_lab] Run `bundle exec irb` to load RobotLab"
6
+ end
data/CHANGELOG.md CHANGED
@@ -11,6 +11,146 @@ 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
+
64
+ ## [0.0.4] - 2026-02-16 [unreleased]
65
+
66
+ ### Added
67
+
68
+ - **`AskUser` tool** for human-in-the-loop interactions
69
+ - Supports open-ended text, multiple choice, and default values
70
+ - IO sourced from `robot.input`/`robot.output` (defaults to `$stdin`/`$stdout`)
71
+ - Full test suite (`test/robot_lab/ask_user_test.rb`)
72
+ - **`Robot#input` / `Robot#output` accessors** for configurable IO streams
73
+ - **`reply` alias** for `RobotResult#last_text_content` — shorter, more natural API
74
+ - **`.irbrc`** for loading RobotLab in project-level IRB sessions
75
+ - **`wait_until` test helper** replacing flaky `sleep`-based assertions in async tests
76
+ - Documentation for AskUser tool across API reference, guides, and examples
77
+
78
+ ### Changed
79
+
80
+ - Bumped version to 0.0.4
81
+ - **Made Rails dependencies optional** — removed `railties`, `activerecord`, `state_machines`, `state_machines-activemodel`, `state_machines-activerecord` from gemspec hard dependencies; moved to Gemfile `:test` group
82
+ - Replaced `require 'active_support'` with targeted `require 'active_support/core_ext/module/delegation'` — only loads what ruby_llm actually needs
83
+ - Added `activesupport >= 7.0` as explicit gemspec dependency with comment explaining it's required by ruby_llm (undeclared upstream)
84
+ - **Tool JSON schema keys are now symbolized** via `deep_symbolize_keys` in `Tool#to_json_schema`
85
+ - Updated all examples to use `reply` alias instead of `last_text_content`
86
+ - Replaced `sleep`-based test assertions with `wait_until` helper in memory, waiter, and robot tests
87
+ - Disabled branch coverage in SimpleCov except in CI
88
+
89
+ ### Fixed
90
+
91
+ - Gem install conflict (`activesupport` version mismatch) when running outside Bundler
92
+ - IRB loading issue where `require_relative` was a no-op due to partial load in `$LOADED_FEATURES` — switched to `load`
93
+ - Robot tests for `send_message` now register a message handler on the receiver to avoid TypedBus warnings
94
+
95
+ ## [0.0.3] - 2026-02-15 [unreleased]
96
+
97
+ ### Added
98
+
99
+ - **Self-contained templates** with extended YAML front matter support
100
+ - `robot_name` — override robot name from template
101
+ - `description` — set robot description from template
102
+ - `tools` — declare tool class names (resolved via `Object.const_get` at build time)
103
+ - `mcp` — declare MCP server configurations
104
+ - Constructor-provided values always take precedence over front matter
105
+ - **Editorial pipeline example** (`15_memory_network_and_bus/`) demonstrating multi-stage workflow with network, memory, and bus coordination
106
+ - OS-specific writer robots, editor, and editor-in-chief roles
107
+ - New prompt templates: `os_advocate`, `os_editor`, `os_chief`
108
+ - Rakefile support for running subdirectory-based examples with `SUBDIR_ENTRY_POINTS` mapping
109
+
110
+ ### Changed
111
+
112
+ - Bumped version to 0.0.3
113
+ - Refactored Comic and Scout classes to use `attr_accessor` instead of `instance_variable_set`/`instance_variable_get`
114
+ - Extensive documentation updates across README, guides, API reference, and examples for front matter extras
115
+
116
+ ## [0.0.2] - 2026-02-15 (unreleased)
117
+
118
+ ### Added
119
+
120
+ - **TypedBus message bus** for robot-to-robot communication
121
+ - `RobotMessage` immutable data class (`Data.define`) with `id`, `from`, `content`, `in_reply_to`
122
+ - Optional `bus:` parameter on Robot constructor — purely additive
123
+ - `on_message` handler with auto-ACK (1-arg block) and manual ACK/NACK (2-arg block)
124
+ - `publish_to_bus` with Async-aware fiber wrapping
125
+ - Typed channels accepting only `RobotMessage` objects
126
+ - **Dynamic robot spawning** via `Robot#spawn` method for creating child robots at runtime
127
+ - **`with_bus` configuration method** for connecting robots to a message bus after creation
128
+ - **Comic robot class** with dynamic comedy tools (`reinvent_style`, `adjust_energy`, `get_coaching`)
129
+ - New examples:
130
+ - `12_message_bus.rb` — two-robot joke critique workflow
131
+ - `13_spawn.rb` — dynamic robot spawning
132
+ - `14_rusty_circuit/` — multi-robot comedy open mic with bus-based coordination
133
+ - New prompt templates: `comedian`, `comedy_critic`, `dispatcher`, `open_mic_comic`, `open_mic_heckler`, `open_mic_scout`, `configurable`, `llm_config_demo`
134
+ - Rake tasks for building documentation sites
135
+ - GitHub Actions workflow for YARD documentation deployment
136
+
137
+ ### Changed
138
+
139
+ - Bumped version to 0.0.2
140
+ - Replaced `ruby_llm-template` dependency with `prompt_manager` (~> 1.0)
141
+ - Updated `ruby_llm` dependency to ~> 1.12
142
+ - Added `typed_bus` as a core dependency
143
+ - Added `myway_config` (~> 0.1) dependency
144
+ - Added `amazing_print` and `hashdiff` as development dependencies
145
+ - Migrated all prompt templates from directory-based format (`system.txt.erb` / `user.txt.erb`) to single `.md` files with YAML front matter
146
+ - Refactored `Robot` class for simplified configuration
147
+ - Refactored `Config` class
148
+ - Extensive documentation updates across all guide, architecture, and API reference pages
149
+
150
+ ### Fixed
151
+
152
+ - GitHub Actions platform limitation (`arm64-darwin` only in lockfile)
153
+
14
154
  ## [0.0.1] - 2026-01-16
15
155
 
16
156
  - refactored the network concept
data/README.md CHANGED
@@ -18,8 +18,10 @@ RobotLab enables you to build sophisticated AI applications using multiple speci
18
18
  - <strong>Network Orchestration</strong> - Connect robots with flexible routing<br>
19
19
  - <strong>Extensible Tools</strong> - Give robots custom capabilities<br>
20
20
  - <strong>MCP Integration</strong> - Connect to external tool servers<br>
21
- - <strong>Shared Memory</strong> - Hierarchical memory with namespaced scopes<br>
21
+ - <strong>Shared Memory</strong> - Reactive key-value store with subscriptions<br>
22
22
  - <strong>Conversation History</strong> - Persist and restore threads<br>
23
+ - <strong>Message Bus</strong> - Bidirectional robot communication via TypedBus<br>
24
+ - <strong>Dynamic Spawning</strong> - Robots create new robots at runtime<br>
23
25
  - <strong>Streaming</strong> - Real-time event streaming<br>
24
26
  - <strong>Rails Integration</strong> - Generators and ActiveRecord support
25
27
  </td>
@@ -52,11 +54,6 @@ The simplest way to create a robot is with an inline `system_prompt`. This appro
52
54
  ```ruby
53
55
  require "robot_lab"
54
56
 
55
- # Configure RobotLab
56
- RobotLab.configure do |config|
57
- config.default_model = "claude-sonnet-4"
58
- end
59
-
60
57
  # Create a robot with an inline system prompt
61
58
  robot = RobotLab.build(
62
59
  name: "assistant",
@@ -64,66 +61,119 @@ robot = RobotLab.build(
64
61
  )
65
62
 
66
63
  # Run the robot
67
- result = robot.run(message: "What is the capital of France?")
64
+ result = robot.run("What is the capital of France?")
68
65
 
69
- puts result.output.first.content
66
+ puts result.last_text_content
70
67
  # => "The capital of France is Paris."
71
68
  ```
72
69
 
73
- ### Using Templates
70
+ ### Configuration
74
71
 
75
- For production applications, RobotLab supports a powerful template system built on ERB. Templates allow you to:
72
+ RobotLab uses [MywayConfig](https://github.com/MadBomber/myway_config) for layered configuration. There is no `configure` block. Configuration is loaded automatically from multiple sources in priority order:
76
73
 
77
- - **Compose prompts** from reusable components
78
- - **Inject dynamic context** at build-time and run-time
79
- - **Version control** your prompts alongside your code
80
- - **Share prompts** across multiple robots
74
+ 1. Bundled defaults (`lib/robot_lab/config/defaults.yml`)
75
+ 2. Environment-specific overrides (development, test, production)
76
+ 3. XDG user config (`~/.config/robot_lab/config.yml`)
77
+ 4. Project config (`./config/robot_lab.yml`)
78
+ 5. Environment variables (`ROBOT_LAB_*` prefix)
81
79
 
82
- Configure the template directory:
80
+ ```bash
81
+ # Set API keys via environment variables (double underscore for nesting)
82
+ export ROBOT_LAB_RUBY_LLM__ANTHROPIC_API_KEY=sk-ant-...
83
+ export ROBOT_LAB_RUBY_LLM__OPENAI_API_KEY=sk-...
84
+ export ROBOT_LAB_RUBY_LLM__MODEL=claude-sonnet-4
85
+ ```
83
86
 
84
87
  ```ruby
85
- RobotLab.configure do |config|
86
- config.template_path = "app/prompts"
87
- end
88
+ # Access configuration values
89
+ RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
90
+ RobotLab.config.ruby_llm.request_timeout #=> 120
91
+ RobotLab.config.streaming_enabled #=> true
88
92
  ```
89
93
 
90
- Each template is a **directory** containing ERB files for different message roles:
94
+ Or create a project config file at `./config/robot_lab.yml`:
91
95
 
96
+ ```yaml
97
+ ruby_llm:
98
+ model: claude-sonnet-4
99
+ anthropic_api_key: sk-ant-...
100
+ request_timeout: 180
92
101
  ```
93
- app/prompts/
94
- assistant/
95
- ├── system.txt.erb # System message (required)
96
- ├── user.txt.erb # User prompt template (optional)
97
- ├── assistant.txt.erb # Pre-filled assistant response (optional)
98
- └── schema.rb # Structured output schema (optional)
102
+
103
+ ### Using Templates
104
+
105
+ For production applications, RobotLab supports a template system built on [PromptManager](https://github.com/MadBomber/prompt_manager). Templates allow you to:
106
+
107
+ - **Compose prompts** from reusable Markdown files
108
+ - **Inject dynamic context** at build-time
109
+ - **Version control** your prompts alongside your code
110
+ - **Share prompts** across multiple robots
111
+
112
+ Each template is a `.md` file with YAML front matter for metadata and parameters:
113
+
114
+ ```
115
+ prompts/
116
+ assistant.md
117
+ classifier.md
118
+ billing.md
99
119
  ```
100
120
 
101
- Create the system message at `app/prompts/assistant/system.txt.erb`:
121
+ Create a template at `prompts/assistant.md`:
102
122
 
103
- ```erb
123
+ ```markdown
124
+ ---
125
+ description: A helpful assistant
126
+ parameters:
127
+ company_name: null
128
+ tone: friendly
129
+ ---
104
130
  You are a helpful assistant for <%= company_name %>.
105
131
 
132
+ Your communication style should be <%= tone %>.
133
+
106
134
  Your responsibilities:
107
135
  - Answer questions accurately and concisely
108
136
  - Be friendly and professional
109
137
  - Admit when you don't know something
110
-
111
- <% if guidelines %>
112
- Additional guidelines:
113
- <%= guidelines %>
114
- <% end %>
115
138
  ```
116
139
 
117
- Reference the template directory using a Symbol:
140
+ Reference the template by symbol:
118
141
 
119
142
  ```ruby
120
143
  robot = RobotLab.build(
121
144
  name: "assistant",
122
145
  template: :assistant,
123
- context: { company_name: "Acme Corp", guidelines: nil }
146
+ context: { company_name: "Acme Corp", tone: "professional" }
124
147
  )
125
148
  ```
126
149
 
150
+ ### Self-Contained Templates
151
+
152
+ Templates can declare tools, MCP servers, name, and description in front matter, making the `.md` file a complete robot definition:
153
+
154
+ ```markdown
155
+ ---
156
+ description: GitHub assistant with MCP tool access
157
+ robot_name: github_bot
158
+ tools:
159
+ - CodeSearchTool
160
+ mcp:
161
+ - name: github
162
+ transport: stdio
163
+ command: npx
164
+ args: ["-y", "@modelcontextprotocol/server-github"]
165
+ model: claude-sonnet-4
166
+ ---
167
+ You are a GitHub assistant. Use available tools to help with repository tasks.
168
+ ```
169
+
170
+ ```ruby
171
+ # Template provides everything — minimal constructor call
172
+ robot = RobotLab.build(template: :github_assistant)
173
+ ```
174
+
175
+ Front matter supports: `description`, `robot_name`, `tools`, `mcp`, `parameters`, and LLM config keys (`model`, `temperature`, `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, `stop`). Constructor-provided values always take precedence over front matter.
176
+
127
177
  ### Combining Templates with System Prompts
128
178
 
129
179
  The `system_prompt` parameter can also be used alongside a template. When both are provided, the template renders first and the `system_prompt` is appended. This is particularly useful during development and testing when you want to add temporary instructions or context to an existing template:
@@ -132,11 +182,76 @@ The `system_prompt` parameter can also be used alongside a template. When both a
132
182
  robot = RobotLab.build(
133
183
  name: "assistant",
134
184
  template: :assistant,
135
- context: { company_name: "Acme Corp" },
185
+ context: { company_name: "Acme Corp", tone: "friendly" },
136
186
  system_prompt: "DEBUG MODE: Log all tool calls. Today's date is #{Date.today}."
137
187
  )
138
188
  ```
139
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
+
241
+ ### Chaining Configuration
242
+
243
+ Robots support method chaining to adjust configuration after creation:
244
+
245
+ ```ruby
246
+ robot = RobotLab.build(name: "writer", system_prompt: "You are a creative writer.")
247
+
248
+ result = robot
249
+ .with_temperature(0.9)
250
+ .with_model("claude-sonnet-4")
251
+ .with_max_tokens(2000)
252
+ .run("Write a haiku about Ruby programming")
253
+ ```
254
+
140
255
  ## Creating a Robot with Tools
141
256
 
142
257
  ```ruby
@@ -163,14 +278,14 @@ class Magic8Ball < RubyLLM::Tool
163
278
  end
164
279
  end
165
280
 
166
- # Create robot with tools
281
+ # Create robot with tools via local_tools: parameter
167
282
  robot = RobotLab.build(
168
283
  name: "oracle",
169
284
  system_prompt: "You are a mystical oracle. Use the Magic 8-Ball to answer questions about the future.",
170
- tools: [Magic8Ball]
285
+ local_tools: [Magic8Ball]
171
286
  )
172
287
 
173
- result = robot.run(message: "Should I start learning Rust?")
288
+ result = robot.run("Should I start learning Rust?")
174
289
  ```
175
290
 
176
291
  ## Orchestrating Multiple Robots
@@ -181,7 +296,10 @@ Networks use [SimpleFlow](https://github.com/MadBomber/simple_flow) pipelines wi
181
296
  # Custom classifier that activates the appropriate specialist
182
297
  class ClassifierRobot < RobotLab::Robot
183
298
  def call(result)
184
- robot_result = run(**extract_run_context(result))
299
+ context = extract_run_context(result)
300
+ message = context.delete(:message)
301
+
302
+ robot_result = run(message, **context)
185
303
 
186
304
  new_result = result
187
305
  .with_context(@name.to_sym, robot_result)
@@ -228,15 +346,15 @@ Both robots and networks have inherent memory that persists across runs:
228
346
  # Standalone robot with inherent memory
229
347
  robot = RobotLab.build(name: "assistant", system_prompt: "You are helpful.")
230
348
 
231
- robot.run(message: "My name is Alice")
232
- robot.run(message: "What's my name?") # Memory persists automatically
349
+ robot.run("My name is Alice")
350
+ robot.run("What's my name?") # Memory persists automatically
233
351
 
234
352
  # Access robot's memory
235
353
  robot.memory[:user_id] = 123
236
354
  robot.memory.data[:category] = "billing"
237
355
 
238
356
  # Runtime memory injection
239
- robot.run(message: "Help me", memory: { session_id: "abc123", tier: "premium" })
357
+ robot.run("Help me", memory: { session_id: "abc123", tier: "premium" })
240
358
 
241
359
  # Reset memory when needed
242
360
  robot.reset_memory
@@ -285,26 +403,123 @@ filesystem_server = {
285
403
  robot = RobotLab.build(
286
404
  name: "developer",
287
405
  template: :developer,
288
- mcp_servers: [filesystem_server]
406
+ mcp: [filesystem_server]
289
407
  )
290
408
 
291
409
  # Robot can now use filesystem tools
292
- result = robot.run(message: "List the files in the current directory")
410
+ result = robot.run("List the files in the current directory")
411
+ ```
412
+
413
+ ## Message Bus
414
+
415
+ Robots can communicate bidirectionally via an optional message bus, independent of the Network pipeline. This enables negotiation loops, convergence patterns, and cyclic workflows.
416
+
417
+ Connect robots to a bus at construction time with `bus:`, or after creation with `with_bus`:
418
+
419
+ ```ruby
420
+ require "robot_lab"
421
+
422
+ bus = TypedBus::MessageBus.new
423
+
424
+ class Comedian < RobotLab::Robot
425
+ def initialize(bus:)
426
+ super(name: "bob", template: :comedian, bus: bus)
427
+ on_message do |message|
428
+ joke = run(message.content.to_s).last_text_content.strip
429
+ send_reply(to: message.from.to_sym, content: joke, in_reply_to: message.key)
430
+ end
431
+ end
432
+ end
433
+
434
+ class ComedyCritic < RobotLab::Robot
435
+ def initialize(bus:)
436
+ super(name: "alice", template: :comedy_critic, bus: bus)
437
+ @accepted = false
438
+ on_message do |message|
439
+ verdict = run("Evaluate this joke:\n\n#{message.content}").last_text_content.strip
440
+ @accepted = verdict.start_with?("FUNNY")
441
+ send_message(to: :bob, content: "Try again.") unless @accepted
442
+ end
443
+ end
444
+ attr_reader :accepted
445
+ end
446
+
447
+ bob = Comedian.new(bus: bus)
448
+ alice = ComedyCritic.new(bus: bus)
449
+ alice.send_message(to: :bob, content: "Tell me a funny robot joke.")
293
450
  ```
294
451
 
452
+ Key features:
453
+
454
+ - **Typed channels** — only `RobotMessage` objects are accepted (type enforcement via `typed_bus`)
455
+ - **Auto-ACK** — `on_message { |message| }` auto-acknowledges; use `|delivery, message|` for manual ACK/NACK
456
+ - **Reply correlation** — `send_reply(to:, content:, in_reply_to:)` tracks conversation threads
457
+ - **Outbox tracking** — sent messages tracked in `robot.outbox` with status and replies
458
+ - **Independent of Network** — bus communication works without a Network pipeline
459
+
460
+ ## Dynamic Robot Spawning
461
+
462
+ Robots can create new robots at runtime using `spawn`. The bus is created lazily — no upfront wiring required:
463
+
464
+ ```ruby
465
+ require "robot_lab"
466
+
467
+ class Dispatcher < RobotLab::Robot
468
+ attr_reader :spawned
469
+
470
+ def initialize(bus: nil)
471
+ super(name: "dispatcher", system_prompt: "Decide which specialist to create.", bus: bus)
472
+ @spawned = {}
473
+
474
+ on_message do |message|
475
+ puts "Got reply from #{message.from}: #{message.content.to_s.lines.first&.strip}"
476
+ end
477
+ end
478
+
479
+ def dispatch(question)
480
+ # Spawn a specialist (reuse if already spawned)
481
+ specialist = @spawned["helper"] ||= spawn(
482
+ name: "helper",
483
+ system_prompt: "You answer questions concisely."
484
+ )
485
+
486
+ # Have the specialist work and reply
487
+ answer = specialist.run(question).last_text_content.strip
488
+ specialist.send_message(to: :dispatcher, content: answer)
489
+ end
490
+ end
491
+
492
+ dispatcher = Dispatcher.new
493
+ dispatcher.dispatch("What is the capital of France?")
494
+ ```
495
+
496
+ Key features:
497
+
498
+ - **`spawn`** — creates a child robot on the same bus; creates a bus lazily if none exists
499
+ - **`with_bus`** — connect a robot to a bus after creation (`bot.with_bus(existing_bus)`)
500
+ - **Fan-out** — multiple robots with the same name all receive messages sent to that name
501
+ - **No setup required** — bus and channels are created automatically on first use
502
+
295
503
  ## Streaming
296
504
 
297
- Subscribe to real-time events during execution:
505
+ Use a `Streaming::Context` with a publish callback to receive real-time events:
298
506
 
299
507
  ```ruby
300
- result = robot.run(message: "Tell me a story") do |event|
508
+ handler = lambda do |event|
301
509
  case event[:event]
302
- when "text.delta"
510
+ when RobotLab::Streaming::Events::TEXT_DELTA
303
511
  print event[:data][:delta]
304
- when "run.completed"
512
+ when RobotLab::Streaming::Events::RUN_COMPLETED
305
513
  puts "\nDone!"
306
514
  end
307
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
+ )
308
523
  ```
309
524
 
310
525
  ## Rails Integration