1. はじめに:LLMは記憶を持たない
LLM APIは基本的にステートレスです。リクエストごとに「システム指示・ツール定義・会話履歴・今回の入力」をすべて送り直さない限り、モデルは直前のやり取りすら知りません。
にもかかわらず、エディタ上のチャットは「さっきのファイルの続き」「この変更を取り消して」に自然に応答し、Agent modeでは数十ステップのタスクを自走します。この体験の正体はモデルの記憶ではなく、クライアント側のオーケストレーションです。
本記事では、VS Code GitHub Copilot Chatを題材に、次の問いを設計思想とトレードオフの観点から解剖します。
- 状態はどこに持ち、毎ターンどう組み立てるのか
- 自律エージェントはどんなループで動き、計画(TODO)はどう管理されるのか
- モデルを切り替えても会話が壊れないのはなぜか、コストと速度はどう守るのか
- 拡張性とガバナンスをどう両立し、どこに限界があるのか
2. 全体像:3層アーキテクチャと処理シーケンス
2.1 役割分担【設計推論】
| 層 | 主な責務 | ステートを持つか |
|---|---|---|
| Extension Host(ローカル) | UI、コンテキスト収集(エディタ状態・選択範囲・ワークスペース)、プロンプト組み立て、ツール実行、ユーザー承認、会話状態の保持 | 持つ(真実の源泉) |
| Proxy / Gateway(クラウド) | 認証・認可、ポリシー適用、モデルルーティング、プロバイダ差異の吸収、レート制御、ログ/監査 | 原則持たない |
| LLM Backend | 推論、Tool Calling(関数呼び出しの生成) | 持たない |
ポイントはステートの置き場所の非対称性です。状態をクライアントに寄せることで、プロキシとLLMは水平スケールしやすく、モデル差し替えも容易になります。代償として、毎ターン全文脈を送信する帯域・トークンコストが発生し、それを緩和するのが§5のPrompt Cachingです。
2.2 処理シーケンス
3. コンテキスト・エンジニアリング:文脈の収集と注入
3.1 暗黙的/明示的コンテキスト【確認済+一般論】
| 種別 | 例 | 設計意図 |
|---|---|---|
| 明示的 |
#file、添付、選択範囲、@participant、MCP Resource(#参照) |
ユーザーが意図を制御できる。再現性が高い |
| 暗黙的 | アクティブエディタ、開いているファイル、ワークスペース情報、(Agent modeでは)現在のエディタのヒント | 入力の手間を減らす。ただしノイズ混入リスクがある |
| 永続的 |
.github/copilot-instructions.md、*.instructions.md、AGENTS.md
|
チーム規約を毎ターン自動適用する |
公式ドキュメントによれば、.github/copilot-instructions.md はワークスペースルートで自動検出され、そのワークスペースの全チャットリクエストに適用されます。*.instructions.md は対象ファイルパターンに応じた条件付き適用、AGENTS.md も常時有効な指示として扱えます。
3.2 なぜ「毎ターン注入」なのか
LLMは記憶しないため、規約を一度伝えても次のリクエストには存在しません。毎ターンの注入は、ステートレス性に対する最もシンプルで確実な解です。
- 利点: 規約の抜け漏れがない/リポジトリにコミットして共有できる/モデル切替でも効く
- 欠点: 毎ターン固定トークンを消費する/指示が肥大化するとコンテキストを圧迫し、重要指示の遵守率も下がる
→ 対策は「プロジェクト共通は短く」「領域別は applyTo で条件付き」という指示の階層化です。
3.3 プロンプト組み立ての擬似コード【一般論/擬似コード】
// 擬似コード: 実装の断定ではなく、設計パターンの説明用
interface PromptParts {
system: string; // ツール利用規約, 出力形式, 安全指示
instructions: string[]; // copilot-instructions.md 等
tools: ToolDef[]; // JSON Schema付きツール定義
history: Message[]; // 過去のターン (ツール結果含む)
turnContext: ContextItem[]; // 今回収集した文脈 (選択範囲, 添付, エディタ状態)
userInput: string;
}
function assemble(p: PromptParts, budget: TokenBudget): Message[] {
// 1. 変動しにくい順に並べる (キャッシュ効率のため: §5.2)
const stable = [
msg("system", p.system),
...p.instructions.map(i => msg("system", i)),
];
// 2. 履歴は予算に合わせて圧縮
const history = compact(p.history, budget.forHistory);
// 3. 今回の文脈と入力は末尾 (最も注意が向きやすい位置)
const turn = [
msg("user", renderContext(p.turnContext)),
msg("user", p.userInput),
];
return [...stable, ...history, ...turn];
}
3.4 トークンバジェット制御と「Lost in the Middle」【一般論】
コンテキストウィンドウは有限で、長いほど遅く高価です。さらに、長文脈では中間部分の情報が使われにくいことが研究で示されています(Liu et al., "Lost in the Middle", TACL 2024)。したがってオーケストレーション層は次を行います。
| 手法 | 内容 | トレードオフ |
|---|---|---|
| Pruning(刈り込み) | 古いツール出力・低関連度の文脈を削除 | 実装が単純。ただし後続ターンで必要になる情報を失うリスク |
| Compaction(要約圧縮) | 古い履歴を要約に置換 | 情報密度を維持できる。ただしプレフィックスが変わりキャッシュが無効化される |
| 配置戦略 | 重要指示は先頭、直近の入力・必要な文脈は末尾に配置 | 中間埋没を緩和。キャッシュ効率とも整合する |
| 優先度付き割り当て | 指示 > 直近の履歴 > 明示添付 > 暗黙文脈の順で予算配分 | 優先度設計がUXを左右する |
実務上の学び: 「何を入れるか」より「何を捨てるか」の設計が品質を決める。
4. Agent Modeの深層:ReActループとDynamic Planning(TODO)
4.1 ReActループ【確認済(概要)+一般論】
Agent modeは、公式ドキュメントでも「高レベルの指示から計画し、ツールを呼び、結果を観察して改善する」自律動作として説明されています。構造はReAct(Reasoning + Acting)パターンそのものです。
ループ制御の設計論点は3つです。
- 終了条件: 「tool_callsが無い」を自然終了とし、ステップ上限・ユーザーキャンセルを安全弁にする
- エラーの扱い: 失敗した出力(コンパイルエラー、テスト失敗)をそのまま観察として返し、モデルに自己修正させる
- 履歴の肥大: ツール出力は大きくなりがち。ループが回るほどコンテキストを圧迫するため、§3.4の圧縮が必須
4.2 TODOによるPlanning【確認済(機能)+設計推論】
公式ドキュメントでは、Agent側の todo list により「全体目標に集中し、進捗を追跡できる」とされています。Plan agent は実装計画を作成し、セッションメモリ(/memories/session/plan.md)に保存、実装エージェントへ計画と会話文脈を引き継ぎます。
TODOをツールとして実装する設計は、ステートレスLLMに対する巧妙な解です。
- 計画をモデルの「頭の中」ではなく外部の構造化状態に置く
- 各ステップで現在のTODOが履歴(ツール結果)として再注入されるため、モデルが自分の進捗を再認識できる
- UIはその構造化状態を描画するだけで、進捗表示が実現する
ツール呼び出しのイメージ(擬似JSON):
{
"tool": "manage_todo_list",
"arguments": {
"todos": [
{ "id": 1, "title": "既存の認証モジュールを調査", "status": "completed" },
{ "id": 2, "title": "トークン検証ロジックを修正", "status": "in-progress" },
{ "id": 3, "title": "ユニットテストを追加・実行", "status": "not-started" }
]
}
}
注: ツール名・スキーマは説明用の例示。実際の名称・仕様は拡張機能のバージョンにより異なる可能性がある(要確認)。
運用上のルール(モデルに守らせる定石): 同時に in-progress にするのは1件/完了したら即更新/状況が変われば計画自体を書き換える。計画を固定せず、観察結果に応じて動的に更新できる点がPlanningの肝です。
4.3 トレードオフ
| 設計 | 利点 | リスク |
|---|---|---|
| 計画をTODOとして外部化 | 進捗可視化、長いタスクで迷子になりにくい | TODO更新自体がトークンとステップを消費 |
| 完全自律ループ | 人手が減る | 暴走・コスト爆発・意図しない変更(→§6のHuman-in-the-Loop) |
| Plan → Implementの分離 | 実装前に人間がレビューできる | 計画が実態と乖離するとき、再計画が必要 |
5. マルチモデル時代の状態引き継ぎとPrompt Caching
5.1 モデル非依存の会話表現と変換【設計推論+一般論】
モデル切替後も会話を続けられるのは、状態を特定ベンダー形式ではなく正規化された内部表現で保持し、送信時にプロバイダ形式へ変換する設計だからです(ここでの変換位置が拡張機能側かプロキシ側かは非公開。両方に変換責務が分かれていても不思議はない)。
正規化表現の例(擬似):
[
{ "role": "user", "content": [{ "type": "text", "text": "この関数のバグを直して" }] },
{ "role": "assistant", "content": [
{ "type": "text", "text": "原因を調べます。" },
{ "type": "tool_call", "id": "call_1", "name": "read_file", "args": { "path": "src/auth.ts" } }
]},
{ "role": "tool", "tool_call_id": "call_1", "content": "...ファイル内容..." }
]
変換先の差異(一般論):
| 項目 | OpenAI系 | Anthropic系 |
|---|---|---|
| ツール結果 |
role: "tool" のメッセージ |
role: "user" 内の tool_result ブロック |
| ツール呼び出し | assistantメッセージの tool_calls
|
assistantコンテンツ内の tool_use ブロック |
| システム指示 |
system / developer ロール |
トップレベルの system フィールド |
| 引数 | JSON文字列 | パース済みJSONオブジェクト |
変換の落とし穴:
- モデル固有の思考ブロック: あるモデルの reasoning/thinking ブロックを別モデルへ引き継げない場合がある。Anthropicの公式ドキュメントでも、受け手モデルが読めない thinking ブロックは落とされ、その位置以降のキャッシュプレフィックスが変わると明記されている
- コンテキスト長・ツール数の上限差: モデル切替時に再プルーニングが必要になる
- ツール定義の互換: JSON Schemaの対応範囲差
5.2 Prompt Cachingと「プレフィックス設計」【確認済(各社仕様)+設計推論】
Prompt Cachingは共通プレフィックスのKV状態を再利用する仕組みです。全文脈を毎ターン送るステートレス設計の弱点(遅延とコスト)を直接緩和します。成立条件は「プレフィックスが完全一致すること」なので、プロンプトの並び順がそのまま経済性を決めます。
[安定] ツール定義 → システム指示 → copilot-instructions → 過去の履歴 | [変動] 今回の文脈 → 今回の入力
▲ ここまでをキャッシュ対象に
キャッシュ効率を壊す典型例(Anthropic/OpenAIの公式ドキュメントで共通して指摘される論点):
- 先頭付近にタイムスタンプやユーザー固有値を入れる
- ツール定義の順序・内容・スキーマを途中で変える(キャッシュ全体が無効化)
- Compaction・要約で過去履歴を書き換える
- thinking設定・effort設定など、プロンプトにレンダリングされる設定を変える
- モデルを切り替える(モデルごとにキャッシュは別物)
→ Copilot Chatがキャッシュを具体的にどう使っているか(breakpointの位置等)は公開情報からは断定できないため、本記事では「この制約に沿った設計が合理的」という示唆にとどめます【設計推論】。
5.3 プロバイダ別仕様の比較(2026-10-04時点・公式ドキュメント確認)
| 項目 | Anthropic (Claude) | OpenAI | Google Gemini |
|---|---|---|---|
| 有効化 |
cache_control(トップレベルの自動、またはブロック単位の明示) |
既定で有効(GPT-5.6以降はimplicit/explicit選択可) | implicit: 2.5以降で既定有効/explicit: 明示的にキャッシュ作成 |
| プレフィックス順序 |
tools → system → messages
|
隠しシステム → tools → developer → 履歴 | (公式の順序規定は要確認) |
| 最小トークン | モデル別: 512(Sonnet 5.5, Opus 5.5 等)/1,024(Sonnet 4.6, 4.5 等)/2,048(Opus 4.7 等)/4,096(Haiku 4.5, Opus 4.5・4.6)。未満はエラーなく非キャッシュ | GPT-5.6以降: 1,024(可視入力)。それ以前: リクエスト設定により変動 | モデル別: 1,024(2.5 Flash, 3 Flash Preview)/4,096(2.5 Pro, 3 Pro Preview)。※ドキュメントの版により値が異なる記述あり。公開前に再確認 |
| TTL | 既定5分(ヒットごとに延長)、1時間も選択可(追加コスト)。起点は「リクエスト開始時刻」 | GPT-5.6以降: 30分(最終ヒット/書込から)。それ以前: in_memory は通常5〜10分(最長1時間)、24h 拡張あり |
explicit: TTL指定(未指定は1時間)、上下限なし、保管時間で課金 |
| 書込・読込コスト | 書込 1.25倍(5分)/ 2倍(1時間)、読込 0.1倍が基本(モデルにより0.025〜0.05倍の例外あり) | GPT-5.6以降: 書込1.25倍、読込0.1倍(一部0.05倍)。それ以前は書込追加料金なし | implicit: 保証なし/explicit: 保証あり(課金体系はGoogleの料金ページ参照) |
| breakpoint | 最大4つ、遡及検索は20ブロック | GPT-5.6以降: explicit最大4書込 | – |
| 利用状況の確認 |
cache_read_input_tokens 等 |
cached_tokens、cache_write_tokens
|
レスポンスのusageメタデータ |
記事本文での書き方の推奨: 「Prompt Cachingの最小トークンは各社1,024前後が目安だが、モデル世代で大きく異なる(Anthropicは512〜4,096)。必ず利用モデルの最新ドキュメントを確認すること」。旧来の「OpenAIは1,024トークンから128トークン刻み」という記述は、GPT-5.6以降には当てはまらない。
6. 拡張性とセキュリティ:ガバナンス設計
6.1 拡張性の3つの入口【確認済】
| 入口 | 向いているもの | 設計上の位置づけ |
|---|---|---|
vscode.lm(LM API) |
拡張機能から言語モデルを呼ぶ、ツールを提供する | VS Code内部APIと深く統合したい場合 |
| MCP | 外部システム連携(DB、SaaS、社内API)を、エディタ非依存で再利用 | プロトコル標準化で、クライアント(VS Code、他IDE、CLI等)を横断して使い回せる |
| Chat Participant |
@名前 で呼び出す特化ワークフロー |
会話の入口を拡張【一般論・要確認】 |
vscode.lm.registerTool の最小形(公式サンプルの構造に準拠):
// package.json の contributes.languageModelTools に宣言 (name, modelDescription, inputSchema ...)
vscode.lm.registerTool('my-ext_tabCount', {
// 実行前に確認ダイアログの文言を返せる = Human-in-the-Loop の入口
async prepareInvocation(options, _token) {
return { invocationMessage: 'タブ数を取得します' };
},
async invoke(options, _token) {
const count = vscode.window.tabGroups.activeTabGroup.tabs.length;
return new vscode.LanguageModelToolResult([
new vscode.LanguageModelTextPart(`${count} tabs`),
]);
},
});
LM API利用側の注意(公式ドキュメント記載):
- LM APIはシステムメッセージ非対応(User/Assistantメッセージで組み立てる)
- 統合テストでの利用はレート制限のため非推奨
- モデルは
selectChatModels({ vendor: 'copilot', family: ... })で選択
6.2 MCPの組み込み状況【確認済】
VS CodeのMCPクライアントがサポートする範囲:
| 機能 | 状況 |
|---|---|
| Tools | Agent modeで利用可能。ツール単位/ツールセット単位で有効・無効を切替 |
| Prompts | スラッシュコマンドとして提示 |
| Resources | チャットで # から参照。リソーステンプレートにも対応 |
| Sampling | MCPサーバー側からのモデル呼び出し依頼を処理(導入時はexperimental、その後拡張) |
| Elicitation | form/URLモードのユーザー入力要求を処理 |
| 認証 | OAuth(認可コードフロー、デバイスコードフロー)対応 |
| 開発支援 | 開発モード(dev キー)でNode.js/PythonサーバーのDebug |
設計上の含意: MCPは「ツール定義の標準」にとどまらず、サーバーからクライアント(ユーザー・モデル)への逆方向の依頼(Sampling/Elicitation)まで含む双方向プロトコルです。これは同時に攻撃面の拡大でもあります(§6.4)。
6.3 Human-in-the-Loopと信頼境界【一般論+確認済(部分)】
自律エージェントの安全装置は、副作用の大きさに応じた承認粒度として設計します。
| 操作 | リスク | 推奨される制御 |
|---|---|---|
| 読み取り(検索、ファイル読込) | 低(ただし機密の流出経路) | 原則自動、除外設定が必要(§6.4) |
| ファイル編集 | 中 | 差分レビュー、Undo可能にする |
| ターミナル実行・外部通信 | 高 | 実行前の明示承認、許可リスト |
| MCP/拡張機能ツール | 提供元次第 | サーバー信頼の明示確認、ツール単位の有効化 |
承認疲れ(approval fatigue)にも注意が必要です。確認が多すぎるとユーザーは無条件承認に流れ、制御が形骸化します。「高リスクだけ確実に止める」設計が肝です。
6.4 コンテンツ除外:.copilotignore ではなく Content exclusion【確認済】
- 機密ファイルをCopilotから隠す公式機能はContent exclusion。リポジトリ管理者/Organizationオーナー/Enterpriseオーナーが設定する
- 対象はCopilot Business / Enterpriseプラン
- 除外されたファイルの内容は、Copilot Chatの回答に使われない
- クライアント(VS Code拡張)は現在のリポジトリURLをサーバーに送り、サーバーが返すポリシーをクライアントが適用する
- 重要な制約: GitHub公式ドキュメントは、VS CodeのEditモード・Agentモードでは未サポートと明記している(また、Copilot CLIやCoding Agentも非対応との記述がある)
アーキテクトへの示唆: 「ポリシーはサーバーで集中管理、適用はクライアント」という分散ガバナンスは、クライアントの機能追加(新しいモード)のたびに適用漏れが生じうる。新しい経路(エージェント、ツール、MCP)を足すたびに、データ流出経路の棚卸しをアーキテクチャレビューに組み込むべきです。コミュニティでは「
.copilotignoreを置いたのに機密が参照された」という報告があり、存在しない仕組みへの過信が実害につながることも示している。
また、MCPサーバーやツール出力は信頼できない入力です。ツール結果に埋め込まれた命令(プロンプトインジェクション)がエージェントの次の行動を誘導しうるため、副作用の大きいツールには必ず承認ゲートを置いてください【一般論】。
7. アーキテクトが得られる学び
自社のAIシステム開発に転用できる原則をまとめます。
- 状態は「モデルの外」に置く。 会話・計画(TODO)・設定をクライアントまたは自前ストアで保持し、毎ターン再構築する。モデルは差し替え可能な計算資源として扱う
- プロンプトは「安定→変動」の順に設計する。 並び順がそのままキャッシュ効率とコストを決める。タイムスタンプ・ユーザー固有値は末尾へ
- 「捨てる設計」を最初に決める。 Pruning・Compactionの方針、優先度付きトークン配分、Lost in the Middle対策としての配置戦略
- 計画は外部化された構造化状態にする。 TODOをツール化し、観察結果に応じて動的更新する。進捗はUIに直結する
- 内部表現を正規化し、境界で変換する。 マルチモデル対応は、ベンダー形式を内部に持ち込まないことで実現する。ただしthinking等のモデル固有要素は引き継げない前提で設計する
- 拡張点は標準プロトコル(MCP)に寄せ、承認粒度は副作用で決める。 双方向機能(Sampling/Elicitation)ほど攻撃面が広がる
- ガバナンスは「適用範囲」を継続的に検証する。 ポリシー定義があっても、新しい実行経路で効かなければ無いのと同じ。公式機能の対応状況をリリースごとに確認する
-
仕様は必ず一次情報で再確認する。 本記事の調査でも、
.copilotignoreの誤解、Prompt Cachingの最小トークンの世代差、LM APIのシステムメッセージ非対応など、「よく知られた話」が現状と異なる点が複数あった
付録:参考一次情報(執筆時点で確認したURL)
- VS Code Language Model API: https://code.visualstudio.com/api/extension-guides/ai/language-model
- VS Code Language Model Tool API: https://code.visualstudio.com/api/extension-guides/ai/tools
- VS Code 1.101 Release Notes(MCP prompts/resources/sampling/auth): https://code.visualstudio.com/updates/v1_101
- VS Code Custom instructions: https://code.visualstudio.com/docs/agent-customization/custom-instructions
- VS Code Planning with agents(plan agent / todo list): https://code.visualstudio.com/docs/agents/planning
- GitHub Copilot Content exclusion: https://docs.github.com/en/copilot/concepts/context/content-exclusion
- Anthropic Prompt caching: https://platform.claude.com/docs/en/build-with-claude/prompt-caching
- OpenAI Prompt caching: https://developers.openai.com/api/docs/guides/prompt-caching
- Google Gemini Context caching: https://ai.google.dev/gemini-api/docs/caching
- Liu et al., Lost in the Middle(TACL 2024)
公開前の最終確認事項: ①§5.3のGemini最小トークン(ドキュメント版による差異)②§4.2のTODOツール名・スキーマ ③Chat Participant APIの現行仕様 ④Content exclusionのAgent mode対応状況(変更されている可能性)