Skip to content

.NET: Align skill folder discovery with spec#5078

Open
SergeyMenshykh wants to merge 9 commits intomicrosoft:mainfrom
SergeyMenshykh:agent-skills-validation
Open

.NET: Align skill folder discovery with spec#5078
SergeyMenshykh wants to merge 9 commits intomicrosoft:mainfrom
SergeyMenshykh:agent-skills-validation

Conversation

@SergeyMenshykh
Copy link
Copy Markdown
Member

@SergeyMenshykh SergeyMenshykh commented Apr 3, 2026

This PR adds support for discovering skill scripts and resources from folders defined in the agent skill specification, with the ability to specify other folders if needed via options.

SergeyMenshykh and others added 8 commits April 1, 2026 11:51
The filtered solution file is generated dynamically by
eng/scripts/New-FilteredSolution.ps1 during CI. Checking it in
risks it becoming stale and out-of-sync with the real solution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The filtered solution file is generated dynamically by
eng/scripts/New-FilteredSolution.ps1 during CI. Checking it in
risks it becoming stale and out-of-sync with the real solution.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@markwallace-microsoft markwallace-microsoft added documentation Improvements or additions to documentation .NET labels Apr 3, 2026
@github-actions github-actions bot changed the title Discover scripts and resources from folders defined in spec .NET: Discover scripts and resources from folders defined in spec Apr 3, 2026
@SergeyMenshykh SergeyMenshykh changed the title .NET: Discover scripts and resources from folders defined in spec .NET: Align skill folder discovery with spec Apr 3, 2026
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SergeyMenshykh SergeyMenshykh self-assigned this Apr 3, 2026
@SergeyMenshykh SergeyMenshykh moved this to In Review in Agent Framework Apr 3, 2026
@SergeyMenshykh SergeyMenshykh marked this pull request as ready for review April 3, 2026 12:40
Copilot AI review requested due to automatic review settings April 3, 2026 12:40
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the .NET file-based skill discovery implementation to align script/resource folder scanning with the Agent Skills specification, while allowing callers to override the scanned folders via new options.

Changes:

  • Added ScriptFolders and ResourceFolders to AgentFileSkillsSourceOptions to control which subfolders are scanned (including support for "." to include skill-root files).
  • Updated AgentFileSkillsSource to scan only the configured folders (non-recursive within each folder) and added validation/filtering for configured folder values.
  • Updated and expanded unit tests to reflect spec folder names (scripts/, references/, assets/) and new configuration behaviors.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 4 comments.

File Description
dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSourceOptions.cs Introduces new options for script/resource folder discovery defaults and overrides.
dotnet/src/Microsoft.Agents.AI/Skills/File/AgentFileSkillsSource.cs Implements folder-based scanning and folder name validation/filtering for scripts/resources.
dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/FileAgentSkillLoaderTests.cs Updates resource discovery tests for spec folders and adds tests for folder configuration/validation.
dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/AgentFileSkillsSourceScriptTests.cs Updates script discovery expectations and adds tests for nested/dot-slash script folder configuration.

Comment on lines +573 to +578
// Reject absolute paths and any path segments that escape upward.
if (Path.IsPathRooted(folder) || folder.Contains("..", StringComparison.Ordinal))
{
LogFolderNameSkippedInvalid(logger, folder);
continue;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FilterValidFolderNames rejects any folder string containing ".." anywhere, which will also skip legitimate relative folders like "my..scripts" even though they don’t escape upward. Consider validating path traversal by splitting into segments and rejecting only segments equal to ".." (and optionally normalizing separators) so valid names aren’t blocked.

Copilot uses AI. Check for mistakes.
Comment on lines +557 to +581
private static IEnumerable<string> FilterValidFolderNames(IEnumerable<string> folders, ILogger logger)
{
foreach (string folder in folders)
{
if (string.IsNullOrWhiteSpace(folder))
{
throw new ArgumentException("Folder names must not be null or whitespace.", nameof(folders));
}

// "." is valid — it means the skill root directory.
if (string.Equals(folder, RootFolderIndicator, StringComparison.Ordinal))
{
yield return folder;
continue;
}

// Reject absolute paths and any path segments that escape upward.
if (Path.IsPathRooted(folder) || folder.Contains("..", StringComparison.Ordinal))
{
LogFolderNameSkippedInvalid(logger, folder);
continue;
}

yield return folder;
}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Folder values aren’t normalized before Distinct/scanning. If callers pass both "references" and "./references" (or include trailing slashes), the same directory will be scanned multiple times and duplicate resources/scripts with identical names can be produced. Normalize folder strings in FilterValidFolderNames (e.g., trim leading "./"/"." and trailing separators) before storing them.

Copilot uses AI. Check for mistakes.
Comment on lines +317 to +336
foreach (string folder in this._resourceFolders.Distinct(StringComparer.OrdinalIgnoreCase))
{
string fileName = Path.GetFileName(filePath);
string targetDirectory = string.Equals(folder, RootFolderIndicator, StringComparison.Ordinal)
? skillDirectoryFullPath
: Path.Combine(skillDirectoryFullPath, folder);

// Exclude SKILL.md itself
if (string.Equals(fileName, SkillFileName, StringComparison.OrdinalIgnoreCase))
if (!Directory.Exists(targetDirectory))
{
continue;
}

// Filter by extension
string extension = Path.GetExtension(filePath);
if (string.IsNullOrEmpty(extension) || !this._allowedResourceExtensions.Contains(extension))
#if NET
var enumerationOptions = new EnumerationOptions
{
RecurseSubdirectories = false,
IgnoreInaccessible = true,
AttributesToSkip = FileAttributes.ReparsePoint,
};

foreach (string filePath in Directory.EnumerateFiles(targetDirectory, "*", enumerationOptions))
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When targetDirectory itself is a symlink/reparse point (e.g., references/ symlinked outside), the code will still enumerate that external directory and then filter files out via the containment check. That can be abused for unnecessary I/O (DoS) and is avoidable—consider detecting FileAttributes.ReparsePoint on targetDirectory and skipping enumeration of that folder entirely.

Copilot uses AI. Check for mistakes.
Comment on lines +34 to +49
/// <summary>
/// Gets or sets the sub-folder names to scan for script files, relative to the skill directory.
/// Use <c>"."</c> to include files directly at the skill root.
/// When <see langword="null"/>, defaults to <c>scripts</c> (per the
/// <see href="https://agentskills.io/specification">Agent Skills specification</see>).
/// When set, replaces the defaults entirely.
/// </summary>
public IEnumerable<string>? ScriptFolders { get; set; }

/// <summary>
/// Gets or sets the sub-folder names to scan for resource files, relative to the skill directory.
/// Use <c>"."</c> to include files directly at the skill root.
/// When <see langword="null"/>, defaults to <c>references</c> and <c>assets</c> (per the
/// <see href="https://agentskills.io/specification">Agent Skills specification</see>).
/// When set, replaces the defaults entirely.
/// </summary>
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The XML docs say “sub-folder names”, but the implementation/tests accept multi-segment relative paths (e.g., "f1/f2/f3", "./scripts/f1") and "." for the root. Please update the documentation to describe these as relative folder paths (and mention normalization expectations) to avoid misleading API consumers.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation .NET

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

4 participants