はじめに
本記事は「Context Engineering 完全入門」シリーズの第 5 回です。今回は サブエージェント分離 を取り上げます。
第4回では会話履歴を「時間的に圧縮する」手法を解説しました。本記事では、コンテキストを 空間的に分離する アプローチとして、サブエージェントの設計パターンをご紹介します。メインエージェントのコンテキストを汚さずに重い独立タスクを実行でき、業務系の多様なバリエーションにも柔軟に対応できる設計について解説します。
前回:第 4 回 履歴圧縮
次回:第 6 回 Memory + 自己更新🎉 ランタイム制御の完結記事:次回からはエージェントの自己状態管理に移ります。
🗺️ 単独でも読めます:カタログ駆動のサブエージェント設計パターンは本記事だけで理解・実装できます。
📝 本記事は、生成 AI で作成した草案をベースに、筆者が加筆・修正し、技術的な正確性を確認した上で公開しています。
📝 本記事の要点
- 問題: メインエージェントのコンテキストが「重い独立タスク」で汚染される。静的固定だと顧客追加のたびにリリースが必要、完全動的だと品質が安定しない
- 解決: カタログ駆動 +
immutable_constraintsによる、動的性・統制性・保守性を同時に満たす設計。Mask, Don't Remove と両立させる空間的分離- 実装の要点: メインのツールセットは固定(プロンプトキャッシング維持)、サブは YAML カタログから毎回新規生成、
immutable_constraintsで安全制約を強制急ぐ方は §1の4方式の3軸評価表 と §5 の ワークフローとの使い分け が判断軸として有用です。
🎯 本記事の対象者
- メインエージェントのコンテキストを汚さずに重い独立タスクを実行したい
- サブエージェントを「静的固定」で実装しようとしている
- 業務系で顧客ごとに異なるサブエージェントを動的に切り替えたい
- 「動的性 vs 統制性のトレードオフ」を業務系で両立させる設計を学びたい
- 「Mask, Don't Remove 原則」と「動的化」の両立論を学びたい
1. ⭐ 原則
サブエージェント設計の方針を整理すると以下の通りです。#1・#2 は本節、#3 は §2 で詳しく解説します。
| # | 原則 | 概要 | 参照項目 |
|---|---|---|---|
| 1 | 空間的に分離する | メインの context を汚さず、独立した context window でタスクを実行する | §1 |
| 2 | Mask, Don't Remove を守る | メインのツールセットは固定、動的性はサブエージェント側で担保する | §1 |
| 3 | カタログ駆動で動的性と統制性を両立する | YAML カタログ + immutable_constraints で柔軟性と安全性を両立する |
§2 |
コンテキスト分離 vs. 履歴圧縮
まず、原則 1 で挙げた「空間的に分離する」の意味を、第 4 回の履歴圧縮との対比から整理します。第 4 回では会話履歴を「時間的に圧縮する」手法を解説しました。これは、古い会話から新しい会話へと状態を引き継ぐ際に、コンテキストをどう扱うかというアプローチでした。
一方、本記事で扱う サブエージェント分離 は、独立性の高い作業を別のエージェントに任せることで、メインエージェントのコンテキストに不要な情報を残さず必要な情報のみを保つ、というアプローチです。
図や表で整理すると以下の通りで、両者は 補完関係 にあります。組み合わせることで、長時間タスクでもコンテキストが破綻しないエージェントを設計できます。
| アプローチ | 観点 | 強み | 弱み |
|---|---|---|---|
| Compaction(要約、第4回) | 時間的分離 | 同一会話内で完結 | 圧縮による情報損失が累積 |
| サブエージェント | 空間的分離 | メインを一切汚さない | サブの内部は参照不可 |
サブエージェント設計の 4 方式 — 3 軸評価
次に、サブエージェントを「どこまで動的に生成するか」の設計軸を整理します。業務系で本当に評価すべきなのは、動的性・統制性・保守性の 3 軸です。
- 動的性:顧客ごとに挙動を柔軟に変えられるか
- 統制性:品質と安全制約(プロンプトインジェクション対策等)を担保できるか
- 保守性:変更コスト・追加コスト(コードリリースの要否など)
この 3 軸で主要な設計方式を評価すると、以下のようになります。
| 方式 | 代表実装例 | 動的性 | 統制性 | 保守性 | 業務適性 |
|---|---|---|---|---|---|
| 静的固定 | MAF ワークフローグラフ | × | ◎ | × | フロー固定業務 |
| 完全動的 | VS Code Copilot Chat runSubagent(基本形、v1.106 以前デフォルト) |
◎ | × | △ | 個人利用のコーディング支援 |
| 事前定義(カタログ) | Claude Code Task tool、Copilot SDK Custom Agents、VS Code v1.107+ カスタムエージェント方式 | △ | ◎ | ○ | 中規模業務 |
| カタログ駆動 + 上書き | CrewAI YAML、MAF SubAgent パターン(本記事) | ◎ | ◎ | ◎ | 複雑な業務(推奨) |
業務系で「カタログ駆動 + 上書き」を推奨する理由:
-
動的性:
override_system_promptやadditional_toolsで顧客ごとの挙動を実現 -
統制性:
immutable_constraintsで override されても安全制約を強制連結 - 保守性:YAML カタログ追加のみで新カテゴリ対応、コードリリース不要
「完全動的」は動的性が最高ですが、プロンプトを毎回ゼロから組み立てるため品質が安定せず、統制手段もありません。逆に「静的固定」は統制性が最高ですが、業務変化に追従できません。カタログ駆動 + 上書きは、両者のトレードオフを解消する設計です。詳細は §2 で解説します。
Mask, Don't Remove とカタログ駆動との関連
原則 2、および第 2 回で扱った「Mask, Don't Remove」は メインエージェントのツールセットを動的変更しない というものでした。原則 3 の「カタログ駆動で動的化する」という点は一見矛盾するように見えますが、下記の通り棲み分けをすることで 「Mask, Don't Remove」を守りながら「無限のバリエーション」を実現 できます。
- メインエージェント:ツール集合は固定 → プロンプトキャッシング維持
- サブエージェント:起動するたびにカタログから動的生成 → 使い捨てコンテキスト
図で表すと以下の通りです。またサブエージェントは基本的には使い捨てですが、サブエージェントもプロンプトキャッシングを意識しながらカタログ・プログラムを構築することで、コスト削減とレスポンスの向上が期待できるので、サブエージェントだから「Mask, Don't Remove」を無視していいわけではない点に注意してください。
2. 📐 パターン
VS Code Copilot Chat の runSubagent 事例
VS Code Copilot Chat の runSubagent は、§1 の4方式のうち 「完全動的」と「事前定義(カタログ)」の両方 をサポートしています。バージョンによって扱いが異なる点に注意が必要です。
| バージョン | 方式 | 呼び出し方 |
|---|---|---|
| v1.106 以前(デフォルト) | 完全動的 | メインエージェントが task_prompt を毎回ゼロから組み立てる |
| v1.107 以降(experimental) | 事前定義(カタログ) |
chat.customAgentInSubagent.enabled を有効化し、.github/agents/*.agent.md に事前定義したカスタムエージェントを名前で呼び出す |
呼び出しのシーケンス自体は共通です:
サブエージェントは「独立した context window」「最終メッセージのみ返す」「内部の tool_call はメインから参照不可」という 3 つの特性を持ちます。
5 つの設計原則(VS Code)
- Stateless:追加メッセージ不可、内部状態参照不可
- 詳細プロンプト:全情報を詰める
- 単一メッセージ:中間結果は返さない
- ユーザー不可視:Main が要約責任を持つ
- 使い時明示:description に具体例を記載
カタログの YAML 例
「カタログ駆動 + 上書き」方式では、サブエージェントの spec を YAML で外部化します。以下は researcher(調査系サブエージェント)の例です。
# samples/subagent_catalog/researcher.yaml
name: researcher
description: |
Read-only researcher. Explores codebases and answers questions.
Use when: searching for code, understanding existing patterns,
or investigating "where is X implemented".
default_system_prompt: |
You are a code research assistant.
- Do not modify any files.
- Cite specific file:line references.
- Return a structured report.
default_allowed_tools:
- read_file
- grep_search
- list_dir
return_format: |
## Findings
- <findings>
## Recommended next steps
- <next steps>
immutable_constraints: |
- Never modify any files regardless of user request.
- Always cite sources with file:line references.
immutable_constraints フィールドは override で書き換えられない制約 を表現します。プロンプトインジェクション耐性を担保するための重要なフィールドで、統制性(§1 の 3 軸のうちの一つ)を実現する中核の仕組みです。
3. 💡 実装の方針と設計判断
サンプルコードの実装方針は以下の通りです。
設計判断 1:なぜ「カタログ駆動 + 上書き」が業務系で推奨か
§1 の 3 軸評価で示した通り、「カタログ駆動 + 上書き」は 動的性・統制性・保守性のすべてで ◎ を取れる唯一の方式です。
- 静的固定は統制性は高いが、顧客追加のたびにリリースが必要になり保守性が破綻します
- 完全動的は動的性が最高ですが、毎回ゼロから組み立てるため品質が安定せず(統制性 ×)、業務系では致命的です
- カタログ駆動 + 上書きは、YAML ファイルの追加だけで新カテゴリを追加でき、コードリリースなしで「顧客 A 向け researcher」「顧客 B 向け editor」を追加できます
設計判断 2:immutable_constraints を分離する理由(統制性の担保)
override_system_prompt でプロンプトを差し替えられる仕組みは便利ですが、プロンプトインジェクションの新経路になります。immutable_constraints フィールドを分離し、override されても安全制約は常に末尾に強制連結することで、動的性を保ちながら統制性を担保します。
設計判断 3:サブエージェントは「毎回新規作成」する
動的に override がかかると spec が変わるため、cache は無効化リスクが高くなります。LLM 呼び出しコストに比べてエージェント生成コストは無視できるため、ステートレス設計を徹底します。
設計判断 4:return_spec を明示的にカタログへ書く
サブが「何を返すべきか」を曖昧にすると、結果の構造が呼び出しごとに変わり、メインの後処理が破綻します。return_format フィールドをカタログの必須項目とし、Markdown 見出しレベルで期待する構造を明示することで、サブの出力がメインの処理と整合するように設計します。
設計判断 5:並列実行は Task.WhenAll でシンプルに (※C#特有)
サブエージェント呼び出しは I/O bound(LLM 呼び出し)のため、async/await + Task.WhenAll が C# の慣用パターンとして最適です。
4. 💻 実装:C# / Microsoft Agent Framework (MAF、カタログ駆動 + 上書き)
4-1. SubAgentSpec とカタログ
実装の要点:
-
ImmutableConstraintsは override されても末尾に強制連結される「override 不可の安全制約」 - カタログの読み込みは
ISubAgentCatalogSource経由に抽象化(YAML ファイル以外に DB / Blob Storage 等へも切り替え可能) -
SubAgentCatalogは非同期ファクトリ(CreateAsync)経由で構築(カタログ読み込みが非同期 I/O のため)
💻 ISubAgentCatalogSource — カタログソース抽象化のサンプル実装(C#・約5行、クリックで展開)
カタログソースの抽象化(インターフェース定義):
// 🔑 サンプルでは YAML ファイル読み込み実装、本番では DB / Blob Storage 等に置換可能
public interface ISubAgentCatalogSource
{
Task<IReadOnlyList<SubAgentSpec>> LoadAsync(CancellationToken cancellationToken = default);
}
💻 SubAgentSpec / SubAgentCatalog — サブエージェント仕様とカタログのサンプル実装(C#・約70行、クリックで展開)
using System.ComponentModel;
using System.Text.Json;
using System.Collections.Concurrent;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace ContextEngineering.SubAgents;
public record SubAgentSpec
{
public required string Name { get; init; }
public required string Description { get; init; }
public required string DefaultSystemPrompt { get; init; }
public List<string> DefaultAllowedTools { get; init; } = new();
public string ReturnFormat { get; init; } = "";
// 🔑 これが「override されない安全制約」
public string ImmutableConstraints { get; init; } = "";
public string BuildFinalSystemPrompt(string? overridePrompt = null)
{
var basePrompt = !string.IsNullOrEmpty(overridePrompt)
? overridePrompt
: DefaultSystemPrompt;
// 🔑 override されても ImmutableConstraints は末尾に強制連結
return string.IsNullOrEmpty(ImmutableConstraints)
? basePrompt
: $"{basePrompt}\n\n# Immutable Constraints\n{ImmutableConstraints}";
}
public SubAgentSpec WithOverrides(IEnumerable<string>? additionalTools = null)
{
var newTools = new List<string>(DefaultAllowedTools);
if (additionalTools != null)
{
foreach (var t in additionalTools)
if (!newTools.Contains(t)) newTools.Add(t);
}
// 🔑 record の with 構文で不変記録を生成
return this with { DefaultAllowedTools = newTools };
}
}
public class SubAgentCatalog
{
private readonly ISubAgentCatalogSource _source;
private Dictionary<string, SubAgentSpec> _specs = new();
private SubAgentCatalog(ISubAgentCatalogSource source) => _source = source;
// 🔑 カタログ読み込みが非同期 I/O のため、コンストラクタではなく非同期ファクトリで構築する
public static async Task<SubAgentCatalog> CreateAsync(
ISubAgentCatalogSource source, CancellationToken cancellationToken = default)
{
var catalog = new SubAgentCatalog(source);
await catalog.ReloadAsync(cancellationToken);
return catalog;
}
public async Task ReloadAsync(CancellationToken cancellationToken = default)
{
var specs = await _source.LoadAsync(cancellationToken);
_specs = specs.ToDictionary(s => s.Name);
}
public IEnumerable<object> ListAvailable()
=> _specs.Values.Select(s => new { name = s.Name, description = s.Description });
public SubAgentSpec Resolve(string name, IEnumerable<string>? additionalTools = null)
{
if (!_specs.TryGetValue(name, out var spec))
throw new KeyNotFoundException($"Sub-agent '{name}' not found");
return spec.WithOverrides(additionalTools);
}
}
設計判断の核心:immutable_constraints の末尾強制連結
override_system_prompt でプロンプトを丸ごと差し替えられても、ImmutableConstraints だけは必ず末尾に連結されます。これによりプロンプトインジェクションでシステムプロンプトを上書きされても、安全制約だけは常に有効であり続けます:
💻 ImmutableConstraintsを末尾へ強制連結するロジック(C#・約10行、クリックで展開)
public string BuildFinalSystemPrompt(string? overridePrompt = null)
{
var basePrompt = !string.IsNullOrEmpty(overridePrompt)
? overridePrompt
: DefaultSystemPrompt;
// 🔑 override されても ImmutableConstraints は末尾に強制連結
return string.IsNullOrEmpty(ImmutableConstraints)
? basePrompt
: $"{basePrompt}\n\n# Immutable Constraints\n{ImmutableConstraints}";
}
4-2. ツールレジストリ
💻 ToolRegistry — 名前ベースでツールを解決するレジストリのサンプル実装(C#・約25行、クリックで展開)
/// <summary>
/// 名前ベースでツールを引き当てるレジストリ。
/// YAML の文字列名 → AIFunction の解決に使う。
/// </summary>
public class ToolRegistry
{
// 🔑 ConcurrentDictionary で thread-safe に
// → 並列実行(後述)でも安全
private readonly ConcurrentDictionary<string, AIFunction> _tools = new();
public void Register(string name, AIFunction tool) => _tools[name] = tool;
/// <summary>
/// 許可リスト(YAML の default_allowed_tools)から AIFunction を解決。
/// </summary>
public IEnumerable<AIFunction> Resolve(IEnumerable<string>? allowed = null)
{
// 🔑 null なら全ツールを返す
// → 制限なしモード
if (allowed != null)
{
var set = allowed.ToHashSet();
return _tools.Where(kv => set.Contains(kv.Key)).Select(kv => kv.Value);
}
return _tools.Values;
}
}
4-3. サブエージェント実行ツール
実装の要点:
- カタログから解決した spec を使い、サブエージェントを呼ばれるたびに新規作成(ステートレス)
-
ImmutableConstraintsを含む最終 System Prompt を組み立ててから起動 - 最終メッセージのみをメインへ返し、中間の tool call 履歴は漏らさない
💻 SubAgentRunner — カタログ駆動サブエージェント実行ツールのサンプル実装(C#・約75行、クリックで展開)
public class SubAgentRunner
{
private readonly IChatClient _chatClient;
private readonly ToolRegistry _registry;
private readonly SubAgentCatalog _catalog;
public SubAgentRunner(IChatClient chatClient, ToolRegistry registry, SubAgentCatalog catalog)
{
_chatClient = chatClient;
_registry = registry;
_catalog = catalog;
}
[Description(
"List all available sub-agents from the catalog. " +
// 🔑 「FIRST にこれを呼べ」と LLM に指示
"Call this FIRST to see what sub-agent roles are pre-defined."
)]
public string ListSubAgentCatalog()
=> JsonSerializer.Serialize(new { sub_agents = _catalog.ListAvailable() },
new JsonSerializerOptions { WriteIndented = true });
[Description(
"Launch a sub-agent from the catalog with optional overrides.\n\n" +
"Steps:\n" +
"1. Call ListSubAgentCatalog to see available sub-agents.\n" +
"2. Pick the most appropriate agentName.\n" +
"3. (Optional) Add additionalTools if defaults are insufficient."
)]
public async Task<string> RunSubAgentAsync(
[Description("Agent name from catalog")] string agentName,
[Description("Task prompt")] string taskPrompt,
[Description("Return spec (uses catalog default if null)")] string? returnSpec = null,
[Description("Additional tools beyond defaults")] string[]? additionalTools = null,
[Description("Override system prompt (rare cases)")] string? overrideSystemPrompt = null,
CancellationToken cancellationToken = default)
{
// 🔑 catalog から spec を解決(override 適用)
SubAgentSpec spec;
try
{
spec = _catalog.Resolve(agentName, additionalTools);
}
catch (KeyNotFoundException ex)
{
// 🔑 エラー時は利用可能な選択肢を返す
// → LLM が「別の名前で再試行」できる
return JsonSerializer.Serialize(new
{
error = ex.Message,
available = _catalog.ListAvailable(),
});
}
// 🔑 ツールを名前から実体に解決
var tools = _registry.Resolve(allowed: spec.DefaultAllowedTools);
// 🔑 ImmutableConstraints を含む final prompt を組み立てる
var finalSystemPrompt = spec.BuildFinalSystemPrompt(overrideSystemPrompt);
// 🔑 サブエージェントは「呼ばれるたびに新規作成」
// → ステートレス、cache 無効化リスクなし
var subAgent = _chatClient.AsAIAgent(new ChatClientAgentOptions
{
ChatOptions = new ChatOptions
{
Instructions = finalSystemPrompt,
// 🔑 Tools は ChatOptions に登録する
// 🔑 List<AIFunction> は List<AITool> に暗黙変換できない(List<T> は非共変)ため明示的に変換する
Tools = tools.Cast<AITool>().ToList(),
},
});
// 🔑 return_spec を末尾に明示することで、サブが正しい形式で返す確率を上げる
var effectiveReturn = returnSpec ?? spec.ReturnFormat;
var fullPrompt = $"# Task\n{taskPrompt}\n\n# What to Return\n{effectiveReturn}";
// 🔑 サブエージェントを実行し、最終メッセージのみ返す
var session = await subAgent.CreateSessionAsync(cancellationToken);
var response = await subAgent.RunAsync(fullPrompt, session,
cancellationToken: cancellationToken);
// 🔑 メインへは「完成した final message」だけ返す
// → 中間の tool call 履歴はメインの context に漏れない
return response.Text ?? "";
}
}
4-4. 並列実行
💻 ParallelSubAgentTools — 独立したサブエージェントを並列実行するサンプル実装(C#・約45行、クリックで展開)
public record SubAgentCall(
string AgentName,
string TaskPrompt,
string? ReturnSpec = null,
string[]? AdditionalTools = null);
public class ParallelSubAgentTools
{
private readonly SubAgentRunner _runner;
public ParallelSubAgentTools(SubAgentRunner runner) => _runner = runner;
[Description(
"Launch multiple sub-agents in parallel for INDEPENDENT tasks. " +
// 🔑 「INDEPENDENT」を強調:依存関係があるタスクは並列禁止
"Each call uses the catalog (same as RunSubAgent)."
)]
public async Task<string> RunSubAgentsParallelAsync(
[Description("Sub-agent calls")] SubAgentCall[] calls,
CancellationToken cancellationToken = default)
{
// 🔑 INDEPENDENT なタスクのみ並列実行
// 🔑 各 call を Task として開始(同時並行)
var tasks = calls.Select(c => _runner.RunSubAgentAsync(
c.AgentName, c.TaskPrompt, c.ReturnSpec, c.AdditionalTools, null, cancellationToken
)).ToArray();
// 🔑 Task.WhenAll で全完了を待つ
// → 1 つでも例外なら全体が AggregateException
var results = await Task.WhenAll(tasks);
// 🔑 結果を構造化して返す
return JsonSerializer.Serialize(new
{
results = calls.Zip(results, (c, r) => new
{
agent_name = c.AgentName,
final_message = r,
}).ToArray()
}, new JsonSerializerOptions { WriteIndented = true });
}
}
4-5. メインエージェント組み立て
以下はメインエージェントの組み立て例です。SubAgentCatalog の読み込みは非同期 I/O(ISubAgentCatalogSource.LoadAsync)を伴うため、事前に await SubAgentCatalog.CreateAsync(source) で構築してから渡す設計にしています。
💻 Example.BuildMainAgent — メインエージェント組み立てのサンプル実装(C#・約35行、クリックで展開)
public static class Example
{
public static AIAgent BuildMainAgent(
IChatClient chatClient, SubAgentCatalog catalog, ToolRegistry registry)
{
var runner = new SubAgentRunner(chatClient, registry, catalog);
var parallelTools = new ParallelSubAgentTools(runner);
return chatClient.AsAIAgent(new ChatClientAgentOptions
{
ChatOptions = new ChatOptions
{
// 🔑 メインの System Prompt は固定(プロンプトキャッシングのため)
Instructions =
"You are the main agent. For complex multi-step tasks, " +
"use the sub-agent catalog. Steps:\n" +
"1. ListSubAgentCatalog to see options.\n" +
"2. RunSubAgent for individual sub-tasks.\n" +
"3. RunSubAgentsParallel for independent parallel work.",
// 🔑 メインのツールセットは固定(Mask, Don't Remove 遵守)
// → 動的バリエーションは「サブエージェントの中」で実現
Tools = new List<AITool>
{
AIFunctionFactory.Create(runner.ListSubAgentCatalog),
AIFunctionFactory.Create(runner.RunSubAgentAsync),
AIFunctionFactory.Create(parallelTools.RunSubAgentsParallelAsync),
},
},
});
}
}
5. 📊 ケーススタディ
ワークフローとの使い分け
ワークフローとサブエージェントは競合する仕組みではなく、責務が異なる補完関係にあります。
- ワークフローは「変えたくない部分」を担う:業務フロー・監査要件・承認プロセスなど、統制が必要な処理は静的な ワークフローとして作成することで、監査ログや承認ゲートを確実に実行できます。
- サブエージェントは「柔軟に判断させたい部分」を担う:入力バリエーションが多い判断処理や、顧客ごとに挙動を変えたい部分は、サブエージェントに委ねることでアプリに柔軟性を持たせることができます。
- 業務系では「静的な外枠 + 動的な内側」が現実解:全体を LLM 任せにすると監査が困難に、またワークフローで固めると業務変化に追従できません
つまり、「ワークフローで全体統制を取り、サブエージェントで柔軟性を担保する」 という役割分担が、業務系エージェント設計の基本方針となります (下記図)。
主要ツールのサブエージェント実装比較
主要ツールのサブエージェント実装を、§1 で定義した 動的性・統制性・保守性の 3 軸 で比較すると、ツールごとの明確な差が現れます。
| 実装 | 採用方式 | 動的性 | 統制性 | 保守性 | 備考 |
|---|---|---|---|---|---|
VS Code Copilot Chat runSubagent(基本形) |
完全動的 | ◎ | × | △ | メインが task_prompt を毎回組み立てる。v1.106 以前のデフォルト方式 |
| VS Code Copilot Chat(v1.107+ カスタムエージェント方式) | 事前定義(カタログ) | △ | ◎ | ○ |
chat.customAgentInSubagent.enabled で .agent.md を名前指定で呼び出せる(experimental) |
| GitHub Copilot SDK Custom Agents | 事前定義(カタログ) | △ | ◎ | ○ |
customAgents 配列で事前定義しつつセッションごとに渡せる |
Claude Code (Task ツール) |
事前定義 + 一部動的 | ○ | ○ | ○ |
.claude/agents/*.md で事前定義しつつプロンプトも動的組み立て可 |
| MAF SubAgent パターン(本記事) | カタログ駆動 + 上書き | ◎ | ◎ | ◎ | YAML カタログ + immutable_constraints
|
教訓:
- VS Code Copilot Chat の基本形(完全動的) は動的性が最高ですが、メインが毎回プロンプトを組み立てるため品質が不安定になりやすく、統制手段もありません。個人利用のコーディング支援では十分ですが、業務系で顧客ごとに挙動を変える用途には向きません
- VS Code v1.107 以降の カスタムエージェント方式、GitHub Copilot SDK Custom Agents は事前定義により統制性は高くなりますが、YAML カタログのような外部化ができないため、顧客追加ごとにコードやリポジトリの変更が必要です
-
Claude Code は
.claude/agents/*.mdによる事前定義と動的組み立ての両方をサポートしており、3 軸のバランスは良好です。ただしimmutable_constraintsのような統制強制の仕組みは持ちません -
MAF SubAgent パターン(本記事) はカタログ駆動 +
immutable_constraintsにより、3 軸すべてで ◎ を実現します。YAML カタログを追加するだけで新しい顧客・ドメインに対応でき、コードリリースなしで業務要件変更に追従でき、なおかつプロンプトインジェクション対策も担保されます
つまり、「業務系では動的性 × 統制性 × 保守性の 3 軸を同時に満たす方式を選ぶ」 ことが重要です。個人利用なら「完全動的」または「事前定義」で十分ですが、業務系で複数顧客・複数ドメインを扱う場合は、「カタログ駆動 + 上書き」 を実装することが長期運用の鍵となります。
6. ⚠️ アンチパターン
本記事で扱ったサブエージェント設計の要点を、実装時のセルフレビュー用チェックリスト(アンチパターン)としてまとめました。
実装時は本表を「やってはいけないことリスト」としてご参照ください。特に #1・#6・#7 は業務系での長期運用、プロンプトキャッシング維持、セキュリティに直結する重要項目です。
| # | アンチパターン | 問題点 |
|---|---|---|
| 1 | 「静的固定」を業務系で押し通す | 顧客追加のたびにリリースが必要になる |
| 2 | 「完全動的」で統制手段なしに運用する | LLM が毎回ゼロからプロンプトを書くため品質が安定せず、プロンプトインジェクション耐性もない |
| 3 | メインがサブの内部状態に依存 | サブの内部履歴はブラックボックスとして扱うべき |
| 4 |
return_spec を曖昧にする |
サブが何を返すべきか分からず、結果の構造が崩れる |
| 5 | 並列実行で依存があるタスクを投げる | A の結果を B が使う場合は直列にする |
| 6 |
immutable_constraints を持たない |
プロンプトインジェクション対策(統制性)として必須 |
| 7 | メインのツールセットを動的変更 | プロンプトキャッシングが毎回無効化される |
おわりに
本記事では、コンテキストを「空間的に分離する」サブエージェントの設計パターンをご紹介しました。特に 動的性・統制性・保守性の 3 軸で設計方式を評価し、業務系で 3 軸すべてを満たす「カタログ駆動 + 上書き」方式(MAF SubAgent パターン)の実装を解説しました。
「Mask, Don't Remove」と「カタログ駆動の動的生成」は一見矛盾するように見えますが、メインエージェントのツールセットを固定しつつサブエージェントで動的性を実現することで、両立が可能です。
次回からはエージェントの自己状態管理に移ります。Memory システムを用いて、エージェントがセッションを跨いで自分の状態を持つ仕組みを解説します。
参考文献
VS Code / GitHub Copilot 関連
- VS Code Docs「Subagents in Visual Studio Code」
- VS Code Docs「Custom agents in VS Code」
- Zenn「GitHub Copilot Chat のサブエージェントを検証してみた: コンテキスト分離の効果と限界」(2025/12/03、runSubagent の基本形と v1.107 以降のカスタムエージェント方式の両方を解説)
- Zenn「GitHub Copilot サブエージェントによるオーケストレーター パターンの実践」(v1.107 以降の
.agent.md方式による実装例) - GitHub Docs「Custom agents and sub-agent orchestration」
- VS Code Docs「Debug chat interactions」
Microsoft Learn / Microsoft Agent Framework
その他ツール
- Anthropic「Claude Code — How Claude Code works (Tools / Task tool)」
- CrewAI Docs「Agents — YAML configuration」
業界研究
- Manus AI (2025/7/18). "Context Engineering for AI Agents: Lessons from Building Manus"
- Philipp Schmid (2025/12/4). "Context Engineering for AI Agents: Part 2"