Skip to content

SkillSource.LoadWith

C# · package Toolnexus · SPEC §3 · SkillSource.cs

public static SkillSource LoadWith(SkillSource.LoadOptions opts)
public sealed class LoadOptions
{
public IEnumerable<string>? Dirs { get; set; }
public IReadOnlyList<SkillSource.SkillDef>? Skills { get; set; }
public IReadOnlyDictionary<string, bool>? Filter { get; set; }
public int SampleLimit { get; set; } // 0 => default 10, n>0 => cap, -1 => omit <skill_files>
}

The general-purpose skill loader behind SkillSource.Load. Load(dirs) is sugar for LoadWith(new LoadOptions { Dirs = dirs })LoadWith is where the rest of §3 lives: skills supplied as data (Skills, no filesystem touched), and a per-agent allowlist (Filter) narrowing whichever skills were discovered.

  • Dirs — one or more roots, globbed for **/SKILL.md exactly like Load.
  • Skills — a list of SkillDef(Name, Description, Content, Resources?, Base?). Each becomes a skill with a logical skill://name/ base (or your own Base) instead of a file:// path, and never reads the filesystem.
  • Filter — a name→bool allowlist applied after discovery: empty/null keeps everything; ≥1 true entry switches to allowlist mode (only true-mapped names survive); an all-false map is a drop-list over the all-on baseline.
  • SampleLimit — how many sibling/resource files the skill tool’s <skill_files> block samples; 0 is the default 10, a positive number caps it, -1 omits the block entirely.

Dirs and Skills are not exclusive — both can be set, and the two sources merge.

  • You have skills that don’t live on disk — generated from a database, fetched from an API, assembled in memory — and want them to behave exactly like filesystem skills to the model.
  • You’re serving multiple agents from one process and each should see a different subset of the same discovered skills (Filter).
  • You want to shrink or drop the <skill_files> sampling block (SampleLimit) for a smaller system prompt.

1. Skills supplied as data — no filesystem at all

Section titled “1. Skills supplied as data — no filesystem at all”
using Toolnexus;
var source = SkillSource.LoadWith(new SkillSource.LoadOptions
{
Skills = new List<SkillSource.SkillDef>
{
new("weather-briefing", "Summarize today's weather for a city", "# Weather Briefing\nAsk for a city, then summarize."),
},
});
if (!source.Skills.ContainsKey("weather-briefing")) throw new Exception("skill not registered");
if (source.Skills["weather-briefing"].Base != "skill://weather-briefing/")
throw new Exception($"unexpected base: {source.Skills["weather-briefing"].Base}");
var res = await source.Tool.ExecuteAsync(new Dictionary<string, object?> { ["name"] = "weather-briefing" });
if (res.IsError || !res.Output.Contains("Base directory for this skill: skill://weather-briefing/"))
throw new Exception(res.Output);
Console.WriteLine("ok: data-sourced skill, no disk touched");

2. Directory + data, narrowed with an allowlist

Section titled “2. Directory + data, narrowed with an allowlist”
using Toolnexus;
var repo = Environment.GetEnvironmentVariable("TOOLNEXUS_REPO") ?? ".";
var skillsDir = Path.Combine(repo, "examples", "skills");
var source = SkillSource.LoadWith(new SkillSource.LoadOptions
{
Dirs = new[] { skillsDir },
Skills = new List<SkillSource.SkillDef>
{
new("weather-briefing", "Summarize today's weather for a city", "# Weather Briefing"),
},
// Allowlist mode (>=1 true): only "hello-world" survives, even though two skills were discovered.
Filter = new Dictionary<string, bool> { ["hello-world"] = true },
});
if (!source.Skills.ContainsKey("hello-world")) throw new Exception("expected hello-world to survive the filter");
if (source.Skills.ContainsKey("weather-briefing")) throw new Exception("expected weather-briefing to be filtered out");
if (source.Skills.Count != 1) throw new Exception($"expected exactly 1 skill, got {source.Skills.Count}");
Console.WriteLine($"ok: {string.Join(", ", source.Skills.Keys)}");
using Toolnexus;
var withSampling = SkillSource.LoadWith(new SkillSource.LoadOptions
{
Skills = new List<SkillSource.SkillDef>
{
new("triage", "Route a support ticket", "# Triage", new[] { "playbook.md", "escalation.md", "contacts.md" }),
},
SampleLimit = 2, // cap the <skill_files> block at 2 entries
});
var capped = await withSampling.Tool.ExecuteAsync(new Dictionary<string, object?> { ["name"] = "triage" });
if (capped.Output.Split("<file>").Length - 1 != 2) throw new Exception(capped.Output);
var noSampling = SkillSource.LoadWith(new SkillSource.LoadOptions
{
Skills = new List<SkillSource.SkillDef>
{
new("triage", "Route a support ticket", "# Triage", new[] { "playbook.md", "escalation.md" }),
},
SampleLimit = -1, // omit <skill_files> entirely
});
var uncapped = await noSampling.Tool.ExecuteAsync(new Dictionary<string, object?> { ["name"] = "triage" });
if (uncapped.Output.Contains("<skill_files>")) throw new Exception("expected no <skill_files> block");
Console.WriteLine("ok: SampleLimit=2 capped, SampleLimit=-1 omitted");
Field Type What it is
Dirs IEnumerable<string>? Roots globbed for **/SKILL.md, same as Load.
Skills IReadOnlyList<SkillDef>? Skills supplied as data — logical base, no disk access.
Filter IReadOnlyDictionary<string, bool>? Post-discovery allowlist/drop-list over the merged skill set.
SampleLimit int 0 ⇒ default 10, n>0 ⇒ cap, -1 ⇒ omit <skill_files>.
  • SkillSource.Load — Glob a skills directory and expose one skill tool with progressive disclosure.
  • SkillSource.ListSkills — Enumerate discovered skills and, crucially, the ones that were skipped and why.