← 返回
AI技术

2026 年 Claude Code 会话压缩:上下文摘要如何工作以及你的代理会遗忘什么

✍️ zhirenhun 📅 2026/9/20 👁 14 阅读 ⏱ 38 分钟
2026 年 Claude Code 会话压缩:上下文摘要如何工作以及你的代理会遗忘什么

Claude Code 会话压缩(2026):上下文摘要如何运作以及您的代理会遗忘什么

本文在人工监督和审查下,由 AI 辅助撰写。

大多数 Claude Code 会话失败源于一个误解:开发者将 200K 上下文窗口视为无限存储,而实际上它是一个带有激进摘要的滚动缓冲区。代理在对话中途达到限制,将其历史压缩为有损摘要,并在关键上下文缺失的情况下继续执行。这种失败模式虽然隐蔽但代价高昂:您的代理会生成语法正确的代码,但该代码违反了它在 50 条消息前学到的约束,而在压缩过程中这些约束被遗忘了。

当 Claude Code 达到大约 160K tokens(即 200K 窗口的 80%)时,运行时会自动触发上下文摘要。系统会保留最近的交互和系统提示,将中间对话浓缩为散文摘要,并丢弃原始消息。此过程悄然进行,不会出现错误。代理继续作出回应,但会话早期的详细推理链、被拒绝的方法和发现的边缘情况都会消失。

正确的做法是将关键上下文锚定在对话缓冲区之外。将 Claude Code 会话视为仅追加日志的开发者在压缩过程中会丢失状态。那些将约束、决策和未完成任务外部化为持久工件的工程师能够在压缩边界之间保持连续性。这种差异在生产环境中表现出来:一种模式会导致代理在长时间对话后漂移,而另一种模式则能够在无限交互中保持连贯性。

关键要点

  • 将约束外部化为持久化工件(决策日志、约束清单)可以在压缩边界之间保持连续性。
  • 手动压缩可以控制哪些内容得以保留;通过显式保留规则强制生成早期摘要,可防止无声的上下文丢失。
  • 令牌纪律(简洁的系统提示、基于工件的状态、修剪死分支)在大多数会话中会延迟或消除压缩。
  • 上下文摘要实际上是如何工作的

    上下文摘要以三阶段流水线运行:保留选择、摘要生成和缓冲区重建。当会话的令牌数超过压缩阈值时,运行时会将对话历史划分为三个段落。系统提示和配置指令占据第一段,保持不变。最近的 N 次交互(通常为 10‑15 轮)占据第三段,同样保持完整。中间段包含对话的主要内容,会被送入摘要模型。

    摘要模型会生成中间段落的精炼散文版本。该摘要旨在保留事实结果:哪些文件被修改、哪些错误被解决、哪些依赖被添加。摘要不会保留导致这些结果的推理过程。如果代理在找到正确方案之前尝试了四种方法,则压缩后会变成“使用 JWT 实现了身份验证”,而不会记录被拒绝的三种策略或它们失败的原因。

    重建的缓冲区包含:原始系统提示、生成的摘要、最近的交互。代理从这个状态继续,没有任何压缩发生的迹象。总令牌数显著下降,使对话得以继续。这里的含义是,代理对先前对话的“记忆”变成了高层次的叙述,而不是详细的逐字记录。

    摘要质量随对话结构而变化。线性对话中每次交互都基于前一次,能够产生连贯的摘要。分支对话中代理探索多条并行路径时,摘要会将这些分支折叠为单一叙述,导致方法之间的区别丢失。具有显式工件创建(决策文档、约束列表)的对话会生成引用这些工件的摘要,从而有效地将关键状态外部化。

    运行时用于摘要的模型比用于主对话的模型更小。这导致了语义压缩瓶颈:主模型所理解的细微差别可能在摘要模型的解释中无法保留。例如,将约束表述为“在非性能关键路径中优先使用函数式模式”可能被摘要为“使用函数式模式”,而例外条款则丢失。

    您的代理在压缩过程中会遗忘什么(以及什么会幸存)

    压缩首先会破坏详细的推理链。当代理在处理一个复杂的类型推理问题时,会解释方案 A 失败的原因,尝试方案 B,发现 TypeScript 的限制,最终使用方案 C 成功,摘要会将其压缩为“已解决类型推理问题”。探索过程消失。由于方案 B 在上下文中不再存在,代理在后续对话中无法引用“我们在方案 B 中遇到的问题”。

    被拒绝的方案会完全消失。如果代理提出使用 Redis 进行缓存,团队因运营限制而拒绝了该方案,随后对话转向了内存解决方案,压缩后的摘要只会说“已实现内存缓存”,没有任何关于 Redis 讨论的记录。会话稍后如果出现 Redis 能解决的新问题,代理可能会再次提出 Redis,却不知之前已经被拒绝。

    仅当在实现过程中发现的边缘情况导致了代码更改时,它们才会在压缩后幸存。例如,代理发现“空数组的 null 处理会失效”并添加了防护条款,摘要会变为“添加了 null 处理”。但如果代理发现该边缘情况,判断在当前架构下不可能发生,并在不修改逻辑的情况下仅在注释中记录了这一结论,摘要可能不会提及它。后来的重构可能会因为代理忘记了这一分析而重新引入该漏洞。

    约束的解释经常会丢失。当人类说“保持 bundle 大小低于 200KB”时,代理会询问“您是说 200KB 的 gzipped 还是未压缩版本?”,人类澄清为“gzipped”,这种澄清存在于中间段落的交流中。压缩后,摘要只会说“已优化 bundle 大小”,没有记录 gzip 要求。随后的工作中,代理会恢复到其默认假设(通常是未压缩)。

    显式工件在压缩过程中得以保留,因为代理将它们存储为独立的上下文项,而不是对话历史。通过 "创建一个名为 DECISIONS.md 的文件来记录我们的方法" 创建的决策文件在压缩过程中会持续存在。代理可以在后续交互中引用和更新此文件。这一区别至关重要:临时对话会在压缩中被删除,而命名工件会持续保留。

    系统提示指令在压缩过程中保持不变。如果您的系统提示包含 "始终使用严格的空值检查",该指令在压缩后仍然有效。作为对话上下文传递的配置("对于此会话,假设使用 PostgreSQL 15")是否能够保留取决于它在对话时间线中的位置。

    最近的交互会完整保留。最近的 10-15 轮对话将保持完整细节,形成一个滑动窗口的最近上下文。这意味着代理在即时后续工作中保持强连贯性,但会丢失长期上下文。100 条消息前实现的功能仅以摘要形式存在,而最近 10 条消息的工作仍然保持详细。

    读取压缩事件:调试被摘要掉的内容

    Claude Code 在进行摘要时会通过其流式 API 发出压缩事件。这些事件在对话元数据流中以类型 context.compaction 出现。事件负载包含原始 token 数量、压缩后 token 数量以及被摘要的消息数量。监控这些事件可以揭示压缩何时发生以及上下文折叠的程度。

    interface CompactionEvent {
      type: "context.compaction";
      timestamp: string;
      preCompactionTokens: number;
      postCompactionTokens: number;
      messagesSummarized: number;
      summaryTokens: number;
    }
    
    function monitorCompaction(eventStream: AsyncIterable<Event>) {
      for await (const event of eventStream) {
        if (event.type === "context.compaction") {
          console.warn(
            `Compaction at ${event.timestamp}: ${event.messagesSummarized} messages ` +
            `(${event.preCompactionTokens}${event.postCompactionTokens} tokens)`
          );
    
          const compressionRatio = 
            event.preCompactionTokens / event.postCompactionTokens;
    
          if (compressionRatio > 3.0) {
            console.error(
              "High compression ratio detected. Significant context loss likely."
            );
          }
        }
      }
    }
    

    压缩比表示摘要的激进程度。比率高于 3.0 意味着摘要的大小不到原始内容的三分之一,这表明信息损失较大。比率低于 2.0 表示摘要较为温和,更多细节得以保留。

    调试上下文丢失需要将生成的摘要与原始对话进行比较。API 不会直接暴露摘要文本,但开发者可以通过检查压缩后助手的即时响应来重建摘要。在压缩事件后的第一个响应中,常会出现诸如 “如前所述” 或 “基于我们之前的实现” 之类的短语,这些短语引用了摘要内容。这些引用揭示了代理认为自己记得的内容。

    interface ConversationMessage {
      role: "user" | "assistant";
      content: string;
      tokenCount: number;
      timestamp: string;
    }
    
    interface ContextSnapshot {
      messages: ConversationMessage[];
      totalTokens: number;
      compactionHistory: CompactionEvent[];
    }
    
    function analyzeContextAfterCompaction(
      snapshot: ContextSnapshot,
      lastCompaction: CompactionEvent
    ): { preserved: string[]; likely_lost: string[] } {
      const recentMessages = snapshot.messages
        .filter(m => new Date(m.timestamp) > new Date(lastCompaction.timestamp))
        .slice(-15);
    
      const preserved = recentMessages.map(m => m.content);
    
      const oldMessages = snapshot.messages
        .filter(m => new Date(m.timestamp) < new Date(lastCompaction.timestamp));
    
      const likely_lost = oldMessages
        .filter(m => m.content.includes("rejected") || m.content.includes("alternative"))
        .map(m => m.content);
    
      return { preserved, likely_lost };
    }
    

    此分析能够识别出包含被否定的方案或替代方案的消息,这些消息出现在被汇总的片段中。它们代表最高风险的上下文丢失,因为它们包含负面信息(即不应做的事情),而摘要很少会保留此类信息。

    生产系统应将压缩事件与其他运营指标一起记录。压缩频率的突然增加表明对话变得更长或代理响应变得更冗长。这两种模式都暗示存在架构问题:前者意味着会话设计鼓励长时间运行的对话而非有限任务,后者意味着提示工程产生了不必要的冗余。

    设计能够经受压缩的会话状态

    会话状态的设计决定了哪些内容能够在压缩中保留。仅存储在对话历史中的临时状态会在摘要过程中消失。存储在外部工件中的持久状态在压缩边界之外仍然可访问。这里的架构选择决定了代理的长期一致性。

    工件模式将关键状态外部化为代理在整个会话期间维护的命名文档。约束清单列出了所有需求、边界情况和架构决策。代理会在新约束出现时更新此清单。在压缩过程中,对话历史会被压缩,但约束清单作为独立的上下文项得以保留。

    清单应使用代理能够可靠解析的结构化格式。非结构化的散文会导致解释不一致。采用具有显式键的 JSON 或 YAML 结构,能够确保代理在多次更新后仍能正确提取需求。

    // CONSTRAINTS.md structure that survives compaction
    interface ConstraintManifest {
      architectural: {
        patterns: string[];
        forbidden: string[];
      };
      performance: {
        bundleSize: { max: string; measured: "gzipped" | "uncompressed" };
        responseTime: { target: string; percentile: number };
      };
      dependencies: {
        allowed: string[];
        prohibited: string[];
        rationale: Record<string, string>;
      };
      edgeCases: {
        description: string;
        handling: string;
        testCoverage: boolean;
      }[];
    }
    
    // Agent reads this at session start and after each update
    const manifest: ConstraintManifest = {
      architectural: {
        patterns: ["functional composition", "immutable state"],
        forbidden: ["class inheritance beyond two levels", "global mutable state"]
      },
      performance: {
        bundleSize: { max: "200KB", measured: "gzipped" },
        responseTime: { target: "200ms", percentile: 95 }
      },
      dependencies: {
        allowed: ["react", "typescript", "vite"],
        prohibited: ["lodash", "moment"],
        rationale: {
          lodash: "bundle size overhead, prefer native methods",
          moment: "deprecated, use native Temporal API"
        }
      },
      edgeCases: [
        {
          description: "null handling for empty arrays",
          handling: "explicit guard clause returning empty array",
          testCoverage: true
        }
      ]
    };
    

    决策日志记录了架构选择背后的理由。当代理评估多种方案时,决策日志会记录每个选项、评估标准以及最终选择。该日志在压缩过程中得以保留,使得代理在遇到类似情况时可以参考过去的决策。

    状态快照提供回滚功能。在关键的对话里程碑(功能完成、测试通过、生产就绪),代理会创建一个包含当前代码、配置和上下文摘要的状态快照。如果后续工作引入回归,团队可以恢复快照,并在保持所有上下文完整的情况下从该点继续工作。

    该模式同样适用于作为可执行文档的测试套件。测试通过断言将需求编码进去。当压缩导致对话上下文丢失,无法记住某个验证存在的原因时,如果行为发生变化,测试套件会以失败的断言形式保留该信息。这使得测试成为上下文管理的一等公民,而不仅仅是验证手段。

    令牌预算限制了制品大小的增长。无限制增长的约束清单最终会加剧压缩压力。应实施定期修剪:删除过时的约束,合并重复条目,归档已解决的边缘情况。清单应当反映当前状态,而非完整历史。

    手动压缩 vs 自动压缩:何时强制生成摘要

    自动压缩在达到 80% 容量(160K 令牌)时触发,开发者无法控制其时机或内容。运行时决定哪些内容得以保留。手动压缩提供了显式控制:开发者可以在战略性的对话边界强制进行摘要生成,并为摘要的生成指定保留规则。

    自动压缩旨在保持对话的连续性。系统优先考虑不间断地保持对话流畅。这对于上下文自然演变的探索性会话效果很好。但对于需要关键早期决策在整个会话过程中保持不变的会话,则会失效。

    手动压缩适用于具有明确阶段的会话:需求收集、架构设计、实现、测试。在每个阶段边界,代理会根据明确的保留指令对已完成阶段进行摘要。需求阶段的摘要保留所有约束。架构阶段的摘要保留所选模式和被替代的方案。实现阶段的摘要保留代码结构和边缘情况处理。

    API 提供一个 compact() 方法,可立即触发摘要生成。该方法接受一个保留策略对象,用于指定摘要中要保留的内容类别。

    interface RetentionPolicy {
      preserveConstraints: boolean;
      preserveRejectedApproaches: boolean;
      preserveEdgeCases: boolean;
      preserveDecisionRationale: boolean;
      customInstructions?: string;
    }
    
    async function manualCompaction(
      session: ClaudeSession,
      policy: RetentionPolicy
    ): Promise<CompactionResult> {
      const result = await session.compact({
        retentionPolicy: policy,
        validateSummary: true
      });
    
      if (!result.success) {
        throw new Error(`Compaction failed: ${result.error}`);
      }
    
      console.log(
        `Compacted ${result.messagesSummarized} messages. ` +
        `Tokens: ${result.preCompactionTokens}${result.postCompactionTokens}`
      );
    
      return result;
    }
    
    // Force compaction after architecture phase
    await manualCompaction(session, {
      preserveConstraints: true,
      preserveRejectedApproaches: true,
      preserveEdgeCases: true,
      preserveDecisionRationale: true,
      customInstructions: 
        "Retain the rationale for choosing event sourcing over CRUD, " +
        "including the PostgreSQL trigger limitation that ruled out CRUD."
    });
    

    保留策略中的自定义指令提供了细粒度的控制。这些指令直接发送给摘要模型,使开发者能够突出显示需要保留的特定对话片段。例如,在一个复杂的类型系统讨论中,可能会包含“保留解释为什么品牌类型解决了 ID 混淆问题”的指令,以确保该推理得以保存。

    手动压缩后的验证可以确认摘要包含预期内容。代理会执行一个测试查询,引用摘要片段中的关键概念。如果代理无法回答,说明摘要丢失了关键信息。这会触发两种情况之一:使用调整后的保留策略重试压缩,或者手动注入上下文以恢复丢失的信息。

    这里的权衡在于会话结构开销。自动压缩不需要会话管理:开发者只需开始对话并一直进行到完成。手动压缩则需要明确的阶段边界和保留决策。对于短会话(少于 100K tokens),开销超过了收益。而对于跨天进行的长期代理协作,手动压缩可以防止细微的上下文漂移,从而避免工作质量下降。

    混合策略结合了两种方法。在阶段内的探索性工作中使用自动压缩,在阶段边界强制执行手动压缩。这样既能在活跃工作期间保持连续性,又能确保阶段过渡时关键上下文得以保留。

    令牌纪律:防止压缩提前发生

    令牌纪律通过减少对话冗余来推迟或消除压缩。大多数会话触发压缩并不是因为任务需要 200K tokens,而是因为冗长的交流和重复的解释不必要地消耗了 tokens。严格的沟通协议能够让会话远低于压缩阈值。

    系统提示驱动代理的冗余程度。提示中说“请详细解释你的推理”会产生比“提供简洁的实现”更长的响应。这种差异在数十次交流中会累积。优化系统提示以实现简洁的团队可以在不丢失信息的情况下实现 40%-60% 的 token 减少。

    工件模式本身可以降低令牌消耗。将状态外部化到持久化文档意味着代理引用文档而不是重复信息。没有工件的对话可能需要在三次不同的交互中解释身份验证流程,作为不同问题的上下文。有了工件,代理只需在 AUTH_DESIGN.md 中一次性编写该流程,并在后续响应中引用该文档。

    修剪死亡的对话分支可以防止令牌浪费。当代理探索一种失败的方法时,开发者应明确标记该分支为死亡:“我们放弃 Redis 方法。不要在未来的响应中引用它。”这会向代理发出信号,使其停止继续携带该上下文,从而减少在维护无关状态上花费的令牌。

    代码示例应出现在工件中,而不是对话中。当代理提供代码示例时,人类应请求“将其放入 src/auth.ts 并我们将在文件上进行迭代”,而不是继续在对话中放置代码。文件仅在被显式读取时消耗令牌,而对话中的代码则会在会话剩余时间内消耗令牌。

    简洁的人类提示与简洁的代理响应同样重要。类似“按钮点击处理器需要验证表单、检查身份、调用 API、处理错误、更新 UI 状态并记录分析”这样的提示,在需求被外部化时,包含的信息与“根据 REQUIREMENTS.md 实现提交处理器”相同。第二种提示每次交互可节省 20+ 个令牌。

    响应长度限制可以防止冗长的偏离。系统提示可以规定“除非要求详细说明,否则保持响应在 500 个令牌以内。”代理学会提供聚焦的答案,并在需要时主动提供详细说明,而不是主动解释一切。这形成了一种拉取模型:只有在需要时,人类才会请求额外的上下文。

    令牌计数中间件会记录每次交换的令牌消耗,从而揭示冗长的模式。消耗 2000+ 令牌的交换很可能包含不必要的解释或重复的上下文。检查这些交换以发现优化机会:什么可以移动到工件?哪些解释是多余的?代理目前携带但已不再需要的上下文是什么?

    围绕工件、简洁提示和响应纪律设计的会话,通常能够在 80‑120K 令牌内完成复杂任务。这些会话永不会触发压缩。由于人类和代理能够高效而非冗长地交流,开发体验得到了提升。较短交互带来的速度提升往往超过上下文管理的收益。

    常见问题

    手动压缩相比自动压缩能否提升摘要质量?

    手动压缩结合显式保留策略会生成摘要,保留您指定的特定对话元素。自动压缩使用通用摘要方法,优化对话连续性,但可能丢失关键决策或被否定的方案。对于具有重要早期上下文的会话,在阶段边界进行手动压缩可以防止静默的上下文丢失。

    代理在压缩过程中是否能检测到上下文丢失?

    代理无法内在地检测上下文丢失。它会将压缩后的状态视为完整,并继续作出响应。开发者必须监控压缩事件,跟踪压缩比,并验证代理是否保持了早期对话的约束。高压缩比(超过 3.0)表明存在显著的信息丢失,需要进行验证。

    制品如何与上下文窗口限制交互?

    制品会计入总上下文窗口,但在压缩过程中会得到特殊处理。命名的文档在摘要过程中会保持不变,而对话历史会被压缩。一个 200KB 的会话可能包含 50KB 的制品和 150KB 的对话。压缩后,制品仍保持 50KB,对话压缩至 30KB,剩余 120KB 可用于继续工作。

    即使在压缩后,会话耗尽上下文窗口时会发生什么?

    一旦压缩使令牌数量降低到阈值以下,会话即可继续。如果会话再次达到 200K,则会触发另一次压缩。经过足够多次迭代后,即使是激进的摘要也无法释放足够的空间。此时,会话必须结束并开始新的会话。启动新会话会丢失所有上下文,除非这些上下文已显式外部化到制品或文件中。

    是否有办法在不进行压缩的情况下保存完整的对话历史?

    不,200K 的上下文窗口是硬性限制。开发者可以在压缩之前将关键信息外部化到制品和决策日志中,但对话记录本身不能超过该限制。对于需要完整保留历史记录的场景,应在 Claude 的上下文系统之外实现对话日志记录,并在后续会话中引用该日志。

    具备压缩感知且不丢失关键上下文的代理

    当会话架构将压缩视为不可避免而非故障模式时,上下文摘要就不再是问题。采用显式状态管理、基于制品的持久化以及代币纪律设计的代理能够在多次压缩循环中有效运行而不出现连贯性损失。模式从对抗压缩转变为在其限制内工作。

    成功的代理架构结合了三个要素:作为制品存续的外部化约束、在阶段边界进行具有明确保留策略的手动压缩,以及使大多数会话完全低于压缩阈值的代币纪律。这些模式共同消除了代理在长时间对话后漂移并产生违背先前决策的工作这一常见故障模式。

    将上下文视为仅追加日志还是视为受管理的资源之间的区别决定了代理在长期协作中的可靠性。实施压缩感知模式的团队在持续数天或数周的会话中能够看到代理行为的一致性。忽视压缩的团队只会在生产环境中遇到微妙的故障——当关键的边缘情况或架构约束从代理记忆中消失时才会显现。

    这涵盖了 Claude Code 上下文管理的基本模式。在生产环境中应用这些方法,效果将立竿见影。

    ——

    🧑‍💻

    zhirenhun

    一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。

    typescript ai javascript webdev