Skip to main content

カスタム エージェントとサブエージェント オーケストレーション

スコープ指定されたツールとプロンプトを使用して特殊なエージェントを定義し、Copilot 1 つのセッション内でサブエージェントとして調整できるようにします。

Overview

カスタム エージェントは、セッションにアタッチする軽量のエージェント定義です。 各エージェントには、独自のシステム プロンプト、ツールの制限、およびオプションの MCP サーバーがあります。 ユーザーの要求がエージェントの専門知識と一致すると、Copilot ランタイムは自動的にそのエージェントに sub-agent として委任し、ライフサイクル イベントを親セッションにストリーミングしながら分離されたコンテキストで実行します。

図: 説明されたプロセスを示すフローチャート。

概念Description
カスタム エージェント独自のプロンプトとツール セットを含む名前付きエージェント構成
サブエージェントタスクの一部を処理するためにランタイムによって呼び出されるカスタム エージェント
推論ユーザーの意図に基づいてエージェントを自動選択するランタイムの機能
親セッションサブエージェントを生成したセッション。は、すべてのライフサイクル イベントを受信します。

カスタム エージェントの定義

セッションの作成時に customAgents 渡します。 各エージェントには、少なくとも namepromptが必要です。

コード言語 navigation

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: "Explores codebases and answers questions using read-only tools",
            tools: ["grep", "glob", "view"],
            prompt: "You are a research assistant. Analyze code and answer questions. Do not modify any files.",
        },
        {
            name: "editor",
            displayName: "Editor Agent",
            description: "Makes targeted code changes",
            tools: ["view", "edit", "bash"],
            prompt: "You are a code editor. Make minimal, surgical changes to files as requested.",
        },
    ],
    onPermissionRequest: async () => ({ kind: "approve-once" }),
});

構成のリファレンス

財産タイプ必須Description
namestringエージェントのユニークID
displayNamestring
イベントに表示される人間が判読できる名前
descriptionstring
エージェントが実行すること - ランタイムがエージェントを選択するのを支援します
tools
string[] または null
エージェントが使用できるツール名。
null または省略 = すべてのツール
promptstringエージェントのシステム プロンプト
mcpServersobject
このエージェントに固有の MCP サーバー構成
inferboolean
ランタイムがこのエージェントを自動選択できるかどうか (既定値: true)
skillsstring[]
起動時にエージェントのコンテキストにプリロードするスキル名

ヒント

適切な description は、ランタイムがユーザーの意図を適切なエージェントと照合するのに役立ちます。 エージェントの専門知識と機能について具体的に説明します。

上記のエージェントごとの構成に加えて、agent自体の**** を設定して、セッションの開始時にアクティブなカスタム エージェントを事前に選択できます。 以下の 「セッション作成時のエージェントの選択 」を参照してください。

セッション構成プロパティタイプDescription
agentstringセッションの作成時に事前に選択するカスタム エージェントの名前。
namecustomAgentsと一致する必要があります。

エージェントごとのスキル

skills プロパティを使用して、エージェントのコンテキストにスキルを事前に読み込むことができます。 指定すると、リストされている各スキルの 完全なコンテンツ が起動時にエージェントのコンテキストに熱心に挿入されます。エージェントはスキル ツールを呼び出す必要はありません。手順は既に存在します。 スキルは オプトインです。エージェントは既定でスキルを受け取らず、サブエージェントは親からスキルを継承しません。 スキル名は、セッション レベルの skillDirectoriesから解決されます。

const session = await client.createSession({
    skillDirectories: ["./skills"],
    customAgents: [
        {
            name: "security-auditor",
            description: "Security-focused code reviewer",
            prompt: "Focus on OWASP Top 10 vulnerabilities",
            skills: ["security-scan", "dependency-check"],
        },
        {
            name: "docs-writer",
            description: "Technical documentation writer",
            prompt: "Write clear, concise documentation",
            skills: ["markdown-lint"],
        },
    ],
    onPermissionRequest: async () => ({ kind: "approve-once" }),
});

この例では、 security-auditorsecurity-scan で始まり、 dependency-check はコンテキストに既に挿入されていますが、 docs-writermarkdown-lintで始まります。 skillsフィールドを持たないエージェントは、スキルコンテンツを受け取らない。

セッション作成時のエージェントの選択

セッション構成で agent を渡して、セッションの開始時にアクティブにするカスタム エージェントを事前に選択できます。 この値は、nameで定義されているいずれかのエージェントのcustomAgentsと一致する必要があります。

これは、作成後に session.rpc.agent.select() を呼び出すことと同じですが、追加の API 呼び出しを回避し、最初のプロンプトからエージェントがアクティブになっていることを確認します。

コード言語 navigation

TypeScript
const session = await client.createSession({
    customAgents: [
        {
            name: "researcher",
            prompt: "You are a research assistant. Analyze code and answer questions.",
        },
        {
            name: "editor",
            prompt: "You are a code editor. Make minimal, surgical changes.",
        },
    ],
    agent: "researcher", // Pre-select the researcher agent
});

サブエージェントの委任のしくみ

カスタム エージェントとのセッションにプロンプトを送信すると、ランタイムはサブエージェントに委任するかどうかを評価します。

  1. 意図の照合 - ランタイムは、ユーザーのプロンプトを各エージェントのnameおよびdescriptionと比較し分析します。
  2. エージェントの選択 - 一致するものが見つかり、 inferfalseされていない場合、ランタイムはエージェントを選択します。
  3. 分離実行 - サブエージェントは、独自のプロンプトと制限付きツール セットを使用して実行されます
  4. イベント ストリーミング - ライフサイクル イベント (subagent.startedsubagent.completedなど) が親セッションにストリームバックされます
  5. 結果の統合 - サブエージェントの出力が親エージェントの応答に組み込まれます

推論の制御

既定では、すべてのカスタム エージェントを自動選択 (infer: true) で使用できます。 infer: falseを設定して、ランタイムがエージェントを自動選択できないようにします。明示的なユーザー要求によってのみ呼び出すエージェントに役立ちます。

{
    name: "dangerous-cleanup",
    description: "Deletes unused files and dead code",
    tools: ["bash", "edit", "view"],
    prompt: "You clean up codebases by removing dead code and unused files.",
    infer: false, // Only invoked when user explicitly asks for this agent
}

サブエージェント イベントのリッスン

サブエージェントを実行すると、親セッションはライフサイクル イベントを生成します。 これらのイベントをサブスクライブして、エージェント アクティビティを視覚化する UI を構築します。

イベントの種類

イベント次の場合に生成されますデータ
subagent.selectedランタイムがタスクのエージェントを選択する
agentNameagentDisplayNametools
subagent.startedサブエージェントが実行を開始する
toolCallIdagentNameagentDisplayNameagentDescription
subagent.completedサブエージェントが正常に終了する
toolCallIdagentNameagentDisplayName
subagent.failedサブエージェントでエラーが発生する
toolCallIdagentNameagentDisplayNameerror
subagent.deselectedランタイムがサブエージェントから他のプロセスに移行する

イベントに登録する

コード言語 navigation

TypeScript
session.on((event) => {
    switch (event.type) {
        case "subagent.started":
            console.log(`▶ Sub-agent started: ${event.data.agentDisplayName}`);
            console.log(`  Description: ${event.data.agentDescription}`);
            console.log(`  Tool call ID: ${event.data.toolCallId}`);
            break;

        case "subagent.completed":
            console.log(`✅ Sub-agent completed: ${event.data.agentDisplayName}`);
            break;

        case "subagent.failed":
            console.log(`❌ Sub-agent failed: ${event.data.agentDisplayName}`);
            console.log(`  Error: ${event.data.error}`);
            break;

        case "subagent.selected":
            console.log(`🎯 Agent selected: ${event.data.agentDisplayName}`);
            console.log(`  Tools: ${event.data.tools?.join(", ") ?? "all"}`);
            break;

        case "subagent.deselected":
            console.log("↩ Agent deselected, returning to parent");
            break;
    }
});

const response = await session.sendAndWait({
    prompt: "Research how authentication works in this codebase",
});

エージェント ツリー UI の構築

サブエージェント イベントには、実行ツリーを再構築できる toolCallId フィールドが含まれます。 エージェント アクティビティを追跡するためのパターンを次に示します。

interface AgentNode {
    toolCallId: string;
    name: string;
    displayName: string;
    status: "running" | "completed" | "failed";
    error?: string;
    startedAt: Date;
    completedAt?: Date;
}

const agentTree = new Map<string, AgentNode>();

session.on((event) => {
    if (event.type === "subagent.started") {
        agentTree.set(event.data.toolCallId, {
            toolCallId: event.data.toolCallId,
            name: event.data.agentName,
            displayName: event.data.agentDisplayName,
            status: "running",
            startedAt: new Date(event.timestamp),
        });
    }

    if (event.type === "subagent.completed") {
        const node = agentTree.get(event.data.toolCallId);
        if (node) {
            node.status = "completed";
            node.completedAt = new Date(event.timestamp);
        }
    }

    if (event.type === "subagent.failed") {
        const node = agentTree.get(event.data.toolCallId);
        if (node) {
            node.status = "failed";
            node.error = event.data.error;
            node.completedAt = new Date(event.timestamp);
        }
    }

    // Render your UI with the updated tree
    renderAgentTree(agentTree);
});

エージェントごとのツールの範囲設定

tools プロパティを使用して、エージェントがアクセスできるツールを制限します。 これは、セキュリティとエージェントの集中を維持するために不可欠です。

const session = await client.createSession({
    customAgents: [
        {
            name: "reader",
            description: "Read-only exploration of the codebase",
            tools: ["grep", "glob", "view"],  // No write access
            prompt: "You explore and analyze code. Never suggest modifications directly.",
        },
        {
            name: "writer",
            description: "Makes code changes",
            tools: ["view", "edit", "bash"],   // Write access
            prompt: "You make precise code changes as instructed.",
        },
        {
            name: "unrestricted",
            description: "Full access agent for complex tasks",
            tools: null,                        // All tools available
            prompt: "You handle complex multi-step tasks using any available tools.",
        },
    ],
});

メモ

toolsnullまたは省略されると、エージェントはセッションで構成されているすべてのツールへのアクセスを継承します。 明示的なツール リ��トを使用して、最小限の特権の原則を適用します。

エージェント専用ツール

セッション構成の defaultAgent プロパティを使用して、既定のエージェント (カスタム エージェントが選択されていないときにターンを処理する組み込みエージェント) から特定のツールを非表示にします。 これにより、これらのツールの機能が必要になったときにメイン エージェントがサブエージェントに委任され、メイン エージェントのコンテキストがクリーンな状態が維持されます。

これは、次の場合に役立ちます。

  • 特定のツールは、メイン エージェントを圧倒する大量のコンテキストを生成します
  • メイン エージェントをオーケストレーターとして機能させ、特殊なサブエージェントに重い作業を委任する
  • オーケストレーションと実行を厳密に分離する必要がある

コード言語 navigation

TypeScript
import { CopilotClient, defineTool, approveAll } from "@github/copilot-sdk";
import { z } from "zod";

const heavyContextTool = defineTool("analyze-codebase", {
    description: "Performs deep analysis of the codebase, generating extensive context",
    parameters: z.object({ query: z.string() }),
    handler: async ({ query }) => {
        // ... expensive analysis that returns lots of data
        return { analysis: "..." };
    },
});

const session = await client.createSession({
    tools: [heavyContextTool],
    defaultAgent: {
        excludedTools: ["analyze-codebase"],
    },
    customAgents: [
        {
            name: "researcher",
            description: "Deep codebase analysis agent with access to heavy-context tools",
            tools: ["analyze-codebase"],
            prompt: "You perform thorough codebase analysis using the analyze-codebase tool.",
        },
    ],
});

どのように機能するのか

defaultAgent.excludedToolsに一覧表示されているツール:

  1. 登録済みであり、ハンドラーを実行できます
  2. メイン エージェントのツール リストから非表示になります。LLM はそれらを直接表示または呼び出しません
  3. それらを**** 配列に含む任意のカスタム サブエージェントでtools

他のツールフィルターとの連携

defaultAgent.excludedTools はセッション レベルの availableToolsexcludedToolsに直交します。

FilterScope影響
availableToolsセッション全体Allowlist - すべてのユーザーに対してこれらのツールのみが存在します
excludedToolsセッション全体ブロックリスト - すべてのユーザーに対してこれらのツールがブロックされます
defaultAgent.excludedToolsメイン エージェントのみこれらのツールはメイン エージェントには表示されませんが、サブエージェントで使用できます

優先順位:

  1. セッション レベルの availableTools/excludedTools が最初に適用されます (グローバルに)
  2. defaultAgent.excludedTools が上に適用され、メイン エージェントのみがさらに制限されます

メモ

ツールが excludedTools (セッション レベル) と defaultAgent.excludedToolsの両方にある場合、セッション レベルの除外が優先されます。ツールはすべてのユーザーが使用できません。

エージェントへの MCP サーバーのアタッチ

各カスタム エージェントは独自の MCP (モデル コンテキスト プロトコル) サーバーを持つ可能性があり、特殊なデータ ソースにアクセスできます。

const session = await client.createSession({
    customAgents: [
        {
            name: "db-analyst",
            description: "Analyzes database schemas and queries",
            prompt: "You are a database expert. Use the database MCP server to analyze schemas.",
            mcpServers: {
                "database": {
                    command: "npx",
                    args: ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"],
                },
            },
        },
    ],
});

パターンとベスト プラクティス

研究者と編集者のペアリング

一般的なパターンは、読み取り専用の研究者エージェントと書き込み可能なエディター エージェントを定義することです。 ランタイムは探索タスクを研究者に委任し、変更タスクをエディターに委任します。

customAgents: [
    {
        name: "researcher",
        description: "Analyzes code structure, finds patterns, and answers questions",
        tools: ["grep", "glob", "view"],
        prompt: "You are a code analyst. Thoroughly explore the codebase to answer questions.",
    },
    {
        name: "implementer",
        description: "Implements code changes based on analysis",
        tools: ["view", "edit", "bash"],
        prompt: "You make minimal, targeted code changes. Always verify changes compile.",
    },
]

エージェントの説明を固有に保つ

ランタイムは、 description を使用してユーザーの意図と一致します。 あいまいな説明は、委任が不適切になります。

// ❌ Too vague — runtime can't distinguish from other agents
{ description: "Helps with code" }

// ✅ Specific — runtime knows when to delegate
{ description: "Analyzes Python test coverage and identifies untested code paths" }

エラーを適切に処理する

サブエージェントは失敗する可能性があります。 常に subagent.failed イベントをリッスンし、アプリケーションで処理します。

session.on((event) => {
    if (event.type === "subagent.failed") {
        logger.error(`Agent ${event.data.agentName} failed: ${event.data.error}`);
        // Show error in UI, retry, or fall back to parent agent
    }
});