Toolnexus.Skill.load (data, providers, filters)
Elixir · package toolnexus · SPEC §3 · elixir/lib/toolnexus/skill.ex
@spec load(String.t() | [String.t()] | keyword() | map()) :: Toolnexus.Skill.Source.t()def load(input)
# options relevant here:# :skills — [%{name:, description:, content:, resources:, base:}]# :provider — (-> [skill-data]) resolved once# :filter — %{name => boolean}There is no separate load_with function in this port — Toolnexus.Skill.load/1 is one
function that accepts :dirs, :skills, :provider and :filter together, and merges whatever
you give it into a single skill source. This page is scoped to the three options
Skill.load (directories) doesn’t cover: supplying skills as data,
supplying them from a lazy provider, and narrowing the result with an allowlist.
When to use it
Section titled “When to use it”- Skills that don’t live on disk — generated at runtime, pulled from a database, assembled
from a config file.
:skillstakes the same shapeload/1would otherwise parse out of aSKILL.md, minus the parsing. - Skills that are expensive to enumerate —
:provideris a 0-arity function resolved exactly once, so you can defer a database call or an API fetch untilload/1actually runs, without eagerly building the list every time your module loads. - The same catalog, narrowed per agent —
:filteris aname => booleanallowlist/droplist applied after directories,:skillsand:providerare all merged, so one skill inventory can back several agents with different subsets.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”1. Skills supplied as data — no disk, no SKILL.md
Section titled “1. Skills supplied as data — no disk, no SKILL.md”A data-sourced skill gets origin: :logical and a skill://name/ base instead of a file://
directory — nothing here ever touches the filesystem.
alias Toolnexus.{Context, Skill}
source = Skill.load( skills: [ %{ name: "changelog", description: "Draft a changelog entry from a diff", content: "1. Summarize the diff.\n2. Pick a Conventional Commits type.", resources: ["templates/entry.md", "templates/breaking.md"] } ] )
[info] = source.skillstrue = info.name == "changelog"true = info.origin == :logicaltrue = info.base == "skill://changelog/"
res = source.tool.execute.(%{"name" => "changelog"}, %Context{})false = res.is_errortrue = String.contains?(res.output, "Base directory for this skill: skill://changelog/")true = String.contains?(res.output, "<file>templates/entry.md</file>")
IO.puts("ok: #{info.name} (#{info.origin}) -> #{info.base}")2. A lazy :provider, resolved once, isolated on failure
Section titled “2. A lazy :provider, resolved once, isolated on failure”:provider is called exactly once when load/1 runs. If it raises or returns something that
isn’t a list, the failure is isolated with a warning — exactly like one MCP server failing to
connect never takes the others down.
alias Toolnexus.Skill
calls = :counters.new(1, [])
provider = fn -> :counters.add(calls, 1, 1) [%{name: "triage", description: "Triage an incoming bug report", content: "1. Reproduce it."}]end
source = Skill.load(provider: provider)
true = Enum.map(source.skills, & &1.name) == ["triage"]true = :counters.get(calls, 1) == 1
# A failing provider does not crash load/1 — it just contributes nothing.failing = fn -> raise "upstream unavailable" endempty_source = Skill.load(provider: failing)[] = empty_source.skillstrue = empty_source.tool.name == "skill"
IO.puts("ok: provider called #{:counters.get(calls, 1)}x, failure isolated")3. :filter narrows a merged directory + data catalog
Section titled “3. :filter narrows a merged directory + data catalog”Directories and data skills merge first; :filter is applied to the merged result, so it can drop
names from either source alike. has_true semantics: if the filter contains at least one true,
it becomes an allowlist (only those survive); otherwise it’s a droplist (only false entries are
removed).
alias Toolnexus.Skill
repo = System.get_env("TOOLNEXUS_REPO") || "."dir = Path.join([repo, "examples", "skills"])
config = [ dirs: [dir], skills: [ %{name: "triage", description: "Triage a bug report", content: "1. Reproduce it."}, %{name: "release", description: "Cut a release", content: "1. Bump the version."} ]]
# No filter: everything from every source.all = Skill.load(config)true = all.skills |> Enum.map(& &1.name) |> Enum.sort() == ["hello-world", "release", "triage"]
# Allowlist mode: at least one `true` present, so only those survive.allow = Skill.load(Keyword.put(config, :filter, %{"triage" => true}))true = Enum.map(allow.skills, & &1.name) == ["triage"]
# Droplist mode: no `true` present, so only `false` entries are removed.drop = Skill.load(Keyword.put(config, :filter, %{"release" => false}))true = drop.skills |> Enum.map(& &1.name) |> Enum.sort() == ["hello-world", "triage"]
IO.puts("ok: all=#{length(all.skills)} allow=#{length(allow.skills)} drop=#{length(drop.skills)}")Options (this page’s scope)
Section titled “Options (this page’s scope)”| Option | Default | What it does |
|---|---|---|
:skills |
[] |
Skills as data: %{name:, description:, content:, resources:, base:}. resources is the sibling-file list the skill tool samples from (no disk walk); base defaults to skill://<name>/. |
:provider |
nil |
A 0-arity function returning a list in the same shape as :skills, resolved exactly once. A raise, or a non-list return, is isolated with a stderr warning — the rest of the source still loads. |
:filter |
nil |
%{name => boolean}. nil/%{} means all. If any value is true, the filter is an allowlist (only true names survive); otherwise it’s a droplist (only false names are dropped). A name that matches no skill logs a warning. Identical semantics to the MCP tools filter and the builtins tools toggle map. |
See also
Section titled “See also”Toolnexus.Skill.load— the directory-sourced form and theskilltool shapeToolnexus.Skill.list— discovery and skip reasons, unfiltered, for authoring the allowlistToolnexus.Tool— whatsource.toolis