以前の記事「Context Engineeringとは何か」では、概念として「エージェントに何を見せるか」を扱いました。そして実装編では、Harness Engineeringを実装するで「何をさせて良いか」、Loop Engineeringを実装するで「結果をどう検証し直すか」を実装しました。
今回はその前提となるもう一つの設計——エージェントに何を見せるかを、実際のコードで組んでいきます。
Context Engineeringの本質は「情報を追加すること」ではなく、「限られたコンテキストウィンドウの中で、今この判断に必要なものだけを渡すこと」です。情報を増やすほど賢くなるわけではなく、むしろ関係のない情報が多いほど、エージェントは重要な部分を見失います。
1. コンテキストの階層を分ける
まず、エージェントに渡す情報を「更新頻度」で分類します。これは実装上、最も効果が大きい整理です。
| 階層 | 例 | 更新頻度 |
|---|---|---|
| 恒久情報 | プロジェクトの規約、命名ルール、禁止事項 | ほぼ変わらない |
| セッション情報 | 今回のタスクの目的、直前の会話 | セッション単位 |
| 動的情報 | ファイルの中身、検索結果、ツールの実行結果 | 呼び出しごと |
Claude Codeでは、恒久情報をCLAUDE.mdに、動的情報をツール呼び出しの結果として渡す設計になっています。この分離がないと、恒久的なルールを毎回のプロンプトに手動で書き足すことになり、トークンを浪費した上に書き漏らしが発生します。
# CLAUDE.md(恒久情報の置き場)
- コミットメッセージは日本語で書く
- テストを実行せずにpushしない
- 外部APIキーはコード中にハードコードしない
2. 「見せない」判断を先に設計する
Context Engineeringで初心者が陥りやすい失敗は、「念のため全部見せる」判断です。ファイル全体、ログ全部、会話履歴全部——これは一見安全に見えますが、実際には次の問題を起こします。
- 関係ない情報に埋もれて、重要な指示が読み飛ばされる
- トークン予算を使い切り、肝心の出力部分が切り詰められる
- ノイズが増えることで、モデルの判断精度がむしろ落ちる
具体的な実装では、「見せる範囲」を明示的に絞ります。
def build_context(file_path: str, task: str) -> str:
content = read_file(file_path)
# ファイル全体ではなく、タスクに関連する関数だけを抽出
relevant_sections = extract_relevant_functions(content, task)
return f"# 関連コード\n{relevant_sections}\n\n# タスク\n{task}"
「関連する部分だけを渡す」ための抽出ロジックそのものが、Context Engineeringの実装の核心です。ここを雑にすると、結局「全部渡す」に逆戻りします。
3. トークン予算を先に割り当てる
コンテキストウィンドウは有限のリソースです。何にどれだけ使うかを、実装として先に決めておきます。
TOKEN_BUDGET = {
"system_rules": 500, # CLAUDE.md相当
"task_description": 200, # 今回の指示
"relevant_code": 3000, # 関連コード
"tool_results": 2000, # 直近のツール実行結果
"history": 1000, # 会話履歴の要約
}
この予算を超えそうな場合、どこから削るかも実装の一部です。優先度は「タスクの指示 > 恒久ルール > 関連コード > ツール結果 > 履歴」の順にするのが実務上安定します。指示とルールを最後まで残し、履歴を最初に削る、という判断です。
4. 長い会話履歴は要約で圧縮する
セッションが長くなると、会話履歴がコンテキストウィンドウを圧迫します。ここでLoop Engineeringと同じ考え方が使えます——古い履歴をそのまま保持するのではなく、要約して圧縮します。
def compress_history(messages: list[str], keep_recent: int = 5) -> list[str]:
if len(messages) <= keep_recent:
return messages
old_messages = messages[:-keep_recent]
recent_messages = messages[-keep_recent:]
summary = summarize(old_messages) # 別途LLM呼び出しで要約
return [f"[過去の要約] {summary}"] + recent_messages
直近のやり取りはそのまま、それより前は要約に置き換える。これにより、セッションが長くなってもコンテキスト消費が線形に増え続けることを防げます。
5. ツールの実行結果も「見せ方」を設計する
ツールが返す結果をそのままコンテキストに積むと、すぐに予算を使い切ります。特に大きなファイルの中身やAPIレスポンスは、必要な部分だけを抜き出して渡します。
def summarize_tool_result(result: str, max_chars: int = 1000) -> str:
if len(result) <= max_chars:
return result
return result[:max_chars] + f"\n...(以下{len(result) - max_chars}文字省略。詳細が必要なら再度読み込んでください)"
省略した事実を隠さず明示するのがポイントです。エージェントが「情報が足りない」と気づいて、必要に応じて再取得を判断できるようにします。
まとめ
Context Engineeringの実装は、次の5点に集約されます。
- 恒久情報・セッション情報・動的情報を分けて管理する
- 「見せない」判断を先に設計し、関連部分だけを抽出する
- トークン予算を先に割り当て、削る優先順位を決めておく
- 長い履歴は要約で圧縮する
- ツール結果も全量ではなく要約し、省略した事実を明示する
拙著『AIエージェント設計論 — Harness/Loop EngineeringからRAGまで』では、この考え方の全体像を扱っています。