Skip to main content
xbot
Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Toggle Dark/Light/Auto mode Back to homepage
Edit page

Phase 1.5 上下文压缩重构方案

状态:✅ 门下省审核通过,待工部实施 创建日期:2026-03-19 依赖:无外部依赖,纯内部重构


1. 概述

1.1 背景与问题

Phase 2 上下文压缩架构(context_manager_phase2.go)存在根本性缺陷——eviction 层会导致死循环

evictByDensity() 驱逐了 LLM 正在使用的代码内容
  → LLM 丢失上下文,重新读取文件
    → 上下文再次膨胀
      → 再次触发 eviction
        → ∞ 死循环

eviction 这个概念在 coding agent 场景下不可救药:agent 的工作模式就是反复读取和修改文件,而 eviction 的核心假设是"旧内容可以安全丢弃"——这两者根本矛盾。

1.2 Phase 1.5 定位

Phase 1.5 = Phase 1 的简单架构 + Phase 2 的好部件

来源保留/删除部件
Phase 1 架构✅ 保留简单的 Offload → Compact 流程
Phase 2 fingerprint✅ 保留ExtractFingerprint() + compressMessagesWithFingerprint()
Phase 2 质量评分✅ 保留EvaluateQuality() + ValidateCompression()
Phase 2 冷却期✅ 保留CompressCooldown(含死循环检测)
Phase 2 智能触发✅ 保留ShouldCompressDynamic() + BuildTriggerInfo()
Phase 2 eviction❌ 删除evictByDensity() 及其所有辅助代码
Phase 2 流水线⚠️ 简化从 “Offload → Evict → Compact” 变为 “Offload → Compact”

1.3 核心变化

  1. 去掉 eviction 层:不再按信息密度驱逐 tool result,避免死循环
  2. 增强 fingerprint:新增"活跃文件"追踪,保护 LLM 正在使用的文件不被截断
  3. 改进压缩 prompt:从"压缩对话"改为"提取任务状态",输出结构化任务状态文档
  4. 智能 tail 保护:涉及活跃文件的 tool 组全量保留,仅对非活跃 tail 截断

2. 删除项

以下代码从 xbot/agent/compress.go 中彻底删除:

2.1 删除的函数

函数名所在文件行号说明
defaultDensityScorer()compress.go第221行默认信息密度评分
containsErrorPattern()compress.go第262行错误关键词检测
containsDecisionPattern()compress.go第274行决策关键词检测
isLargeCodeDump()compress.go第286行大代码块检测
isRepetitiveGrepResult()compress.go第300行重复 grep 结果检测
evictByDensity()compress.go第338行核心 eviction 函数
buildCompressResultFromEvicted()compress.go第421行从 evicted 消息构建结果
countEvictedMessages()compress.go第639行统计被驱逐消息数

2.2 删除的结构体/类型

名称所在文件行号说明
evictToolGroupcompress.go第331行工具组边界 [start, end]
DensityScorecompress.go第214行信息密度评分结果

2.3 删除的调用(从 phase2Manager 中移除)

context_manager_phase2.go 中的 Compress() 方法(第65行起)和 ManualCompress() 方法(第167行起):

  • Compress() 中删除 evictByDensity() 调用
  • Compress() 中删除 buildCompressResultFromEvicted() 调用
  • Compress() 中删除 countEvictedMessages() 调用
  • ManualCompress() 中删除 evictByDensity() 调用
  • ManualCompress() 中删除 buildCompressResultFromEvicted() 调用

2.4 删除的测试文件

文件说明
evict_test.go整个文件删除。包含 24 个测试函数(TestContainsErrorPattern_*TestContainsDecisionPattern_*TestIsLargeCodeDump_*TestIsRepetitiveGrepResult_*TestDefaultDensityScorer_*TestEvictByDensity_* 等)

2.5 删除 compress_test.go 中的测试

测试函数行号说明
TestBuildCompressResultFromEvicted_NoDuplicateSystemMessage第204行测试被删函数
TestBuildCompressResultFromEvicted_SystemPreservedAtFront第240行测试被删函数

3. 保留但整合项

以下部件从 Phase 2 保留,并整合到简化后的架构中:

3.1 fingerprint 引导

保留ExtractFingerprint() + compressMessagesWithFingerprint()

  • 位置quality.go:24ExtractFingerprint)、compress.go:666compressMessagesWithFingerprint
  • 行为:不变。压缩前提取关键信息指纹,注入 LLM prompt 引导保留
  • 增强:见第 4 节

3.2 质量评分

保留EvaluateQuality() + ValidateCompression() + ValidateMarkers()

  • 位置quality.go:57ValidateCompression)、quality.go:117EvaluateQuality)、quality.go:417ValidateMarkers
  • 行为:不变。Phase 2 的 Compress() 仍使用质量评分决定是否重试

3.3 冷却期与死循环检测

保留CompressCooldownRecordIneffective() / RecordEffective()

  • 位置trigger.go:123CompressCooldown)、trigger.go:156RecordIneffective)、trigger.go:164RecordEffective
  • 行为:不变。连续低效压缩自动加大冷却期

3.4 智能触发

保留ShouldCompressDynamic() + BuildTriggerInfo() + calculateDynamicThreshold() + DetectToolPattern()

  • 位置context_manager_phase2.go:35ShouldCompressDynamic)、trigger.go:183calculateDynamicThreshold)、trigger.go:234DetectToolPattern)、trigger.go:284BuildTriggerInfo
  • 行为:不变。三因子动态阈值(阶段因子 + 增长因子 + 模式因子)

3.5 thinTail / aggressiveThinTail

保留但改为可选thinTail() + aggressiveThinTail()

  • 位置compress.go:512thinTail)、compress.go:573aggressiveThinTail
  • 行为变更:不再无条件截断所有旧 tool 组,改为仅当 token 超限时对非活跃 tail 截断
  • 活跃文件保护:见第 4.3 节

4. 增强项

4.1 增强 fingerprint:活跃文件追踪

目标:识别 LLM 最近 N 轮正在操作的文件,在压缩时全量保护。

4.1.1 新增 ActiveFile 结构体

quality.go 中新增:

// ActiveFile 最近 N 轮活跃文件记录
type ActiveFile struct {
    Path         string   // 文件路径
    LastSeenIter int      // 最后出现的轮次
    Functions    []string // 涉及的函数签名(从 tool result 中提取)
}

// ExtractActiveFiles 从最近 N 轮 tool call 中提取活跃文件。
// 扫描 messages 尾部的 tool call,提取 Read/Edit/Shell 等涉及的文件路径。
func ExtractActiveFiles(messages []llm.ChatMessage, lastN int) []ActiveFile

4.1.2 修改 KeyInfoFingerprint 结构体

quality.go:12 中,为 KeyInfoFingerprint 新增字段:

type KeyInfoFingerprint struct {
    FilePaths    []string      // 原有
    Identifiers  []string      // 原有
    Errors       []string      // 原有
    Decisions    []string      // 原有
    ActiveFiles  []ActiveFile  // 新增:最近 N 轮的活跃文件
}

4.1.3 修改 ExtractFingerprint()

quality.go:24ExtractFingerprint() 中调用 ExtractActiveFiles(),将活跃文件填充到 fp.ActiveFiles

func ExtractFingerprint(messages []llm.ChatMessage) KeyInfoFingerprint {
    // ... 原有逻辑 ...
    
    // 新增:提取活跃文件
    fp.ActiveFiles = ExtractActiveFiles(messages, 3)
    return fp
}

4.1.4 ExtractActiveFiles 实现要点

从 messages 尾部向前扫描,找到最近 3 轮 tool call(一轮 = 一组 assistant(tool_calls) + 对应 tool results),按以下规则提取文件路径:

工具名参数来源路径提取字段
ReadArguments JSONpath
Edit / WriteArguments JSONpathfile_path
GlobArguments JSONpattern
GrepArguments JSONpath
Shell无固定参数Content 中正则提取 /\S+\.go
SubAgent不适用不提取
  • Content 中提取函数签名(正则匹配 func \w+ 模式)
  • 去重后按 LastSeenIter 降序排列

4.2 改进压缩 prompt

目标:从"压缩对话摘要"改为"提取结构化任务状态文档"。

4.2.1 替换 structuredCompressionPrompt

compress.go:22 中,将现有的 structuredCompressionPrompt 替换为新版本 taskStatePrompt

const taskStatePrompt = `You are a task state extraction expert. Extract the current task state from the conversation into a structured document.

## Goal
Transform verbose conversation history into a concise, structured task state document.
Focus on WHAT has been done, WHAT is in progress, and WHAT remains.

## Output Format
Use these structured sections and markers:

### 📋 Task Summary
Brief overview of what the user asked for and current progress.

### 📁 Active Files
@file:{path} — Files currently being worked on (MUST include ALL active file references)
@func:{signature} — Key function signatures from active files

### ✅ Completed Steps
- What has been done so far (with file paths and specific details)

### 🔄 Current Step
What is being worked on right now. Include:
- Current file being edited/read
- Pending modifications
- Context needed to continue

### ❌ Errors (MUST preserve ALL)
@error:{description} — Every error encountered (essential for debugging)

### 📌 Decisions
@decision:{description} — All decisions made during this session

### 📝 Pending Tasks
@todo:{description} — Tasks not yet started

## Compression Rules
1. PRESERVE ALL file paths that appear in active file operations
2. PRESERVE ALL error messages verbatim
3. PRESERVE all function signatures from active files
4. Include specific details (variable names, line numbers, code snippets)
5. If 📂 [offload:...] markers exist, preserve them verbatim
6. Prioritize RECENT information over old history
7. This is NOT a summary — it's a task state document for continuing work`

4.2.2 修改 prompt 注入逻辑

compress.go:666compressMessagesWithFingerprint() 中:

  1. 将第778行的 structuredCompressionPrompt 替换为 taskStatePrompt
  2. fpSection 中新增活跃文件区块:
if len(fp.ActiveFiles) > 0 {
    fpSection.WriteString("\n## ACTIVE FILES (must be fully preserved in output):\n")
    for _, af := range fp.ActiveFiles {
        fmt.Fprintf(&fpSection, "  @file:%s\n", af.Path)
        for _, fn := range af.Functions {
            fmt.Fprintf(&fpSection, "  @func:%s\n", fn)
        }
    }
}

4.3 不截断活跃 tail

目标:tail 部分如果涉及活跃文件,全量保留。

4.3.1 修改 thinTail() 签名

// thinTail 精简尾部旧工具组,保留最近 keepGroups 组完整内容。
// activeFiles: 活跃文件集合,涉及这些文件的 tool 组不会被截断。
func thinTail(tail []llm.ChatMessage, keepGroups int, activeFiles []ActiveFile) []llm.ChatMessage

4.3.2 实现要点

  • 在识别工具组后,额外检查每个组的 tool call 参数中是否包含活跃文件路径
  • 如果 tool group 涉及活跃文件,将其标记为"活跃组",即使不在 keepGroups 范围内也不截断
  • 仅对非活跃组执行原有的截断逻辑

4.3.3 同步修改 aggressiveThinTail()

签名同样增加 activeFiles 参数,逻辑与 thinTail 一致。

4.3.4 修改调用链

compressMessagesWithFingerprint() 中,第690行的 thinTail 调用和第815行的 aggressiveThinTail 调用均需传递 activeFiles

// 第二步:精简尾部旧工具组
activeFiles := ExtractActiveFiles(messages, 3)
var thinnedTail []llm.ChatMessage
if tailStart < len(messages) {
    thinnedTail = thinTail(messages[tailStart:], 1, activeFiles)
}

5. 架构变更

5.1 Phase 2 新流程(简化后)

Compress():
  1. ExtractFingerprint() — 提取指纹 + 活跃文件
  2. thinTail(activeFiles) — 仅截断非活跃 tail
  3. compressMessagesWithFingerprint() — LLM 压缩(使用 taskStatePrompt)
  4. EvaluateQuality() + ValidateCompression() — 质量校验
  5. 低质量时重试(compressMessagesWithFingerprintAndLostItems)
  6. 仍不足时 aggressiveThinTail(activeFiles)

对比旧流程:

旧:Offload → Evict(evictByDensity)→ Compact(LLM)
新:Offload → Compact(LLM + 增强fingerprint + 活跃文件保护)

5.2 phase2Manager 简化

phase2Manager 从"三层流水线调度器"简化为"外层壳":

职责保留说明
冷却期管理CompressCooldown
智能触发ShouldCompressDynamic()
质量评分EvaluateQuality() + ValidateCompression()
低质量重试compressMessagesWithFingerprintAndLostItems()
eviction 调度❌ 删除不再调用 evictByDensity()
evicted 结果构建❌ 删除不再调用 buildCompressResultFromEvicted()

5.3 context_manager.go 中的注释更新

NewContextManager() 第216-217行需更新:

// 旧(第216-217行):
// Phase 2: 三层渐进压缩(Offload → Evict → Compact)
log.WithField("mode", mode).Info("Using Phase 2 smart compression (Offload → Evict → Compact)")
// 新:
// Phase 2: 智能压缩(Offload → Compact,无 Evict 层)
log.WithField("mode", mode).Info("Using Phase 1.5 smart compression (Offload → Compact)")

同时更新 ContextModePhase2 常量注释(第17行):

// 旧:
// ContextModePhase2 Phase 2 三层渐进压缩
// 新:
// ContextModePhase2 Phase 2 智能压缩(Offload → Compact,含 fingerprint 引导与活跃文件保护)

5.4 context_manager_phase2.go 完整重写

phase2Manager.Compress() 方法(第65行起)重写为:

func (m *phase2Manager) Compress(ctx context.Context, messages []llm.ChatMessage, client llm.LLM, model string) (*CompressResult, error) {
    originalTokens, _ := llm.CountMessagesTokens(messages, model)

    log.Ctx(ctx).WithFields(map[string]interface{}{
        "original_tokens": originalTokens,
        "max_tokens":      m.config.MaxContextTokens,
    }).Info("Phase 1.5 compress: starting")

    // 步骤1:提取指纹(含活跃文件)
    fp := ExtractFingerprint(messages)

    // 步骤2:带 fingerprint 引导的 LLM 压缩
    result, err := compressMessagesWithFingerprint(ctx, messages, fp, client, model)
    if err != nil {
        return nil, err
    }

    // 步骤3:质量校验
    compressedText := joinMessages(result.SessionView)
    compressedTokens, _ := llm.CountMessagesTokens(result.LLMView, model)
    quality := EvaluateQuality(originalTokens, compressedTokens, fp, compressedText)

    _, lostItems := ValidateCompression(messages, result.SessionView, fp)
    retentionRate := 1.0
    if totalItems := len(fp.FilePaths) + len(fp.Identifiers) + len(fp.Errors) + len(fp.Decisions); totalItems > 0 {
        retentionRate = float64(totalItems-len(lostItems)) / float64(totalItems)
    }

    // 步骤4:低质量重试
    if quality < 0.6 && retentionRate < 0.8 && client != nil && len(lostItems) > 0 {
        retryResult, retryErr := compressMessagesWithFingerprintAndLostItems(ctx, messages, fp, lostItems, client, model)
        if retryErr == nil {
            retryText := joinMessages(retryResult.SessionView)
            retryTokens, _ := llm.CountMessagesTokens(retryResult.LLMView, model)
            retryQuality := EvaluateQuality(originalTokens, retryTokens, fp, retryText)
            if retryQuality > quality {
                quality = retryQuality
                result = retryResult
            }
        }
    }

    // 步骤5:标记完整性检测
    missingMarkers := ValidateMarkers(compressedText, fp)
    if len(missingMarkers) > 0 {
        log.Ctx(ctx).WithFields(map[string]interface{}{
            "missing_markers": len(missingMarkers),
            "quality_score":   quality,
        }).Warn("Phase 1.5 compress missing structured markers")
    }

    log.Ctx(ctx).WithFields(map[string]interface{}{
        "quality_score":  quality,
        "retention_rate": retentionRate,
        "markers":        countStructuredMarkers(compressedText),
        "new_tokens":     compressedTokens,
    }).Info("Phase 1.5 compress quality report")

    return result, nil
}

phase2Manager.ManualCompress() 方法(第167行起)重写为:

func (m *phase2Manager) ManualCompress(ctx context.Context, messages []llm.ChatMessage, client llm.LLM, model string) (*CompressResult, error) {
    fp := ExtractFingerprint(messages)
    result, err := compressMessagesWithFingerprint(ctx, messages, fp, client, model)
    if err != nil {
        // 降级到不带指纹的压缩
        return compressMessages(ctx, messages, client, model)
    }
    return result, nil
}

5.5 不再需要 compressMessagesWithFingerprint 中的 evicted 参数

旧流程中 Compress() 先调用 evictByDensity() 得到 evicted 消息,再传入 compressMessagesWithFingerprint()。 新流程直接传入原始 messages,由 compressMessagesWithFingerprint() 内部处理 tail 截断。

这实际上简化了调用链——compressMessagesWithFingerprint() 已经内置了 tail 分离和 thinTail 逻辑(第690行、第815行),无需外部预处理。


6. 受影响文件清单

6.1 agent/compress.go

操作对象行号说明
删除DensityScore 结构体第214行信息密度评分结果
删除defaultDensityScorer()第221行默认密度评分函数
删除containsErrorPattern()第262行错误关键词检测
删除containsDecisionPattern()第274行决策关键词检测
删除isLargeCodeDump()第286行大代码块检测
删除isRepetitiveGrepResult()第300行重复 grep 检测
删除evictToolGroup 结构体第331行工具组边界
删除evictByDensity()第338行核心 eviction 函数
删除buildCompressResultFromEvicted()第421行evicted 结果构建
删除countEvictedMessages()第639行统计被驱逐消息数
替换structuredCompressionPrompt第22行改为 taskStatePrompt
修改thinTail()第512行新增 activeFiles 参数
修改aggressiveThinTail()第573行新增 activeFiles 参数
修改compressMessagesWithFingerprint()第666行使用新 prompt + 注入活跃文件 + 传递 activeFiles 给 thinTail

6.2 agent/quality.go

操作对象行号说明
新增ActiveFile 结构体活跃文件记录
新增ExtractActiveFiles()提取最近 N 轮活跃文件
修改KeyInfoFingerprint第12行新增 ActiveFiles 字段
修改ExtractFingerprint()第24行调用 ExtractActiveFiles() 填充新字段

6.3 agent/context_manager_phase2.go

操作对象行号说明
重写Compress()第65行去掉 eviction,直接 fingerprint + LLM 压缩
重写ManualCompress()第167行去掉 eviction,直接 fingerprint + LLM 压缩
修改注释第12行附近流程描述从 “Offload → Evict → Compact” 更新

6.4 agent/context_manager.go

操作对象行号说明
修改ContextModePhase2 注释第17行从"三层渐进压缩"更新
修改NewContextManager() 日志文案第216-217行从 “Offload → Evict → Compact” 更新

6.5 agent/evict_test.go

操作说明
删除整个文件24 个测试函数全部删除

6.6 agent/compress_test.go

操作对象行号说明
删除TestBuildCompressResultFromEvicted_NoDuplicateSystemMessage第204行被删函数的测试
删除TestBuildCompressResultFromEvicted_SystemPreservedAtFront第240行被删函数的测试
修改TestCompressMessagesWithFingerprint_InjectsFingerprint第263行验证新 prompt 格式(active files 注入)

6.7 agent/quality_test.go

操作对象说明
新增TestExtractActiveFiles_*活跃文件提取测试
新增TestExtractFingerprint_WithActiveFiles验证 fingerprint 包含活跃文件
修改TestExtractFingerprint_*考虑新增 ActiveFiles 字段

6.8 agent/context_manager_test.go

操作对象说明
修改TestPhase2Manager_Compress更新预期行为(不再有 eviction 阶段)

6.9 未受影响的文件

以下文件不受影响,无需修改:

  • agent/trigger.go — 智能触发逻辑完全保留
  • agent/trigger_test.go — 对应测试保留
  • agent/context_manager_phase1.go — Phase 1 管理器不变
  • agent/offload.go / agent/offload_test.go — offload 独立于 eviction

7. 测试策略

7.1 需要删除的测试

测试文件测试函数数原因
evict_test.go全部 24 个测试eviction 功能整体删除
compress_test.go2 个(TestBuildCompressResultFromEvicted_*被删函数测试

共计删除 26 个测试。

7.2 需要修改的测试

测试文件测试函数修改内容
compress_test.goTestCompressMessagesWithFingerprint_InjectsFingerprint(第263行)验证新 prompt 包含 taskStatePrompt 关键词(如"task state"、“Active Files”)而非旧的 “context compression”
compress_test.goTestCompressMessagesWithFingerprint_EmptyFingerprintNoInjection(第317行)确保 ActiveFiles 为空时不注入活跃文件区块
context_manager_test.goTestPhase2Manager_CompressPhase 2 Compress 不再有 eviction 阶段,直接走 LLM 压缩路径
quality_test.goTestExtractFingerprint_*验证 fp.ActiveFiles 字段被正确填充

7.3 需要新增的测试

quality_test.go 新增

测试函数说明
TestExtractActiveFiles_WithReadToolCalls从 Read tool call 的 Arguments JSON 提取 path 字段
TestExtractActiveFiles_WithEditToolCalls从 Edit tool call 的 Arguments JSON 提取 path 字段
TestExtractActiveFiles_WithShellGrep从 Shell(grep) 结果中正则提取文件路径
TestExtractActiveFiles_Deduplication相同文件出现多次只记录一次
TestExtractActiveFiles_FunctionSignatures从 tool result 中提取函数签名
TestExtractActiveFiles_EmptyMessages空消息返回空结果
TestExtractFingerprint_WithActiveFiles验证 ExtractFingerprint 正确填充 ActiveFiles

compress_test.go 新增

测试函数说明
TestThinTail_PreservesActiveFileGroups涉及活跃文件的 tool 组不被截断
TestThinTail_StillThinsInactiveGroups不涉及活跃文件的 tool 组正常截断
TestAggressiveThinTail_PreservesActiveFileGroups激进模式下也保护活跃文件
TestCompressMessagesWithFingerprint_ActiveFilesInPrompt活跃文件被注入到压缩 prompt 中

context_manager_test.go 新增

测试函数说明
TestPhase2Manager_Compress_NoEviction验证 Phase 2 Compress 不再调用 eviction

共计新增约 11 个测试。


8. 向后兼容

8.1 API 兼容性

接口变化影响
ContextManager 接口(context_manager.go:40不变所有实现者签名不变
SmartCompressor 接口(context_manager.go:185不变Phase 2 仍实现此接口
phase2Manager.Mode()不变返回 ContextModePhase2
ContextModePhase2 常量不变值仍为 "phase2"
/context mode phase2 命令不变仍可切换,用户无感知

8.2 行为变化

场景旧行为新行为
Phase 2 自动压缩Evict → Compact直接 Compact
Phase 2 手动压缩Evict → Compact直接 Compact
压缩 prompt“压缩对话历史”“提取任务状态文档”
tail 截断无条件截断旧组保护活跃文件组

用户感知:

  • /context mode phase2 仍然有效
  • /compress 手动命令仍然有效
  • ✅ 已有 session 数据不受影响(压缩是对运行时上下文的操作,不改变 session 存储格式)
  • ⚠️ 压缩后的文本格式会变化(从对话摘要变为结构化任务状态文档),但这是内部格式,不影响功能

8.3 回滚方案

如果 Phase 1.5 出现问题,回滚步骤:

  1. git revert 到重构前的 commit
  2. eviction 相关代码已在版本控制中完整保留
  3. 无数据库 schema 变更,无 session 数据格式变更
  4. 回滚后 ContextModePhase2 恢复为 eviction 模式

9. 实施步骤

按依赖关系排序,每步独立可验证:

步骤内容验证方式
Step 1删除 eviction 代码:从 compress.go 删除 evictByDensity 等 8 个函数/结构体go build ./agent/... 通过;被删函数引用均不存在
Step 2删除 eviction 测试:删除 evict_test.go 整个文件 + compress_test.go 中 2 个被删函数测试go test ./agent/... 通过(不含新测试)
Step 3重写 phase2Manager:简化 Compress()(第65行)和 ManualCompress()(第167行)go test ./agent/... -run TestPhase2Manager 通过
Step 4更新注释和日志:context_manager.go 第17行、第216-217行代码审查
Step 5新增 ActiveFile + ExtractActiveFiles()quality.gogo test ./agent/... -run TestExtractActiveFiles 通过
Step 6修改 KeyInfoFingerprint(第12行)+ ExtractFingerprint()(第24行)go test ./agent/... -run TestExtractFingerprint 通过
Step 7替换压缩 prompt(第22行)+ 修改 compressMessagesWithFingerprint()(第666行)go test ./agent/... -run TestCompressMessagesWithFingerprint 通过
Step 8修改 thinTail()(第512行)/ aggressiveThinTail()(第573行)增加活跃文件保护`go test ./agent/… -run “TestThinTail
Step 9全量测试go test ./agent/... 全部通过
Step 10集成测试启动 xbot,使用 phase2 模式执行长对话,观察压缩行为

10. 风险与注意

风险等级缓解措施
去掉 eviction 后 LLM 压缩成本增加eviction 本身有死循环风险,LLM 压缩是正确路径;通过 thinTail 控制输入大小
新 prompt 效果不如旧 prompttaskStatePrompt 是更结构化的输出格式,理论上效果更好;通过质量评分验证
活跃文件提取不准确ExtractActiveFiles 只从 tool call Arguments JSON 中提取路径(path 字段),来源可靠
Phase 2 行为变化对已有用户的影响极低压缩是内部操作,用户不直接感知压缩 prompt 格式变化
删除代码过多导致回归被删代码有完善的测试覆盖(26 个测试),删除后已有测试保证其他功能正常

附录 A:代码量估算

类别文件删除行数新增行数修改行数
eviction 核心代码compress.go~25000
压缩 promptcompress.go~30~350
thinTail 签名compress.go0~20~10
compressMessagesWithFingerprintcompress.go0~15~10
ActiveFile 相关quality.go0~60~5
phase2Manager 重写context_manager_phase2.go~130~500
context_manager 注释context_manager.go220
测试删除evict_test.go + compress_test.go~50000
测试新增quality_test.go + compress_test.go + context_manager_test.go0~250~30
合计~912~432~55

净减少约 425 行代码,同时增加了活跃文件保护和更结构化的压缩输出。


附录 B:关键代码引用

概念文件行号
ContextManager 接口context_manager.go第40行
SmartCompressor 接口context_manager.go第185行
ContextModePhase2 常量及注释context_manager.go第17行
NewContextManager() Phase 2 分支context_manager.go第216行
phase2Manager 结构体context_manager_phase2.go第12行
phase2Manager.Compress()context_manager_phase2.go第65行
phase2Manager.ManualCompress()context_manager_phase2.go第167行
phase2Manager.ShouldCompressDynamic()context_manager_phase2.go第35行
DensityScore 结构体compress.go第214行
defaultDensityScorer()compress.go第221行
containsErrorPattern()compress.go第262行
containsDecisionPattern()compress.go第274行
isLargeCodeDump()compress.go第286行
isRepetitiveGrepResult()compress.go第300行
evictToolGroup 结构体compress.go第331行
evictByDensity()compress.go第338行
buildCompressResultFromEvicted()compress.go第421行
structuredCompressionPromptcompress.go第22行
thinTail()compress.go第512行
aggressiveThinTail()compress.go第573行
compressMessagesWithFingerprint()compress.go第666行
countEvictedMessages()compress.go第639行
KeyInfoFingerprint 结构体quality.go第12行
ExtractFingerprint()quality.go第24行
ValidateCompression()quality.go第57行
EvaluateQuality()quality.go第117行
ValidateMarkers()quality.go第417行
CompressCooldowntrigger.go第123行
calculateDynamicThreshold()trigger.go第183行
DetectToolPattern()trigger.go第234行
BuildTriggerInfo()trigger.go第284行