Packages
fnord
0.9.28
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/skills/runtime.ex
defmodule Skills.Runtime do
@moduledoc """
Runtime helpers for executing skills.
This module owns the glue between skill definitions (TOML) and the runtime
components used to execute them:
- model preset parsing (skill `model` string -> `AI.Model.t()`)
- tool tag mapping (skill `tools` -> `AI.Tools.toolbox()`)
- response_format validation
Keeping these helpers in one place avoids duplicating the execution rules
between the agent (`AI.Agent.Skill`) and the tool entry points.
"""
@type model_error :: {:unknown_model_preset, String.t()}
@type tool_tag :: String.t()
@type toolbox_error ::
{:unknown_tool_tag, tool_tag}
| {:missing_basic_tool_tag, [tool_tag]}
@type response_format_error ::
{:invalid_response_format, term()}
| {:missing_response_format_type, map()}
@doc """
Resolve a model preset string (from skill TOML) into an `AI.Model` struct.
Supported values:
- `smart`
- `balanced`
- `fast`
- `web`
- `large_context`
- `large_context:<speed>` where speed is `smart|balanced|fast`
The plain `large_context` form preserves the default behavior by calling
`AI.Model.large_context/0`.
"""
@spec model_from_string(String.t()) :: {:ok, AI.Model.t()} | {:error, model_error}
def model_from_string(model) when is_binary(model) do
case String.split(model, ":", parts: 2) do
["smart"] -> {:ok, AI.Model.smart()}
["balanced"] -> {:ok, AI.Model.balanced()}
["fast"] -> {:ok, AI.Model.fast()}
["web"] -> {:ok, AI.Model.web_search()}
["large_context"] -> {:ok, AI.Model.large_context()}
["large_context", speed] -> large_context_with_speed(speed)
_ -> {:error, {:unknown_model_preset, model}}
end
end
defp large_context_with_speed(speed) do
case speed do
"smart" -> {:ok, AI.Model.large_context(:smart)}
"balanced" -> {:ok, AI.Model.large_context(:balanced)}
"fast" -> {:ok, AI.Model.large_context(:fast)}
_ -> {:error, {:unknown_model_preset, "large_context:#{speed}"}}
end
end
@doc """
Build a toolbox from skill tool tags.
Tags are mapped to `AI.Tools.with_*` groupers. Toolbox construction is
deterministic and ignores input order.
The `basic` tag is required; it acts as the toolbox entrypoint.
"""
@spec toolbox_from_tags([tool_tag]) :: {:ok, AI.Tools.toolbox()} | {:error, toolbox_error}
def toolbox_from_tags(tags) when is_list(tags) do
tags =
tags
|> Enum.filter(&is_binary/1)
|> Enum.uniq()
allowed_tags = AI.Tools.skill_tool_tags()
case Enum.find(tags, fn tag -> not Enum.member?(allowed_tags, tag) end) do
unknown_tag when is_binary(unknown_tag) ->
{:error, {:unknown_tool_tag, unknown_tag}}
nil ->
if Enum.member?(tags, "basic") do
tags
|> build_toolbox_from_tags()
|> then(&{:ok, &1})
else
{:error, {:missing_basic_tool_tag, tags}}
end
end
end
defp build_toolbox_from_tags(tags) do
base = AI.Tools.basic_tools()
# Apply tags in a stable order.
Enum.reduce(stable_tag_order(), base, fn tag, toolbox ->
if Enum.member?(tags, tag) do
apply_tool_tag(tag, toolbox)
else
toolbox
end
end)
end
defp stable_tag_order(), do: AI.Tools.stable_skill_tool_tag_order()
defp apply_tool_tag(tag, toolbox) do
case tag do
"mcp" -> AI.Tools.with_mcps(toolbox)
"frobs" -> AI.Tools.with_frobs(toolbox)
"task" -> AI.Tools.with_task_tools(toolbox)
"coding" -> AI.Tools.with_coding_tools(toolbox)
"web" -> AI.Tools.with_web_tools(toolbox)
"ui" -> AI.Tools.with_ui(toolbox)
"rw" -> AI.Tools.with_rw_tools(toolbox)
"skills" -> AI.Tools.with_skills(toolbox)
_ -> raise "Unknown tool tag reached apply_tool_tag unexpectedly: #{tag}"
end
end
@doc """
Validate a response_format value from a skill.
`nil` is allowed and means default text responses.
When present, the response format must be a map and should include a `type`
key.
"""
@spec validate_response_format(nil | map()) ::
{:ok, nil | map()} | {:error, response_format_error}
def validate_response_format(nil), do: {:ok, nil}
def validate_response_format(%{} = map) do
case Map.get(map, "type") || Map.get(map, :type) do
nil -> {:error, {:missing_response_format_type, map}}
type when is_binary(type) and byte_size(type) > 0 -> {:ok, map}
other -> {:error, {:invalid_response_format, other}}
end
end
def validate_response_format(other), do: {:error, {:invalid_response_format, other}}
# ---------------------------------------------------------------------------
# Reasoning preamble
#
# Injected before every skill's system_prompt to establish baseline reasoning
# discipline. This is a distilled version of the coordinator's reasoning and
# evidence-hygiene guidelines, adapted for autonomous skill agents that don't
# have interactive back-and-forth with the user.
# ---------------------------------------------------------------------------
@reasoning_preamble """
## Reasoning discipline
Think step-by-step. Establish facts, then relationships, then conclusions.
Evidence hygiene:
- Cite only observable artifacts (file paths, line numbers, function names, log output).
- Connect facts explicitly: "X because Y" - not "X might be related to Y."
- Prefer the minimal sufficient chain of evidence. Short, correct, and traceable
beats long and speculative.
Validation and uncertainty:
- Identify assumptions and validate them against the source before relying on them.
- If uncertainty remains after investigation, state it plainly and explain what
would resolve it. Do not speculate past what the evidence supports.
- Tag unknowns explicitly (e.g., "Uncertain: X - could not confirm because Y").
Critical stance:
- Challenge weak premises and missing data early.
- Do not guess when the risk of being wrong is high. Say what you don't know.
"""
@doc """
Returns the reasoning preamble that is prepended to every skill's system prompt.
This establishes baseline reasoning discipline for all skill agents.
"""
@spec reasoning_preamble() :: String.t()
def reasoning_preamble, do: @reasoning_preamble
@doc """
Return the list of allowed toolboxes.
Toolboxes are the skill's tool tags; they select tool groups.
"""
@spec allowed_toolboxes() :: [tool_tag]
def allowed_toolboxes(), do: AI.Tools.skill_tool_tags()
@doc """
Return the list of allowed model presets.
This list is intended for interactive selection.
"""
@spec allowed_model_presets() :: [String.t()]
def allowed_model_presets() do
[
"smart",
"balanced",
"fast",
"web",
"large_context",
"large_context:smart",
"large_context:balanced",
"large_context:fast"
]
end
end