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.mdexactly likeLoad.Skills— a list ofSkillDef(Name, Description, Content, Resources?, Base?). Each becomes a skill with a logicalskill://name/base (or your ownBase) instead of afile://path, and never reads the filesystem.Filter— a name→bool allowlist applied after discovery: empty/null keeps everything; ≥1trueentry switches to allowlist mode (onlytrue-mapped names survive); an all-falsemap is a drop-list over the all-on baseline.SampleLimit— how many sibling/resource files theskilltool’s<skill_files>block samples;0is the default 10, a positive number caps it,-1omits the block entirely.
Dirs and Skills are not exclusive — both can be set, and the two sources merge.
When to use it
Section titled “When to use it”- 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.
Why this and not the alternative
Section titled “Why this and not the alternative”Examples
Section titled “Examples”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)}");3. Capping the file-sampling block
Section titled “3. Capping the file-sampling block”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");Fields
Section titled “Fields”| 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>. |
See also
Section titled “See also”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.