Class: Clacky::Tools::InvokeSkill

Inherits:
Base
  • Object
show all
Defined in:
lib/clacky/tools/invoke_skill.rb

Overview

Tool for invoking skills within the agent This allows the AI to call skills as tools rather than requiring explicit user commands

Instance Method Summary collapse

Methods inherited from Base

#category, #description, #name, #parameters, #to_function_definition

Instance Method Details

#execute(skill_name:, task:, agent: nil, skill_loader: nil, working_dir: nil) ⇒ Hash

Execute the skill invocation

Parameters:

  • skill_name (String)

    Name of the skill to invoke

  • task (String)

    Task description to pass to the skill

  • agent (Clacky::Agent) (defaults to: nil)

    Agent instance (injected)

  • skill_loader (Clacky::SkillLoader) (defaults to: nil)

    Skill loader instance (injected)

Returns:

  • (Hash)

    Result of skill execution



32
33
34
35
36
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
# File 'lib/clacky/tools/invoke_skill.rb', line 32

def execute(skill_name:, task:, agent: nil, skill_loader: nil, working_dir: nil)
  # Validate injected dependencies
  return { error: "Agent context is required" } unless agent
  return { error: "Skill loader is required" } unless skill_loader

  # Find skill by name
  skill = skill_loader.find_by_name(skill_name)
  return { error: "Skill not found: #{skill_name}" } unless skill

  # Execute skill based on its configuration.
  # Note: disable-model-invocation only prevents the skill from appearing in AVAILABLE SKILLS
  # (so the model won't auto-discover it). It does NOT block execution here — the user may
  # have triggered this skill explicitly via a slash command (/skill-name).
  if skill.fork_agent?
    # Execute in isolated subagent
    result = agent.send(:execute_skill_with_subagent, skill, task)
    {
      message: "Skill '#{skill_name}' executed in subagent",
      result: result,
      skill_type: "subagent"
    }
  else
    # Deferred injection path: enqueue the skill inject on the agent.
    #
    # Injecting inside execute() would produce an illegal message ordering for Bedrock:
    #   assistant: {toolUse: invoke_skill}
    #   assistant: {text: skill_instructions}   ← injected here (breaks pairing)
    #   user:      {toolResult: invoke_skill}   ← observe() appends this too late
    #
    # Instead, enqueue the injection so the agent loop can flush it AFTER observe()
    # appends the toolResult, producing the correct sequence:
    #   assistant: {toolUse: invoke_skill}
    #   user:      {toolResult: ...}            ← observe() appends first
    #   assistant: {text: skill_instructions}   ← flush_pending_injections runs here
    #   user:      "[SYSTEM] please proceed"
    agent.enqueue_injection(skill, task)
    "Skill '#{skill_name}' instructions expanded. Proceed to execute the task."
  end
end

#format_call(args) ⇒ String

Format the tool call for display

Parameters:

  • args (Hash)

    Tool arguments

Returns:

  • (String)

    Formatted call description



75
76
77
78
# File 'lib/clacky/tools/invoke_skill.rb', line 75

def format_call(args)
  skill = args[:skill_name] || args["skill_name"]
  "InvokeSkill(#{skill})"
end

#format_result(result) ⇒ String

Format the tool result for display

Parameters:

  • result (Hash)

    Tool execution result

Returns:

  • (String)

    Formatted result summary



83
84
85
86
87
88
89
90
91
92
93
# File 'lib/clacky/tools/invoke_skill.rb', line 83

def format_result(result)
  if result.is_a?(String)
    result
  elsif result[:error]
    "Error: #{result[:error]}"
  elsif result[:skill_type] == "subagent"
    "Subagent executed successfully"
  else
    "Skill content expanded"
  end
end