Skip to content

ContextManager:コンテキストウィンドウの切り詰め戦略

源码版本v4.0.10

役割

ContextManager は Cline が「コンテキストウィンドウ (context window) が限界に達しそうな時」の切り詰めと最適化を担う。1 ラウンドの LLM リクエストの total token がモデル上限に近づいた時、主 Task または SubagentRunner がそのメソッドを呼び、古い対話メッセージを戦略に従って一部分削除しつつ、最新の数ラウンドとすべてのツール定義、そして「重要」とマークされたより古いメッセージを保持する。このモジュールは生のメッセージを保存しない——それは依然として apiConversationHistory 配列にある——代わりに conversationHistoryDeletedRange: [start, end] 区間を管理し、呼び出し元に「このインデックス範囲は無効になった」ことを伝える。加えてメッセージ内容の上書き修正を記録する contextHistoryUpdates の in-memory map を保持する。

Cline アーキテクチャ上の位置は「メッセージ履歴」と「実際に API に送るリクエスト本体」の中間にある。各ラウンドのリクエスト前、主 Task は getTruncatedMessages (getTruncatedMessages:344) で history 配列を実際の送信版に切り出す。リクエスト後に token 数が臨界に達した場合、getNewContextMessagesAndMetadata (getNewContextMessagesAndMetadata:227) でさらに削るかを判断する。つまり ContextManager は純関数 + 内部状態の小さなモジュールで、主 agent loop が「次ラウンドで何を送るか」を決めるために呼ぶ。

設計動機

  • 区間だけ削り元配列は変更しない:配列を splice する代わりに [start, end] を管理することで、checkpoint ロールバックのコストを O(1) にする (getNextTruncationRange:299)。
  • 前 2 条を必ず保持:index 0 と 1 は「中核 user/assistant pair」。削除後も次のメッセージは assistant メッセージでなければならず、user-assistant 交互構造を保つ (preserve pairing:331)。
  • 4 段階の保持戦略:none / lastTwo / half / quarter。どれだけ削るかに対応する。token 超過が大きいほど保持は減る (keep strategies:309)。
  • 動的しきい値:totalTokens / 2 が既に maxAllowedSize を超える場合は直接 quarter に切り替える。200k モデルから 64k モデルへの切り替えシナリオをカバーする (quarter fallback:252)。
  • file read 最適化を切り詰めより優先:ファイル読み取りの大きな tool_result を「マーキング + 後で必要に応じて復元」方式で圧縮する。30% 以上節約できれば切り詰めを回避できる (attemptFileReadOptimizationCore:626)。
  • timestamp ソートでロールバックをサポート:contextHistoryUpdates[timestamp, updateType, update] 配列で格納し、時間順で右から左へ cutoff を探す。ある時点以降の修正を正確に取り消せる (truncate by timestamp:573)。
  • auto-condense の新パス:next-gen モデルは 0.75 しきい値の auto-condense に進み、旧モデルは依然として maxAllowedSize で判定する (thresholdPercentage:163)。

主要ファイル

データフロー

主 Task フローでは getNewContextMessagesAndMetadata が意思決定の中心である。まず前回 API リクエストの token 数を読み、maxAllowedSize と比較し、超過時のみ動く:

typescript
// apps/vscode/src/core/context/context-management/ContextManager.ts
if (previousApiReqIndex >= 0) {
  const previousRequestText = clineMessages[previousApiReqIndex]?.text
  if (previousRequestText) {
    const timestamp = clineMessages[previousApiReqIndex].ts
    const { tokensIn, tokensOut, cacheWrites, cacheReads }: ClineApiReqInfo = JSON.parse(previousRequestText)
    const totalTokens = (tokensIn || 0) + (tokensOut || 0) + (cacheWrites || 0) + (cacheReads || 0)
    const { maxAllowedSize } = getContextWindowInfo(api)

    if (totalTokens >= maxAllowedSize) {
      // 切り模型の時 half では足りない可能性があるため、token/2 が maxAllowedSize を超える場合は quarter に
      const keep = totalTokens / 2 > maxAllowedSize ? "quarter" : "half"

      let { anyContextUpdates, needToTruncate } = this.attemptFileReadOptimizationCore(
        apiConversationHistory,
        conversationHistoryDeletedRange,
        timestamp,
      )

      if (needToTruncate) {
        anyContextUpdates = this.applyStandardContextTruncationNoticeChange(timestamp) || anyContextUpdates
        conversationHistoryDeletedRange = this.getNextTruncationRange(
          apiConversationHistory,
          conversationHistoryDeletedRange,
          keep,
        )
        updatedConversationHistoryDeletedRange = true
      }

      if (anyContextUpdates) {
        await this.saveContextHistory(taskDirectory)
      }
    }
  }
}

const truncatedConversationHistory = this.getAndAlterTruncatedMessages(
  apiConversationHistory,
  conversationHistoryDeletedRange,
)

この処理は main truncation logic:240 付近にある。切り詰め区間算出後、getAndAlterTruncatedMessagesdeletedRange[1] + 1 以降の history を applyContextHistoryUpdates に流し (applyContextHistoryUpdates:362)、file read 最適化 / 標準切り詰め通知などの上書き修正を適用する。さらに ensureToolResultsFollowToolUse がペア補正を行い、各 tool_use の直後に tool_result が続くことを保証する。

境界と失敗

  • メッセージが 1 条以下は非処理:getAndAlterTruncatedMessages は元の配列をそのまま返す (early return:358)。
  • deletedRange が同じ時は再計算しない:compactConversationForContextWindow は new range と old range が完全一致すれば早期リターン (range equal early return:929)。無駄な書き換えを避ける。
  • range 終端は assistant でなければならない:1 段削除後、次のメッセージが assistant でなければ rangeEndIndex -= 1 で 1 つ戻す (assistant pairing:333)。
  • file read 最適化が 30% 未満でも切り詰め:percentSaved < 0.3 なら needToTruncate: true (30 percent threshold:654)。
  • saveContextHistory のディスク書き込み失敗は log のみ:fs.writeFile を try/catch で囲み、主フローに例外を伝播させない (save error handling:142)。
  • モデルごとに buffer を変える:contextWindow <= 64k は 80% 公式を使い、小モデルの buffer が薄くなりすぎるのを回避 (deepseek buffer:31)。
  • checkpoint ロールバックはタイムスタンプ基準:truncateContextHistory は deletedRange を変更せず、指定 timestamp 以降の update のみ削除する。履歴を任意 checkpoint まで戻せる (truncateContextHistory:552)。

まとめ

ContextManager は Cline が長会話でも崩れない柱である。SubagentRunner がどう使うかは subagent、主 Task が再帰の中でどう呼び出して次ラウンドの history を決めるかは agent-loop/task-class、task ディレクトリと永続化周りは storage/state-manager を参照のこと。

公式資料: Cline 文档 · README