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



104
105
106
107
108
109
110
111
112
113
114
# File 'lib/clacky/tools/invoke_skill.rb', line 104

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

#format_result_for_llm(result) ⇒ String

Format the tool result for the LLM. Converts the raw Hash into a markdown string so the LLM can directly read the subagent output without parsing a JSON wrapper. JSON-encoding the result as a nested string caused models to miss the "result" field and hallucinate that the subagent returned nothing.

Parameters:

  • result (Hash, String)

    Tool execution result

Returns:

  • (String)

    LLM-friendly formatted result



87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/clacky/tools/invoke_skill.rb', line 87

def format_result_for_llm(result)
  if result.is_a?(String)
    result
  elsif result[:error]
    "Error: #{result[:error]}"
  elsif result[:skill_type] == "subagent"
    subagent_result = result[:result].to_s.strip
    subagent_result = "(subagent produced no output)" if subagent_result.empty?
    "Subagent executed successfully.\n\n#{subagent_result}"
  else
    "Skill content expanded"
  end
end