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,247 +0,0 @@
1
- # AgentSkills.io Support — Design Spec
2
-
3
- **Date:** 2026-05-06
4
- **Status:** Approved
5
-
6
- ## Overview
7
-
8
- Add support for the [AgentSkills.io](https://agentskills.io) open standard to RobotLab's existing skills system. Skills are currently single `.md` prompt_manager template files referenced by symbol ID. This spec extends `skills:` to also recognize the AgentSkills folder format (`~/.prompts/skills/<name>/SKILL.md`) with bundled scripts auto-exposed as tools and runtime embedding-based matching for progressive disclosure.
9
-
10
- ## Goals
11
-
12
- - Unified `skills:` API — no new constructor params; format detected automatically
13
- - AgentSkills folder skills use runtime embedding similarity to decide injection per `run()` call
14
- - Scripts in `scripts/` auto-wrapped as `RobotLab::Tool` subclasses
15
- - Zero breakage to existing PM template skills behavior
16
- - Graceful degradation when embeddings fail or no skills match
17
-
18
- ## Non-Goals
19
-
20
- - Automatic catalog-wide skill discovery without explicit `skills:` listing
21
- - Fetching remote skills from registries
22
- - Skills referencing `references/` or `assets/` sub-folders (deferred)
23
- - Modifying the AgentSkills.io specification
24
-
25
- ---
26
-
27
- ## Discovery Path
28
-
29
- AgentSkill folders are resolved from a single fixed root: `~/.prompts/skills/`. Given `skills: [:check_style]`, the loader checks `~/.prompts/skills/check_style/SKILL.md`. If that file exists, it is an AgentSkill. If not, the existing PM template resolution applies.
30
-
31
- This root is not configurable in v1.
32
-
33
- ---
34
-
35
- ## New Classes
36
-
37
- ### `RobotLab::AgentSkill`
38
-
39
- Plain Ruby value object. Constructed by the catalog or the expand_skills loader.
40
-
41
- ```
42
- AgentSkill
43
- name: String # from SKILL.md front matter
44
- description: String # from SKILL.md front matter — used for embedding
45
- path: Pathname # directory path
46
- instructions: String # lazy: SKILL.md body — everything after the closing ---
47
- scripts: Array<Pathname> # lazy: glob of scripts/*
48
- ```
49
-
50
- - `instructions` and `scripts` are loaded on first access (lazy).
51
- - Raises `RobotLab::ConfigurationError` on construction if `SKILL.md` is missing `name` or `description`.
52
- - `description_vector` — memoized fastembed **passage** embedding of `description` (`DocumentStore#passage_vector`), computed on first match attempt.
53
-
54
- ### `RobotLab::AgentSkillCatalog`
55
-
56
- Module-level singleton (`RobotLab::AgentSkillCatalog`). Scans `~/.prompts/skills/` at process start (lazy, on first access). Caches `AgentSkill` objects keyed by name symbol.
57
-
58
- Responsibilities:
59
- - `find(id)` — return `AgentSkill` for a given symbol, or `nil`
60
- - `all` — return all discovered skills
61
- - Internal: compute and cache description vectors using the `DocumentStore` fastembed infrastructure
62
-
63
- ### `RobotLab::AgentSkillMatching`
64
-
65
- Module included in `Robot`. Contains the `run()` override and the similarity logic.
66
-
67
- ### `RobotLab::ScriptTool`
68
-
69
- Factory method `ScriptTool.from_path(path)` returns an anonymous `RobotLab::Tool` subclass. The tool:
70
- - Name: script filename without extension, underscored (e.g. `check_style`)
71
- - Description: first comment line of the script, or `"Run #{name}"`
72
- - Execution: shells out via `Open3.capture2e`, returns stdout+stderr as the tool result
73
- - Raises `RobotLab::ToolNotFoundError` if the script is not executable
74
-
75
- ---
76
-
77
- ## Modified Code
78
-
79
- ### `Robot::TemplateRendering#expand_skills`
80
-
81
- Before resolving a skill ID as a PM template, check `AgentSkillCatalog.find(skill_id)`:
82
-
83
- ```
84
- expand_skills([:runbook_protocol, :check_style], visited)
85
- :runbook_protocol → catalog.find → nil → PM template (existing path)
86
- :check_style → catalog.find → AgentSkill<check_style>
87
- → store in @pending_agent_skills
88
- → do NOT add to @expanded_skills
89
- ```
90
-
91
- `@expanded_skills` continues to hold only PM-format skill IDs, preserving all existing behavior.
92
-
93
- ### `Robot` constructor
94
-
95
- Add `@pending_agent_skills = []` alongside `@expanded_skills = nil`.
96
-
97
- ### `Robot::AgentSkillMatching#run`
98
-
99
- Prepended to `Robot` to intercept `run()`:
100
-
101
- ```ruby
102
- def run(message, **kwargs)
103
- activated = match_agent_skills(message)
104
- inject_agent_skills(activated)
105
- super
106
- ensure
107
- restore_after_agent_skills(activated)
108
- end
109
- ```
110
-
111
- **`match_agent_skills(message)`**
112
- 1. Return `[]` if `@pending_agent_skills` is empty
113
- 2. Embed `message` as a **query** vector (`DocumentStore#query_vector`) — distinct from the passage embedding used for skill descriptions
114
- 3. For each pending AgentSkill, compute cosine similarity between message query-vector and `skill.description_vector` (passage-vector)
115
- 4. Return skills where similarity >= `SIMILARITY_THRESHOLD` (default: `0.70`)
116
-
117
- **`inject_agent_skills(skills)`**
118
- 1. Prepend each skill's `instructions` to the chat's system prompt (using `with_instructions`)
119
- 2. Instantiate `ScriptTool` for each script in each skill's `scripts/`; add to `@local_tools`
120
-
121
- **`restore_after_agent_skills(skills)`**
122
- 1. Remove injected script tools from `@local_tools`
123
- 2. Restore system prompt to pre-injection state
124
-
125
- The system prompt is snapshotted before injection and restored in the `ensure` block so state does not accumulate across calls.
126
-
127
- ---
128
-
129
- ## SKILL.md Format
130
-
131
- Follows the AgentSkills.io specification. Front matter is YAML; body is Markdown.
132
-
133
- ```markdown
134
- ---
135
- name: check_style
136
- description: Review Ruby code style against project conventions
137
- ---
138
- When reviewing code, check for:
139
- - Frozen string literal comments
140
- - Method length under 20 lines
141
- - No inline rescue
142
- ```
143
-
144
- RobotLab reads only `name` and `description` from front matter. All other front matter keys are ignored (no LLM config via SKILL.md — that remains the PM template system's domain).
145
-
146
- ---
147
-
148
- ## Script Tools
149
-
150
- Given `~/.prompts/skills/check_style/scripts/run_rubocop.sh`:
151
-
152
- - Tool name: `run_rubocop`
153
- - Description: first `# comment` line in the file, else `"Run run_rubocop"`
154
- - Input: optional `args` string passed as CLI arguments
155
- - Execution: `Open3.capture2e("bash #{path} #{args}")`, returns combined output
156
- - Not executable → log warn, skip (no error raised to the robot)
157
-
158
- ---
159
-
160
- ## Similarity Threshold
161
-
162
- Default: `0.70` (cosine similarity, 0..1 range).
163
-
164
- Accessible as `RobotLab::AgentSkillMatching::SIMILARITY_THRESHOLD`. Not yet user-configurable in v1; a future `config.agent_skill_threshold` can override it.
165
-
166
- ---
167
-
168
- ## Error Handling
169
-
170
- | Situation | Behavior |
171
- |---|---|
172
- | `SKILL.md` missing `name` or `description` | `ConfigurationError` raised at catalog load |
173
- | fastembed fails on message | Log warn, skip all AgentSkill injection for that call |
174
- | Script not executable | Log warn, skip that tool; other scripts still wrap |
175
- | No skills match threshold | Normal `run()` with no injection |
176
- | Circular skill reference via AgentSkill | Existing cycle detection in `expand_skills` handles it |
177
-
178
- ---
179
-
180
- ## Testing
181
-
182
- ### Unit tests (`test/robot_lab/agent_skill_test.rb`)
183
- - Parses `SKILL.md` front matter correctly
184
- - Raises `ConfigurationError` when `name` or `description` missing
185
- - `instructions` lazy-loads body content
186
- - `scripts` lazy-globs `scripts/` directory
187
-
188
- ### Unit tests (`test/robot_lab/agent_skill_catalog_test.rb`)
189
- - Scans fixture skills directory
190
- - `find` returns correct `AgentSkill` by symbol
191
- - `find` returns `nil` for unknown ID
192
-
193
- ### Unit tests (`test/robot_lab/script_tool_test.rb`)
194
- - `from_path` produces a tool with correct name and description
195
- - Tool execution returns stdout+stderr
196
- - Non-executable script skipped with warning
197
-
198
- ### Unit tests (`test/robot_lab/robot/agent_skill_matching_test.rb`)
199
- - `match_agent_skills` returns skills above threshold (mock embeddings)
200
- - `match_agent_skills` returns empty when below threshold
201
- - `inject_agent_skills` prepends instructions to system prompt
202
- - `restore_after_agent_skills` removes injected tools and restores prompt
203
- - Empty `@pending_agent_skills` short-circuits without embedding
204
-
205
- ### Integration tests (`test/robot_lab/robot_test.rb`)
206
- - Robot with `skills: [:pm_template, :agent_skill_fixture]` correctly expands PM template eagerly and AgentSkill at runtime
207
- - AgentSkill scripts appear in tool list during run, absent after
208
-
209
- ### Fixtures
210
- - `test/fixtures/skills/test_skill/SKILL.md` — valid skill
211
- - `test/fixtures/skills/bad_skill/SKILL.md` — missing description
212
- - `test/fixtures/skills/scripted_skill/SKILL.md` + `scripts/hello.sh`
213
-
214
- ---
215
-
216
- ## File Layout
217
-
218
- ```
219
- lib/robot_lab/
220
- agent_skill.rb # AgentSkill value object
221
- agent_skill_catalog.rb # Singleton scanner/registry
222
- script_tool.rb # ScriptTool factory
223
- robot/
224
- agent_skill_matching.rb # run() override mixin
225
-
226
- test/robot_lab/
227
- agent_skill_test.rb
228
- agent_skill_catalog_test.rb
229
- script_tool_test.rb
230
- robot/
231
- agent_skill_matching_test.rb
232
-
233
- test/fixtures/skills/
234
- test_skill/SKILL.md
235
- bad_skill/SKILL.md
236
- scripted_skill/SKILL.md
237
- scripted_skill/scripts/hello.sh
238
- ```
239
-
240
- ---
241
-
242
- ## Open Questions (deferred)
243
-
244
- - `references/` and `assets/` subdirectories: make content available as context variables in future
245
- - User-configurable similarity threshold via `RobotLab.config`
246
- - Auto-catalog mode: discover all skills without explicit `skills:` listing
247
- - AgentSkill nesting (a SKILL.md referencing other skill IDs)
data/examples/.envrc DELETED
@@ -1,3 +0,0 @@
1
- source_up
2
-
3
- export ROBOT_LAB_TEMPLATE_PATH="${PWD}/prompts"
@@ -1,31 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 1: Simple Robot
5
- #
6
- # Demonstrates creating and running a basic robot with a template.
7
- #
8
- # Usage:
9
- # ruby examples/01_simple_robot.rb
10
-
11
- require_relative "common"
12
-
13
- # Create a simple robot using a template
14
- robot = RobotLab.build(
15
- **llm_opts,
16
- name: "helper",
17
- template: :helper
18
- )
19
-
20
- banner "Simple Robot"
21
-
22
- # Run the robot
23
- result = robot.run("What is 2 + 2? Please explain your reasoning briefly.")
24
-
25
- # Display the result
26
- puts "Robot: #{robot.name}"
27
- puts "Output:"
28
- result.output.each do |message|
29
- puts " #{message.content}" if message.respond_to?(:content)
30
- end
31
- hr
data/examples/02_tools.rb DELETED
@@ -1,109 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 2: Robot with Tools
5
- #
6
- # Demonstrates creating a robot with custom tools using RubyLLM::Tool.
7
- #
8
- # Usage:
9
- # ruby examples/02_tools.rb
10
-
11
- require_relative "common"
12
-
13
- # Define tools using RubyLLM::Tool
14
- class Calculator < RubyLLM::Tool
15
- description "Performs basic arithmetic operations"
16
-
17
- parameter :operation,
18
- type: "string",
19
- description: "The operation to perform (add, subtract, multiply, divide)"
20
-
21
- parameter :a,
22
- type: "number",
23
- description: "First operand"
24
-
25
- parameter :b,
26
- type: "number",
27
- description: "Second operand"
28
-
29
- def execute(operation:, a:, b:)
30
- case operation
31
- when "add" then a + b
32
- when "subtract" then a - b
33
- when "multiply" then a * b
34
- when "divide" then a.to_f / b
35
- else "Unknown operation: #{operation}"
36
- end
37
- end
38
- end
39
-
40
- class FortuneCookie < RubyLLM::Tool
41
- description "Get a fortune cookie message with wisdom and lucky numbers"
42
-
43
- parameter :category,
44
- type: "string",
45
- description: "The category of fortune (wisdom, love, career, adventure)"
46
-
47
- FORTUNES = {
48
- "wisdom" => [
49
- "The obstacle in the path becomes the path.",
50
- "A journey of a thousand miles begins with a single step.",
51
- "The best time to plant a tree was 20 years ago. The second best time is now."
52
- ],
53
- "love" => [
54
- "The heart that loves is always young.",
55
- "To love and be loved is to feel the sun from both sides.",
56
- "Love is not about finding the right person, but being the right person."
57
- ],
58
- "career" => [
59
- "Opportunity dances with those already on the dance floor.",
60
- "Your work is your signature. Sign it with excellence.",
61
- "The expert in anything was once a beginner."
62
- ],
63
- "adventure" => [
64
- "Life shrinks or expands in proportion to one's courage.",
65
- "Not all who wander are lost.",
66
- "The biggest adventure you can take is to live the life of your dreams."
67
- ]
68
- }.freeze
69
-
70
- def execute(category:)
71
- {
72
- category: category,
73
- fortune: FORTUNES.fetch(category, FORTUNES["wisdom"]).sample,
74
- lucky_numbers: Array.new(6) { rand(1..49) }.sort
75
- }
76
- end
77
- end
78
-
79
- # Create robot with tools
80
- robot = RobotLab.build(
81
- **llm_opts,
82
- name: "assistant",
83
- template: :assistant,
84
- local_tools: [Calculator, FortuneCookie]
85
- )
86
-
87
- banner "Robot with Tools"
88
-
89
- # Run the robot.
90
- #
91
- # tools: :inherit is REQUIRED to actually offer the tools to the model.
92
- # Robot#run defaults to tools: :none, which means "send zero tools this turn"
93
- # — the robot still holds them in local_tools, but the provider never sees
94
- # them and the LLM answers from memory instead of calling Calculator.
95
- # :inherit means "no per-turn filter, use every attached tool".
96
- result = robot.run(
97
- "What is 15 multiplied by 7? Also, give me a fortune about my career.",
98
- tools: :inherit
99
- )
100
-
101
- # Display results
102
- puts "Robot: #{robot.name}"
103
- puts "Tools offered: #{robot.local_tools.map(&:name).join(', ')}"
104
- puts "\nOutput:"
105
- result.output.each do |message|
106
- puts " #{message.content}" if message.respond_to?(:content)
107
- end
108
-
109
- hr
@@ -1,118 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- # Example 3: Multi-Robot Network
5
- #
6
- # Demonstrates creating a network of robots with conditional routing
7
- # using SimpleFlow's optional step activation.
8
- #
9
- # Usage:
10
- # ruby examples/03_network.rb
11
-
12
- require_relative "common"
13
-
14
- SPECIALIST_TASKS = %i[billing technical general].freeze
15
-
16
- # Classifier robot that activates the appropriate specialist
17
- class ClassifierRobot < RobotLab::Robot
18
- def call(result)
19
- run_context = extract_run_context(result)
20
- message = run_context.delete(:message)
21
- robot_result = run(message, **run_context)
22
-
23
- new_result = result
24
- .with_context(@name.to_sym, robot_result)
25
- .continue(robot_result)
26
-
27
- # Examine LLM output and activate appropriate specialist
28
- category = robot_result.reply.to_s.strip.downcase
29
-
30
- case category
31
- when /billing/
32
- new_result.activate(:billing)
33
- when /technical/
34
- new_result.activate(:technical)
35
- else
36
- new_result.activate(:general)
37
- end
38
- end
39
- end
40
-
41
- # Shared RunConfig — operational defaults every robot in this network inherits.
42
- #
43
- # RunConfig has no `provider` field (see RunConfig::FIELDS), and an LM Studio
44
- # model is absent from RubyLLM's registry, so provider and model still travel
45
- # together on each robot via **llm_opts. The RunConfig carries the settings
46
- # that genuinely are shared.
47
- shared_config = RobotLab::RunConfig.new(temperature: 0.3, max_tool_rounds: 5)
48
-
49
- classifier = ClassifierRobot.new(
50
- **llm_opts,
51
- name: "classifier",
52
- template: :classifier,
53
- config: shared_config
54
- )
55
-
56
- billing_robot = RobotLab.build(
57
- **llm_opts,
58
- name: "billing",
59
- template: :billing,
60
- config: shared_config
61
- )
62
-
63
- technical_robot = RobotLab.build(
64
- **llm_opts,
65
- name: "technical",
66
- template: :technical,
67
- config: shared_config
68
- )
69
-
70
- general_robot = RobotLab.build(
71
- **llm_opts,
72
- name: "general",
73
- template: :general,
74
- config: shared_config
75
- )
76
-
77
- # Create network with optional task routing and shared config
78
- network = RobotLab.create_network(name: "support_network", config: shared_config) do
79
- task :classifier, classifier, depends_on: :none
80
- task :billing, billing_robot, depends_on: :optional
81
- task :technical, technical_robot, depends_on: :optional
82
- task :general, general_robot, depends_on: :optional
83
- end
84
-
85
- banner "Multi-Robot Network"
86
- puts "Routing plan:"
87
- puts " Entry task: classifier"
88
- puts " Optional specialists: #{SPECIALIST_TASKS.join(', ')}"
89
- puts " Runtime behavior: classifier activates exactly one specialist"
90
- puts
91
- puts "Underlying SimpleFlow structure:"
92
- puts " Optional tasks: #{network.to_h[:optional_tasks].join(', ')}"
93
- hr
94
-
95
- # Run the network with a billing question
96
- result = network.run(message: "I was charged twice for my subscription last month. Can you help?")
97
-
98
- # Display results
99
- puts "Network: #{network.name}"
100
- puts "Executed tasks: #{result.context.keys.grep_v(:run_params).join(', ')}"
101
- puts "Skipped specialists: #{(SPECIALIST_TASKS - result.context.keys).join(', ')}"
102
- puts "\nConversation flow:"
103
-
104
- # Show classifier result
105
- if result.context[:classifier]
106
- classifier_result = result.context[:classifier]
107
- puts "\n1. Robot: classifier"
108
- puts " Classification: #{classifier_result.reply}"
109
- end
110
-
111
- # Show specialist result (the final value)
112
- if result.value.is_a?(RobotLab::RobotResult)
113
- puts "\n2. Robot: #{result.value.robot_name}"
114
- content = result.value.reply
115
- puts " Response: #{content[0..200]}..." if content
116
- end
117
-
118
- hr