Files
holocron/plugins/kyberforge/docs/research/docs/github-copilot-plugins/sdk.md
Defame1297 83e1a50a51 docs(kyberforge): add GitHub Copilot plugins and sub-agents research
## Why
Parallel research track to the Claude Code plugins research already
committed. Needed to understand GitHub Copilot's extensibility model
before designing cross-tool plugin compatibility for the kyberforge
plugin system.

## Implementation Notes
Three distinct Copilot extension tracks are covered: CLI plugins
(plugin.json + marketplaces), cloud/IDE custom agents (frontmatter .md
files committed to repos), and the SDK programmatic API. The SDK track
got its own topic file (sdk.md) because the content doesn't fit neatly
into the default topic list. Sources include Context7 (/websites/github_en_copilot),
both user-provided reference URLs, and four additional deepened pages.

## Impact
Provides a reference baseline for evaluating .claude-plugin/ / plugin.json
compatibility between Claude Code and Copilot CLI — the two formats share
a manifest discovery path and the strict:false field enables cross-tool
plugin distribution.

---
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 14:22:32 +00:00

6.5 KiB

topic, source_keys
topic source_keys
sdk
github-sdk-custom-agents
context7-github-en-copilot

Copilot SDK — Custom Agents

The Copilot SDK is the programmatic interface for building Copilot-powered applications. Custom agents in the SDK context are runtime configurations attached to a session rather than files on disk.

Core Concepts

  • Custom agent — a named configuration with its own system prompt, restricted tool set, and optional MCP servers.
  • Sub-agent — a custom agent invoked by the Copilot runtime to handle a portion of a task in an isolated context window.
  • Inference — the runtime's automatic selection of an agent based on user intent. Enabled by default (infer: true). Set infer: false on agents that should only run when explicitly requested (especially destructive agents).

Session Creation

Pass customAgents to client.createSession() along with a permission handler. Permission handling is session-wide — there is no per-agent override.

import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient();
await client.start();

const session = await client.createSession({
    model: "gpt-4.1",
    customAgents: [
        {
            name: "researcher",
            displayName: "Research Agent",
            description: "Read-only codebase exploration",
            tools: ["grep", "glob", "view"],
            prompt: "You are a research assistant. Do not modify files.",
        },
        {
            name: "editor",
            displayName: "Editor Agent",
            description: "Makes targeted code changes",
            tools: ["view", "edit", "bash"],
            prompt: "You are a code editor. Make minimal changes.",
        },
    ],
    onPermissionRequest: async () => ({ kind: "approve-once" }),
});

CustomAgentConfig Fields

Field Required Default Notes
name Yes — Identifier; must match agent pre-selection exactly
displayName No — Label shown in lifecycle events
description No — Used for runtime intent matching; quality directly affects routing accuracy
tools No all session tools null or omitted = all; explicit list = restrict
prompt Yes — System prompt injected into this agent's context
mcpServers No — Agent-scoped MCP config; not inherited from session; not visible to other agents
infer No true Set false for agents that should not be auto-selected
skills No — Skill names from skillDirectories; eagerly injected at startup; not inherited by sub-agents

Session-Level Fields Relevant to Agents

Field Type Notes
customAgents array Agent definitions
agent string Pre-activate a named agent; exact name match required
skillDirectories string[] Directories for resolving skills names
defaultAgent.excludedTools string[] Hides tools from main agent; sub-agents still see them
model string Default model for all agents

Tool Scoping

Two orthogonal mechanisms control tool access:

Per-agent tools list — restricts what that agent can call. null or omitting the field means all session tools are available.

defaultAgent.excludedTools — hides tools from the built-in (main/orchestrator) agent while keeping them available to named sub-agents. Use this to prevent the orchestrator from using heavy-context or destructive tools directly.

Precedence: session-level excludedTools beats defaultAgent.excludedTools. If a tool appears in both, no agent gets it.

Permission Handling

// TypeScript
onPermissionRequest: async () => ({ kind: "approve-once" })

// Python
on_permission_request=lambda req, inv: PermissionDecisionApproveOnce()

// Go
OnPermissionRequest: func(req, inv) (rpc.PermissionDecision, error) {
    return &rpc.PermissionDecisionApproveOnce{}, nil
},

// .NET
OnPermissionRequest = (req, inv) => Task.FromResult(PermissionDecision.ApproveOnce())

// Java
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)

Sub-Agent Lifecycle Events

Subscribe to all session events via session.on(handler). Filter by event.type:

Event Type Key Fields
subagent.selected agentName, agentDisplayName, tools
subagent.started toolCallId, agentName, agentDisplayName, agentDescription
subagent.completed toolCallId, agentName, agentDisplayName
subagent.failed toolCallId, agentName, agentDisplayName, error
subagent.deselected (no data)

toolCallId is stable across started/completed/failed for a single invocation — key an agent-tree map on it.

const agentTree = new Map();

session.on((event) => {
    if (event.type === "subagent.started") {
        agentTree.set(event.toolCallId, {
            name: event.agentName,
            status: "running",
            startedAt: Date.now(),
        });
    }
    if (event.type === "subagent.completed") {
        const node = agentTree.get(event.toolCallId);
        if (node) node.status = "done";
    }
    if (event.type === "subagent.failed") {
        const node = agentTree.get(event.toolCallId);
        if (node) { node.status = "failed"; node.error = event.error; }
    }
});

Language-Specific Notes

TypeScript: camelCase field names (customAgents, displayName, onPermissionRequest).

Python: snake_case (custom_agents, display_name, on_permission_request).

Go: PascalCase struct fields (CustomAgents, DisplayName); copilot.CustomAgentConfig struct.

.NET: SessionConfig object initializer with CustomAgents = new List<CustomAgentConfig> { new() { ... } }.

Java: builder pattern — new CustomAgentConfig().setName(...).setTools(List.of(...)).setPrompt(...).

Key Gotchas

  • Description quality directly affects routing. Vague descriptions produce bad inference. Be specific about what the agent does and what it will not do.
  • infer: true is the default. Explicitly set infer: false on agents with destructive capability (file deletion, schema migrations) so the runtime cannot select them autonomously.
  • Sub-agents do not inherit parent skills. Each agent must declare its own skills list.
  • Agent-scoped MCP is not shared. An MCP server in a sub-agent's mcpServers block is invisible to the main agent and other sub-agents.
  • subagent.failed requires explicit handling. The runtime does not retry or fall back automatically.
  • agent pre-selection is an exact match. A mismatch silently falls through to inference.