robot_lab 0.1.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/.architecture/AGENTS.md +32 -0
- data/.architecture/config.yml +8 -0
- data/.architecture/members.yml +60 -0
- data/.architecture/reviews/feature-free-will.md +490 -0
- data/.architecture/reviews/overall-codebase.md +427 -0
- data/.claude/settings.local.json +57 -0
- data/.codex/config.toml +2 -0
- data/.irbrc +2 -2
- data/.rubocop.yml +172 -0
- data/CHANGELOG.md +72 -0
- data/CLAUDE.md +139 -0
- data/README.md +91 -95
- data/Rakefile +109 -3
- data/agent2agent_review.md +192 -0
- data/agentf_improvements.md +253 -0
- data/agents.md +14 -0
- data/docs/examples/index.md +37 -2
- data/docs/getting-started/configuration.md +20 -7
- data/docs/guides/index.md +16 -16
- data/docs/guides/knowledge.md +7 -1
- data/docs/guides/observability.md +132 -0
- data/docs/index.md +30 -3
- data/docs/superpowers/plans/2026-05-06-agentskills.md +1303 -0
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +247 -0
- data/examples/.envrc +1 -0
- data/examples/01_simple_robot.rb +5 -9
- data/examples/02_tools.rb +5 -9
- data/examples/03_network.rb +8 -9
- data/examples/04_mcp.rb +21 -29
- data/examples/05_streaming.rb +12 -18
- data/examples/06_prompt_templates.rb +11 -19
- data/examples/07_network_memory.rb +16 -31
- data/examples/08_llm_config.rb +10 -22
- data/examples/09_chaining.rb +16 -27
- data/examples/10_memory.rb +12 -28
- data/examples/11_network_introspection.rb +15 -29
- data/examples/12_message_bus.rb +5 -12
- data/examples/13_spawn.rb +5 -10
- data/examples/14_rusty_circuit/.envrc +1 -0
- data/examples/14_rusty_circuit/comic.rb +2 -0
- data/examples/14_rusty_circuit/heckler.rb +1 -1
- data/examples/14_rusty_circuit/open_mic.rb +1 -3
- data/examples/14_rusty_circuit/scout.rb +2 -0
- data/examples/15_memory_network_and_bus/.envrc +1 -0
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +6 -3
- data/examples/15_memory_network_and_bus/linux_writer.rb +1 -1
- data/examples/15_memory_network_and_bus/output/combined_article.md +6 -6
- data/examples/15_memory_network_and_bus/output/final_article.md +6 -8
- data/examples/15_memory_network_and_bus/output/linux_draft.md +4 -2
- data/examples/15_memory_network_and_bus/output/mac_draft.md +3 -3
- data/examples/15_memory_network_and_bus/output/memory.json +6 -6
- data/examples/15_memory_network_and_bus/output/revision_1.md +10 -11
- data/examples/15_memory_network_and_bus/output/revision_2.md +6 -8
- data/examples/15_memory_network_and_bus/output/windows_draft.md +3 -3
- data/examples/16_writers_room/.envrc +1 -0
- data/examples/16_writers_room/writers_room.rb +2 -4
- data/examples/17_skills.rb +8 -17
- data/examples/18_rails/Gemfile +1 -0
- data/examples/19_token_tracking.rb +9 -15
- data/examples/20_circuit_breaker.rb +10 -19
- data/examples/21_learning_loop.rb +11 -20
- data/examples/22_context_compression.rb +6 -13
- data/examples/23_convergence.rb +6 -17
- data/examples/24_structured_delegation.rb +11 -15
- data/examples/25_history_search.rb +5 -12
- data/examples/26_document_store.rb +6 -13
- data/examples/27_incident_response/incident_response.rb +4 -5
- data/examples/28_mcp_discovery.rb +8 -11
- data/examples/29_ractor_tools.rb +4 -9
- data/examples/30_ractor_network.rb +10 -19
- data/examples/31_launch_assessment.rb +10 -23
- data/examples/32_newsletter_reader.rb +188 -0
- data/examples/33_stock_generator.rb +80 -0
- data/examples/33_stock_predictor.rb +306 -0
- data/examples/34_agentskills.rb +72 -0
- data/examples/README.md +1 -1
- data/examples/common.rb +76 -0
- data/examples/ruboruby.md +423 -0
- data/examples/temp.md +51 -0
- data/lib/robot_lab/agent_skill.rb +63 -0
- data/lib/robot_lab/agent_skill_catalog.rb +74 -0
- data/lib/robot_lab/ask_user.rb +2 -2
- data/lib/robot_lab/bus_poller.rb +12 -5
- data/lib/robot_lab/config.rb +1 -12
- data/lib/robot_lab/delegation_future.rb +1 -1
- data/lib/robot_lab/doom_loop_detector.rb +98 -0
- data/lib/robot_lab/history_compressor.rb +4 -10
- data/lib/robot_lab/mcp/client.rb +1 -2
- data/lib/robot_lab/mcp/connection_poller.rb +3 -3
- data/lib/robot_lab/mcp/server.rb +1 -1
- data/lib/robot_lab/mcp/server_discovery.rb +0 -2
- data/lib/robot_lab/memory.rb +32 -27
- data/lib/robot_lab/memory_change.rb +2 -2
- data/lib/robot_lab/message.rb +4 -4
- data/lib/robot_lab/network.rb +11 -6
- data/lib/robot_lab/robot/agent_skill_matching.rb +99 -0
- data/lib/robot_lab/robot/bus_messaging.rb +9 -27
- data/lib/robot_lab/robot/history_search.rb +4 -1
- data/lib/robot_lab/robot/mcp_management.rb +5 -11
- data/lib/robot_lab/robot/template_rendering.rb +60 -40
- data/lib/robot_lab/robot.rb +323 -206
- data/lib/robot_lab/robot_result.rb +6 -5
- data/lib/robot_lab/run_config.rb +5 -11
- data/lib/robot_lab/script_tool.rb +76 -0
- data/lib/robot_lab/state_proxy.rb +7 -5
- data/lib/robot_lab/tool.rb +3 -3
- data/lib/robot_lab/tool_config.rb +1 -1
- data/lib/robot_lab/tool_manifest.rb +5 -7
- data/lib/robot_lab/user_message.rb +2 -2
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab/waiter.rb +1 -1
- data/lib/robot_lab.rb +41 -52
- data/logfile +8 -0
- data/mkdocs.yml +2 -3
- data/robot_concurrency.md +38 -0
- data/simple_acp_review.md +298 -0
- data/site/404.html +2300 -0
- data/site/api/core/index.html +2706 -0
- data/site/api/core/memory/index.html +3793 -0
- data/site/api/core/network/index.html +3500 -0
- data/site/api/core/robot/index.html +4566 -0
- data/site/api/core/state/index.html +3390 -0
- data/site/api/core/tool/index.html +3843 -0
- data/site/api/index.html +2635 -0
- data/site/api/mcp/client/index.html +3435 -0
- data/site/api/mcp/index.html +2783 -0
- data/site/api/mcp/server/index.html +3252 -0
- data/site/api/mcp/transports/index.html +3352 -0
- data/site/api/messages/index.html +2641 -0
- data/site/api/messages/text-message/index.html +3087 -0
- data/site/api/messages/tool-call-message/index.html +3159 -0
- data/site/api/messages/tool-result-message/index.html +3252 -0
- data/site/api/messages/user-message/index.html +3212 -0
- data/site/api/streaming/context/index.html +3282 -0
- data/site/api/streaming/events/index.html +3347 -0
- data/site/api/streaming/index.html +2738 -0
- data/site/architecture/core-concepts/index.html +3757 -0
- data/site/architecture/index.html +2797 -0
- data/site/architecture/message-flow/index.html +3238 -0
- data/site/architecture/network-orchestration/index.html +3433 -0
- data/site/architecture/robot-execution/index.html +3140 -0
- data/site/architecture/state-management/index.html +3498 -0
- data/site/assets/css/custom.css +56 -0
- data/site/assets/images/favicon.png +0 -0
- data/site/assets/images/robot_lab.jpg +0 -0
- data/site/assets/javascripts/bundle.79ae519e.min.js +16 -0
- data/site/assets/javascripts/bundle.79ae519e.min.js.map +7 -0
- data/site/assets/javascripts/lunr/min/lunr.ar.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.da.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.de.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.du.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.el.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.es.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.fi.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.fr.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.he.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.hi.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.hu.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.hy.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.it.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.ja.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.jp.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.kn.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.ko.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.multi.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.nl.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.no.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.pt.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.ro.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.ru.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.sa.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.stemmer.support.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.sv.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.ta.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.te.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.th.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.tr.min.js +18 -0
- data/site/assets/javascripts/lunr/min/lunr.vi.min.js +1 -0
- data/site/assets/javascripts/lunr/min/lunr.zh.min.js +1 -0
- data/site/assets/javascripts/lunr/tinyseg.js +206 -0
- data/site/assets/javascripts/lunr/wordcut.js +6708 -0
- data/site/assets/javascripts/workers/search.2c215733.min.js +42 -0
- data/site/assets/javascripts/workers/search.2c215733.min.js.map +7 -0
- data/site/assets/stylesheets/main.484c7ddc.min.css +1 -0
- data/site/assets/stylesheets/main.484c7ddc.min.css.map +1 -0
- data/site/assets/stylesheets/palette.ab4e12ef.min.css +1 -0
- data/site/assets/stylesheets/palette.ab4e12ef.min.css.map +1 -0
- data/site/concepts/index.html +3455 -0
- data/site/examples/basic-chat/index.html +2880 -0
- data/site/examples/index.html +2907 -0
- data/site/examples/mcp-server/index.html +3018 -0
- data/site/examples/multi-robot-network/index.html +3131 -0
- data/site/examples/rails-application/index.html +3329 -0
- data/site/examples/tool-usage/index.html +3085 -0
- data/site/getting-started/configuration/index.html +3745 -0
- data/site/getting-started/index.html +2572 -0
- data/site/getting-started/installation/index.html +2981 -0
- data/site/getting-started/quick-start/index.html +2942 -0
- data/site/guides/building-robots/index.html +4290 -0
- data/site/guides/creating-networks/index.html +3858 -0
- data/site/guides/index.html +2586 -0
- data/site/guides/mcp-integration/index.html +3581 -0
- data/site/guides/memory/index.html +3586 -0
- data/site/guides/rails-integration/index.html +4019 -0
- data/site/guides/streaming/index.html +3157 -0
- data/site/guides/using-tools/index.html +3802 -0
- data/site/index.html +2671 -0
- data/site/search/search_index.json +1 -0
- data/site/sitemap.xml +183 -0
- data/site/sitemap.xml.gz +0 -0
- data/site/tags.json +1 -0
- data/temp.md +6 -0
- data/tool_manifest_plan.md +155 -0
- metadata +154 -92
- data/docs/examples/rails-application.md +0 -419
- data/docs/guides/ractor-parallelism.md +0 -364
- data/docs/guides/rails-integration.md +0 -681
- data/docs/superpowers/plans/2026-04-14-ractor-integration.md +0 -1538
- data/docs/superpowers/specs/2026-04-14-ractor-integration-design.md +0 -258
- data/lib/generators/robot_lab/install_generator.rb +0 -90
- data/lib/generators/robot_lab/job_generator.rb +0 -40
- data/lib/generators/robot_lab/robot_generator.rb +0 -55
- data/lib/generators/robot_lab/templates/initializer.rb.tt +0 -42
- data/lib/generators/robot_lab/templates/job.rb.tt +0 -21
- data/lib/generators/robot_lab/templates/migration.rb.tt +0 -32
- data/lib/generators/robot_lab/templates/result_model.rb.tt +0 -52
- data/lib/generators/robot_lab/templates/robot.rb.tt +0 -31
- data/lib/generators/robot_lab/templates/robot_job.rb.tt +0 -18
- data/lib/generators/robot_lab/templates/robot_test.rb.tt +0 -34
- data/lib/generators/robot_lab/templates/routing_robot.rb.tt +0 -59
- data/lib/generators/robot_lab/templates/thread_model.rb.tt +0 -40
- data/lib/robot_lab/document_store.rb +0 -155
- data/lib/robot_lab/ractor_boundary.rb +0 -42
- data/lib/robot_lab/ractor_job.rb +0 -37
- data/lib/robot_lab/ractor_memory_proxy.rb +0 -85
- data/lib/robot_lab/ractor_network_scheduler.rb +0 -154
- data/lib/robot_lab/ractor_worker_pool.rb +0 -117
- data/lib/robot_lab/rails_integration/engine.rb +0 -29
- data/lib/robot_lab/rails_integration/job.rb +0 -158
- data/lib/robot_lab/rails_integration/railtie.rb +0 -51
- data/lib/robot_lab/rails_integration/turbo_stream_callbacks.rb +0 -72
|
@@ -0,0 +1,247 @@
|
|
|
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
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export ROBOT_LAB_TEMPLATE_PATH="${PWD}/prompts"
|
data/examples/01_simple_robot.rb
CHANGED
|
@@ -8,20 +8,16 @@
|
|
|
8
8
|
# Usage:
|
|
9
9
|
# ANTHROPIC_API_KEY=your_key ruby examples/01_simple_robot.rb
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
|
|
13
|
-
|
|
14
|
-
require_relative "../lib/robot_lab"
|
|
11
|
+
require_relative "common"
|
|
15
12
|
|
|
16
13
|
# Create a simple robot using a template
|
|
17
14
|
robot = RobotLab.build(
|
|
15
|
+
model: LLM[:default].model,
|
|
18
16
|
name: "helper",
|
|
19
|
-
template: :helper
|
|
20
|
-
model: "claude-3-haiku-20240307"
|
|
17
|
+
template: :helper
|
|
21
18
|
)
|
|
22
19
|
|
|
23
|
-
|
|
24
|
-
puts "-" * 40
|
|
20
|
+
banner "Simple Robot"
|
|
25
21
|
|
|
26
22
|
# Run the robot
|
|
27
23
|
result = robot.run("What is 2 + 2? Please explain your reasoning briefly.")
|
|
@@ -32,4 +28,4 @@ puts "Output:"
|
|
|
32
28
|
result.output.each do |message|
|
|
33
29
|
puts " #{message.content}" if message.respond_to?(:content)
|
|
34
30
|
end
|
|
35
|
-
|
|
31
|
+
hr
|
data/examples/02_tools.rb
CHANGED
|
@@ -8,10 +8,7 @@
|
|
|
8
8
|
# Usage:
|
|
9
9
|
# ANTHROPIC_API_KEY=your_key ruby examples/02_tools.rb
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
|
|
13
|
-
|
|
14
|
-
require_relative "../lib/robot_lab"
|
|
11
|
+
require_relative "common"
|
|
15
12
|
|
|
16
13
|
# Define tools using RubyLLM::Tool
|
|
17
14
|
class Calculator < RubyLLM::Tool
|
|
@@ -81,14 +78,13 @@ end
|
|
|
81
78
|
|
|
82
79
|
# Create robot with tools
|
|
83
80
|
robot = RobotLab.build(
|
|
81
|
+
model: LLM[:default].model,
|
|
84
82
|
name: "assistant",
|
|
85
83
|
template: :assistant,
|
|
86
|
-
local_tools: [Calculator, FortuneCookie]
|
|
87
|
-
model: "claude-3-haiku-20240307"
|
|
84
|
+
local_tools: [Calculator, FortuneCookie]
|
|
88
85
|
)
|
|
89
86
|
|
|
90
|
-
|
|
91
|
-
puts "-" * 40
|
|
87
|
+
banner "Robot with Tools"
|
|
92
88
|
|
|
93
89
|
# Run the robot
|
|
94
90
|
result = robot.run("What is 15 multiplied by 7? Also, give me a fortune about my career.")
|
|
@@ -100,4 +96,4 @@ result.output.each do |message|
|
|
|
100
96
|
puts " #{message.content}" if message.respond_to?(:content)
|
|
101
97
|
end
|
|
102
98
|
|
|
103
|
-
|
|
99
|
+
hr
|
data/examples/03_network.rb
CHANGED
|
@@ -9,10 +9,7 @@
|
|
|
9
9
|
# Usage:
|
|
10
10
|
# ANTHROPIC_API_KEY=your_key ruby examples/03_network.rb
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
|
|
14
|
-
|
|
15
|
-
require_relative "../lib/robot_lab"
|
|
12
|
+
require_relative "common"
|
|
16
13
|
|
|
17
14
|
# Classifier robot that activates the appropriate specialist
|
|
18
15
|
class ClassifierRobot < RobotLab::Robot
|
|
@@ -40,7 +37,7 @@ class ClassifierRobot < RobotLab::Robot
|
|
|
40
37
|
end
|
|
41
38
|
|
|
42
39
|
# Shared RunConfig — all robots in this network use the same model
|
|
43
|
-
shared_config = RobotLab::RunConfig.new(model:
|
|
40
|
+
shared_config = RobotLab::RunConfig.new(model: LLM[:default].model)
|
|
44
41
|
|
|
45
42
|
# Create specialized robots (no model: needed — inherited from RunConfig)
|
|
46
43
|
classifier = ClassifierRobot.new(
|
|
@@ -50,18 +47,21 @@ classifier = ClassifierRobot.new(
|
|
|
50
47
|
)
|
|
51
48
|
|
|
52
49
|
billing_robot = RobotLab.build(
|
|
50
|
+
model: LLM[:default].model,
|
|
53
51
|
name: "billing",
|
|
54
52
|
template: :billing,
|
|
55
53
|
config: shared_config
|
|
56
54
|
)
|
|
57
55
|
|
|
58
56
|
technical_robot = RobotLab.build(
|
|
57
|
+
model: LLM[:default].model,
|
|
59
58
|
name: "technical",
|
|
60
59
|
template: :technical,
|
|
61
60
|
config: shared_config
|
|
62
61
|
)
|
|
63
62
|
|
|
64
63
|
general_robot = RobotLab.build(
|
|
64
|
+
model: LLM[:default].model,
|
|
65
65
|
name: "general",
|
|
66
66
|
template: :general,
|
|
67
67
|
config: shared_config
|
|
@@ -75,11 +75,10 @@ network = RobotLab.create_network(name: "support_network", config: shared_config
|
|
|
75
75
|
task :general, general_robot, depends_on: :optional
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
-
|
|
79
|
-
puts "-" * 40
|
|
78
|
+
banner "Multi-Robot Network"
|
|
80
79
|
puts "Network structure:"
|
|
81
80
|
puts network.visualize
|
|
82
|
-
|
|
81
|
+
hr
|
|
83
82
|
|
|
84
83
|
# Run the network with a billing question
|
|
85
84
|
result = network.run(message: "I was charged twice for my subscription last month. Can you help?")
|
|
@@ -102,4 +101,4 @@ if result.value.is_a?(RobotLab::RobotResult)
|
|
|
102
101
|
puts " Response: #{content[0..200]}..." if content
|
|
103
102
|
end
|
|
104
103
|
|
|
105
|
-
|
|
104
|
+
hr
|
data/examples/04_mcp.rb
CHANGED
|
@@ -21,10 +21,7 @@
|
|
|
21
21
|
# - Reading file contents and repository information
|
|
22
22
|
# - Managing branches and commits
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
|
|
26
|
-
|
|
27
|
-
require_relative "../lib/robot_lab"
|
|
24
|
+
require_relative "common"
|
|
28
25
|
|
|
29
26
|
# GitHub MCP server configuration using StdIO transport
|
|
30
27
|
github_server = {
|
|
@@ -39,14 +36,11 @@ github_server = {
|
|
|
39
36
|
}
|
|
40
37
|
}
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
MCP Integration Example: GitHub
|
|
44
|
-
#{"=" * 40}
|
|
45
|
-
|
|
46
|
-
This example demonstrates using the GitHub MCP server to interact
|
|
47
|
-
with GitHub repositories through the Model Context Protocol.
|
|
39
|
+
banner "MCP Integration Example: GitHub"
|
|
48
40
|
|
|
49
|
-
|
|
41
|
+
puts "This example demonstrates using the GitHub MCP server to interact"
|
|
42
|
+
puts "with GitHub repositories through the Model Context Protocol."
|
|
43
|
+
puts
|
|
50
44
|
|
|
51
45
|
# Verify prerequisites
|
|
52
46
|
unless ENV["GITHUB_PERSONAL_ACCESS_TOKEN"]
|
|
@@ -65,9 +59,7 @@ end
|
|
|
65
59
|
# Part 1: Direct MCP Client Usage
|
|
66
60
|
# ============================================================================
|
|
67
61
|
|
|
68
|
-
|
|
69
|
-
puts "-" * 40
|
|
70
|
-
puts
|
|
62
|
+
section "Part 1: Direct MCP Client Usage"
|
|
71
63
|
puts "Connecting to GitHub MCP server..."
|
|
72
64
|
|
|
73
65
|
begin
|
|
@@ -91,7 +83,7 @@ begin
|
|
|
91
83
|
|
|
92
84
|
# List available tools
|
|
93
85
|
puts "Available GitHub Tools:"
|
|
94
|
-
|
|
86
|
+
hr
|
|
95
87
|
|
|
96
88
|
tools = client.list_tools
|
|
97
89
|
if tools.empty?
|
|
@@ -103,7 +95,7 @@ begin
|
|
|
103
95
|
end
|
|
104
96
|
end
|
|
105
97
|
|
|
106
|
-
|
|
98
|
+
hr
|
|
107
99
|
puts "Total: #{tools.size} tools available"
|
|
108
100
|
puts
|
|
109
101
|
|
|
@@ -134,22 +126,22 @@ begin
|
|
|
134
126
|
# Part 2: Robot + MCP Integration
|
|
135
127
|
# ============================================================================
|
|
136
128
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
puts "PART 2: Robot + MCP Integration"
|
|
140
|
-
puts "-" * 40
|
|
141
|
-
puts
|
|
129
|
+
|
|
130
|
+
section "Part 2: Robot + MCP Integration"
|
|
142
131
|
|
|
143
132
|
puts "Creating Robot with MCP server integration..."
|
|
144
133
|
puts
|
|
145
134
|
|
|
146
|
-
# Create a Robot with MCP server - tools are automatically discovered
|
|
135
|
+
# Create a Robot with MCP server - tools are automatically discovered.
|
|
136
|
+
# connect_mcp! forces eager connection; without it MCP clients initialize
|
|
137
|
+
# lazily on the first run() call, leaving mcp_clients empty until then.
|
|
147
138
|
robot = RobotLab.build(
|
|
139
|
+
model: LLM[:default].model,
|
|
148
140
|
name: "github_assistant",
|
|
149
141
|
template: :github_assistant,
|
|
150
|
-
mcp_servers: [github_server]
|
|
151
|
-
model: "claude-3-haiku-20240307"
|
|
142
|
+
mcp_servers: [github_server]
|
|
152
143
|
)
|
|
144
|
+
robot.connect_mcp!
|
|
153
145
|
|
|
154
146
|
puts "Robot created: #{robot.name}"
|
|
155
147
|
puts " Model: #{robot.model}"
|
|
@@ -159,7 +151,7 @@ begin
|
|
|
159
151
|
|
|
160
152
|
# Show discovered MCP tools
|
|
161
153
|
puts "Discovered MCP Tools:"
|
|
162
|
-
|
|
154
|
+
hr
|
|
163
155
|
robot.mcp_tools.first(10).each do |tool|
|
|
164
156
|
puts " #{tool.name}"
|
|
165
157
|
puts " #{tool.description&.slice(0, 60)}..." if tool.description
|
|
@@ -170,13 +162,13 @@ begin
|
|
|
170
162
|
# Run the robot with a query that will use MCP tools
|
|
171
163
|
puts "Running Robot with a GitHub query..."
|
|
172
164
|
puts "Query: 'What are the top 3 most starred Ruby web frameworks on GitHub?'"
|
|
173
|
-
|
|
165
|
+
hr
|
|
174
166
|
|
|
175
167
|
result = robot.run("What are the top 3 most starred Ruby web frameworks on GitHub? Just list their names and star counts.")
|
|
176
168
|
|
|
177
169
|
puts
|
|
178
170
|
puts "Robot Response:"
|
|
179
|
-
|
|
171
|
+
hr
|
|
180
172
|
result.output.each do |msg|
|
|
181
173
|
puts msg.content if msg.respond_to?(:content)
|
|
182
174
|
end
|
|
@@ -185,7 +177,7 @@ begin
|
|
|
185
177
|
# Show tool calls if any were made
|
|
186
178
|
if result.tool_calls.any?
|
|
187
179
|
puts "Tool Calls Made:"
|
|
188
|
-
|
|
180
|
+
hr
|
|
189
181
|
result.tool_calls.each do |tc|
|
|
190
182
|
tool_info = tc.respond_to?(:tool) ? tc.tool : tc
|
|
191
183
|
puts " #{tool_info[:name] || tool_info}"
|
|
@@ -212,5 +204,5 @@ rescue Errno::ENOENT
|
|
|
212
204
|
end
|
|
213
205
|
|
|
214
206
|
puts
|
|
215
|
-
|
|
207
|
+
hr
|
|
216
208
|
puts "Example complete!"
|
data/examples/05_streaming.rb
CHANGED
|
@@ -11,10 +11,7 @@
|
|
|
11
11
|
# Usage:
|
|
12
12
|
# ANTHROPIC_API_KEY=your_key ruby examples/05_streaming.rb
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
ENV['ROBOT_LAB_TEMPLATE_PATH'] ||= File.join(__dir__, "prompts")
|
|
16
|
-
|
|
17
|
-
require_relative "../lib/robot_lab"
|
|
14
|
+
require_relative "common"
|
|
18
15
|
|
|
19
16
|
# Send logger output to a file instead of stdout
|
|
20
17
|
require 'logger'
|
|
@@ -22,19 +19,17 @@ log_file = File.join(__dir__, "05.log")
|
|
|
22
19
|
RobotLab.config.logger = Logger.new(log_file)
|
|
23
20
|
RubyLLM.configure { |c| c.logger = Logger.new(log_file) }
|
|
24
21
|
|
|
25
|
-
|
|
26
|
-
puts "=" * 50
|
|
27
|
-
puts ""
|
|
22
|
+
banner "Streaming Content"
|
|
28
23
|
|
|
29
24
|
# ── 1. Stored callback (on_content:) ─────────────────────────────
|
|
30
25
|
#
|
|
31
26
|
# Wire streaming at build time. The callback fires on every run() call.
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
puts "-" * 50
|
|
28
|
+
section "1. Stored Callback (on_content:)"
|
|
35
29
|
|
|
36
30
|
chunks_received = 0
|
|
37
31
|
robot = RobotLab.build(
|
|
32
|
+
model: LLM[:default].model,
|
|
38
33
|
name: "storyteller",
|
|
39
34
|
system_prompt: "You are a concise storyteller. Keep responses under 3 sentences.",
|
|
40
35
|
on_content: ->(chunk) {
|
|
@@ -53,11 +48,11 @@ puts ""
|
|
|
53
48
|
#
|
|
54
49
|
# Pass a block to run() for one-off streaming.
|
|
55
50
|
|
|
56
|
-
|
|
57
|
-
puts "-" * 50
|
|
51
|
+
section "2. Per-call Block"
|
|
58
52
|
|
|
59
53
|
block_chunks = 0
|
|
60
54
|
bare_robot = RobotLab.build(
|
|
55
|
+
model: LLM[:default].model,
|
|
61
56
|
name: "factbot",
|
|
62
57
|
system_prompt: "You are concise. Answer in one sentence."
|
|
63
58
|
)
|
|
@@ -75,13 +70,13 @@ puts ""
|
|
|
75
70
|
#
|
|
76
71
|
# When both exist, both fire: stored callback first, then block.
|
|
77
72
|
|
|
78
|
-
|
|
79
|
-
puts "-" * 50
|
|
73
|
+
section "3. Both Together (stored fires first, then block)"
|
|
80
74
|
|
|
81
75
|
stored_log = []
|
|
82
76
|
block_log = []
|
|
83
77
|
|
|
84
78
|
combo_robot = RobotLab.build(
|
|
79
|
+
model: LLM[:default].model,
|
|
85
80
|
name: "combo",
|
|
86
81
|
system_prompt: "You are concise. Answer in one sentence.",
|
|
87
82
|
on_content: ->(chunk) { stored_log << chunk.content }
|
|
@@ -101,11 +96,11 @@ puts ""
|
|
|
101
96
|
#
|
|
102
97
|
# on_content participates in the config cascade.
|
|
103
98
|
|
|
104
|
-
|
|
105
|
-
puts "-" * 50
|
|
99
|
+
section "4. Via RunConfig (config cascade)"
|
|
106
100
|
|
|
107
101
|
config_chunks = 0
|
|
108
102
|
config = RobotLab::RunConfig.new(
|
|
103
|
+
model: LLM[:default].model,
|
|
109
104
|
on_content: ->(chunk) {
|
|
110
105
|
print chunk.content
|
|
111
106
|
config_chunks += 1
|
|
@@ -113,6 +108,7 @@ config = RobotLab::RunConfig.new(
|
|
|
113
108
|
)
|
|
114
109
|
|
|
115
110
|
config_robot = RobotLab.build(
|
|
111
|
+
model: LLM[:default].model,
|
|
116
112
|
name: "config_bot",
|
|
117
113
|
system_prompt: "You are concise. Answer in one sentence.",
|
|
118
114
|
config: config
|
|
@@ -126,9 +122,7 @@ puts ""
|
|
|
126
122
|
|
|
127
123
|
# ── Summary ──────────────────────────────────────────────────────
|
|
128
124
|
|
|
129
|
-
|
|
130
|
-
puts "Summary"
|
|
131
|
-
puts ""
|
|
125
|
+
section "Summary"
|
|
132
126
|
puts <<~SUMMARY
|
|
133
127
|
on_content: callback — wired at build time, fires every run()
|
|
134
128
|
run() { |chunk| ... } — per-call streaming block
|