乐于分享
好东西不私藏

Claude Code 源码拆解(四):Agent的"记忆系统"——知识与压缩

Claude Code 源码拆解(四):Agent的"记忆系统"——知识与压缩

这是「Claude Code 源码拆解」系列的第四篇。上一篇我们让Agent学会了”列计划”和”派活”,这一篇我们解决一个更根本的问题:如何让Agent既能”记住”又不会”撑爆”


一、两个看似矛盾的需求

Agent运行久了,会遇到两个问题:

  1. 知识需求:遇到新领域,Agent需要专业知识才能工作
  2. 上下文爆炸:对话越来越长,token消耗越来越大

这两个需求看似矛盾:

  • 要让Agent”懂”,得给它知识 → 上下文会变长
  • 要让Agent”省”,得压缩上下文 → 信息会丢失

Claude Code怎么解这个矛盾的?

两个独立机制,各司其职:

  • Skills:按需加载知识,不提前占位
  • Compact:智能压缩历史,保留关键信息

二、Skills:两层注入策略

2.1 错误做法:把知识塞进System Prompt

SYSTEM = """You are a coding agent.# PDF处理指南1. 使用pdfplumber库...2. 处理表格时...3. 中文OCR需要...# 代码审查规范1. 命名规范...2. 错误处理...3. 安全检查...# MCP协议文档1. 传输层...2. 认证...3. 资源订阅..."""

问题:

  • System Prompt每次API调用都会发送
  • 90%的知识Agent用不上
  • 白白浪费大量token

2.2 正确做法:两层注入

Layer 1:目录层(在System Prompt里只放简介)

SYSTEM = """You are a coding agent.Use load_skill to access specialized knowledge.Skills available:  - pdf: 处理PDF文件,提取文本和表格  - code-review: 代码审查,检查规范和安全  - mcp-builder: 构建MCP服务器"""

Layer 2:内容层(按需加载,通过tool_result注入)

# 当模型调用 load_skill("pdf")tool_result = """<skill name="pdf"># PDF处理完整指南## 依赖安装pip install pdfplumber pymupdf## 基础用法import pdfplumberwith pdfplumber.open("file.pdf") as pdf:    for page in pdf.pages:        text = page.extract_text()## 表格提取tables = page.extract_tables()## OCR支持...(省略几百行)</skill>"""

2.3 代码实现

classSkillLoader:def__init__(self, skills_dir: Path):        self.skills = {}# 扫描所有 SKILL.md 文件for f in skills_dir.rglob("SKILL.md"):            text = f.read_text()            meta, body = self._parse_frontmatter(text)            name = meta.get("name", f.parent.name)            self.skills[name] = {"meta": meta, "body": body}defget_descriptions(self) -> str:"""Layer 1: 简短描述,放入System Prompt"""        lines = []for name, skill in self.skills.items():            desc = skill["meta"].get("description""")            lines.append(f"  - {name}{desc}")return"\n".join(lines)defget_content(self, name: str) -> str:"""Layer 2: 完整内容,通过tool_result返回"""        skill = self.skills.get(name)ifnot skill:returnf"Unknown skill: {name}"returnf"<skill name=\"{name}\">\n{skill['body']}\n</skill>"

Skill文件结构(SKILL.md):

---name: pdfdescription: 处理PDF文件,提取文本和表格tags: document, extraction---# PDF处理指南## 依赖安装pip install pdfplumber## 基础用法...

关键洞察:

Don’t put everything in the system prompt. Load on demand.不要把所有东西都塞进System Prompt。按需加载。


三、Compact:三层压缩策略

现在来看另一个问题:对话历史越来越长怎么办?

Claude Code用三层策略处理:

3.1 Layer 1:微压缩(micro_compact)—— 每轮静默执行

KEEP_RECENT = 3defmicro_compact(messages: list):"""保留最近3个tool_result,把更早的替换成占位符"""# 1. 先建立 tool_use_id -> tool_name 的映射    tool_name_map = {}for msg in messages:if msg["role"] == "assistant":for block in msg.get("content", []):if block.type == "tool_use":                    tool_name_map[block.id] = block.name# 2. 收集所有 tool_result    tool_results = []for msg in messages:if msg["role"] == "user"and isinstance(msg.get("content"), list):for part in msg["content"]:if isinstance(part, dict) and part.get("type") == "tool_result":                    tool_results.append(part)if len(tool_results) <= KEEP_RECENT:return# 不用压缩# 3. 替换旧的tool_resultfor result in tool_results[:-KEEP_RECENT]:if len(result.get("content""")) > 100:            tool_id = result.get("tool_use_id""")            tool_name = tool_name_map.get(tool_id, "unknown")            result["content"] = f"[Previous: used {tool_name}]"

效果:

之前:[50000字符的bash输出]之后:[Previous: used bash]

特点:

  • 静默执行,模型无感知
  • 保留最近3轮,足够模型理解上下文
  • 大幅减少token,但保留”做了什么”的信息

3.2 Layer 2:自动压缩(auto_compact)—— 触发阈值时执行

THRESHOLD = 50000# token阈值defagent_loop(messages):whileTrue:# Layer 1        micro_compact(messages)# Layer 2: 检查是否需要自动压缩if estimate_tokens(messages) > THRESHOLD:            messages[:] = auto_compact(messages)# 继续循环...

auto_compact做什么?

defauto_compact(messages: list) -> list:# 1. 保存完整对话到文件(可追溯)    transcript_path = save_transcript(messages)# 2. 让LLM生成摘要    summary = client.messages.create(        model=MODEL,        messages=[{"role""user","content"f"Summarize for continuity:\n{json.dumps(messages)}"        }]    )# 3. 用摘要替换整个对话历史return [        {"role""user""content"f"[Compressed. Transcript: {transcript_path}]\n{summary}"},        {"role""assistant""content""Understood. Continuing with context."},    ]

效果:

  • 10万token的对话 → 2千token的摘要
  • 完整对话保存到.transcripts/,需要时可查
  • Agent可以”无限”运行下去

3.3 Layer 3:手动压缩(compact工具)—— 模型主动触发

# 模型可以主动调用compact工具{"name""compact""description""Trigger manual compression."}

什么时候模型会主动压缩?

  • 模型觉得”之前的对话太乱了”
  • 模型准备开始一个新阶段
  • 模型想”清空脑子”

3.4 三层策略对比

层级
触发条件
执行方式
效果
丢失信息
Layer 1: micro_compact
每轮静默
自动,模型无感知
tool_result → 占位符
工具输出的详细内容
Layer 2: auto_compact
token > 阈值
自动,保存完整记录
整个对话 → 摘要
对话的逐字记录
Layer 3: compact工具
模型主动调用
手动,模型决策
整个对话 → 摘要
由模型判断

三层协作流程:

每一轮:  tool_result输出       ↓  [Layer 1: micro_compact]  ← 静默,替换旧的tool_result       ↓  token数 > 阈值?     ↓           ↓    否           是     ↓           ↓   继续    [Layer 2: auto_compact] ← 保存+摘要+替换                ↓          继续循环                ↓     模型觉得需要?          ↓           ↓         否           是          ↓           ↓        继续    [Layer 3: compact工具]

四、这一篇学到了什么

  1. Skills是”知识注入” —— 两层策略:目录在System,内容按需加载
  2. Compact是”记忆管理” —— 三层策略:微压缩、自动压缩、手动压缩
  3. 两者互不干扰 —— Skills解决”懂不懂”,Compact解决”记不记”
  4. 循环本身还是没变 —— 这两个也是Harness机制

下一篇,我们将进入更高级的话题:如何让Agent持久化任务状态,以及在后台执行耗时操作?

我们会看到Task系统和Background Tasks如何让Agent”7×24小时”工作而不卡住。


🔔 关注获取更新

这个系列会持续更新,**下一期我们将让Agent实现”持久化”和”后台执行”**。

如果你想知道:

  • Agent重启后如何”记得”之前的任务?
  • 耗时操作怎么在后台跑?
  • 如何实现任务之间的依赖关系?

关注公众号,第一时间获取更新。