robot_lab 0.3.0 → 0.3.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/CHANGELOG.md +12 -0
- data/README.md +1 -1
- data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -1
- data/lib/robot_lab/version.rb +1 -1
- metadata +3 -253
- data/.architecture/AGENTS.md +0 -32
- data/.architecture/config.yml +0 -8
- data/.architecture/members.yml +0 -60
- data/.architecture/reviews/feature-free-will.md +0 -490
- data/.architecture/reviews/overall-codebase.md +0 -427
- data/.claude/settings.local.json +0 -57
- data/.codex/config.toml +0 -2
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.irbrc +0 -6
- data/.loki +0 -65
- data/.quality/reek_baseline.txt +0 -43
- data/.rubocop.yml +0 -10
- data/Archspec.rb +0 -44
- data/CLAUDE.md +0 -141
- data/COMMITS.md +0 -196
- data/Rakefile +0 -193
- data/_typos.toml +0 -21
- data/agent2agent_review.md +0 -192
- data/agentf_improvements.md +0 -253
- data/agents.md +0 -14
- data/docs/api/core/index.md +0 -147
- data/docs/api/core/memory.md +0 -578
- data/docs/api/core/network.md +0 -553
- data/docs/api/core/result.md +0 -211
- data/docs/api/core/robot.md +0 -1667
- data/docs/api/core/state.md +0 -145
- data/docs/api/core/tool.md +0 -520
- data/docs/api/errors.md +0 -323
- data/docs/api/hooks.md +0 -469
- data/docs/api/index.md +0 -137
- data/docs/api/mcp/client.md +0 -387
- data/docs/api/mcp/index.md +0 -267
- data/docs/api/mcp/server.md +0 -252
- data/docs/api/mcp/transports.md +0 -339
- data/docs/api/messages/index.md +0 -126
- data/docs/api/messages/text-message.md +0 -170
- data/docs/api/messages/tool-call-message.md +0 -206
- data/docs/api/messages/tool-result-message.md +0 -242
- data/docs/api/messages/user-message.md +0 -232
- data/docs/api/skills.md +0 -423
- data/docs/api/streaming/context.md +0 -280
- data/docs/api/streaming/events.md +0 -185
- data/docs/api/streaming/index.md +0 -156
- data/docs/api/support.md +0 -846
- data/docs/architecture/core-concepts.md +0 -575
- data/docs/architecture/index.md +0 -252
- data/docs/architecture/message-flow.md +0 -366
- data/docs/architecture/network-orchestration.md +0 -478
- data/docs/architecture/robot-execution.md +0 -396
- data/docs/architecture/state-management.md +0 -410
- data/docs/assets/css/custom.css +0 -56
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +0 -641
- data/docs/examples/basic-chat.md +0 -258
- data/docs/examples/index.md +0 -425
- data/docs/examples/mcp-server.md +0 -403
- data/docs/examples/multi-robot-network.md +0 -476
- data/docs/examples/tool-usage.md +0 -468
- data/docs/getting-started/configuration.md +0 -645
- data/docs/getting-started/index.md +0 -56
- data/docs/getting-started/installation.md +0 -197
- data/docs/getting-started/quick-start.md +0 -293
- data/docs/guides/building-robots.md +0 -1187
- data/docs/guides/creating-networks.md +0 -736
- data/docs/guides/hooks.md +0 -1206
- data/docs/guides/index.md +0 -78
- data/docs/guides/knowledge.md +0 -219
- data/docs/guides/mcp-integration.md +0 -740
- data/docs/guides/memory.md +0 -458
- data/docs/guides/observability.md +0 -733
- data/docs/guides/streaming.md +0 -323
- data/docs/guides/using-tools.md +0 -780
- data/docs/index.md +0 -280
- data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
- data/examples/.envrc +0 -3
- data/examples/01_simple_robot.rb +0 -31
- data/examples/02_tools.rb +0 -109
- data/examples/03_network.rb +0 -118
- data/examples/04_mcp.rb +0 -217
- data/examples/05_streaming.rb +0 -137
- data/examples/06_prompt_templates.rb +0 -325
- data/examples/07_network_memory.rb +0 -313
- data/examples/08_llm_config.rb +0 -162
- data/examples/09_chaining.rb +0 -251
- data/examples/10_memory.rb +0 -315
- data/examples/11_network_introspection.rb +0 -239
- data/examples/12_message_bus.rb +0 -67
- data/examples/13_spawn.rb +0 -85
- data/examples/14_rusty_circuit/.envrc +0 -1
- data/examples/14_rusty_circuit/.gitignore +0 -1
- data/examples/14_rusty_circuit/README.md +0 -1
- data/examples/14_rusty_circuit/comic.rb +0 -149
- data/examples/14_rusty_circuit/display.rb +0 -217
- data/examples/14_rusty_circuit/heckler.rb +0 -65
- data/examples/14_rusty_circuit/open_mic.rb +0 -132
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
- data/examples/14_rusty_circuit/scout.rb +0 -165
- data/examples/14_rusty_circuit/show.log +0 -234
- data/examples/15_memory_network_and_bus/.envrc +0 -1
- data/examples/15_memory_network_and_bus/README.md +0 -66
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
- data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
- data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
- data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
- data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
- data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
- data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/memory.json +0 -13
- data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
- data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
- data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
- data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
- data/examples/16_writers_room/.envrc +0 -1
- data/examples/16_writers_room/display.rb +0 -158
- data/examples/16_writers_room/output/.gitignore +0 -4
- data/examples/16_writers_room/output/README.md +0 -69
- data/examples/16_writers_room/output/opus_001.md +0 -263
- data/examples/16_writers_room/output/opus_001_notes.log +0 -470
- data/examples/16_writers_room/output/opus_002.md +0 -245
- data/examples/16_writers_room/output/opus_002_notes.log +0 -546
- data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
- data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
- data/examples/16_writers_room/prompts/writer.md +0 -37
- data/examples/16_writers_room/room.rb +0 -186
- data/examples/16_writers_room/tools.rb +0 -173
- data/examples/16_writers_room/writer.rb +0 -121
- data/examples/16_writers_room/writers_room.rb +0 -256
- data/examples/17_skills.rb +0 -234
- data/examples/18_rails/.envrc +0 -3
- data/examples/18_rails/.gitignore +0 -5
- data/examples/18_rails/Gemfile +0 -11
- data/examples/18_rails/README.md +0 -67
- data/examples/18_rails/Rakefile +0 -4
- data/examples/18_rails/app/controllers/application_controller.rb +0 -4
- data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
- data/examples/18_rails/app/jobs/application_job.rb +0 -4
- data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
- data/examples/18_rails/app/models/application_record.rb +0 -5
- data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
- data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
- data/examples/18_rails/app/robots/chat_robot.rb +0 -25
- data/examples/18_rails/app/tools/time_tool.rb +0 -9
- data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
- data/examples/18_rails/app/views/chat/index.html.erb +0 -67
- data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
- data/examples/18_rails/bin/dev +0 -7
- data/examples/18_rails/bin/rails +0 -6
- data/examples/18_rails/bin/setup +0 -15
- data/examples/18_rails/config/application.rb +0 -33
- data/examples/18_rails/config/cable.yml +0 -2
- data/examples/18_rails/config/database.yml +0 -5
- data/examples/18_rails/config/environment.rb +0 -4
- data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
- data/examples/18_rails/config/routes.rb +0 -6
- data/examples/18_rails/config.ru +0 -4
- data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
- data/examples/19_token_tracking.rb +0 -138
- data/examples/20_circuit_breaker.rb +0 -147
- data/examples/21_learning_loop.rb +0 -181
- data/examples/22_context_compression.rb +0 -172
- data/examples/23_convergence.rb +0 -133
- data/examples/24_structured_delegation.rb +0 -151
- data/examples/25_history_search/conversation.jsonl +0 -30
- data/examples/25_history_search.rb +0 -133
- data/examples/26_document_store/api_versioning_adr.md +0 -52
- data/examples/26_document_store/incident_postmortem.md +0 -46
- data/examples/26_document_store/postgres_runbook.md +0 -49
- data/examples/26_document_store/redis_caching_guide.md +0 -48
- data/examples/26_document_store/sidekiq_guide.md +0 -51
- data/examples/26_document_store.rb +0 -140
- data/examples/27_incident_response/README.md +0 -65
- data/examples/27_incident_response/incident_response.rb +0 -261
- data/examples/28_mcp_discovery.rb +0 -113
- data/examples/29_ractor_tools.rb +0 -240
- data/examples/30_ractor_network.rb +0 -252
- data/examples/31_launch_assessment.rb +0 -246
- data/examples/32_newsletter_reader.rb +0 -193
- data/examples/33_stock_generator.rb +0 -80
- data/examples/33_stock_predictor.rb +0 -327
- data/examples/34_agentskills.rb +0 -76
- data/examples/35_hooks.rb +0 -266
- data/examples/README.md +0 -550
- data/examples/common.rb +0 -180
- data/examples/prompts/assistant.md +0 -5
- data/examples/prompts/audit_trail.md +0 -8
- data/examples/prompts/billing.md +0 -10
- data/examples/prompts/classifier.md +0 -7
- data/examples/prompts/comedian.md +0 -6
- data/examples/prompts/comedy_critic.md +0 -10
- data/examples/prompts/configurable.md +0 -9
- data/examples/prompts/dispatcher.md +0 -12
- data/examples/prompts/entity_extractor.md +0 -14
- data/examples/prompts/escalation.md +0 -42
- data/examples/prompts/frontmatter_mcp_test.md +0 -9
- data/examples/prompts/frontmatter_named_test.md +0 -5
- data/examples/prompts/frontmatter_tools_test.md +0 -6
- data/examples/prompts/general.md +0 -7
- data/examples/prompts/github_assistant.md +0 -14
- data/examples/prompts/helper.md +0 -4
- data/examples/prompts/incident_responder.md +0 -18
- data/examples/prompts/keyword_extractor.md +0 -11
- data/examples/prompts/llm_config_demo.md +0 -20
- data/examples/prompts/order_support.md +0 -35
- data/examples/prompts/os_advocate.md +0 -13
- data/examples/prompts/os_chief.md +0 -13
- data/examples/prompts/os_editor.md +0 -13
- data/examples/prompts/parameterized_main_test.md +0 -6
- data/examples/prompts/pii_redactor.md +0 -7
- data/examples/prompts/product_support.md +0 -37
- data/examples/prompts/runbook_protocol.md +0 -12
- data/examples/prompts/sentiment_analyzer.md +0 -12
- data/examples/prompts/skill_a_test.md +0 -4
- data/examples/prompts/skill_b_test.md +0 -4
- data/examples/prompts/skill_config_test.md +0 -5
- data/examples/prompts/skill_cycle_a_test.md +0 -6
- data/examples/prompts/skill_cycle_b_test.md +0 -6
- data/examples/prompts/skill_description_test.md +0 -4
- data/examples/prompts/skill_leaf_test.md +0 -4
- data/examples/prompts/skill_nested_test.md +0 -6
- data/examples/prompts/skill_refs_main_test.md +0 -6
- data/examples/prompts/skill_self_ref_test.md +0 -6
- data/examples/prompts/skill_with_mcp_test.md +0 -9
- data/examples/prompts/skill_with_params_test.md +0 -6
- data/examples/prompts/skill_with_robot_name_test.md +0 -5
- data/examples/prompts/skill_with_tools_test.md +0 -6
- data/examples/prompts/sre_compliance.md +0 -10
- data/examples/prompts/structured_output.md +0 -7
- data/examples/prompts/synthesizer.md +0 -17
- data/examples/prompts/technical.md +0 -10
- data/examples/prompts/template_with_skills_test.md +0 -6
- data/examples/prompts/triage.md +0 -22
- data/examples/ruboruby.md +0 -423
- data/examples/run_all.rb +0 -60
- data/examples/xyzzy.rb +0 -97
- data/logfile +0 -8
- data/mkdocs.yml +0 -209
- data/robot_concurrency.md +0 -38
- data/simple_acp_review.md +0 -298
- data/temp.md +0 -6
- 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
data/examples/01_simple_robot.rb
DELETED
|
@@ -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
|
data/examples/03_network.rb
DELETED
|
@@ -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
|