Claude Code 源码拆解(四):Agent的"记忆系统"——知识与压缩
这是「Claude Code 源码拆解」系列的第四篇。上一篇我们让Agent学会了”列计划”和”派活”,这一篇我们解决一个更根本的问题:如何让Agent既能”记住”又不会”撑爆”
一、两个看似矛盾的需求
Agent运行久了,会遇到两个问题:
-
知识需求:遇到新领域,Agent需要专业知识才能工作 -
上下文爆炸:对话越来越长,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 |
|
|
|
|
| Layer 2: auto_compact |
|
|
|
|
| Layer 3: compact工具 |
|
|
|
|
三层协作流程:
每一轮: tool_result输出 ↓ [Layer 1: micro_compact] ← 静默,替换旧的tool_result ↓ token数 > 阈值? ↓ ↓ 否 是 ↓ ↓ 继续 [Layer 2: auto_compact] ← 保存+摘要+替换 ↓ 继续循环 ↓ 模型觉得需要? ↓ ↓ 否 是 ↓ ↓ 继续 [Layer 3: compact工具]
四、这一篇学到了什么
-
Skills是”知识注入” —— 两层策略:目录在System,内容按需加载 -
Compact是”记忆管理” —— 三层策略:微压缩、自动压缩、手动压缩 -
两者互不干扰 —— Skills解决”懂不懂”,Compact解决”记不记” -
循环本身还是没变 —— 这两个也是Harness机制
下一篇,我们将进入更高级的话题:如何让Agent持久化任务状态,以及在后台执行耗时操作?
我们会看到Task系统和Background Tasks如何让Agent”7×24小时”工作而不卡住。
🔔 关注获取更新
这个系列会持续更新,**下一期我们将让Agent实现”持久化”和”后台执行”**。
如果你想知道:
-
Agent重启后如何”记得”之前的任务? -
耗时操作怎么在后台跑? -
如何实现任务之间的依赖关系?
关注公众号,第一时间获取更新。
夜雨聆风