相信用过 Claude Code 的开发者都遇到过这样的窘境:对话开始时一切顺畅,但随着任务深入,响应速度越来越慢,最终弹出一个令人沮丧的提示——“context window full”。这是因为Claude的上下文窗口有其上限(Sonnet模型为200K tokens)。
本文将深入解析一个在Anthropic黑客马拉松上获奖的项目:Everything Claude Code (ECC)。该项目由Affaan Mustafa历时十个月精心打磨,其核心目标正是攻克这个硬性限制:如何在有限的上文窗口中塞入更多功能,同时避免会话崩溃?
下面,我们来拆解ECC项目中几个关键的上下文压缩与优化策略。
Agent描述压缩:从26K降至2K的秘诀
问题根源何在?
ECC项目包含了27个各司其职的 specialized agents。每个agent都是一个Markdown文件,内含完整的功能说明、工具定义和使用场景。如果将这些描述全部加载到上下文中,仅agent描述本身就会占用约121KB,折合26K tokens。
这意味着什么?——你的上下文窗口还没开始做正事,就已经被“说明书”吃掉了13%。
三层压缩模式
在 scripts/lib/agent-compress.js 中,ECC实现了一个压缩库,提供了三种压缩模式:
1. catalog模式(仅保留元数据)
function compressToCatalog(agent) {
return {
name: agent.name,
description: agent.description,
tools: agent.tools,
model: agent.model,
};
}
此模式仅保留名称、描述、工具和模型四个核心字段。27个agent压缩后仅占用2-3K tokens,压缩率超过85%。
2. summary模式(元数据 + 首段摘要)
function extractSummary(body, maxSentences = 1) {
const lines = body.split('\n');
const paragraphs = [];
let current = [];
let inCodeBlock = false;
for (const line of lines) {
const trimmed = line.trim();
// 跟踪代码块状态
if (trimmed.startsWith('```')) {
inCodeBlock = !inCodeBlock;
continue;
}
if (inCodeBlock) continue;
if (trimmed === '') {
if (current.length > 0) {
paragraphs.push(current.join(' '));
current = [];
}
continue;
}
// 跳过标题、列表项、编号列表、表格行
if (
trimmed.startsWith('#') ||
trimmed.startsWith('- ') ||
trimmed.startsWith('* ') ||
/^\d+\.\s/.test(trimmed) ||
trimmed.startsWith('|')
) {
if (current.length > 0) {
paragraphs.push(current.join(' '));
current = [];
}
continue;
}
current.push(trimmed);
}
const firstParagraph = paragraphs.find(p => p.length > 0);
if (!firstParagraph) return '';
const sentences = firstParagraph.match(/[^.!?]+[.!?]+/g) || [firstParagraph];
return sentences.slice(0, maxSentences).map(s => s.trim()).join(' ').trim();
}
此模式会智能解析agent正文,跳过代码块和各种Markdown格式标记,提取第一段文本内容。压缩后约4-5K tokens。
3. full模式(完整内容)
此模式不会进行压缩。只有当某个agent被真正调用时,才会以懒加载的方式读取其完整内容。
巧妙的懒加载机制
整个设计的精髓在于 lazyLoadAgent 函数:
function lazyLoadAgent(agentsDir, agentName) {
// 安全校验:只允许字母数字下划线和横线
if (!/[\w-]+$/.test(agentName)) return null;
const filePath = path.resolve(agentsDir, `${agentName}.md`);
// 路径遍历防护
if (!filePath.startsWith(resolvedAgentsDir + path.sep)) return null;
if (!fs.existsSync(filePath)) return null;
return loadAgent(filePath);
}
这一设计确保了:
- 安全:包含路径遍历防护,防止恶意输入读取系统任意文件。
- 按需加载:在catalog模式下,会话开始时仅加载元数据;仅在需要时才加载完整的agent描述。
- 快速选择:在选择agent的阶段,上下文仅需承载2-3K tokens,而非最初的26K。
安全测试(tests/lib/agent-compress.test.js)验证了其可靠性:
// 测试路径遍历防护
if (test('lazyLoadAgent rejects path traversal attempts', () => {
const agent = lazyLoadAgent(tmpDir, '../etc/passwd');
assert.strictEqual(agent, null);
}));
// 测试非法字符过滤
if (test('lazyLoadAgent rejects names with invalid characters', () => {
const agent1 = lazyLoadAgent(tmpDir, 'foo/bar');
assert.strictEqual(agent1, null);
const agent2 = lazyLoadAgent(tmpDir, 'foo bar'); // 空格
assert.strictEqual(agent2, null);
const agent3 = lazyLoadAgent(tmpDir, 'foo..bar'); // 双点
assert.strictEqual(agent3, null);
}));
// 测试不存在的agent
if (test('lazyLoadAgent returns null for non-existent agent', () => {
const agent = lazyLoadAgent(tmpDir, 'does-not-exist');
assert.strictEqual(agent, null);
}));
实际的压缩效果如何?我们来看测试验证:
// tests/lib/agent-compress.test.js
const result = buildAgentCatalog(realAgentsDir, { mode: 'catalog' });
assert.ok(result.agents.length > 0, 'Should find at least one agent');
assert.ok(result.stats.compressedBytes < result.stats.originalBytes,
'Catalog should be smaller than original');
// 验证压缩率
const ratio = result.stats.compressedBytes / result.stats.originalBytes;
assert.ok(ratio < 0.5, `Compression ratio ${ratio.toFixed(2)} should be < 0.5`);
// 实际结果: ratio ≈ 0.15 (85% 压缩率)
// 验证token估算
assert.ok(
result.stats.compressedTokenEstimate < 5000,
`Token estimate ${result.stats.compressedTokenEstimate} exceeds 5000`
);
// 27 个 agent 压缩后约 2500 tokens
结果令人振奋:压缩率超过85%,token占用从26K骤降至2-3K。
Frontmatter解析的实现细节
Agent文件使用YAML frontmatter存储元数据,解析器需要妥善处理各种边界情况,这其中涉及了不少 Node.js 的路径和字符串处理技巧:
function parseFrontmatter(content) {
// 匹配 ---\n frontmatter \n---\n body
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/);
if (!match) {
return { frontmatter: {}, body: content };
}
const frontmatter = {};
for (const line of match[1].split('\n')) {
const colonIdx = line.indexOf(':');
if (colonIdx === -1) continue;
const key = line.slice(0, colonIdx).trim();
let value = line.slice(colonIdx + 1).trim();
// 处理 JSON 数组(如 tools: ["Read", "Grep"])
if (value.startsWith('[') && value.endsWith(']')) {
try {
value = JSON.parse(value);
} catch {
// 解析失败保持原字符串
}
}
// 去除引号包裹
if (typeof value === 'string' &&
value.startsWith('"') && value.endsWith('"')) {
value = value.slice(1, -1);
}
frontmatter[key] = value;
}
return { frontmatter, body: match[2] || '' };
}
Context Budget:审计你的Token都去哪了
仅靠压缩还不够,你还需要知道Token被消耗在了何处。ECC贴心地提供了 /context-budget 命令,专门用于分析Token消耗构成。
四大组件扫描
该命令会扫描你的项目设置,并统计四大类组件的Token消耗估算。
核心扫描逻辑(skills/context-budget/SKILL.md):
// Agent 扫描
function scanAgents(agentsDir) {
const agents = fs.readdirSync(agentsDir)
.filter(f => f.endsWith('.md'))
.map(f => {
const content = fs.readFileSync(path.join(agentsDir, f), 'utf8');
const lines = content.split('\n');
const tokens = Math.ceil(lines.length * 1.3); // 每行约 1.3 tokens
// 解析 frontmatter 检查 description 长度
const { frontmatter } = parseFrontmatter(content);
const descWords = frontmatter.description?.split(/\s+/).length || 0;
return {
name: f,
lines: lines.length,
tokens,
heavy: lines.length > 200, // 标记重型 agent
bloated: descWords > 30, // 标记臃肿 frontmatter
};
});
return {
count: agents.length,
totalTokens: agents.reduce((sum, a) => sum + a.tokens, 0),
issues: agents.filter(a => a.heavy || a.bloated),
};
}
// MCP Server 扫描
function scanMcpServers(mcpConfig) {
const servers = Object.values(mcpConfig.mcpServers || {});
const toolCount = servers.reduce((sum, s) =>
sum + (s.tools?.length || 0), 0);
const cliTools = ['gh', 'git', 'npm', 'aws', 'supabase', 'vercel'];
const cliReplaceable = servers.filter(s =>
cliTools.some(cli =>
s.name?.includes(cli) || s.description?.includes(cli)
)
);
return {
serverCount: servers.length,
toolCount,
estimatedTokens: toolCount * 500, // 每个 tool 约 500 tokens
issues: [
servers.length > 10 && `${servers.length} MCP servers (recommend <10)`,
...cliReplaceable.map(s => `${s.name} is CLI-replaceable`),
].filter(Boolean),
};
}
扫描细节解读:
- *Agents (`agents/.md`)**:按每行约1.3个token估算。标记超过200行的“重型agent”和description超过30词的“臃肿frontmatter”。
- *Skills (`skills//SKILL.md
)**:同样按1.3 token/行估算。标记超过400行的大文件,并检查.agents/skills/` 目录中可能存在的重复副本。
- MCP Servers (
.mcp.json):每个tool的schema定义大约消耗500 tokens。标记tool数量超过20的server,并特别指出那些仅包装了简单CLI命令(如 gh, git, npm)的、可被替代的server。
- CLAUDE.md:合并项目级和用户级的CLAUDE.md文件,并标记总行数超过300的情况。
输出报告示例
运行 /context-budget 后,你会得到一份清晰的审计报告:
Context Budget Report
═══════════════════════════════════════
Total estimated overhead: ~33,000 tokens
Context model: Claude Sonnet (200K window)
Effective available context: ~167,000 tokens (83%)
Component Breakdown:
┌─────────────────┬────────┬───────────┐
│ Component │ Count │ Tokens │
├─────────────────┼────────┼───────────┤
│ Agents │ 16 │ ~12,400 │
│ Skills │ 28 │ ~6,200 │
│ Rules │ 8 │ ~4,800 │
│ MCP tools │ 87 │ ~43,500 │
│ CLAUDE.md │ 2 │ ~1,200 │
└─────────────────┴────────┴───────────┘
⚠ Issues Found (3):
- 3 heavy agents (>200 lines)
- 14 MCP servers enabled (recommend <10)
- 3 CLI-replaceable MCP servers
Top 3 Optimizations:
1. Remove 3 MCP servers → save ~27,500 tokens (47% overhead reduction)
2. Use catalog mode for agents → save ~11,000 tokens
3. Compact CLAUDE.md → save ~400 tokens
这个工具的价值在于可视化。很多时候你只是在不断添加新功能,却意识不到它们已经悄然吞噬了一半的上下文预算。
Strategic Compact:在合适的时机做压缩
ECC不依赖于Claude Code内置的自动压缩机制(后者往往在不合时宜的节点触发,例如任务进行到一半时突然压缩,导致关键上下文丢失)。他们实现了一套 Strategic Compact(策略性压缩) 机制。
核心原理:在工具调用时计数
在 scripts/hooks/suggest-compact.js 中,通过 PreToolUse Hook(在执行Edit/Write等操作前触发)来进行调用计数:
(注:此处展示核心逻辑,完整实现包含详尽的错误处理和会话隔离)
if (count === threshold) {
log(`[StrategicCompact] ${threshold} tool calls reached - consider /compact`);
}
if (count > threshold && (count - threshold) % 25 === 0) {
log(`[StrategicCompact] ${count} tool calls - good checkpoint for /compact`);
}
默认阈值是50次工具调用。达到阈值后,会首次建议执行 /compact。之后每超出阈值25次,会再次提醒,为你标记出合适的压缩检查点。
策略性压缩的最佳时机
ECC建议在以下明确的任务边界进行手动压缩:
| 阶段转换 |
是否压缩 |
原因 |
| Research → Planning |
是 |
研究成果占用空间大,但计划是提炼后的精华 |
| Planning → Implementation |
是 |
计划可以存入TodoWrite或文件,腾出空间写代码 |
| Implementation → Testing |
看情况 |
如果测试需要引用刚写的代码,则先不压缩 |
| Debugging → Next feature |
是 |
调试痕迹对新功能而言是噪音 |
| Mid-implementation |
否 |
会丢失变量名、文件路径等关键中间信息 |
理解压缩的“幸存者”
很多人不敢压缩,是担心丢失重要信息。ECC明确了压缩后哪些内容能保留:
能够存活的:
- CLAUDE.md 中的指令(始终加载)
- TodoWrite 中的任务列表
- Memory 文件 (
~/.claude/memory/)
- Git 状态(提交记录、分支信息)
- 磁盘上的所有文件
会丢失的:
- 中间的推理和分析过程
- 之前读取过的文件内容(除非重新读取)
- 多轮对话的上下文历史
- 工具调用的历史和计数
- 口头表达的细微偏好
因此,ECC的核心建议是:将任何你认为重要的中间状态,先保存到文件或Memory中,然后再执行压缩。
环境变量层面的优化
除了架构设计,ECC还提供了一套推荐的环境变量配置,据称最高可降低70%的token消耗:
{
"model": "sonnet",
"env": {
"MAX_THINKING_TOKENS": "10000",
"CLAUDE_AUTOCOMPACT_PCT_OVERRIDE": "50",
"CLAUDE_CODE_SUBAGENT_MODEL": "haiku"
}
}
这些环境变量在代码中被读取并应用,例如在 suggest-compact.js 中读取 COMPACT_THRESHOLD。
MAX_THINKING_TOKENS=10000:默认值是31999,Claude会利用这些token进行内部“思考”。将其降至10000可节省约70%的“隐藏成本”。对于简单任务,甚至可以设置为0以完全关闭。
CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=50:默认在上下文使用率达到95%时触发自动压缩,此时响应质量可能已下降。改为50%触发,为复杂任务留出更充足的余量。
CLAUDE_CODE_SUBAGENT_MODEL=haiku:子agent(如负责文件读取、代码搜索的Task工具)默认继承主模型(通常是昂贵的Sonnet)。换成更轻量的Haiku模型可节省约80%的成本,且对于此类简单任务完全够用。
对MCP Server的取舍
ECC在文档中提出了一个重要警告:每个启用的MCP server都会将其所有tool的定义schema塞入上下文窗口。如果启用过多MCP,200K的窗口可能仅剩70K可用。
他们的建议是:
- 将启用的MCP server数量保持在10个以下。
- 优先使用CLI工具(例如用
gh 命令行替代GitHub MCP,用 aws cli 替代AWS MCP)。
- 利用
disabledMcpServers 配置按项目禁用不需要的server。
- 评估默认配置的Memory MCP等server是否真的需要,可以考虑关闭。
/context-budget 扫描器会专门标记出那些“CLI-replaceable”的MCP server,指导你进行优化。
总结:Token优化是一个系统工程
回顾一下,ECC提供了一套组合拳来解决上下文限制问题:
- Agent Compression —— 懒加载 + 三层压缩模式,将agent描述从26K降至2-3K tokens。
- Context Budget —— 审计工具,可视化呈现token消耗分布,帮你定位“内存大户”。
- Strategic Compact —— 基于工具调用计数的策略性压缩建议,避免在任务中途丢失上下文。
- Model Routing —— 让轻量模型(Haiku)处理简单任务,重量模型(Sonnet/Opus)专注复杂推理。
- Thinking Tokens控制 —— 通过环境变量限制内部推理token,削减隐藏成本。
- MCP管理 —— 严格控制MCP server的数量和必要性,减少不必要的schema开销。
这些技术背后贯穿着同一个核心理念:不要试图一次性将所有信息都塞进有限的上下文窗口,而是采用按需加载、及时清理、在明确边界进行管理的策略。
如果你也在使用Claude Code进行复杂的项目开发,并时常受限于上下文窗口,那么ECC项目的这些 开源实战 经验和具体实现,无疑提供了极具价值的参考。更多深入的技术讨论和实践分享,也欢迎在 云栈社区 的相应板块进行交流。
关键源码参考与测试验证
Agent压缩核心实现 (scripts/lib/agent-compress.js):
function buildAgentCatalog(agentsDir, options = {}) {
const mode = options.mode || 'catalog';
let agents = loadAgents(agentsDir);
const originalBytes = agents.reduce((sum, a) => sum + a.byteSize, 0);
let compressed;
if (mode === 'catalog') {
compressed = agents.map(compressToCatalog);
} else if (mode === 'summary') {
compressed = agents.map(compressToSummary);
} else {
compressed = agents.map(a => ({...a, body: a.body}));
}
const compressedJson = JSON.stringify(compressed);
const compressedTokenEstimate = Math.ceil(compressedJson.length / 4);
return {
agents: compressed,
stats: {
totalAgents: agents.length,
originalBytes,
compressedBytes: Buffer.byteLength(compressedJson, 'utf8'),
compressedTokenEstimate,
mode,
},
};
}
运行测试验证压缩效果:
# 运行Agent压缩测试
node tests/lib/agent-compress.test.js
运行Strategic Compact测试:
node tests/hooks/suggest-compact.test.js
文章基于ECC v1.9.0版本代码分析,其中Agent描述压缩是本次重点解析的特性。