网络隐形追踪漫谈

网络隐形追踪漫谈:从Claude封号到GDID破案

不少同学都有一个潜在的认知误区:认为开启VPN、更换公网IP就能实现网络隐身。但 VPN 仅能遮盖网络层IP,完全无法覆盖设备层、应用层、行为层的永久指纹。真正的身份追踪,从来不靠动态可变的IP,而是靠多层固化的设备、系统、行为特征交叉锁定。

近期引发开发者圈震动的 Claude 地域封号事件、Windows GDID 黑客溯源落网案例,就是最典型的佐证。两大案例本质相同:脱离IP做身份锚定。本文将结合这两个案例,拆解溯源机制,浅析网络匿名追踪的底层逻辑。

一、Claude 封杀中国大陆账户的核心机制

近期Claude Code封号事件,颠覆了大众对平台地域限制的认知。Anthropic 从Claude Code v2.1.91版本(4月初)开始,悄悄上线了实验性隐蔽检测机制,检测并封杀大量中国大陆账号。经资深技术专家逆向拆解后被曝光,Anthropic官方承认该机制为防账户转售、防模型蒸馏的实验性措施,迫于舆论压力,最终在2026年7月初的新版本中正式剔除该功能。这套机制的核心特点是完全不靠IP校验,依托离线检测+隐形隐写回传等手段,即便挂全局VPN、海外代理,依然能精准锁定中国大陆用户。

1. 双层离线本地识别

第一层为系统时区校验,也是命中率最高的核心手段。客户端本地直接读取系统底层时区参数,只要匹配 Asia/Shanghai、Asia/Urumqi 两个时区,直接标记为中国大陆高风险用户。绝大多数国内开发者即便挂海外代理,也不会刻意修改系统底层时区,该手段实战命中率极高。

第二层为加密中转域名黑名单校验。客户端内置一份经过Base64+XOR(91)加密混淆的147条域名黑名单,后台静默比对用户自定义的 ANTHROPIC_BASE_URL 代理地址。名单全覆盖 .cn 顶级域名、BAT互联网厂商、DeepSeek、月之暗面、MiniMax、阶跃星辰等国内主流AI实验室,以及市面所有主流Claude API中转服务商地址,只要检测到国内中转链路,直接触发风控标记。

2. 隐写术无感回传

这是整套机制最隐蔽、最具争议的设计:判定后的地域标签不单独发包、不留日志、不触发流量异常,而是嵌入每一次对话请求的系统提示词中,实现无感隐形上报,普通抓包、流量监测、日志排查完全无法察觉。

核心隐写规则至少有两处,肉眼完全无法分辨:一是日期格式差异化标记,将标准横杠格式 2026-07-01 强制替换为斜杠格式 2026/07/01;二是利用4种不同的Unicode撇号(U+0027、右单引号、修饰符撇号、角分号)替换 Today's 中的单引号,通过不同符号组合,区分「中国大陆域名、中国大陆AI实验室、国内代理中转、普通海外用户」四类用户群体,服务端解包即可精准判定用户真实地域属性。

3. 服务端多层交叉复核

本地标记完成后,会夹在Promtp中传回服务端,仔细看都看不出来。服务端会叠加多维度二次校验,对高置信客户进行封杀。更狠的是,封杀后Claude会引导你去申诉。但该申诉页面,会进一步收集用户信息(网络信息、浏览器指纹、填报信息等),你越申诉越实锤,封你没商量。

4. 不透明性+高权限,导致信任危机

Claude Code被广泛用于代码开发,本地权限很高,可以执行大量Shell和脚本,用户对其信任度也很高。但这种「本地高权限+隐写回传」组合,被业界认为存在严重供应链安全风险:

今天可以封杀中国大陆,明天可以封杀其他国家或地区。
通过标记,企业核心数据可以被静默采集,静默回传。有些商业机密要的就是一个是或否。
通过标记,识别企业核心人员,通过对话记录分析,同样可以得到商业机密。
通过标记,该公司可以针对性的,对用户进行打压(比如意见不同)。
在特殊时期,容易被用来进行定向攻击,甚至对标记的客户投放病毒。

换句话说,一个被用户高度信任的、本应中立的开发工具,携带了不应有的间谍软件功能。

二、Windows 设备唯一ID:可穿透VPN的核心溯源利器

2026年4月,19岁黑客 Peter Stokes(代号Bouquet)在赫尔辛基机场被FBI抓捕。该黑客为了防止被识别到,跨越多国利用多层VPN完成攻击,用传统手段难以追踪。但本次的破案关键并非IP追踪,而是Windows专属的GDID全局设备标识符,这是本案溯源最核心的追踪手段。

GDID(Global Device Identifier)是微软给每一次 Windows 安装分配的一个持久唯一设备标识符,系统更新、换硬件、挂 VPN 都不变,只有重装 Windows 才会换新。

VPN工作在网络层,仅替换公网出口IP,完全无法干预系统应用层的心跳上报。Windows默认开启系统遥测服务,会定时向微软服务器推送HTTPS加密心跳包,每一条日志都固定携带三组核心数据:永久GDID、精准时间戳、当前设备公网出口IP(即便为VPN节点IP)。

本次黑客抓捕的完整溯源闭环极具参考性:黑客Stokes攻击Tiffany业务系统时,全程挂VPN匿名,通过代理IP注册ngrok账户,自以为实现完美隐身。但他的Windows设备持续向微软上报携带GDID的遥测日志。FBI依据美国《2703(d)》申请司法调取,精准匹配「ngrok注册时段、受害者系统攻击时段」的同一GDID记录,直接将多笔攻击行为锁定至同一物理设备。

在此基础上,办案方还完成了第二层身份闭环:该GDID对应设备在不同时段的公网IP,与嫌疑人社交账号、Apple ID的登录轨迹高度重合,匹配出多国城市的出行记录,与官方出入境信息完全对应,进一步确定了身份。

后续警方通过该GDID关联的历史所有出口IP、上线时段,梳理出黑客跨国活动轨迹,最终在芬兰赫尔辛基机场实施抓捕。核心教训非常直白:VPN只能隐藏「你在哪(网络出口)」,但GDID永久标记「你是谁(物理设备)」,只要系统遥测未彻底关闭,设备就自带永久溯源信号。

三、常见隐形追踪手段

1. 浏览器指纹追踪

这是目前互联网最通用、低成本、高精准的无Cookie追踪方案,完全免疫VPN、无痕模式、缓存清理、浏览器更换。平台不再依赖单一渲染特征,而是采集Canvas/WebGL图像渲染指纹、AudioContext音频指纹等核心硬件渲染参数,搭配系统字体集、插件列表、屏幕分辨率与色深、系统时区语言、CPU核心数、内存大小等数十项参数。单一参数可伪造,但多参数组合后的熵值极高,可生成全网唯一的设备指纹,同台设备即便更换浏览器,依然能被精准识别。

2. 网络协议泄漏追踪

网络层泄漏是匿名翻车的重灾区,绝大多数VPN无法彻底封堵底层协议漏洞,直接暴露真实设备与网络信息。最经典的翻车点为WebRTC ICE协议泄漏,网页可直接穿透VPN隧道,抓取设备真实的局域网IP地址,不受任何代理伪装影响;其次是DNS泄漏,大量VPN无法接管系统全局DNS,域名解析请求会绕过VPN隧道,走本地运营商网关,直接暴露真实地域;除此之外,服务商可通过TCP栈指纹识别系统版本、网络模块特征,精准锁定设备唯一性。

3. 底层硬件标识追踪

除了Windows全局设备ID,各类应用可直接读取设备原生硬件唯一信息,包括硬盘序列号、主板TPM安全芯片标识、网卡MAC地址、CPU唯一编码等。这类硬件信息固化在硬件芯片中,普通格式化、重装系统、更换网络均无法修改,是锁定物理设备的终极手段之一。

4. 全平台应用层遥测与生态共享ID

不止Windows GDID,主流厂商全系生态都有互通的持久设备ID,穿透所有网络伪装。微软体系下,Edge、VS Code、Office、Teams等全系软件共享同一套设备标识,数据互通、交叉溯源;同时TPM安全芯片背书密钥、BitLocker恢复密钥、应用商店许可证,均为硬件级绑定的永久ID,重装系统也难以清除。苹果设备同理,IDFA、IDFV、iCloud设备列表、连续互通握手特征,可实现全设备、全账号联动追踪。这类遥测均走独立HTTPS通道,VPN完全无法拦截。

5. 系统配置与环境指纹追踪

平台可通过采集设备系统配置生成专属指纹,包括系统默认时区与时差校验、壁纸样式、输入法版本、开机启动项、系统字体缩放比例、硬件驱动版本等。普通用户不会刻意修改这些细微配置,长期保持固定参数,可作为辅助特征,精准匹配同一设备与用户。

6. 行为生物特征追踪

最高阶的溯源无需任何设备数据,依托用户独一无二的行为与社交图谱即可锁定身份。一方面是原生操作行为特征:打字节奏、按键间隔、鼠标滑动轨迹、点击习惯、页面停留时长、上网作息规律,每个人的行为生物特征终身不变,可跨设备、跨IP、跨账号关联身份。另一方面是账号社交轨迹交叉:同一用户不同账号的登录时段、操作习惯、访问路径高度重合,极易被大数据匹配识别。除此之外,信用卡BIN属地、收货地址、税号等KYC实名锚点,是比技术指纹更难清洗的终极溯源依据。

7. 内容元数据与隐形水印追踪

用户上传、发布的文件、图片、视频中,自带隐形溯源信息。各类文件默认保留设备编码、创建时间、系统型号等元数据;平台会对用户生成的内容添加隐形数字水印,肉眼、常规工具无法识别,但后台可精准提取,实现内容与设备、用户的绑定溯源。同时网页邮件的透明像素追踪器,可在资源加载瞬间抓取真实IP与设备信息。

四、总结

当下网络溯源体系,早已从「追踪IP地址」升级为「设备指纹+系统特征+网络漏洞+行为画像+实名信息」的多维交叉验证。仅依靠VPN做IP伪装,已经难以起到隐身作用。真正有效的网络隔离与匿名防护,必须同时落地:使用虚拟机或隔离环境、配合新装系统镜像、彻底禁用遥测服务、配合指纹浏览器与DNS加密,多维度联动才能规避隐形追踪。

上下文压缩:为何编程Agent容易失控,聊天Agent却能聊几小时?

上下文压缩:为何编程Agent容易失控,聊天Agent却能聊几小时?

最近在做 Agent 相关开发时,发现一个很有意思的现象:同样是上下文压缩,在不同业务场景下的影响天差地别。

用 Claude Code、Codex、Cursor、Trae这类编程工具写代码,对话压缩个两三次,你就会明显感觉到它 “变笨了”—— 找不到工具、记不住文件路径、忘记之前定好的约束,甚至开始重复做已经完成的事。而日常聊天类产品,比如你跟豆包聊一下午,天南海北扯几十轮,上下文不知道被压缩了多少轮,你却几乎感觉不到明显的质量下降。

这背后不是模型能力的差距,而是两类业务对上下文信息的要求,本质上就不在一个维度上

一、先搞清楚:上下文压缩到底在做什么

上下文压缩不是简单的 “删除旧消息”。目前主流的压缩方式大致分三层:

微观清理层(Micro-compact):每次 API 调用前,静默清理过期的工具返回结果、冗余的日志输出、重复的状态信息。这一层几乎无损,成本极低。

摘要压缩层(Auto-compact):当上下文接近窗口阈值时,调用模型把历史对话重写为一段摘要,用几十到几百 token 替代几千 token 的原始对话。这是真正有损的一步。

KV Cache 层:推理引擎层面做的 token 合并、低秩压缩、量化,例如 DeepSeek 采用的 MLA(多头潜在注意力)/ HCA 机制,通过对 KV 进行低秩压缩,大幅减少了显存占用。这一层对语义损伤较小,但对精细符号有影响。

以 Claude Code 为例,它有完整的 5-7 层渐进式压缩流水线,从工具结果落盘、历史裁剪、微压缩,到最后的全量摘要,是一套 “不到万不得已不动用有损压缩” 的防御体系。但只要触发了摘要式压缩,信息损耗就不可逆转。

二、核心差异一:符号精确性 vs 语义连续性

这是最本质的区别。

编程类业务是典型的高信息密度 + 零容错符号系统。代码世界里,差一个字符就是天壤之别:
文件名 userService.tsUserService.ts 是两个文件
变量名 userIduser_id 差一个下划线,就是完全不同的标识符
函数参数从 (id: string) 变成 (userId: string),调用方全崩
文件路径 src/api/v2/handler.ts 记错一级目录,工具直接找不到文件
缩进、括号层级错一处,整个代码的语法结构直接失效

摘要式压缩的本质是语义蒸馏—— 它的底层逻辑是「丢弃细节、保留大意」,擅长留住 “这段代码在做用户鉴权” 这种宏观描述,但不擅长精确保留 validateToken(payload: JwtPayload): boolean 这种精确符号。可在代码世界里,”大意” 几乎没有实用价值:只知道 “这里有个处理用户数据的函数”,却记不住具体函数名和参数列表,Agent 根本无法完成调用。

每压缩一次,符号精度就衰减一次;多轮压缩后,具体的标识符就模糊成了 “某个验证函数”。一次关键的符号丢失,就可能直接造成语法错误或逻辑断裂,让 Agent 直接失控。

聊天类业务是低信息密度 + 高容错的语义系统。人类日常对话本身就充满语义冗余:同一件事往往会反复表述,核心信息包裹在大量寒暄、铺垫和修饰里。你和豆包聊旅行、聊美食、聊电影,压缩后只要还能记住 “用户想去日本、喜欢吃拉面、上周刚看过某部电影” 这些语义要点,对话就能继续顺畅进行。

记错几个细节?漏掉一两句无关紧要的寒暄?没关系,自然语言的模糊性天然提供了极高的容错空间,用户甚至根本察觉不到。

打个比方:压缩就像把一张高清图转成缩略图。聊天场景下,你只需要认出 “这是一只猫”,缩略图足够了;编程场景下,你需要数清猫身上有几根毛、每根毛的精确角度 —— 缩略图完全不够用。

三、核心差异二:链式推理的误差放大效应

编程 Agent 的工作方式是多步链式推理:读文件 → 分析问题 → 制定方案 → 修改代码 → 运行测试 → 修复错误 → 再测试…… 每一步都依赖前一步的精确结果。从本质上看,这是一个严密的离散状态机:Agent 需要在内存中持续维护一组精确的 “状态变量”—— 当前修改的文件路径、当前的变量作用域、上一步工具返回的报错信息、待完成的任务清单。这些状态是非此即彼的,不存在中间地带。

这就形成了一个误差放大器
第一次压缩:记错了一个变量名 → 写出来的代码有 bug
第二次压缩:忘记了之前发现的某个边界条件 → 修复方向跑偏
第三次压缩:连已经改了哪些文件都记不清了 → 开始重复劳动、陷入循环

每一轮压缩引入的微小误差,都会在后续的推理链条中被放大。到第三四轮压缩时,Agent 的内部状态已经和真实状态严重偏离,表现出来就是 “失控”—— 工具乱调、逻辑混乱、忘记任务目标。

更麻烦的是 Agent 的隐性状态维护。Claude Code 会维护 todo list、已完成项、已知错误列表这些隐性状态,这些状态不会每次都显式说出来,而是存在对话的隐含逻辑里。上下文压缩最容易抹掉这些离散的状态节点,相当于状态机丢失了当前的状态指针,直接导致 Agent”失忆”,忘了自己做到哪一步。

聊天业务完全没有这个负担。对话更像是连续的语义流,核心是主题和情感的延续,不存在严格的前后依赖链条。每一轮对话相对独立,用户说一句、AI 答一句,话题跳了、偏了、忘了某个细节,都不影响对话的整体体验。这种语义层面的抽象与泛化,恰恰是大模型最擅长的能力,也是聊天场景不怕压缩的核心原因之一。

四、核心差异三:结构化数据的脆弱性

编程 Agent 重度依赖结构化的工具调用协议。每一次工具调用在消息序列里都是严格配对的:Assistant 发出一个 tool_use(带 ID),User 回复对应的 tool_result(带相同 ID)。

上下文压缩时,这个配对关系非常容易被破坏。比如:
裁剪历史时,切点落在 tool_use 和 tool_result 之间,产生 “孤儿消息”
摘要重写时,把结构化的 tool_use 块写成了自然语言描述
多轮并行工具调用的顺序被打乱,导致状态错乱

除此之外,工具调用本身有严格的 JSON 格式约束,工具定义的 Schema、参数结构都必须完整保留才能被正确解析。但当前的压缩技术很难完美保障结构化数据的完整性:无论是语义摘要还是 Token 裁剪,都可能破坏 JSON 的闭合结构、截断字段定义。一旦解析器无法识别工具调用格式,Agent 的整个执行循环就会直接崩溃。

这就是为什么很多人遇到 “Claude Code 突然不调用工具了,开始用嘴说命令”—— 不是它不想调用,是压缩后的上下文里,工具调用的结构化边界已经模糊了,模型把它当成了普通文本。

而聊天业务几乎没有结构化数据。整条对话就是纯文本消息,压缩前后都是纯文本,不存在结构破坏的问题,自然也不会遇到 “格式解析失败” 这类硬阻断故障。

五、核心差异四:任务闭环 vs 开放交互

还有一个容易被忽略的视角:谁在驱动对话前进

编程 Agent 是自主闭环执行的。你说一句 “帮我重构这个模块”,接下来的十几轮可能都是 Agent 自己在驱动:读文件、改代码、跑命令、查错误…… 用户可能全程只看着。这意味着 Agent 必须自己维护完整的任务状态,一旦压缩导致状态丢失,没有人来帮它纠正。

聊天产品是用户驱动每一轮的。每一轮对话都是用户发起、用户掌舵。如果 AI 记错了什么,用户自然会提醒;如果话题跑偏了,用户会拉回来。用户本身就是上下文质量的校正器。

换句话说:聊天场景下,用户的每一次输入都在“隐式的”重新锚定上下文;编程场景下,Agent 自己在黑盒里跑,压缩跑偏了也没人拉一把。

六、核心差异五:深度推理模式 vs 直觉联想模式

除了业务场景的客观差异,模型本身的设计取向也放大了这种感受差。

像 Claude 这类主打长上下文深度推理的模型,在 Agent 工作流中实际上是在运行系统 2 思维模式:慢速、严谨、步步为营,每一步推导都严格依赖前面的结论和信息。这种模式对上下文的连贯性和完整性要求极高,上下文压缩就像在一个人演算数学题时突然抽走半页草稿纸,哪怕只丢失少量信息,也可能让后续的推理完全跑偏。

而聊天场景下的模型更多运行在系统 1 模式:快速、直觉、联想式生成。它不需要严密的逻辑链条,只需要顺着语义和情绪自然延续即可,本身就不要求强逻辑连贯性,因此对压缩带来的信息损耗抗干扰能力要强得多。

七、结语

总结一下,五类差异层层叠加:

维度 编程类业务 聊天类业务
信息类型 精确符号系统,差一字符即错 模糊语义系统,容错率极高
推理结构 严格链式状态机,误差逐级放大 发散式语义流,误差相互独立
数据结构 结构化工具调用,边界脆弱 纯自然语言,结构简单
驱动方式 Agent 自主闭环,无人校正 用户驱动每轮,自然校正
思维模式 深度逻辑推理,对连贯性要求高 直觉联想生成,抗干扰能力强

这也是为什么做 Agent 框架的团队,永远在和上下文管理死磕 —— 因为你面对的不是 “聊天记不记得住” 的体验问题,而是 “符号系统能不能保真” 的工程问题。聊天场景 80 分的压缩算法,放到编程场景可能连及格线都到不了。

从另一个角度说,这也解释了为什么 “无限上下文” 至今都是伪命题。不是技术做不到更长的窗口,而是当任务本身要求符号级精确时,窗口再大也没用 —— 注意力稀释、位置编码失真、压缩损耗,这些问题不会因为窗口变大就消失

对编程 Agent 来说,真正的解法从来不是 “把窗口做更大”,而是:结构化外部记忆、状态显式化、可验证的任务边界,以及 —— 承认压缩必然有损,在工程上设计好熔断和重置机制。长远来看,核心思路是将关键状态与工具定义从易受压缩的对话上下文中剥离,为 Agent 设计独立的、不受压缩影响的外部记忆体与草稿板,用结构化存储保障核心信息的绝对保真。

而在日常使用中,最直接有效的办法也很简单:当发现编程 Agent 的对话已经过长、即将触发多次压缩时,及时开启新会话,重新明确当前的核心状态与目标,往往是防止 Agent”失控” 最具性价比的方案。毕竟,人类程序员写代码也会记不住,还不是靠注释、文档和 Git 吗?

iPhone18印度泄密事件:供应链转移路上的一记警钟

iPhone18印度泄密事件:供应链转移路上的一记警钟

事件概述

2026年6月底,消费电子行业爆出重磅消息:苹果印度核心代工厂塔塔电子遭到黑客组织World Leaks的网络攻击,总计超过630GB、20余万份标注有机密水印的内部文件被泄露至暗网,其中包含大量尚未发布的iPhone 18 Pro系列核心资料。这被业内普遍认为是苹果公司历史上最严重的一次供应链泄密事件。

泄露了什么

此次泄密的内容量级和敏感程度远超以往任何一次:

完整供应链底牌:至少6份核心文件详细列出了iPhone 18 Pro系列数百个零部件对应的独家供应商名单,从主板芯片、电池组件到摄像头模组,甚至螺丝等微小部件的采购来源、单价、合作条款全部公开。在此之前,苹果每年公布的供应商名单只会列出厂商名称,绝不会标注具体供货品类,这是苹果维持供应链议价权的核心手段。
硬件设计图纸:包含标注”机密”水印的主板设计图、A20 Pro芯片的数据手册、整机三维结构图纸等核心研发资料。
测试素材:2026年初的新机跌落测试实拍视频与照片、实验室实测影像等量产验证阶段素材。
生产排产计划:量产排产台账、采购合同等运营层面的内部文件。

事件时间线

6月之前:黑客组织 World Leaks 通过通过多种攻击手段,获取了塔塔电子内网的高管权限,窃取大量核心数据,包括20余万份文件,总共630GB(大部分为苹果资料,还有小部分特斯拉、高通相关资料)。

6月上旬:6月初,World Leaks向塔塔电子勒索了一笔非常高昂的赎金(据说4000万美金),并且威胁如果不交,就会在暗网公布全部的内容。勒索谈判陷入僵局,塔塔电子没有按要求缴纳赎金。于是,黑客就在6月12日把全部文件公布在暗网上。

6月23日:塔塔电子发布声明,承认遭遇”网络安全事件”,确认内部系统被入侵,但未披露具体泄露量级。

6月底:由于数据在暗网上,不方便公众访问,所以直到10多天后,路透社、财联社等媒体才看到了可信的证据,大量媒体跟进报道,事件发酵。

7月初:苹果官方正式开始调查此事,塔塔电子启动内部安全排查;印度计算机应急响应小组介入调查;暗网数据包被各方下载研判,各路竞对率先进入”开卷模式”。

后续影响

1、苹果保密体系:物理侧铜墙铁壁,网络侧被击穿

苹果供应链保密此前一直以物理管控为核心:中国厂区的生物门禁、摄像头封贴、图纸分级授权,搭配“分段生产、信息切割”策略——单家供应商仅接触单一模块,无法获取整机完整资料,这套体系十几年未出现同级泄密事故。

此次泄密完全绕过物理防线:勒索组织利用塔塔内网搁置半年未修复的系统漏洞,获取高管权限后潜伏数十天,直接从全局生产数据池拖走全部核心文件。苹果沿用多年的“物理分段隔离”逻辑,在集中化的IT数据池面前彻底失效,本质是海外代工厂的IT安全治理强度远未跟上物理管控标准。

2、供应链议价权:数十年信息差壁垒被击穿

苹果能在全球供应链中长期维持强势地位,核心支撑正是建立在信息隔离之上的“信息差”优势。全链路数百家供应商仅能知晓自身的供货范围、采购量与报价,既无法得知同品类备选供应商情况,也不清楚整机完整成本结构。苹果凭借全局信息垄断,在零部件压价、产能调配、备选供应商布局中始终掌握绝对主动权。

此次泄密直接击穿了这层信息壁垒:完整的供应商匹配清单、分品类采购单价、全BOM成本底价随文件流出,将苹果供应链的全局底牌完全公开。上游供应商可精准判断自身在链路中的不可替代性——核心独家部件厂商议价底气显著增强,非独家供应商也可参照同类报价摸清苹果价格底线,苹果单方面主导的采购谈判格局被打破,后续供应链议价、产能分配的强势话语权将被实质性削弱。

3、全产业连锁反应:周边与竞品提前进入开卷模式

此次泄露包含Gerber PCB文件、主板走线、影像参数等核心研发资料,直接拉低了全行业的逆向门槛。对华强北周边产业链而言,无需等新机发布后拆机逆向,壳膜、MagSafe配件、第三方散热等产品可提前两个月完成开模量产出货,直接挤压苹果官方配件的溢价空间。

同时,核心硬件参数与技术路线提前曝光,也让安卓头部厂商能针对性调整旗舰产品的卖点配置与发布节奏,提前打造对标方案,进一步稀释iPhone 18系列发布时的技术新鲜感与市场冲击力。

4、印度制造:高速扩张下的信誉欠账

据Counterpoint预测,2026年印度产iPhone占比将达26%,较四年前的6%翻了四倍,其中塔塔电子承接了印度三分之一iPhone出口产能,是苹果“中国+1”产能对冲战略的核心支点。原本用于分散地缘风险的节点,反而成了新的风险源。

路透分析指出,苹果短期无法替换塔塔,双方产能绑定深、替换成本极高,但对印度制造业IT防御能力的信心已实质性动摇。后续大概率将推进两项调整:一是强制印度厂区全面接入苹果网络安全审计标准;二是收缩核心数据权限,将主板设计、芯片参数等核心资料收回美/中安全节点,印度侧仅保留组装级工艺文件,高端机型的产能转移节奏也可能放缓。

结语

数据泄露已成事实,绝无可能彻底清除。距iPhone 18系列发布会只有两个月,这次史诗级泄密无疑打乱了苹果的新品发布节奏与市场预期管理,且看苹果后续如何快速止血吧。
更深远的影响在于,它让整个行业重新审视”供应链转移”的额外风险——产能规模可以快速堆砌,但安全管控、保密文化、产业工人素养这些软实力,绝非短期投入就能补齐。

进程级Agent沙箱轻量化落地方案

进程级Agent沙箱轻量化落地方案

进程级Agent沙箱核心技术方案

根据《进程级Agent沙箱的12道安全防线》,对主流操作系统的12个管控维度,推荐了轻量级落地方案,供大家参考。

macOS(Seatbelt)

管控方向 轻量化方案
文件系统隔离 SBPL 规则定义工作目录白名单与读写权限,其余默认 deny;分配独立私有 tmp 目录,TCC 全局屏蔽隐私路径
环境变量隔离 execve 启动前批量清理,仅白名单透传 PATH、HOME、TMPDIR 等必要变量,剔除 DYLD 注入类变量
工具管控与权限分级 代码签名 + 公证校验,搭配 Hardened Runtime 加固;禁用交互式 Shell,限制 -c/-e 等危险参数
人工确认机制 Seatbelt 底层拦截定时任务、服务注册等高危操作,用户态弹窗展示命令与风险;超时默认拒绝,支持可信规则放行
进程权限降权 标准用户身份运行,禁用sudo提权;依托SIP保护系统路径,禁止setuid程序执行
硬件访问管控 SBPL 规则限制 IOKit 设备访问,配合 TCC 框架全局屏蔽摄像头、麦克风等隐私硬件;收敛/dev目录权限,阻断块设备直接访问
Syscall 过滤器 Seatbelt内置调用拦截,屏蔽ptrace、task_for_pid等高危调用;强制内存 W^X 保护
进程树与生命周期管控 进程组绑定+父进程监控回收完整子进程树;限制最大进程数防御fork炸弹
资源限制 setrlimit管控CPU、内存、文件句柄、单文件大小配额;SBPL补充磁盘写入量与tmp用量约束
网络隔离 ALF 防火墙默认拦截全部入站连接,Network Extension 实现出站域名白名单;支持三级网络模式切换
IPC 与信号屏蔽 SBPL deny mach-lookup 隔离 Mach 端口,限制跨进程调试信号发送
审计日志与溯源 OpenBSM Audit Trail 全量记录行为,日志输出至沙箱外目录;高危操作实时告警

总结:Seatbelt (SBPL) 为核心,TCC为辅助,Hardened Runtime 兜底。

Linux(Namespace + Seccomp)

管控方向 轻量化方案
文件系统隔离 bubblewrap白名单挂载工作目录,只读挂载系统依赖库;独立挂载私有/tmp,屏蔽系统敏感目录;
内核5.13以上版本,启用Landlock LSM,可实现无需特权即可生效的文件路径级强制访问控制(MAC)
环境变量隔离 bwrap --clearenv 清空默认变量,--setenv 显式配置变量白名单
工具管控与权限分级 路径 + SHA256 双重校验工具身份;禁用交互式 Shell,限制 -c/-e 代码执行参数;子进程自动继承规则
人工确认机制 父进程(Agent Host)拦截 execve 高危调用,弹窗展示风险;超时默认拒绝,支持可信规则自动放行
进程权限降权 User Namespace 映射沙箱 root 为宿主机高 UID 普通用户;cap_drop 裁剪高危能力;开启 PR_SET_NO_NEW_PRIVS禁止execve提权
硬件访问管控 仅挂载 /dev/null、/dev/zero 等基础设备,屏蔽磁盘、音视频、串口等所有外设节点
Syscall 过滤器 seccomp-bpf白名单模式,拦截ptrace、mount、kexec等高危调用;强制内存 W^X 规则
进程树与生命周期管控 PR_SET_PDEATHSIG + PID Namespace,父进程异常退出子进程自动终止;RLIMIT_NPROC+cgroups pids.max双重限制最大进程数
资源限制 Cgroups v2 硬配额管控 CPU、内存、磁盘IO带宽与IOPS、进程数;配置执行超时强制终止
网络隔离 Network Namespace默认仅保留lo,基于用户态透明代理实现出站域名白名单,nftables做IP/端口层兜底;禁止入站连接与内网IP访问
IPC 与信号屏蔽 IPC Namespace 隔离共享内存、消息队列;PID Namespace 阻断跨沙箱信号与进程探测
审计日志与溯源 Auditd​监听execve/open/connect等syscall全量落盘;内核5.15以上版本,配合CAP_SYS_ADMIN可启用Fanotify做文件层实时拦截(特权环境);日志存储于沙箱外,高危行为实时告警

总结:bwrap (Namespace) 打底,Seccomp 守门,Cgroups 限流。

Windows(AppContainer)

管控方向 轻量化方案
文件系统隔离 AppContainer文件夹重定向至独立工作区;NTFS DACL配置读写权限分级;分配独立临时目录;注册表层面,AppContainer对HKCU指定分支自动虚拟化,HKLM系统键默认无写入权限,防范持久化后门
环境变量隔离 CreateEnvironmentBlock 定制环境,剔除危险注入类变量,仅透传业务必要项
工具管控与权限分级 进程代码完整性(CIG)禁止加载未签名动态模块,配合AppContainer限制可执行文件范围;禁用交互式Shell,限制危险执行参数
人工确认机制 COM 接口拦截高危 Shell 执行,弹窗展示命令与影响范围;超时默认拒绝,支持可信规则放行
进程权限降权 AppContainer SID + 低完整性级别(Low IL);禁用 runas 等提权命令
硬件访问管控 设备 Capability 屏蔽摄像头、麦克风、USB 存储等外设;限制硬件设备访问权限
Syscall 过滤器 ACG 阻断动态代码生成,Win32k 系统调用过滤;强制内存 W^X 保护
进程树与生命周期管控 Job Object 绑定全进程树,开启 KILL_ON_JOB_CLOSE;限制最大进程数防 fork 炸弹
资源限制 Job Object 管控 CPU、内存、文件句柄配额;配置执行超时强制终止
网络隔离 WFP做IP/端口级出站管控,配合用户态代理实现域名白名单;默认禁止全部入站连接,限制内网IP段访问
IPC与信号屏蔽 独立 Window Station / Desktop,阻断 UIPI 窗口消息注入;隔离命名管道与共享内存
审计日志与溯源 ETW 全量记录文件、进程、网络、拦截事件;日志存储至沙箱外目录,高危行为实时告警

总结:AppContainer为墙,Job Object为锁,WFP/ETW为监控。老版本Windows可退化为受限令牌+Job Object+Low IL组合方案。

对于各平台的落地方案,你有什么好的建议呢?欢迎留言,一起讨论和改进

进程级Agent沙箱的12道安全防线

进程级Agent沙箱的12道安全防线

进程级Agent沙箱技术要求

在AI Agent从“聊天助手”向“自主执行者”演进的今天,我们正面临着前所未有的安全挑战。当Agent开始拥有访问文件系统、调用命令行工具甚至发起网络请求的能力时,一个失控的Prompt就可能让整个服务器沦陷。

为了让Agent既能“放手干活”,又不“惹是生非”,进程级沙箱(Process-level Sandboxing)成为了保障宿主环境安全的最后一道防线。

本文尝试构筑一个基础的安全防线,咱们一起来看下需要考虑哪些方面。

1. 根基之固:文件系统隔离 (Filesystem Isolation)

沙箱的起点在于文件系统的边界控制。我们不能相信Agent的任何路径输入。

  • 最小化可见性:采用白名单机制挂载路径。Agent的工作区(Workspace)是其唯一可见的“世界”,系统目录(如 /etc, /usr/bin)及包含密钥的隐私路径默认完全屏蔽。
  • 权限分级:即使在工作区内,也要区分读、写、执行权限。例如,数据目录通常只需写权限,而脚本目录需执行权限。
  • 攻击面封堵:必须严防目录遍历(../)以及利用符号链接(Symlink)或硬链接指向沙箱外的敏感文件。
  • 临时目录独享:这是常被忽视的一点。每个沙箱实例必须拥有独立的 tmp 目录,严禁共享系统 /tmp,以防止通过临时文件进行的跨沙箱数据窃取或竞争条件攻击。

2. 环境的纯净:环境变量隔离(Environment Variable Isolation)

环境变量是隐形的攻击载体。Agent启动时,必须对环境进行“大扫除”。

  • 白名单透传:仅允许明确声明的变量进入沙箱。
  • 清理注入向量:重点清理 LD_PRELOADLD_LIBRARY_PATHPYTHONPATH。这些变量常被用于动态链接库注入,是绕过代码执行限制的常用手段。
  • 锁定网络配置:禁止通过篡改 HTTP_PROXY 等代理变量来绕过网络隔离策略。

3. 工具的枷锁:工具管控与权限分级 (Tool Governance)

Agent执行的每一个工具(二进制文件)都必须经过严格的身份验证和权限审查。

  • 身份三重奏:通过路径校验、哈希校验(Hash)及平台签名校验,确保执行的不是被篡改过的木马程序。
  • 三级权限模型:
    • 低危:自动放行(如 ls, cat)。
    • 中危:策略管控(如网络请求)。
    • 高危:必须人工确认(如 rm -rf, 系统配置修改)。
  • 参数级约束:这是防御的关键。必须禁用解释器的交互模式,严格限制 -c-e 等允许执行任意代码的参数。
  • 禁止交互式Shell:严禁启动 bashsh 等交互式Shell,同时禁止通过管道(|)、重定向(>)串联未授权命令,防止Shell从标准输入读取执行代码。

4. 人的决策:人工确认机制(Human-in-the-Loop)

无论自动化规则多么完善,对于高危操作,人必须是回路的一部分。

  • 底层拦截:确认逻辑必须在沙箱底层实现,防止Agent通过逻辑漏洞绕过前端UI的确认弹窗。
  • 透明化:向用户展示完整的命令、预期的影响范围及潜在风险。
  • 灵活性:支持配置可信规则模板(如“允许读写 ~/Documents”),匹配后自动放行;支持超时默认拒绝和会话级临时信任,平衡安全与效率。
  • 持久化防御:特别拦截 crontab、计划任务、开机自启项和系统服务注册,防止Agent植入后门实现持久化控制。

5. 身份的降维:OS进程权限降权(Process Privilege Drop)

遵循“最小权限原则”,Agent进程绝不能拥有比它需要的更多的权力。

  • 专用账号:使用专用的低权限账号(如 agent-runner)运行,绝对禁止使用 root 或 Administrator。
  • 裁剪Capabilities:在Linux下,剥离所有非必要的内核Capabilities;在Windows下,设置低完整性级别(Low Integrity Level)。
  • 阻断提权:禁止执行 setuid/setgid 程序,屏蔽 su/runas 等提权命令。
  • 实例隔离:多Agent实例使用独立身份运行,防止横向渗透。同时,禁用核心转储(Core Dump),避免内存中的敏感数据(如密码、Token)被写入磁盘文件。

6. 物理世界的屏障:硬件访问管控(Hardware Access Control)

Agent无需访问物理硬件,因此默认全部封锁。

  • 节点重定向:默认禁用或重定向所有物理设备节点。
  • 隐私保护:特别屏蔽块设备(如U盘)、字符设备(如摄像头、麦克风)、串口及蓝牙设备节点,防止成为间谍软件。

7. 内核之门:Syscall 过滤器 (Seccomp-BPF)

如果说防火墙控制网络流量,Seccomp则控制着进程对Linux内核的访问。

  • 白名单模式:仅放行业务必需的Syscall(如 read, write),其余一律拦截。
  • 高危拦截:坚决拦截 ptrace(调试)、mount(挂载)、内核模块加载等调用。
  • 参数校验:对文件和网络类调用补充路径与地址参数校验。
  • W^X原则:强制内存“写异或执行”规则,禁止内存页同时具有可写和可执行属性,这是防御Shellcode注入的有效手段。

8. 生命的周期:进程树与生命周期管控(Process Hierarchy and Lifecycle Control)

Agent可能会尝试启动子进程来逃逸监控,我们必须对其进行全生命周期管理。

  • 策略继承:确保子进程自动继承父沙箱的所有规则(Cgroups, Namespaces, Seccomp)。
  • 连带销毁:主进程退出时,必须自动销毁完整的进程树(PID Namespace),杜绝孤儿进程残留。
  • 防御Fork炸弹:限制最大进程数。
  • 禁止跨进程干扰:禁止进程间访问、代码注入、调试及动态库注入。

9. 资源的天花板:资源限制 (Resource Limits/Cgroups)

防止Agent由于Bug或恶意代码耗尽宿主资源。

  • 硬性配额:对CPU、内存、磁盘IO设置硬上限。
  • 存储限制:限制磁盘写入总量、单文件大小、文件/目录总数量及文件句柄数。
  • 超时熔断:单次任务执行超时强制终止。
  • 防御DoS:有效防御死循环、大文件生成导致的宿主资源耗尽攻击。

10. 网络的孤岛:网络隔离 (Network Isolation)

网络是Agent连接外部世界的通道,也是最大的风险源。

  • 三级模式:默认采用“完全断网”;必要时开启“域名白名单”;仅在极度信任下“全量放行”。
  • 内网防护:严禁访问内网IP段(如 192.168.0.0/16, 10.0.0.0/8)及本地回环上的敏感端口(如Redis 6379, MySQL 3306)。
  • 流量整形:限制出站协议、请求频率与单包大小。
  • 入站封锁:默认禁止所有入站连接,仅允许已建立的连接回传数据。

11. 进程间的柏林墙:IPC与信号屏蔽(Inter-Process Communication)

防止通过进程间通信(IPC)机制逃逸沙箱。

  • 信号隔离:限制跨进程信号发送,屏蔽调试类信号(如 SIGTRAP)。
  • 通信隔离:隔离共享内存、消息队列、信号量等跨沙箱通信机制。
  • PID Namespace:沙箱内不可见外部进程,也无法向外部进程发送信号。

12. 黑匣子:审计日志与行为溯源(Audit Logs and Behavior Tracing)

最后,我们需要一个无法篡改的“黑匣子”来记录一切。

  • 全量记录:记录每一次文件操作、命令执行、网络请求及拦截事件。
  • 防篡改:日志输出到沙箱外的专属目录,沙箱内进程仅拥有追加权限,无权删除或修改。
  • 结构化存储:支持按类型、时间、风险等级进行高效检索。
  • 实时告警:对高危操作和逃逸尝试进行实时告警,以便安全人员及时介入。

结语

构建一个安全的进程级Agent沙箱,本质上是在构建一个零信任的执行环境。它不相信任何输入,不授予任何多余权限,并对一切行为进行审计。

上述12个维度的技术点并非孤立存在,而是相互交织,构成了一个基础的防御体系。当前只是一个很粗略的版本,欢迎留言,一起讨论和改进

Agent常见沙箱技术解析

Agent常见沙箱技术解析

Agent常见沙箱

最近AI Agent技术火热,大家都很感兴趣,不少同学都已开始实践。在落地的过程中,有一对矛盾始终绕不开:
1、必须给Agent赋能和赋权,否则Agent就没有行动力
2、必须管控好Agent,防止Agent一抽风就把电脑炸了
对于问题1,我们有harness、tools、skills、MCP、sub-agent等等
对于问题2,就要用到沙箱技术了

市面上的沙箱技术五花八门,各有千秋。今天咱们就浅聊一下Agent场景下,常见的几类沙箱技术方案。

一、OS进程级

这是最贴近用户桌面操作系统的防线,利用内核自带的安全模块和机制,直接对进程的行为进行“围追堵截”。

MacOS:Seatbelt

MacOS的Seatbelt机制非常成熟。它基于TrustedBSD框架,允许内核按照预定义的SBPL (Sandbox Policy Language) 配置文件,对进程的文件、VFS、网络及IPC操作进行强管控。

  • 特点:原生支持,策略灵活。
  • 场景:适合在Mac环境下运行本地Agent时的权限限制。

Linux:Landlock + seccomp + bubblewrap

Linux下的组合拳最为丰富,通常可以分层叠加使用:

  1. Landlock:LSM(Linux Security Module)的一种,提供路径级访问控制。它的优势在于非特权,普通用户进程无需root权限即可启用,非常适合限制Agent对特定目录的读写。
  2. seccomp:系统调用过滤器。Agent只需要有限的Syscall(如read, write),通过seccomp-bpf可以禁止它调用forkexecve或操作网络栈的系统调用。
  3. bubblewrap:基于User/Mount Namespace的工具。它能创建一个全新的文件系统视图(类似chroot),让Agent以为自己在一个独立的系统中,但实际上只是宿主机的一个子目录。

组合效果:bubblewrap负责“画地为牢”(视图隔离),Landlock负责“看守大门”(文件访问),seccomp负责“限制手脚”(禁止危险操作)。

Windows:AppContainer + Job Object

Windows同样提供了内核级的原生隔离:

  • AppContainer:这是UWP应用使用的隔离机制,也是沙箱的基石。它能细粒度地管控文件、网络(出站/入站)和注册表的访问权限,默认拒绝一切,白名单放行。
  • Job Object:主要用于资源配额管控。你可以限制Agent进程组的CPU时长、内存占用,以及防止子进程逃离作业控制。

二、运行时级

如果说OS级沙箱是在修补现有系统的漏洞,那么WebAssembly (Wasm) 则是从设计之初就考虑了安全。

  • 原理:Wasm运行时(如Wasmtime、WasmEdge)本身没有直接的系统访问权限(System Interface)。所有的IO操作都必须通过WASI(WebAssembly System Interface)进行,且由宿主环境明确授权。
  • 优势:
    • 极速启动:毫秒级启动时间,非常适合短时运行的Agent任务。
    • 内存安全:线性内存模型,隔离性极佳。
    • 确定性:执行环境高度一致。
  • 场景:非常适合作为插件系统或轻量级代码执行器的沙箱。例如,WasmEdge已经有很多LLM推理和Agent的应用案例。

三、容器级

容器是目前云原生时代的主流选择,但在Agent场景下,我们需要关注其安全性差异。

标准容器沙箱 (docker/runc)

传统的Docker使用runc,依赖Linux Namespace进行资源隔离(PID, Mount, Network等)和Cgroups进行资源配额限制。

  • 痛点:虽然隔离了视图,但容器内的进程共享宿主机内核。如果Agent触发了内核漏洞,依然有可能逃逸。

增强型容器沙箱 (gVisor/runsc)

为了解决内核共享的问题,gVisor应运而生。

  • 原理:它在用户态实现了一个“虚拟内核”(Sentry)。容器内发起的所有Syscall都会被runsc拦截,并在用户态模拟处理,而不是直接传递给宿主机内核。
  • 优势:极大地缩小了攻击面,即使Agent试图发起恶意Syscall,也只是攻击了用户态的内核模拟层,无法触及真实的Host Kernel。
  • 场景:非常适合运行不可信代码的Agent服务。

四、虚拟机级

当安全等级要求极高时,我们必须回到虚拟机。

传统虚拟机沙箱

QEMU/KVM、Hyper-V等传统方案提供了硬件级别的隔离。

  • 缺点:太重了。启动慢(秒级/分钟级),内存开销大。对于需要频繁创建销毁的Agent场景来说,性价比极低。

MicroVM 沙箱 (Firecracker & ZeroBoot)

AWS Lambda背后的功臣——Firecracker,重新定义了轻量级虚拟化。

  • 原理:基于KVM进行极致的裁剪和优化,去除了不必要的设备模拟(如BIOS、PCI总线),只保留必要的virtio设备。
  • 优势:
    • 毫秒级启动:接近容器的速度。
    • 极低开销:内存开销极低(<5MB)。
    • 硬隔离:拥有VM级别的安全性。
  • 场景:Multi-tenant环境下的Agent执行。ZeroBoot也是类似的思路,旨在加速MicroVM的启动过程。

总结

随着Agent能力的增强,沙箱技术不再是一个可选项,而是基础设施的基石。希望这篇文章能帮你在构建Agent系统时,选对那把“安全锁”。

你对哪种沙箱技术最感兴趣?或者你在实践中遇到了哪些坑?欢迎在评论区交流。

Prompt KV Cache是如何实现的

Prompt KV Cache是如何实现的

如果你是大模型API的重度用户,看着token快速燃烧,大概率会思考过这样一个问题:

每次和大模型对话,都会先有几百个token的固定system prompt(角色设定、工具定义、甚至是一整份文档),然后才是几十个token用户的问题。但我们却要为这段固定不变的system prompt反复付费、反复等它算完。这是一种巨大的浪费,不能想想办法节约一些token吗?

Prompt KV Cache要解决的就是这件事:让完全相同的prompt前缀不要重复计算,可以在后续的对话中,继续复用已经计算好的结果。本文来剖析一下,这个技术是如何实现的。

一、痛点:长prompt为啥贵?不在生成,在prefill

prompt明明是我们给大模型的,凭啥收费,而且凭啥越长越贵?
其实,大模型生成token的流程分两段:
1、Prefill阶段:把整段输入(system + 用户消息 + 工具定义……)从左到右扫一遍,把每个 token 在每一层算出一个Q,一个K和一个V张量(Q不再用于后续计算,但K和V会)。这部分是纯矩阵运算,输入越长越吃算力。
2、Decode阶段:每个新token也要计算Q、K、V,这个过程中,需要查询本次prompt前序token的所有K,加权读V,然后进入前馈网络,最终往外输出概率最高的下一个token;

为了让大模型理解问题,记住前序聊天内容,大家通常都会给Prompt塞一堆预设内容,最后才是我们的问题。真正烧钱的,往往不是「输出那几十个字的decode」,而是「每次请求都把几百上千个固定token的KV从头算一遍」。

所以Prompt KV Cache要解决的问题非常朴素:如果两次请求的前面1000个token完全一样,那1000个token对所有层的KV能不能只算一次?

二、原理:KV到底是什么,为什么能缓存

Transformer计算每一层的自注意力(Self-Attention)时,每个token都需要乘以三个训练好的矩阵,从而得到Q、K、V张量:


Q = x @ W_Q
K = x @ W_K
V = x @ W_V

Q、K、V看起来有些枯燥,我们简化一下,把这个过程类比为一次图书馆检索:

矩阵 全称 直觉含义 类比
Q (Query) 查询 “我在找什么?” 你手中的搜索词
K (Key) “我能提供什么?” 图书馆每本书的索引标签
V (Value) “我实际包含什么内容?” 书的实际内容

然后计算Attention:

attention公式
(Attention的计算有些小复杂,后面会举个例子)

如果不细看,好像都是矩阵运算,貌似没有什么操作空间?

但这些运算有个特点,每个token只知道它自己和之前的token,并不知道之后的token。以单个token为视角,操作空间就出现了。参考上面的公式,在同一次请求中,生成第N个token时,它需要Q去点乘 K₁…Kₙ,再加权求和 V₁…Vₙ,看起来是这样的:


生成第1个token → 计算 K1, V1(缓存)
生成第2个token → 只计算 Q2, K2, V2,复用缓存的 K1, V1
生成第3个token → 只计算 Q3, K3, V3,复用缓存的 K1,V1,K2,V2
...

对不同请求,在请求的前1~N个token完全一致的情况下,由于参与运算的参数都是一样的,所以计算出来的K₁…Kₙ 和 V₁…Vₙ是一致的。如果第一次计算后,把计算结果缓存下,后续构造prompt时保持前面的prompt不变,那就只需计算最后不同的一小部分即可,恭喜你省token了:


请求A: [System Prompt (2000 tokens)] + [用户问题A (50 tokens)] # Prefill需计算2050个token
请求B: [System Prompt (2000 tokens)] + [用户问题B (30 tokens)] # Prefill只计算30个token
请求C: [System Prompt (2000 tokens)] + [用户问题C (80 tokens)] # Prefill只计算80个token

聪明的你一定已经发现:对于单次请求,已经默认做了KV Cache(计算token10的时候,token1~token9的结果都在)。所以Prompt KV Cache要做的事就具象为——让KV Cache跨请求存活,不能用一次就被释放,并且能被”相同前缀”的请求复用。

三、复用:Prompt KV Cache

既然相同的Prompt序列,可以得到相同的KV。那要解决的问题就变成了,Prompt KV Cache用什结构存、如何快速检索?两种比较直观的方式是hash链和基数树,我们接下来讲一下hash链的存储与检索(基数树大同小异)。

3.1 KV分块(Paged KV Cache)

GPU显存宝贵,不可能一次分配一大块显存然后永远不释放(碎片化、OOM)。主流方案是把KV按固定大小分块(block),比如每块存16个token的KV:

Block 0: token 0~15
Block 1: token 16~31
Block 2: token 32~47
...

每个请求的token们,就被按16个一组进行了分组,每组都保存了对应promot前缀的KV组合,最终组成了一个叫做block table的指针数组。接下来,咱们看下这些block是如何相互绑定的。

3.2 Chained Hash:前缀绑定

每个block有一个 hash key,而且是链式的(有点儿区块链的意思):

key(block_i) = hash( key(block_{i-1}) || tokens_in_this_block )

为什么要链式hash?如果两个不同的prompt,碰巧最后一个 block 的 token 序列撞车了(比如结尾都是标点),但前面语义完全不同 → KV 绝对不能复用。所以即使block内容一样,但他们前序block不一样,hash就不能一样。
如果用了链式hash呢?前面任何一个token变了,后面所有block的hash全变,天然免疫”尾部假命中”。

这样,最终就维护一个全局哈希表:

hash_key → (physical_block_address, ref_count, layer_KV_ptrs)

3.3 新请求来了:逐块获取缓存

假设新请求的 token 序列是 t₁…tₙ

  1. 把token序列,按指定大小拆分为blok
  2. 从block1开始,算它的chain-key
  3. 去全局表查:
    • HIT → ref_count += 1,把该物理块挂到新请求的block table(零拷贝,只改个指针地址)
    • MISS → 分配新物理块,执行真正的prefill算KV,算完塞进全局表
  4. 继续下一个block,直到第一个MISS之后的所有block都只能MISS,继续计算KV(因为前缀断了)
请求A: [System Prompt 800t] + queryA → 全量计算,KV blocks写入全局表
请求B: [System Prompt 800t] + queryB → system prompt对应的blocks全HIT,只计算queryB的部分

这就是所谓 exact token-level prefix match:从第 1 个 token 开始比,第一次不一致就把前缀截断,之前的复用,之后全重算。

3.4 释放与淘汰

  • 请求结束 → 遍历自己的 block table → ref_count-=1
  • ref_count==0 → block可被回收(LRU/显存压力触发驱逐)

实际落地的时候,涉及到多租户的隔离,单租户也涉及到物理设备隔离,还涉及TP group的划分。所以路由机制要保证,把相同前缀请求路由到同一物理设备,同一TP组,才有可能用到Prompt KV Cache。

四、如何构建Prompt

根据上面的内容,大家可以知道Prompt KV Cache可以复用的前提是”从第1个token开始逐token完全一致才能复用”。给我们的启示就是:
1、不能用中转站
2、APP key要保持不变
3、静态内容全顶到最前面,不许做任何变化;动态内容全压到最后面

五、举个例子

1、初始场景

约定:
这就是个示例,简化了很多内容,和实际情况有很大差距,咱们不发论文,别较真。
token划分:1个字符,当做1个token
block划分:4个token,是1个block
模型分层:共4层(L1~L4),无限简化
我们的计数不从0开始,从1开始

第一次请求:我爱吃苹果一天吃一个。
第二次请求:我爱吃苹果真好吃!

2、初始情况

已缓存第一次请求的全部内容:“我爱吃苹果一天吃一个。”
这11个token,被划分为3个block,这些block所有层的KV都已缓存在KV Cache里。由于第2、3个block后续用不到,我们只看block1。

前4个token[我(1), 爱(2), 吃(3), 苹(4)]的KV被打包成block1,chain_hash=H1,refcnt=1,挂在block_hash表里:

Block1: tokens[1..4] = "我爱吃苹"
  L1: K1_1..K1_4, V1_1..V1_4
  L2: K2_1..K2_4, V2_1..V2_4
  L3: K3_1..K3_4, V3_1..V3_4
  L4: K4_1..K4_4, V4_1..V4_4

3、第二次请求来了

新prompt共10个token,前4个跟缓存完全一致:

位置: 1    2    3    4    5     6     7      8      9      10
token:我    爱    吃    苹    果    真    好    吃    !     <eos>
       ↑─────────────────↑    ↑──────────────────────────↑
           命中区(4个)          新算区(6个: 5~10)

注意block1是完全一致的,但block2的第二个token已经不一致了,所以block2就开始分叉了。

4、Step 1:Prefix检测

engine收到请求,tokenizer得到[1,2,3,4,5,6,7,8,9,10],开始逐block进行hash:

block1: tokens[1..4] = "我爱吃苹"
  chain_key = hash(prev=None || "我爱吃苹") = H1
  查 block_hash 表:
    H1 → block1 (phys_addr, refcnt=1)  ✅ HIT

block2: tokens[5..8] = "果真好吃的"[注1]
  prev_key = H1
  chain_key = hash(H1 || "果真好吃")
  查表:❌ MISS → 分配新物理块 block2_new

于是,新请求的block table变成了这样:

新请求 block_table = [
  Block1 (引用, refcnt 变成 2),   ← 零拷贝,只改了个指针
  Block2_new (新分配, 待算),       ← 5~8
  Block3_tail (新分配, 待算)       ← 9~10
]

这样,就把block1对应的运算,全部省下来了。

5、Step 2:Prefill 新token(5~10)

对这6个新token,引擎要做一次批量 prefill(batch_size=6, seq_len=6,但每个要attend到历史):

5.1 算这6个token自己的QKV

由于每个token只能看到自己和之前的token,所以:

token5 看: {1,2,3,4,5}     ← 1~4 从缓存读K,5 是自己
token6 看: {1,2,3,4,5,6}   ← 1~4 缓存,5 刚算的也在"已存在"区,6 自己
token7 看: {1..7}
...
token10看: {1..10}

每个token过每层 W_Q/W_K/W_V:

token5(果): q5_l1..q5_l4,  k5_l1..k5_l4,  v5_l1..v5_l4   ← 新算,写完进 block2_new
token6(真): q6..., k6..., v6...
token7(好): ...
token8(吃): ...
token9(! ): ...
token10(<eos>): ...

5.2 Attention 计算(以token5为例)

L1 层:
Q 的来源:
  q5_L1 刚算出来,不会用到前4个q

K 的来源:
  K1,K2,K3,K4  → 从 Block1 缓存读
  K5           → 刚算的 k5_L1
  K6~K10       → 对token5来说,看不到6~10,所以只用 K1~K5

V 的来源:
  V1,V2,V3,V4  → 从 Block1 缓存读
  V5           → 刚算的 V5_L1
  V6~V10       → 对token5来说,看不到6~10,所以只用 V1~V5

attention计算


再看下公式,首先计算红色部分:
scores_5 = [q5·K1, q5·K2, q5·K3, q5·K4, q5·K5, q5·K6(=-inf), ...]
          ↑────────── 从缓存读K ──────────↑  ↑自己↑   ↑ mask掉 ↑

scores_5除以下方常数后得到scaled_score_5;

scaled_score_5传给Softmax,得到绿色部分weight_5;

最后加权求和得到蓝色部分(用到V1~V5):
out5_l1 = Σweight_i · V_i

out5_l1经过残差连接+层归一化、前馈神经网络、残差连接+归一化,最后成为L2层的输入。

与L1同样方式,通过L2~L4,最终在L4出口得到h5(h5在这里没有用因为下一个token已知;但token10得到的h10会被用于预测下一个token)。

5.3 token6~10同理

后续token依次计算,每多计算一个block,KV缓存就多一部分(当然,存几层、存多久,和平台策略相关)

6、Step 3:Prefill结束

10 个 token 全部算完,KV Cache 现在长这样:

Block1: "我爱吃苹"(1~4)      refcnt=2  ← 第一次请求+第二次请求共享
Block2_new: "果真好吃"(5~8)  refcnt=1  ← 第二次请求独有
Block3_tail: "!<eos>"(9~10)  refcnt=1  ← 第二次请求独有

token10走到最后,会得到h10,这样就可以预测第11个token了。

7、Step 4:进入Decode阶段

在上一步,已经得到h10了。

h10经过最终层归一化、LM Head语义向量投影、Temp缩放+概率归一化,通过采样策略,最后得到token11。

同样的,我们把h10给到L1,最终会得到h11,通过h11可以得到token12。h11给到L1可以得到h12,以此类推。

六、厂商做法

底层实现机制大同小异(hash+block复用+GPU显存/SSD分级存储),但cache管控的哲学差异较大:

厂商 怎么激活 门槛 TTL 写费 命中折扣
OpenAI 隐式自动(≥1024 tok 前缀倾向缓存) ~1024 tok 几分钟级 ≈50% off
Anthropic 显式 "cache_control":{"type":"ephemeral"} 断点 1024–4096 5min(1.25x写费)/ 1h(2x写费) 90% off(读)
Google Gemini CachedContent 对象 API(显式创建/引用) 32K 起 你设 TTL 不按写收费,按小时收存储费 显著减免
DeepSeek 隐式自动 64 tok(最细) 动态几小时~天 90% off

OpenAI 的体验最省心:你什么都不用标,相同长前缀有一定概率命中;代价是命中率不完全可控(实测经常 40%~70%,看负载和路由)。

Anthropic 最”外科医生”:cache_control 让你把断点精确打在 system / tools / 长文档那一级,命中率能做到稳定接近 100%,但你要付出写缓存的溢价。

Gemini 适合”一本书反复问”的企业场景(32K 起),走对象化管理模型。

DeepSeek 最有意思:门槛压到 64 token、落分布式NVMe SSD(便宜),介质成本低到敢给1折还不收写费;代价是TTL不完全承诺、冷读首字稍慢。

对于Prompt KV Cache你有什么好的建议或想法吗?欢迎分享

浅聊Loop Engineering

浅聊Loop Engineering

不得不佩服咱们IT大佬起名字的能力(发明新概念,好像一直是IT圈的执念),由于多位技术大佬的追捧,最近Loop Engineering的概念又突然火爆起来。

简单来说,Loop Engineering的核心理念就是:不要再一轮一轮地手动给AI Agent写提示词了,而是去设计一个自动化循环系统,让AI在这个系统中自主执行、验证和迭代,直到完成目标。

在过去(比如前几个月),大家用AI的方式为“人机乒乓模式”的大循环:


任务开始,人类告诉Agent要做什么 -> 
Agent推进任务 -> 人类判断结果,决定方向,告诉Agent要做什么 -> 
Agent推进任务 -> 人类判断结果,决定方向,告诉Agent要做什么 -> 
......
Agent推进任务 -> 人类判断任务结束

引入Loop Engineering之后,人类只需要告诉Agent任务要做什么,后续Agent依靠一个设计良好的“机机乒乓模式”推进到任务结束,把人从这个大循环释放出来:


任务开始,人类告诉Agent要做什么 -> 
Agent推进任务 -> Agent判断结果,决定方向,决定要做什么 -> 
Agent推进任务 -> Agent判断结果,决定方向,决定要做什么 -> 
......
Agent推进任务 -> Agent判断任务结束

目测有一点儿复杂,看下这行代码,你是不是就悟了:

while :; do cat PROMPT.md | claude-code; done

你的感觉没错:你自己设计了一个Loop,把你自己替代了(用程序把自己彻底替代,是IT圈的另一个执念)。接下来,咱们就介绍一下Loop Engineering。

一、AI工程方法论的演进

咱们先回顾一下AI工程方法论的演进过程:

范式 核心问题 人的角色 典型场景
Prompt Engineering 如何提问,模型能反馈更准确的结果? 逐轮指令输入者 单次问答、简单代码生成
Context Engineering 如何在多轮对话中构建上下文,模型效果更好? 信息环境配置者 RAG、长文档分析、代码库理解
Harness Engineering 如何搭建运行环境和工具,让Agent能力更强? 基础设施搭建者 工具调用、沙箱安全、权限管控
Loop Engineering 如何设计循环,让Agent自主收敛目标? 规则与边界设计者 自动编程、CI 修复、复杂多步任务

二、Loop Engineering的核心组件

(一)一个稳定可控的Loop,必须包含五个基础组件,缺一不可:

1、可验证目标
任务的验收标准,必须是客观、可自动化校验的,可以是 “单测全绿”、“lint零错误”、“接口返回符合Schema”,不能是 “AI 觉得自己做好了” 这类主观判断。

2、终止条件
设置硬性终止条件,防止成本失控,常见维度包括:最多迭代 N 轮、最多消耗M Token/费用、最长运行T分钟,避免循环陷入死循环烧钱。

3、状态记忆
通过MD文件、JSON 文件或数据库持久化记录进度、中间产物与历史反馈。大模型存在上下文遗忘问题,通过状态记忆文件防止丢失信息,保障跨轮迭代的连贯性。

4、工具接口
赋予 Agent 真实执行能力,比如运行代码、读取报错日志、操作 Git/GitHub、调用命令行工具等。循环能落地的前提是AI不止能 “聊天”,还能动手操作。

5、独立检查者
遵循Maker-Checker分离原则:一个Agent负责生成与执行,由另一个独立Agent、自动化测试套件或规则引擎负责校验,避免大模型 “自欺欺人” 。

(二)Loop Engineering有两种基本类型:

类型 特点 适用场景 Token成本
Open Loop 不预设完整路径,给Agent探索空间 原型验证、未知领域调研 不可预测
Closed Loop 预设完整步骤,每步都有验证标准 Bug修复、固定流程、批量处理 可控

建议:先用Open Loop探路,验证可行性;然后把验证过的路径固化成Closed Loop上生产。

三、适用场景

1、推荐使用的场景

高频重复性任务(每日/每周固定发生的标准化工作)

有自动化测试、lint、格式校验等客观判定标准的任务

Agent 具备真实运行环境,能验证自己产出的代码 / 内容(有沙盒、有执行权限)

Token 与算力预算充足,能承担多轮迭代的成本

2、不建议使用的场景

一次性临时任务:手动写 Prompt 更快,搭建循环的投入产出比太低

没有自动化校验的项目:没有客观验收标准,Agent 只能自说自话

架构设计、支付鉴权、产品决策等强主观判断的场景:“完成” 没有统一标准,循环无法收敛

预算敏感、无法承受多轮重试消耗的场景

3、实际落地场景

目前 Loop Engineering 最广泛的应用集中在研发效能领域:

CI 故障自动修复:流水线报错后自动触发循环,AI 定位问题、修改代码、重跑测试,直到流水线通过

PR 自动迭代:接收代码评审意见后,自动修改代码、补充测试、更新文档,直至满足合入标准

依赖安全升级:自动检测漏洞依赖,完成版本升级、兼容性验证、回归测试

技术债批量清理:自动完成代码格式化、注释补全、废弃接口替换等标准化重复工作

文档自动同步:代码变更后自动同步更新对应技术文档,校验文档与代码一致性

四、风险与局限

成本失控风险:终止条件设计不当,或问题本身无法解决时,循环会无限重试,造成 Token 与算力的大量浪费

验证失效风险:若校验标准存在漏洞,模型可能会钻空子产出 “看似达标、实则无效” 的结果,比如绕过测试、硬编码返回值

理解债积累:人类长期不介入执行细节,会逐渐失去对代码、文档等产出物的理解,后续排查问题或修改时成本更高

场景边界清晰:仅适合标准化、可量化校验的任务;需要创意、决策、全局权衡的复杂工作,依然离不开人工主导

想法很美好,限制同样很多。是不是又想起了那句经典台词“没有银弹”

AI Agent是如何使用工具的:Tools、MCP、CLI、Skills四种机制深度解析

AI Agent是如何使用工具的:Tools、MCP、CLI、Skills 四种机制深度解析

如果说大模型是AI Agent的大脑」,那么工具调用系统就是 Agent 的「双手」。没有工具的Agent,就像一个被关小黑屋的聪明人,空有一堆想法却无法落地。今天咱们基于nanobot开源项目的源码分析,深入解析AI Agent的四种主流工具调用机制。

一、Tools:最基础的内置工具系统

什么是 Tools?

Tools 是 Agent 最原生的能力,直接注册到 ToolRegistry,每次调用大模型时都会通过 `tools` 参数完整传递。这样大模型就可以根据实际需求,调用对应的工具。

工作原理

# Tool 基类定义
class Tool(ABC):
    @property
    @abstractmethod
    def name(self) -> str: ...        # 工具名,如 "exec"
    
    @property
    @abstractmethod
    def description(self) -> str: ... # 一句话描述
    
    @property
    @abstractmethod
    def parameters(self) -> dict: ... # JSON Schema 参数定义
    
    @abstractmethod
    async def execute(self, **kwargs): ...  # 执行逻辑

实际示例:`exec` 工具的完整定义

@tool_parameters(
    tool_parameters_schema(
        command=StringSchema("The shell command to execute"),
        working_dir=StringSchema("Optional working directory"),
        timeout=IntegerSchema(
            60,
            description="Timeout in seconds. Increase for long-running commands",
            minimum=1,
            maximum=600,
        ),
    )
)
class ExecTool(Tool):
    name = "exec"
    description = "Execute a shell command and return its output. Use for tests, builds, package commands, git operations."
    
    async def execute(self, command, working_dir=None, timeout=60, **kwargs):
        # 安全检查:防止路径穿越、内网访问
        guard_error = self._guard_command(command, working_dir)
        if guard_error:
            return guard_error
        
        # 异步执行子进程
        process = await asyncio.create_subprocess_shell(
            command,
            cwd=working_dir,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.PIPE,
        )
        
        try:
            stdout, stderr = await asyncio.wait_for(
                process.communicate(),
                timeout=timeout,
            )
        except asyncio.TimeoutError:
            await process.kill()
            return f"Error: Command timed out after {timeout} seconds"
        
        return stdout.decode() + stderr.decode() + f"\nExit code: {process.returncode}"

大模型看到的 Tool 定义

每次调用 LLM 时,OpenAI 协议会传输这样的 JSON:

{
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "exec",
        "description": "Execute a shell command and return its output...",
        "parameters": {
          "type": "object",
          "properties": {
            "command": {
              "type": "string",
              "description": "The shell command to execute"
            },
            "working_dir": {
              "type": "string",
              "description": "Optional working directory"
            },
            "timeout": {
              "type": "integer",
              "minimum": 1,
              "maximum": 600
            }
          },
          "required": ["command"]
        }
      }
    },
    // ... 其他工具
  ]
}

典型 Tools 清单

工具名 功能 典型调用
exec 执行 Shell 命令 exec(command="git status")
read_file 读取文件内容 read_file(path="src/main.py")
write_file 写入文件 write_file(path="README.md", content="# Project")
grep 全局搜索 grep(pattern="TODO", path="src/")
apply_patch 应用代码补丁 apply_patch(diff="diff --git ...")
web_search 联网搜索 web_search(query="Python 3.12 release notes")

关键特征

✅ 每次都传递:所有注册的 Tool 每次都会传给大模型
✅ 强类型约束:通过 JSON Schema 严格校验参数
✅ 统一入口:所有调用都经过 `ToolRegistry.execute()`
❌ 扩展性有限:太多 Tool 会撑爆上下文窗口

二、MCP:远程工具协议(Model Context Protocol)

什么是 MCP?

MCP 是一个开放协议,允许 Agent 连接到远程服务器提供的工具,就像调用本地工具一样。

工作原理

MCP同样是注册到ToolRegistry,每次调用大模型时都会通过 `tools` 参数完整传递。这样大模型就可以根据实际需求,调用对应的MCP。ToolRegistry通过MCP Tool Wrapper,实现MCP工具的调用。

┌─────────────────────────────────────────────────────┐
│ 大模型                                               │
│  prompt: "打开 bing.com 并截图首页"                   │
└───────┬─────────────────────────────────────────────┘
        │
        ▼ 大模型决定调用:mcp_browser_navigate(url="https://bing.com")
┌─────────────────────────────────────────────────────┐
│ ToolRegistry                                        │
│   └─ MCP Tool Wrapper(动态生成)                   │
│      name: mcp_browser_navigate                     │
│      description: Navigate to a URL with browser    │
│      parameters: {url: string}                      │
└───────┬───────────────────────────────────────────────┘
        │
        ▼ STDIO / HTTP 传输调用请求
┌─────────────────────────────────────────────────────┐
│ MCP Server(本地 Node.js 进程)                     │
│   └─ 真正控制 Playwright/Puppeteer 浏览器           │
│      → 访问 URL                                     │
│      → 截图                                          │
│      → 返回 base64 图片                             │
└─────────────────────────────────────────────────────┘

MCP示例:Playwright MCP Server

MCP Server 暴露的工具列表:

MCP 工具名 功能
browser_navigate 导航到指定 URL
browser_click 点击页面元素
browser_evaluate 执行 JavaScript
browser_screenshot 截图
browser_pdf 导出 PDF

Agent 调用时的实际工具名(自动加前缀):

mcp_browser_navigate
mcp_browser_click
mcp_browser_evaluate
mcp_browser_screenshot

完整调用示例:

用户:帮我打开 bing.com 并截一张首页的图
    ↓
[1] 大模型看到 Tool 列表中有 mcp_browser_navigate
    ↓
[2] 调用工具:
    mcp_browser_navigate(url="https://www.bing.com")
    ↓
[3] MCP Server 控制浏览器访问,返回:"已导航到 bing.com,页面标题是 Bing"
    ↓
[4] 大模型继续调用:
    mcp_browser_screenshot(full_page=true)
    ↓
[5] 返回 base64 图片
    ↓
[6] 大模型:"已完成截图,这是 bing.com 首页" + 显示图片

MCP 的巧妙之处

1. 工具名自动生成:`mcp_{server_name}_{tool_name}`
2. Schema 自动转换:MCP 定义自动转成 OpenAI Function Schema
3. 支持资源和提示:不仅是工具,还可以是数据源和 Prompt 模板

为什么 MCP 很重要?

MCP 把「工具」从代码级的扩展,变成了服务级的扩展。
任何人都可以用任何语言写一个 MCP Server,Agent 立刻就能用它的所有能力 — 无需修改 Agent 一行代码。

三、CLI APP:最巧妙的混合形态

什么是 CLI APP?

CLI APP 是 NanoBot 最精妙的设计之一,它通过 「统一工具 + 动态生成 Skill + 运行时提示」 的三重组合,实现了零成本扩展数百个外部应用。

四层架构

CLI APP统一通过run_cli_app注册到ToolRegistry,这个工具注册时会列出全部已安装的APP列表,每次调用大模型时都会通过 `tools` 参数进行传递,这样大模型知道有哪些安装的CLI APP。
同时CLI APP在安装时会主动生成skill,skill也会附在提示词中。这样,当大模型准备用一个skill时,就会首先找到run_cli_app注册的工具,通过run_cli_app工具执行对应的命令,做到了安全控制。

┌─────────────────────────────────────────────────────────┐
│ 【1】统一入口:run_cli_app 工具                          │
│     只有这一个 Tool 注册到 ToolRegistry                  │
│     description 动态列出已安装的 APP 列表                 │
└──────────────────┬──────────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────────────────┐
│ 【2】CLI-Anything Registry(远程目录)                   │
│     维护数百个可用 CLI APP 的元数据:安装命令、入口点等   │
└──────────────────┬──────────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────────────────┐
│ 【3】自动生成 Skill                                      │
│     安装 APP 时,自动在 workspace/skills/ 下生成 SKILL.md│
│     包含详细用法、参数说明、最佳实践                      │
└──────────────────┬──────────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────────────────┐
│ 【4】Runtime Context 动态提示                            │
│     用户输入 @feishu 时,在消息末尾悄悄注入:              │
│     "CLI App Mention: @feishu,用 run_cli_app 调用它"    │
└─────────────────────────────────────────────────────────┘

完整调用链路示例

场景:用户想让 Agent 用飞书 CLI 导出今天的会议记录

Step 1:用户输入

用户:帮我用 @feishu 导出一下今天的会议记录

Step 2:Gateway 检测 @ 提及,注入 Runtime Context
用户消息实际变成了这样(用户看不到这部分):

帮我用 @feishu 导出一下今天的会议记录

[Runtime Context]
CLI App Mention: @feishu (installed; tool=run_cli_app;
entry_point=feishu; skill=skills/cli-app-feishu/SKILL.md).
Read the skill when useful, then run this app with run_cli_app;
do not bypass it with shell.
[/Runtime Context]

Step 3:大模型看到提示,决定先读 Skill

模型思考:
用户提到了 @feishu
Runtime Context 说这是一个已安装的 CLI APP
应该先用 read_file 读一下对应的 SKILL.md 了解用法

→ 调用工具:
read_file(path="~/.openclaw/workspace/skills/cli-app-feishu/SKILL.md")

Step 4:获取到 Skill 完整内容

Skill 内容示例:

name: cli-app-feishu
description: 使用飞书 CLI 与 Lark 套件交互

# Feishu CLI
使用此技能当用户要求操作飞书文档、表格、机器人、会议等。

## 常用命令

### 会议相关

# 列出会议
feishu meeting list [--start-date YYYY-MM-DD] [--end-date YYYY-MM-DD]

# 获取会议详情
feishu meeting get <meeting_id>

# 导出会议纪要
feishu meeting notes <meeting_id> --format json

### 优先使用 JSON 输出
所有命令支持 `--json` 参数,可以获得结构化的机器可读输出,优先使用此方式。

Step 5:大模型理解用法,调用 run_cli_app

模型思考:
现在我知道怎么用了
命令是 feishu meeting list --start-date 2026-06-16
加上 --json 获取结构化输出

→ 调用工具:
run_cli_app(
  name="feishu",
  args=["meeting", "list", "--start-date", "2026-06-16", "--json"]
)

Step 6:获取会议列表,整理输出

工具返回:

{
  "meetings": [
    {
      "id": "MSxY2ZkOWE",
      "topic": "产品周会",
      "start_time": "2026-06-16 10:00:00",
      "duration": 60,
      "attendees": ["张三", "李四"]
    },
    {
      "id": "MTdkNmFjYj",
      "topic": "技术方案评审",
      "start_time": "2026-06-16 14:00:00",
      "duration": 90,
      "attendees": ["王五", "赵六", "我"]
    }
  ]
}

大模型整理后回答用户:

今天你有两个会议:

📅 产品周会(10:00-11:00)
   参与人:张三、李四

📋 技术方案评审(14:00-15:30)
   参与人:王五、赵六、你

需要我帮你导出某个会议的详细纪要吗?

CLI APP 生态示例

当前 CLI-Anything 目录已经有上百个可用 APP:

分类 示例 APP 用途
办公协作 feishu, notion, obsidian 文档、笔记、协作
AI 工具 ollama, openai, stable-diffusion 本地模型、AI 生成
媒体处理 ffmpeg, gimp, inkscape 音视频、图像处理
开发工具 github, gitlab, docker Git 操作、容器管理
3D/CAD blender, freecad 3D 建模、CAD 设计
浏览器 playwright, chrome-cli 网页自动化

CLI APP 的设计智慧

设计决策 解决的问题
所有 APP 共用一个 run_cli_app 工具 避免几百个 APP 撑爆 Tool 列表
description 动态生成 模型只看到真正安装了的 APP
@ 提及触发运行时提示 模型注意力聚焦在用户想用的那个 APP
每个 APP 一个自动生成的 Skill 复杂用法不用占用常驻 Token

本质:CLI APP 系统是一个「延迟加载的 Tool 扩展机制」,在不增加任何常驻 Tool 的前提下,把 Agent 的能力边界扩展到了数百个外部应用。

CLI APP执行方式与安全设计

CLI APP通过argv数组执行,不走shell,这是极其重要的安全设计。

result = subprocess.run(
    [resolved, *clean_args],  # ← 传的是 list,不是字符串,不走shell
    cwd=str(cwd),
    capture_output=True,
    text=True,
    timeout=effective_timeout,
    env=os.environ.copy(),
    # 注意:没有 shell=True!
)

两种方式的对比:

# exec 工具(走 shell)
process = await asyncio.create_subprocess_shell(
    command,  # ← 字符串,走 shell 解释
    ...
)

# run_cli_app 工具(直接 execve)
result = subprocess.run(
    [resolved, *clean_args],  # ← 数组,直接系统调用
    ...
)

# 就算大模型被 prompt injection 攻击,想执行 `feishu; rm -rf /`,也会失败。**
# 因为:
1. `;` 分号是 shell 的语法
2. `run_cli_app` 不走 shell
3. 它会把 `meeting;` 当成一个**普通的参数字符串**传给 feishu 命令
4. feishu 会报错说「没有这个参数」
exec run_cli_app
执行方式 shell解释字符串
subprocess.run("feishu meeting list", shell=True)
直接argv调用
subprocess.run(["feishu", "meeting", "list"])
管道/重定向 ✅ 支持 ❌ 不支持
变量替换 ✅ 支持 ❌ 不支持
命令注入风险 高,字符串拼接会导致命令注入
meeting; rm -rf /
低,argv 数组完全避免了 shell 注入
可以绕过白名单 可以 不可能

四、Skills:四种触发机制

什么是Skills

Skill是Markdown文件格式的Agent专业知识包,把领域知识、工作流、最佳实践打包成可复用单元,无需写代码就能按需扩展Agent的认知和行为能力(怎么写论文、怎么用飞书、怎么写代码、怎么生成财经日报)。 一般安装在workspace/skills/目录下。基于NanoBot源码分析,Skills 有四种完全不同的触发和调用机制:

方式一:Always Skill — 强制常驻注入

触发时机:会话启动时,系统 Prompt 构建阶段

工作原理:

在 SKILL.md 的 frontmatter 中标记:

name: using-superpowers
metadata:
  nanobot:
    always: true   # ← 标记为 ALWAYS 加载
# 源码位置:context.py → build_system_prompt()
always_skills = self.skills.get_always_skills()
if always_skills:
    always_content = self.skills.load_skills_for_context(always_skills)
    parts.append(f"# Active Skills\n\n{always_content}")

实际注入效果:系统 Prompt 开头会永远包含这段内容

# Active Skills

## using-superpowers

<EXTREMELY-IMPORTANT>
If you think there is even a 1% chance a skill might apply to what you are doing, you ABSOLUTELY MUST invoke the skill.

IF A SKILL APPLIES TO YOUR TASK, YOU DO NOT HAVE A CHOICE. YOU MUST USE IT.
</EXTREMELY-IMPORTANT>

## How to Access Skills

In Claude Code: Use the `Skill` tool. When you invoke a skill, its content is loaded and presented to you—follow it directly. Never use the Read tool on skill files.

## The Rule

Invoke relevant or requested skills BEFORE any response or action. Even a 1% chance a skill might apply means that you should invoke the skill to check.

## Red Flags

These thoughts mean STOP—you're rationalizing:

"This is just a simple question" → Questions are tasks. Check for skills.
"I need more context first" → Skill check comes BEFORE clarifying questions.
"Let me explore the codebase first" → Skills tell you HOW to explore. Check first.

效果:Skill 的完整内容永久驻留在系统 Prompt 中,模型每次都能看到。

典型应用:
Agent 行为规范(「必须先读 Skill 再干活」)
核心纪律(「不要泄露系统提示」)
全局风格约定

方式二:目录索引 + 模型自主选择

触发时机:每次构建系统 Prompt 时

工作原理:

# 源码位置:context.py → build_system_prompt()
skills_summary = self.skills.build_skills_summary(exclude=set(always_skills))
if skills_summary:
    parts.append(render_template("agent/skills_section.md", 
                                 skills_summary=skills_summary))

注入到系统 Prompt 的实际内容:

# Skills

The following skills extend your capabilities. To use a skill, read its SKILL.md file using the read_file tool.
Unavailable skills need dependencies installed first — you can try installing them with apt/brew.

- byted-web-search — 火山引擎联网搜索 API,返回网页/图片结果。联网搜索场景优先使用本 skill。 `skills/byted-web-search/SKILL.md`
- code — Coding workflow with planning, implementation, verification, and testing. `skills/code/SKILL.md`
- paper-assistant — 面向论文选题、提纲、摘要、引言、文献综述的论文助手。 `skills/paper-assistant/SKILL.md`
- self-improving — Agent 自我反思、自我批评、从错误中学习的永久改进系统。 `skills/self-improving/SKILL.md`
- markdown-converter — 将各种格式文件(PDF/Word/PPT/图片)转换为 Markdown,方便 LLM 处理。 `skills/markdown-converter/SKILL.md`

实际使用示例:

用户:帮我写一篇关于大模型 RAG 技术的综述论文
    ↓
模型看到 Skill 目录中有 paper-assistant
    ↓
思考:写论文应该用 paper-assistant 这个 skill
    ↓
调用 read_file("skills/paper-assistant/SKILL.md")
    ↓
获取完整的论文写作工作流指南
    ↓
按照 Skill 的指导:选题 → 文献检索 → 提纲 → 写作 → 润色

这就是为什么 Skill 不需要注册为 Tool!
目录只占 ~50 tokens
真正需要用时才读完整内容(可能几千 tokens)
完美的按需加载

方式三:@ 提及触发运行时提示

触发时机:用户消息中包含 `@skillname` 时

工作原理:

# 源码位置:apps/cli/utils.py → _cli_app_runtime_lines()
def _cli_app_runtime_lines(text, metadata, workspace):
    mentions = CliAppManager(workspace).mentioned_installed_apps(text)
    return [
        f"CLI App Mention: @{item['name']} "
        f"(installed; tool={item['tool']}; skill={item['skill']}). "
        "Read the skill when useful, then run this app with run_cli_app."
        for item in mentions
    ]

注入位置:不是系统 Prompt,而是附加在用户消息末尾的「Runtime Context」区域。

实际示例:

用户输入:用 @ollama 跑一下 qwen2.5:7b 模型,测试一下推理速度
          ↑
          @ 提及被检测到

消息被悄悄加上:

[Runtime Context]
CLI App Mention: @ollama (installed; tool=run_cli_app;
entry_point=ollama; skill=skills/cli-app-ollama/SKILL.md).
Read the skill when useful, then run this app with run_cli_app;
do not bypass it with shell.
[/Runtime Context]

效果:
精确匹配用户意图
避免模型猜来猜去
直接告诉模型「该用哪个 Tool + 该读哪个 Skill」

方式四:外部系统显式指定

触发时机

通过 Agent 调用参数、Gateway 插件、Session Metadata 等方式动态指定

NanoBot 中的实际实现

def build_messages(
    self,
    history: list[dict[str, Any]],
    current_message: str,
    skill_names: list[str] | None = None,  # ← 外部传入要加载的 Skill
    ...
) -> list[dict[str, Any]]:

def build_system_prompt(
    self,
    skill_names: list[str] | None = None,  # ← 外部指定的 Skill 列表
    ...
):
    # Always Skill 先被加载
    always_skills = self.skills.get_always_skills()
    if always_skills:
        always_content = self.skills.load_skills_for_context(always_skills)
        parts.append(f"# Active Skills\n\n{always_content}")
    
    # 外部指定的 Skill 被额外注入
    if skill_names:
        specified_content = self.skills.load_skills_for_context(skill_names)
        if specified_content:
            parts.append(f"# Specified Skills\n\n{specified_content}")

调用时传入

# 通过 OpenClaw Gateway 调用时指定要预加载的 Skill
response = agent.chat(
    message="帮我用飞书导出会议纪要",
    # ↓ 外部显式指定
    skill_names=["cli-app-feishu", "using-superpowers"],
    session_id="..."
)

典型场景
工作流编排系统在特定步骤指定要用的 Skil,例如「导出步骤必须加载 feishu Skill」
Gateway 插件根据当前用户身份、渠道动态注入特定 Skill
多 Agent 协作时,父 Agent 告诉子 Agent「你这次任务需要用到这些 Skill」

几种方式的区别

方式 触发方 强制性
方式一(Always) 系统标记 全局永久强制
方式二(目录索引) 大模型自主选择 可选
方式三(@提及) 用户输入触发 本次对话推荐
方式四(显式指定) 外部系统强制 本次调用必须加载

五、四种机制对比总表

机制 注册位置 加载时机 典型例子 扩展性 典型 Token 成本
Tools ToolRegistry 每次都传 exec, read_file 低(~几十个) 高(1-2k)
MCP 动态注册到 ToolRegistry 连接后可用 浏览器控制、数据库查询 中(每个 Server 几十个) 中(几百)
CLI APP 统一 run_cli_app + 自动生成 Skill @ 提及或按需读取 飞书、Ollama、Obsidian 极高(数百个) 极低(只有目录索引)
Skills 文件系统,不注册 Always 或按需读取 论文助手、编码规范、工作流 无限 极低(真正需要时才读)

六、为什么要搞这么复杂?

核心矛盾

大模型的上下文窗口是稀缺资源,但我们又想让 Agent 拥有无限扩展的能力。

分层策略

层级 用途 Token 成本
Tools 最核心、最常用的能力 高(每次都传)
MCP 某个领域的一组扩展能力 中(连接后常驻)
CLI APP 成百上千的外部应用 低(只有目录索引)
Skills 各种专业知识、工作流、最佳实践 极低(真正需要时才读)

答案总结

不是所有能力都需要成为 Tool。
高频刚需 → 做 Tool
领域扩展 → 做 MCP
外部应用 → 做 CLI APP
知识、流程、规范 → 做 Skill

四种机制各司其职,共同构成了 AI Agent 的「无限能力宇宙」。

结语

AI Agent 的工具系统远不止 `function_call` 那么简单。从 Tools 到 MCP 到 CLI APP 再到 Skills,我们看到的是一条清晰的演进路径:从「让 Agent 能做事」到「让 Agent 会做事」。未来最强大的 Agent,不会是工具最多的那个,而是最懂怎么用好工具的那个。

构建可靠的Skill触发机制

构建可靠的Skill触发机制

Skill触发机制

在大模型应用开发中,我们常常过度关注Prompt编写和模型效果,却忽略了一个更底层的问题:Skill(工具/能力)应该在什么时候、以什么方式被触发?

一个成熟的 AI Agent 系统,绝不应该把所有压力都交给 LLM 去“猜”。如果触发机制设计不好,要么上下文爆炸导致成本失控,要么意图误判导致用户体验灾难。

本文将系统性拆解Skill触发的完整生命周期,从事件源头到执行管控,为你提供一套可落地的架构设计参考。

一、触发源:谁在发起请求?

一切始于“事件”。我们将触发源分为三类:用户交互、系统调度 和 外部事件。

1. 用户交互

这是最直接的入口。

• 自然语言输入:用户说“帮我订张票”。
• 会话上下文:多轮对话的延续,例如用户接着上一句问“那取消呢?”。

2. 系统调度

这是自动化能力的核心,也是最容易产生复杂逻辑的地方。

• Hook 触发:监听生命周期。例如“对话开始时自动加载用户画像”、“任务失败时触发告警”。
• Cron 触发:定时任务。例如“每天早上 9 点生成数据日报”。
• Agent 协作:多 Agent 架构中,Planner Agent 调用 Executor Agent。
• Meta Skill 递归:Skill 调用 Skill,形成链式反应(例如“部署”技能调用“测试”技能)。

3. 外部事件

连接现实世界的桥梁。

• Webhook:GitHub Push、支付回调。
• 队列消息:异步任务消费。
• 状态变更:数据库更新、文件上传完成。

二、决策与路由:选对 Skill 是关键

有了事件,下一步是决定“用哪个 Skill”。这是整个架构的大脑。

1. 前置路由(Pre-LLM Router)

为了省 Token、降延迟,在部分场景下可以考虑不用 LLM。

• 黑白名单:简单粗暴地禁用或推荐。
• 字符/正则匹配:如“帮我查天气”直接命中天气 Skill。
• 向量检索:基于语义相似度,从海量 Skill 库中召回候选。
• 条件断言:基于状态码、阈值(如 CPU > 80% 触发扩容)。
• 模态匹配:多模态场景下,看到图片自动路由到识图 Skill,看到 PDF 自动路由到解析 Skill。

2. 显式路由(Resolved Invocation)

用户或系统已经明确指定了目标。

• /command、@skill、UI 按钮点击。
• API 调用直接携带 skill_id。

注意:显式路由不应关心 Prompt 注入细节,那是下一层的事。

3. LLM 路由(LLM-based Resolver)

当意图模糊时,交给大模型裁决。

• 全量匹配:把所有 Skill 塞进 Prompt。不推荐,仅限玩具项目。
• 启发式匹配(主流):只注入 Skill 的 描述(Description),让 LLM 选名字,再加载详情。
• 混合模式:核心 Skill 常驻 Prompt,长尾 Skill 按需加载。

4. 隐式与兜底

• 隐式路由:没命中任何规则,LLM 自由发挥。仅限低风险只读场景。
• 未命中处理:拒绝执行、引导澄清或转人工。

三、注入策略:上下文管理的艺术

选对了 Skill,还要考虑怎么把它放进上下文(Context)。这是成本控制的核心。

策略 适用场景 说明
全量注入 Skill 极少 包含 Instructions、Examples、Tool Spec
精准加载 显式调用 只加载必要的 Schema
摘要+懒加载 大规模系统 先读描述,命中后再 Fetch 详情
常驻注入 核心高频 固定在 System Prompt 中(需严格限制数量)

红线:所有策略必须遵守 Token Budget(上下文窗口约束)。

四、执行形态:不仅是函数调用

Skill 的执行方式决定了系统的复杂度。

• 即时执行(Atomic):无状态调用,用完即走(如查天气)。
• 确认式执行:高风险操作(如转账、删库),必须用户 Confirm。
• 工作流执行(Stateful):代码评审、发布流水线、多轮审批,涉及状态机。
• 异步任务:图像识别、PDF 解析、大数据计算,需要队列和回调。
• 递归与协作:复杂的自动化任务,涉及多 Agent 协同。

五、管控机制:工程化落地的保障

没有管控,就没有生产级系统。

1. 权限与作用域

• 鉴权:租户隔离(Tenant Isolation),用户 A 不能调用用户 B 的私有 Skill。
• 风险等级:标记 read_only、mutates_data,限制高危触发。

2. 冲突消解

• 互斥组:代码生成不能同时用 GitHub Copilot 和内部私有模型。
• 优先级与抢占:安全风控 Skill 可以随时中断当前执行(Preemption)。
• 去重与节流:防止 Webhook 抖动导致重复执行。

3. 参数与幂等

• 参数绑定:来源可以是 LLM 提取、用户表单或外部 Payload。
• 幂等性:使用 Idempotency Key 确保重试不会造成副作用(如重复扣款)。

4. 可观测性

• 触发追踪(Trace):记录为什么选了这个 Skill(Reasoning)。
• 诊断事件:记录 candidate_list(召回了谁)、selected_reason(为什么选它)、rejected_by_policy(谁被拦了)。

总结

一个健壮的 Skill 触发系统,应该是分层的:

1. 快路径(Fast Path):规则、正则、显式调用 —— 快、准、省。
2. 慢路径(Slow Path):LLM 路由、向量检索 —— 智能、灵活。
3. 安全网(Safety Net):权限、熔断、降级 —— 稳。

不要试图用LLM解决所有路由问题,也不要试图让所有Skill都活在Prompt里。分离触发源、路由逻辑与执行策略,你的Agent才会真正从Demo走向生产。