Skip to content

Commit 00d6fb3

Browse files
committed
skill: add inherit directive for tool inheritance (#18)
Port of the `tools: inherit` directive from llm.rb's skill implementation to mruby-llm. When a skill sets `tools: inherit` in its SKILL.md frontmatter, the sub-agent spawned by the skill inherits tools from its parent context or agent instead of declaring a fixed list.
1 parent 4442f72 commit 00d6fb3

3 files changed

Lines changed: 75 additions & 14 deletions

File tree

mrblib/mruby-llm/skill.rb

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ def initialize(path)
5656
@instructions = ""
5757
@frontmatter = LLM::Object.from({})
5858
@tools = []
59+
@inherit_tools = false
5960
end
6061

6162
##
@@ -74,7 +75,8 @@ def load!
7475
# @param [LLM::Context] ctx
7576
# @return [Hash]
7677
def call(ctx)
77-
instructions, tools, tracer = self.instructions, self.tools, ctx.llm.tracer
78+
instructions, tracer = self.instructions, ctx.llm.tracer
79+
tools = @inherit_tools ? [*ctx.params[:tools]].reject(&:skill?) : self.tools
7880
params = ctx.params.merge(mode: ctx.mode).reject { |key, _| [:tools, :schema].include?(key) }
7981
concurrency = params[:stream].extra[:concurrency] if LLM::Stream === params[:stream]
8082
params[:concurrency] = concurrency if concurrency
@@ -110,6 +112,13 @@ def to_tool(ctx)
110112
end
111113
end
112114

115+
##
116+
# Returns true when a skill should inherit tools from its parent
117+
# @return [Boolean]
118+
def inherit_tools?
119+
@inherit_tools
120+
end
121+
113122
private
114123

115124
def messages_for(ctx)
@@ -131,8 +140,25 @@ def parse(content)
131140
@frontmatter = LLM::Object.from(LLM::YAML.safe_load(match[1]) || {})
132141
@name = @frontmatter.name || @name
133142
@description = @frontmatter.description || @description
134-
@tools = [*@frontmatter.tools].map { LLM::Tool.find_by_name!(_1) }
143+
@inherit_tools, @tools = parse_tools(@frontmatter.tools)
135144
@instructions = content[match.end(0)..-1] || ""
136145
end
146+
147+
def parse_tools(tools)
148+
case tools
149+
when String
150+
tools == "inherit" ? [true, []] : raise_invalid_error!(tools)
151+
when Array
152+
[false, tools.map { LLM::Tool.find_by_name!(_1) }]
153+
when NilClass
154+
[false, []]
155+
else
156+
raise_invalid_error!(tools)
157+
end
158+
end
159+
160+
def raise_invalid_error!(tools)
161+
raise LLM::Error, "invalid value for tools key: '#{tools}'"
162+
end
137163
end
138164
end
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
name: inherit-skill
3+
description: Inherit skill
4+
tools: inherit
5+
---
6+
7+
Use inherited tools.

spec/skill_spec.rb

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
describe "LLM::Skill" do
44
let(:root) { File.join(File.dirname(__FILE__), "fixtures") }
5-
let(:skill_dir) { File.join(root, "skills", "frontmatter-skill") }
6-
let(:skill) { LLM::Skill.load(skill_dir) }
75

86
before do
97
Class.new(LLM::Tool) do
@@ -12,20 +10,50 @@
1210
end
1311

1412
describe ".load" do
15-
it "parses indented frontmatter names" do
16-
expect(skill.name).must_equal "frontmatter-skill"
17-
end
13+
describe "with a tool list" do
14+
let(:skill_dir) { File.join(root, "skills", "frontmatter-skill") }
15+
let(:skill) { LLM::Skill.load(skill_dir) }
1816

19-
it "parses indented frontmatter descriptions" do
20-
expect(skill.description).must_equal "Frontmatter skill"
21-
end
17+
it "parses indented frontmatter names" do
18+
expect(skill.name).must_equal "frontmatter-skill"
19+
end
20+
21+
it "parses indented frontmatter descriptions" do
22+
expect(skill.description).must_equal "Frontmatter skill"
23+
end
24+
25+
it "parses indented frontmatter tools" do
26+
expect(skill.tools.first.name).must_equal "frontmatter-read-file"
27+
end
28+
29+
it "keeps the body as instructions" do
30+
expect(skill.instructions).must_equal "Use frontmatter.\n"
31+
end
2232

23-
it "parses indented frontmatter tools" do
24-
expect(skill.tools.first.name).must_equal "frontmatter-read-file"
33+
it "does not inherit tools" do
34+
expect(skill.inherit_tools?).must_equal false
35+
end
2536
end
2637

27-
it "keeps the body as instructions" do
28-
expect(skill.instructions).must_equal "Use frontmatter.\n"
38+
describe "with tools: inherit" do
39+
let(:skill_dir) { File.join(root, "skills", "inherit-skill") }
40+
let(:skill) { LLM::Skill.load(skill_dir) }
41+
42+
it "parses indented frontmatter names" do
43+
expect(skill.name).must_equal "inherit-skill"
44+
end
45+
46+
it "parses indented frontmatter descriptions" do
47+
expect(skill.description).must_equal "Inherit skill"
48+
end
49+
50+
it "returns an empty tool list" do
51+
expect(skill.tools).must_be_empty
52+
end
53+
54+
it "inherits tools from parent" do
55+
expect(skill.inherit_tools?).must_equal true
56+
end
2957
end
3058
end
3159
end

0 commit comments

Comments
 (0)