--- topic: sdk source_keys: - 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. ```typescript 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 // 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. ```typescript 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 { 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.