ContextManager: Abschneide-Strategie für das Kontextfenster
Verantwortung
ContextManager ist Clines Abschneider und Optimierer für den Fall, dass «das Kontext (context) Fenster kurz vor dem Überlaufen steht». Wenn die Gesamt-Token einer LLM-Anfrage sich dem Modelllimit nähert, rufen der Haupt-Task oder SubagentRunner seine Methoden auf, um einen älteren Abschnitt der Konversationsnachrichten nach Strategie zu löschen, dabei aber die letzten paar Runden, alle Werkzeugdefinitionen und die als «wichtig» markierten früheren Nachrichten zu behalten. Er speichert keine rohen Nachrichten — die bleiben im apiConversationHistory-Array — sondern pflegt einen Bereich conversationHistoryDeletedRange: [start, end], der dem Aufrufer mitteilt, «dieser Indexbereich ist ungültig», zusätzlich zu einer In-memory-Map contextHistoryUpdates für Überschreibungen von Nachrichteninhalten.
Seine Position in der Cline-Architektur liegt zwischen «Nachrichtenverlauf» und dem «an die API gesendeten Request-Body». Vor jeder Anfrage nutzt der Haupt-Task getTruncatedMessages (getTruncatedMessages:344), um das History-Array auf die tatsächlich zu sendende Version zuzuschneiden; nach der Anfrage, wenn die Token-Zahl kritisch wird, entscheidet getNewContextMessagesAndMetadata (getNewContextMessagesAndMetadata:227), ob weiter gekürzt wird. Also ist ContextManager ein kleinines Modul mit rein funktionalem Charakter und internem Zustand, das von der Haupt-Agent-Loop aufgerufen wird, um zu entscheiden, «was die nächste Runde gesendet wird».
Entwurfsmotivation
- Nur Bereich löschen, Array nicht verändern: Pflegen von
[start, end]statt wirklich das Array zu splicen, macht Rollback-Kosten für Checkpoints O(1) (getNextTruncationRange:299). - Immer die ersten zwei behalten: Index 0 und 1 sind das «zentrale user/assistant-Paar», nach dem Löschen muss die nächste Nachricht weiterhin eine assistant-Nachricht sein, um die user-assistant-Wechselstruktur zu erhalten (
preserve pairing:331). - Vier Stufen Behaltens-Strategie:
none/lastTwo/half/quarter, entsprechend wie viel gelöscht wird. Je mehr Token überlaufen, desto weniger wird behalten (keep strategies:309). - Dynamische Schwelle: Wenn totalTokens / 2 bereits größer als maxAllowedSize ist, geht es direkt über
quarter, was den Szenario abdeckt, von einem 200k-Modell auf ein 64k-Modell zu wechseln (quarter fallback:252). - file read-Optierung vor dem Abschneiden: Erst die großen tool_result-Blöcke des DateileSENS durch «Markieren + späteres bedarfsgerechtes Wiederherstellen» komprimieren; wenn mehr als 30 % eingespart wird, entfällt das Abschneiden (
attemptFileReadOptimizationCore:626). - Zeitstempel-Sortierung für Rollback:
contextHistoryUpdateswird als Array aus[timestamp, updateType, update]gespeichert; von rechts nach links nach dem Cutoff gesucht, kann man Änderungen nach einem Zeitpunkt präzise rückgängig machen (truncate by timestamp:573). - Neuer auto-condense-Pfad: Next-Gen-Modelle nutzen auto-condende mit 0,75-Schwelle, alte Modelle bleiben bei
maxAllowedSize(thresholdPercentage:163).
Schlüsseldateien
ContextManager class:44— hält diecontextHistoryUpdates-Map, keine Singleton.initializeContextHistory:100— liest beim Start die Historycontext_history_updatesvon der Festplatte.saveContextHistory:131— persistiert die In-memory-Updates zurück ins Task-Verzeichnis.shouldCompactContextWindow:150— boolesche Entscheidung für den Haupt-Task, vergleicht die Token-Zahl der letzten Anfrage mit der Schwelle.getNewContextMessagesAndMetadata:227— Haupteinstieg, entscheidet über das Abschneiden und gibt die abgeschnittene History und den neuen deletedRange zurück.getNextTruncationRange:299— berechnet bei gegebener keep-Strategie den zu löschenden Bereich[start, end].getTruncatedMessages:344— öffentliche Schnittstelle, wendet history updates an und gibt das endgültige, zu sendende Nachrichten-Array zurück.ensureToolResultsFollowToolUse:375— korrigiert die Paarung von tool_use und tool_result, die nach dem Löschen eines Bereichs verschoben sein können.truncateContextHistory:552— löscht alle Updates nach einem gegebenen Zeitstempel, für Checkpoint-Rollbacks.applyContextOptimizations:606— findet und speichert file read-Optimierungspunkte.attemptFileReadOptimizationInMemory:696— In-memory-Version für SubagentRunner, gibt die optimierte Konversation direkt zurück.getContextWindowInfo:10— berechnetcontextWindowundmaxAllowedSize, verschiedene Modelle behalten unterschiedliche Puffer.
Datenfluss
Im Haupt-Task-Fluss ist getNewContextMessagesAndMetadata der entscheidende Punkt. Beim Eintritt wird zuerst die Token-Zahl der letzten API-Anfrage gelesen, mit maxAllowedSize verglichen, und nur bei Überschreitung gehandelt:
// 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) {
// Beim Modellwechsel reicht half möglicherweise nicht, deshalb bei token/2 > maxAllowedSize direkt über 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,
)Dieses Stück befindet sich nahe main truncation logic:240. Nachdem der Abschneidebereich berechnet wurde, wendet getAndAlterTruncatedMessages auf die History ab deletedRange[1] + 1 die Funktion applyContextHistoryUpdates an (applyContextHistoryUpdates:362), um die Überschreibungsänderungen wie file read-Optimierungen / Standard-Abschneidehinweise anzuwenden. ensureToolResultsFollowToolUse führt dann eine weitere Paarungskorrektur durch, um sicherzustellen, dass jedem tool_use sofort sein tool_result folgt.
Grenzen und Fehler
- Nachrichten <= 1 werden nicht behandelt:
getAndAlterTruncatedMessagesgibt direkt das Original-Array zurück (early return:358). - Gleicher deletedRange wird nicht neu berechnet:
compactConversationForContextWindowerkennt, wenn der neue Bereich dem alten vollständig entspricht, und beendet frühzeitig (range equal early return:929), um unnötiges Umschreiben zu vermeiden. - Bereichsende muss assistant sein: Nach dem Löschen eines Abschnitts muss die nächste Nachricht eine assistant-Nachricht sein, sonst wird
rangeEndIndex -= 1um eins zurückgesetzt (assistant pairing:333). - file read-Optimierung mit < 30 % Ersparnis muss trotzdem abschneiden: Bei
percentSaved < 0.3wirdneedToTruncate: truegesetzt (30 percent threshold:654). - Fehlschlag von saveContextHistory wird nur geloggt:
try/catchumschließtfs.writeFile, wirft nicht an den Hauptfluss zurück (save error handling:142). - Verschiedene Modelle behalten unterschiedliche Puffer:
contextWindow <= 64knutzt die 80 %-Formel, um zu verhindern, dass der Puffer bei kleinen Modellen zu knapp wird (deepseek buffer:31). - Checkpoint-Rollback über Zeitstempel:
truncateContextHistoryändert nicht den deletedRange, sondern löscht nur Updates nach dem angegebenen Zeitstempel, sodass die History zu einem beliebigen Checkpoint zurückgesetzt werden kann (truncateContextHistory:552).
Zusammenfassung
ContextManager ist die Stütze, die Cline vor dem Absturz in langen Konversationen bewahrt. Um zu sehen, wie es vom SubagentRunner genutzt wird, lies subagent; um zu sehen, wie der Haupt-Task es innerhalb der Rekursion aufruft, um die History der nächsten Runde zu entscheiden, lies agent-loop/task-class; um mehr über das Task-Verzeichnis und Persistierung zu erfahren, lies storage/state-manager.
Siehe offizielle Dokumentation: Cline-Dokumentation · README