Packages
fnord
0.9.20
0.9.40
0.9.39
0.9.38
0.9.37
0.9.36
0.9.35
0.9.34
0.9.33
0.9.32
0.9.31
0.9.30
0.9.29
0.9.28
0.9.27
0.9.26
0.9.25
0.9.24
0.9.23
0.9.22
0.9.21
0.9.20
0.9.19
0.9.18
0.9.17
0.9.16
0.9.15
0.9.14
0.9.13
0.9.12
0.9.11
0.9.10
0.9.9
0.9.8
0.9.7
0.9.6
0.9.5
0.9.4
0.9.3
0.9.2
0.9.1
0.9.0
0.8.99
0.8.98
0.8.97
0.8.96
0.8.95
0.8.94
0.8.93
0.8.92
0.8.91
0.8.90
0.8.89
0.8.88
0.8.87
0.8.86
0.8.85
0.8.84
0.8.83
0.8.82
0.8.81
0.8.80
0.8.79
0.8.78
0.8.77
0.8.76
0.8.75
0.8.74
0.8.73
0.8.72
0.8.71
0.8.70
0.8.69
0.8.68
0.8.67
0.8.66
0.8.65
0.8.64
0.8.63
0.8.62
0.8.61
0.8.60
0.8.59
0.8.58
0.8.57
0.8.56
0.8.55
0.8.54
0.8.53
0.8.52
0.8.51
0.8.50
0.8.49
0.8.48
0.8.47
0.8.46
0.8.45
0.8.44
0.8.43
0.8.42
0.8.41
0.8.40
0.8.39
0.8.38
0.8.37
0.8.36
0.8.35
0.8.34
0.8.33
0.8.32
0.8.31
0.8.30
0.8.29
0.8.27
0.8.26
0.8.25
0.8.24
0.8.23
0.8.22
0.8.21
0.8.20
0.8.19
0.8.18
0.8.17
0.8.16
0.8.15
0.8.14
0.8.13
0.8.12
0.8.11
0.8.1
0.8.0
0.7.24
0.7.23
0.7.22
0.7.21
0.7.20
0.7.19
0.7.18
0.7.17
0.7.16
0.7.15
0.7.14
0.7.13
0.7.12
0.7.11
0.7.10
0.7.9
0.7.8
0.7.7
0.7.6
0.7.5
0.7.3
0.7.2
0.7.1
0.7.0
0.6.9
0.6.8
0.6.7
0.6.6
0.6.5
0.6.4
0.6.3
0.6.1
0.6.0
0.5.9
0.5.8
0.5.7
0.5.6
0.5.5
0.5.4
0.5.3
0.5.2
0.5.1
0.5.0
0.4.44
0.4.43
0.4.42
0.4.41
0.4.40
0.4.39
0.4.38
0.4.37
0.4.36
0.4.35
0.4.34
0.4.33
0.4.32
0.4.30
0.4.29
0.4.28
0.4.27
0.4.26
0.4.25
0.4.24
0.4.23
0.4.22
0.4.21
0.4.20
0.4.19
0.4.18
0.4.17
0.4.16
0.4.15
0.4.14
0.4.13
0.4.12
0.4.11
0.4.10
0.4.9
0.4.8
0.4.7
0.4.6
0.4.5
0.4.4
0.4.3
0.4.2
0.4.1
0.4.0
0.3.0
0.2.0
0.1.0
AI code archaeology
Current section
Files
Jump to
Current section
Files
lib/ai/tools/run_skill.ex
defmodule AI.Tools.RunSkill do
@moduledoc """
Execute an enabled Skill by name.
This is an integration point between the coordinator and Skills. It:
- lists skills dynamically in its tool spec,
- resolves a selected skill by name,
- enforces RW gating (skills requiring `rw` tools need `--edit`),
- enforces a recursion depth limit for skill-to-skill calls,
- runs the skill via `AI.Agent.Skill`.
"""
@behaviour AI.Tools
@tool_name "run_skill"
@impl AI.Tools
def async?, do: false
@impl AI.Tools
def is_available?, do: true
@impl AI.Tools
def read_args(%{} = args) do
with {:ok, skill} <- get_string(args, "skill"),
{:ok, prompt} <- get_string(args, "prompt") do
{:ok, %{"skill" => skill, "prompt" => prompt}}
end
end
defp get_string(args, key) do
case Map.fetch(args, key) do
{:ok, value} when is_binary(value) and byte_size(value) > 0 -> {:ok, value}
_ -> {:error, {:missing_arg, key}}
end
end
@impl AI.Tools
def spec() do
names = skills_names()
details = skills_descriptions()
%{
type: "function",
function: %{
name: @tool_name,
description: "Run a named skill.\n\nAvailable skills: #{names}",
parameters: %{
type: "object",
required: ["skill", "prompt"],
additionalProperties: false,
properties: %{
skill: %{
type: "string",
description: "Skill name. Available skills:\n#{details}"
},
prompt: %{
type: "string",
description: "Input prompt to send to the skill"
}
}
}
}
}
end
defp skills_names() do
case Skills.list_enabled() do
{:ok, []} -> "(none)"
{:ok, skills} -> Enum.map_join(skills, ", ", & &1.name)
{:error, _} -> "(none)"
end
end
defp skills_descriptions() do
case Skills.list_enabled() do
{:ok, []} ->
"(none found)"
{:ok, skills} ->
skills
|> Enum.map(fn %{name: name, effective: %{skill: skill}} ->
"- #{name}: #{skill.description}"
end)
|> Enum.join("\n")
{:error, reason} ->
"(error loading skills: #{inspect(reason)})"
end
end
@impl AI.Tools
def call(%{"skill" => name, "prompt" => prompt}) do
# Soft-gating: when max depth is reached, still run the skill but strip
# its ability to call other skills (mirrors the researcher pattern).
can_recurse? =
case Services.SkillDepth.inc_depth() do
{:ok, _depth} -> true
{:error, :max_depth_reached} -> false
end
try do
with {:ok, resolved} <- Skills.get_enabled(name),
:ok <- enforce_rw_gating(resolved) do
run_skill(resolved, prompt, not can_recurse?)
end
after
case can_recurse? do
true -> Services.SkillDepth.dec_depth()
false -> :ok
end
end
end
defp enforce_rw_gating(%{effective: %{skill: %Skills.Skill{tools: tools}}}) do
if Enum.member?(tools, "rw") and not Settings.get_edit_mode() do
{:error, {:denied, "Cannot run rw skills; the user did not pass --edit."}}
else
:ok
end
end
defp run_skill(%{effective: %{skill: skill}}, prompt, strip_skills?) do
agent = AI.Agent.new(AI.Agent.Skill)
AI.Agent.get_response(agent, %{
skill: skill,
prompt: prompt,
strip_skills?: strip_skills?
})
end
@impl AI.Tools
def ui_note_on_request(%{"skill" => skill}) do
{"Run skill", skill}
end
@impl AI.Tools
def ui_note_on_result(_args, _result) do
nil
end
@impl AI.Tools
def tool_call_failure_message(_args, _error) do
:default
end
end