diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Program.cs
index f6b9b58b79..c9dc86e3b4 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Program.cs
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step01_FileBasedSkills/Program.cs
@@ -24,6 +24,7 @@
var skillsProvider = new AgentSkillsProvider(
Path.Combine(AppContext.BaseDirectory, "skills"),
SubprocessScriptRunner.RunAsync);
+
// --- Agent Setup ---
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new DefaultAzureCredential())
.GetResponsesClient()
diff --git a/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs b/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs
index 7f5e356a60..aa70c4461e 100644
--- a/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs
+++ b/dotnet/samples/02-agents/AgentSkills/Agent_Step03_ClassBasedSkills/Program.cs
@@ -51,7 +51,7 @@
/// Properties annotated with are automatically
/// discovered as skill resources, and methods annotated with
/// are automatically discovered as skill scripts. Alternatively,
-/// and can be overridden.
+/// and can be overridden.
///
internal sealed class UnitConverterSkill : AgentClassSkill
{
diff --git a/dotnet/src/Microsoft.Agents.AI/CompatibilitySuppressions.xml b/dotnet/src/Microsoft.Agents.AI/CompatibilitySuppressions.xml
index 6a2c790f22..a7f87560bf 100644
--- a/dotnet/src/Microsoft.Agents.AI/CompatibilitySuppressions.xml
+++ b/dotnet/src/Microsoft.Agents.AI/CompatibilitySuppressions.xml
@@ -43,6 +43,27 @@
lib/net10.0/Microsoft.Agents.AI.dll
true
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Content
+ lib/net10.0/Microsoft.Agents.AI.dll
+ lib/net10.0/Microsoft.Agents.AI.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Resources
+ lib/net10.0/Microsoft.Agents.AI.dll
+ lib/net10.0/Microsoft.Agents.AI.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Scripts
+ lib/net10.0/Microsoft.Agents.AI.dll
+ lib/net10.0/Microsoft.Agents.AI.dll
+ true
+
CP0002
M:Microsoft.Agents.AI.AgentSkillScript.RunAsync(Microsoft.Agents.AI.AgentSkill,Microsoft.Extensions.AI.AIFunctionArguments,System.Threading.CancellationToken)
@@ -106,6 +127,27 @@
lib/net472/Microsoft.Agents.AI.dll
true
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Content
+ lib/net472/Microsoft.Agents.AI.dll
+ lib/net472/Microsoft.Agents.AI.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Resources
+ lib/net472/Microsoft.Agents.AI.dll
+ lib/net472/Microsoft.Agents.AI.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Scripts
+ lib/net472/Microsoft.Agents.AI.dll
+ lib/net472/Microsoft.Agents.AI.dll
+ true
+
CP0002
M:Microsoft.Agents.AI.AgentSkillScript.RunAsync(Microsoft.Agents.AI.AgentSkill,Microsoft.Extensions.AI.AIFunctionArguments,System.Threading.CancellationToken)
@@ -169,6 +211,27 @@
lib/net8.0/Microsoft.Agents.AI.dll
true
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Content
+ lib/net8.0/Microsoft.Agents.AI.dll
+ lib/net8.0/Microsoft.Agents.AI.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Resources
+ lib/net8.0/Microsoft.Agents.AI.dll
+ lib/net8.0/Microsoft.Agents.AI.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Scripts
+ lib/net8.0/Microsoft.Agents.AI.dll
+ lib/net8.0/Microsoft.Agents.AI.dll
+ true
+
CP0002
M:Microsoft.Agents.AI.AgentSkillScript.RunAsync(Microsoft.Agents.AI.AgentSkill,Microsoft.Extensions.AI.AIFunctionArguments,System.Threading.CancellationToken)
@@ -232,6 +295,27 @@
lib/net9.0/Microsoft.Agents.AI.dll
true
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Content
+ lib/net9.0/Microsoft.Agents.AI.dll
+ lib/net9.0/Microsoft.Agents.AI.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Resources
+ lib/net9.0/Microsoft.Agents.AI.dll
+ lib/net9.0/Microsoft.Agents.AI.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Scripts
+ lib/net9.0/Microsoft.Agents.AI.dll
+ lib/net9.0/Microsoft.Agents.AI.dll
+ true
+
CP0002
M:Microsoft.Agents.AI.AgentSkillScript.RunAsync(Microsoft.Agents.AI.AgentSkill,Microsoft.Extensions.AI.AIFunctionArguments,System.Threading.CancellationToken)
@@ -295,6 +379,27 @@
lib/netstandard2.0/Microsoft.Agents.AI.dll
true
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Content
+ lib/netstandard2.0/Microsoft.Agents.AI.dll
+ lib/netstandard2.0/Microsoft.Agents.AI.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Resources
+ lib/netstandard2.0/Microsoft.Agents.AI.dll
+ lib/netstandard2.0/Microsoft.Agents.AI.dll
+ true
+
+
+ CP0002
+ M:Microsoft.Agents.AI.AgentSkill.get_Scripts
+ lib/netstandard2.0/Microsoft.Agents.AI.dll
+ lib/netstandard2.0/Microsoft.Agents.AI.dll
+ true
+
CP0002
M:Microsoft.Agents.AI.AgentSkillScript.RunAsync(Microsoft.Agents.AI.AgentSkill,Microsoft.Extensions.AI.AIFunctionArguments,System.Threading.CancellationToken)
@@ -316,6 +421,13 @@
lib/netstandard2.0/Microsoft.Agents.AI.dll
true
+
+ CP0005
+ M:Microsoft.Agents.AI.AgentSkill.GetContentAsync(System.Threading.CancellationToken)
+ lib/net10.0/Microsoft.Agents.AI.dll
+ lib/net10.0/Microsoft.Agents.AI.dll
+ true
+
CP0005
M:Microsoft.Agents.AI.AgentSkillScript.RunAsync(Microsoft.Agents.AI.AgentSkill,System.Nullable{System.Text.Json.JsonElement},System.IServiceProvider,System.Threading.CancellationToken)
@@ -323,6 +435,13 @@
lib/net10.0/Microsoft.Agents.AI.dll
true
+
+ CP0005
+ M:Microsoft.Agents.AI.AgentSkill.GetContentAsync(System.Threading.CancellationToken)
+ lib/net472/Microsoft.Agents.AI.dll
+ lib/net472/Microsoft.Agents.AI.dll
+ true
+
CP0005
M:Microsoft.Agents.AI.AgentSkillScript.RunAsync(Microsoft.Agents.AI.AgentSkill,System.Nullable{System.Text.Json.JsonElement},System.IServiceProvider,System.Threading.CancellationToken)
@@ -330,6 +449,13 @@
lib/net472/Microsoft.Agents.AI.dll
true
+
+ CP0005
+ M:Microsoft.Agents.AI.AgentSkill.GetContentAsync(System.Threading.CancellationToken)
+ lib/net8.0/Microsoft.Agents.AI.dll
+ lib/net8.0/Microsoft.Agents.AI.dll
+ true
+
CP0005
M:Microsoft.Agents.AI.AgentSkillScript.RunAsync(Microsoft.Agents.AI.AgentSkill,System.Nullable{System.Text.Json.JsonElement},System.IServiceProvider,System.Threading.CancellationToken)
@@ -337,6 +463,13 @@
lib/net8.0/Microsoft.Agents.AI.dll
true
+
+ CP0005
+ M:Microsoft.Agents.AI.AgentSkill.GetContentAsync(System.Threading.CancellationToken)
+ lib/net9.0/Microsoft.Agents.AI.dll
+ lib/net9.0/Microsoft.Agents.AI.dll
+ true
+
CP0005
M:Microsoft.Agents.AI.AgentSkillScript.RunAsync(Microsoft.Agents.AI.AgentSkill,System.Nullable{System.Text.Json.JsonElement},System.IServiceProvider,System.Threading.CancellationToken)
@@ -344,6 +477,13 @@
lib/net9.0/Microsoft.Agents.AI.dll
true
+
+ CP0005
+ M:Microsoft.Agents.AI.AgentSkill.GetContentAsync(System.Threading.CancellationToken)
+ lib/netstandard2.0/Microsoft.Agents.AI.dll
+ lib/netstandard2.0/Microsoft.Agents.AI.dll
+ true
+
CP0005
M:Microsoft.Agents.AI.AgentSkillScript.RunAsync(Microsoft.Agents.AI.AgentSkill,System.Nullable{System.Text.Json.JsonElement},System.IServiceProvider,System.Threading.CancellationToken)
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkill.cs b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkill.cs
index 6f549301d0..38342d6816 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkill.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkill.cs
@@ -1,7 +1,8 @@
// Copyright (c) Microsoft. All rights reserved.
-using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
using Microsoft.Shared.DiagnosticIds;
namespace Microsoft.Agents.AI;
@@ -34,29 +35,44 @@ public abstract class AgentSkill
///
/// Gets the full skill content.
///
- ///
+ /// Cancellation token.
+ ///
/// For file-based skills this is the raw SKILL.md file content, optionally
/// augmented with a synthesized scripts block when scripts are present.
/// For code-defined skills this is a synthesized XML document
/// containing name, description, and body (instructions, resources, scripts).
- ///
- public abstract string Content { get; }
+ ///
+ public abstract ValueTask GetContentAsync(CancellationToken cancellationToken = default);
///
- /// Gets the resources associated with this skill, or if none.
+ /// Gets a resource owned by this skill by name.
///
+ /// The resource name (e.g. an identifier or a relative path referenced inside the skill content).
+ /// Cancellation token.
+ ///
+ /// The , or when no resource with the given name exists.
+ ///
///
- /// The default implementation returns .
- /// Override this property in derived classes to provide skill-specific resources.
+ /// The default implementation returns . Override in derived classes that
+ /// expose resources.
///
- public virtual IReadOnlyList? Resources => null;
+ public virtual ValueTask GetResourceAsync(
+ string name,
+ CancellationToken cancellationToken = default) => default;
///
- /// Gets the scripts associated with this skill, or if none.
+ /// Gets a script owned by this skill by name.
///
+ /// The script name.
+ /// Cancellation token.
+ ///
+ /// The , or when no script with the given name exists.
+ ///
///
- /// The default implementation returns .
- /// Override this property in derived classes to provide skill-specific scripts.
+ /// The default implementation returns . Override in derived classes that
+ /// expose scripts.
///
- public virtual IReadOnlyList? Scripts => null;
+ public virtual ValueTask GetScriptAsync(
+ string name,
+ CancellationToken cancellationToken = default) => default;
}
diff --git a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs
index af1225c9df..4d9ae154f0 100644
--- a/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI/Skills/AgentSkillsProvider.cs
@@ -186,13 +186,10 @@ private async Task CreateContextAsync(InvokingContext context, Cancel
return await base.ProvideAIContextAsync(context, cancellationToken).ConfigureAwait(false);
}
- bool hasScripts = skills.Any(s => s.Scripts is { Count: > 0 });
- bool hasResources = skills.Any(s => s.Resources is { Count: > 0 });
-
return new AIContext
{
- Instructions = this.BuildSkillsInstructions(skills, includeScriptInstructions: hasScripts, hasResources),
- Tools = this.BuildTools(skills, hasScripts, hasResources),
+ Instructions = this.BuildSkillsInstructions(skills),
+ Tools = this.BuildTools(skills),
};
}
@@ -219,29 +216,20 @@ private async Task GetOrCreateContextAsync(InvokingContext context, C
}
}
- private IList BuildTools(IList skills, bool hasScripts, bool hasResources)
+ private IList BuildTools(IList skills)
{
IList tools =
[
AIFunctionFactory.Create(
- (string skillName) => this.LoadSkill(skills, skillName),
+ (string skillName, CancellationToken cancellationToken) => this.LoadSkillAsync(skills, skillName, cancellationToken),
name: "load_skill",
description: "Loads the full content of a specific skill"),
- ];
-
- if (hasResources)
- {
- tools.Add(AIFunctionFactory.Create(
+ AIFunctionFactory.Create(
(string skillName, string resourceName, IServiceProvider? serviceProvider, CancellationToken cancellationToken = default) =>
this.ReadSkillResourceAsync(skills, skillName, resourceName, serviceProvider, cancellationToken),
name: "read_skill_resource",
- description: "Reads a resource associated with a skill, such as references, assets, or dynamic data."));
- }
-
- if (!hasScripts)
- {
- return tools;
- }
+ description: "Reads a resource associated with a skill, such as references, assets, or dynamic data."),
+ ];
AIFunction scriptFunction = AIFunctionFactory.Create(
(string skillName, string scriptName, JsonElement? arguments = null, IServiceProvider? serviceProvider = null, CancellationToken cancellationToken = default) =>
@@ -257,7 +245,7 @@ private IList BuildTools(IList skills, bool hasScripts,
return [.. tools, scriptFunction];
}
- private string? BuildSkillsInstructions(IList skills, bool includeScriptInstructions, bool includeResourceInstructions)
+ private string? BuildSkillsInstructions(IList skills)
{
string promptTemplate = this._options?.SkillsInstructionPrompt ?? DefaultSkillsInstructionPrompt;
@@ -270,32 +258,29 @@ private IList BuildTools(IList skills, bool hasScripts,
sb.AppendLine(" ");
}
- string resourceInstruction = includeResourceInstructions
- ? """
+ const string ResourceInstruction =
+ """
- Use `read_skill_resource` to read any referenced resources, using the name exactly as listed
(e.g. `"style-guide"` not `"style-guide.md"`, `"references/FAQ.md"` not `"FAQ.md"`).
- """
- : string.Empty;
+ """;
- string scriptInstruction = includeScriptInstructions
- ? "- Use `run_skill_script` to run referenced scripts, using the name exactly as listed."
- : string.Empty;
+ const string ScriptInstruction = "- Use `run_skill_script` to run referenced scripts, using the name exactly as listed.";
return new StringBuilder(promptTemplate)
.Replace(SkillsPlaceholder, sb.ToString().TrimEnd())
- .Replace(ResourceInstructionsPlaceholder, resourceInstruction)
- .Replace(ScriptInstructionsPlaceholder, scriptInstruction)
+ .Replace(ResourceInstructionsPlaceholder, ResourceInstruction)
+ .Replace(ScriptInstructionsPlaceholder, ScriptInstruction)
.ToString();
}
- private string LoadSkill(IList skills, string skillName)
+ private async Task LoadSkillAsync(IList skills, string skillName, CancellationToken cancellationToken)
{
if (string.IsNullOrWhiteSpace(skillName))
{
return "Error: Skill name cannot be empty.";
}
- var skill = skills?.FirstOrDefault(skill => skill.Frontmatter.Name == skillName);
+ var skill = skills.FirstOrDefault(skill => skill.Frontmatter.Name == skillName);
if (skill == null)
{
return $"Error: Skill '{skillName}' not found.";
@@ -303,7 +288,7 @@ private string LoadSkill(IList skills, string skillName)
LogSkillLoading(this._logger, skillName);
- return skill.Content;
+ return await skill.GetContentAsync(cancellationToken).ConfigureAwait(false);
}
private async Task