Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,9 @@ You have access to skills containing domain-specific knowledge and capabilities.
When a task aligns with a skill's domain, follow these steps in exact order:
- Use `load_skill` to retrieve the skill's instructions.
- Follow the provided guidance.
- 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"`).
- Use `read_skill_resource` only for a resource explicitly referenced by the loaded skill.
- Pass its resource path exactly as written in that skill.
- Never infer or guess resource paths.
- Use `run_skill_script` to run referenced scripts, using the name exactly as listed.
Only load what is needed, when it is needed.
""";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,26 @@ public async Task InvokingCoreAsync_WithSkills_AppendsInstructionsAndToolsAsync(
Assert.Contains("run_skill_script", toolNames);
}

[Fact]
public async Task InvokingCoreAsync_DefaultPromptUsesRuleOnlyResourceGuidanceAsync()
{
// Arrange
var skill = new AgentInlineSkill("resource-guidance", "Resource guidance test", "Body.");
var provider = new AgentSkillsProvider(skill);
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());

// Act
var result = await provider.InvokingAsync(invokingContext, CancellationToken.None);

// Assert
Assert.NotNull(result.Instructions);
Assert.Contains("only for a resource explicitly referenced by the loaded skill", result.Instructions);
Assert.Contains("Pass its resource path exactly as written in that skill", result.Instructions);
Assert.Contains("Never infer or guess resource paths", result.Instructions);
Assert.DoesNotContain("style-guide", result.Instructions);
Assert.DoesNotContain("references/FAQ.md", result.Instructions);
}

[Fact]
public async Task InvokingCoreAsync_NullInputInstructions_SetsInstructionsAsync()
{
Expand Down Expand Up @@ -439,6 +459,25 @@ public async Task LoadSkill_DefaultOptions_ReturnsFullContentAsync()
Assert.Contains("Skill body.", text);
}

[Fact]
public async Task LoadSkill_ListsExplicitResourcePathExactlyAsync()
{
// Arrange
var skill = new AgentInlineSkill("resource-skill", "Resource path test", "Consult the explicitly referenced guide.");
skill.AddResource("references/actual-guide.md", "Guide content");
var provider = new AgentSkillsProvider(skill);
var invokingContext = new AIContextProvider.InvokingContext(this._agent, session: null, new AIContext());
var result = await provider.InvokingAsync(invokingContext, CancellationToken.None);
var loadSkillTool = result.Tools!.First(t => t.Name == "load_skill") as AIFunction;

// Act
var content = await loadSkillTool!.InvokeAsync(
new AIFunctionArguments(new Dictionary<string, object?> { ["skillName"] = "resource-skill" }));

// Assert
Assert.Contains("<resource name=\"references/actual-guide.md\"/>", content!.ToString());
}

[Fact]
public async Task LoadSkill_EmptySkillName_ReturnsErrorAsync()
{
Expand Down
5 changes: 3 additions & 2 deletions python/packages/core/agent_framework/_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -1809,8 +1809,9 @@ def _indent_width(s: str) -> int:
Only load what is needed, when it is needed."""

RESOURCE_INSTRUCTIONS: Final[str] = (
"- Use `read_skill_resource` to read any referenced resources, using the name exactly as listed\n"
' (e.g. `"style-guide"` not `"style-guide.md"`, `"references/FAQ.md"` not `"FAQ.md"`).\n'
"- Use `read_skill_resource` only for a resource explicitly referenced by the loaded skill.\n"
"- Pass its resource path exactly as written in that skill.\n"
"- Never infer or guess resource paths.\n"
)

SCRIPT_RUNNER_INSTRUCTIONS: Final[str] = (
Expand Down
26 changes: 26 additions & 0 deletions python/packages/core/tests/core/test_skills.py
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,20 @@ def test_default_prompt_contains_skills(self) -> None:
assert "<description>Does stuff.</description>" in prompt
assert "load_skill" in prompt

def test_default_prompt_uses_rule_only_resource_guidance(self) -> None:
skills = [
InlineSkill(frontmatter=SkillFrontmatter(name="my-skill", description="Does stuff."), instructions="Body"),
]

prompt = SkillsProvider._create_instructions(None, skills)

assert prompt is not None
assert "only for a resource explicitly referenced by the loaded skill" in prompt
assert "Pass its resource path exactly as written in that skill" in prompt
assert "Never infer or guess resource paths" in prompt
assert "style-guide" not in prompt
assert "references/FAQ.md" not in prompt

def test_skills_sorted_alphabetically(self) -> None:
skills = [
InlineSkill(frontmatter=SkillFrontmatter(name="zebra", description="Z skill."), instructions="Body"),
Expand Down Expand Up @@ -806,6 +820,18 @@ async def test_load_skill_preserves_file_skill_content(self, tmp_path: Path) ->
result = await provider._load_skill(_raw_skills(provider), "my-skill")
assert "See [doc](references/FAQ.md)." in result

async def test_load_skill_lists_explicit_resource_path_exactly(self) -> None:
skill = InlineSkill(
frontmatter=SkillFrontmatter(name="my-skill", description="Skill."),
instructions="Consult the explicitly referenced guide.",
resources=[InlineSkillResource(name="references/actual-guide.md", content="Guide content")],
)
provider = SkillsProvider([skill])

result = await provider._load_skill([skill], "my-skill")

assert '<resource name="references/actual-guide.md"/>' in result

async def test_load_skill_unknown_returns_error(self, tmp_path: Path) -> None:
provider = SkillsProvider.from_paths(str(tmp_path))
await _init_provider(provider)
Expand Down
Loading