Blog

コンテキストエンジニアリングとは何か - コンテキストウィンドウに「何を入れないか」を設計する

2026-07-19 23:00:00

コンテキストエンジニアリングとは何か - コンテキストウィンドウに「何を入れないか」を設計する

コンテキストエンジニアリングとは、コンテキストウィンドウに何を・どう載せるかを設計する規律です。

プロンプト1回を磨くことでも、コンテキストウィンドウを大きくすることでもありません。

むしろ「何を入れないか」を決めること。コンテキストウィンドウは広くても、入れすぎれば遅く・高く・不正確になるからです。

前回の「ループエンジニアリングとは何か - プロンプトより「ループ」を設計する」が「エージェントをどう回すか(縦軸)」なら、こちらは「コンテキストウィンドウに何を載せるか(横軸)」。

この記事では、コンテキストエンジニアリングが何で、どんな手があり、なぜ効くのかを、検証実装を動かしながら整理します。対象はエージェントを設計・運用する人です。

コンテキストエンジニアリングとは

AIを使う単位は、プロンプト(1回の入力)から、もっと広い「コンテキストウィンドウの中身全体」へ移りました。

コンテキストウィンドウには、システムプロンプト・会話履歴・ツールの定義と結果・ユーザーの今回の依頼・生成される出力(推論モデルなら思考トークンも)が含まれます。

コンテキストエンジニアリングは、この入れるものを選び、形を整え、量を抑える仕事です。

コンテキストウィンドウが広いなら全部入れればいい、とはなりません。理由は2つです。

  • コスト: 入れたトークンには毎回お金がかかる。長い履歴やツール出力を丸ごと積むと、トークン肥大で請求が膨らむ。
  • 品質: 入れすぎると精度も落ちる(長文での劣化=context rot、中央の見落とし=lost-in-the-middle)。コンテキストウィンドウに余裕があることと、そこを正確に読めることは別です。

トークン肥大を止める5つの手

実務で使う手は、だいたい次の5つに整理できます(参考: 「Fix Agent Failures With Context Engineering for LLMs」)。

  • 検索(retrieval / RAG): 全部を入れず、関連する断片だけを引いて入れる。外部知識も長文も、必要な箇所だけを精度とコストで選んで取り込む。
  • 圧縮(compaction): 伸び続ける会話履歴やツールの長い出力を、要約に畳んでコンテキストウィンドウを一定に保つ。抱えきれない進捗や中間結果は、外部ファイル(ノート)に書き出して文脈から外し、必要な時だけ読み戻す。長く回るループほど効く。
  • 選別と順序: 不要になった文脈を捨て、重要な情報はコンテキストウィンドウの端(先頭・末尾)に置く(中央は見落とされやすいため)。
  • キャッシュ: 変わらない前置き(システムプロンプト等)はプロンプトキャッシュで入力コストを下げる。
  • 構造化: 使うツールだけを提示する(多すぎると選択を誤り、定義のトークンもかさむ)。さらに、1つの巨大な文脈で抱えず、サブエージェントに小分けして各自の文脈で処理し、結果だけ受け取る。Tool Search Tool(必要なツール定義だけを検索して読み込む=ツール版のRAG)も有用です。

注意点: まず検索・選別、それでも長いものだけを圧縮で畳む。

鍵になるのがAttention budgetという考え方です。モデルが1回の推論で文脈に振り向けられるAttentionの総量には上限があります。

そのため、トークンを足す前に毎回「これは“高シグナル”か?」と問い、迷うなら入れない。賢いモデルほど、この引き算が効きます(参考: 「Effective context engineering for AI agents」)。

3つのコーディングエージェントでの実装例(CLAUDE.md / AGENTS.md / GEMINI.md)

このコンテキストエンジニアリングを、いちばん身近に実装しているのがコーディングエージェントの「指示ファイル」です。Claude Code・OpenAI Codex・Gemini CLI は、それぞれ持続的なコンテキストを決まったファイルから読み込みます。

ツール

ファイル

読み込み階層(広い→狭い)

取り込み(import)

Claude Code

CLAUDE.md

組織ポリシー → ユーザー ~/.claude/CLAUDE.md → プロジェクト ./CLAUDE.md → ローカル CLAUDE.local.md → サブディレクトリ

@パス で他ファイルを取り込み(Claude専用)

OpenAI Codex

AGENTS.md

~/.codex/AGENTS.md → プロジェクトを上から辿る(ネスト可)

AGENTS.md は ツール非依存の開放標準

Gemini CLI

GEMINI.md

~/.gemini/GEMINI.md → プロジェクト → サブディレクトリ

@file.md(相対/絶対・再帰は最大5・コードブロック内は無効)

共通するのは「広い順に重ねて、近いものを優先する」こと。そして「@import で分割する」「サブディレクトリに置くと、そこで作業する時だけ読まれる」という、まさにコンテキストエンジニアリングの形になっています。

情報をどのファイルに分けるか

ここから学べる実務テクニックは、情報を1ファイルに詰めずスコープで分けることです。

  • ユーザー全体(~/.claude 等): 自分の好み・共通の書き方。全プロジェクトで効く。
  • プロジェクト直下: そのリポの規約・アーキテクチャ・命名・テストコマンド。チームで共有する。
  • サブディレクトリ: そのモジュール固有の事情。そこで作業する時だけ読み込まれる(=必要な時に必要な分だけ=just-in-time)。
  • @import で小分け: 肥大した1ファイルは、役割ごとの小さなファイルに割り、@ で必要なものだけ参照する。

これらの指示ファイルは人がキュレートするメモリでもあります。

ツールをまたぐなら AGENTS.mdに寄せる のが楽です。Claude Code 側は CLAUDE.md@AGENTS.md の1行を書いて取り込めば、3ツールに対して1つのファイルで共有できます。

実検証: 規程QAで「キャッシュ・自作ツール・既存ツール」を1つに

題材は社内規程(返品ポリシー)へのQAアシスタントです。

「開封済みの化粧品は返品できますか?根拠も」といった質問に、規程を引用して答えます。

本検証では、3つのテクニックを実践します。

  1. 長い規程をプロンプトキャッシュに固定する
  2. 回答の根拠は自作のcite ツールで規程本文から引用する
  3. 回答の要点は、既存の bash ツールでログファイルへ追記する

LLM単体にできるのは、学習済みの知識をもとに文章を返すことだけです。ツールは、その外側の機能をLLMから呼び出せるようにする仕組みです。あらかじめ「使ってよいツールの一覧(名前・説明・引数)」を渡しておくと、AIが「ここは調べる必要がある」「ここはメールを送る場面だ」と判断し、必要なツールを自分で呼び出します。呼び出した結果はコンテキストウィンドウに戻り、それを踏まえて続きを考えます。

代表的なツールは次のとおりです。

入手元

ツール例

Anthropic 公式

Web検索(web_search)、Webページ取得(web_fetch)、コード実行(code_execution)、シェル(bash)、ファイル編集(text_editor)、記憶(memory)、画面操作(computer)、ツール検索(tool_search)

OpenAI 公式

Web検索(web_search)、ファイル検索(file_search)、コード実行(code_interpreter)、画面操作(computer_use)、画像生成(image_generation)、リモートMCP接続(mcp)

Google 公式

Google検索グラウンディング(google_search)、URLコンテキスト(url_context)、コード実行(code_execution)、ファイル検索(File Search)、Google Mapsグラウンディング、リモートMCP接続

各社の公式MCP

Google Workspace(Gmail、Calendar、Drive)、GitHub、Notion、Linear、Stripe、Sentry、Cloudflare、Figma、Slack、Canva

自作の関数ツール

料金・為替の計算、社内DB/在庫の照会(SQL)、予約枠チェック、請求書・PDF生成、入力バリデーション(この記事の実検証の cite もこれ)

たとえば「開封した化粧品は返品できる?」という質問なら、AIは規程を調べるツールで該当条項を引き、その根拠を添えて答えられます。ツールが無ければ、同じAIでも「一般論を文章で返す」までしかできません。

import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.messages.CacheControlEphemeral;
import com.anthropic.models.messages.ContentBlock;
import com.anthropic.models.messages.ContentBlockParam;
import com.anthropic.models.messages.Message;
import com.anthropic.models.messages.MessageCreateParams;
import com.anthropic.models.messages.MessageParam;
import com.anthropic.models.messages.TextBlockParam;
import com.anthropic.models.messages.Tool;
import com.anthropic.models.messages.ToolBash20250124;
import com.anthropic.models.messages.ToolResultBlockParam;
import com.anthropic.models.messages.ToolUseBlock;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;

public class DocQaAgent {

  private static final String MODEL = System.getenv().getOrDefault("LLM_MODEL", "claude-sonnet-5");
  private static final int MAX_TURNS = 5;
  private static final long MAX_TOKENS = 1_024L;
  private static final String CITE_TOOL_NAME = "cite";
  private static final String BASH_TOOL_NAME = "bash";

  private static final Path OUTPUT_MD = Path.of("output.md");
  private static final Path QA_LOG_MD = Path.of("qa_log.md"); // bash ツールで追記させる回答ログ

  private static final Splitter SECTION_SPLITTER =
      Splitter.onPattern("\\R+").trimResults().omitEmptyStrings();
  private static final Splitter QUERY_SPLITTER =
      Splitter.onPattern("[\\s、,//]+").trimResults().omitEmptyStrings();

  public record CiteArgs(String query) {}

  public record BashArgs(String command) {}

  static final class OutputLog {
    private final StringBuilder markdown = new StringBuilder();

    void println(String line) {
      System.out.println(line);
      markdown.append(line).append(System.lineSeparator());
    }

    void printf(String format, Object... args) {
      String text = String.format(format, args);
      System.out.print(text);
      markdown.append(text);
    }

    void save() throws IOException {
      Files.writeString(OUTPUT_MD, markdown.toString(), StandardCharsets.UTF_8);
    }
  }

  // 長い規程本文(動かない・大きい=キャッシュ向き)を、条項ごとに検索できる形へ分割する
  private static final class PolicySections {
    private static final ImmutableList<String> VALUES =
        ImmutableList.copyOf(SECTION_SPLITTER.splitToList(POLICY.strip()));
  }

  // カスタムツールの引数を取得する
  // Tool Runnerを使わない手動ループなので、tool_useのraw inputをCiteArgsへ変換する
  private static CiteArgs parseCiteArgs(ToolUseBlock toolUse) {
    return toolUse._input().convert(CiteArgs.class);
  }

  // bash(公式ツール)の実行:モデルが出したコマンドを手元のシェルで走らせる。
  // デモなのでそのまま実行するが、本番は許可リスト等で境界を引く
  private static String runBash(String command) {
    try {
      Process p = new ProcessBuilder("bash", "-lc", command).redirectErrorStream(true).start();
      String stdout = new String(p.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
      int code = p.waitFor();
      String log =
          Files.exists(QA_LOG_MD) ? Files.readString(QA_LOG_MD, StandardCharsets.UTF_8) : "";
      return """
          exit=%d

          stdout:
          %s

          %s:
          %s
          """
          .formatted(code, stdout, QA_LOG_MD, log);
    } catch (Exception e) {
      return "exit=1\n" + e.getMessage();
    }
  }

  // citeの実処理:検索語を最も多く含む条項を返す。結果は決定的
  static String cite(String query) {
    if (StringUtils.isBlank(query)) {
      return PolicySections.VALUES.getFirst();
    }

    List<String> keywords = QUERY_SPLITTER.splitToList(query.toLowerCase(Locale.ROOT));

    String bestSection = PolicySections.VALUES.getFirst();
    int bestScore = -1;

    for (String section : PolicySections.VALUES) {
      String normalizedSection = section.toLowerCase(Locale.ROOT);
      int score = 0;

      for (String keyword : keywords) {
        if (normalizedSection.contains(keyword)) {
          score++;
        }
      }

      if (score > bestScore) {
        bestScore = score;
        bestSection = section;
      }
    }

    return bestSection;
  }

  public static void main(String[] args) throws Exception {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();
    OutputLog output = new OutputLog();

    // citeツールのquery引数に渡す入力スキーマ
    Map<String, Object> querySchema =
        Map.of(
            "type", "string",
            "description", "探すキーワード(スペース区切り)。例: 開封 化粧品");

    // カスタムツール定義
    Tool citeTool =
        Tool.builder()
            .name(CITE_TOOL_NAME)
            .description(
                """
            返品ポリシー本文から、質問の根拠になる条項を検索して返す。
            回答の根拠を示すときは必ず使う。
            queryはスペース区切りのキーワード。""")
            .inputSchema(
                Tool.InputSchema.builder()
                    .properties(
                        Tool.InputSchema.Properties.builder()
                            .putAdditionalProperty("query", JsonValue.from(querySchema))
                            .build())
                    .required(ImmutableList.of("query"))
                    .build())
            .build();

    // system:小さな指示ブロック+大きな規程本文
    // 規程側にcacheControlを置き、動かない大きなコンテキストをキャッシュする
    TextBlockParam instruction =
        TextBlockParam.builder()
            .text(
                "あなたは返品規程のQAアシスタントです。"
                    + "回答の根拠は必ずciteツールで規程本文から引用してください。"
                    + "回答が確定したら、bash ツールで要点1行を "
                    + QA_LOG_MD
                    + " に追記して記録してください。")
            .build();

    TextBlockParam policyDocument =
        TextBlockParam.builder()
            .text("# 返品ポリシー\n" + POLICY)
            .cacheControl(CacheControlEphemeral.builder().build())
            .build();

    // カスタムツール cite + 既存ツール bash(定義は既製・実行は手元)を両方渡す
    MessageCreateParams.Builder base =
        MessageCreateParams.builder()
            .model(MODEL)
            .maxTokens(MAX_TOKENS)
            .systemOfTextBlockParams(ImmutableList.of(instruction, policyDocument))
            .addTool(citeTool)
            .addTool(ToolBash20250124.builder().build());

    output.println("=== ドキュメントQA(キャッシュ+自作cite+bashログ保存)===");

    String question =
        args.length == 0 ? "開封済みの化粧品は返品できますか? 根拠の条項も教えてください。" : String.join(" ", args);

    output.println("質問: " + question);
    output.println("");

    List<MessageParam> messages = new ArrayList<>();
    messages.add(MessageParam.builder().role(MessageParam.Role.USER).content(question).build());

    for (int turn = 1; turn <= MAX_TURNS; turn++) {
      Message response =
          client.messages().create(base.messages(ImmutableList.copyOf(messages)).build());

      // キャッシュの効きめをusageで確認する(初回=write、2回目以降=read)
      long cacheWrite = response.usage().cacheCreationInputTokens().orElse(0L);
      long cacheRead = response.usage().cacheReadInputTokens().orElse(0L);

      output.printf(
          "[turn %d usage] cache_write=%d / cache_read=%d / input=%d%n",
          turn, cacheWrite, cacheRead, response.usage().inputTokens());

      // tool_useを含むアシスタント応答を会話履歴へ戻す
      messages.add(response.toParam());

      // cite(自作)も bash(公式定義)も、実行はローカル。結果を tool_result で返す。
      List<ContentBlockParam> toolResults = new ArrayList<>();

      for (ContentBlock block : response.content()) {
        if (block.toolUse().isEmpty()) {
          continue;
        }

        ToolUseBlock toolUse = block.toolUse().get();
        output.printf("  [tool_use] name=%s input=%s%n", toolUse.name(), toolUse._input());

        String result;
        if (CITE_TOOL_NAME.equals(toolUse.name())) {
          CiteArgs citeArgs = parseCiteArgs(toolUse);
          result = cite(citeArgs.query());

          output.printf("  [cite] query=\"%s\" -> %s%n", citeArgs.query(), result);

        } else if (BASH_TOOL_NAME.equals(toolUse.name())) {
          BashArgs arg = toolUse._input().convert(BashArgs.class);
          result = runBash(arg.command());
          output.printf("  [bash] $ %s%n%s%n", arg.command(), result);

        } else {
          output.printf("  [unknown tool] %s%n", toolUse.name());
          continue;
        }
        toolResults.add(
            ContentBlockParam.ofToolResult(
                ToolResultBlockParam.builder().toolUseId(toolUse.id()).content(result).build()));
      }

      if (!toolResults.isEmpty()) {
        messages.add(
            MessageParam.builder()
                .role(MessageParam.Role.USER)
                .contentOfBlockParams(ImmutableList.copyOf(toolResults))
                .build());
        continue;
      }

      // ツール呼び出しが残っていなければ最終回答を出力する。
      response.content().stream()
          .flatMap(block -> block.text().stream())
          .map(textBlock -> textBlock.text())
          .forEach(text -> output.println("\n回答: " + text));
      output.save();
      return;
    }

    output.println("最大ターン数に達したため処理を終了しました。");
    output.save();
  }

  // (1) 長い規程本文(動かない・大きい=キャッシュ向き)。
  private static final String POLICY =
      """
      【§0 総則】この返品ポリシーは、当社オンラインストア(以下「当店」)が販売する商品の返品・交換・返金の条件と手続きを定めるものである。会員・非会員を問わず、当店で購入したすべての注文に適用する。用語の定義は各条の本文中に示す。
      【§1 対象】本ポリシーは当店で購入した商品の返品・交換・返金に適用する。ギフト券・電子書籍等のデジタルコンテンツ・受注生産の予約商品は本ポリシーの対象外とし、それぞれ別途定める個別規約に従うものとする。
      【§2 返品期限】返品は商品到着後14日以内に申請した場合に限り受け付ける。15日以降の申請は理由を問わず受け付けない。到着日は配送業者の配達完了記録を基準とし、不在による持ち戻り・再配達の期間も期限の計算に含める。
      【§3 未開封品の返品】未開封かつ未使用の商品は、§2の期限内であれば理由を問わず返品できる。ただし返送途中の破損を避けるため、購入時の外装・緩衝材を保った状態で返送すること。外装の汚損が著しい場合は減額の対象となる。
      【§4 開封済み品の原則】開封または使用済みの商品は、原則として返品できない。ここでいう「開封」とは、商品を保護する封・シュリンク・シール・ブリスターのいずれかを破った状態を指す。動作確認のための最小限の開封もこれに含む。
      【§5 化粧品・衛生用品の特則】化粧品・スキンケア・下着・水着・マスク・カミソリ等の衛生に関わる商品は、衛生上の理由から、開封した時点で返品・交換ともに一切不可とする。少量の試用やパッチテストを行った場合も使用済みとみなす。未開封の場合は§3に従う。
      【§6 不良品・誤配送の例外】初期不良・輸送中の破損・数量違い・誤配送の場合は、開封済みであっても返品・交換・全額返金の対象とする。到着後30日以内に、不良箇所が分かる写真を添えてサポートへ申請すること。この場合の返送料は当店が負担する。
      【§7 返金方法】返金は原則として購入時と同一の決済手段へ行う。クレジットカードは各社の締め日により、返金の反映が翌月以降となる場合がある。コンビニ払い・銀行振込で購入した場合は、返金先の口座情報の登録が必要となる。
      【§8 返送料の負担】サイズ違い・イメージ相違・注文間違い等の自己都合による返送料は顧客の負担とする。§6に該当する不良・誤配送の返送料は当店が負担する。着払い・代金引換での返送は受け取れないため、必ず元払いで返送すること。
      【§9 交換】交換は同一商品の在庫がある場合に限り受け付ける。在庫が無い場合は返金対応とする。色違い・サイズ違いへの交換は一注文につき一度限りとし、二度目以降は返品・再購入の扱いとなる。
      【§10 セール品】セール・アウトレット・在庫処分として販売した商品は、不良品を除き返品・交換ともに不可とする。購入前に商品説明・サイズ表・注意書きを必ず確認すること。
      【§11 ギフト】ギフトとして購入された商品の返金は、購入者本人の決済手段に対してのみ行う。受取人へ直接の返金は行わない。ギフトラッピング・のし等の付帯サービス料金は返金の対象外とする。
      【§12 大型商品】家具・大型家電等の大型商品は、設置後の返品を受け付けない。返品は開梱前かつ§2の期限内に限る。設置・据付工事を伴う商品については、工事に着手した時点で返品不可となる。
      【§13 個人情報】返品手続きで取得した氏名・住所・連絡先・口座情報は、返品処理と法令遵守の目的にのみ利用し、本人の同意なく第三者へ提供しない。保管期間は関係法令に従う。
      【§14 添付品】付属品・タグ・保証書・説明書・専用箱を含む一式が揃っていない場合、返品を断る、または不足分に相当する額を返金額から差し引くことがある。
      【§15 申請方法】返品はマイページの注文履歴から申請し、発行される返品番号を箱の見やすい位置に明記して返送する。返品番号の記載がない返送・事前申請のない返送は受付できない。
      【§16 到着確認】返送された商品の状態を確認したうえで、到着後5営業日以内に返金または交換の可否を通知する。年末年始・大型連休等の繁忙期は、通知が前後することがある。
      【§17 不正利用】着用・使用の痕跡があるにもかかわらず未開封・未使用と偽って申請した場合、当該申請を無効とし、以後の取引をお断りする。加えて、確認・返送に要した費用を請求することがある。
      【§18 越境】海外への発送分については、返品時の送料および関税・輸入諸税は顧客の負担とし、返金は商品代金のみとする。為替レートの変動による差益・差損は調整しない。
      【§19 改定】本ポリシーは予告なく改定することがある。適用されるのは各注文の購入時点の版であり、最新版は当店のポリシーページに掲示する。重要な変更はメール等で告知する。
      【§20 問い合わせ】本ポリシーに関する不明点は、サポート窓口(平日10時〜17時)へ問い合わせること。円滑な対応のため、返品番号と注文番号を事前に用意すること。
      【§21 予約・受注生産】予約商品・受注生産品・オーダーメイド品は、製作に着手した後のキャンセル・返品を受け付けない。着手前であれば§2に準じて取り消せる。
      【§22 定期便】定期購入は、次回発送予定日の5日前までにマイページから解約すれば次回分を停止できる。既に発送された分については通常商品と同様に§2に従う。
      【§23 食品・生鮮】食品・生鮮食品・冷蔵冷凍品は、品質保持の観点から、不良品を除き返品・交換を受け付けない。到着後は速やかに状態を確認すること。
      【§24 医薬品類】医薬品・医薬部外品は、法令上の理由により、開封の有無を問わず返品・交換ができない。
      【§25 デジタル商品】ダウンロード商品・ソフトウェアライセンス・電子クーポンは、購入または引き換えが完了した後の返金を行わない。
      【§26 ポイント】返品が成立した場合、その購入で付与したポイントは取り消す。既に利用済みのポイントについては、相当額を返金額から差し引く、または別途請求することがある。
      【§27 クーポン】クーポンを適用した購入の返金額は、値引き後の実支払額を上限とする。使用済みクーポンの再発行・返却は行わない。
      【§28 部分返品】複数商品をまとめて購入し一部のみ返品する場合、まとめ買い割引・送料無料等の適用条件を満たさなくなったときは、割引分・送料相当額を返金額から調整する。
      【§29 メーカー保証】メーカー保証のある商品は、保証期間内の不具合はメーカー窓口の対応を優先する。当店への返品と保証申請を重複して行うことはできない。
      【§30 リコール】安全上のリコール対象となった商品は、本ポリシーの返品期限にかかわらず、告知に従って回収・返金する。
      【§31 転売目的】転売・再販売の目的と判断される大量購入分については、返品を制限し、または受け付けないことがある。
      【§32 名入れ・加工】名入れ・刻印・裾上げ・カット等のカスタマイズ加工を施した商品は、不良を除き返品・交換できない。
      【§33 付録・特典】雑誌の付録・購入特典・ノベルティのみの返品や、特典の欠品を理由とする本体商品の返品は受け付けない。
      【§34 サイズ交換の送料】サイズ交換の初回返送料を当店が負担するキャンペーンを実施する場合がある。適用条件は各商品ページの記載に従う。
      【§35 長期不在】長期不在・受取拒否により当店へ返送された注文は、再送時の送料を顧客負担とする。一定期間内に連絡がない場合はキャンセル扱いとする。
      【§36 領収書】返品・返金の完了後に領収書が必要な場合は、返金後の実支払額に基づいて再発行する。旧領収書は破棄すること。
      【§37 セット・福袋】セット商品・福袋は原則として個別商品単位の返品を受け付けず、セット全体を単位として返品するものとする。
      【§38 価格保証】予約時より販売価格が下がった場合の差額返金は、対象商品と対象期間を限って実施することがある。
      【§39 決済手数料】代金引換手数料・振込手数料等の決済手数料は、当店都合による返品の場合を除き、返金の対象としない。
      【§40 準拠法】本ポリシーの解釈は日本法に準拠する。返品・返金に関する紛争は、当店所在地を管轄する裁判所を第一審の専属的合意管轄裁判所とする。
      【§41 補償上限】返金の総額は、当該注文の商品代金と、当店に責がある場合の必要実費を上限とし、それを超える間接損害・逸失利益は補償しない。
      【§42 返品不可の明示】商品ページに「返品不可」と明示された商品は、不良品・誤配送を除き、本ポリシーの一般条項に優先して返品を受け付けない。
      【§43 第三者出品】マーケットプレイス等で第三者が販売・発送した商品は本ポリシーの対象外であり、各出品者の返品規定に従うものとする。
      【§44 分割配送】分割配送された注文は、各配送分の到着日をそれぞれの基準として§2の返品期限を個別に判定する。
      【§45 返金の相殺】顧客に未払い金がある場合、当店は返金額をその未払い金に充当(相殺)したうえで、残額を返金することができる。
      【§46 通知手段】返品に関する通知は、注文時に登録されたメールアドレス宛に行う。登録情報の誤りや受信拒否により到達しない場合の不利益は顧客が負う。
      【§47 代理申請】本人以外による返品申請は、正当な代理権が確認できる場合に限り受け付ける。確認できない場合は本人からの再申請を求める。
      【§48 検品基準】返送品の使用・汚損・不足の判断は、当店の定める検品基準に基づき、写真等の記録を残したうえで客観的に行う。
      【§49 再販・廃棄】返品された商品のうち再販売できないものは、関係法令に従い適切に廃棄またはリサイクルし、顧客情報は安全に消去する。
      【§50 附則】本ポリシーは掲示日から適用する。個別の販売ページに本ポリシーと異なる条件が定められている場合は、当該個別条件を優先して適用する。
      【§51 責任範囲】当店の返品対応に関する責任は、本ポリシーに明記した範囲に限られ、これを超える保証は明示・黙示を問わず行わない。
      【§52 不可抗力】天災・輸送障害・システム障害・法令改正等の不可抗力により手続きが遅延・不能となった場合、当店はその遅延・不能について責任を負わない。
      【§53 言語】本ポリシーは日本語を正文とし、参考として提供する翻訳版との間に相違があるときは、日本語版の記載を優先して適用する。
      """;
}

各ターンで usage を見ると、キャッシュの効きめが分かります。実行するとこうなります(出力例)。

=== ドキュメントQA(キャッシュ+自作cite+bashログ保存)===
質問: 開封済みの化粧品は返品できますか? 根拠の条項も教えてください。

[turn 1 usage] cache_write=4908 / cache_read=0 / input=103
  [tool_use] name=cite input={query=化粧品 開封 衛生}
  [cite] query="化粧品 開封 衛生" -> 【§5 化粧品・衛生用品の特則】化粧品・スキンケア・下着・水着・マスク・カミソリ等の衛生に関わる商品は、衛生上の理由から、開封した時点で返品・交換ともに一切不可とする。少量の試用やパッチテストを行った場合も使用済みとみなす。未開封の場合は§3に従う。
[turn 2 usage] cache_write=0 / cache_read=4908 / input=317
  [tool_use] name=bash input={command=echo "$(date '+%Y-%m-%d %H:%M:%S') Q:開封済み化粧品の返品可否 A:不可(§5)。ただし不良品・誤配送は§6の例外対象。" >> qa_log.md
cat qa_log.md}
  [bash] $ echo "$(date '+%Y-%m-%d %H:%M:%S') Q:開封済み化粧品の返品可否 A:不可(§5)。ただし不良品・誤配送は§6の例外対象。" >> qa_log.md

[turn 3 usage] cache_write=0 / cache_read=4908 / input=939

回答: qa_log.mdに要点を記録しました。

**まとめ**:開封済みの化粧品は【§5】により衛生上の理由から返品・交換ともに一切不可です。未開封であれば【§3】に従い14日以内の返品が可能、また初期不良・誤配送の場合は【§6】の例外により開封済みでも対応可能です。

注意点:

  • キャッシュが効くのは前置きが安定しているときだけ。規程を1文字でも書き換えると前置きのキャッシュは無効になり、書き込みを払い直します。日付やユーザー名を system の先頭に差し込まないこと(プレフィックスが毎回変わり、以降すべてがキャッシュされません)。
  • キャッシュには最小サイズがあります(モデルにより約2,000〜4,000トークン)。短い前置きはマーカーを付けても静かにキャッシュされません。今回の規程は4千トークン超なので効きます。

単一コンテキストから long-running harness へ

ここまでは「1つのコンテキストウィンドウをどう小さくきれいに保つか」でした。 最近の前線は、その先に移っています。コンテキストウィンドウに収まらない長い仕事を、複数セッションにまたいで続ける“harness(ハーネス)”の設計です。

長いタスクでは、単一コンテキストでは出なかった3つの症状が現れます。

  • 健忘(amnesia): 新しいセッションは、前のセッションが何をしたかを知らない。
  • コンテキスト腐敗(context rot): コンテキストが長いほど要点を見失い、初期の情報がノイズに埋もれる。
  • コンテキスト不安(context anxiety): 上限が近いと感じたモデルが焦って切り上げ、半完成を「完成」と言い張る。

対策は、メモリをコンテキストウィンドウの外に持ち、セッションをまたいで引き継ぐことです。この引き継ぎを支える仕組みがハーネスです。

モデル本体を賢くするのではなく、モデルの外で「どこに保存し、次のセッションへどう渡し、いつ新しく開き直すか」を担当します。

中身のLLMが1回ごとに忘れても、ハーネスが記憶と段取りを保ち続けるので、長いタスクを最後まで機能させます。

まとめ

コンテキストエンジニアリングは、コンテキストウィンドウの中身を設計する規律です。

テクニックは検索・圧縮・選別/順序・キャッシュ・構造化の5つです。

elatt では、この「コンテキストウィンドウを小さくきれいに保って回す」を実運用で日々やっています。

設計や事業の相談などお気軽にお問い合わせからご連絡ください。

コンテキストエンジニアリングとは何か - コンテキストウィンドウに「何を入れないか」を設計する - 合同会社ふんどし