robot_lab 0.3.0 → 0.5.0

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 (274) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/README.md +29 -19
  4. data/lib/robot_lab/errors.rb +4 -0
  5. data/lib/robot_lab/hook_context.rb +9 -2
  6. data/lib/robot_lab/mcp/registry.rb +99 -0
  7. data/lib/robot_lab/mcp/server.rb +47 -83
  8. data/lib/robot_lab/mcp/server_discovery.rb +22 -9
  9. data/lib/robot_lab/mcp.rb +259 -0
  10. data/lib/robot_lab/robot/mcp_management.rb +159 -105
  11. data/lib/robot_lab/robot/tool_call_gating.rb +144 -0
  12. data/lib/robot_lab/robot.rb +72 -99
  13. data/lib/robot_lab/run_config.rb +3 -2
  14. data/lib/robot_lab/tool.rb +8 -23
  15. data/lib/robot_lab/version.rb +1 -1
  16. data/lib/robot_lab.rb +1 -4
  17. metadata +16 -292
  18. data/.architecture/AGENTS.md +0 -32
  19. data/.architecture/config.yml +0 -8
  20. data/.architecture/members.yml +0 -60
  21. data/.architecture/reviews/feature-free-will.md +0 -490
  22. data/.architecture/reviews/overall-codebase.md +0 -427
  23. data/.claude/settings.local.json +0 -57
  24. data/.codex/config.toml +0 -2
  25. data/.envrc +0 -6
  26. data/.github/workflows/deploy-github-pages.yml +0 -52
  27. data/.irbrc +0 -6
  28. data/.loki +0 -65
  29. data/.quality/reek_baseline.txt +0 -43
  30. data/.rubocop.yml +0 -10
  31. data/Archspec.rb +0 -44
  32. data/CLAUDE.md +0 -141
  33. data/COMMITS.md +0 -196
  34. data/Rakefile +0 -193
  35. data/_typos.toml +0 -21
  36. data/agent2agent_review.md +0 -192
  37. data/agentf_improvements.md +0 -253
  38. data/agents.md +0 -14
  39. data/docs/api/core/index.md +0 -147
  40. data/docs/api/core/memory.md +0 -578
  41. data/docs/api/core/network.md +0 -553
  42. data/docs/api/core/result.md +0 -211
  43. data/docs/api/core/robot.md +0 -1667
  44. data/docs/api/core/state.md +0 -145
  45. data/docs/api/core/tool.md +0 -520
  46. data/docs/api/errors.md +0 -323
  47. data/docs/api/hooks.md +0 -469
  48. data/docs/api/index.md +0 -137
  49. data/docs/api/mcp/client.md +0 -387
  50. data/docs/api/mcp/index.md +0 -267
  51. data/docs/api/mcp/server.md +0 -252
  52. data/docs/api/mcp/transports.md +0 -339
  53. data/docs/api/messages/index.md +0 -126
  54. data/docs/api/messages/text-message.md +0 -170
  55. data/docs/api/messages/tool-call-message.md +0 -206
  56. data/docs/api/messages/tool-result-message.md +0 -242
  57. data/docs/api/messages/user-message.md +0 -232
  58. data/docs/api/skills.md +0 -423
  59. data/docs/api/streaming/context.md +0 -280
  60. data/docs/api/streaming/events.md +0 -185
  61. data/docs/api/streaming/index.md +0 -156
  62. data/docs/api/support.md +0 -846
  63. data/docs/architecture/core-concepts.md +0 -575
  64. data/docs/architecture/index.md +0 -252
  65. data/docs/architecture/message-flow.md +0 -366
  66. data/docs/architecture/network-orchestration.md +0 -478
  67. data/docs/architecture/robot-execution.md +0 -396
  68. data/docs/architecture/state-management.md +0 -410
  69. data/docs/assets/css/custom.css +0 -56
  70. data/docs/assets/images/robot_lab.jpg +0 -0
  71. data/docs/concepts.md +0 -641
  72. data/docs/examples/basic-chat.md +0 -258
  73. data/docs/examples/index.md +0 -425
  74. data/docs/examples/mcp-server.md +0 -403
  75. data/docs/examples/multi-robot-network.md +0 -476
  76. data/docs/examples/tool-usage.md +0 -468
  77. data/docs/getting-started/configuration.md +0 -645
  78. data/docs/getting-started/index.md +0 -56
  79. data/docs/getting-started/installation.md +0 -197
  80. data/docs/getting-started/quick-start.md +0 -293
  81. data/docs/guides/building-robots.md +0 -1187
  82. data/docs/guides/creating-networks.md +0 -736
  83. data/docs/guides/hooks.md +0 -1206
  84. data/docs/guides/index.md +0 -78
  85. data/docs/guides/knowledge.md +0 -219
  86. data/docs/guides/mcp-integration.md +0 -740
  87. data/docs/guides/memory.md +0 -458
  88. data/docs/guides/observability.md +0 -733
  89. data/docs/guides/streaming.md +0 -323
  90. data/docs/guides/using-tools.md +0 -780
  91. data/docs/index.md +0 -280
  92. data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
  93. data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
  94. data/examples/.envrc +0 -3
  95. data/examples/01_simple_robot.rb +0 -31
  96. data/examples/02_tools.rb +0 -109
  97. data/examples/03_network.rb +0 -118
  98. data/examples/04_mcp.rb +0 -217
  99. data/examples/05_streaming.rb +0 -137
  100. data/examples/06_prompt_templates.rb +0 -325
  101. data/examples/07_network_memory.rb +0 -313
  102. data/examples/08_llm_config.rb +0 -162
  103. data/examples/09_chaining.rb +0 -251
  104. data/examples/10_memory.rb +0 -315
  105. data/examples/11_network_introspection.rb +0 -239
  106. data/examples/12_message_bus.rb +0 -67
  107. data/examples/13_spawn.rb +0 -85
  108. data/examples/14_rusty_circuit/.envrc +0 -1
  109. data/examples/14_rusty_circuit/.gitignore +0 -1
  110. data/examples/14_rusty_circuit/README.md +0 -1
  111. data/examples/14_rusty_circuit/comic.rb +0 -149
  112. data/examples/14_rusty_circuit/display.rb +0 -217
  113. data/examples/14_rusty_circuit/heckler.rb +0 -65
  114. data/examples/14_rusty_circuit/open_mic.rb +0 -132
  115. data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
  116. data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
  117. data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
  118. data/examples/14_rusty_circuit/scout.rb +0 -165
  119. data/examples/14_rusty_circuit/show.log +0 -234
  120. data/examples/15_memory_network_and_bus/.envrc +0 -1
  121. data/examples/15_memory_network_and_bus/README.md +0 -66
  122. data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
  123. data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
  124. data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
  125. data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
  126. data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
  127. data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
  128. data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
  129. data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
  130. data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
  131. data/examples/15_memory_network_and_bus/output/memory.json +0 -13
  132. data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
  133. data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
  134. data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
  135. data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
  136. data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
  137. data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
  138. data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
  139. data/examples/16_writers_room/.envrc +0 -1
  140. data/examples/16_writers_room/display.rb +0 -158
  141. data/examples/16_writers_room/output/.gitignore +0 -4
  142. data/examples/16_writers_room/output/README.md +0 -69
  143. data/examples/16_writers_room/output/opus_001.md +0 -263
  144. data/examples/16_writers_room/output/opus_001_notes.log +0 -470
  145. data/examples/16_writers_room/output/opus_002.md +0 -245
  146. data/examples/16_writers_room/output/opus_002_notes.log +0 -546
  147. data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
  148. data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
  149. data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
  150. data/examples/16_writers_room/prompts/writer.md +0 -37
  151. data/examples/16_writers_room/room.rb +0 -186
  152. data/examples/16_writers_room/tools.rb +0 -173
  153. data/examples/16_writers_room/writer.rb +0 -121
  154. data/examples/16_writers_room/writers_room.rb +0 -256
  155. data/examples/17_skills.rb +0 -234
  156. data/examples/18_rails/.envrc +0 -3
  157. data/examples/18_rails/.gitignore +0 -5
  158. data/examples/18_rails/Gemfile +0 -11
  159. data/examples/18_rails/README.md +0 -67
  160. data/examples/18_rails/Rakefile +0 -4
  161. data/examples/18_rails/app/controllers/application_controller.rb +0 -4
  162. data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
  163. data/examples/18_rails/app/jobs/application_job.rb +0 -4
  164. data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
  165. data/examples/18_rails/app/models/application_record.rb +0 -5
  166. data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
  167. data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
  168. data/examples/18_rails/app/robots/chat_robot.rb +0 -25
  169. data/examples/18_rails/app/tools/time_tool.rb +0 -9
  170. data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
  171. data/examples/18_rails/app/views/chat/index.html.erb +0 -67
  172. data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
  173. data/examples/18_rails/bin/dev +0 -7
  174. data/examples/18_rails/bin/rails +0 -6
  175. data/examples/18_rails/bin/setup +0 -15
  176. data/examples/18_rails/config/application.rb +0 -33
  177. data/examples/18_rails/config/cable.yml +0 -2
  178. data/examples/18_rails/config/database.yml +0 -5
  179. data/examples/18_rails/config/environment.rb +0 -4
  180. data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
  181. data/examples/18_rails/config/routes.rb +0 -6
  182. data/examples/18_rails/config.ru +0 -4
  183. data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
  184. data/examples/19_token_tracking.rb +0 -138
  185. data/examples/20_circuit_breaker.rb +0 -147
  186. data/examples/21_learning_loop.rb +0 -181
  187. data/examples/22_context_compression.rb +0 -172
  188. data/examples/23_convergence.rb +0 -133
  189. data/examples/24_structured_delegation.rb +0 -151
  190. data/examples/25_history_search/conversation.jsonl +0 -30
  191. data/examples/25_history_search.rb +0 -133
  192. data/examples/26_document_store/api_versioning_adr.md +0 -52
  193. data/examples/26_document_store/incident_postmortem.md +0 -46
  194. data/examples/26_document_store/postgres_runbook.md +0 -49
  195. data/examples/26_document_store/redis_caching_guide.md +0 -48
  196. data/examples/26_document_store/sidekiq_guide.md +0 -51
  197. data/examples/26_document_store.rb +0 -140
  198. data/examples/27_incident_response/README.md +0 -65
  199. data/examples/27_incident_response/incident_response.rb +0 -261
  200. data/examples/28_mcp_discovery.rb +0 -113
  201. data/examples/29_ractor_tools.rb +0 -240
  202. data/examples/30_ractor_network.rb +0 -252
  203. data/examples/31_launch_assessment.rb +0 -246
  204. data/examples/32_newsletter_reader.rb +0 -193
  205. data/examples/33_stock_generator.rb +0 -80
  206. data/examples/33_stock_predictor.rb +0 -327
  207. data/examples/34_agentskills.rb +0 -76
  208. data/examples/35_hooks.rb +0 -266
  209. data/examples/README.md +0 -550
  210. data/examples/common.rb +0 -180
  211. data/examples/prompts/assistant.md +0 -5
  212. data/examples/prompts/audit_trail.md +0 -8
  213. data/examples/prompts/billing.md +0 -10
  214. data/examples/prompts/classifier.md +0 -7
  215. data/examples/prompts/comedian.md +0 -6
  216. data/examples/prompts/comedy_critic.md +0 -10
  217. data/examples/prompts/configurable.md +0 -9
  218. data/examples/prompts/dispatcher.md +0 -12
  219. data/examples/prompts/entity_extractor.md +0 -14
  220. data/examples/prompts/escalation.md +0 -42
  221. data/examples/prompts/frontmatter_mcp_test.md +0 -9
  222. data/examples/prompts/frontmatter_named_test.md +0 -5
  223. data/examples/prompts/frontmatter_tools_test.md +0 -6
  224. data/examples/prompts/general.md +0 -7
  225. data/examples/prompts/github_assistant.md +0 -14
  226. data/examples/prompts/helper.md +0 -4
  227. data/examples/prompts/incident_responder.md +0 -18
  228. data/examples/prompts/keyword_extractor.md +0 -11
  229. data/examples/prompts/llm_config_demo.md +0 -20
  230. data/examples/prompts/order_support.md +0 -35
  231. data/examples/prompts/os_advocate.md +0 -13
  232. data/examples/prompts/os_chief.md +0 -13
  233. data/examples/prompts/os_editor.md +0 -13
  234. data/examples/prompts/parameterized_main_test.md +0 -6
  235. data/examples/prompts/pii_redactor.md +0 -7
  236. data/examples/prompts/product_support.md +0 -37
  237. data/examples/prompts/runbook_protocol.md +0 -12
  238. data/examples/prompts/sentiment_analyzer.md +0 -12
  239. data/examples/prompts/skill_a_test.md +0 -4
  240. data/examples/prompts/skill_b_test.md +0 -4
  241. data/examples/prompts/skill_config_test.md +0 -5
  242. data/examples/prompts/skill_cycle_a_test.md +0 -6
  243. data/examples/prompts/skill_cycle_b_test.md +0 -6
  244. data/examples/prompts/skill_description_test.md +0 -4
  245. data/examples/prompts/skill_leaf_test.md +0 -4
  246. data/examples/prompts/skill_nested_test.md +0 -6
  247. data/examples/prompts/skill_refs_main_test.md +0 -6
  248. data/examples/prompts/skill_self_ref_test.md +0 -6
  249. data/examples/prompts/skill_with_mcp_test.md +0 -9
  250. data/examples/prompts/skill_with_params_test.md +0 -6
  251. data/examples/prompts/skill_with_robot_name_test.md +0 -5
  252. data/examples/prompts/skill_with_tools_test.md +0 -6
  253. data/examples/prompts/sre_compliance.md +0 -10
  254. data/examples/prompts/structured_output.md +0 -7
  255. data/examples/prompts/synthesizer.md +0 -17
  256. data/examples/prompts/technical.md +0 -10
  257. data/examples/prompts/template_with_skills_test.md +0 -6
  258. data/examples/prompts/triage.md +0 -22
  259. data/examples/ruboruby.md +0 -423
  260. data/examples/run_all.rb +0 -60
  261. data/examples/xyzzy.rb +0 -97
  262. data/lib/robot_lab/mcp/client.rb +0 -223
  263. data/lib/robot_lab/mcp/connection_poller.rb +0 -195
  264. data/lib/robot_lab/mcp/transports/base.rb +0 -64
  265. data/lib/robot_lab/mcp/transports/sse.rb +0 -120
  266. data/lib/robot_lab/mcp/transports/stdio.rb +0 -170
  267. data/lib/robot_lab/mcp/transports/streamable_http.rb +0 -141
  268. data/lib/robot_lab/mcp/transports/websocket.rb +0 -111
  269. data/logfile +0 -8
  270. data/mkdocs.yml +0 -209
  271. data/robot_concurrency.md +0 -38
  272. data/simple_acp_review.md +0 -298
  273. data/temp.md +0 -6
  274. data/tool_manifest_plan.md +0 -155
@@ -1,252 +0,0 @@
1
- # Architecture Overview
2
-
3
- RobotLab is designed around a few core architectural principles that enable flexible, composable AI workflows.
4
-
5
- ## Design Philosophy
6
-
7
- ### 1. Separation of Concerns
8
-
9
- Each component has a single, well-defined responsibility:
10
-
11
- - **Robot**: LLM-powered agent (subclass of `RubyLLM::Agent`) with personality, tools, and memory
12
- - **Network**: Orchestrates robot execution as a DAG pipeline via SimpleFlow
13
- - **Memory**: Reactive key-value store for robot and network data
14
- - **Tool**: Provides external capabilities to robots (inherits from `RubyLLM::Tool`)
15
- - **Task**: Wraps a robot for pipeline execution with per-task configuration
16
-
17
- ### 2. Composability
18
-
19
- Components are designed to be mixed and matched:
20
-
21
- - Robots can be used standalone or within networks
22
- - Tools can be shared across robots or scoped per-robot via `local_tools:`
23
- - Networks define DAG pipelines with sequential, parallel, and optional execution
24
- - Memory can be standalone (per-robot) or shared (per-network)
25
- - `with_*` methods return `self` for fluent chaining
26
-
27
- ### 3. Provider Agnostic
28
-
29
- RobotLab abstracts away LLM provider differences through RubyLLM:
30
-
31
- - Unified interface across Anthropic, OpenAI, Gemini, DeepSeek, Mistral, and others
32
- - Consistent tool calling interface
33
- - Automatic provider detection from model names
34
- - Easy switching between providers via configuration
35
-
36
- ## System Architecture
37
-
38
- ```mermaid
39
- graph TB
40
- subgraph "Application Layer"
41
- A[Your Application]
42
- end
43
-
44
- subgraph "RobotLab Core"
45
- B[Network]
46
- C[Task]
47
- D[Robot < RubyLLM::Agent]
48
- E[Memory]
49
- F[RobotResult]
50
- end
51
-
52
- subgraph "Configuration"
53
- G[Config < MywayConfig::Base]
54
- R[RunConfig]
55
- end
56
-
57
- subgraph "Cross-Cutting"
58
- HK[Hooks<br/>HookRegistry x3]
59
- end
60
-
61
- subgraph "Integration Layer"
62
- H[MCP Client]
63
- I[Tools &lt; RubyLLM::Tool]
64
- J[Templates / prompt_manager]
65
- SK[AgentSkills<br/>+ optional Sandbox]
66
- end
67
-
68
- subgraph "Execution Layer"
69
- K[SimpleFlow::Pipeline]
70
- L[RubyLLM Chat]
71
- TB[TypedBus + BusPoller]
72
- end
73
-
74
- subgraph "Provider Layer"
75
- M[Anthropic]
76
- N[OpenAI]
77
- O[Gemini]
78
- P[MCP Servers]
79
- end
80
-
81
- A --> B
82
- A --> D
83
- B --> C
84
- C --> D
85
- B --> K
86
- B --> E
87
- B --> TB
88
- D --> E
89
- D --> L
90
- D --> TB
91
- D --> H
92
- D --> I
93
- D --> J
94
- D --> SK
95
- D --> F
96
- SK --> I
97
- G --> R
98
- R --> B
99
- R --> C
100
- R --> D
101
- G --> D
102
- G --> L
103
- HK -.wraps.-> B
104
- HK -.wraps.-> C
105
- HK -.wraps.-> D
106
- HK -.wraps.-> I
107
- L --> M
108
- L --> N
109
- L --> O
110
- H --> P
111
- ```
112
-
113
- The dotted `wraps` edges are the hook system: `Hooks.run` brackets every network
114
- run, task, robot run, LLM generation, tool call, compaction, and `learn` call. It
115
- is the framework's extension seam — the extension gems (`robot_lab-audit`,
116
- `robot_lab-durable`, …) attach here rather than subclassing core objects. See the
117
- [Hooks API](../api/hooks.md).
118
-
119
- ### Robot subsystems
120
-
121
- A `Robot` composes several small collaborators, each documented on the
122
- [Support](../api/support.md) and [Skills](../api/skills.md) API pages:
123
-
124
- ```mermaid
125
- graph LR
126
- D[Robot]
127
-
128
- D --> BM[BusMessaging<br/>RobotMessage envelopes]
129
- BM --> BP[BusPoller<br/>per-robot serialization]
130
- D --> BD[Budget::Ledger<br/>token_budget / cost_budget]
131
- D --> DL[DoomLoopDetector<br/>always installed per run]
132
- D --> HC[HistoryCompressor<br/>compress_history / auto_compact]
133
- D --> DF[DelegationFuture<br/>delegate async: true]
134
- D --> HS[HistorySearch<br/>search_history]
135
- D --> ASM[AgentSkillMatching<br/>prepended around run]
136
-
137
- ASM --> AS[AgentSkill<br/>SKILL.md bundle]
138
- AS --> CAT[AgentSkillCatalog]
139
- AS --> CAP[Capabilities]
140
- AS --> ST[ScriptTool]
141
- ST --> EX{ScriptTool.executor}
142
- EX -. "installed by<br/>robot_lab-sandbox" .-> SB[Sandbox<br/>Seatbelt or Null]
143
- CAP -. "used if installed" .-> SB
144
-
145
- HC --> TA[TextAnalysis<br/>TF / TF-IDF]
146
- HS --> TA
147
- CV[Convergence] --> TA
148
- SD[MCP::ServerDiscovery] --> TA
149
- ```
150
-
151
- `TextAnalysis` is the shared floor under every similarity feature — history
152
- compression, history search, convergence detection, and MCP server discovery all
153
- route through it, which is why they share the one optional `classifier`
154
- dependency.
155
-
156
- ## Core Components
157
-
158
- | Component | Description | Documentation |
159
- |-----------|-------------|---------------|
160
- | **Robot** | LLM agent (subclass of `RubyLLM::Agent`) with template-based prompts, tools, and memory | [Core Concepts](core-concepts.md) |
161
- | **Network** | Orchestrates multiple robots as a SimpleFlow pipeline | [Network Orchestration](network-orchestration.md) |
162
- | **Memory** | Reactive key-value store with pub/sub and blocking reads | [Memory Management](state-management.md) |
163
- | **Task** | Wraps a robot for pipeline execution with per-task config | [Network Orchestration](network-orchestration.md) |
164
- | **RobotResult** | Captures LLM output, tool calls, and metadata from a run | [Message Flow](message-flow.md) |
165
- | **RunConfig** | Per-run settings object (LLM fields, `mcp`/`tools`, callbacks, infrastructure) that cascades global → network → task → robot | [Core Concepts](core-concepts.md) |
166
- | **Config** | MywayConfig-based global configuration with env var and file support | [Configuration](#configuration) |
167
- | **Hook** | Handler base class for the seven hook families — the framework's extension seam | [Hooks API](../api/hooks.md) |
168
- | **AgentSkill** | A `SKILL.md` bundle whose instructions and `scripts/` become prompt text and tools | [Skills API](../api/skills.md) |
169
- | **Sandbox** | Opt-in OS-level confinement (macOS Seatbelt) for skill scripts, derived from `Capabilities`; ships in the optional `robot_lab-sandbox` gem, not core | [Skills API](../api/skills.md) |
170
- | **RobotMessage** | Immutable envelope for TypedBus inter-robot messaging, serialized per robot by `BusPoller` | [Support API](../api/support.md) |
171
- | **Budget::Ledger** | Thread-safe reserve/reconcile ledger behind `token_budget` / `cost_budget` | [Support API](../api/support.md) |
172
-
173
- ## Configuration
174
-
175
- Global configuration is a MywayConfig subclass (`Config < MywayConfig::Base`). It is loaded from multiple sources in priority order:
176
-
177
- 1. **Bundled defaults** (`lib/robot_lab/config/defaults.yml`)
178
- 2. **Environment overrides** (development, test, production)
179
- 3. **XDG user config** (`~/.config/robot_lab/robot_lab.yml` — the filename repeats the app name; `config.yml` is never read)
180
- 4. **Project config** (`./config/robot_lab.yml` — the only file with an ERB pass)
181
- 5. **Environment variables** (`ROBOT_LAB_*` prefix, double underscore for nesting)
182
- 6. **Constructor params**
183
-
184
- Top-level wrappers behave differently per file. In the **XDG user config**, a section named for the current environment is honoured — the loader looks for `parsed.key?(env)` (`Anyway::Settings.current_environment`, else `RAILS_ENV`, else `RACK_ENV`, else `"development"`) and falls back to the root when absent — so both `development:` and a flat file work there. A `defaults:` wrapper is not an environment name, so it is ignored. The **project config** must be flat outside Rails (all wrappers ignored); under Rails, `anyway_config` sets `current_environment` to `Rails.env` and the project file becomes environmental, so a flat file is ignored and keys must sit under `development:` / `test:` / `production:`. See [Core Concepts](core-concepts.md#configuration) for details.
185
-
186
- ```ruby
187
- # Access configuration
188
- RobotLab.config.ruby_llm.model #=> "claude-sonnet-4"
189
- RobotLab.config.ruby_llm.request_timeout #=> 120
190
-
191
- # Block form (yields the same Config object)
192
- RobotLab.configure do |c|
193
- c.logger = Logger.new($stdout)
194
- end
195
- ```
196
-
197
- ## Data Flow
198
-
199
- 1. **Input**: User calls `robot.run("message")` or `network.run(message: "...")`
200
- 2. **Memory**: Robot resolves active memory (standalone or network-shared)
201
- 3. **MCP**: Robot resolves MCP servers from hierarchical config and connects clients. `run` defaults to `mcp: :none`, so a plain `run` connects nothing
202
- 4. **Tools**: Robot resolves and filters tools from hierarchical config. `run` defaults to `tools: :none`, so a plain `run` sends the LLM zero tools; pass `tools: :inherit` to send the robot's attached tools
203
- 5. **Execution**: Robot delegates to `Agent#ask` which calls `@chat.ask` on RubyLLM
204
- 6. **Tool Loop**: LLM may invoke tools; RubyLLM handles the tool call/result loop
205
- 7. **Result**: Robot builds and returns a `RobotResult`
206
- 8. **Network**: If in a network, result flows to dependent tasks via SimpleFlow
207
-
208
- ## Key Patterns
209
-
210
- ### Factory Methods
211
-
212
- Robots and networks are created via factory methods on the `RobotLab` module:
213
-
214
- ```ruby
215
- robot = RobotLab.build(
216
- name: "assistant",
217
- template: :assistant,
218
- context: { tone: "friendly" }
219
- )
220
-
221
- network = RobotLab.create_network(name: "pipeline") do
222
- task :analyst, analyst_robot, depends_on: :none
223
- task :writer, writer_robot, depends_on: [:analyst]
224
- end
225
- ```
226
-
227
- ### Fluent Chaining
228
-
229
- `with_*` methods on Robot delegate to the underlying `@chat` and return `self`:
230
-
231
- ```ruby
232
- robot = RobotLab.build(name: "bot")
233
- .with_instructions("Be concise.")
234
- .with_temperature(0.3)
235
- .with_model("gpt-4o")
236
- ```
237
-
238
- ### Hierarchical Configuration
239
-
240
- Tools and MCP servers use hierarchical resolution: `run()/task > robot build-time > network > global config`. Values can be `:none`, `:inherit`, or an explicit array (which filters the already-attached tools by name — entries must match how the tool was attached, class or instance). `run()` defaults both to `:none`, so the runtime level is an explicit "send nothing" unless you override it.
241
-
242
- ### SimpleFlow Pipeline
243
-
244
- Networks are thin wrappers around `SimpleFlow::Pipeline`. Each robot is wrapped in a `Task` that implements the `call(result)` interface. Tasks define dependencies (`:none`, `[:task_names]`, or `:optional`) to control execution order.
245
-
246
- ## Next Steps
247
-
248
- - [Core Concepts](core-concepts.md) - Deep dive into robots and tools
249
- - [Robot Execution](robot-execution.md) - How robots process messages
250
- - [Network Orchestration](network-orchestration.md) - Multi-robot workflows
251
- - [Memory Management](state-management.md) - Managing memory and reactive features
252
- - [Message Flow](message-flow.md) - How messages move through the system
@@ -1,366 +0,0 @@
1
- # Message Flow
2
-
3
- This page explains how messages move through RobotLab, from user input to LLM response.
4
-
5
- ## Message Types
6
-
7
- `RobotLab::Message` is the abstract base for three conversation message classes. `ToolMessage` is a plain value object that they reference, and two further message classes — `UserMessage` and `RobotMessage` — live outside the hierarchy entirely:
8
-
9
- ```mermaid
10
- classDiagram
11
- class Message {
12
- <<abstract>>
13
- +type: String
14
- +role: String
15
- +content
16
- +stop_reason: String
17
- +text?() bool
18
- +tool_call?() bool
19
- +tool_result?() bool
20
- +system?() bool
21
- +user?() bool
22
- +assistant?() bool
23
- +stopped?() bool
24
- +tool_stop?() bool
25
- +to_h() Hash
26
- }
27
-
28
- class TextMessage {
29
- +content: String
30
- }
31
-
32
- class ToolMessage {
33
- <<PORO>>
34
- +id: String
35
- +name: String
36
- +input: Hash
37
- +to_h() Hash
38
- +to_json() String
39
- }
40
-
41
- class ToolCallMessage {
42
- +tools: Array~ToolMessage~
43
- }
44
-
45
- class ToolResultMessage {
46
- +tool: ToolMessage
47
- +content: Hash
48
- +success?() bool
49
- +error?() bool
50
- +data()
51
- +error()
52
- }
53
-
54
- Message <|-- TextMessage
55
- Message <|-- ToolCallMessage
56
- Message <|-- ToolResultMessage
57
- ToolMessage -- ToolCallMessage
58
- ToolMessage -- ToolResultMessage
59
- ```
60
-
61
- `ToolMessage` does **not** inherit from `Message` — its superclass is `Object`. It has no `type`, `role`, or predicate methods; only `id`, `name`, `input`, `to_h`, and `to_json`. The `tool_call?` predicate lives on `Message`, so it answers for `ToolCallMessage`, not for `ToolMessage`.
62
-
63
- The two classes outside the hierarchy:
64
-
65
- | Class | Role |
66
- |-------|------|
67
- | `UserMessage` | Envelope for user input carrying `session_id`, an extra `system_prompt`, `metadata`, `id`, `created_at`. `UserMessage.from` normalizes a String/Hash/TextMessage; `#to_message` converts to a `TextMessage` |
68
- | `RobotMessage` | Immutable `Data.define(:id, :from, :content, :in_reply_to)` envelope for robot-to-robot traffic on the message bus, with `#key` (`"from:id"`) and `#reply?` |
69
-
70
- ### TextMessage
71
-
72
- Regular text content from users or assistants:
73
-
74
- ```ruby
75
- TextMessage.new(
76
- role: "user",
77
- content: "What's the weather in Paris?"
78
- )
79
-
80
- TextMessage.new(
81
- role: "assistant",
82
- content: "The weather in Paris is sunny and 22 degrees C.",
83
- stop_reason: "stop"
84
- )
85
- ```
86
-
87
- ### ToolMessage
88
-
89
- A standalone value object (not a `Message`) representing a tool invocation and its parameters:
90
-
91
- ```ruby
92
- tool = ToolMessage.new(
93
- id: "tool_123",
94
- name: "get_weather",
95
- input: { location: "Paris" }
96
- )
97
-
98
- tool.to_h #=> { type: "tool", id: "tool_123", name: "get_weather", input: { location: "Paris" } }
99
- ```
100
-
101
- Note that the `type: "tool"` key appears only in `to_h`; there is no `type` reader and no predicate methods.
102
-
103
- ### ToolCallMessage
104
-
105
- LLM's request to execute one or more tools:
106
-
107
- ```ruby
108
- ToolCallMessage.new(
109
- role: "assistant",
110
- tools: [
111
- ToolMessage.new(id: "call_1", name: "get_weather", input: { location: "Paris" })
112
- ],
113
- stop_reason: "tool"
114
- )
115
- ```
116
-
117
- The signature is `initialize(role:, tools:, stop_reason: nil)` — `role` and
118
- `tools` are required and there is **no** `content:` keyword. Passing one raises
119
- `ArgumentError`.
120
-
121
- ### ToolResultMessage
122
-
123
- Result from tool execution:
124
-
125
- ```ruby
126
- ToolResultMessage.new(
127
- tool: tool_message,
128
- content: { data: { temp: 22, condition: "sunny" } }
129
- )
130
- ```
131
-
132
- ## Message Flow: Standalone Robot
133
-
134
- The primary execution path is `robot.run("message")`:
135
-
136
- ```mermaid
137
- sequenceDiagram
138
- participant User
139
- participant Robot
140
- participant Memory
141
- participant MCP
142
- participant Tools
143
- participant Agent
144
- participant Chat
145
- participant LLM
146
-
147
- User->>Robot: robot.run("message")
148
- Robot->>Memory: resolve_active_memory
149
- Memory-->>Robot: active memory
150
-
151
- Robot->>MCP: resolve_mcp_hierarchy
152
- MCP-->>Robot: resolved MCP config
153
- Robot->>Robot: ensure_mcp_clients
154
-
155
- Robot->>Tools: resolve_tools_hierarchy
156
- Tools-->>Robot: filtered + capped tools
157
- Robot->>Chat: @chat.with_tools(*filtered, replace: true)
158
-
159
- Robot->>Agent: ask("message")
160
- Agent->>Chat: @chat.ask("message")
161
- Chat->>LLM: Provider API call
162
-
163
- loop Tool Loop (handled by RubyLLM)
164
- LLM-->>Chat: Tool call response
165
- Chat->>Tools: Execute tool
166
- Tools-->>Chat: Tool result
167
- Chat->>LLM: Send tool result
168
- end
169
-
170
- LLM-->>Chat: Final response
171
- Chat-->>Agent: RubyLLM::Response
172
- Agent-->>Robot: response
173
-
174
- Robot->>Robot: build_result(response, memory)
175
- Robot-->>User: RobotResult
176
- ```
177
-
178
- ### Step-by-Step
179
-
180
- 1. **`robot.run("message")`**: Entry point. Accepts a positional string argument.
181
-
182
- 2. **Resolve Memory**: Determines which memory to use:
183
- - `network_memory` if provided (network execution)
184
- - `network.memory` if in a network context
185
- - `robot.memory` (standalone, the default)
186
-
187
- 3. **Merge Runtime Memory**: If a `memory:` keyword argument is passed, it is merged into the active memory.
188
-
189
- 4. **Set Current Writer**: Sets `memory.current_writer = robot.name` so subscription callbacks know which robot wrote a value.
190
-
191
- 5. **Resolve MCP Hierarchy**: Resolves MCP server configuration through the hierarchy: `runtime (run/task) > robot build-time > network > global config`. `run` defaults `mcp:` to `:none`, so nothing is connected unless you pass `mcp: :inherit` or an explicit list.
192
-
193
- 6. **Ensure MCP Clients**: Initializes or updates MCP client connections and discovers tools from them. Connection failures are logged and recorded in `failed_mcp_server_names`, never raised.
194
-
195
- 7. **Resolve Tools Hierarchy**: Resolves which tools are available through the same hierarchy. `run` defaults `tools:` to `:none`, which means "send zero tools this turn" — pass `tools: :inherit` to send the robot's attached tools.
196
-
197
- 8. **Filter Tools**: Filters by the resolved allowlist, clamps to `max_tools` (128 by default), and applies the set with `@chat.with_tools(*filtered, replace: true)` so the persistent chat holds exactly this turn's tools.
198
-
199
- 9. **Agent#ask**: Delegates to the parent class `RubyLLM::Agent#ask`, which calls `@chat.ask(message)`.
200
-
201
- 10. **LLM Interaction**: RubyLLM handles the provider-specific API call, including the tool call/result loop.
202
-
203
- 11. **Build Result**: Wraps the LLM response in a `RobotResult` containing output messages, tool calls, and metadata.
204
-
205
- 12. **Return**: Returns the `RobotResult` to the caller.
206
-
207
- ## Message Flow: Network Execution
208
-
209
- When running through a network, the flow adds pipeline orchestration:
210
-
211
- ```mermaid
212
- sequenceDiagram
213
- participant User
214
- participant Network
215
- participant Pipeline
216
- participant Task
217
- participant Robot
218
- participant LLM
219
-
220
- User->>Network: network.run(message: "...")
221
- Network->>Network: Inject network_memory into run_context
222
- Network->>Pipeline: SimpleFlow::Pipeline.call_parallel(initial_result)
223
-
224
- loop For each ready task
225
- Pipeline->>Task: task.call(result)
226
- Task->>Task: Deep merge task context with run_params
227
- Task->>Robot: robot.call(enhanced_result)
228
- Robot->>Robot: extract_run_context(result)
229
- Robot->>Robot: run(message, network_memory: ...)
230
- Robot->>LLM: Agent#ask -> @chat.ask
231
- LLM-->>Robot: Response
232
- Robot-->>Task: result.with_context(:name, robot_result).continue(robot_result)
233
- Task-->>Pipeline: SimpleFlow::Result
234
- end
235
-
236
- Pipeline-->>Network: Final SimpleFlow::Result
237
- Network-->>User: result
238
- ```
239
-
240
- ### Key Points
241
-
242
- - **Network creates initial result**: `SimpleFlow::Result.new(run_context, context: { run_params: run_context })`, after injecting `network_memory`, `network`, and (when non-empty) `network_config` into the run context, all wrapped in the `:network_run` hook
243
- - **Task wraps robot**: Each `Task` runs the `:task` hook and deep-merges its own context, `mcp`, `tools`, `memory`, and `config` into the run params before delegating to the robot
244
- - **Robot extracts context**: `extract_run_context(result)` pulls the message, MCP, tools, memory, network, and task out of the SimpleFlow result and passes them to `run` as keyword arguments
245
- - **Shared memory**: All robots use `network.memory` during network execution
246
- - **Result accumulation**: Each robot stores its `RobotResult` in `result.context[:robot_name]` — the key comes from `@name.to_sym` in `Robot#call`, so it is the **robot's** name, not the task name. They match only when the two are spelled the same way
247
-
248
- ## RobotResult
249
-
250
- The return value of `robot.run("message")`:
251
-
252
- ```ruby
253
- result = robot.run("What is Ruby?")
254
-
255
- result.last_text_content #=> "Ruby is a dynamic programming language..."
256
- result.reply #=> alias for last_text_content
257
- result.has_tool_calls? #=> false
258
- result.robot_name #=> "assistant"
259
- result.output #=> [TextMessage(role: "assistant", content: "...")]
260
- result.tool_calls #=> []
261
- result.stop_reason #=> nil (always — see below)
262
- result.created_at #=> Time
263
- result.id #=> "uuid"
264
- result.checksum #=> "sha256-hex"
265
- result.input_tokens #=> 42
266
- result.output_tokens #=> 128
267
- result.duration #=> Float, nil
268
- ```
269
-
270
- `stop_reason` is always `nil` on a `Robot#run` result: `build_result` reads it as `response.respond_to?(:stop_reason) ? response.stop_reason : nil`, and `RubyLLM::Message` does not define the method. It is consequently dropped from `export`, and `result.stopped?` reduces to `!result.has_tool_calls?`.
271
-
272
- `output` always holds exactly one synthesized `TextMessage` built from the final response text — it is not a transcript of the turn, and it never contains `ToolCallMessage` or `ToolResultMessage` entries. `tool_calls` is read off the final assistant message, which carries no tool calls once RubyLLM's tool loop has finished, so it is effectively always empty. To observe tool activity, use the `on_tool_call` / `on_tool_result` callbacks or the tool hooks.
273
-
274
- ### Result Serialization
275
-
276
- ```ruby
277
- # Export for persistence (excludes debug fields)
278
- hash = result.export
279
-
280
- # Full hash including debug fields
281
- hash = result.to_h
282
-
283
- # JSON
284
- json = result.to_json
285
-
286
- # Reconstruct from hash
287
- result = RobotResult.from_hash(hash)
288
- ```
289
-
290
- ## Message Predicates
291
-
292
- These are defined on `Message`, so every `TextMessage`, `ToolCallMessage`, and `ToolResultMessage` responds to all of them. `ToolMessage` responds to none of them:
293
-
294
- ```ruby
295
- message.text? # Is it a TextMessage?
296
- message.tool_call? # Is it a ToolCallMessage?
297
- message.tool_result? # Is it a ToolResultMessage?
298
-
299
- message.user? # Is role "user"?
300
- message.assistant? # Is role "assistant"?
301
- message.system? # Is role "system"?
302
-
303
- message.stopped? # Is stop_reason "stop"?
304
- message.tool_stop? # Is stop_reason "tool"?
305
- ```
306
-
307
- Valid values are constrained at construction: `type` must be one of `text`, `tool_call`, `tool_result`; `role` one of `system`, `user`, `assistant`, `tool_result`; `stop_reason` one of `tool`, `stop`. Anything else raises `ArgumentError`.
308
-
309
- ## Creating Messages
310
-
311
- ### From Strings
312
-
313
- ```ruby
314
- TextMessage.new(role: "user", content: "Hello")
315
- ```
316
-
317
- ### From Hashes
318
-
319
- ```ruby
320
- Message.from_hash(
321
- type: "text",
322
- role: "user",
323
- content: "Hello"
324
- )
325
- ```
326
-
327
- ## Serialization
328
-
329
- Messages can be serialized:
330
-
331
- ```ruby
332
- # To hash
333
- hash = message.to_h
334
- #=> { type: "text", role: "user", content: "Hello" }
335
-
336
- # To JSON
337
- json = message.to_json
338
-
339
- # From hash
340
- message = Message.from_hash(hash)
341
- ```
342
-
343
- ## Template Resolution
344
-
345
- When a robot has a template, it is resolved at build time via prompt_manager:
346
-
347
- ```ruby
348
- robot = RobotLab.build(
349
- name: "helper",
350
- template: :helper,
351
- context: { tone: "friendly" }
352
- )
353
- ```
354
-
355
- The template resolution process:
356
- 1. `PM.parse(:helper)` loads the template file from the configured prompts directory
357
- 2. Non-LLM front matter (`robot_name`, `description`, `tools`, `mcp`, `skills`) is applied to the robot, filling in only what the constructor did not provide
358
- 3. The LLM front-matter keys become a `RunConfig` that the robot's own `@config` merges over — front matter is the base, constructor kwargs win — and `apply_to` dispatches `chat.with_<field>` for each field the chat supports. In practice only `model` and `temperature` reach the chat this way; `top_p`, `top_k`, `max_tokens`, `presence_penalty`, `frequency_penalty`, and `stop` are parsed and silently dropped because `RubyLLM::Chat` defines no `with_` method for them. Set those via constructor kwargs instead
359
- 4. The template body is rendered with ERB (`<%= var %>`; `{{ var }}` is not interpolated) and set as system instructions via `@chat.with_instructions(rendered)`
360
-
361
- If both `template:` and `system_prompt:` are provided, the system prompt is appended to the rendered template, producing one combined system message — and it is re-appended on every template re-render, so supplying run-time context never silently drops it.
362
-
363
- ## Next Steps
364
-
365
- - [Memory Management](state-management.md) - How memory stores conversation data
366
- - [Network Orchestration](network-orchestration.md) - Multi-robot pipeline execution