Skip to content

feat: add per-agent skills support to SDK types and docs (#958)#995

Draft
MackinnonBuck wants to merge 2 commits intomainfrom
mackinnonbuck/agent-skills-958
Draft

feat: add per-agent skills support to SDK types and docs (#958)#995
MackinnonBuck wants to merge 2 commits intomainfrom
mackinnonbuck/agent-skills-958

Conversation

@MackinnonBuck
Copy link
Copy Markdown
Collaborator

Summary

Adds SDK support for scoping skills to individual subagents, addressing #958.

Changes

Types (all 4 SDKs):

  • Node.js (nodejs/src/types.ts): Added skills?: string[] to CustomAgentConfig
  • Python (python/copilot/session.py): Added skills field to agent config
  • Go (go/types.go): Added Skills []string to agent config struct
  • .NET (dotnet/src/Types.cs): Added List<string>? Skills to agent config class

Documentation:

  • docs/features/custom-agents.md: Added skills to config reference table and new section on per-agent skills
  • docs/features/skills.md: Updated "Skills + Custom Agents" section with per-agent example
  • docs/features/streaming-events.md: Added agentName field to skill.invoked event docs

Tests (all 4 SDKs):

  • Node.js, Python, Go, .NET e2e tests covering:
    • Agent with skills can invoke listed skills
    • Agent without skills field gets no skills (backward compatible)

Design decisions

  • Opt-in model: skills is optional. Omitting it means the agent has no access to skills (not all skills).
  • Name-based resolution: Skills are referenced by name from the session-level skillDirectories pool.
  • Cross-SDK parity: All 4 SDKs support the new field consistently.

Depends on: github/copilot-agent-runtime PR (runtime must land first for e2e tests to pass)

Closes #958

Add a 'skills' field to CustomAgentConfig across all four SDK languages
(Node.js, Python, Go, .NET) to support scoping skills to individual
subagents. Skills are opt-in: agents get no skills by default.

Changes:
- Add skills?: string[] to CustomAgentConfig in all SDKs
- Update custom-agents.md with skills in config table and new section
- Update skills.md with per-agent skills example and opt-in note
- Update streaming-events.md with agentName on skill.invoked event
- Add E2E tests for agent-scoped skills in all four SDKs
- Add snapshot YAML files for new test scenarios

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@MackinnonBuck MackinnonBuck requested a review from a team as a code owner April 2, 2026 21:30
Copilot AI review requested due to automatic review settings April 2, 2026 21:30
@MackinnonBuck MackinnonBuck marked this pull request as draft April 2, 2026 21:31
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

Adds per-custom-agent skill scoping to the SDK surface area (types + docs) and introduces new cross-SDK E2E coverage + replay snapshots for the opt-in behavior.

Changes:

  • Added skills to CustomAgentConfig across Node.js, Python, Go, and .NET SDK types.
  • Updated docs to describe per-agent skill scoping and added agentName to skill.invoked event docs.
  • Added E2E tests and new replay snapshots for “agent with skills” vs “agent without skills field”.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
nodejs/src/types.ts Adds skills?: string[] to CustomAgentConfig with JSdoc explaining opt-in semantics.
python/copilot/session.py Adds skills to the Python CustomAgentConfig TypedDict.
go/types.go Adds Skills []string to Go CustomAgentConfig for JSON serialization.
dotnet/src/Types.cs Adds List<string>? Skills to .NET CustomAgentConfig with XML docs.
docs/features/custom-agents.md Documents the new skills field and adds a dedicated per-agent skills section.
docs/features/skills.md Updates “Skills + Custom Agents” section with per-agent skills example and opt-in note.
docs/features/streaming-events.md Documents agentName on skill.invoked.
nodejs/test/e2e/skills.test.ts Adds Node E2E tests for agent-scoped skills behavior.
python/e2e/test_skills.py Adds Python E2E tests for agent-scoped skills behavior.
go/internal/e2e/skills_test.go Adds Go E2E tests for agent-scoped skills behavior.
dotnet/test/SkillsTests.cs Adds .NET E2E tests for agent-scoped skills behavior.
test/snapshots/skills/should_allow_agent_with_skills_to_invoke_skill.yaml Replay snapshot for the “agent with skills can invoke” scenario.
test/snapshots/skills/should_not_provide_skills_to_agent_without_skills_field.yaml Replay snapshot for the “no skills field => no skills” scenario.
nodejs/package-lock.json Lockfile metadata updates.
Files not reviewed (1)
  • nodejs/package-lock.json: Language not supported

Comment on lines +751 to +752
# List of skill names available to this agent (opt-in; omit for no skills)
skills: NotRequired[list[str]]
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The Python client currently converts CustomAgentConfig to wire format via CopilotClient._convert_custom_agent_to_wire_format(), but that converter does not include the new skills field, so any skills you set here will be silently dropped and never reach the CLI/runtime. Please update the converter to pass through skills (and keep the opt-in semantics by only including it when present).

Copilot uses AI. Check for mistakes.
Comment on lines +96 to +111
it("should allow agent with skills to invoke skill", async () => {
const skillsDir = createSkillDir();
const customAgents: CustomAgentConfig[] = [
{
name: "skill-agent",
description: "An agent with access to test-skill",
prompt: "You are a helpful test agent.",
skills: ["test-skill"],
},
];

const session = await client.createSession({
onPermissionRequest: approveAll,
skillDirectories: [skillsDir],
customAgents,
});
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

These tests create custom agents but never explicitly select which agent should handle the prompt. That makes the assertions depend on inference/selection behavior rather than deterministically validating per-agent skill scoping. Consider setting agent: "skill-agent" (and agent: "no-skill-agent" in the negative case) in the session config so the test always exercises the intended agent.

Copilot uses AI. Check for mistakes.
session = await ctx.client.create_session(
on_permission_request=PermissionHandler.approve_all,
skill_directories=[skills_dir],
custom_agents=custom_agents,
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

These tests add custom agents but don't explicitly select the agent for the session, so the prompt may be handled by the default agent or inferred routing, which weakens the guarantee that you're testing per-agent skill scoping. Consider passing agent="skill-agent" / agent="no-skill-agent" when creating the session to make the test deterministic.

Suggested change
custom_agents=custom_agents,
custom_agents=custom_agents,
agent="skill-agent",

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +129
t.Run("should allow agent with skills to invoke skill", func(t *testing.T) {
ctx.ConfigureForTest(t)
cleanSkillsDir(t, ctx.WorkDir)
skillsDir := createTestSkillDir(t, ctx.WorkDir, skillMarker)

customAgents := []copilot.CustomAgentConfig{
{
Name: "skill-agent",
Description: "An agent with access to test-skill",
Prompt: "You are a helpful test agent.",
Skills: []string{"test-skill"},
},
}

session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{
OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
SkillDirectories: []string{skillsDir},
CustomAgents: customAgents,
})
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The test configures CustomAgents but does not explicitly set SessionConfig.Agent, so whether the request is handled by the intended custom agent depends on inference/routing behavior. To deterministically validate per-agent skills, set Agent: "skill-agent" (and Agent: "no-skill-agent" in the negative test) in the SessionConfig.

Copilot uses AI. Check for mistakes.
Comment on lines +90 to +110
[Fact]
public async Task Should_Allow_Agent_With_Skills_To_Invoke_Skill()
{
var skillsDir = CreateSkillDir();
var customAgents = new List<CustomAgentConfig>
{
new CustomAgentConfig
{
Name = "skill-agent",
Description = "An agent with access to test-skill",
Prompt = "You are a helpful test agent.",
Skills = ["test-skill"]
}
};

var session = await CreateSessionAsync(new SessionConfig
{
SkillDirectories = [skillsDir],
CustomAgents = customAgents
});

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

These tests configure CustomAgents but do not set SessionConfig.Agent, so they rely on inferred agent selection rather than explicitly exercising the intended agent. To make per-agent skill scoping deterministic, set Agent = "skill-agent" / Agent = "no-skill-agent" in the session config for the respective tests.

Copilot uses AI. Check for mistakes.
| `allowedTools` | `string[]` | | Tools auto-approved while this skill is active |
| `pluginName` | `string` | | Plugin the skill originated from |
| `pluginVersion` | `string` | | Plugin version |
| `agentName` | `string` | | Name of the agent that invoked the skill, when invoked by a custom agent |
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

This doc adds an agentName field to the skill.invoked event, but the SDK-generated event payload types currently do not include agentName for skill.invoked (e.g., nodejs/src/generated/session-events.ts and dotnet/src/Generated/SessionEvents.cs). Please either update/regenerate the event schemas/types to include this field, or remove it from the docs until it is actually emitted and modeled.

Suggested change
| `agentName` | `string` | | Name of the agent that invoked the skill, when invoked by a custom agent |

Copilot uses AI. Check for mistakes.
@github-actions

This comment has been minimized.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Generated by SDK Consistency Review Agent for issue #995

| `allowedTools` | `string[]` | | Tools auto-approved while this skill is active |
| `pluginName` | `string` | | Plugin the skill originated from |
| `pluginVersion` | `string` | | Plugin version |
| `agentName` | `string` | | Name of the agent that invoked the skill, when invoked by a custom agent |
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The agentName field is now documented here for the skill.invoked event, but the Node.js and .NET generated SDK types don't yet expose it in the skill.invoked-specific data structures:

  • Node.js (nodejs/src/generated/session-events.ts): the skill.invoked discriminated union data type only has name, path, content, allowedTools?, pluginName?, pluginVersion?, and description? — no agentName.
  • .NET (dotnet/src/Generated/SessionEvents.cs): SkillInvokedData has the same 7 fields — no AgentName.

By contrast, Go and Python use a flat/unified Data struct that already includes AgentName/agent_name (they expose all fields from all event types in one struct), so they're fine.

Since these are auto-generated files (AUTO-GENERATED FILE - DO NOT EDIT), they'll need to be regenerated from the updated schema once the dependent runtime PR lands. It would be worth adding a follow-up task or a note in this PR to regenerate the Node.js and .NET types so that agentName is accessible in a type-safe way for consumers of those SDKs.

Update type comments, docs, and test descriptions to reflect that
per-agent skills are eagerly injected into the agent's context at
startup rather than filtered for invocation. Sub-agents do not
inherit skills from the parent.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 3, 2026

SDK Consistency Review

This PR adds skills support to all four SDKs — great to see the cross-SDK parity! The Node.js, Go, and .NET implementations look correct. However, there is one functional bug in the Python SDK that needs to be addressed.

🐛 Python: skills silently dropped in wire-format conversion

The Python SDK uses a TypedDict for CustomAgentConfig with snake_case keys, and client.py's _convert_custom_agent_to_wire_format explicitly maps each field from Python naming to the camelCase JSON wire format. The skills field was added to the type definition in session.py but was not added to the conversion function, so it will be silently omitted from the JSON sent to the CLI.

See inline comment on python/copilot/client.py line 2024 for the one-line fix needed.

✅ Everything else looks consistent

SDK Type added Test added Wire-format correct
Node.js types.ts ✅ (direct serialization)
Go types.go ✅ (direct JSON marshaling)
.NET Types.cs ✅ ([JsonPropertyName("skills")])
Python session.py client.py conversion missing

Generated by SDK Consistency Review Agent for issue #995 ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FEATURE: Subagent skills

2 participants