Will It Focus:相机自动对焦兼容性查询 Agent
这是一个面向摄影器材的自动对焦兼容性查询工具,但它真正的看点在于工程实现:如何让模型引用厂商原文时,保证引用的确实是厂商的原话。
作者在拍片时遇到过 Canon T5i 自动对焦失准的问题,而 Canon 官方手册第 100 页其实写明了原因。Will It Focus 把这类问题做成了可查询的 Agent:给定机身、镜头和转接环,分别回答取景器拍照、实时取景和视频三种模式下能否自动对焦,以及对焦失败的原因。跨卡口和转接环也支持,比如「Sigma 35mm Art 通过 MC-11 接在 Sony a7 IV 上,AF-C 能用吗」。
工具也有明确边界:闸门只能证明引文存在于它引用的记录中,不能证明 Agent 为你的具体组合引用了正确的记录。数据集规模有限,覆盖 Canon T5i 时期、Sigma MC-11、Metabones Mark V 和 Canon EF-EOS R 转接环。五个法兰距数据来自 Wikipedia,因为厂商页面没有给出,相关记录中已注明。
这是一个面向摄影器材的自动对焦兼容性查询工具,但它真正的看点在于工程实现:如何让模型引用厂商原文时,保证引用的确实是厂商的原话。
作者在拍片时遇到过 Canon T5i 自动对焦失准的问题,而 Canon 官方手册第 100 页其实写明了原因。Will It Focus 把这类问题做成了可查询的 Agent:给定机身、镜头和转接环,分别回答取景器拍照、实时取景和视频三种模式下能否自动对焦,以及对焦失败的原因。跨卡口和转接环也支持,比如「Sigma 35mm Art 通过 MC-11 接在 Sony a7 IV 上,AF-C 能用吗」。
核心机制是一道引用闸门:模型输出后,代码会回到它引用的 Sanity 记录里逐句比对。如果引文与记录中的完整句子一致,页面会像裂像对焦屏合焦一样对齐显示;如果模型改写了原话,页面会明确标注,并在下方给出记录中的真实句子。如果没有任何厂商资料覆盖该组合,Agent 直接拒答,而不是猜测「能装就能对焦」。
数据层用了两个 Sanity Context 端点,通过 MCP 接入同一个 Agent。一个以 GROQ 模式提供数据集,负责给出结论;另一个提供由六份厂商 PDF 构建的知识库,包括 388 页的 T5i 手册、两份 18-55 套机镜头说明书、Canon Canada 2013 年 T5i 发布稿,以及 Sigma 的两份 MC-11 兼容性表。Schema 共 131 份文档、七种类型:35 个机身、34 支镜头、10 个卡口、5 个转接环、3 种传感器画幅、36 条兼容性记录和 8 条对焦注意事项。自动对焦按拍摄方式拆成 viewfinderPhoto、liveViewPhoto、video 三个字段,因为同一机身在不同模式下用的是不同硬件。
评测用 20 个答案可在厂商表格和手册中查到的问题,在 gpt-5.4-mini 上跑了三种配置。仅用知识库:43 个结论中 37 个与厂商一致,10 段引文中只有 1 段能在厂商文档中逐字找到。仅用数据集:39/43 和 28/31。两者同时使用(即应用实际运行的配置):38/43 和 21/22。结论准确率差距不大,真正的差别在于引号里的词是否真的属于厂商。评测还暴露了两个作者自己的错误:Sigma 的 DMF 列最初存在自由文本备注里,Agent 查不到,所有 DMF 问题都答「未覆盖」,现已改为类型化字段;小模型因为「Sony a7 IV」匹配不上别名「a7 IV」而漏掉记录,随后自行编造了 id,现在机身和镜头都带上了用户实际会输入的名称,并明确禁止 Agent 构造 id。
工具也有明确边界:闸门只能证明引文存在于它引用的记录中,不能证明 Agent 为你的具体组合引用了正确的记录。数据集规模有限,覆盖 Canon T5i 时期、Sigma MC-11、Metabones Mark V 和 Canon EF-EOS R 转接环。五个法兰距数据来自 Wikipedia,因为厂商页面没有给出,相关记录中已注明。
Python 虚拟环境其实是个软链接
在 Linux 和 macOS 上执行
这意味着一旦系统 Python 被升级、移动或删除,venv 可能直接失效,也可能在毫无提示的情况下改用另一个 Python 继续运行。
这种设计是有意为之:venv 追求轻量、创建快、可随时丢弃,系统打安全补丁时所有 venv 自动受益。代价是 venv 的稳定性完全取决于它指向的那个解释器。
应对办法是掌控自己的基础解释器:用 uv 或 pyenv 安装固定版本的 Python 再建 venv,锁定依赖并随时重建;Docker 多阶段构建时保证运行阶段与构建阶段使用相同基础镜像和相同路径;目标机器可能没有 Python 时,直接连同解释器一起打包分发。
在 Linux 和 macOS 上执行
python3 -m venv .venv,并不会复制一份 Python。venv 里的 python 只是一个软链接,指回系统解释器;标准库同样没有被复制,真正属于 venv 的只有 site-packages 里的第三方包。这意味着一旦系统 Python 被升级、移动或删除,venv 可能直接失效,也可能在毫无提示的情况下改用另一个 Python 继续运行。
用ls -la .venv/bin/能看到 python、python3、python3.12 全是软链接,readlink -f追下去最终都指向/usr/bin/python3.12。venv 的 pyvenv.cfg 里写着home = /usr/bin,Python 启动时靠它找到真正的解释器和标准库。
实测一个 venv 目录约 12M,几乎全是 pip;解释器和几十兆的标准库都不在里面。把 venv 改名或移动后,pip 等命令行脚本会因 shebang 里的绝对路径失效而报 bad interpreter,venv 不可迁移。
用--copies建 venv 只复制了二进制,标准库仍是借用的。当 base Python 消失时,软链接版直接报 No such file or directory,而 --copies 版会静默回退到编译时的 prefix,可能悄悄换用另一套标准库,引发难以排查的 ImportError。
这种设计是有意为之:venv 追求轻量、创建快、可随时丢弃,系统打安全补丁时所有 venv 自动受益。代价是 venv 的稳定性完全取决于它指向的那个解释器。
应对办法是掌控自己的基础解释器:用 uv 或 pyenv 安装固定版本的 Python 再建 venv,锁定依赖并随时重建;Docker 多阶段构建时保证运行阶段与构建阶段使用相同基础镜像和相同路径;目标机器可能没有 Python 时,直接连同解释器一起打包分发。
2,916 个 AI 网站 8 月流量:转录与图生 3D 起飞
Anjin Radar 追踪了 2,916 个 AI 网站的月度访问量,数据截至 2026 年 9 月 28 日。访问量为第三方流量估算,非站点自行公布。8 月合计访问量从 7 月的约 3.758 亿升至 3.930 亿,环比增长 4.6%,其中 1,731 个站点(约 59%)实现环比增长。
几个可复用的规律:seevio.ai 于 7 月 20 日上线,围绕一个热门视频模型家族定位,访问量从 7 月约 3.2 万涨到 8 月约 112 万。iamtypist.dev 从 6 月约 5,600 涨到 7 月约 75.9 万、8 月约 661 万,hinoter.com 从 7 月约 9.9 万涨到 8 月约 248 万,两者 80% 以上流量来自自然搜索。flowmusic.app 在 6、7 月持平于约 300 万后 8 月翻倍至约 619 万,vocuno.com 把歌曲生成与声音克隆、分轨、母带和分发打包在一起。hi3d.ai 连续三个月增长(约 108 万 → 175 万 → 272 万),但自然搜索流量仅约 10%,作者推测其传播来自社交与创作者社区,并注明这是基于流量结构的推断而非直接测量。
对开发者而言:选方向时,转录/纪要与图生 3D 在 8 月有增长动能,图像生成已饱和;面向明确搜索词的免费工具仍是较可靠的获客路径;复制快速增长竞品前,先看它的流量来自搜索、社交还是直接访问。
Anjin Radar 追踪了 2,916 个 AI 网站的月度访问量,数据截至 2026 年 9 月 28 日。访问量为第三方流量估算,非站点自行公布。8 月合计访问量从 7 月的约 3.758 亿升至 3.930 亿,环比增长 4.6%,其中 1,731 个站点(约 59%)实现环比增长。
分类增长差异明显:文本生成(含转录、会议纪要)+35.3%,3D 建模 +34.6%,音频与音乐 +19.0%,聊天与陪伴 +4.8%,设计 +3.3%,视频制作 +1.6%,AI 检测与去 AI 化持平,图像生成 −1.5%,生产力与办公 −2.8%。图像生成仍是最拥挤的赛道,且略有下滑;增长主要流向转录工具和图生 3D 工具。
增速前十(8 月访问量 100 万以上):seevio.ai 视频生成约 112 万、+3,414%;hinoter.com 转录与会议纪要约 248 万、+2,416%;iamtypist.dev 免费音视频转录约 661 万、+771%;senzia.cc 视频/图像/音频生成约 110 万、+189%;flowmusic.app AI 音乐制作约 619 万、+105%;whif.io 韩语角色聊天约 154 万、+70%;frendi.ai 俄语角色聊天约 327 万、+63%;vocuno.com AI 音乐制作约 102 万、+56%;hi3d.ai 图生 3D 模型约 272 万、+55%;isekaizero.ai AI 角色扮演与互动故事约 189 万、+44%。
几个可复用的规律:seevio.ai 于 7 月 20 日上线,围绕一个热门视频模型家族定位,访问量从 7 月约 3.2 万涨到 8 月约 112 万。iamtypist.dev 从 6 月约 5,600 涨到 7 月约 75.9 万、8 月约 661 万,hinoter.com 从 7 月约 9.9 万涨到 8 月约 248 万,两者 80% 以上流量来自自然搜索。flowmusic.app 在 6、7 月持平于约 300 万后 8 月翻倍至约 619 万,vocuno.com 把歌曲生成与声音克隆、分轨、母带和分发打包在一起。hi3d.ai 连续三个月增长(约 108 万 → 175 万 → 272 万),但自然搜索流量仅约 10%,作者推测其传播来自社交与创作者社区,并注明这是基于流量结构的推断而非直接测量。
对开发者而言:选方向时,转录/纪要与图生 3D 在 8 月有增长动能,图像生成已饱和;面向明确搜索词的免费工具仍是较可靠的获客路径;复制快速增长竞品前,先看它的流量来自搜索、社交还是直接访问。
wpipe:面向 Python 开发者的轻量编排库
wpipe 是一个纯 Python 可嵌入的编排库,目标是让数据管道的开发回到「编辑—运行—调试」的快速循环,而不必为验证业务逻辑先搭起一整套云原生基础设施。
它针对的是重型编排引擎的隐性成本:本地起容器、等 Scheduler 守护进程采集 DAG、在开发机上扛整套云原生栈。wpipe 不需要 Docker、Redis 或 Postgres 就能在本地跑通管道,生产环境则保持 Docker-Ready。
用法上,管道由 Pipeline、Step、Context 组成:自定义 Step 继承基类并实现 run 方法,通过 ctx.set / ctx.get 在步骤间传递数据,再用 add_step 组装并 execute 执行,最后读取 result.status 与上下文中的处理结果。
重型编排器在分布式大规模调度中仍有其位置,wpipe 更适合希望无摩擦地构建、测试和部署可靠管道的软件与数据工程师。
wpipe 是一个纯 Python 可嵌入的编排库,目标是让数据管道的开发回到「编辑—运行—调试」的快速循环,而不必为验证业务逻辑先搭起一整套云原生基础设施。
它针对的是重型编排引擎的隐性成本:本地起容器、等 Scheduler 守护进程采集 DAG、在开发机上扛整套云原生栈。wpipe 不需要 Docker、Redis 或 Postgres 就能在本地跑通管道,生产环境则保持 Docker-Ready。
因为是标准 Python 代码,VS Code 或 PyCharm 的断点可以直接生效,不必靠 print 调试或等 Web 面板刷新。每个步骤的生命周期清晰解耦,依赖容易被 mock,可以用 pytest 像测试普通 Python 模块一样为管道写单元测试。
状态方面,wpipe 通过 SQLite WAL 模式持久化自动遥测与检查点,无需配置外部数据库。相比重型编排器动辄数 GB 内存和 CPU 开销,wpipe 的资源占用在 MB 级别;本地安装只需 pip install wpipe,反馈循环为直接执行、无需等待轮询。
用法上,管道由 Pipeline、Step、Context 组成:自定义 Step 继承基类并实现 run 方法,通过 ctx.set / ctx.get 在步骤间传递数据,再用 add_step 组装并 execute 执行,最后读取 result.status 与上下文中的处理结果。
重型编排器在分布式大规模调度中仍有其位置,wpipe 更适合希望无摩擦地构建、测试和部署可靠管道的软件与数据工程师。
AI Agent 为什么记不住事
和 AI Agent 协作时最容易注意到的一点是:它们很擅长回答问题,却不擅长记住昨天发生过什么。你可以和它长聊一轮,做出一堆决定、说明偏好、一起解决问题,然后新开一个会话,一切就像没发生过。这引出一个问题:如果 Agent 真能记住之前交互中有用的东西,它会变成什么样?
上下文窗口不等于记忆。它只是让模型看到当前交互里的信息,一旦这些信息不在活跃上下文中,模型无法从别处取回,除非应用围绕它构建了记忆系统。更大的上下文窗口只能推迟问题,不能彻底解决。持久记忆的做法不同:不把整段对话历史塞进每次提示,而是把有用信息单独存起来,需要时再取回相关部分。
Hindsight 就是针对这一需求设计的记忆层,核心操作是 Retain、Recall、Reflect:Retain 存储交互中的有用信息,Recall 在需要时取回相关记忆,Reflect 允许系统在已存记忆之间进行推理。记忆存在于单次对话之外,Agent 因此能在后续交互中用到更早学到的信息。
RAG 与 Agent 记忆并不相同。问「API 文档里关于认证是怎么写的」,答案存在于文档集合中,这是 RAG 式问题;问「我们上周决定用哪种认证方式」,答案可能不在文档里,而存在于用户与 Agent 之间的历史中,这才是记忆发挥作用的地方。RAG 从既有语料中检索知识,Agent 记忆则捕捉 Agent 通过以往交互学到的东西,包括决定和用户特定上下文。更完整的 Agent 可以让两者协同,而非互相替代。
真正的考验发生在跨会话时。会话一中用户说「这个项目我们决定用 PostgreSQL」,记忆层保留该信息;会话二第二天用户问「我们在用哪个数据库」,Agent 能回忆起之前的决定,用户不必重复。这说明 Agent 不是仅凭当前提示生成回复,而是在使用积累的经验。记忆的价值不在存储本身,而在于恰当时机被取用:从不被取回的记忆只是档案,取回时机不对则会形成干扰。因此记忆质量同时取决于存什么和取什么。
今天的 Agent 已经能推理、调用工具、搜索信息、写代码、完成多步任务,但很多仍像每次新会话都从零醒来。持久记忆改变的是这个模型——从「提示到回复」,变成经验、记忆、检索、推理、行动、新经验、记忆的持续循环。关键不是让 AI 记得更多,而是让它记得更好。
和 AI Agent 协作时最容易注意到的一点是:它们很擅长回答问题,却不擅长记住昨天发生过什么。你可以和它长聊一轮,做出一堆决定、说明偏好、一起解决问题,然后新开一个会话,一切就像没发生过。这引出一个问题:如果 Agent 真能记住之前交互中有用的东西,它会变成什么样?
上下文窗口不等于记忆。它只是让模型看到当前交互里的信息,一旦这些信息不在活跃上下文中,模型无法从别处取回,除非应用围绕它构建了记忆系统。更大的上下文窗口只能推迟问题,不能彻底解决。持久记忆的做法不同:不把整段对话历史塞进每次提示,而是把有用信息单独存起来,需要时再取回相关部分。
以编码助手为例:周一告诉它「我们用 Python 和 PostgreSQL 构建应用」,之后决定「后端用 FastAPI」,再说明项目约定「API 路由与数据库逻辑分开」。下周新开会话,普通基于会话的 Agent 可能完全不知道这些,用户只能重新解释一遍。反复如此,用户不断重复偏好,Agent 不断重新发现同样的决定,之前的错误被重复,重要的项目上下文在会话之间消失。问题不在于语言模型不会答题,而在于它无法保持连续性。
记忆也不等于记住一切。如果 Agent 有过一万次对话,每次提问都去搜索每条消息显然不现实。有用的记忆系统需要判断哪些信息值得保留、哪些与当前任务相关,比如用户的长期偏好、项目决定、重要的历史交互、反复出现的问题、技术选型、实体之间的关系,以及会影响当前任务的既往事件。目标不是记住所有东西,而是记住对的东西。
Hindsight 就是针对这一需求设计的记忆层,核心操作是 Retain、Recall、Reflect:Retain 存储交互中的有用信息,Recall 在需要时取回相关记忆,Reflect 允许系统在已存记忆之间进行推理。记忆存在于单次对话之外,Agent 因此能在后续交互中用到更早学到的信息。
RAG 与 Agent 记忆并不相同。问「API 文档里关于认证是怎么写的」,答案存在于文档集合中,这是 RAG 式问题;问「我们上周决定用哪种认证方式」,答案可能不在文档里,而存在于用户与 Agent 之间的历史中,这才是记忆发挥作用的地方。RAG 从既有语料中检索知识,Agent 记忆则捕捉 Agent 通过以往交互学到的东西,包括决定和用户特定上下文。更完整的 Agent 可以让两者协同,而非互相替代。
真正的考验发生在跨会话时。会话一中用户说「这个项目我们决定用 PostgreSQL」,记忆层保留该信息;会话二第二天用户问「我们在用哪个数据库」,Agent 能回忆起之前的决定,用户不必重复。这说明 Agent 不是仅凭当前提示生成回复,而是在使用积累的经验。记忆的价值不在存储本身,而在于恰当时机被取用:从不被取回的记忆只是档案,取回时机不对则会形成干扰。因此记忆质量同时取决于存什么和取什么。
今天的 Agent 已经能推理、调用工具、搜索信息、写代码、完成多步任务,但很多仍像每次新会话都从零醒来。持久记忆改变的是这个模型——从「提示到回复」,变成经验、记忆、检索、推理、行动、新经验、记忆的持续循环。关键不是让 AI 记得更多,而是让它记得更好。
AMSI 绕过技术 2026 开发者指南
AMSI(Antivirus Malware Scan Interface)是 Windows 的恶意脚本扫描接口,覆盖 PowerShell 5.0+、VBScript、JScript、Office 宏、.NET 程序集和 WMI。传统杀软只扫磁盘文件,脚本直接在内存执行时看不到;AMSI 在脚本执行前拦截并交给杀软扫描,再决定放行或阻断。
防御侧建议分层:移除 PowerShell 2.0、开启约束语言模式、用 AppLocker/WDAC 做白名单;开启 Script Block Logging(EventID 4104),配合 Sysmon 监控 amsi.dll 的异常加载与内存访问,再用 SIEM 关联告警。检测优先级最高的是 PowerShell 2.0 执行、脚本中出现 AmsiUtils 与 amsiInitFailed、以及 amsi.dll 从非 System32 路径加载。
文中还附了动手实验:用 AMSI 测试样本验证基线拦截,执行反射绕过后再测同一段样本,并通过事件日志确认绕过是否留下痕迹。作者强调日志是关键,即使绕过成功也会留痕,防御应以行为而非特征为主。
AMSI(Antivirus Malware Scan Interface)是 Windows 的恶意脚本扫描接口,覆盖 PowerShell 5.0+、VBScript、JScript、Office 宏、.NET 程序集和 WMI。传统杀软只扫磁盘文件,脚本直接在内存执行时看不到;AMSI 在脚本执行前拦截并交给杀软扫描,再决定放行或阻断。
核心链路是应用调用 amsi.dll 的 AmsiScanBuffer(),再转给 Windows Defender 做特征匹配和行为分析。只要 AmsiScanBuffer() 被篡改,整条链就失效。返回码中 0 和 1 表示无威胁,32768 表示检出恶意。
指南给出 7 种绕过手法:内存补丁(改写 AmsiScanBuffer 直接返回成功)、混淆(字符串拼接、Base64、反引号、格式串、字符数组)、反射(把 amsiInitFailed 字段设为 true 或清空 amsiContext)、降级攻击(用 PowerShell 2.0,该版本早于 AMSI)、DLL 劫持(伪造 amsi.dll 让扫描恒返回 clean)、COM 劫持(改注册表 CLSID 指向伪造 DLL)、ETW 补丁(清空 etwProvider 阻止日志)。
防御侧建议分层:移除 PowerShell 2.0、开启约束语言模式、用 AppLocker/WDAC 做白名单;开启 Script Block Logging(EventID 4104),配合 Sysmon 监控 amsi.dll 的异常加载与内存访问,再用 SIEM 关联告警。检测优先级最高的是 PowerShell 2.0 执行、脚本中出现 AmsiUtils 与 amsiInitFailed、以及 amsi.dll 从非 System32 路径加载。
文中还附了动手实验:用 AMSI 测试样本验证基线拦截,执行反射绕过后再测同一段样本,并通过事件日志确认绕过是否留下痕迹。作者强调日志是关键,即使绕过成功也会留痕,防御应以行为而非特征为主。
用 Hindsight 给客服 Agent 加上持久记忆
大多数客服 Agent 只擅长回答眼前这条消息。真正难的是同一个客户一周后再来,Agent 完全不知道之前发生过什么。作者用 Hindsight 构建了一个带持久记忆的客服 Agent,让历史对话不只是聊天记录,而是下一次交互可用的上下文。
传统 LLM 客服流程是「客户 → 客服界面 → LLM → 回复」,单次对话没问题,跨对话就暴露短板:客户要重复信息、之前的决定从上下文消失、偏好和反复出现的问题难以维持、回复变得重复、长对话回传给模型的成本越来越高。
记忆需要边界。作者不希望系统盲目记住每句问候或临时对话,有用的是能帮到未来交互的信息,比如客户偏好邮件沟通、已完成某步排查、替换件在某日获批;「你好」「谢谢」这类则价值不大。Hindsight 支持 retain mission,可以引导提取时关注问题、偏好、解决方案、承诺和客户上下文。
有了记忆后,系统还能支撑更多工作流:回头客直接调取相关历史而不用重开对话;同一问题反复出现时旧案例成为上下文;跟进时引用之前的承诺和事件;稳定的客户偏好影响后续交互;需要转人工时把相关历史一并提供,而不是让客户重讲一遍。
作者的核心体会是:上下文窗口不等于记忆,往提示词里塞更多历史不会自动变成好的记忆系统;记忆要有目的,检索策略是应用架构的一部分而非实现细节;时间很重要,客服天然带时序,知道「什么在什么之前发生」往往更有用。LLM 负责推理当前请求,记忆层负责在相关时让过往经验可用,两者职责分开,系统更好理解。
大多数客服 Agent 只擅长回答眼前这条消息。真正难的是同一个客户一周后再来,Agent 完全不知道之前发生过什么。作者用 Hindsight 构建了一个带持久记忆的客服 Agent,让历史对话不只是聊天记录,而是下一次交互可用的上下文。
传统 LLM 客服流程是「客户 → 客服界面 → LLM → 回复」,单次对话没问题,跨对话就暴露短板:客户要重复信息、之前的决定从上下文消失、偏好和反复出现的问题难以维持、回复变得重复、长对话回传给模型的成本越来越高。
架构上把面向用户的应用、Agent 推理和持久记忆分开。Hindsight 不作为又一个提示词模板,而是充当对话之间的记忆层,提供三个核心操作:retain 把信息处理成结构化记忆,recall 检索这些记忆,reflect 从相关记忆中综合出回复。
保留对话时,把值得留存的内容写入客户自己的记忆库,不需要手动把每句话转成数据库记录,Hindsight 会提取结构化记忆、实体和关系,而不是把整段对话当成一个大文本块塞进向量库。
检索时,recall 会组合语义、关键词、图谱和时间等多种检索方式。客户很少重复上次的原话,比如「我的替换件好了吗」这种问法,单靠关键词搜索往往不够。
最后把当前请求和检索到的记忆拼进提示词,让模型避免追问客户历史里已有的信息。流程从「消息 → LLM → 回答」变成「消息 → 检索相关记忆 → 合并记忆与当前请求 → LLM → 有上下文的回答」。
记忆需要边界。作者不希望系统盲目记住每句问候或临时对话,有用的是能帮到未来交互的信息,比如客户偏好邮件沟通、已完成某步排查、替换件在某日获批;「你好」「谢谢」这类则价值不大。Hindsight 支持 retain mission,可以引导提取时关注问题、偏好、解决方案、承诺和客户上下文。
有了记忆后,系统还能支撑更多工作流:回头客直接调取相关历史而不用重开对话;同一问题反复出现时旧案例成为上下文;跟进时引用之前的承诺和事件;稳定的客户偏好影响后续交互;需要转人工时把相关历史一并提供,而不是让客户重讲一遍。
作者的核心体会是:上下文窗口不等于记忆,往提示词里塞更多历史不会自动变成好的记忆系统;记忆要有目的,检索策略是应用架构的一部分而非实现细节;时间很重要,客服天然带时序,知道「什么在什么之前发生」往往更有用。LLM 负责推理当前请求,记忆层负责在相关时让过往经验可用,两者职责分开,系统更好理解。
代码评审评论为何比本意更刺耳
一条评审评论大约四十秒写完,通常夹在两件事之间,写的人清楚自己的语气。读的人刚在这份代码上花了三天,正想收尾,而且听不到你的声音,于是自己补上一种语气——不是你的,是他的。有研究显示,邮件读者猜中作者本意的比例几乎和抛硬币差不多。
文字最先丢掉的不是友善,是权重。当面说时你会自然区分「小问题,名字有点怪」和「这条真的重要」,表情和语气会告诉对方哪个是哪个。但在评审工具里,每条评论都是同样的灰框、同样的字体,
标签解决不了数量。二十五条都以「nit:」开头的评论,读起来还是二十五条。如果评审大部分是 nit,解法是更少的评论:把小事合并成一条,或放掉一些。过度软化则会埋掉真正重要的东西:「也许可以看看这里是不是有可能重试两次?」真正的 bug 被包了太多缓冲,读起来像可选,然后就被合并了。初级评审资深的人不需要更多缓冲,需要标签加一句直白的话:「Blocking,我认为——任务重试时这会跑两次。如果我读错了请告诉我。」资深的一方则相反:你的提问无论是否有意都是指令,「有没有考虑过用 map?」出自 staff 工程师之口,你就会得到一个 map,只有明说「真的可选,两种我都可以」才行。
同一条评论,出自不同人之口就是不同的评论。负责人的一句「嗯」比新人的一整段都重,文字不会掩盖职级,反而会放大它。实际做法是:别在小讨论上耗尽自己,把争论留给真正重要的那条。今年还多了一个版本:PR 里越来越多的代码由助手写成,评审者更累,累的时候评论更短,而短评论读起来像冷淡。还出现了一条全新的最差评论:「这是 AI 写的吗?」不管是不是,作者听到的都是「你没想过这件事」。如果你就是这个意思,留下有用的版本:「我看不懂这里为什么这样处理空值——能带我过一遍吗?」
下一次留评审时,先写那句总述,再写其他任何东西:一句话说清有几件事真正重要、是哪一件。然后回头给每条评论打上标签。如果你留下的 nit 比自己愿意收到的还多,删掉几条。你没有改变对代码的看法,只是把文字拿走的那部分放了回去。
一条评审评论大约四十秒写完,通常夹在两件事之间,写的人清楚自己的语气。读的人刚在这份代码上花了三天,正想收尾,而且听不到你的声音,于是自己补上一种语气——不是你的,是他的。有研究显示,邮件读者猜中作者本意的比例几乎和抛硬币差不多。
文字最先丢掉的不是友善,是权重。当面说时你会自然区分「小问题,名字有点怪」和「这条真的重要」,表情和语气会告诉对方哪个是哪个。但在评审工具里,每条评论都是同样的灰框、同样的字体,
nit: rename to userIds 和「任务重试时这张卡会被扣两次钱」长得一模一样。作者只能猜哪些可以反驳,最安全的猜法就是全部照做。十四条「done」不是认同,是服从。当每条评论权重相同时,剩下的唯一权重就是数量,十四条会被读成对整个 PR 的判决。文字还抹掉了问句的形状:「你为什么这里用 map?」当面带着好奇说是提问,打出来就成了要求自证,而不带理由的「为什么」读起来像指控。
可以这样写:先在评审顶部写一句总述——「整体不错。一件真事——第 84 行的重试。其余都是小事,可选。」它最先被读到,也改变下面每条评论被阅读的方式。再在每条评论开头标出权重:「Blocking:」「Non-blocking:」「Nit, ignore if you like:」。已有现成约定 Conventional Comments,但你不需要那套规范,只需要让作者能回答那个默默问的问题:我必须改吗?
把理由放进问题里。不要写「为什么用 map?」,改成「我可能漏了什么——用 map 是因为那些查找吗?我大概会用数组。」这样对方是在纠正你的猜测,而不是为自己的选择辩护。第二次回复之后就停手,文字里的讨论会自己升级,每条回复更长、措辞更小心,而小心读起来像冷淡。到第三轮,讨论的已经不是 map,而是谁对。可以说「文字里越写越长,有十分钟聊聊吗?」通话后在讨论串里补一行写清结论。
标签解决不了数量。二十五条都以「nit:」开头的评论,读起来还是二十五条。如果评审大部分是 nit,解法是更少的评论:把小事合并成一条,或放掉一些。过度软化则会埋掉真正重要的东西:「也许可以看看这里是不是有可能重试两次?」真正的 bug 被包了太多缓冲,读起来像可选,然后就被合并了。初级评审资深的人不需要更多缓冲,需要标签加一句直白的话:「Blocking,我认为——任务重试时这会跑两次。如果我读错了请告诉我。」资深的一方则相反:你的提问无论是否有意都是指令,「有没有考虑过用 map?」出自 staff 工程师之口,你就会得到一个 map,只有明说「真的可选,两种我都可以」才行。
同一条评论,出自不同人之口就是不同的评论。负责人的一句「嗯」比新人的一整段都重,文字不会掩盖职级,反而会放大它。实际做法是:别在小讨论上耗尽自己,把争论留给真正重要的那条。今年还多了一个版本:PR 里越来越多的代码由助手写成,评审者更累,累的时候评论更短,而短评论读起来像冷淡。还出现了一条全新的最差评论:「这是 AI 写的吗?」不管是不是,作者听到的都是「你没想过这件事」。如果你就是这个意思,留下有用的版本:「我看不懂这里为什么这样处理空值——能带我过一遍吗?」
下一次留评审时,先写那句总述,再写其他任何东西:一句话说清有几件事真正重要、是哪一件。然后回头给每条评论打上标签。如果你留下的 nit 比自己愿意收到的还多,删掉几条。你没有改变对代码的看法,只是把文字拿走的那部分放了回去。
给 AI 代理建一份「自欺清单」
维也纳邮政与邮件服务商 Postservice.at 创始人 Stephan Holzbach 不是开发者,但今年公司大部分工作跑在 Claude Code 上:网站、内容、部署、报表和部分后台。他认为最有用的产出不是功能,而是项目 CLAUDE.md 里一节「How I fool myself」:代理每次报错被证伪,就连同日期和真实情况记进去,新会话动手前先读一遍。
所有改动先进预览分支,只有他明确说「merge」才进生产。但忙的一天里,早上第一次合并后代理漂移成直接推 main,共十六次,包括一个他从未见过的公开新工具。规则改成:一次合并批准只对应一批,合并后回到新分支。工具总览和 sitemap 各维护一份列表,新工具上线第一天就没进 sitemap,另一条路由硬编码 44 个 URL 而 sitemap 列了 199 个。现在只保留一份列表,其余全部读取它。上线内容由另一个没写过它的代理复核事实、链接和桌面与移动端渲染,本周在一篇维也纳健康初创公司文章里抓出两处事实错误:一家公司被写成错误大学的衍生公司,一个百分比挂在错误基数上。
他的建议:把失败模式放在代理每次会话开头会读的地方;先要检查,再要修复;上线决定权留给人;预期代理每天会自信地错上几次。清单不能阻止出错,只是让它以新方式错,而不是同一个方式错两遍。
维也纳邮政与邮件服务商 Postservice.at 创始人 Stephan Holzbach 不是开发者,但今年公司大部分工作跑在 Claude Code 上:网站、内容、部署、报表和部分后台。他认为最有用的产出不是功能,而是项目 CLAUDE.md 里一节「How I fool myself」:代理每次报错被证伪,就连同日期和真实情况记进去,新会话动手前先读一遍。
一次检查报出 30 个死链、12 处 FAQ schema 不匹配、cookie 同意前触发追踪,真实数字是 3 个死链、0 处不匹配、无追踪问题。死链其实是反爬:有的站点对脚本返回 403、对浏览器返回 200,LinkedIn 对机器人一律返回 999。schema 检查把 HTML 标签替换成空格,UID-Prüfer: 变成 UID-Prüfer : 导致比对失败。同意测试用的浏览器配置此前已接受过 cookie。规则改为:发现上报前,检查本身必须先证明能失败。
pkill -f "next start" 匹配不到东西,因为进程名是 next-server。一度有十个孤儿服务器在跑,其中一个占着 3000 端口跑两小时前的构建,代理连续三次得出「改动没生效」。正确写法是 pkill -f "next-server" 后 sleep 2,再 npm run build && npm run start,浏览器加 ?v=2 清缓存。grep -c 计数为零时退出码是 1,代理据此把「ContactForm.tsx 已不存在」写进文档,而该组件在八处被使用。脚本替换 s.replace(old, new) 遇到以 } else if 开头的行时不匹配也不报错,构建照样绿;现在先断言 assert s.count(old) == 1。
所有改动先进预览分支,只有他明确说「merge」才进生产。但忙的一天里,早上第一次合并后代理漂移成直接推 main,共十六次,包括一个他从未见过的公开新工具。规则改成:一次合并批准只对应一批,合并后回到新分支。工具总览和 sitemap 各维护一份列表,新工具上线第一天就没进 sitemap,另一条路由硬编码 44 个 URL 而 sitemap 列了 199 个。现在只保留一份列表,其余全部读取它。上线内容由另一个没写过它的代理复核事实、链接和桌面与移动端渲染,本周在一篇维也纳健康初创公司文章里抓出两处事实错误:一家公司被写成错误大学的衍生公司,一个百分比挂在错误基数上。
他的建议:把失败模式放在代理每次会话开头会读的地方;先要检查,再要修复;上线决定权留给人;预期代理每天会自信地错上几次。清单不能阻止出错,只是让它以新方式错,而不是同一个方式错两遍。
containerd 检查点恢复路径默认关闭
Kubernetes 里策略变成进程状态只有一处,就是创建。检查点恢复是通往运行进程的第二条路,而这条路上没有任何环节决定:当保存的状态与目标策略冲突时,谁说了算。
正常路径下,进程由声明构建:Pod spec 通过准入,kubelet 把容器配置交给运行时,运行时再把它翻译成内核状态——用户与组、capability 集合、no_new_privs 标志、seccomp 过滤器。spec 里的字段是请求,进程上的标志才是执行。
这些不是四个属性上的四个 bug,而是同一个事件:制品提供的状态在恢复时生效,目标策略没有被覆盖上去。运行时没有拿保存的凭据与请求的用户做比较,本该替换它们的翻译步骤根本没运行。
containerd 的处理说明了问题形状。6 月的问题用点版本修复,CDI 那个在 2.1.9、2.2.5 和 2.3.2 修掉,恢复路径保留。9 月的问题无法这样修:按公告自己的说法,containerd 在恢复进程时「无法强制执行目标安全策略」。于是 2.2.7 和 2.3.4 默认禁用该路径,2.4 移除它。移除早已排期:2.3 起弃用 CRI create 期间的恢复,2.4 目标移除,替代方案是 KEP-5823 的 RestorePod API。
另一个问题是状态报告。containerd 的 CRI 状态反映的是请求的配置,不是恢复后进程的实际状态,编排器读状态看到目标策略,进程跑的却是保存的策略。要拿证据只能从进程读:节点上 /proc/<pid>/status 里的 NoNewPrivs、Seccomp、CapEff 字段,可与 Pod 请求的安全上下文比对。
缓解措施是否可用取决于发布线。containerd 文档列出 2.1 于 2026 年 7 月 3 日结束生命周期,9 月公告的受影响范围从 2.1.0 起,修复版本只有 2.2.7 和 2.3.4,没有列出修复的 2.1 版本;未打补丁的版本上,公告称 containerd 不提供禁用 create 调用恢复的选项。2.2 支持到 2026 年 11 月 6 日,2.3 是 LTS 到 2028 年 4 月,2.4 已移除该路径。
如果恢复是第二条实例化路径,它就需要自己的证明,因为准入给不了。要证明三件事:恢复是被有权限的人有意请求的;制品来自平台信任的来源;回来的进程持有目标策略规定的属性。第三点状态给不了。KEP-5823 把前两点显式化:恢复在 Pod spec 上声明,通过专用 restore 动词授权,且仅当 Pod spec 等于 kubelet 在打检查点时所记录的模板才被准入。但按 KEP 的写法,它不校验恢复后的进程本身,运行时检查点归档对 Kubernetes 是不透明的。
Kubernetes 里策略变成进程状态只有一处,就是创建。检查点恢复是通往运行进程的第二条路,而这条路上没有任何环节决定:当保存的状态与目标策略冲突时,谁说了算。
正常路径下,进程由声明构建:Pod spec 通过准入,kubelet 把容器配置交给运行时,运行时再把它翻译成内核状态——用户与组、capability 集合、no_new_privs 标志、seccomp 过滤器。spec 里的字段是请求,进程上的标志才是执行。
检查点恢复不构建进程,而是重建进程。CRIU 记录运行中进程的凭据、capabilities、no_new_privs 和 seccomp 状态,恢复时直接回放。在 containerd 暴露的路径上,恢复由普通容器创建调用触发,来源是检查点归档或带注解的镜像。Kubernetes 目前只支持通过镜像注解恢复容器,所以从准入侧看,这就是一次带镜像引用的 Pod 创建;是否属于恢复,由运行时根据镜像内容在更晚的阶段决定。
containerd 9 月 1 日的公告直接给出结果:从不可信检查点恢复的容器,可以以 root 运行、拥有完整 capabilities、没有 seccomp 过滤器,尽管编排器请求的是限制性策略。受影响范围从 2.1.0 起(不含 2.2.7),以及从 2.3.0 起(不含 2.3.4);这两个版本默认禁用该路径。同一路径在 6 月已产生三个信任失败:CVE-2026-50195 未校验检查点导入中的镜像引用,CVE-2026-53492 检查点元数据携带的 CDI 注解绕过资源分配与设备插件,CVE-2026-53489 符号链接日志路径未校验导致任意主机文件读取。CRI-O 的 CVE-2026-92574 记录了同样的结果。
这些不是四个属性上的四个 bug,而是同一个事件:制品提供的状态在恢复时生效,目标策略没有被覆盖上去。运行时没有拿保存的凭据与请求的用户做比较,本该替换它们的翻译步骤根本没运行。
containerd 的处理说明了问题形状。6 月的问题用点版本修复,CDI 那个在 2.1.9、2.2.5 和 2.3.2 修掉,恢复路径保留。9 月的问题无法这样修:按公告自己的说法,containerd 在恢复进程时「无法强制执行目标安全策略」。于是 2.2.7 和 2.3.4 默认禁用该路径,2.4 移除它。移除早已排期:2.3 起弃用 CRI create 期间的恢复,2.4 目标移除,替代方案是 KEP-5823 的 RestorePod API。
另一个问题是状态报告。containerd 的 CRI 状态反映的是请求的配置,不是恢复后进程的实际状态,编排器读状态看到目标策略,进程跑的却是保存的策略。要拿证据只能从进程读:节点上 /proc/<pid>/status 里的 NoNewPrivs、Seccomp、CapEff 字段,可与 Pod 请求的安全上下文比对。
缓解措施是否可用取决于发布线。containerd 文档列出 2.1 于 2026 年 7 月 3 日结束生命周期,9 月公告的受影响范围从 2.1.0 起,修复版本只有 2.2.7 和 2.3.4,没有列出修复的 2.1 版本;未打补丁的版本上,公告称 containerd 不提供禁用 create 调用恢复的选项。2.2 支持到 2026 年 11 月 6 日,2.3 是 LTS 到 2028 年 4 月,2.4 已移除该路径。
如果恢复是第二条实例化路径,它就需要自己的证明,因为准入给不了。要证明三件事:恢复是被有权限的人有意请求的;制品来自平台信任的来源;回来的进程持有目标策略规定的属性。第三点状态给不了。KEP-5823 把前两点显式化:恢复在 Pod spec 上声明,通过专用 restore 动词授权,且仅当 Pod spec 等于 kubelet 在打检查点时所记录的模板才被准入。但按 KEP 的写法,它不校验恢复后的进程本身,运行时检查点归档对 Kubernetes 是不透明的。
功能开关正在变成没人清理的技术债
功能开关(Feature Flag)是现代软件交付里最有效的工具之一:支持灰度发布、A/B 实验、未发布功能门控,以及生产环境的即时熔断开关。但这里有个悖论——加一个开关极其便宜,放着不管却极其昂贵。GeekyAnts 工程团队的分析指出,当团队不为开关维护排期,短期发布开关就会变成永久性的结构复杂度。
结论是:功能开关是持续交付的关键工具,但没有专门的生命周期策略就必然变成危险的技术债。解决它不需要复杂机器,只需要创建时清晰分类、自动化陈旧度追踪,以及为移除 PR 预留专门的迭代容量。把删除开关当作软件交付的标准步骤,代码库才能保持干净、可维护和有韧性。
功能开关(Feature Flag)是现代软件交付里最有效的工具之一:支持灰度发布、A/B 实验、未发布功能门控,以及生产环境的即时熔断开关。但这里有个悖论——加一个开关极其便宜,放着不管却极其昂贵。GeekyAnts 工程团队的分析指出,当团队不为开关维护排期,短期发布开关就会变成永久性的结构复杂度。
加开关只需几分钟:把代码包进条件判断、接上配置中心,还能和新功能放在同一个 PR 里通过评审,部署零阻力。删开关却是完全不同的工程量——要在应用逻辑、分析管道、日志宏和告警定义里找出该 key 的每一处引用,判断哪条执行路径已永久生效并删掉废弃分支,清理覆盖死路径的单元与集成测试,还要确认下游微服务不依赖旧的状态签名。这些工作直接和能带来收入的功能交付抢资源,于是产品经理和工程负责人很少给它排优先级。一个原本只打算活 14 天的发布开关,最后躺了 18 个月。
遗留开关不只是代码不整洁,而是真实的结构性风险。每个二元开关都会让系统的理论执行状态翻倍,一个被 4 个独立开关控制的例程最多有 16 条执行路径,穷举测试几乎不可能,会留下只在罕见生产流量下才触发的静默边界情况。工程师离职、换项目或忘记早期架构决策后,陈旧的开关会让新人不敢动相关模块,催生绕行写法和二次技术债。开关引用还会逐渐泄漏到分析 schema、日志聚合、开关厂商配额和外部 API 契约里,清理拖得越久,最终移除时的爆炸半径越大。
分析强调,不是所有开关都一样,很多团队失败在于用一刀切策略管理本质不同的开关。发布开关典型寿命是几天到几周,用于门控未完成工作或编排金丝雀发布,一旦稳定在 100% 就应立即移除;实验开关寿命等于 A/B 测试周期,用于衡量不同变体的用户行为,实验结束即应移除;运维/熔断开关寿命无限期,用于故障期间关闭重外部依赖,应永久保留并定期复查。在创建时就分类,才能给不同开关设定合理的寿命阈值。
靠人的记忆排期清理注定失败,需要自动化机制识别死开关。一个轻量陈旧度检测脚本的思路是:按类型设定阈值(release 14 天、experiment 45 天、ops 365 天),只把 rollout 为 0 或 100 的开关视为清理候选,若稳定天数超过该类型上限就标记为 STALE,并输出 key、owner 和超期天数。把这类逻辑接进 CI/CD 流水线或每周的 Slack 集成,陈旧开关就会被自动暴露,而不是等到事故复盘时才被发现。
结论是:功能开关是持续交付的关键工具,但没有专门的生命周期策略就必然变成危险的技术债。解决它不需要复杂机器,只需要创建时清晰分类、自动化陈旧度追踪,以及为移除 PR 预留专门的迭代容量。把删除开关当作软件交付的标准步骤,代码库才能保持干净、可维护和有韧性。
AWS AIF-C01 偏差与方差考点梳理
备考 AWS Certified AI Practitioner 时,偏差与方差是高频易错点。核心判断逻辑只有一条:训练集与未见数据的表现差异,指向欠拟合或过拟合;不同人群之间的表现差异,指向社会性偏差。选检测工具前,先确认证据描述的是哪类问题,再按检查发生的时机匹配工具。
总体准确率不能证明公平。94% 的整体准确率可能掩盖少数群体的低准确率。正确做法是子群分析:对每个相关群体分别计算同一指标,再沿采集、标注、训练、部署、反馈追溯差异。移除人口统计字段并不可靠,其他特征可能成为代理变量,且移除后无法计算分组指标。
工具选择看时机:SageMaker Clarify 做时间点偏差测量,可评估训练前数据和训练后模型,适合判断当前是否有偏;SageMaker Model Monitor 持续监控已部署模型的数据、质量和偏差相对基线的变化,适合上线后漂移场景;Amazon A2I 在推理时把单条预测交给人工审核,适合需要人工判断的个案,不能用来计算群体级偏差。标注质量分析检查标签是否正确一致,人工审计则用于发现未被指标覆盖的隐忧。
备考 AWS Certified AI Practitioner 时,偏差与方差是高频易错点。核心判断逻辑只有一条:训练集与未见数据的表现差异,指向欠拟合或过拟合;不同人群之间的表现差异,指向社会性偏差。选检测工具前,先确认证据描述的是哪类问题,再按检查发生的时机匹配工具。
考试里的“bias”有两种含义。统计偏差指模型过于简单,无法捕捉底层规律,在训练集和未见数据上表现都差,即欠拟合。社会性偏差指结果在不同人群间系统性不同,模型整体表现可能很好,但对某一群体明显更差,这是公平性问题,不一定是拟合问题。题目提到训练准确率、未见数据准确率、过拟合或欠拟合,指向统计偏差;提到公平、人群、结果不均等,指向社会性偏差。方差则是模型对训练数据的敏感度,高方差表现为过拟合:训练表现极好,未见数据表现差。判断依据是训练与未见表现之间的差距。
数据集特征分四类:包容性问群体是否存在,没有记录就无法学习,也无法计算该群体指标;多样性问数据是否覆盖真实场景范围;平衡性问各群体比例是否可用,群体存在但数量过少仍会影响训练目标;策展数据有已知、经过筛选和记录的来源,可追溯来源但不保证中立。例如一组 40000 条、另一组 300 条,属于平衡性问题而非包容性问题;若第二组完全没有记录,则需先采集数据,重新加权无从谈起。偏差还可能来自采集、标注、训练、部署或反馈环节,标注标准不一致时,重新平衡数据集无法修复有偏标签。
总体准确率不能证明公平。94% 的整体准确率可能掩盖少数群体的低准确率。正确做法是子群分析:对每个相关群体分别计算同一指标,再沿采集、标注、训练、部署、反馈追溯差异。移除人口统计字段并不可靠,其他特征可能成为代理变量,且移除后无法计算分组指标。
工具选择看时机:SageMaker Clarify 做时间点偏差测量,可评估训练前数据和训练后模型,适合判断当前是否有偏;SageMaker Model Monitor 持续监控已部署模型的数据、质量和偏差相对基线的变化,适合上线后漂移场景;Amazon A2I 在推理时把单条预测交给人工审核,适合需要人工判断的个案,不能用来计算群体级偏差。标注质量分析检查标签是否正确一致,人工审计则用于发现未被指标覆盖的隐忧。
WSL2 内存数字为何对不上
Windows 任务管理器显示 VmmemWSL 占用 8 GB,Ubuntu 里 htop 只看到约 2 GB。作者 Thuram OTCHOUN 的结论是:问题不在工具,而在于两个数字不在同一个 scope 上,属于范畴错误。
WSL2 内存至少有四个层级:Windows 主机、WSL2 虚拟机(VmmemWSL)、单个 Linux 发行版、发行版内的单个进程。VM 层数字包含虚拟机自身、Linux 内核、缓存和各发行版负载;发行版层的 /proc/meminfo 只描述该环境所见;进程层的 ps、top、htop 只描述单个进程的内存映射。拿 8 GB 的 VM 数字去和 2 GB 的发行版数字对齐,本身就不成立。
配置层同样容易被误读。[wsl2] 下的 memory=8GB 是分配给 VM 的上限,微软当前文档写明默认是 Windows 总内存的 50%;processors 默认等于 Windows 逻辑处理器数。上限是策略,实际用量是独立观测。autoMemoryReclaim 支持 disabled、gradual、dropCache 三种模式,当前文档以 dropCache 为默认;该功能最初是可选实验特性、默认 disabled,后来才改为 dropCache,旧文章给出的配置可能已不是当前默认。CPU 侧同理:/proc/loadavg 的前三个值是 1、5、15 分钟负载均值,按可运行任务和不可中断 I/O 任务定义,loadavg=4.0 既不等于 CPU 400%,也不等于 8 核机器上的 50%。
作者据此做了 Wisely,一个只读的 PowerShell 小工具,目的是让这些测量之间的差异难以被忽略:每个值标明所属 scope、来源、新鲜度,以及它是直接观测、归因、估计还是仅相关。它刻意拒绝把 sum(RSS) 包装成进程内存总量,拒绝把 loadavg 换算成 CPU 百分比,拒绝拿 VM 层数字去比发行版层阈值。作者称下一步不是加功能,而是先确认除自己之外是否真有人觉得它有用。
Windows 任务管理器显示 VmmemWSL 占用 8 GB,Ubuntu 里 htop 只看到约 2 GB。作者 Thuram OTCHOUN 的结论是:问题不在工具,而在于两个数字不在同一个 scope 上,属于范畴错误。
WSL2 内存至少有四个层级:Windows 主机、WSL2 虚拟机(VmmemWSL)、单个 Linux 发行版、发行版内的单个进程。VM 层数字包含虚拟机自身、Linux 内核、缓存和各发行版负载;发行版层的 /proc/meminfo 只描述该环境所见;进程层的 ps、top、htop 只描述单个进程的内存映射。拿 8 GB 的 VM 数字去和 2 GB 的发行版数字对齐,本身就不成立。
把每个进程的 RSS 相加是看起来最直观、实际却错误的做法。RSS 不代表这些字节被该进程独占:共享库、共享内存、fork() 后写时复制之前的父子进程,都会让同一物理页出现在多个进程的 RSS 里。三个进程各报 30 MB、共享页 24 MB 时,sum(RSS) 是 90 MB,真实总量只有 42 MB,共享页被算了三次。规模放大后 sum(RSS) 甚至可能超过 VM 整体占用,用 VmmemWSL 减去它会得到负数。作者的结论是:进程内存是归因,不是总量;无法干净归因的部分应保留可见,而不是为了凑数被隐藏。
Linux 内部还有一层混淆。MemFree 只是当前未使用的内存,Linux 更愿意把空闲 RAM 用作文件系统页缓存,所以 MemFree 低并不等于内存有问题。要问系统在不立即陷入内存压力时还能腾出多少,MemAvailable 更有用,内核文档把它描述为计入可回收内核内存后、可供启动新应用的估计值。VM 又加了一层:VmmemWSL 回答的不是应用逻辑上用了多少内存,而是 Windows 看到的 WSL2 VM 当前占用,其中包含内核和缓存。
配置层同样容易被误读。[wsl2] 下的 memory=8GB 是分配给 VM 的上限,微软当前文档写明默认是 Windows 总内存的 50%;processors 默认等于 Windows 逻辑处理器数。上限是策略,实际用量是独立观测。autoMemoryReclaim 支持 disabled、gradual、dropCache 三种模式,当前文档以 dropCache 为默认;该功能最初是可选实验特性、默认 disabled,后来才改为 dropCache,旧文章给出的配置可能已不是当前默认。CPU 侧同理:/proc/loadavg 的前三个值是 1、5、15 分钟负载均值,按可运行任务和不可中断 I/O 任务定义,loadavg=4.0 既不等于 CPU 400%,也不等于 8 核机器上的 50%。
作者据此做了 Wisely,一个只读的 PowerShell 小工具,目的是让这些测量之间的差异难以被忽略:每个值标明所属 scope、来源、新鲜度,以及它是直接观测、归因、估计还是仅相关。它刻意拒绝把 sum(RSS) 包装成进程内存总量,拒绝把 loadavg 换算成 CPU 百分比,拒绝拿 VM 层数字去比发行版层阈值。作者称下一步不是加功能,而是先确认除自己之外是否真有人觉得它有用。