<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>潘锦的空间 &#187; Claude Code</title>
	<atom:link href="https://www.phppan.com/tag/claude-code/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.phppan.com</link>
	<description>SaaS SaaS架构 团队管理 技术管理 技术架构 PHP 内核 扩展 项目管理</description>
	<lastBuildDate>Sat, 25 Apr 2026 00:56:17 +0000</lastBuildDate>
	<language>zh-CN</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>https://wordpress.org/?v=3.9.40</generator>
	<item>
		<title>OpenClaw 的 Skills 的实现和 Claude Code 不一样</title>
		<link>https://www.phppan.com/2026/03/openclaw-skilss-claude-code/</link>
		<comments>https://www.phppan.com/2026/03/openclaw-skilss-claude-code/#comments</comments>
		<pubDate>Sat, 21 Mar 2026 01:01:17 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[OpenClaw]]></category>
		<category><![CDATA[skills]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2479</guid>
		<description><![CDATA[OpenClaw 的 Skills，本质上不是一个「可调用工具」，它更像一套经过约束的运行手册：启动时把技能目 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">OpenClaw 的 Skills，本质上不是一个「可调用工具」，它更像一套经过约束的运行手册：启动时把技能目录扫描出来，压成一份 <code>&lt;available_skills&gt;</code> 清单塞进 system prompt，模型自己判断要不要选一个 skill，然后再通过 Read 工具去读这个 skill 的 <code>SKILL.md</code>。读完以后，没有任何独立执行器接管，还是在当前这条 session 的 tool-loop 里继续跑。</p>
<p data-tool="mdnice编辑器">Claude Code 走的是另一条路。它把 skill 做成了 tool，工具里负责校验、加载、执行，甚至可以放进一个新上下文里跑完，再把结果回传主对话。</p>
<p data-tool="mdnice编辑器">这两个实现方向，表面上都叫 skills，工程含义完全不一样。</p>
<p data-tool="mdnice编辑器">如果你是做 Agent 平台、企业内 Copilot、代码助手、任务执行器，这个差异不只是架构图上的审美问题。它会直接影响：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>prompt 预算怎么花</section>
</li>
<li>
<section>权限边界放在哪里</section>
</li>
<li>
<section>skill 的治理成本有多高</section>
</li>
<li>
<section>运行链路是可控还是失控</section>
</li>
<li>
<section>你后面想不想做隔离执行、审计、回放、灰度发布</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content">1. OpenClaw 的 Skills，到底是怎么被「召回」的</span></h1>
<p data-tool="mdnice编辑器">很多人一看到 skills，第一反应是：是不是和 memory 一样，先走 embedding 检索，再把相关技能召回进上下文。</p>
<p data-tool="mdnice编辑器">不是。</p>
<p data-tool="mdnice编辑器">OpenClaw 的 skills 召回机制非常直接，甚至可以说有点「朴素」：<strong>扫描目录，生成目录清单，注入提示词，让模型自己选</strong>。整条链路分三段：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>发现</section>
</li>
<li>
<section>注入</section>
</li>
<li>
<section>读取</section>
</li>
</ol>
<p data-tool="mdnice编辑器">OpenClaw 反过来做了一件更工程化的事：先把 skill 列表显式给模型，再用 system prompt 约束它怎么选。</p>
<p data-tool="mdnice编辑器">这个机制的起点在 <code>src/agents/skills/workspace.ts</code> 的 <code>loadSkillEntries()</code>。</p>
<h2 data-tool="mdnice编辑器"><span class="content">1.1 技能发现：先扫目录，再谈调用</span></h2>
<p data-tool="mdnice编辑器">OpenClaw 会从多个 root 目录扫描 <code>SKILL.md</code>，然后合并成最终技能集。这里是有明确的覆盖优先级的：</p>
<p data-tool="mdnice编辑器"><code>extra &lt; bundled &lt; managed &lt; agents-personal &lt; agents-project &lt; workspace</code></p>
<p data-tool="mdnice编辑器">它的逻辑是：越靠近当前工作空间、越贴近用户项目的 skill，优先级越高。平台预置的 bundled skill 可以兜底，但项目级 skill 要能覆盖它。否则你做企业落地时会很难受，团队定制流程永远被平台内置逻辑压着打。</p>
<p data-tool="mdnice编辑器">从目录结构看，OpenClaw 支持两种 skill 形态：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>root 本身就是一个 skill，目录下直接有 <code>SKILL.md</code></section>
</li>
<li>
<section>root 下的子目录分别是 skill，每个子目录里有自己的 <code>SKILL.md</code></section>
</li>
</ul>
<p data-tool="mdnice编辑器">这样，团队在实际维护 skill 时，有两种典型组织方式：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>单一 skill 仓库，根目录就是技能内容</section>
</li>
<li>
<section>skills 集合仓库，每个子目录一个技能</section>
</li>
</ul>
<p data-tool="mdnice编辑器">都支持，落地阻力会小很多。</p>
<p data-tool="mdnice编辑器">它对 <code>SKILL.md</code> 做了体积限制，默认超过 256KB 就直接跳过。</p>
<p data-tool="mdnice编辑器"><code>SKILL.md</code> 本来就应该是高密度操作指南，不应该演变成一个什么都往里塞的大文档。你一旦允许 skill 文件无限变大，后面一定会有人把 SOP、FAQ、设计文档、事故复盘全扔进去，最后技能选择和加载成本一起爆炸。OpenClaw 在扫描阶段直接卡体积，其实是在替平台维护纪律。</p>
<h2 data-tool="mdnice编辑器"><span class="content">1.2 frontmatter 解析</span></h2>
<p data-tool="mdnice编辑器">给 skill 增加最小限度的结构化元信息</p>
<p data-tool="mdnice编辑器">扫描到 <code>SKILL.md</code> 后，OpenClaw 会读原文并解析 frontmatter。坏掉或者缺失的 frontmatter 会被忽略。</p>
<p data-tool="mdnice编辑器">这个决策也很对。</p>
<p data-tool="mdnice编辑器">OpenClaw 这里更像是「有就用，没有拉倒」。它承认 markdown 本体才是 skill 的核心，frontmatter 只是辅助控制面。这个姿态很适合 skills 这种增长很快、来源很多的资产类型。</p>
<p data-tool="mdnice编辑器">但这里也埋了一个工程 trade-off：frontmatter 被弱约束，意味着后续治理和平台能力扩展会受限。你今天只做 discovery 和 basic gating，这么玩没问题；你明天如果要做 skill 分类、依赖分析、版本兼容、批量审计，元信息松散会让成本陡增。</p>
<p data-tool="mdnice编辑器">OpenClaw 当前站在了「先把系统跑起来」的一边，没有走「先把规范做重」的路子。</p>
<p data-tool="mdnice编辑器">这意味着它更适合中小规模 skill 生态，或者说更适合个人助手这类工具，而不是一个强治理的企业级 skill marketplace。</p>
<h1 data-tool="mdnice编辑器"><span class="content">2. 不是所有 skill 都会进 <code>&lt;available_skills&gt;</code></span></h1>
<p data-tool="mdnice编辑器">扫描出来只是候选集，不等于模型能看到。</p>
<p data-tool="mdnice编辑器">OpenClaw 在注入 system prompt 之前会做一轮 gating。这个步骤比很多人想象得重要，因为它决定了模型到底暴露给了哪些能力。</p>
<p data-tool="mdnice编辑器">过滤逻辑里有几类条件。</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.1 配置开关</span></h2>
<p data-tool="mdnice编辑器">最基础的 enable/disable</p>
<p data-tool="mdnice编辑器">如果某个 skill 在配置里被标成 <code>skills.entries.&lt;skillKey&gt;.enabled === false</code>，它就会被剔除。</p>
<p data-tool="mdnice编辑器">这是最基础的 kill switch。工程上没什么可争议的，必须有。不然你没法快速止血。某个 skill 写坏了、依赖环境挂了、被发现会引导模型做危险操作，没有一键下线能力，平台就不配上线。</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.2 bundled allowlist</span></h2>
<p data-tool="mdnice编辑器">对内置技能单独管控</p>
<p data-tool="mdnice编辑器"><code>skills.allowBundled</code> 只约束 bundled 来源。</p>
<p data-tool="mdnice编辑器">这个细节很有意思。它说明 OpenClaw 把 bundled skills 当成一类特殊资产处理：平台自带，但不默认无条件信任。</p>
<p data-tool="mdnice编辑器">为什么这事重要？因为内置 skill 经常是平台演进中最容易「偷偷变多」的那部分。你今天打包 5 个，明天为了演示方便塞到 20 个，后天 prompt 里一大坨 descriptions，模型选 skill 的噪声越来越大。allowlist 的存在，本质上是在给平台预装能力上保险栓。</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.3 eligibility 判断</span></h2>
<p data-tool="mdnice编辑器">按运行环境判断 skill 是否可用</p>
<p data-tool="mdnice编辑器">OpenClaw 会根据 <code>metadata.requires</code> 去判断 skill 能不能用，条件包括：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>二进制依赖是否存在</section>
</li>
<li>
<section>环境变量是否满足</section>
</li>
<li>
<section>配置是否具备</section>
</li>
<li>
<section>操作系统是否匹配</section>
</li>
<li>
<section>remote 平台是否匹配</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这个设计非常工程化。因为 skill 如果涉及真实操作，它一定和环境耦合。比如某个 skill 依赖特定 cli，或者要求某个 API token，或者只能在 Linux 跑。你如果不在注入前做 eligibility，而是让模型先看到 skill，再在执行时失败，用户体验会很差，模型行为也会变形：它会看到一个貌似可用的方案，实际一调就炸。</p>
<p data-tool="mdnice编辑器">OpenClaw 选择「<strong>先过滤，再暴露</strong>」，这是我非常认同的策略。因为对模型来说，看得见就等于潜在可用。你让它看见无效 skill，本质上是在制造认知噪声。</p>
<h2 data-tool="mdnice编辑器"><span class="content">2.4 <code>disable-model-invocation</code></span></h2>
<p data-tool="mdnice编辑器">系统内部保留，模型不可见</p>
<p data-tool="mdnice编辑器">如果某个 skill 标记了 <code>disable-model-invocation</code>，它不会进入 prompt 的 <code>&lt;available_skills&gt;</code>，但仍然存在于系统内部，可用于管理或校验。</p>
<p data-tool="mdnice编辑器">因为技能资产不只有「给模型用」这一种角色。你可能有些 skill 需要：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>用于测试</section>
</li>
<li>
<section>用于审核</section>
</li>
<li>
<section>用于运维校验</section>
</li>
<li>
<section>用于内部 pipeline</section>
</li>
<li>
<section>用于未来发布但暂不开放</section>
</li>
</ul>
<p data-tool="mdnice编辑器">OpenClaw 没把「存在」和「可被模型调用」绑死，这样 skill registry 才像个平台能力，而不是一个 prompt 拼装器。</p>
<h1 data-tool="mdnice编辑器"><span class="content">3. <code>&lt;available_skills&gt;</code> 才是 OpenClaw 的真正召回入口</span></h1>
<p data-tool="mdnice编辑器">很多人说 skills 被召回，其实这句话在 OpenClaw 语境里容易让人误解。</p>
<p data-tool="mdnice编辑器">真正被放进上下文里的第一层，不是 <code>SKILL.md</code> 内容，而是 <code>&lt;available_skills&gt;</code> 这个目录清单。它由 <code>buildWorkspaceSkillsPrompt()</code> 生成，注入到 system prompt。</p>
<p data-tool="mdnice编辑器">也就是说，模型最先看到的是技能目录，不是技能正文。</p>
<p data-tool="mdnice编辑器">这个做法也算是渐进式披露的一种实现。先给模型足够决定是否要读 skill 的最小信息，避免一开始就把所有技能全文灌进去。</p>
<h2 data-tool="mdnice编辑器"><span class="content">3.1 system prompt 里的强规则，决定了技能选择流程</span></h2>
<p data-tool="mdnice编辑器">OpenClaw 在 system prompt 里有一段明确的 Skills mandatory 规则，大意是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>先扫描 <code>&lt;available_skills&gt;</code> 里的 <code>&lt;description&gt;</code></section>
</li>
<li>
<section>只有明确匹配一个 skill 时，才去读对应的 <code>SKILL.md</code></section>
</li>
<li>
<section>最多只读一个 skill</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这三条里，最重要的是后两条。</p>
<h3 data-tool="mdnice编辑器"><span class="content">3.1.1 只有明确匹配一个才读</span></h3>
<p data-tool="mdnice编辑器">这是在压制模型的泛化冲动。模型很喜欢「我觉得这个也相关，那个也相关」，然后多读几个，最后把自己卷进上下文泥潭。OpenClaw 用提示词硬性要求「明确匹配一个」才允许进入下一步，这本质上是在给模型加稀疏化约束。</p>
<p data-tool="mdnice编辑器">效果未必 100% 可控，但方向是对的。</p>
<h3 data-tool="mdnice编辑器"><span class="content">3.1.2 最多只读一个 skill</span></h3>
<p data-tool="mdnice编辑器">如果你做过基于 prompt 的 tool orchestration，就会知道一个常见死法：模型在一堆说明里来回跳，读完 A 觉得 B 也有用，再读 B，顺手 C 也看看，最后 token 花了不少，任务还没干。</p>
<p data-tool="mdnice编辑器">OpenClaw 这里直接规定 upfront 只能读一个 skill。我个人判断，这不是因为理论上最优，而是因为工程上最稳。</p>
<p data-tool="mdnice编辑器">它牺牲了一部分组合式技能能力，换来了几个非常实际的好处：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>token 消耗更可控</section>
</li>
<li>
<section>模型决策路径更短</section>
</li>
<li>
<section>skill 选择失败更容易定位</section>
</li>
<li>
<section>调试和回放更容易做</section>
</li>
</ul>
<p data-tool="mdnice编辑器">代价也很明确：如果任务天然需要多个 skill 协同，OpenClaw 当前机制会显得笨。模型要么自己在单个 skill 指导下绕着做，要么根本选不准。</p>
<p data-tool="mdnice编辑器">这也是它和 Claude Code 一个很大的分歧。Claude Code 把 skill 视作 tool，更容易天然支持复杂编排；OpenClaw 把 skill 视作可读说明，天然倾向于单次聚焦。</p>
<h2 data-tool="mdnice编辑器"><span class="content">3.2 技能过多时的降级策略：compact 和 truncated</span></h2>
<p data-tool="mdnice编辑器"><code>&lt;available_skills&gt;</code> 不是无上限注入的。技能太多时，OpenClaw 会降级成 compact 格式，甚至截断，并提示 <code>skills truncated</code>。</p>
<p data-tool="mdnice编辑器">这个点看起来像 prompt 小技巧，其实是很重要的预算治理。</p>
<p data-tool="mdnice编辑器">因为技能系统一旦跑起来，数量几乎只会越来越多。最初十几个 skill，description 全量塞进去还行；到几十上百个 skill，再这么搞，system prompt 很快会变成垃圾堆。OpenClaw 至少意识到了这个问题，所以做了两级退化：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>compact：保留更少信息</section>
</li>
<li>
<section>truncated：截断并明确提示</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这说明它把 prompt 当成有限资源，而不是无限容器。</p>
<p data-tool="mdnice编辑器">但实话实说，这个策略只是在「延缓爆炸」，不是根治。skill 数量继续增长以后，靠 compact/truncate 顶不住。因为问题不只是 token 多了，而是模型在目录里做决策的辨识难度会越来越高。description 再压缩，skill 之间的区分度也会变差。</p>
<p data-tool="mdnice编辑器">所以我对这块的判断是：<strong>OpenClaw 的 catalog 注入机制适合中等规模 skill 集，不适合无限扩张。</strong><br />
如果你团队未来真要做上百个 skill 的企业级平台，迟早要引入更分层的 catalog、分类路由、或者显式 selector，而不是靠一坨 <code>&lt;available_skills&gt;</code> 让模型裸选。</p>
<h1 data-tool="mdnice编辑器"><span class="content">4. 真正的 <code>SKILL.md</code> 是怎么进入上下文的</span></h1>
<p data-tool="mdnice编辑器">OpenClaw 的第二层加载是按需读取。模型先看到目录清单，选中 skill 以后，才通过 Read 工具去读对应路径的 <code>SKILL.md</code>。</p>
<h2 data-tool="mdnice编辑器"><span class="content">4.1 技能正文不是 system prompt 的一部分</span></h2>
<p data-tool="mdnice编辑器">system prompt 里只有规则和技能目录。真正的 <code>SKILL.md</code> 内容，是在模型发起一次 read tool call 后，作为 toolResult 追加进当前消息流。</p>
<p data-tool="mdnice编辑器">也就是说，skill 正文进入上下文的方式，和你用工具读一个普通文件没有本质区别。差别只是 system prompt 先规定了什么时候允许这样读。</p>
<p data-tool="mdnice编辑器">这个设计的好处非常明确：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>大幅降低初始上下文体积</section>
</li>
<li>
<section>让技能正文成为按需成本，而不是固定成本</section>
</li>
<li>
<section>skill 更新后无需改 system prompt 模板，只影响运行时读到的内容</section>
</li>
<li>
<section>便于把 skill 当文件资产管理，而不是 prompt 模板片段</section>
</li>
</ul>
<p data-tool="mdnice编辑器">坏处也很明确：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>skill 的执行效果更依赖模型有没有正确触发 read</section>
</li>
<li>
<section>skill 的权威性被弱化成一条 toolResult，而不是顶层指令</section>
</li>
<li>
<section>如果 toolResult 很长，后续上下文里它和普通读文件结果没什么层级差异</section>
</li>
</ul>
<p data-tool="mdnice编辑器">从实现角度看，OpenClaw 这里是很克制的。它没有发明一个 Skill Runtime，没有做一套专门的 skill 调度协议，就是借已有的 read 工具完成正文加载。</p>
<p data-tool="mdnice编辑器">这让系统保持简单，但也让 skill 更像「读到的一份说明书」，而不是「被激活的执行单元」。</p>
<h1 data-tool="mdnice编辑器"><span class="content">5. OpenClaw 的安全边界</span></h1>
<p data-tool="mdnice编辑器">既然 skill 是文件，模型要去读 <code>SKILL.md</code>，安全问题马上就来了：路径能不能逃逸？symbolic link 能不能把 skill 指到 root 外？模型能不能顺着 location 读到不该读的内容？</p>
<p data-tool="mdnice编辑器">OpenClaw 这里做了两层保护。</p>
<h2 data-tool="mdnice编辑器"><span class="content">5.1 第一层：扫描阶段的 containment 检查</span></h2>
<p data-tool="mdnice编辑器">在技能发现阶段，它会对 realpath 做 containment 检查，防止 symlink 把 skill 指到 root 外面。</p>
<p data-tool="mdnice编辑器">这是典型的「尽早失败」策略。我一直觉得文件系统相关的安全问题，能在发现阶段拦的，不要拖到执行阶段。因为一旦把异常路径放进 skill registry，后面每个环节都得假设它可能有问题，整个系统会变得很难推理。</p>
<p data-tool="mdnice编辑器">扫描时就把越界项挡掉，registry 内部的数据至少是干净的。</p>
<h2 data-tool="mdnice编辑器"><span class="content">5.2 第二层：Read 工具的 workspace root guard</span></h2>
<p data-tool="mdnice编辑器">模型真正发起读取时，Read 工具还会做 sandbox root 校验，禁止 <code>..</code> 或绝对路径逃逸 workspace root。</p>
<p data-tool="mdnice编辑器">也就是说，即使 discovery 阶段没出问题，真正读取文件时还有一层运行时防线。</p>
<p data-tool="mdnice编辑器">文件边界这事，单点防护永远不够。扫描阶段挡的是「注册进来的 skill 本身」，读取阶段挡的是「模型实际提出的路径参数」。二者保护的对象不一样。</p>
<p data-tool="mdnice编辑器">工程上要是只做第一层，你会被运行时路径拼接坑；只做第二层，你会把很多脏数据带进 registry，影响可观测性和调试。</p>
<p data-tool="mdnice编辑器">OpenClaw 在这块虽然不算复杂，但做法是标准的。</p>
<h1 data-tool="mdnice编辑器"><span class="content">6. Skills 的渐进式披露</span></h1>
<p data-tool="mdnice编辑器">OpenClaw 的 skills 机制，如果只看「枚举目录 -&gt; 注入 prompt -&gt; read 文件」，会有人觉得太朴素。真正值得注意的是，它和 memory 一样，贯彻了一套很明确的渐进式披露思路。</p>
<p data-tool="mdnice编辑器">这是上下文预算治理的逻辑。</p>
<p data-tool="mdnice编辑器">原则就两条：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>先给最小可用信息</section>
</li>
<li>
<section>确认需要后再扩大读取范围</section>
</li>
</ol>
<p data-tool="mdnice编辑器">在 OpenClaw 里，memory 和 skills 都是这么干的，只是路径不同。</p>
<h2 data-tool="mdnice编辑器"><span class="content">6.1 Memory 是先搜 snippet，再精读指定行段</span></h2>
<p data-tool="mdnice编辑器"><code>memory_search</code> 先返回短片段，带 path 和 line range，不是全文注入。底层还有 <code>SNIPPET_MAX_CHARS = 700</code> 的硬上限。如果后端预算紧，还会继续裁结果。</p>
<p data-tool="mdnice编辑器">之后再通过 <code>memory_get</code> 按 <code>path + from/lines</code> 去拉具体行段。</p>
<p data-tool="mdnice编辑器">这个流程是非常标准的 progressive disclosure：先定位，再精读。</p>
<h2 data-tool="mdnice编辑器"><span class="content">6.2 Skills 是先给目录，再只读一个 <code>SKILL.md</code></span></h2>
<p data-tool="mdnice编辑器">skills 这边没有向量召回，而是目录清单。模型先看 <code>&lt;available_skills&gt;</code>，只在明确匹配一个时才读正文，并且 upfront 最多一个。</p>
<p data-tool="mdnice编辑器">两条链路表面不同，本质一样：都在防止大块文本无脑灌进上下文。</p>
<p data-tool="mdnice编辑器">我为什么说这点值钱？因为很多团队做 Agent 系统时，最大的问题不是模型不会做事，而是上下文管理太粗糙。什么都想喂进去，最后 token 烧得飞快，模型还因为噪声太高做不准。</p>
<p data-tool="mdnice编辑器">OpenClaw 至少在架构层面承认了一件事实：<strong>上下文是预算，不是仓库。</strong></p>
<p data-tool="mdnice编辑器">这件事说起来简单，真正落实到工具语义、提示词规则、底层裁剪，很多系统做不到。</p>
<h1 data-tool="mdnice编辑器"><span class="content">7. OpenClaw 没有「独立 Skill 执行器」</span></h1>
<p data-tool="mdnice编辑器">在 OpenClaw 里，skill 读完之后，并没有一个单独的执行环境接管它。没有所谓「Skill Runtime」去解释 <code>SKILL.md</code>，也没有「新上下文执行 skill」这回事。它还是在同一个 <code>activeSession.prompt(...)</code> 的 tool-loop 里继续跑。</p>
<p data-tool="mdnice编辑器">链路大概是这样：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>system prompt 里给出 <code>&lt;available_skills&gt;</code> 和选择规则</section>
</li>
<li>
<section>模型决定某个 skill 匹配当前请求</section>
</li>
<li>
<section>模型调用 read 工具读取对应 <code>SKILL.md</code></section>
</li>
<li>
<section>toolResult 里带回 <code>SKILL.md</code> 文本</section>
</li>
<li>
<section>这个 toolResult 被追加到当前 session 的 messages</section>
</li>
<li>
<section>模型再次被调用，看到刚才读到的 skill 内容</section>
</li>
<li>
<section>模型按 skill 指导，继续发起后续 toolCall，比如 <code>exec</code>、<code>write</code>、<code>edit</code></section>
</li>
<li>
<section>直到输出最终回答</section>
</li>
</ol>
<p data-tool="mdnice编辑器">关键点：<strong>skill 内容只是同一消息流里多了一条 toolResult</strong>。</p>
<p data-tool="mdnice编辑器">这意味着什么？</p>
<p data-tool="mdnice编辑器">意味着 OpenClaw 的 skill 执行，本质上是「模型读了一份流程说明，然后继续在原对话里行动」。它没有新的边界，没有新的记忆隔离，没有新的权限域。真正的隔离，来自工具列表裁剪和文件读写沙箱，不来自上下文切换。</p>
<p data-tool="mdnice编辑器">这和 Claude Code 很不一样。</p>
<h1 data-tool="mdnice编辑器"><span class="content">8. Claude Code 的 skills，更像「可调用子程序」</span></h1>
<p data-tool="mdnice编辑器">Claude Code 的 skill 流程有几个特征：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>skill 是 tools 列表里的一个明确工具</section>
</li>
<li>
<section>模型会调用 Skill tool，而不是自己去 read 某个 markdown</section>
</li>
<li>
<section>skill prompt 的加载是 tool 内部动作</section>
</li>
<li>
<section>权限校验也主要发生在 tool 内</section>
</li>
<li>
<section>skill 可以在新上下文执行，执行完再把结果带回主对话</section>
</li>
</ul>
<p data-tool="mdnice编辑器">从工程抽象上看，Claude Code 的 skill 更像一个「子程序入口」。它有名字、有调用接口、有内部装载逻辑，甚至有上下文隔离能力。</p>
<p data-tool="mdnice编辑器">OpenClaw 没有走这条路。它把 skill 设计成「模型可读的文件」，由模型自己决定是否展开，并在原上下文里继续操作。</p>
<p data-tool="mdnice编辑器">这两个方向，没有谁天然高级，但适用面不同</p>
<p data-tool="mdnice编辑器">如果你要的是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>更强的执行隔离</section>
</li>
<li>
<section>更容易做权限封装</section>
</li>
<li>
<section>更容易做结果回传和子任务边界</section>
</li>
<li>
<section>更适合复杂、多步骤、可复用的任务单元</section>
</li>
</ul>
<p data-tool="mdnice编辑器">Claude Code 那种 tool 化 skill 更合适。</p>
<p data-tool="mdnice编辑器">如果你要的是：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>实现简单</section>
</li>
<li>
<section>skill 作者门槛低</section>
</li>
<li>
<section>把 skill 当 markdown 资产管理</section>
</li>
<li>
<section>快速把流程知识接进现有 ReAct tool-loop</section>
</li>
</ul>
<p data-tool="mdnice编辑器">OpenClaw 这种文件化 skill 更实用。</p>
<p data-tool="mdnice编辑器">工程上不看理念，看代价结构。</p>
<h1 data-tool="mdnice编辑器"><span class="content">9. OpenClaw 为什么会做成这样</span></h1>
<p data-tool="mdnice编辑器">我猜它的设计动机是：<strong>尽量复用现有 agent runtime，而不是为 skills 单独发明一层执行框架。</strong></p>
<p data-tool="mdnice编辑器">你看它的做法就知道了：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>discovery 复用文件系统扫描</section>
</li>
<li>
<section>selection 复用 system prompt</section>
</li>
<li>
<section>loading 复用 read 工具</section>
</li>
<li>
<section>execution 复用原有 tool-loop</section>
</li>
<li>
<section>safety 复用 sandbox path guard 和 tool policy</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这是一套很节制的架构思路。好处是：</p>
<p data-tool="mdnice编辑器"><strong>实现成本低</strong> ：不用造新协议，不用加新消息类型，不用多一层 runtime。对一个正在演进中的 agent 系统来说，这非常现实。 <strong>Skill 作者认知负担小</strong>：本质上写一个 <code>SKILL.md</code> 就行。对于组织内部推广，这是巨大优势。因为真正阻碍 skill 规模化的，从来不是模型能力，而是作者生态能不能起来。 <strong>运行链路可观测性还不错</strong>：所有事情都发生在同一 session 里。read 了什么、接着调了什么工具、toolResult 长什么样，回放起来相对直观。</p>
<p data-tool="mdnice编辑器">但它的代价也有。</p>
<p data-tool="mdnice编辑器"><strong>代价 1：skill 缺少强执行边界</strong></p>
<p data-tool="mdnice编辑器">skill 本身不是 tool，没有独立权限面。你没法像封装一个函数那样，给 skill 绑定专属 schema、专属校验、专属 side effect 边界。最终还是模型在拿到 skill 文本后，自由地调用后续工具。</p>
<p data-tool="mdnice编辑器">这就意味着 skill 的约束力主要来自提示词，不来自执行器。</p>
<p data-tool="mdnice编辑器"><strong>代价 2：组合式编排能力弱</strong></p>
<p data-tool="mdnice编辑器">system prompt 里要求 upfront 最多读一个 skill，这对预算控制很好，对复杂任务不友好。多 skill 协同在这个体系里不是一等公民。</p>
<p data-tool="mdnice编辑器"><strong>代价 3：skill 的结果不可天然封装</strong></p>
<p data-tool="mdnice编辑器">Claude Code 那种「在新上下文执行 skill，再返回结果」，天然有个输入输出边界。OpenClaw 这里没有。skill 一旦展开，后续动作直接混进主会话消息流。你很难把它当作一个独立执行单元来治理。</p>
<p data-tool="mdnice编辑器"><strong>代价 4：对模型的选择质量要求高</strong></p>
<p data-tool="mdnice编辑器">因为没有独立 selector，也没有 tool-level dispatcher，skill 能不能选对，主要靠 <code>&lt;available_skills&gt;</code> 的 descriptions 和 system prompt 规则。这对 skill 描述质量要求很高。写得不清楚，模型就选歪。数量一多，问题更明显。</p>
<h1 data-tool="mdnice编辑器"><span class="content">10. 权限控制</span></h1>
<p data-tool="mdnice编辑器">OpenClaw 的思路是「先裁剪能力，再让模型行动」</p>
<p data-tool="mdnice编辑器">这一点我很喜欢，因为它比「调用时再临时拦截」更稳。</p>
<p data-tool="mdnice编辑器">OpenClaw 的权限控制，不是把所有工具都亮给模型，然后在调用某个危险工具时说不行。它更像是：</p>
<ol data-tool="mdnice编辑器">
<li>
<section>先根据 policy pipeline 把不允许的工具从列表里移掉</section>
</li>
<li>
<section>再把剩下的工具暴露给模型</section>
</li>
<li>
<section>模型根本看不到被禁的能力</section>
</li>
</ol>
<p data-tool="mdnice编辑器">skills 这边也一样：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>不合格的 skill 不进入 <code>&lt;available_skills&gt;</code></section>
</li>
<li>
<section><code>disable-model-invocation</code> 的 skill 对模型不可见</section>
</li>
<li>
<section>Read 路径有 workspace root guard</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这种「先裁剪，再推理」的方式有个很大的好处：模型认知空间更干净。它不会围着一堆不可用能力打转，也不会生成大量被拒调用的无效动作。</p>
<p data-tool="mdnice编辑器">如果你做企业场景，这比事后 deny 好得多。因为用户只会看到 agent 做合理尝试，而不是不停撞墙。</p>
<p data-tool="mdnice编辑器">当然，它的代价是灵活性差一点。你没法在非常细粒度的时刻做动态放行，除非重新构建 tool list 或 skill list。但我认为这笔账是划算的。Agent 系统的第一优先级不是灵活，是可控。</p>
<h1 data-tool="mdnice编辑器"><span class="content">11. 从平台设计角度看呢</span></h1>
<p data-tool="mdnice编辑器">觉得比较好的点：</p>
<p data-tool="mdnice编辑器"><strong>第一，简单。</strong><br />
这个简单不是简陋，是尽量不新增系统概念。skill 就是文件，加载靠 read，执行靠原有 tool-loop。对演进中的 agent 框架来说，这是非常健康的选择。</p>
<p data-tool="mdnice编辑器"><strong>第二，预算意识强。</strong><br />
<code>&lt;available_skills&gt;</code> 目录注入、最多只读一个 skill、compact/truncated 降级，这些都说明它把 context 当稀缺资源处理。</p>
<p data-tool="mdnice编辑器"><strong>第三，权限思路对。</strong><br />
先过滤 skill 和 tool，再让模型行动。可见性先于可调用性，这很工程化。</p>
<p data-tool="mdnice编辑器"><strong>第四，适合知识流程化。</strong><br />
很多团队真正需要的，不是一个会自己发明流程的 agent，而是把组织内已有 SOP 结构化地喂给模型。OpenClaw 这套很适合干这个。</p>
<p data-tool="mdnice编辑器">一些局限：</p>
<p data-tool="mdnice编辑器"><strong>第一，规模扩展性一般。</strong><br />
catalog 注入机制天然不适合 skill 数量无限增长。它适合几十级别，不适合大规模 marketplace。</p>
<p data-tool="mdnice编辑器"><strong>第二，组合能力弱。</strong><br />
「最多只读一个 skill」对稳态有帮助，对复杂任务编排是限制。</p>
<p data-tool="mdnice编辑器"><strong>第三，skill 缺少运行时身份。</strong><br />
它不是 tool，没有明确的输入输出边界，也没有独立权限与审计单元。后续做深治理会比较难。</p>
<p data-tool="mdnice编辑器"><strong>第四，过度依赖模型选择。</strong><br />
一旦 descriptions 写得不好，或者目录过大，skill 选择质量会变成系统上限。</p>
<h1 data-tool="mdnice编辑器"><span class="content">12. 适用场景</span></h1>
<p data-tool="mdnice编辑器">在系统架构选型时，如果有以下的条件，可以优先选择 OpenClaw 的设计逻辑：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>团队已经有一套成熟的 tool-loop，想低成本引入 skills</section>
</li>
<li>
<section>主要目标是把流程知识、操作手册、领域步骤注入 agent</section>
</li>
<li>
<section>skill 作者很多，技术水平参差不齐，需要低门槛 markdown 入口</section>
</li>
<li>
<section>更看重实现速度和治理可落地，而不是复杂编排能力</section>
</li>
<li>
<section>skill 数量在可控范围内，不会迅速膨胀到几百个</section>
</li>
</ul>
<p data-tool="mdnice编辑器">有如下的情况时，就不太适用使用这种方案：</p>
<ul data-tool="mdnice编辑器">
<li>
<section>skill 本质上是可执行业务子任务，需要独立生命周期</section>
</li>
<li>
<section>需要强隔离执行、单独审计、结果回传和失败恢复</section>
</li>
<li>
<section>任务天然需要多个 skill 协同编排</section>
</li>
<li>
<section>技能库规模会非常大，必须做复杂路由</section>
</li>
<li>
<section>权限模型要求细到「某个 skill 可以做 A 但不能做 B」</section>
</li>
</ul>
<p data-tool="mdnice编辑器">这些场景下，我更倾向 Claude Code 那种 tool 化 skill，或者更进一步，直接走 subagent / workflow node / task runtime。</p>
<h1 data-tool="mdnice编辑器"><span class="content">13. 小结</span></h1>
<p data-tool="mdnice编辑器">如果只用一句话概括两者区别：</p>
<p data-tool="mdnice编辑器"><strong>Claude Code 的 skill 更像可调用子程序，OpenClaw 的 skill 更像按需展开的运行手册。</strong></p>
<p data-tool="mdnice编辑器">前者强调执行单元，后者强调上下文注入。<br />
前者天然适合隔离和编排，后者天然适合轻量接入和流程沉淀。<br />
前者的复杂度在 runtime，后者的复杂度在 prompt 和内容治理。</p>
<p data-tool="mdnice编辑器">OpenClaw 用极其精简的 Prompt 规则和现成的 Read 工具，低成本实现了 Agent Skills 标准。它够用，但也把长文本管理的压力，原封不动地推给了底层大模型的 Context Window。</p>
<p data-tool="mdnice编辑器">我个人对 OpenClaw 这套实现是认可的，前提是别把它想象成一个万能技能平台。它解决的是「如何在不重写 agent runtime 的前提下，把结构化流程知识接进模型执行链路」这个问题。这个问题它解得挺干净。</p>
<p data-tool="mdnice编辑器">但如果你要的是 Claude Code 那种「新上下文执行 skill、像子程序一样调用、跑完把结果带回来」，你该找的是子会话、subagent、任务编排层，而不是继续往 <code>SKILL.md</code> 上堆规则。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2026/03/openclaw-skilss-claude-code/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Agent 核心策略：Manus、Gemini CLI 和 Claude Code 的上下文压缩策略和细节</title>
		<link>https://www.phppan.com/2025/09/agent-core-strategy-context-compression-strategy-and-details-for-manus-gemini-cli-and-claude-code/</link>
		<comments>https://www.phppan.com/2025/09/agent-core-strategy-context-compression-strategy-and-details-for-manus-gemini-cli-and-claude-code/#comments</comments>
		<pubDate>Sat, 06 Sep 2025 14:31:40 +0000</pubDate>
		<dc:creator><![CDATA[admin]]></dc:creator>
				<category><![CDATA[架构和远方]]></category>
		<category><![CDATA[AIAgent]]></category>
		<category><![CDATA[Claude Code]]></category>
		<category><![CDATA[Gemini CLI]]></category>
		<category><![CDATA[Manus]]></category>

		<guid isPermaLink="false">https://www.phppan.com/?p=2415</guid>
		<description><![CDATA[做 AI Agent 开发的都需要考虑上下文爆炸的问题，不仅仅是成本问题，还有性能问题。 许多团队选择了压缩策 [&#8230;]]]></description>
				<content:encoded><![CDATA[<section id="nice" style="color: #000000;" data-tool="mdnice编辑器" data-website="https://www.mdnice.com">
<p data-tool="mdnice编辑器">做 AI Agent 开发的都需要考虑上下文爆炸的问题，不仅仅是成本问题，还有性能问题。</p>
<p data-tool="mdnice编辑器">许多团队选择了压缩策略。但过度激进的压缩不可避免地导致信息丢失。</p>
<p data-tool="mdnice编辑器">这背后的根本矛盾在于：<strong style="color: #0e88eb;">Agent 需要基于完整的历史状态来决策下一步行动，但我们无法预知当前的某个观察细节是否会在未来的某个关键时刻变得至关重要。</strong></p>
<p data-tool="mdnice编辑器">前面一篇文章讲了整个上下文的管理策略，今天着重聊一下上下文管理的压缩策略和细节。</p>
<p data-tool="mdnice编辑器">下面根据 Manus、Gemini CLI 和 Claude Code 这三个项目的源码来聊一下上下文的压缩。</p>
<p data-tool="mdnice编辑器">三个产品，有三个不同的选择，或者说设计哲学。</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">Manus 的选择是：永不丢失。</strong> 他们认为，从逻辑角度看，任何不可逆的压缩都带有风险。所以他们选择了一条看似&#8221;笨拙&#8221;但实际上很聪明的路：把文件系统当作&#8221;终极上下文&#8221;。</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">Claude Code 的选择是：极限压榨。</strong> 92% 的压缩触发阈值，这个数字相当激进。他们想要榨干上下文窗口的每一个 token，直到最后一刻才开始压缩。</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">Gemini CLI 的选择是：稳健保守。</strong> 70% 就开始压缩，宁可频繁一点，也要保证系统的稳定性。</p>
<p data-tool="mdnice编辑器">这三种选择没有对错，只是适用场景不同。接下来我们逐个分析。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">1. Manus</span></h1>
<p data-tool="mdnice编辑器">Manus 的压缩最让我印象深刻的是其在可恢复性上的策略。</p>
<p data-tool="mdnice编辑器">Manus 团队认为：任何不可逆的压缩都带有风险。你永远不知道现在丢掉的信息是不是未来解决问题的关键。</p>
<p data-tool="mdnice编辑器">他们选择了不做真正的删除，而是将其外部化存储。</p>
<p data-tool="mdnice编辑器">传统做法是把所有观察结果都塞进上下文里。比如让 Agent 读一个网页，它会把整个 HTML 内容都存下来，可能就是上万个 token。Manus 不这么干。</p>
<p data-tool="mdnice编辑器">当 Agent 访问网页时，Manus 只在上下文中保留 URL 和简短的描述，完整的网页内容并不保存。需要重新查看网页内容时，通过保留的 URL 重新获取即可。这就像是你在笔记本上记录&#8221;参见第 23 页的图表&#8221;，而不是把整个图表重新画一遍。</p>
<p data-tool="mdnice编辑器">文档处理也是同样的逻辑。一个 100 页的 PDF 文档，Manus 不会把全部内容放进上下文，而是只记录文档路径、页数、最后访问的位置等元信息。当 Agent 需要查看具体内容时，再通过文件路径读取相应的页面。</p>
<p data-tool="mdnice编辑器">Manus 把文件系统视为&#8221;终极上下文&#8221;——一个容量几乎无限、天然持久化、Agent 可以直接操作的外部记忆系统。</p>
<p data-tool="mdnice编辑器">文件系统的层级结构天然适合组织信息。Agent 可以创建不同的目录来分类存储不同类型的信息：项目背景放一个文件夹，技术细节放另一个文件夹，错误日志单独存放。需要时按图索骥，而不是在一个巨大的上下文中大海捞针。</p>
<p data-tool="mdnice编辑器">这不是简单的存储。Manus 训练模型学会主动使用文件系统来管理自己的「记忆」。当发现重要信息时，Agent 会主动将其写入特定的文件中，而不是试图把所有东西都记在上下文里。就像一个经验丰富的研究员，知道什么该记在脑子里，什么该写在笔记本上，什么该归档保存。</p>
<p data-tool="mdnice编辑器">在 Manus 团队对外的文章开头，指出了为什么必须要有压缩策略：</p>
<p data-tool="mdnice编辑器">第一，<strong style="color: #0e88eb;">观察结果可能非常庞大</strong>。与网页或 PDF 等非结构化数据交互时，一次观察就可能产生数万个 token。如果不压缩，可能一两次操作就把上下文占满了。</p>
<p data-tool="mdnice编辑器">第二，<strong style="color: #0e88eb;">模型性能会下降</strong>。这是个很多人忽视的问题。即使模型声称支持 200k 的上下文窗口，但实际使用中，超过一定长度后，模型的注意力机制效率会显著下降，响应质量也会变差。这就像人的工作记忆一样，信息太多反而会降低处理效率。</p>
<p data-tool="mdnice编辑器">第三，<strong style="color: #0e88eb;">成本考虑</strong>。长输入意味着高成本，即使使用了前缀缓存等优化技术，成本依然可观。特别是在需要大量交互的场景下，成本会快速累积。</p>
<p data-tool="mdnice编辑器">在具体实现过程中，可恢复压缩有几个关键点：</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">保留最小必要信息</strong>。对于每个外部资源，只保留能够重新获取它的最小信息集。网页保留 URL，文档保留路径，API 响应保留请求参数。这些信息占用的空间极小，但足以在需要时恢复完整内容。</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">智能的重新加载时机</strong>。不是每次提到某个资源就重新加载，而是根据上下文判断是否真的需要详细内容。如果只是确认文件存在，就不需要读取内容；如果要分析具体细节，才触发加载。</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">缓存机制</strong>。虽然内容不在上下文中，但 Manus 会在本地维护一个缓存。最近访问过的资源会暂时保留，避免频繁的重复加载。这个缓存是独立于上下文的，不占用宝贵的 token 额度。</p>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2. Claude Code</span></h1>
<p data-tool="mdnice编辑器">Claude Code 的策略完全是另一个极端——他们要把上下文用到极致。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2.1 92% 的阈值</span></h2>
<p data-tool="mdnice编辑器">这个数字有一些讲究。留 8% 的缓冲区既保证了压缩过程有足够的时间完成，又避免了频繁触发压缩带来的性能开销。更重要的是，这个缓冲区给了系统一个「反悔」的机会——如果压缩质量不达标，还有空间执行降级策略。</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">const</span> COMPRESSION_CONFIG = {
  <span class="hljs-attr" style="color: #d19a66;">threshold</span>: <span class="hljs-number" style="color: #d19a66;">0.92</span>,           <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 92%阈值触发</span>
  <span class="hljs-attr" style="color: #d19a66;">triggerVariable</span>: <span class="hljs-string" style="color: #98c379;">"h11"</span>,    <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// h11 = 0.92</span>
  <span class="hljs-attr" style="color: #d19a66;">compressionModel</span>: <span class="hljs-string" style="color: #98c379;">"J7()"</span>,  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 专用压缩模型</span>
  <span class="hljs-attr" style="color: #d19a66;">preserveStructure</span>: <span class="hljs-literal" style="color: #56b6c2;">true</span>    <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 保持8段结构</span>
};
</code></pre>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2.2 八段式结构化摘要</span></h2>
<p data-tool="mdnice编辑器">Claude Code 的压缩不是简单的截断或摘要，而是八段式结构。这个结构我们可以学习一下：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">const</span> COMPRESSION_SECTIONS = [
  <span class="hljs-string" style="color: #98c379;">"1. Primary Request and Intent"</span>,    <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 主要请求和意图</span>
  <span class="hljs-string" style="color: #98c379;">"2. Key Technical Concepts"</span>,        <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 关键技术概念</span>
  <span class="hljs-string" style="color: #98c379;">"3. Files and Code Sections"</span>,       <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 文件和代码段</span>
  <span class="hljs-string" style="color: #98c379;">"4. Errors and fixes"</span>,              <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 错误和修复</span>
  <span class="hljs-string" style="color: #98c379;">"5. Problem Solving"</span>,               <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 问题解决</span>
  <span class="hljs-string" style="color: #98c379;">"6. All user messages"</span>,             <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 所有用户消息</span>
  <span class="hljs-string" style="color: #98c379;">"7. Pending Tasks"</span>,                 <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 待处理任务</span>
  <span class="hljs-string" style="color: #98c379;">"8. Current Work"</span>                   <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 当前工作</span>
];
</code></pre>
<p data-tool="mdnice编辑器">每一段都有明确的目的和优先级。「主要请求和意图」确保 Agent 永远不会忘记用户最初想要什么；「关键技术概念」保留重要的技术决策和约束条件；「错误和修复」避免重复踩坑；「所有用户消息」则保证用户的原始表达不会丢失。</p>
<p data-tool="mdnice编辑器">这种结构的好处是，即使经过多次压缩，Agent 仍然能保持工作的连贯性。关键信息都在，只是细节被逐步抽象化了。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2.3 专用压缩模型 J7</span></h2>
<p data-tool="mdnice编辑器">Claude Code 使用了一个专门的压缩模型 J7 来处理上下文压缩。这不是主模型，而是一个专门优化过的模型，它的任务就是理解长对话并生成高质量的结构化摘要。</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">async</span> <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">function</span> <span class="hljs-title" style="color: #61aeee;">contextCompression</span>(<span class="hljs-params">currentContext</span>) </span>{
  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 检查压缩条件</span>
  <span class="hljs-keyword" style="color: #c678dd;">if</span> (currentContext.tokenRatio &lt; h11) {
    <span class="hljs-keyword" style="color: #c678dd;">return</span> currentContext;  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 无需压缩</span>
  }
  
  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 调用专用压缩模型</span>
  <span class="hljs-keyword" style="color: #c678dd;">const</span> compressionPrompt = <span class="hljs-keyword" style="color: #c678dd;">await</span> AU2.generatePrompt(currentContext);
  <span class="hljs-keyword" style="color: #c678dd;">const</span> compressedSummary = <span class="hljs-keyword" style="color: #c678dd;">await</span> J7(compressionPrompt);
  
  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 构建新的上下文</span>
  <span class="hljs-keyword" style="color: #c678dd;">const</span> newContext = {
    <span class="hljs-attr" style="color: #d19a66;">summary</span>: compressedSummary,
    <span class="hljs-attr" style="color: #d19a66;">recentMessages</span>: currentContext.recent(<span class="hljs-number" style="color: #d19a66;">5</span>),  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 保留最近5条</span>
    <span class="hljs-attr" style="color: #d19a66;">currentTask</span>: currentContext.activeTask
  };
  
  <span class="hljs-keyword" style="color: #c678dd;">return</span> newContext;
}
</code></pre>
<p data-tool="mdnice编辑器">AU2 负责生成压缩提示词，它会分析当前上下文，提取关键信息，然后构造一个结构化的提示词给 J7。J7 处理后返回符合八段式结构的压缩摘要。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2.4 上下文生命周期管理</span></h2>
<p data-tool="mdnice编辑器">Claude Code 把上下文当作有生命周期的实体来管理。这个设计理念很先进——上下文不是静态的数据，而是动态演化的有机体。</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-class"><span class="hljs-keyword" style="color: #c678dd;">class</span> <span class="hljs-title" style="color: #e6c07b;">ContextManager</span> </span>{
  <span class="hljs-keyword" style="color: #c678dd;">constructor</span>() {
    <span class="hljs-keyword" style="color: #c678dd;">this</span>.compressionThreshold = <span class="hljs-number" style="color: #d19a66;">0.92</span>;  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// h11 = 0.92</span>
    <span class="hljs-keyword" style="color: #c678dd;">this</span>.compressionModel = <span class="hljs-string" style="color: #98c379;">"J7"</span>;      <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 专用模型</span>
  }
  
  <span class="hljs-keyword" style="color: #c678dd;">async</span> manageContext(currentContext, newInput) {
    <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 1. 上下文更新</span>
    <span class="hljs-keyword" style="color: #c678dd;">const</span> updatedContext = <span class="hljs-keyword" style="color: #c678dd;">this</span>.appendToContext(currentContext, newInput);
    
    <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 2. 令牌使用量检查</span>
    <span class="hljs-keyword" style="color: #c678dd;">const</span> tokenUsage = <span class="hljs-keyword" style="color: #c678dd;">await</span> <span class="hljs-keyword" style="color: #c678dd;">this</span>.calculateTokenUsage(updatedContext);
    
    <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 3. 压缩触发判断</span>
    <span class="hljs-keyword" style="color: #c678dd;">if</span> (tokenUsage.ratio &gt;= <span class="hljs-keyword" style="color: #c678dd;">this</span>.compressionThreshold) {
      <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 4. 八段式压缩执行</span>
      <span class="hljs-keyword" style="color: #c678dd;">const</span> compressionPrompt = <span class="hljs-keyword" style="color: #c678dd;">await</span> AU2.generateCompressionPrompt(updatedContext);
      <span class="hljs-keyword" style="color: #c678dd;">const</span> compressedSummary = <span class="hljs-keyword" style="color: #c678dd;">await</span> <span class="hljs-keyword" style="color: #c678dd;">this</span>.compressionModel.generate(compressionPrompt);
      
      <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 5. 新上下文构建</span>
      <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-keyword" style="color: #c678dd;">this</span>.buildCompressedContext(compressedSummary, updatedContext);
    }
    
    <span class="hljs-keyword" style="color: #c678dd;">return</span> updatedContext;
  }
}
</code></pre>
<p data-tool="mdnice编辑器">整个流程是自动化的。每次有新的输入，系统都会评估是否需要压缩。压缩不是一次性的动作，而是持续的过程。随着对话的进行，早期的详细内容会逐渐被抽象化，但关键信息始终保留。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2.5 优雅降级机制</span></h2>
<p data-tool="mdnice编辑器">当压缩失败时，系统不会死板地报错或者强行应用低质量的压缩结果，而是有一整套 Plan B、Plan C。这种&#8221;永不放弃&#8221;的设计理念，让系统在各种极端情况下都能稳定运行。</p>
<p data-tool="mdnice编辑器">降级策略包括：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">自适应重压缩</strong>：如果首次压缩质量不佳，会调整参数重试</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">混合模式保留</strong>：压缩旧内容，但完整保留最近的交互</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">保守截断</strong>：最坏情况下，至少保证系统能继续运行</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2.6 压缩后的信息恢复</span></h2>
<p data-tool="mdnice编辑器">虽然 Claude Code 的压缩是有损的，但它通过巧妙的设计最小化了信息损失的影响。压缩后的八段式摘要不是简单的文本，而是结构化的信息，包含了足够的上下文让 Agent 能够理解之前发生了什么，需要做什么。</p>
<p data-tool="mdnice编辑器">特别值得一提的是第 6 段&#8221;All user messages&#8221;。即使其他内容被压缩了，用户的所有消息都会以某种形式保留。这确保了用户的意图和需求不会在压缩过程中丢失。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">2.7 实践指南</span></h2>
<p data-tool="mdnice编辑器">Claude Code 在实践中还有一些最佳实践：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">定期使用 <code style="color: #0e8aeb;">/compact</code> 命令压缩长对话</strong>：用户可以主动触发压缩，不必等到自动触发</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">在上下文警告出现时及时处理</strong>：系统会在接近阈值时发出警告，用户应该及时响应</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">通过 <code style="color: #0e8aeb;">Claude.md</code> 文件保存重要信息</strong>：将关键信息外部化，减少上下文消耗</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3. Gemini CLI</span></h1>
<p data-tool="mdnice编辑器">Gemini CLI 选择了一条中庸之道，或者说是实用之道。Gemini CLI 项目开源了，这部分的说明会多一些。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3.1 70/30？</span></h2>
<p data-tool="mdnice编辑器">Gemini CLI 选择了 70% 作为压缩触发点，30% 作为保留比例。这个比例我们也可以参考学习一下：</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">为什么是 70% 而不是 92%</strong></p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">更早介入，避免紧急压缩导致的卡顿</section>
</li>
<li>
<section style="color: #010101;">给压缩过程留出充足的缓冲空间</section>
</li>
<li>
<section style="color: #010101;">适合轻量级应用场景，不追求极限性能</section>
</li>
</ul>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">30% 保留的合理性：</strong></p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;">刚好覆盖最近 5-10 轮对话</section>
</li>
<li>
<section style="color: #010101;">足够维持上下文连续性</section>
</li>
<li>
<section style="color: #010101;">不会让用户感觉&#8221;突然失忆&#8221;</section>
</li>
</ul>
<p data-tool="mdnice编辑器">共背后的逻辑是：宁可频繁一点地压缩，也要保证每次压缩都是从容的、高质量的。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3.2 精选历史提取</span></h2>
<p data-tool="mdnice编辑器">Gemini CLI 有个独特的概念叫精选历史&#8221;。不是所有的历史都值得保留，系统会智能地筛选有效内容：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">function</span> <span class="hljs-title" style="color: #61aeee;">extractCuratedHistory</span>(<span class="hljs-params">comprehensiveHistory: Content[]</span>): <span class="hljs-title" style="color: #61aeee;">Content</span>[] </span>{
  <span class="hljs-keyword" style="color: #c678dd;">if</span> (comprehensiveHistory === <span class="hljs-literal" style="color: #56b6c2;">undefined</span> || comprehensiveHistory.length === <span class="hljs-number" style="color: #d19a66;">0</span>) {
    <span class="hljs-keyword" style="color: #c678dd;">return</span> [];
  }
  <span class="hljs-keyword" style="color: #c678dd;">const</span> curatedHistory: Content[] = [];
  <span class="hljs-keyword" style="color: #c678dd;">const</span> length = comprehensiveHistory.length;
  <span class="hljs-keyword" style="color: #c678dd;">let</span> i = <span class="hljs-number" style="color: #d19a66;">0</span>;
  <span class="hljs-keyword" style="color: #c678dd;">while</span> (i &lt; length) {
    <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 用户轮次直接保留</span>
    <span class="hljs-keyword" style="color: #c678dd;">if</span> (comprehensiveHistory[i].role === <span class="hljs-string" style="color: #98c379;">'user'</span>) {
      curatedHistory.push(comprehensiveHistory[i]);
      i++;
    } <span class="hljs-keyword" style="color: #c678dd;">else</span> {
      <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 处理模型轮次</span>
      <span class="hljs-keyword" style="color: #c678dd;">const</span> modelOutput: Content[] = [];
      <span class="hljs-keyword" style="color: #c678dd;">let</span> isValid = <span class="hljs-literal" style="color: #56b6c2;">true</span>;
      <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 收集连续的模型轮次</span>
      <span class="hljs-keyword" style="color: #c678dd;">while</span> (i &lt; length &amp;&amp; comprehensiveHistory[i].role === <span class="hljs-string" style="color: #98c379;">'model'</span>) {
        modelOutput.push(comprehensiveHistory[i]);
        <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 检查内容有效性</span>
        <span class="hljs-keyword" style="color: #c678dd;">if</span> (isValid &amp;&amp; !isValidContent(comprehensiveHistory[i])) {
          isValid = <span class="hljs-literal" style="color: #56b6c2;">false</span>;
        }
        i++;
      }
      <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 只有当所有模型轮次都有效时才保留</span>
      <span class="hljs-keyword" style="color: #c678dd;">if</span> (isValid) {
        curatedHistory.push(...modelOutput);
      }
    }
  }
  <span class="hljs-keyword" style="color: #c678dd;">return</span> curatedHistory;
}
</code></pre>
<p data-tool="mdnice编辑器">这个策略的巧妙之处在于：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">用户输入全部保留</strong>：所有用户输入都被视为重要信息，无条件保留</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">模型轮次有条件保留</strong>：连续的模型轮次被视为一个整体进行评估</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">全有或全无的处理</strong>：要么全部保留，要么全部丢弃，避免了复杂的部分保留逻辑</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3.3 内容有效性判断</span></h2>
<p data-tool="mdnice编辑器">什么样的内容会被认为是无效的？Gemini CLI 有明确的标准：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">function</span> <span class="hljs-title" style="color: #61aeee;">isValidContent</span>(<span class="hljs-params">content: Content</span>): <span class="hljs-title" style="color: #61aeee;">boolean</span> </span>{
  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 检查 parts 数组是否存在且非空</span>
  <span class="hljs-keyword" style="color: #c678dd;">if</span> (content.parts === <span class="hljs-literal" style="color: #56b6c2;">undefined</span> || content.parts.length === <span class="hljs-number" style="color: #d19a66;">0</span>) {
    <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-literal" style="color: #56b6c2;">false</span>;
  }
  <span class="hljs-keyword" style="color: #c678dd;">for</span> (<span class="hljs-keyword" style="color: #c678dd;">const</span> part of content.parts) {
    <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 检查 part 是否为空</span>
    <span class="hljs-keyword" style="color: #c678dd;">if</span> (part === <span class="hljs-literal" style="color: #56b6c2;">undefined</span> || <span class="hljs-built_in" style="color: #e6c07b;">Object</span>.keys(part).length === <span class="hljs-number" style="color: #d19a66;">0</span>) {
      <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-literal" style="color: #56b6c2;">false</span>;
    }
    <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 检查非思考类型的 part 是否有空文本</span>
    <span class="hljs-keyword" style="color: #c678dd;">if</span> (!part.thought &amp;&amp; part.text !== <span class="hljs-literal" style="color: #56b6c2;">undefined</span> &amp;&amp; part.text === <span class="hljs-string" style="color: #98c379;">''</span>) {
      <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-literal" style="color: #56b6c2;">false</span>;
    }
  }
  <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-literal" style="color: #56b6c2;">true</span>;
}
</code></pre>
<p data-tool="mdnice编辑器">无效内容包括：空响应、错误输出、中断的流式响应等。这种预过滤机制确保进入压缩流程的都是高质量的内容。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3.4 五段式结构化摘要</span></h2>
<p data-tool="mdnice编辑器">相比 Claude Code 的八段式，Gemini CLI 的五段式更简洁，但涵盖了所有关键信息：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;">1. overall_goal - 用户的主要目标
2. key_knowledge - 重要技术知识和决策
3. file_system_state - 文件系统当前状态
4. recent_actions - 最近执行的重要操作
5. current_plan - 当前执行计划
</code></pre>
<p data-tool="mdnice编辑器">压缩时，系统会生成 XML 格式的结构化摘要。这种格式的好处是结构清晰，LLM 容易理解和生成，同时也便于后续的解析和处理。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3.5 基于 Token 的智能压缩</span></h2>
<p data-tool="mdnice编辑器">Gemini CLI 的压缩不是简单的定时触发，而是基于精确的 token 计算：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">async</span> tryCompressChat(
  prompt_id: <span class="hljs-built_in" style="color: #e6c07b;">string</span>,
  force: <span class="hljs-built_in" style="color: #e6c07b;">boolean</span> = <span class="hljs-literal" style="color: #56b6c2;">false</span>,
): <span class="hljs-built_in" style="color: #e6c07b;">Promise</span>&lt;ChatCompressionInfo | <span class="hljs-literal" style="color: #56b6c2;">null</span>&gt; {
  <span class="hljs-keyword" style="color: #c678dd;">const</span> curatedHistory = <span class="hljs-keyword" style="color: #c678dd;">this</span>.getChat().getHistory(<span class="hljs-literal" style="color: #56b6c2;">true</span>);

  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 空历史不压缩</span>
  <span class="hljs-keyword" style="color: #c678dd;">if</span> (curatedHistory.length === <span class="hljs-number" style="color: #d19a66;">0</span>) {
    <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-literal" style="color: #56b6c2;">null</span>;
  }

  <span class="hljs-keyword" style="color: #c678dd;">const</span> model = <span class="hljs-keyword" style="color: #c678dd;">this</span>.config.getModel();

  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 计算当前历史的 token 数量</span>
  <span class="hljs-keyword" style="color: #c678dd;">const</span> { totalTokens: originalTokenCount } =
    <span class="hljs-keyword" style="color: #c678dd;">await</span> <span class="hljs-keyword" style="color: #c678dd;">this</span>.getContentGenerator().countTokens({
      model,
      contents: curatedHistory,
    });

  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 获取压缩阈值配置</span>
  <span class="hljs-keyword" style="color: #c678dd;">const</span> contextPercentageThreshold =
    <span class="hljs-keyword" style="color: #c678dd;">this</span>.config.getChatCompression()?.contextPercentageThreshold;

  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 如果未强制压缩且 token 数量低于阈值，则不压缩</span>
  <span class="hljs-keyword" style="color: #c678dd;">if</span> (!force) {
    <span class="hljs-keyword" style="color: #c678dd;">const</span> threshold =
      contextPercentageThreshold ?? COMPRESSION_TOKEN_THRESHOLD; <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 默认 0.7</span>
    <span class="hljs-keyword" style="color: #c678dd;">if</span> (originalTokenCount &lt; threshold * tokenLimit(model)) {
      <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-literal" style="color: #56b6c2;">null</span>;
    }
  }

  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 计算压缩点，保留最后 30% 的历史</span>
  <span class="hljs-keyword" style="color: #c678dd;">let</span> compressBeforeIndex = findIndexAfterFraction(
    curatedHistory,
    <span class="hljs-number" style="color: #d19a66;">1</span> - COMPRESSION_PRESERVE_THRESHOLD, <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// COMPRESSION_PRESERVE_THRESHOLD = 0.3</span>
  );

  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 确保压缩点在用户轮次开始处</span>
  <span class="hljs-keyword" style="color: #c678dd;">while</span> (
    compressBeforeIndex &lt; curatedHistory.length &amp;&amp;
    (curatedHistory[compressBeforeIndex]?.role === <span class="hljs-string" style="color: #98c379;">'model'</span> ||
      isFunctionResponse(curatedHistory[compressBeforeIndex]))
  ) {
    compressBeforeIndex++;
  }

  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 分割历史为需要压缩和需要保留的部分</span>
  <span class="hljs-keyword" style="color: #c678dd;">const</span> historyToCompress = curatedHistory.slice(<span class="hljs-number" style="color: #d19a66;">0</span>, compressBeforeIndex);
  <span class="hljs-keyword" style="color: #c678dd;">const</span> historyToKeep = curatedHistory.slice(compressBeforeIndex);

  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 使用 LLM 生成历史摘要</span>
  <span class="hljs-keyword" style="color: #c678dd;">this</span>.getChat().setHistory(historyToCompress);
  <span class="hljs-keyword" style="color: #c678dd;">const</span> { text: summary } = <span class="hljs-keyword" style="color: #c678dd;">await</span> <span class="hljs-keyword" style="color: #c678dd;">this</span>.getChat().sendMessage(
    {
      message: {
        text: <span class="hljs-string" style="color: #98c379;">'First, reason in your scratchpad. Then, generate the &lt;state_snapshot&gt;.'</span>,
      },
      config: {
        systemInstruction: { text: getCompressionPrompt() },
      },
    },
    prompt_id,
  );

  <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 创建新的聊天历史，包含摘要和保留的部分</span>
  <span class="hljs-keyword" style="color: #c678dd;">this</span>.chat = <span class="hljs-keyword" style="color: #c678dd;">await</span> <span class="hljs-keyword" style="color: #c678dd;">this</span>.startChat([
    {
      role: <span class="hljs-string" style="color: #98c379;">'user'</span>,
      parts: [{ text: summary }],
    },
    {
      role: <span class="hljs-string" style="color: #98c379;">'model'</span>,
      parts: [{ text: <span class="hljs-string" style="color: #98c379;">'Got it. Thanks for the additional context!'</span> }],
    },
    ...historyToKeep,
  ]);
}
</code></pre>
<p data-tool="mdnice编辑器">这个实现有几个细节值得注意：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">支持强制压缩</strong>：通过 force 参数，用户可以主动触发压缩</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">智能分割点选择</strong>：确保压缩点在用户轮次开始，避免打断对话逻辑</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">两阶段压缩</strong>：先生成摘要，再重建对话历史</section>
</li>
</ul>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3.6 多层压缩机制</span></h2>
<p data-tool="mdnice编辑器">Gemini CLI 的压缩是分层进行的，每一层都有特定的目标：</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">第一层：内容过滤</strong>：过滤掉无效内容、thought 类型的部分，确保进入下一层的都是有价值的信息。</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">第二层：内容整合</strong>：合并相邻的同类内容，比如连续的纯文本 Part 会被合并成一个，减少结构冗余。</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">第三层：智能摘要</strong>：当 token 使用量超过阈值时，触发 LLM 生成结构化摘要。</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">第四层：保护机制</strong>：确保关键信息不被压缩丢失，比如用户的最新指令、正在进行的任务等。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3.7 模型适配的 Token 限制</span></h2>
<p data-tool="mdnice编辑器">不同的模型有不同的 token 限制，Gemini CLI 对此有精细的适配：</p>
<pre class="custom" data-tool="mdnice编辑器"><code class="hljs" style="color: #abb2bf;"><span class="hljs-keyword" style="color: #c678dd;">export</span> <span class="hljs-function"><span class="hljs-keyword" style="color: #c678dd;">function</span> <span class="hljs-title" style="color: #61aeee;">tokenLimit</span>(<span class="hljs-params">model: Model</span>): <span class="hljs-title" style="color: #61aeee;">TokenCount</span> </span>{
  <span class="hljs-keyword" style="color: #c678dd;">switch</span> (model) {
    <span class="hljs-keyword" style="color: #c678dd;">case</span> <span class="hljs-string" style="color: #98c379;">'gemini-1.5-pro'</span>:
      <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-number" style="color: #d19a66;">2</span>_097_152;
    <span class="hljs-keyword" style="color: #c678dd;">case</span> <span class="hljs-string" style="color: #98c379;">'gemini-1.5-flash'</span>:
    <span class="hljs-keyword" style="color: #c678dd;">case</span> <span class="hljs-string" style="color: #98c379;">'gemini-2.5-pro'</span>:
    <span class="hljs-keyword" style="color: #c678dd;">case</span> <span class="hljs-string" style="color: #98c379;">'gemini-2.5-flash'</span>:
    <span class="hljs-keyword" style="color: #c678dd;">case</span> <span class="hljs-string" style="color: #98c379;">'gemini-2.0-flash'</span>:
      <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-number" style="color: #d19a66;">1</span>_048_576;
    <span class="hljs-keyword" style="color: #c678dd;">case</span> <span class="hljs-string" style="color: #98c379;">'gemini-2.0-flash-preview-image-generation'</span>:
      <span class="hljs-keyword" style="color: #c678dd;">return</span> <span class="hljs-number" style="color: #d19a66;">32</span>_000;
    <span class="hljs-keyword" style="color: #c678dd;">default</span>:
      <span class="hljs-keyword" style="color: #c678dd;">return</span> DEFAULT_TOKEN_LIMIT; <span class="hljs-comment" style="font-style: italic; color: #5c6370;">// 1_048_576</span>
  }
}
</code></pre>
<p data-tool="mdnice编辑器">系统会根据使用的模型自动调整压缩策略。对于支持超长上下文的模型（如 gemini-1.5-pro 的 200 万 token），可以更宽松；对于受限的模型，会更积极地压缩。</p>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3.8 历史记录的精细处理</span></h2>
<p data-tool="mdnice编辑器"><code style="color: #0e8aeb;">recordHistory</code> 方法负责记录和处理历史，实施了多个优化策略：</p>
<ol data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">避免重复</strong>：不会重复添加相同的用户输入</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">过滤思考过程</strong>：thought 类型的 Part 会被过滤掉，不进入最终历史</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">合并优化</strong>：相邻的模型轮次会被合并，相邻的纯文本也会合并</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">占位符策略</strong>：如果模型没有有效输出，会添加空的占位符保持结构完整</section>
</li>
</ol>
<h2 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">3.9 压缩的用户体验设计</span></h2>
<p data-tool="mdnice编辑器">Gemini CLI 特别注重压缩对用户体验的影响：</p>
<ul data-tool="mdnice编辑器">
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">无感压缩</strong>：70% 的阈值确保压缩发生在用户察觉之前</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">连续性保持</strong>：保留 30% 的最新历史，确保当前话题的连贯性</section>
</li>
<li>
<section style="color: #010101;"><strong style="color: #0e88eb;">透明反馈</strong>：压缩前后的 token 数量变化会被记录和报告</section>
</li>
</ul>
<h1 data-tool="mdnice编辑器"><span class="content" style="color: #0e8aeb;">4. 写在最后</span></h1>
<p data-tool="mdnice编辑器">研究完这三个项目的源码，我最大的感受是：<strong style="color: #0e88eb;">压缩策略的选择，本质上是对「什么是重要的」这个问题的回答。</strong> Manus 说「所有信息都可能重要，所以我不删除，只是暂时收起来」；Claude Code 说「结构化的摘要比原始细节更重要」；Gemini CLI 说「用户体验比技术指标更重要」。三种回答，三种哲学。</p>
<p data-tool="mdnice编辑器">这让我想起一句话：<strong style="color: #0e88eb;">在 AI 时代，真正稀缺的不是信息，而是注意力。</strong> 上下文压缩就是在教 AI 如何分配注意力——什么该记住，什么可以忘记，什么需要随时能找回来。</p>
<p data-tool="mdnice编辑器">这是人类智慧的核心能力之一。我们每天都在做类似的决策：重要的事情记在心里，次要的写在本子上，琐碎的存在手机里。Manus、Claude Code 和 Gemini CLI 只是用不同的方式在教 AI 做同样的事。</p>
<p data-tool="mdnice编辑器"><strong style="color: #0e88eb;">没有完美的压缩策略，只有最适合你场景的策略。</strong> 选择哪种策略不重要，重要的是理解它们背后的设计智慧，然后根据自己的需求做出明智的选择。</p>
<p data-tool="mdnice编辑器">以上。</p>
</section>
]]></content:encoded>
			<wfw:commentRss>https://www.phppan.com/2025/09/agent-core-strategy-context-compression-strategy-and-details-for-manus-gemini-cli-and-claude-code/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
