Class: Rubino::Skills::SkillTool

Inherits:
Tools::Base show all
Defined in:
lib/rubino/skills/skill_tool.rb

Overview

Tool that allows the agent to load a skill on demand, and (Variant A —reference-style affordance) to CREATE a new skill inline during the turn.

The agent sees skill names/descriptions in the system prompt and can invoke this tool to load the full skill instructions into context, or — after a complex, repeatable task — to distil what it just did into a new skill with action: “create” (0 extra LLM calls; the create happens inline on the tool-call the model already emitted).

Constant Summary collapse

NAME_RE =

kebab-case, <=64 chars, mirrors the skill-creator frontmatter contract.

/\A[a-z0-9]+(?:-[a-z0-9]+)*\z/

Instance Attribute Summary

Attributes inherited from Tools::Base

#cancel_token, #read_tracker, #stream_chunk

Instance Method Summary collapse

Methods inherited from Tools::Base

#cancellation_requested?, #config_key, #emit_chunk, #risky?, #to_tool_definition, workspace_root, workspace_roots

Constructor Details

#initialize(registry: nil) ⇒ SkillTool

Returns a new instance of SkillTool.



19
20
21
# File 'lib/rubino/skills/skill_tool.rb', line 19

def initialize(registry: nil)
  @registry = registry || Registry.new
end

Instance Method Details

#call(arguments) ⇒ Object

action: “load” (default) — three-level progressive disclosure:

skill(name)                       -> Level 2: SKILL.md body
skill(name, file_path: "ref.md")  -> Level 3: one bundled file

action: “create” — write a new <name>/SKILL.md inline (Variant A).



82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/rubino/skills/skill_tool.rb', line 82

def call(arguments)
  action = (arguments["action"] || arguments[:action] || "load").to_s
  return create(arguments) if action == "create"

  skill_name = arguments["name"] || arguments[:name]
  file_path  = arguments["file_path"] || arguments[:file_path]

  skill = @registry.find(skill_name)
  return not_found(skill_name) unless skill
  return disabled(skill_name) unless @registry.enabled?(skill_name)

  return load_bundled_file(skill, skill_name, file_path) if file_path && !file_path.to_s.empty?

  load_body(skill, skill_name)
end

#descriptionObject



27
28
29
30
31
32
33
34
35
# File 'lib/rubino/skills/skill_tool.rb', line 27

def description
  "Load a specialized skill's instructions into context, or create a new " \
    "skill. action defaults to \"load\": use it when a task matches one of " \
    "the available skills listed under \"## Skills\" in the system prompt " \
    "(pass file_path to load a bundled file). After finishing a complex, " \
    "multi-step task (typically 5+ tool calls) that is likely to recur and " \
    "isn't already covered, call action: \"create\" with name, description, " \
    "and body to save it as a reusable skill."
end

#input_schemaObject



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/rubino/skills/skill_tool.rb', line 37

def input_schema
  {
    type: "object",
    properties: {
      action: {
        type: "string",
        enum: %w[load create],
        description: "\"load\" (default) loads an existing skill; " \
                     "\"create\" writes a new skill from name/description/body."
      },
      name: {
        type: "string",
        description: "The skill name. For load: the skill to load. " \
                     "For create: a kebab-case name (<=64 chars)."
      },
      file_path: {
        type: "string",
        description: "Optional (load only). Relative path of a bundled file within the " \
                     "skill (e.g. 'references/api.md', 'scripts/run.py') to load " \
                     "its contents. Use the linked_files listed when the skill " \
                     "body is first loaded."
      },
      description: {
        type: "string",
        description: "Required for create. One line: what the skill is for and WHEN " \
                     "it applies (the only text future runs see before loading it)."
      },
      body: {
        type: "string",
        description: "Required for create. The markdown body: proven step-by-step " \
                     "instructions, commands, and pitfalls. Be specific and prescriptive."
      }
    },
    required: %w[name]
  }
end

#nameObject



23
24
25
# File 'lib/rubino/skills/skill_tool.rb', line 23

def name
  "skill"
end

#risk_levelObject



74
75
76
# File 'lib/rubino/skills/skill_tool.rb', line 74

def risk_level
  :low
end