<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <author>
    <name>smallnest</name>
  </author>
  <generator uri="https://hexo.io/">Hexo</generator>
  <id>https://colobu.com/</id>
  <link href="https://colobu.com/" rel="alternate"/>
  <link href="https://colobu.com/atom.xml" rel="self"/>
  <rights>All rights reserved 2026, smallnest</rights>
  <subtitle>大道至简 Simplicity is the ultimate form of sophistication</subtitle>
  <title>鸟窝</title>
  <updated>2026-06-22T00:21:15.997Z</updated>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="AI" scheme="https://colobu.com/categories/AI/"/>
    <category term="AI" scheme="https://colobu.com/tags/AI/"/>
    <content>
      <![CDATA[<blockquote><p>&quot;Same person. Different era. The difference is the tooling.&quot;<br>人未变，时代已改。拉开差距的，全在工具。</p><p>——Garry Tan, Y Combinator 总裁 &amp; CEO, 2026 年</p></blockquote><p>卷首语用五个人的故事画出了一幅图景：Karpathy 半年没写代码，Amodei 预言 90% 代码将由 AI 完成，Garry Tan 的产出翻了 810 倍，Boris Cherny 不再写代码只审查代码，antirez 放下了亲手雕琢每一行的执念。这些信号指向同一个结论——软件工程正在经历自 1968 年这门学科诞生以来最深刻的一次范式转换。</p><p>本章建立理解这场变革所需的概念坐标。它是怎么一步步走到今天的？新旧范式之间真正的断裂在哪里？全书贯穿的那根主线——&quot;用结构化知识驾驭非结构化 AI 能力&quot;——是怎么来的？</p><span id="more"></span><h2 id="1-1-软件工程简史-一个假设的五十年"><a href="#1-1-软件工程简史-一个假设的五十年" class="headerlink" title="1.1 软件工程简史: 一个假设的五十年"></a>1.1 软件工程简史: 一个假设的五十年</h2><p>1968 年 NATO 软件工程会议上，&quot;软件危机&quot;被正式命名——项目失败、成本超支、交付延期——催生了&quot;软件工程&quot;这门学科。解决方案是用工程化流程约束创造力：阶段分解、评审关口、文档驱动。</p><p>此后半个多世纪，方法论不断演化。一个核心假设从未被挑战。</p><p><strong>瀑布模型</strong>（Winston Royce, 1970）将开发划分为需求分析、设计、实现、测试、维护的严格线性阶段。Royce 本人的论文远比后来的教条版本微妙——他实际上提倡迭代（&quot;do it twice&quot;）——但瀑布作为一种管理模式被广泛采纳。它的核心前提是：需求可以在编码之前被完整地理解。</p><p><strong>敏捷运动</strong>（Agile Manifesto, 2001）拆掉了瀑布的刚性阶段门。&quot;个体和互动高于流程和工具，可工作的软件高于详尽的文档。&quot;敏捷承认了需求会变，但保留了最底层的假设——<strong>写代码的人依然是写代码的人</strong>。所有的 Scrum 站会、Sprint 计划、用户故事，都建立在一个未经审视的前提之上：软件是由人类工程师一行一行写出来的。</p><p><strong>DevOps</strong> 打破的是开发与运维之间的墙。CI&#x2F;CD、基础设施即代码、容器化——这些运动将运维带入了工程视野。但它们同样没有触及那个核心信条。DevOps 优化的是&quot;写完之后怎么办&quot;，而不是&quot;谁来写&quot;。</p><p>最后这个假设——<strong>代码由人亲自编写</strong>——在 2023 年第一次动摇了。不是因为新的管理方法或流程改进，而是因为大语言模型。它能理解自然语言指令，自主探索代码库，执行完整的功能开发。它不是更聪明的自动补全。它在转移编程行为的主体。</p><img src="/2026/06/22/software-engineering-fifty-years-paradigm-shift/image-20260523093209852.png" class=""><h2 id="1-2-AI-Coding-Agent-的四层演进"><a href="#1-2-AI-Coding-Agent-的四层演进" class="headerlink" title="1.2 AI Coding Agent 的四层演进"></a>1.2 AI Coding Agent 的四层演进</h2><p>如果将 AI Agent 的能力演进放到时间轴上，可以看到四个清晰的阶段。每个阶段都在重新定义&quot;编程&quot;这个行为的含义——不只是效率的提升，而是<strong>人类和机器在编程活动中各自的角色</strong>发生了质变。</p><table><thead><tr><th>层次</th><th>时间</th><th>交互范式</th><th>人类角色</th><th>AI 角色</th><th>代表作</th></tr></thead><tbody><tr><td>L1: 补全</td><td>2022-</td><td>人写代码，AI 预测下一行</td><td>作者</td><td>自动补全</td><td>GitHub Copilot</td></tr><tr><td>L2: 对话</td><td>2023-</td><td>人问问题，AI 回答</td><td>学习者</td><td>知识库</td><td>ChatGPT, Claude</td></tr><tr><td>L3: 任务</td><td>2025-</td><td>人下指令，AI 完成功能</td><td>指挥者</td><td>实现者</td><td>Claude Code, Cursor</td></tr><tr><td>L4: 自主流程</td><td>2026-</td><td>人定义目标，AI 管理全流程</td><td>审查者</td><td>工程经理</td><td>autoresearch, Ralph Loop</td></tr><tr><td><strong>L1 补全</strong>：GitHub Copilot 在 2022 年 6 月正式发布，程序员第一次体验到了&quot;AI 帮你写下一行&quot;。但它本质上是智能化的自动补全——AI 预测，人类决策。</td><td></td><td></td><td></td><td></td><td></td></tr></tbody></table><p><strong>L2 对话</strong>：ChatGPT 改变了交互范式。程序员学会了&quot;提问&quot;——替代了查文档、搜 Stack Overflow。但代码仍然是程序员亲手写的。AI 是搜索引擎的替代品，不是代码的作者。</p><p><strong>L3 任务</strong>：Claude Code 于 2025 年 2 月发布，同时 Cursor、Codex CLI、OpenCode 等工具将体验升级为&quot;AI 帮你实现一个功能&quot;。Agent 开始具备自主理解代码库、定位修改位置、执行多文件变更的能力。人类从&quot;写代码的人&quot;向&quot;分配任务的人&quot;滑动。</p><p><strong>L4 自主流程</strong>：进入 2026 年，Agent 不再只是执行任务，而是开始<strong>管理流程</strong>——从理解需求到设计方案、实现代码、编写测试、创建 PR，一次会话走完整条链路。这正是卷首语中 Boris Cherny 描述的工作状态：他不再写代码，只审查代码。</p><p>从 L1 到 L4，变的不是 AI 能力的量，而是<strong>编程行为中主体的位置</strong>。L1 中人类是唯一主体。L4 中 AI 变成主动参与者，人类变成监督者和质量守门人。</p><img src="/2026/06/22/software-engineering-fifty-years-paradigm-shift/image-20260523093742002.png" class=""><h2 id="1-3-Vibe-Coding-解放还是陷阱？"><a href="#1-3-Vibe-Coding-解放还是陷阱？" class="headerlink" title="1.3 Vibe Coding: 解放还是陷阱？"></a>1.3 Vibe Coding: 解放还是陷阱？</h2><p>AI 让编程门槛降到了历史最低点。任何人——不管会不会写代码——都可以用一句话让 AI 生成一个能跑的应用。Andrej Karpathy 给这种现象取了一个生动的名字：<strong>Vibe Coding（氛围编程）</strong>。</p><p>这是解放，毫无疑问。但它也是一个精心包装的陷阱。</p><p>Vibe Coding 的典型模式：对 AI 说&quot;给我做一个 todo app&quot;→ AI 十几秒生成一整套代码 → &quot;加一个暗黑模式&quot;→ &quot;加一个拖拽排序&quot;。你甚至不需要知道 useState 是什么。你只要&quot;vibe&quot;就可以了。</p><p>陷阱在于：<strong>它拉高下限的同时，模糊了上限的存在。</strong> 当你不需要理解自己的代码如何工作，当你的应用在&quot;看起来能运行&quot;和&quot;真正能在生产环境运行&quot;之间存在巨大的鸿沟——Vibe Coding 的产物往往是一团不可测试、不可重构、不可理解的代码浆糊。</p><p>这就是 Karpathy 为什么特意区分了两个概念：<strong>Vibe Coding 与 Agentic Engineering（智能体工程）</strong>。前者的关键词是&quot;放手&quot;——把需求丢给 AI，接受它吐出的一切。后者的关键词是&quot;掌控&quot;——把 AI 视为极其强大但带有随机性和盲区的工具，通过结构化方法引导、约束、验证它的产出。</p><p>作为卷首语中详细展开过的论证，这里不再重复，但需要强调它的推论：<strong>AI 不会救你，它会放大你。</strong> 你给它清晰的架构，它还你整洁的代码；你给它模糊的意图，它还你一团浆糊。一个人用 AI 后的产出上限，是由他在没有 AI 时的工程素养决定的。Redis 创造者 antirez 用一周时间完成 DS4 项目——但他审查了 AI 生成的每一行代码。</p><p>这就是为什么，在 AI 让编码门槛降到历史最低点的时刻，工程化的价值反而达到了历史最高点。</p><h2 id="1-4-核心主张-从-Prompt-Driven-到-Skill-Driven"><a href="#1-4-核心主张-从-Prompt-Driven-到-Skill-Driven" class="headerlink" title="1.4 核心主张: 从 Prompt-Driven 到 Skill-Driven"></a>1.4 核心主张: 从 Prompt-Driven 到 Skill-Driven</h2><p>这就引出了全书最核心的概念转折。</p><p>过去两年间，一个洞见在不同的人和实践中反复出现、逐步收敛。它不是某个人独自发明的，而是社区在实践中达成的共识：<strong>Prompt 是临时的，Skill 是持久的。</strong></p><p>当你对 AI 说&quot;帮我做代码审查&quot;，你能得到什么？一个审查结果。它的质量取决于你当时的表达、AI 当时的理解、对话上下文的完整度。改天再做一次，结果可能完全不同。Prompt 消失在对话历史里——不可复用、不可迭代、不可积累。</p><p>而一个 Skill——比如 &#x2F;review-it——是一个结构化指令文件，定义了什么算好的审查、审查哪些维度、遇到每种问题怎么处理。它可以在不同项目、不同时间、对不同的代码重复使用。每次使用都是一次验证机会，如果出现偏差，你可以改进 Skill 本身，让下一次更好。Skill 留在你的工具链里——可复用、可迭代、可积累。</p><p>&quot;对话一次&quot;和&quot;建立一个系统&quot;之间的区别，就是一位 AI 时代的合格工程师和一位&quot;vibe coder&quot;之间的区别。</p><p>也正是在这个意义上，四个层次构成了一个递进：<strong>Prompt Engineering</strong> 让 AI 理解意图（一次性对话）→ <strong>Skill Engineering</strong> 让能力可复用（持久化方法论）→ <strong>Agent Orchestration</strong> 让多个 Agent 协同（编排调度）→ <strong>Harness Engineering</strong> 让整个系统安全可控（运行环境与权限）。每一层建立在前一层基础上。Skill Engineering 处于&quot;从临时到持久&quot;的转折点上——它不是让 AI 变得更聪明，而是让使用 AI 的人变得更体系化。</p><p>这引出了全书的核心理念：</p><p><strong>在 AI 时代，软件工程的核心竞争力不再是&quot;你能写多快的代码&quot;，而是&quot;你能不能构建一套让 AI 高质量产出的方法和系统。&quot;</strong></p><p>这套系统应具备五个特征：<strong>可复用、可验证、可迭代、可组合、不依赖特定模型。</strong></p><p>围绕这些原则，一个全新的方法论生态已经生长出来。以下方法论将在本书第一部分逐一展开：</p><table><thead><tr><th>方法论</th><th>核心思想</th><th>关键贡献者</th><th>详章</th></tr></thead><tbody><tr><td>Matt Skills 系统</td><td>将工程经验沉淀为可复用的能力单元</td><td>Matt Pocock</td><td>第2章</td></tr><tr><td>Spec-Driven Development</td><td>规格文档作为人类与 AI 之间的&quot;合约&quot;</td><td>OpenSpec &#x2F; GitHub Spec-Kit</td><td>第3章</td></tr><tr><td>Ralph Loop</td><td>AI 在循环中自我改进，直到满足验收标准</td><td>Frank Bria</td><td>第4章</td></tr><tr><td>gstack</td><td>23 个专家角色构建虚拟工程团队</td><td>Garry Tan</td><td>第5章</td></tr><tr><td>superpowers</td><td>159K+ Stars 的 AI 编程 Skills 方法论库</td><td>obra &#x2F; jnMetaCode</td><td>第6章</td></tr><tr><td>autoresearch</td><td>多 Agent 轮转交叉审核，端到端全自动闭环</td><td>smallnest</td><td>第7章</td></tr><tr><td>Goal Workflow</td><td>目标驱动的四步研发闭环</td><td>smallnest</td><td>第8章</td></tr><tr><td>这些方法论看似各不相同，但它们共享同一个底层逻辑——也是贯穿全书的那根线：<strong>用结构化知识驾驭非结构化 AI 能力。</strong></td><td></td><td></td><td></td></tr></tbody></table><h2 id="1-5-本章小结"><a href="#1-5-本章小结" class="headerlink" title="1.5 本章小结"></a>1.5 本章小结</h2><p>我们正站在软件工程史上一个罕见的转折点上。上一次这种量级的改变，要追溯到 1968 年——当这门学科本身被命名时。</p><p>这次转折有三个核心特征：</p><p>第一，<strong>编程行为的主体正在转移</strong>。五十年来的第一次，不是人类在敲键盘。人类从&quot;作者&quot;变成了&quot;指挥者&quot;，再变成&quot;审查者&quot;。</p><p>第二，<strong>速度和质量之间出现了新的张力</strong>。AI 让写代码极快，但验证代码的速度远远跟不上。Vibe Coding 是解放，也是陷阱。工程化不是减速带，而是让你高速行驶时不翻车的底盘。</p><p>第三，<strong>方法论的价值超过了工具本身</strong>。工具每天都在变。但&quot;用结构化知识驾驭非结构化 AI 能力&quot;的原则不会变。Skills 系统、Spec-Driven Development、闭环工作流——这些不是某个具体工具的说明书，而是在任何 AI 工具上都能成立的工程原则。</p><p>从下一章开始，我们将逐一展开这些方法论。但在进入细节之前，请记住本章最核心的那个判断：</p><p><strong>AI 不会救你，它会放大你。在 AI 时代，你的工程素养不是变得不重要了，而是第一次变得如此重要。</strong></p>]]>
    </content>
    <id>https://colobu.com/2026/06/22/software-engineering-fifty-years-paradigm-shift/</id>
    <link href="https://colobu.com/2026/06/22/software-engineering-fifty-years-paradigm-shift/"/>
    <published>2026-06-21T16:00:00.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>&quot;Same person. Different era. The difference is the tooling.&quot;<br>人未变，时代已改。拉开差距的，全在工具。</p>
<p>——Garry Tan, Y Combinator 总裁 &amp; CEO, 2026 年</p>
</blockquote>
<p>卷首语用五个人的故事画出了一幅图景：Karpathy 半年没写代码，Amodei 预言 90% 代码将由 AI 完成，Garry Tan 的产出翻了 810 倍，Boris Cherny 不再写代码只审查代码，antirez 放下了亲手雕琢每一行的执念。这些信号指向同一个结论——软件工程正在经历自 1968 年这门学科诞生以来最深刻的一次范式转换。</p>
<p>本章建立理解这场变革所需的概念坐标。它是怎么一步步走到今天的？新旧范式之间真正的断裂在哪里？全书贯穿的那根主线——&quot;用结构化知识驾驭非结构化 AI 能力&quot;——是怎么来的？</p>]]>
    </summary>
    <title>01 引言：软件工程范式的五十年之变</title>
    <updated>2026-06-22T00:21:15.997Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="AI" scheme="https://colobu.com/categories/AI/"/>
    <category term="AI" scheme="https://colobu.com/tags/AI/"/>
    <content>
      <![CDATA[<blockquote><p>&quot;I don&#39;t think I&#39;ve typed like a line of code probably since December, basically, which is an extremely large change.&quot;<br>从去年十二月起，我基本上一行代码都没写过，这是一个巨大的变化。</p><p>——Andrej Karpathy，No Priors 播客，2026 年 3 月</p></blockquote><span id="more"></span><h2 id="半年没写一行代码"><a href="#半年没写一行代码" class="headerlink" title="半年没写一行代码"></a>半年没写一行代码</h2><p>2026 年春天的一个午后，Andrej Karpathy 坐在播客录制间里，用他标志性的、几乎不带任何修辞起伏的语调，说出了一句让整个软件行业安静下来的话。</p><p>他已经半年没有亲手写过一行代码了。</p><p>不是两周，不是一个月。是从去年十二月开始，一天都没有。</p><p>在任何一个其他时代，这句话如果出自一位顶尖程序员之口，只意味着一件事：他离开了这个行业。但 Andrej Karpathy——OpenAI 联合创始人、前特斯拉 AI 总监、计算机视觉领域最具影响力的研究者之一——并没有离开。恰恰相反，他正处在职业生涯中产出最高的时期。他以自然语言驱动 AI Agent，完成从创业项目到开源探索的全部开发工作——一人之力推动着过去需要一个完整团队才能完成的迭代节奏。英语成了他新的编程语言，而 AI 成了他的编译器。他只是不再亲手写代码了。</p><p>这是一个关于杠杆的故事。</p><img src="/2026/06/21/karpathy-half-year-no-code-prologue/image-20260523081343679.png" class=""><p>2026 年 5 月 19 日——Karpathy 宣布了一个消息：<strong>他加入了 Anthropic。</strong> &quot;我认为未来几年在 LLM 的前沿将会特别具有塑造性，&quot;他写道，&quot;我非常兴奋能加入这个团队，重新回到研发工作中。我仍然对教育充满热情，并计划在适当的时候继续我的相关工作。&quot;这个消息之所以意味深长，不在于一个人换了东家——而在于这位&quot;半年没写一行代码&quot;的工程师选择加入的公司，正是 Claude Code 的缔造者，而他返回的还是软件开发的前线。从 OpenAI 到特斯拉到独立探索，再到 Anthropic，他的轨迹恰好画出了 AI 软件工程从实验室到产品、从工具到基础设施的完整弧线。</p><p>在那期播客以及随后的多次深度访谈中，Karpathy 展开了一幅比他那句名言本身更为深邃的思想图景。他将软件工程的历史划分为三个时代——<strong>软件 1.0、软件 2.0、软件 3.0</strong>——并以此解释了他所看到的这场变革的本质。</p><p>软件 1.0 是人类编写明确的代码规则。你告诉机器每一步怎么做，机器照做。这是过去半个多世纪的编程范式。软件 2.0 是人类通过创建数据集来训练神经网络，让模型从数据中学到规则。这是过去十年的深度学习范式。而软件 3.0，Karpathy 说，编程变成了**提示（Prompting）**本身——上下文窗口成了控制这个新型计算设备（LLM）的杠杆。他说，LLM 已经不再是传统意义上的&quot;程序&quot;，而是一种全新的计算机：你输入一段文本，它输出一段文本，但这中间执行的是人类无法逐行追踪的、基于大规模统计模拟和强化学习的涌现计算。他称之为 <strong>&quot;召唤幽灵&quot;</strong>——我们构建的不是具有动物般内在动力的智能体，而是基于统计模式的模拟产物。它们能在瞬间重构十万行代码或发现零日漏洞，却会在常识问题上给出荒谬的建议。它们的智能是&quot;参差不齐的&quot;（Jagged Intelligence）：在可验证的领域（编程、数学）突飞猛进，因为强化学习能给明确的验证奖励；而在不可验证的领域，它们依然脆弱。</p><p>正是基于这种认识，Karpathy 提出了一个他亲自命名的概念区分：<strong>&quot;氛围编程&quot;（Vibe Coding）与&quot;智能体工程&quot;（Agentic Engineering）。</strong> Vibe Coding 的意义在于「拉高下限」——它让任何人，无论是否具备专业背景，都能让 AI 生成一个能跑的应用。这是一种民主化，也是一种诱惑。但 Agentic Engineering 的核心是「守住上限」——它是一门新的工程学科，要解决的问题是如何协调那些强大但带有随机性、容易出错的 AI 智能体，在不引入漏洞、不牺牲质量的前提下大幅提升开发速度。</p><p>Karpathy 的措辞很克制，但判断很锋利：<strong>掌握 Agentic Engineering 的工程师带来的效率提升，将远远超越过去所谓的&quot;10 倍工程师&quot;。</strong></p><p>这意味着人类的角色将发生根本性的重塑。开发者不再需要死记硬背 PyTorch 的张量维度或 NumPy 的 API 细节——这些都可以放权给拥有&quot;完美记忆力&quot;的 AI 智能体。但放手细节的同时，人类必须提升另一个维度的能力：品味、判断力、架构直觉、系统审美。人类与智能体共同制定详细的规格说明，然后智能体来填充底层实现。Karpathy 用了一个工业时代的比喻来总结：<strong>人类不再是打字员，而是工头。</strong></p><p>但他说出的最重要的一句话，也许是这句旁人转述给他的格言：&quot;<strong>你可以外包你的思考，但不能外包你的理解力。</strong>&quot;机器可以生成代码、总结文档、分析数据，但人类始终是那个决定&quot;为什么要建这个系统&quot;和&quot;如何指导智能体&quot;的人。利用 AI 工具来增强自身的理解力，而不是用 AI 来替代自身的思考——这才是 Agentic Engineering 的终极壁垒。</p><!--more--><h2 id="从软件危机到智能体崛起"><a href="#从软件危机到智能体崛起" class="headerlink" title="从软件危机到智能体崛起"></a>从软件危机到智能体崛起</h2><p>过去两年间，软件工程领域发生的变化，比过去二十年加起来还要剧烈。这不是修辞。这是一场从「工具辅助人类」到「人类指导工具」的根本性反转——程度的加深叠加方向的逆转。</p><p>时间线拉长一些，才能看清这件事的历史分量。</p><p>1968 年，北大西洋公约组织在德国加米施召开了后来被载入史册的 NATO 软件工程会议。在那次会议上，&quot;软件危机&quot;（Software Crisis）作为一个正式术语被提出——软件项目的失败率居高不下，成本超支成为常态，交付日期一再推迟。那次会议催生了&quot;软件工程&quot;这个学科本身。当时的解决方案是用工程化的流程约束创造力：瀑布模型、需求规格、阶段评审、文档驱动。</p><p>半个多世纪以来，这个基本框架没有变过。敏捷运动拆掉了瀑布的刚性阶段门，但保留了&quot;人写代码、流程管质量&quot;的核心假设。DevOps 打破了开发与运维的墙，但写代码的人依然是写代码的人。<br> <img src="/2026/06/21/karpathy-half-year-no-code-prologue/image-20260523075424864.png" class=""><br>直到 AI 编码 Agent 的出现。</p><p>2022 年，GitHub Copilot 让程序员第一次体验到了&quot;AI 帮你写下一行&quot;的感觉——它是一个聪明的自动补全工具。2025 年，Claude Code、Codex、Cursor、OpenCode 等一系列工具将这种体验升级为&quot;AI 帮你写一个函数&quot;。到了 进入 2026 年，这些工具已经进化为能够自主理解整个代码库、管理完整开发流程、甚至学习私有 API 和内部框架的工程 Agent。</p><p>变化的斜率不是线性的。它在加速。</p><h2 id="先行者们看到了同一件事"><a href="#先行者们看到了同一件事" class="headerlink" title="先行者们看到了同一件事"></a>先行者们看到了同一件事</h2><p>Andrej Karpathy 不是唯一一个感受到这场震荡的人。如果你仔细聆听，你会发现来自不同背景、不同时代、不同编程哲学的声音正在汇成同一个和弦。</p><p>2025 年，Anthropic 的 CEO Dario Amodei 在一次公开访谈中给出了一个让很多人不以为然的预测：AI 将在三到六个月内编写 90% 的代码，十二个月内编写几乎全部代码。批评者说这是营销。投资者说这是讲故事。但到了 2026 年，这个预测正在被一个又一个的数据点验证。Claude Code 的用户不仅仅是&quot;使用 AI 辅助编码&quot;——他们在与一个能够自主探索代码库、提出架构方案、执行完整功能开发的 Agent 协作。人类工程师的角色正在从&quot;写代码的人&quot;转变为&quot;定义目标、审查产出、做架构决策的人&quot;。</p><img src="/2026/06/21/karpathy-half-year-no-code-prologue/image-20260523081614821.png" class=""><p>Y Combinator 总裁兼 CEO Garry Tan 用一个对比数字让整个硅谷沉默了。他公开了自己作为同一个工程师、同样高强度工作状态下，2013 年和 2026 年的 GitHub 贡献数据：2026 年的逻辑代码行产出是 2013 年的 <strong>八百一十倍</strong>。这不是百分比增长，这是数量级的跃迁。他在全职运营 Y Combinator 的同时，用自己开发的 gstack 方法论，在六十天内交付了三个生产级服务和四十多个功能。他自己这样说：&quot;Same person. Different era. The difference is the tooling.&quot;同样的人，不同的时代。差别只在于工具。</p><img src="/2026/06/21/karpathy-half-year-no-code-prologue/image-20260523081109306.png" class=""><p>Garry Tan 的数字让人震撼。2026 年 5 月的硅谷 AI Ascent 大会上，Claude Code 的缔造者 Boris Cherny 给出的画面则让人恍惚。Cherny——Anthropic 的工程负责人（Engineering Lead）、Claude Code 的创造者——走上台，平静地描述了他现在的日常工作：<strong>他不再写代码。他审查代码。</strong> 他曾经同时运行大约一千个 AI Agent，并在一天之内合并了一百五十个拉取请求。今天的开发者，Cherny 说，本质上是一支临时工模型大军的&quot;工程经理&quot;。</p><img src="/2026/06/21/karpathy-half-year-no-code-prologue/image-20260523081945708.png" class=""><p>但 Cherny 也没有回避最棘手的问题：<strong>AI Agent 编写补丁的速度，已经远远超过了人类组织验证它们的能力。</strong> 这就是&quot;验证差距&quot;（Verification Gap）。模型经常在工作真正完成之前表现得&quot;非常自信&quot;——你既不能完全不信任 Agent，那样你会失去速度；也不能完全信任 Agent，那样你会失去质量。他给出的答案简洁得近乎冷酷：<strong>委派任务前的判断力，给予信任前要求证据的能力，合并代码后的责任感。</strong> 敲击键盘的速度和记忆 API 的数量不再重要。判断力、验证力、责任感才是新的硬通货。这三项能力构成了 AI 时代工程师的新技能栈。</p><p>但也许最令人动容的转变来自 Redis 的创造者 Salvatore Sanfilippo——社区里人们叫他 antirez。在程序员群体中，antirez 代表了一种几乎已经消失的浪漫：他相信每一行代码都应该经过人手的雕琢。他曾经写道：&quot;I love writing software, line by line. My career was a continuous effort to create software well written, minimal, where the human touch was the fundamental feature.&quot;他热爱一行一行地写代码。他的整个职业生涯都在追求一种极简的、充满人性触感的软件美学。</p><p>然而，就是这个人，在 2025 年坦承：&quot;Facts are facts, and AI is going to change programming forever.&quot;事实就是事实，AI 将永远改变编程。</p><img src="/2026/06/21/karpathy-half-year-no-code-prologue/image-20260523082322418.png" class=""><p>antirez 没有选择抵制。他选择理解。他提出了一个至关重要的概念区分——<strong>&quot;Automatic Programming&quot;（自动编程）与 &quot;Vibe Coding&quot;（氛围编码）是两回事</strong>。Vibe Coding 是把需求丢给 AI，接受它吐出的一切，不做审查，不做设计。而真正的 Automatic Programming 需要人类的直觉、设计判断、持续引导和对软件系统的深刻理解。AI 是放大器，不是替代品。他用 AI 在一周内完成了 DS4 项目的开发，让它成为了当时最流行的本地 AI 体验工具。但他审查了 AI 生成的每一行代码，做出了每一个关键架构决策。</p><h2 id="AI-放大了一切——包括你的工程缺陷"><a href="#AI-放大了一切——包括你的工程缺陷" class="headerlink" title="AI 放大了一切——包括你的工程缺陷"></a>AI 放大了一切——包括你的工程缺陷</h2><p>这些声音——Karpathy 的平静陈述、Amodei 的大胆预测、Garry Tan 的冰冷数据、Cherny 的工程坦率、antirez 的审慎拥抱——来自完全不同的方向，却指向同一个结论：**软件工程的范式正在发生五十年之变。**一个人的产出可以等于过去一个团队。自然语言正在成为最强大的编程接口。写代码这项技能，正在从&quot;必须自己动手&quot;变成&quot;必须自己动脑&quot;。</p><p>但如果你仔细听，在这些声音下面，有一个更深层的矛盾正在浮出水面。</p><p>AI 让&quot;写代码&quot;变得前所未有的容易，却让&quot;写好软件&quot;变得前所未有的困难。</p><p>任何人都可以用一句话让 AI 生成一个能跑的应用。这就是 Vibe Coding 的诱惑：你不需要理解数据结构，不需要考虑边界条件，不需要设计错误处理——你只需要说&quot;给我做一个&quot;。AI 会给你一个。它甚至看起来还不错。但当这个应用需要维护、需要扩展、需要与团队协作、需要经受生产环境的流量冲击时，Vibe Coding 的产物往往暴露了它的本质：一团不可测试、不可重构、不可理解的代码浆糊。</p><p>AI 可以让你以一百倍的速度写出代码。它也可以让你以一百倍的速度积累技术债务。AI 可以让你一小时交付一个原型。它也可以让你一周后完全无法理解自己的代码做了什么。AI 不会救你——它会放大你。你给它清晰的架构，它还你整洁的代码；你给它模糊的意图，它还你一团浆糊。它暴露你的工程能力，也同等精确地暴露你的工程缺陷。</p><p>这就是为什么，在 AI 让编码门槛降到历史最低点的时刻，<strong>工程化的价值反而达到了历史最高点</strong>。</p><h2 id="当开发速度不再稀缺，工程化就是最后的壁垒"><a href="#当开发速度不再稀缺，工程化就是最后的壁垒" class="headerlink" title="当开发速度不再稀缺，工程化就是最后的壁垒"></a>当开发速度不再稀缺，工程化就是最后的壁垒</h2><p>如果你的木工房里突然出现了一把能以一百倍速度切割木材的激光刀，你最需要的是更精确的测量工具、更严格的工艺流程、更可靠的安全护栏——而不是更快的刀。软件工程同理。当执行的速度被 AI 提升到前所未有的高度时，决定质量的是执行之前的规划、执行之中的约束、执行之后的验证。执行本身不再稀缺。</p><p>这正是过去两年间涌现的一系列新方法论试图解决的问题。</p><p>Matt Pocock——TypeScript 社区最受尊敬的工程教育家之一——提出了 Skills 系统的概念。他的核心洞见：Prompt 是临时的，Skill 是持久的。你不需要每次都对 AI 解释&quot;如何做代码审查&quot;，你只需要给它一个 Skill。&#x2F;diagnose 系统化调试、&#x2F;grill-me 启动前对齐、&#x2F;tdd 红-绿-重构——每一个 Skill 都是针对 AI 编程中特定失败模式的工程化解药，小而聚焦，模型无关，鼓励改造。</p><p>如果说 Skills 解决的是&quot;单次交互的质量&quot;，那么 Spec-Driven Development 解决的就是&quot;跨次交互的一致性&quot;。OpenSpec 和 Spec-Kit 代表的 SDD 方法论将规格文档变成了人类与 AI 之间的一份&quot;合约&quot;——在写代码之前先写规格，你不需要审查 AI 的每一行思维过程，你只需要审查它在规格层面是否履约。</p><p>Ralph Loop 将这个逻辑推到了极致：让 AI Agent 在循环中持续改进自己的代码，直到满足验收标准为止。Frank Bria 设计的双条件出口门机制要求 AI 既要说「我做完了」，还要显式发出退出信号。因为 AI 的自我评估不可信，需要多重验证。</p><p>Garry Tan 的 gstack 则展示了一种完全不同的想象力：将 Claude Code 变成一个拥有二十三个专家角色的虚拟工程团队。CEO 审查战略、工程经理审查架构、QA 审查质量、安全官审查漏洞——整个 Sprint 从 Think 到 Reflect 被构建为一条七阶段审查流水线。一个人就是一支军队。</p><p>superpowers 框架——全球已获超过十五万颗星标——将这些 Skills 组织成了一整套方法论库。而 jnMetaCode 的中文增强版 superpowers-zh，则为中国开发者补充了国内代码托管平台适配、中文排版规范、Conventional Commits 本地化等原创能力。</p><p>这些方法论背后的共同逻辑是什么？</p><p><strong>用结构化知识驾驭非结构化 AI 能力。</strong></p><p>Prompt 消失在对话历史里，Skill 留在你的工具链里。Vibe Coding 的产物不可复现，Spec-Driven 的产出有据可查。一次性的 AI 对话无法保证质量，闭环工作流让每一次产出都经过验证。</p><h2 id="为智能体构建运行环境"><a href="#为智能体构建运行环境" class="headerlink" title="为智能体构建运行环境"></a>为智能体构建运行环境</h2><p>2025 年末，一个被称为&quot;Harness Engineering&quot;的新概念开始在 AI Agent 开发者社区中流传。它的核心关注点不是写 AI 模型，不是做产品功能，而是构建编码 Agent 的底层运行基础设施——工具系统、权限模型、hooks 机制、配置管理层级。社区逐渐认识到：如果说 Prompt Engineering 是&quot;教会 AI 说什么&quot;，Skill Engineering 是&quot;教会 AI 做什么&quot;，那么 Harness Engineering 就是&quot;为 AI Agent 构建安全可靠的运行环境&quot;。</p><img src="/2026/06/21/karpathy-half-year-no-code-prologue/image-20260523082838441.png" class=""><p>这是一个信号。它意味着 AI Agent 的开发正在从一个&quot;试试看&quot;的实验阶段，进入一个需要专业工程实践的成熟阶段。正如游戏引擎架构之于游戏开发、编译器设计之于语言工具开发，Harness Engineering 正在成为 AI Agent 产品开发中的专门工程领域。它代表了从&quot;能用的 Agent&quot;到&quot;可靠的 Agent 产品&quot;之间那条必须跨越的工程鸿沟。</p><h2 id="关于这本书"><a href="#关于这本书" class="headerlink" title="关于这本书"></a>关于这本书</h2><p>我是一名在软件工程领域工作了近30年的程序员。过去两年里，我和许多同行一样，眼睁睁看着自己熟悉的那个世界——手写代码、逐行调试、Code Review——被 AI 一步步重构。我参与了多个 AI 驱动的开源项目，也亲手构建了一套名为 Goal Workflow 的 AI 研发工作流技能集。这本书中的每一个方法论，我都亲自实践过；每一个结论，都来自真实的项目迭代而非纸上推演。</p><p>这本书的写作动机，正是源于上述所有这些变化的交汇点。</p><p>全书分为三个部分。</p><p>第一部分——原理篇（第 1–11 章）——是全书的主体。我们从软件工程范式的五十年之变出发，逐一考察当前最具代表性的方法论：Matt Pocock 的 Skills 系统、OpenSpec 与 Spec-Kit 的规格驱动开发、Ralph Loop 的自主循环引擎、Garry Tan 的 gstack 虚拟团队方法、superpowers 技能框架、autoresearch 的全自动化开发流程，以及 Goal Workflow 的目标驱动研发闭环。然后将这些方法论放在一起对比、碰撞、融合。在此基础上，我们深入 Harness Engineering——为 AI Agent 构建安全可控运行环境的专门工程领域，以及用 Kanban 编排 AI Agent 项目的实践。这一部分帮助你构建属于自己的 AI 研发体系。</p><p>第二部分——技能篇（第 12–17 章）——聚焦 AI 软件工程的实用技能与工具链。我们考察 Anthropic 官方插件如何为 Agent 注入领域知识与工程工作流，Understand-Anything 如何构建代码知识图谱，UML 在理解 AI 生成代码中的新用途，AI 时代的重构方法论，Go 语言的 AI 开发工具链，以及 autoreview 与 Crabbox 带来的自动化代码审查与远程验证。这一部分是从方法论到日常工程实践的桥梁。</p><p>第三部分——实战篇（第 18–23 章）——以一个真实的 Go 语言项目 goscapy 为载体，完整演示前两部分的方法论和技能在真实项目中的落地执行。从项目背景理解到 PRD 规划，从 &#x2F;goal 迭代实现到 &#x2F;review-it 自动化审查，从 &#x2F;ship-it 交付合入到 Bonus Skills 的增强工具链——每一步都是实战，每一步都有真实代码和真实决策。goscapy 是一个纯 Go 实现的网络协议库，多协议支持、跨平台兼容，在生产环境中运行。这不是一个玩具项目。</p><p>但需要说清楚的是：这<strong>不是</strong>一本「如何使用 AI 工具」的操作手册。工具每天都在变。今天的 Claude Code 明天就不长这样，后天又会出现全新的工具形态。这是一本关于「<strong>如何在 AI 时代思考软件工程</strong>」的方法论著作。</p><h2 id="声明式编程的古老智慧"><a href="#声明式编程的古老智慧" class="headerlink" title="声明式编程的古老智慧"></a>声明式编程的古老智慧</h2><p>回到 Karpathy。</p><p>在那期播客里，除了那句被广泛引用的&quot;半年没写一行代码&quot;之外，他还说了另一句话，没那么出名，但同样重要。他说，使用 AI 编程的体验让他想起了一个古老的计算机科学概念：<strong>声明式编程</strong>。你不需要告诉计算机&quot;怎么做&quot;，你只需要告诉它&quot;要什么&quot;。</p><p>SQL 是声明式的——你说&quot;给我这些列、从这个表、满足这些条件&quot;，数据库引擎自己决定执行计划。在 AI 时代，整个软件开发正在变成声明式的：你说&quot;给我一个支持多协议、高并发、跨平台的网络包处理库&quot;，AI Agent 自己决定架构、选择模式、实现细节。</p><p>但声明式编程有一个前提：声明本身必须是精确的。模糊的 SQL 查询返回模糊的结果。模糊的需求描述产生模糊的软件。</p><p>这就是为什么，在 AI 可以帮你写出一切的时代，<strong>知道&quot;要什么&quot;比知道&quot;怎么做&quot;更重要</strong>。而&quot;知道要什么&quot;——定义清晰的验收标准、设计合理的架构约束、建立可验证的质量门——正是软件工程这门学科用半个世纪沉淀下来的核心能力。</p><p>这些能力从来没有过时。它们只是在等待一个让它们变得至关重要的时刻。</p><p>那个时刻就是现在。</p><p>欢迎来到 AI 时代的软件工程。</p>]]>
    </content>
    <id>https://colobu.com/2026/06/21/karpathy-half-year-no-code-prologue/</id>
    <link href="https://colobu.com/2026/06/21/karpathy-half-year-no-code-prologue/"/>
    <published>2026-06-21T13:20:27.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>&quot;I don&#39;t think I&#39;ve typed like a line of code probably since December, basically, which is an extremely large change.&quot;<br>从去年十二月起，我基本上一行代码都没写过，这是一个巨大的变化。</p>
<p>——Andrej Karpathy，No Priors 播客，2026 年 3 月</p>
</blockquote>]]>
    </summary>
    <title>00 卷首语：当 Karpathy 说他半年没写一行代码</title>
    <updated>2026-06-22T00:21:15.906Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="AI" scheme="https://colobu.com/categories/AI/"/>
    <category term="AI" scheme="https://colobu.com/tags/AI/"/>
    <content>
      <![CDATA[<p><a href="https://0xkato.xyz/tags/#machine-learning">Machine Learning</a> <a href="https://0xkato.xyz/tags/#transformers">Transformers</a> <a href="https://0xkato.xyz/tags/#llm">LLM</a> <a href="https://0xkato.xyz/tags/#neural-networks">Neural Networks</a> <a href="https://0xkato.xyz/tags/#ai">AI</a></p><p>本文带你走一遍 LLM 的工作原理。现代 LLM 大多是由 transformer 块反复堆叠而成的，因此理解了 transformer 机制，你就掌握了大部分。</p><p>我将覆盖现代基于 transformer 的 LLM 内部的核心机制，避开那些复杂的数学。别误会，你应该学数学，但本文可以作为一个入门。</p><p>大多数现代 LLM 共享同一套 transformer 家族的骨架。差异来自于各自的训练数据、规模和配置选择，以及在此之上的后训练。读完本文后，你应该能够阅读许多现代 LLM 论文或模型卡，并知道每个部分在讲架构中的哪个组件。</p><p>路线如下：</p><ol><li>Token——一串文本如何变成一组整数序列</li><li>Embedding——这些整数如何获得含义</li><li>位置编码——模型如何知道 token 的顺序</li><li>Attention——token 之间如何交换信息</li></ol><span id="more"></span><ol start="5"><li>多头注意力——模型如何同时追踪多种关系</li><li>前馈网络——模型存储结构的主要所在</li><li>残差流与层归一化——是什么让深层堆叠可训练</li><li>预测下一个 token——模型实际输出什么，以及生成循环如何运作</li><li>架构 vs 训练权重——现代 LLM 之间广泛共享什么，以及什么不同</li></ol><p><img src="https://www.0xkato.xyz/assets/transformer-pipeline.png" alt="Transformer pipeline from tokenization to next-token prediction"></p><p>文中穿插了一些简短解释（tiny explainer），无论你的背景如何都能跟上。</p><hr><h2 id="Tokenization（分词）"><a href="#Tokenization（分词）" class="headerlink" title="Tokenization（分词）"></a>Tokenization（分词）</h2><p>模型不直接阅读文本。它们读取整数 ID。这一步将你的提示转换为一组整数序列。</p><p>这个转换步骤叫做 tokenization（分词）。一个 tokenizer（分词器）接收一个字符串并产生一组整数序列，其中每个整数指向固定词汇表中的一个条目。现代 LLM 的词汇表通常包含数万到数十万个条目。</p><blockquote><p><strong>简短解释：token ID</strong><br>token ID 是模型用于某个词汇表条目的整数。模型处理的是数字，而不是书写的单词本身。</p></blockquote><p>Token 通常不是完整的单词。它们通常是子词片段。单词 &quot;tokenization&quot; 可能被拆分为 [&quot;token&quot;, &quot;ization&quot;]。&quot;running&quot; 可能被拆分为 [&quot;run&quot;, &quot;ning&quot;]。原因在于效率。全词词汇表太大，且无法泛化到新词。字符级词汇表又太小，迫使模型从零开始学习最简单的模式。子词分词则处于中间地带。最常见的片段成为单独的 token，罕见或新颖的词则由更小的片段组合而成。</p><blockquote><p><strong>简短解释：词汇表（vocabulary）</strong><br>词汇表是分词器的固定片段列表。每个片段有一个 ID，模型只能直接接收来自该列表的 ID。</p></blockquote><p>这种权衡在意想不到的地方表现出来。经典例子：问 LLM &quot;strawberry&quot; 中有几个 R。LLM 过去常常回答错误。这不是模型不会计数。而是模型不直接操作字母，它只操作那些恰好拼写出一个单词的 token ID——而这个单词人类会逐字母拆分。</p><p><img src="https://www.0xkato.xyz/assets/transformer-tokenization.png" alt="Tokenization turns text into token IDs"></p><p>不同的模型家族使用不同的分词器。GPT 模型使用 BPE（Byte Pair Encoding）变体。SentencePiece 在 LLaMA 风格的模型中很常见。选择影响计算量（更少的 token 意味着更少的工作）和多语言覆盖等，但基本形式相同：文本进，整数出。</p><p>现在提示已经是一组整数序列，下一步是赋予这些整数含义。</p><hr><h2 id="Embedding（嵌入）"><a href="#Embedding（嵌入）" class="headerlink" title="Embedding（嵌入）"></a>Embedding（嵌入）</h2><p>一个像 <code>1024</code> 这样的 token ID 只是一个行索引。它本身没有任何意义。赋予它意义的是一个巨大的表，叫做嵌入矩阵（embedding matrix）。</p><p>每个模型都有一个。它对词汇表中的每个条目都有一行，每行是一个长长的数字向量。每行的长度就是模型的隐藏维度大小（hidden size）。在许多 7B 级别的模型中，这意味着每个 token 对应 4,096 个数字。更大的模型通常使用更宽的向量。</p><blockquote><p><strong>简短解释：向量（vector）</strong><br>向量就是一个数字列表。在 transformer 中，每个 token 变成一个向量，这样模型就可以用它做数学运算。</p></blockquote><p>当分词器把整数交给模型时，模型查找那一行并用向量替换它。那个向量就是 token 的嵌入（embedding）。它是模型对该 token &quot;含义&quot; 的表示，是在训练过程中学到的。</p><blockquote><p><strong>简短解释：嵌入矩阵（embedding matrix）</strong><br>嵌入矩阵是一个查找表。token ID 进，学到的向量出。</p></blockquote><p>这些嵌入的一个有趣特性是，语义上相似的 token 最终会得到相似的向量。&quot;king&quot; 的向量在空间中接近 &quot;queen&quot; 的向量，&quot;Paris&quot; 的向量接近 &quot;France&quot; 的向量。这一切都不是硬编码的。它是在足够多的文本上训练后涌现出来的，模型学会这些位置是因为它们能让模型更好地预测文本。</p><p>你可以对嵌入做算术运算，有时候确实有效。著名的例子是 <code>king − man + woman ≈ queen</code>。嵌入空间的几何结构携带着真实的语义结构，尽管没有人告诉模型要以这种方式构建它。</p><p><img src="https://www.0xkato.xyz/assets/transformer-embedding-analogy.png" alt="Embedding space analogy with semantic relationships"></p><p>有一点需要明确：在这个阶段，每个 token 都被它的嵌入替换了，但嵌入本身不包含 token 在序列中的位置信息。&quot;dog&quot; 的向量无论在提示的第一个还是第五个位置，都是同一个向量。这是个问题。</p><p>这就是位置编码要填补的空白。</p><hr><h2 id="Positional-Encoding（位置编码）"><a href="#Positional-Encoding（位置编码）" class="headerlink" title="Positional Encoding（位置编码）"></a>Positional Encoding（位置编码）</h2><p>纯粹的 self-attention 没有内置的词序表示。没有某种位置信号，它无法直接知道 &quot;dog&quot; 在 &quot;bites&quot; 之前而不是之后。</p><p>词序会改变含义。所以模型需要另一个组件。它需要一种方式将每个 token 的位置注入到数学运算中。</p><blockquote><p><strong>简短解释：位置编码（positional encoding）</strong><br>位置编码是模型获取顺序信息的方式。它告诉模型每个 token 在序列中的位置。</p></blockquote><p>最初的 transformer 论文（Vaswani et al. 2017）的解决方案是给每个位置赋予自己的数字模式，并在任何其他处理之前直接加到每个 token 的嵌入上。位置 1 有一种模式，位置 5 有不同的模式，位置 100 有另一种模式。这些模式来自不同频率的正弦和余弦波。这样一来，位置 1 的 &quot;dog&quot; 的嵌入就不同于位置 5 的 &quot;dog&quot; 的嵌入，仅仅因为加在它上面的位置模式不同。</p><p>这能够工作，而且选择正弦编码的部分原因是它们可以外推到超出训练时见过的序列长度。但加法式的位置方案仍然有两个随着模型规模扩大而变得重要的问题。</p><p>首先，嵌入必须在同一组数字中同时承载含义和位置。能塞进去的东西是有限的。</p><p>其次，尤其是学到的绝对位置嵌入（learned absolute position embeddings）不能干净地泛化。如果你训练的提示最长 2,048 个 token，模型在训练时从未见过位置 5,000，那个位置的嵌入就不是以同样的方式学到的。</p><p>现代模型大多使用一种不同的方案，叫做 Rotary Position Embeddings（RoPE），由 Su et al. 于 2021 年提出，现在被 LLaMA、Mistral、Gemma、Qwen 和大多数其他开源权重家族所使用。直觉是：RoPE 不是将位置信息加到每个 token 的向量上，而是将 Query 和 Key 向量旋转一个取决于 token 位置的角度。位置 1 的 token 转一个小的角度，位置 100 的 token 转一个更大的角度。当两个 token 在后面的 attention 中被比较时，重要的是它们 Query 和 Key 旋转的差值，这编码了它们相距多远。</p><blockquote><p><strong>简短解释：RoPE</strong><br>RoPE 代表 Rotary Position Embeddings。它不是加一个位置向量，而是旋转 Query 和 Key 向量，使相对距离在 attention 中显现出来。</p></blockquote><p><img src="https://www.0xkato.xyz/assets/transformer-rope.png" alt="Rotary position embeddings rotate vectors by position"></p><p>实际的优点是真实的。RoPE 自然地编码相对位置（这更接近 attention 实际需要的东西）。它能更好地泛化到更长的上下文。而且它不给模型增加新的参数。</p><p>即使有了好的位置编码，现代 LLM 仍然有一个已记录在案的&quot;迷失在中间（lost in the middle）&quot;问题（Liu et al. 2023）。它们使用长提示开头和结尾的信息比使用中间的信息更可靠。这就是为什么像&quot;把重要上下文放在前面&quot;或&quot;在末尾重复关键信息&quot;这样的提示工程技巧确实有用。模型并不是同等地使用你提示的每个部分。</p><p>有了 token 含义和位置都编码完成，下一个问题是：token 实际上如何交换信息？</p><hr><h2 id="Attention"><a href="#Attention" class="headerlink" title="Attention"></a>Attention</h2><p>这就是赋予这个架构名字的机制。Attention。</p><p>在每个 transformer 层内部，attention 做一件事。它让每个 token 查看它被允许看到的其他 token，并决定哪些对接下来发生的事重要。</p><p>它通过同时给每个 token 赋予三个角色来实现。每个 token 被转换成三个新的向量，称为 Query、Key 和 Value（Q、K、V）。</p><blockquote><p><strong>简短解释：Q、K、V</strong><br>Query 表示&quot;我在找什么&quot;，Key 表示&quot;我匹配什么&quot;，Value 是匹配成功时被传递的信息。</p></blockquote><ul><li>Query 问：&quot;我从其他 token 那里在寻找什么？&quot;</li><li>Key 说：&quot;这就是我提供给正在看我的 token 的东西。&quot;</li><li>Value 携带：&quot;这就是匹配发生时被传递的东西。&quot;</li></ul><p>同一个 token 同时扮演全部三个角色。Q、K、V 的变换是学到的矩阵，所以模型在训练过程中会弄清楚每个 token 应该寻找什么以及它应该提供什么。</p><p>匹配通过相似度分数来发生。每个 token 的 Query 与它被允许看到的每个 token 的 Key 进行比较，使用缩放点积（scaled dot product）。直观地说，这衡量了两个向量的对齐程度。缩放使得数字在 softmax 之前保持稳定。</p><blockquote><p><strong>简短解释：点积（dot product）</strong><br>点积是一种简单的方法，用于给两个向量的对齐程度打分。对齐程度越高意味着匹配越强。</p></blockquote><p>然后，匹配分数通过 softmax 转换为权重。Softmax 接收任意一组数字，将它们转化为总和为 1 的类概率分布。匹配分数更高的 token 获得更高的权重，然后用这些权重取 value 向量的加权平均。</p><blockquote><p><strong>简短解释：softmax</strong><br>Softmax 将原始分数转化为加起来等于 1 的权重。大分数得大权重，小分数得小权重。</p></blockquote><p>举个例子。考虑句子 &quot;The cat that I saw yesterday was sleeping.&quot; 当模型处理 &quot;was&quot; 时，它需要弄清什么在睡觉。&quot;was&quot; 的 Query 向量与它被允许看到的 token 的 Key 向量进行比较。与 &quot;cat&quot; 的点积很高，因为模型已经学会像 &quot;was&quot; 这样的动词需要一个主语，而像 &quot;cat&quot; 这样的主语会产生与之对齐良好的 Key 向量。与 &quot;yesterday&quot; 的点积很低。Softmax 将这些分数转化为权重，&quot;cat&quot; 得到高权重，&quot;yesterday&quot; 得到低权重。然后模型对相应的 value 向量取加权和，所以 &quot;cat&quot; 的 value 主导了结果。&quot;was&quot; 的新表示现在主要由 &quot;cat&quot; 的 value 塑造。这就是几个位置之前的 token 如何成为被指代对象。</p><p>有一个 GPT 风格语言模型特有的约束，即它们从左到右生成文本。位置 5 的 token 只能关注位置 1 到 5。它不能关注位置 6、7、8 的 token，因为它们还没被生成。这叫做因果掩码（causal masking）。实现很简单：未来 token 的匹配分数低到经过 softmax 后权重几乎为零。</p><blockquote><p><strong>简短解释：因果掩码（causal masking）</strong><br>因果掩码隐藏未来的 token。它阻止 decoder-only 语言模型在预测下一个 token 时向前偷看。</p></blockquote><p><img src="https://www.0xkato.xyz/assets/transformer-attention-heatmap.png" alt="Attention heatmap showing causal masking and high attention to cat"></p><p>可解释性研究中最有趣的发现之一是关于一种专门的注意力头，叫做 induction head（归纳头），由 Anthropic 在 2022 年发现。这些头学会了在提示中发现 &quot;A B … A&quot; 这种模式，并预测接下来的会是 B。当模型第二次看到 &quot;A&quot; 时，归纳头回溯到上一次 &quot;A&quot; 出现的地方，看到它后面是什么，然后复制那个。它们是已知的最清晰的上下文学习（in-context learning）机制之一——LLM 从你的提示中捕捉到一个模式并继续它的能力。</p><blockquote><p><strong>简短解释：归纳头（induction head）</strong><br>归纳头是一种注意力头，它注意到提示中重复的模式并帮助延续它们。</p></blockquote><p>Attention 有一个巨大的成本。在全注意力（full attention）中，每个 token 与它被允许看到的所有 token 比较，所以提示长度加倍，工作量大约翻四倍。这就是为什么长提示运行成本高，以及为什么最近很多研究都在关注让 attention 更高效（FlashAttention、稀疏注意力、线性注意力）。</p><p>但一个注意力头只给模型提供一种关于关系的学到的视角。</p><hr><h2 id="Multi-Head-Attention（多头注意力）"><a href="#Multi-Head-Attention（多头注意力）" class="headerlink" title="Multi-Head Attention（多头注意力）"></a>Multi-Head Attention（多头注意力）</h2><p>一次 attention 传递给模型提供了一种决定哪些 token 对其他 token 重要的方式。这还不够。语言中有许多同时发生的关系。主谓一致。代词及其指代的名词。句子之间的长距离引用。词序和局部短语。</p><p>多头注意力通过并行地运行多次 attention 来解决这个问题，每个并行的传递在它自己较小的空间中操作。每个并行传递被称为一个头（head）。</p><blockquote><p><strong>简短解释：注意力头（attention head）</strong><br>一个注意力头是一次独立的 attention 传递，拥有自己学到的投影。</p></blockquote><p>这部分经常被描述错误——包括在大量教程中。每个头并不是获取原始 token 向量的字面切片。每个头有自己学到的投影矩阵，将完整的 token 向量映射到它自己较小的 Q、K、V 向量。所以如果一个模型每个 token 有 4,096 个数字和 32 个头，每个头通常在 128 维空间中工作，但那 128 个数字是完整 4,096 的学到的投影，而不是固定的切片。是同一 token 的不同&quot;视角&quot;，而不是它的不同分块。</p><p>每个头独立运行它的 attention 传递。然后所有头的输出被拼接（concatenate）起来，经过一个最终的线性层，将它们混合回一个完整大小的向量。模型也学习那个最终的混合。</p><p><img src="https://www.0xkato.xyz/assets/transformer-multi-head-attention.png" alt="Multi-head attention combines specialized attention heads"></p><p>有意思的是，不同的头往往最终部分地专门化。模型从未被告知每个头应该做什么。专门化是在训练中自然涌现的。研究人员发现了追踪语法的头（将动词连接到宾语、冠词连接到名词）、弄清代词指代哪个名词的头、追踪位置模式的头、归纳头，以及更多。一个 transformer 层可能有 32 个头。一个现代前沿模型有几十层。所以一个典型的 LLM 总共有数千个注意力头，每个都贡献自己学到的视角。</p><p>有一个实际的成本问题驱动了近期的架构变化。每个头需要将所有已生成 token 的 Key 和 Value 向量保存在内存中，这样当生成新 token 时模型不必从头重新计算所有内容。这叫做 KV 缓存（KV cache），它是在长上下文长度下运行 LLM 的主要内存成本。</p><blockquote><p><strong>简短解释：KV 缓存（KV cache）</strong><br>KV 缓存在生成过程中存储旧的 Key 和 Value 向量。它省去了模型每增加一个 token 就重新计算整个提示的工作。</p></blockquote><p>现代 decoder-only LLM 大多使用一种变体，叫做 Grouped-Query Attention（GQA）。不是每个头都有自己的 key 和 value，而是头分组共享相同的 key 和 value 头。LLaMA-2 70B 有 64 个 query 头但只有 8 个 key&#x2F;value 头。Mistral 7B 有 32 个 query 头和 8 个 key&#x2F;value 头。结果几乎是全多头注意力相同的精度，但内存压力和推理成本大大降低。</p><blockquote><p><strong>简短解释：GQA</strong><br>Grouped-Query Attention 允许多个 query 头共享更少的 key&#x2F;value 头。这在保持多个 query 视角的同时削减了 KV 缓存内存。</p></blockquote><hr><h2 id="Feed-Forward-Network（前馈网络）"><a href="#Feed-Forward-Network（前馈网络）" class="headerlink" title="Feed-Forward Network（前馈网络）"></a>Feed-Forward Network（前馈网络）</h2><p>在 attention 完成 token 之间的信息混合之后，每一层还有第二步，但谈论它的人少得多。前馈网络。</p><p>如果说 attention 是 token 之间互相交谈，那么前馈网络是每个 token 独立地做更多处理。它对每个 token 的向量独立运行，没有跨 token 的混合。</p><p>前馈网络按顺序做三件事：</p><ol><li>将 token 的向量扩展到更大的尺寸（原始 transformer 使用 4x，而现代 SwiGLU 模型通常使用不同的扩展尺寸）。</li><li>应用一个非线性函数。</li><li>将向量压缩回原始尺寸。</li></ol><p><img src="https://www.0xkato.xyz/assets/transformer-ffn.png" alt="Feed-forward network expands, transforms, and compresses each token vector"></p><p>中间那个非线性步骤做了某件值得理解的具体事情。非线性是一个弯曲其输入的函数。最简单的，ReLU，对任何负数输出零，对正数原样传递。</p><blockquote><p><strong>简短解释：非线性（non-linearity）</strong><br>非线性是一个函数，它阻止网络坍塌成一个大的线性变换。</p></blockquote><p>没有它，FFN 只是两个线性层叠在一起，而堆叠纯线性数学会坍塌。两个线性层连续排列在数学上等价于一个线性层，一百层线性层连续排列仍然等价于一层。非线性阻止了这种坍塌，它是 FFN 能够做到比单次矩阵乘法更丰富的事情的原因。</p><p>原始 transformer 使用 ReLU。GPT 和 BERT 转向 GELU。现代模型如 LLaMA、Mistral 和 PaLM 使用 SwiGLU。扩展-然后-压缩的结构保持不变。被迭代改进的是非线性本身。</p><p>密集 transformer 模型的大部分参数都在 FFN 中，而不是 attention 中。大部分权重位于前馈层中。</p><p>而这些参数不是泛化的。它们承载了模型存储的大部分事实和语义结构。研究人员发现 FFN 内部的某些神经元与特定的概念或事实强烈关联。一个神经元可能在埃菲尔铁塔相关的文本上强烈激活。另一个在编程语言上。另一个在过去式动词上。当模型&quot;知道&quot;巴黎是法国的首都时，这个事实由特定层中的 FFN 权重和激活来表示。</p><p>这种存储记忆的特性有一个有趣的推论。研究人员已经弄清楚了如何在训练好的模型中直接编辑某些事实而无需重新训练。像 ROME（Rank-One Model Editing）这样的方法可以通过对特定 FFN 权重矩阵进行目标低秩编辑，将&quot;埃菲尔铁塔在巴黎&quot;改为&quot;埃菲尔铁塔在罗马&quot;。然后模型会倾向于生成与编辑后的关联一致的文本。</p><p>一些现代前沿模型已经开始用称为 Mixture of Experts（MoE）的东西替换密集 FFN。不是每层有一个前馈网络，模型有许多并行的 FFN（称为 experts），以及一个微小的路由网络来选择哪些 expert 处理每个 token。Mixtral 8x7B 每层有 8 个 expert；对于任何给定的 token 只有 2 个被激活。总参数量大幅增加，但每个 token 的计算量增长慢得多，因为只有少数几个 expert 在运行。这就是如何在不成比例地扩展推理成本的情况下扩展参数规模。</p><blockquote><p><strong>简短解释：MoE</strong><br>Mixture of Experts 意味着模型有几个前馈网络，并将每个 token 只路由通过其中少数几个。</p></blockquote><p>Mixtral 8x7B 总共有 467 亿参数，但每个 token 只用大约 129 亿。对于非常大型的模型，这已经成为一个常见选项，因为它让你在不断增加参数规模的同时不让推理成本成比例增长。</p><hr><h2 id="Residual-Stream-and-Layer-Normalization（残差流与层归一化）"><a href="#Residual-Stream-and-Layer-Normalization（残差流与层归一化）" class="headerlink" title="Residual Stream and Layer Normalization（残差流与层归一化）"></a>Residual Stream and Layer Normalization（残差流与层归一化）</h2><p>残差流使得模型是&quot;加法式&quot;的而不是&quot;替换式&quot;的。在 attention 运行之后，或前馈网络运行之后，结果通常不替换 token 的向量。它被加到上面。逐个位置地加。新向量等于旧向量加上子块的输出。</p><blockquote><p><strong>简短解释：残差连接（residual connection）</strong><br>残差连接将块的输出加回它起始时的向量。它为信息和梯度提供了通过网络的捷径。</p></blockquote><p>跨越三十、五十或一百层，每层的贡献累积起来，而不是简单地覆写前一个向量。这个运行中的和被称为残差流（residual stream），它有一个奇特的性质。原始输入嵌入仍然有一条到达后面层的直接加法路径，与沿途每个子块的贡献混合在一起。</p><p><img src="https://www.0xkato.xyz/assets/transformer-residual-stream.png" alt="Residual stream accumulates attention and feed-forward outputs"></p><p>残差连接不是为 transformer 发明的。它们来自 ResNet（He et al. 2015），最初用于图像识别。动机是深层网络无法训练。训练信号在穿越许多层回来时变得太弱（或有时太强）。模型实际上无法从自己的错误中学习。添加一条捷径路径让信号直接从输出流回输入。突然间你可以训练有数百层的网络了。Transformer 继承了同样的技巧。</p><p>在现代可解释性研究中，残差流已经成为核心对象。每个组件——每个注意力头、每个前馈网络、甚至最后的反嵌入步骤——都从残差流读取并写回其中。</p><p>第二部分，层归一化（layer normalization），存在的原因要实际得多。没有它，残差流将无法保持稳定。流经数十次加法的数字倾向于要么爆炸式增长，要么坍缩到零。无论哪种情况，训练都会失败。层归一化在每个子块之间将每个 token 的向量重新缩放到一个受控的范围。</p><blockquote><p><strong>简短解释：层归一化（layer normalization）</strong><br>层归一化重新缩放 token 向量，使其数字在模型训练期间保持在一个稳定的范围内。</p></blockquote><p>原始 2017 年的 transformer 在每个子块之后应用归一化（post-norm）。这对于浅层模型有效，但随着深度增加变得更难可靠训练。现代 transformer（GPT-2 以后，LLaMA、Mistral）通常在每个子块之前应用归一化（pre-norm）。这是使得非常深的 transformer 更容易训练的变更之一。</p><p>归一化函数本身也变了。许多现代开源模型（LLaMA、Mistral、Gemma、Phi）使用一种更简单的变体，叫做 RMSNorm。原始层归一化同时做两件事：将每个向量向零平移，然后重新缩放数字的大小。RMSNorm 去掉了平移步骤，只保留缩放。经验上，缩放承载了大部分好处，同时计算成本更低。</p><blockquote><p><strong>简短解释：RMSNorm</strong><br>RMSNorm 是一种更便宜的归一化方法，在不先减去均值的情况下重新缩放向量大小。</p></blockquote><p>这就是那些不光彩的基础设施。没有残差连接，非常深的模型会变得极难训练。没有层归一化，运行中的和可能爆炸或坍缩。有了两者，你就能得到数百层深的模型。</p><hr><h2 id="Next-Token-Prediction（下一个-token-预测）"><a href="#Next-Token-Prediction（下一个-token-预测）" class="headerlink" title="Next-Token Prediction（下一个 token 预测）"></a>Next-Token Prediction（下一个 token 预测）</h2><p>在所有 attention 和前馈处理层完成之后，模型对序列中的每个 token 都有一个向量。在生成过程中，要预测下一个词，它只取最后一个 token 的最终向量。</p><p>那个最后的向量被转换为每个可能的下一个 token 对应一个数字。如果词汇表有 100,000 个 token，那就是 100,000 个数字。这些数字叫做 logits。它们还不是概率。它们可以是任何大小，正数或负数。</p><blockquote><p><strong>简短解释：logits</strong><br>Logits 是每个可能的下一个 token 的原始分数。只有在 softmax 之后它们才变成概率。</p></blockquote><p>Softmax 将这些 logits 转化为模型在可能的下一个 token 上的概率分布。和之前一样的操作，在模型中的不同位置。</p><p>模型通常不每次只选最高概率的 token。解码设置控制输出的确定性或多样性程度。Temperature 改变分布的尖锐程度。Top-k 和 top-p 将选择限制在最合理的一组下一个 token。这就是为什么同一个模型在一种设置下可以感觉精确，在另一种设置下可以更有创意。</p><blockquote><p><strong>简短解释：temperature</strong><br>Temperature 控制采样期间的随机性。低 temperature 使模型更保守；高 temperature 使输出更多样化。</p></blockquote><p>一旦选出一个 token，它就被添加到输入中。模型在更长的序列上运行下一步，通常重用 KV 缓存，这样就不必从头重新计算整个前缀。新 token 的新 attention。新前馈。新最终向量。新预测。循环继续，直到模型输出一个序列结束 token 或达到长度限制。一整段话就是这个循环，一次一个 token。</p><p>这个单一目标——预测下一个 token——是基础 LLM 的核心训练信号。基础模型不是被训练来做事实准确性、对话能力、推理或编程的。它被训练来预测海量文本中的下一个 token。之后的后训练才能将模型调整为指令遵循、偏好、安全性和对话行为。</p><p>有一个值得了解的重大效率创新。它叫做投机解码（speculative decoding）。一个小型快速模型提前提出几个 token。大模型并行地验证它们。如果提出的 token 在大模型的概率下被接受，就接受它们。如果没有，就回退到大模型。做得正确的话，输出分布与单独运行大模型一致，但循环可以快得多。</p><blockquote><p><strong>简短解释：投机解码（speculative decoding）</strong><br>投机解码使用一个小型草稿模型向前猜测，然后让较大的模型一次验证几个猜测的 token。</p></blockquote><p>下一个 token 预测循环是架构中最简单的部分，但它是让整个系统运作起来的东西。</p><hr><h2 id="Architecture-vs-Trained-Weights（架构-vs-训练权重）"><a href="#Architecture-vs-Trained-Weights（架构-vs-训练权重）" class="headerlink" title="Architecture vs Trained Weights（架构 vs 训练权重）"></a>Architecture vs Trained Weights（架构 vs 训练权重）</h2><p>我们已经走过了核心机制：token、嵌入、位置编码、attention、多头注意力、前馈网络、残差流与归一化，以及输出侧的下一个 token 循环。这就是基本架构的一遍遍历。</p><p>那么 GPT、Claude、Gemini 和 LLaMA 之间实际有什么区别？公开细节各不相同，而闭源模型不会公布所有的架构选择。但在本文所覆盖的层面，它们大致处于同一 transformer 家族的设计空间之中。</p><p>大多数现代基于 transformer 的 LLM 使用相同的大致结构：分词、嵌入、位置编码、堆叠的 transformer 层（每层有多头注意力和前馈网络）、残差流、层归一化，以及下一个 token 预测。</p><p>模型之间的不同在于：</p><ol><li>训练权重本身——从不同的训练数据、在不同的规模上学习而来。</li><li>配置：层数、词汇表大小、头数、参数量、MoE 还是密集。</li><li>后训练：指令微调、基于人类反馈的学习、在基础模型之上应用的安全控制。</li></ol><blockquote><p><strong>简短解释：权重（weights）</strong><br>权重是模型内部学到的数字。训练会改变这些数字，直到模型能很好地预测文本。</p></blockquote><p>2023-2025 年的&quot;现代 transformer&quot;技术栈在许多严肃的前沿和开源权重模型上收敛到了一组共同的选择，尽管不同的团队是独立达到这些选择的。Pre-norm 布局。RMSNorm。RoPE。SwiGLU。Grouped-Query Attention。在一些最大型的模型中使用 Mixture of Experts。这些都不是一次性发明的。它们是在原始 2017 年设计之上大约五年的精炼中累积起来的。</p><hr><h2 id="未来走向"><a href="#未来走向" class="headerlink" title="未来走向"></a>未来走向</h2><p>Transformer 家族架构的收敛在机器学习历史上是不寻常的。在这个领域的大部分历史中，每个问题都有自己的专门网络。图像识别用一种。语言用另一种。音频用第三种。视觉和语言团队几乎不共享方法。</p><p>现在 transformer 风格的模型出现在语言、视觉、音频和多模态系统中。Transformer 吸收了该领域的很大一部分。</p><p>这可能会改变。Mamba 和其他状态空间模型是可信的替代方案，特别是对于非常长的序列。混合架构正在被探索。Mixture-of-Experts 已经以五年前会被认为是异域的方式改变了前沿上&quot;架构&quot;的含义。</p><p>但本文中的核心机制——token、嵌入、位置编码、attention、前馈网络、残差流与归一化，以及下一个 token 预测——是持久的部分。即使架构发生变化，这些也是任何序列模型必须以某种形式解决的问题。</p><p>如果你读到了这里，你现在可以阅读许多现代 transformer 论文或模型卡，并知道每个部分在讲哪个组件。这就是目标。</p><hr><p><strong>原文来源</strong>：0xkato, &quot;How LLMs Actually Work&quot;, June 1, 2026, <a href="https://www.0xkato.xyz/how-llms-actually-work/">https://www.0xkato.xyz/how-llms-actually-work/</a></p>]]>
    </content>
    <id>https://colobu.com/2026/06/21/how-llm-actually-works/</id>
    <link href="https://colobu.com/2026/06/21/how-llm-actually-works/"/>
    <published>2026-06-21T03:09:44.000Z</published>
    <summary>
      <![CDATA[<p><a href="https://0xkato.xyz/tags/#machine-learning">Machine Learning</a> <a href="https://0xkato.xyz/tags/#transformers">Transformers</a> <a href="https://0xkato.xyz/tags/#llm">LLM</a> <a href="https://0xkato.xyz/tags/#neural-networks">Neural Networks</a> <a href="https://0xkato.xyz/tags/#ai">AI</a></p>
<p>本文带你走一遍 LLM 的工作原理。现代 LLM 大多是由 transformer 块反复堆叠而成的，因此理解了 transformer 机制，你就掌握了大部分。</p>
<p>我将覆盖现代基于 transformer 的 LLM 内部的核心机制，避开那些复杂的数学。别误会，你应该学数学，但本文可以作为一个入门。</p>
<p>大多数现代 LLM 共享同一套 transformer 家族的骨架。差异来自于各自的训练数据、规模和配置选择，以及在此之上的后训练。读完本文后，你应该能够阅读许多现代 LLM 论文或模型卡，并知道每个部分在讲架构中的哪个组件。</p>
<p>路线如下：</p>
<ol>
<li>Token——一串文本如何变成一组整数序列</li>
<li>Embedding——这些整数如何获得含义</li>
<li>位置编码——模型如何知道 token 的顺序</li>
<li>Attention——token 之间如何交换信息</li>
</ol>]]>
    </summary>
    <title>LLM 究竟是如何工作的？</title>
    <updated>2026-06-22T00:21:15.717Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="Go" scheme="https://colobu.com/categories/Go/"/>
    <category term="Go" scheme="https://colobu.com/tags/Go/"/>
    <content>
      <![CDATA[<p>Go 在发布新版本时经常会附带<strong>实验性特性（experimental features）</strong>。</p><p>这些实验性特性有不同的形式：有时是标准库中全新的包，有时是编译器或运行时的改动，偶尔也可能是对 Go 行为的破坏性变更。</p><p>大多数情况下，实验性特性的目的是在某个功能正式进入 <strong>通用可用（general availability）</strong> 阶段、成为 Go 的永久组成部分之前，从用户那里获取真实世界的反馈。如果该特性导致性能退化，或收到社区的负面反馈，它可以在最终定稿前被修改——甚至被完全放弃。</p><span id="more"></span><h2 id="一些示例"><a href="#一些示例" class="headerlink" title="一些示例"></a>一些示例</h2><p>让我们看几个近期的例子，来说明 Go 实验特性可能涉及的内容类型。</p><ul><li><p>Go 1.24 发布时附带了新的 <code>testing/synctest</code> 包的实验性支持（该包提供了测试并发代码的支持）。在收到反馈后，该包的 API 略有调整，并在 Go 1.25 中正式进入通用可用阶段。</p></li><li><p>Go 1.25 发布时附带了<a href="https://github.com/golang/go/issues/73581">新的垃圾回收器</a>设计的实验性支持，具有更好的性能。在吸收反馈之后，新的垃圾回收器在 Go 1.26 中成为默认选项。</p></li><li><p>Go 1.21 发布时附带了<a href="https://go.dev/wiki/LoopvarExperiment">循环变量语义行为变更</a>的实验性支持。这一变更消除了 Go 代码中一个以前常见的 bug，但在技术上是对语言的破坏性变更。将该变更作为实验发布，让人们在 Go 1.22 中该行为成为默认之前有机会测试自己的代码。</p></li></ul><h2 id="实验特性的生命周期"><a href="#实验特性的生命周期" class="headerlink" title="实验特性的生命周期"></a>实验特性的生命周期</h2><p>实验特性并没有单一固定的生命周期，但有一些常见的模式。</p><p>大多数实验特性最初以**默认关闭（off-by-default）**的方式发布。你需要显式选择加入（opt-in）来试用该功能，通常是通过设置 <code>GOEXPERIMENT</code> 环境变量（稍后会详细讨论）。</p><p>如果一切顺利，经过一到两个版本之后，实验特性被最终确定，进入通用可用阶段，并变为<strong>默认开启（on-by-default）</strong>。</p><p>如果一个实验特性影响了某些行为，那么它在进入通用可用阶段后，有时——但并非总是——会有一个过渡性的宽限期，在此期间可以临时禁用该特性并使用旧行为。例如，在 Go 1.26 中，新的垃圾回收器设计（上面简要提到过）进入了通用可用阶段并默认开启，但如果需要，仍然可以禁用它并使用旧的垃圾回收器。</p><p>以上就是最常见的模式，但有时候事情会更漫长或走向不同的方向。例如：</p><ul><li>Go 1.22 发布时附带了编译器内联逻辑的实验性实现，两年多过去了，它仍然默认关闭，处于评估之中。</li><li>同一次发布还附带了一个<strong>内存 arena</strong> 实验。在收到用户的负面反馈和顾虑之后，它仍然默认关闭，处于<a href="https://avittig.medium.com/golangs-big-miss-on-memory-arenas-f1375524cc90">无限期搁置</a>状态，最终可能会被完全移除。</li></ul><p>或者，当 Go 团队对某个变更足够有信心时，他们可能会跳过反馈阶段，直接进入通用可用阶段……但仍然可能有一个过渡性的宽限期，在此期间可以禁用它。</p><p>一个很好的例子是 Go 1.24 将其 map 实现改为使用 <a href="https://go.dev/blog/swisstable">Swiss tables</a>。Go 团队对实现及其性能优势足够有信心，因此直接进入通用可用阶段并默认开启，但——至少目前——如果你愿意，仍然可以选择退出并使用旧的 map 实现。</p><p>所以在实践中，实验特性实际上有三种大致状态：</p><ul><li>默认关闭，评估中</li><li>默认关闭，搁置&#x2F;休眠中</li><li>默认开启，但有临时退出选项</li></ul><h2 id="永久实验特性"><a href="#永久实验特性" class="headerlink" title="永久实验特性"></a>永久实验特性</h2><p>Go 还有一些实验性功能，它们并非通常意义上的&quot;实验&quot;。</p><p>这些功能默认关闭，但它们不在评估中，不在寻求反馈，也没有预期它们最终会进入通用可用阶段并变为默认开启。</p><p>尽管它们通过 <code>GOEXPERIMENT</code> 环境设置以与其他实验相同的方式控制，但实际上它们更像是你可能在特定场景下想要使用的可选 Go 功能。</p><p>在下文中，我将这些称为&quot;永久实验特性（permanent experiments）&quot;。</p><p>例如，有一个 <a href="https://codereview.appspot.com/6749064">field tracking</a> 诊断功能，用于追踪哪些结构体字段被访问。它已经存在十年了，并且<a href="https://github.com/golang/go/issues/42712#issuecomment-737414957">没有意图</a>让它进入通用可用阶段。还有一个 <a href="https://go.googlesource.com/go/+/0a820007e70fdd038950f28254c6269cd9588c02">static lock ranking</a> 功能，这是一个用于查找 Go 运行时中潜在死锁的诊断工具。</p><h2 id="当前有哪些可用的实验特性？"><a href="#当前有哪些可用的实验特性？" class="headerlink" title="当前有哪些可用的实验特性？"></a>当前有哪些可用的实验特性？</h2><p>要了解当前有哪些可用的实验特性及其状态，出奇地困难。</p><p>遗憾的是，Go 官方文档或 <a href="https://go.dev/wiki/All">Go Wiki</a> 中没有一个页面来追踪实验特性的状态，为了写这篇文章，我不得不从各处拼凑信息。如果你想做同样的事：</p><ul><li>可以运行 <code>$ go doc goexperiment.Flags</code> 获取所有可用实验的列表。</li><li>可以通过阅读 <code>src/internal/buildcfg/exp.go</code> 的源码——特别关注 <code>ParseGOEXPERIMENT()</code> 函数中的 <code>baseline</code> 变量声明——来找出哪些实验是默认开启的。</li><li>可以将实验名称与 Go 发布说明交叉对照，并搜索 GitHub issues 来尝试弄清当前的状态。</li></ul><p>据我所知，截至 Go 1.26，以下是当前可用的永久实验特性：</p><table><thead><tr><th>实验名称</th><th>描述</th><th>状态</th></tr></thead><tbody><tr><td><code>FieldTrack</code></td><td>追踪哪些结构体字段被访问的诊断工具</td><td>默认关闭，<a href="https://github.com/golang/go/issues/42712#issuecomment-737414957">永久存在</a></td></tr><tr><td><code>StaticLockRanking</code></td><td>验证锁获取顺序以捕获死锁的诊断工具</td><td>默认关闭，永久存在</td></tr><tr><td><code>CgoCheck2</code></td><td><a href="https://tip.golang.org/doc/go1.21#runtimepkgruntime">检查 cgo 指针传递规则</a>的诊断工具；运行时开销太大，不适合默认开启</td><td>默认关闭，永久存在</td></tr><tr><td><code>BoringCrypto</code></td><td>用 FIPS 认证的 BoringSSL 替换 Go 的 crypto；自 <a href="https://go.dev/doc/go1.24#fips140">Go 1.24</a> 起已不再需要</td><td>默认关闭，<a href="https://github.com/golang/go/issues/42712#issuecomment-737414957">永久存在</a>，但<a href="https://go.dev/blog/fips140">很快将被移除</a></td></tr><tr><td><code>PreemptibleLoops</code></td><td>允许调度器在循环<a href="https://github.com/golang/go/issues/10958">回边处抢占 goroutine</a>；自 Go 1.14 起通常不再需要，但在<a href="https://go.dev/doc/go1.14#runtime">不支持抢占的平台上</a>可能仍然有用</td><td>默认关闭，永久存在</td></tr></tbody></table><p>以下是当前默认关闭的实验特性及其状态：</p><table><thead><tr><th>实验名称</th><th>描述</th><th>状态</th></tr></thead><tbody><tr><td><code>HeapMinimum512KiB</code></td><td>将最小堆大小从 4MB 减少到 512KiB；可能在受限环境中很有用</td><td>默认关闭，<a href="https://github.com/golang/go/commit/c5c1955077cb94736b0f311b3a02419d166f45ac">可能已休眠</a></td></tr><tr><td><code>Arenas</code></td><td><a href="https://uptrace.dev/blog/golang-memory-arena">内存 arena</a> 实现</td><td>默认关闭，收到负面反馈后<a href="https://github.com/golang/go/issues/51317">已搁置</a></td></tr><tr><td><code>NewInliner</code></td><td>重写的编译器内联器，具有更好的调用点启发式算法</td><td>默认关闭，评估中（自 <a href="https://go.dev/doc/go1.22#compiler">Go 1.22</a> 起可用）</td></tr><tr><td><code>JSONv2</code></td><td>新的 <code>encoding/json/v2</code> 包，提供改进的 JSON 编码&#x2F;解码函数</td><td>默认关闭，评估中（自 <a href="https://go.dev/doc/go1.25#json_v2">Go 1.25</a> 起可用）</td></tr><tr><td><code>RuntimeSecret</code></td><td>新的 <code>runtime/secret</code> 包，提供清零内存的函数；仅支持 Linux amd64&#x2F;arm64</td><td>默认关闭，评估中（自 <a href="https://go.dev/doc/go1.26#new-experimental-runtimesecret-package">Go 1.26</a> 起可用）</td></tr><tr><td><code>GoroutineLeakProfile</code></td><td>新增 <code>goroutineleak</code> pprof 分析类型</td><td>默认关闭，评估中（自 <a href="https://go.dev/doc/go1.26#goroutineleak-profiles">Go 1.26</a> 起可用）</td></tr><tr><td><code>SIMD</code></td><td>新的 <code>simd/archsimd</code> 包，提供对架构特定 SIMD 操作的访问；仅支持 amd64</td><td>默认关闭，评估中（自 <a href="https://go.dev/doc/go1.26#simd">Go 1.26</a> 起可用）</td></tr><tr><td><code>RuntimeFreegc</code></td><td>在安全时允许立即重用内存而无需等待 GC 周期</td><td>默认关闭，评估中（自 Go 1.26 起可用，但状态信息见 <a href="https://github.com/golang/go/issues/74299">#74299</a>）</td></tr><tr><td><code>SizeSpecializedMalloc</code></td><td>启用按大小类别特化的 malloc 实现</td><td>默认关闭，评估中（自 Go 1.26 起可用，但状态信息见 <a href="https://github.com/golang/go/issues/74299">#74299</a>）</td></tr></tbody></table><p>以下是当前默认开启的实验特性：</p><table><thead><tr><th>实验名称</th><th>描述</th><th>状态</th></tr></thead><tbody><tr><td><code>LoopVar</code></td><td>每次迭代独立的<a href="https://go.dev/wiki/LoopvarExperiment">循环变量作用域</a></td><td>自 <a href="https://go.dev/doc/go1.22">Go 1.22</a> 起默认开启，但为边缘情况保留了退出选项</td></tr><tr><td><code>Dwarf5</code></td><td>DWARF 5 调试信息生成；减小二进制文件大小</td><td>默认开启，保留临时退出选项（退出选项<a href="https://go.dev/doc/go1.25#dwarf5-support">可能在未来的版本中被移除</a>）</td></tr><tr><td><code>RandomizedHeapBase64</code></td><td>在启动时随机化堆基址，作为安全措施</td><td>默认开启，保留临时退出选项（退出选项<a href="https://go.dev/doc/go1.26#heap-base-address-randomization">预计将在未来版本中移除</a>）</td></tr><tr><td><code>GreenTeaGC</code></td><td>新的垃圾回收器，具有更好的性能；在 darwin&#x2F;ios&#x2F;aix 上不可用</td><td>默认开启，保留临时退出选项（退出选项<a href="https://go.dev/doc/go1.26#new-garbage-collector">预计将在 Go 1.27 中移除</a>）</td></tr><tr><td><code>RegabiWrappers</code></td><td>用于在 ABI0 和 ABIInternal 函数之间调用的 ABI 包装器；仅支持 64 位架构</td><td>默认开启，保留临时退出选项，但退出选项仅对 s390x 有效，且<a href="https://github.com/golang/go/commit/6da07b9b44d2ae08921cb97900f076c96a7bf6fc">将在 Go 1.27 中移除</a></td></tr><tr><td><code>RegabiArgs</code></td><td>在所有编译的 Go 函数中启用寄存器参数&#x2F;返回值；仅支持 64 位架构</td><td>默认开启，保留临时退出选项，但退出选项仅对 s390x 有效，且<a href="https://github.com/golang/go/commit/6da07b9b44d2ae08921cb97900f076c96a7bf6fc">将在 Go 1.27 中移除</a></td></tr></tbody></table><h2 id="如何启用和禁用实验特性？"><a href="#如何启用和禁用实验特性？" class="headerlink" title="如何启用和禁用实验特性？"></a>如何启用和禁用实验特性？</h2><p>实验特性通过 <code>GOEXPERIMENT</code> 环境设置来控制。</p><p>如果你想尝试某些默认关闭的实验特性，应将实验名称作为逗号分隔的<strong>小写</strong>值包含在 <code>GOEXPERIMENT</code> 中。例如，如果你想在构建应用程序时启用 <code>JSONv2</code> 和 <code>GoroutineLeakProfile</code> 实验，可以这样做：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ GOEXPERIMENT=jsonv2,goroutineleakprofile go build ./...</span><br></pre></td></tr></table></figure><p>如果你想关闭某个默认开启的实验特性，可以在小写实验名称前加上 <code>no</code> 前缀。例如，如果你想在构建应用程序时关闭 <code>GreenTeaGC</code> 和 <code>RandomizedHeapBase64</code> 实验，可以这样做：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ GOEXPERIMENT=nogreenteagc,norandomizedheapbase64 go build ./...</span><br></pre></td></tr></table></figure><p>混合启用和禁用的实验也完全没有问题：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ GOEXPERIMENT=jsonv2,nogreenteagc go build ./...</span><br></pre></td></tr></table></figure><p>注意，如果你使用不同的 <code>GOEXPERIMENT</code> 值构建同一个包，Go 会将它们视为不同的构建，并在构建缓存中存储独立的条目。</p><p>我在上面的示例中使用了 <code>go build</code>，但你在使用 <code>go run</code> 或 <code>go test</code> 时也可以使用完全相同的模式。如果你想亲自尝试，试着创建以下使用实验性 <a href="https://pkg.go.dev/encoding/json/v2"><code>encoding/json/v2</code></a> 包的程序：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">package main</span><br><span class="line"></span><br><span class="line">import (</span><br><span class="line">    &quot;encoding/json/v2&quot;</span><br><span class="line">    &quot;fmt&quot;</span><br><span class="line">)</span><br><span class="line"></span><br><span class="line">type Person struct &#123;</span><br><span class="line">    Name string `json:&quot;name&quot;`</span><br><span class="line">    Age  int    `json:&quot;age&quot;`</span><br><span class="line">    City string `json:&quot;city&quot;`</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line">func main() &#123;</span><br><span class="line">    p := Person&#123;Name: &quot;Ada&quot;, Age: 36, City: &quot;Vienna&quot;&#125;</span><br><span class="line"></span><br><span class="line">    data, _ := json.Marshal(p, json.StringifyNumbers(true))</span><br><span class="line">    fmt.Println(string(data))</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>如果你正常运行此程序，它将无法编译，你会看到类似以下的错误消息：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">$ go run main.go</span><br><span class="line">package command-line-arguments</span><br><span class="line">        imports encoding/json/v2: build constraints exclude all Go files in /usr/local/go/src/encoding/json/v2</span><br></pre></td></tr></table></figure><p>但如果你启用了 <code>JSONv2</code> 实验，程序将按预期运行：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ GOEXPERIMENT=jsonv2 go run main.go</span><br><span class="line">&#123;&quot;name&quot;:&quot;Ada&quot;,&quot;age&quot;:&quot;36&quot;,&quot;city&quot;:&quot;Vienna&quot;&#125;</span><br></pre></td></tr></table></figure><h2 id="你应该关注哪些实验特性？"><a href="#你应该关注哪些实验特性？" class="headerlink" title="你应该关注哪些实验特性？"></a>你应该关注哪些实验特性？</h2><p>如果你像我一样是个普通的 Gopher，主要用 Go 编写程序而非开发 Go 本身，那么大多数可用的实验特性可能与你关系不大。</p><p>最有意思、最相关的几个可能是：</p><ul><li><a href="https://go.dev/blog/greenteagc"><code>GreenTeaGC</code></a> —— 如果你在使用 Go 1.26，你已经在默认使用它了。但如果你注意到任何性能或行为问题，你应该知道自己仍然可以禁用它（并且还应该提交一个 issue）。</li><li><a href="https://go.dev/doc/go1.25#dwarf5-support"><code>Dwarf5</code></a> —— 同样，如果你在使用 Go 1.25 或更高版本，你已经在默认使用它了。但如果你遇到任何问题，知道自己仍然可以禁用它是有用的。</li><li><a href="https://go.dev/doc/go1.25#json_v2"><code>JSONv2</code></a> —— 我不建议在它进入通用可用阶段之前切换，但如果你编写大量处理 JSON 的代码，值得尝试新的 <code>encoding/json/v2</code> 包，熟悉即将到来的内容，并在发现问题时给出反馈。</li><li><a href="https://go.dev/doc/go1.26#goroutineleak-profiles"><code>GoroutineLeakProfile</code></a> —— 这个特性可以立即派上用场，如果你怀疑有 goroutine 泄漏并需要调试，值得启用。</li><li><a href="https://go.dev/doc/go1.26#new-experimental-runtimesecret-package"><code>RuntimeSecret</code></a> —— 如果你编写加密代码或需要处理敏感数据，值得尝试并给出反馈。</li><li><a href="https://github.com/golang/go/issues/74299"><code>RuntimeFreegc</code></a> —— 如果你的应用程序严重依赖垃圾回收器，可能值得在启用此特性的情况下对你的代码进行基准测试，看看是否提高了性能，并在发现问题时给出反馈。</li></ul><p>最后，需要强调的是，实验特性不受 Go 兼容性承诺的覆盖。它们的 API、行为和性能特征都可能发生变化，因此通常最好避免过早采用，不要在实验特性最终确定之前依赖它们。</p><p>但实验特性通常是对 Go 中一些最重大变更的预览。如果你知道某个实验最终进入通用可用阶段并默认开启后可能会影响你或你的代码，那么尝试它、酌情运行基准测试、并在发现问题时给出反馈是个好主意。</p><p>如果你想追踪有哪些可用的实验特性及其状态，Go 发布说明最近在记录实验特性及其使用方式方面做得越来越好了。结合这篇博客文章和每次 Go 新版本发布时浏览发布说明，你应该能对整体情况有一个不错的了解。</p><hr><p><strong>原文来源</strong>：Alex Edwards, &quot;Go Experiments Explained&quot;, June 1, 2026, <a href="https://www.alexedwards.net/blog/go-experiments-explained">https://www.alexedwards.net/blog/go-experiments-explained</a></p>]]>
    </content>
    <id>https://colobu.com/2026/06/21/go-experimental-features-explained/</id>
    <link href="https://colobu.com/2026/06/21/go-experimental-features-explained/"/>
    <published>2026-06-21T02:05:27.000Z</published>
    <summary>
      <![CDATA[<p>Go 在发布新版本时经常会附带<strong>实验性特性（experimental features）</strong>。</p>
<p>这些实验性特性有不同的形式：有时是标准库中全新的包，有时是编译器或运行时的改动，偶尔也可能是对 Go 行为的破坏性变更。</p>
<p>大多数情况下，实验性特性的目的是在某个功能正式进入 <strong>通用可用（general availability）</strong> 阶段、成为 Go 的永久组成部分之前，从用户那里获取真实世界的反馈。如果该特性导致性能退化，或收到社区的负面反馈，它可以在最终定稿前被修改——甚至被完全放弃。</p>]]>
    </summary>
    <title>Go 实验特性详解</title>
    <updated>2026-06-22T00:21:16.000Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="Go" scheme="https://colobu.com/categories/Go/"/>
    <category term="Go" scheme="https://colobu.com/tags/Go/"/>
    <content>
      <![CDATA[<blockquote><p>在 Go 1.17 之前，Go 编译器总是生成可由任何 64 位 x86 处理器执行的 x86 二进制文件。<br>Go 1.18 为 AMD64 引入了 <a href="https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels">4 个架构级别</a> 。每个级别在编译器可以包含在生成的二进制文件中的 x86 指令集上有所不同：</p><ul><li>GOAMD64&#x3D;v1（默认值）：基准模式。仅生成所有 64 位 x86 处理器都能执行的指令。</li><li>GOAMD64&#x3D;v2：所有 v1 指令，加上 CMPXCHG16B、LAHF、SAHF、POPCNT、SSE3、SSE4.1、SSE4.2、SSSE3。</li><li>GOAMD64&#x3D;v3：所有 v2 指令，加上 AVX、AVX2、BMI1、BMI2、F16C、FMA、LZCNT、MOVBE、OSXSAVE。</li><li>GOAMD64&#x3D;v4：所有 v3 指令，加上 AVX512F、AVX512BW、AVX512CD、AVX512DQ、AVX512VL。</li></ul><p>例如，设置 GOAMD64&#x3D;v3 将允许 Go 编译器在生成的二进制文件中使用 AVX2 指令（这在某些情况下可能会提高性能）；但是这些二进制文件将无法在不支持 AVX2 的旧 x86 处理器上运行。<br>Go 工具链也可能生成更新的指令，但会通过动态检查来确保它们只在支持的处理器上执行。例如，如果设置了 GOAMD64&#x3D;v1，并且 CPUID 报告 POPCNT 指令可用，那么 math&#x2F;bits.OnesCount 仍然会使用该指令。否则，它会回退到通用实现。<br>Go 工具链目前不生成任何 AVX512 指令。<br>不支持 SSE3 的平台不支持种族检测器。</p></blockquote><p>64 位 Intel 和 AMD 处理器已经演进了几十年。当你为 64 位 Intel 或 AMD 处理器编译 Go 程序时，编译器默认面向的是一个将近 20 年前的指令集。生成的二进制文件几乎能在任何 x64 芯片上运行，但同时也放弃了自 2003 年以来添加的所有指令。</p><p>我们通常用<strong>微架构级别</strong>（microarchitecture levels）来描述这一分层。每个级别捆绑了一组可以假定存在的指令集扩展：</p><table><thead><tr><th>级别</th><th>新增内容（大致）</th></tr></thead><tbody><tr><td><strong>v1</strong></td><td>原始 AMD64 基线（SSE2）</td></tr><tr><td><strong>v2</strong></td><td><code>popcnt</code>、SSE4.2</td></tr><tr><td><strong>v3</strong></td><td>AVX2</td></tr><tr><td><strong>v4</strong></td><td>AVX-512（F&#x2F;BW&#x2F;DQ&#x2F;VL）</td></tr></tbody></table><span id="more"></span><p>在我看来，这个阶梯已经略显过时了。它大约在 2020 年定型，而硬件已经向前发展了。我们还需要加入最新的 AVX-512 子扩展（VBMI、VBMI2、VNNI、BF16、FP16、VPOPCNTDQ 等），这些在最新的服务器和消费级芯片上已经支持，但 <code>v4</code> 并未要求。虽然 <code>v1</code> 到 <code>v4</code> 是一种有用的通用语言，但今天一个现实的&quot;用尽该 CPU 提供的一切&quot;目标至少需要一个 <code>v5</code>，而且可以说整个方案应该被更细粒度的特性检测所取代。</p><p>无论如何，Go 工具链通过 <a href="https://go.dev/wiki/MinimumRequirements#amd64"><code>GOAMD64</code></a> 环境变量暴露了这个 <code>v1</code> 到 <code>v4</code> 的阶梯。设置 <code>GOAMD64=v3</code> 告诉编译器可以使用直到 AVX2（含）的所有指令。默认值是 <code>v1</code>，即最低公共分母。</p><p>这就引出了一个显而易见的问题：如果我拿一个真实的、对性能敏感的库，在每个级别上重新编译，实际能获得多大收益？我选择了 <a href="https://github.com/RoaringBitmap/roaring">Roaring Bitmaps</a>，它是一种用于数据库和搜索引擎的压缩位集数据结构。</p><p>Roaring Bitmap 存储一组 32 位整数。它将 32 位空间按高 16 位划分为每块 65,536 个值的 chunk，每个 chunk 存储在一个仅保存低 16 位的<strong>容器</strong>（container）中。容器有三种形式，库始终保留最小的那种：</p><ul><li><strong>数组容器（array container）</strong>：一个已排序的 16 位值列表，当 chunk 稀疏时使用（最多几千个元素）；</li><li><strong>位图容器（bitmap container）</strong>：一个扁平的 8 KB 位向量（65,536 位，每个可能值对应一位），当 chunk 密集时使用；</li><li><strong>run 容器（run container）</strong>：一个 <code>[start, length]</code> 区间列表，当集合中的位聚集成连续区间时使用。简单说就是把连续的一大段 1 压缩成&quot;从哪开始、有多长&quot;——比如&quot;第 3 到第 100 位全是 1&quot;只记成 <code>[3, 98]</code>，而不是逐位存 98 个 1。</li></ul><p>我拉取了该库的最新版本，然后用它自带的基准测试套件运行了四次，每次一个级别，每个级别采集八个样本。我在一台 Intel Xeon Gold 6548N（Emerald Rapids，支持全部四个级别，包括 AVX-512）上完成测试，使用 Go 1.26.2 和 Roaring v2.18.2。</p><p><strong>种群计数</strong>（population count，或称 popcount，也叫汉明重量）简单来说就是一个机器字中被置为 1 的位数。Roaring 大量依赖它：位图容器的基数——它持有多少个值——是其 1024 个 64 位字的种群计数之和。现代 x86 芯片有专门的 <code>popcnt</code> 指令可以在单次操作中完成这一计算，但该指令只在 <code>v2</code> 级别（SSE4.2，2008 年）才可用。没有它，编译器只能退回到多指令的位操作序列。</p><p>最清晰的单一结果是种群计数：计算位图容器中置位比特的数量。<code>v1</code> 基线无法使用 <code>popcnt</code> 指令，因此 Go 生成的是软件回退实现。一旦我们移到 <code>v2</code>，<code>popcnt</code> 变得可用，耗时几乎减半：</p><p><img src="https://lemire.me/blog/wp-content/uploads/2026/06/popcount_levels.svg"></p><p>这是 43% 的减少，而且是免费的：无需改动源码，只需一个编译器标志。不过请注意，<code>v3</code> 和 <code>v4</code> 没有进一步改善。单条 <code>popcnt</code> 指令已经是最优的了；就 Go 编译器而言，AVX2 和 AVX-512 在此没有可添加的东西。</p><p>种群计数是容易获得的胜利。库中其他部分呢？</p><p>另一个明显的胜利是从密集位图构建容器。<code>FromDense array</code> 基准测试接收一个原始的 8 KB 位向量，并为其构造最紧凑的容器：它对每个字做 popcount 以获知基数，然后扫描出所有置位比特的位置。这种逐字的 popcount-扫描 循环正是编译器在 256 位寄存器可用后可以自动向量化的模式，因此收益在 <code>v2</code> 之后继续增长：</p><p><img src="https://lemire.me/blog/wp-content/uploads/2026/06/fromdense_levels.svg"></p><p><code>v2</code> 通过使用标量 <code>popcnt</code>&#x2F;<code>tzcnt</code> 指令已减少了 21%，而 <code>v3</code>（AVX2）几乎将其翻倍，达到 38% 的减少。与种群计数一样，<code>v4</code> 没有任何增益。</p><p>集合操作也呈现出相同的模式。<code>IntersectionCardinality</code> 基准测试计算两个位图共有多少个值：对于位图容器，它将字逐对做 AND 操作，然后对结果做种群计数，而不用物化出交集。在这里 <code>v2</code> 基本没有作用（标量 <code>popcnt</code> 已经在内部循环中了），但 <code>v3</code> 让编译器将 AND-计数 循环扩展到 256 位寄存器，将耗时减少了 22%：</p><p><img src="https://lemire.me/blog/wp-content/uploads/2026/06/intersectcard_levels.svg"></p><p>要点总结：</p><ol><li>在现代硬件上，每个人都应该使用 <code>v2</code> 或更高版本。生成的二进制文件可以在任何数据中心和任何非古董笔记本电脑上运行。</li><li><code>v3</code> 级别值得研究。</li><li><code>v4</code> 级别在我的一些基准测试中本应有所帮助，但实际上没有。我怀疑是 Go 编译器在这方面还不够好。</li></ol><p>（显然：请运行你自己的基准测试。）</p><hr><p><strong>原文来源</strong>：Daniel Lemire, &quot;How much do amd64 microarchitecture levels help in Go?,&quot; in <em>Daniel Lemire&#39;s blog</em>, June 6, 2026, <a href="https://lemire.me/blog/2026/06/06/how-much-do-amd64-microarchitecture-levels-help-in-go/">https://lemire.me/blog/2026/06/06/how-much-do-amd64-microarchitecture-levels-help-in-go/</a></p><hr><h2 id="精选评论"><a href="#精选评论" class="headerlink" title="精选评论"></a>精选评论</h2><p><strong>Xarn</strong>（2026年6月8日）：</p><blockquote><p>关于 v4 没有任何作用，来自他们的文档：</p><blockquote><p>Go 工具链目前不生成任何 AVX512 指令。</p></blockquote></blockquote><p><strong>Marco</strong>（2026年6月9日）：</p><blockquote><p>我自己的使用 AVX512 指令的经验是，使用它们的逻辑（和数据结构）与常规使用的结构差异如此之大，编译器不太可能翻译并非专门为它们构建的代码。问题是编程语言何时会创建易于使用的语法来方便地使用这些扩展。</p></blockquote>]]>
    </content>
    <id>https://colobu.com/2026/06/21/amd64-microarchitecture-level-go-performance/</id>
    <link href="https://colobu.com/2026/06/21/amd64-microarchitecture-level-go-performance/"/>
    <published>2026-06-21T01:38:49.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>在 Go 1.17 之前，Go 编译器总是生成可由任何 64 位 x86 处理器执行的 x86 二进制文件。<br>Go 1.18 为 AMD64 引入了 <a href="https://en.wikipedia.org/wiki/X86-64#Microarchitecture_levels">4 个架构级别</a> 。每个级别在编译器可以包含在生成的二进制文件中的 x86 指令集上有所不同：</p>
<ul>
<li>GOAMD64&#x3D;v1（默认值）：基准模式。仅生成所有 64 位 x86 处理器都能执行的指令。</li>
<li>GOAMD64&#x3D;v2：所有 v1 指令，加上 CMPXCHG16B、LAHF、SAHF、POPCNT、SSE3、SSE4.1、SSE4.2、SSSE3。</li>
<li>GOAMD64&#x3D;v3：所有 v2 指令，加上 AVX、AVX2、BMI1、BMI2、F16C、FMA、LZCNT、MOVBE、OSXSAVE。</li>
<li>GOAMD64&#x3D;v4：所有 v3 指令，加上 AVX512F、AVX512BW、AVX512CD、AVX512DQ、AVX512VL。</li>
</ul>
<p>例如，设置 GOAMD64&#x3D;v3 将允许 Go 编译器在生成的二进制文件中使用 AVX2 指令（这在某些情况下可能会提高性能）；但是这些二进制文件将无法在不支持 AVX2 的旧 x86 处理器上运行。<br>Go 工具链也可能生成更新的指令，但会通过动态检查来确保它们只在支持的处理器上执行。例如，如果设置了 GOAMD64&#x3D;v1，并且 CPUID 报告 POPCNT 指令可用，那么 math&#x2F;bits.OnesCount 仍然会使用该指令。否则，它会回退到通用实现。<br>Go 工具链目前不生成任何 AVX512 指令。<br>不支持 SSE3 的平台不支持种族检测器。</p>
</blockquote>
<p>64 位 Intel 和 AMD 处理器已经演进了几十年。当你为 64 位 Intel 或 AMD 处理器编译 Go 程序时，编译器默认面向的是一个将近 20 年前的指令集。生成的二进制文件几乎能在任何 x64 芯片上运行，但同时也放弃了自 2003 年以来添加的所有指令。</p>
<p>我们通常用<strong>微架构级别</strong>（microarchitecture levels）来描述这一分层。每个级别捆绑了一组可以假定存在的指令集扩展：</p>
<table>
<thead>
<tr>
<th>级别</th>
<th>新增内容（大致）</th>
</tr>
</thead>
<tbody><tr>
<td><strong>v1</strong></td>
<td>原始 AMD64 基线（SSE2）</td>
</tr>
<tr>
<td><strong>v2</strong></td>
<td><code>popcnt</code>、SSE4.2</td>
</tr>
<tr>
<td><strong>v3</strong></td>
<td>AVX2</td>
</tr>
<tr>
<td><strong>v4</strong></td>
<td>AVX-512（F&#x2F;BW&#x2F;DQ&#x2F;VL）</td>
</tr>
</tbody></table>]]>
    </summary>
    <title>amd64 微架构级别对 Go 程序性能提升多少？</title>
    <updated>2026-06-22T00:21:15.650Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category scheme="https://colobu.com/tags/undefined/"/>
    <content>
      <![CDATA[<blockquote><p>I don&#39;t talk to an agent anymore, I talk to a loop or a routine.<br>——Boris Cherny</p></blockquote><img src="/2026/06/17/loop-engineering-batch-8-issues-kuiniu-tool/image-20260611034205752.png" class=""><p>先讲一个真实的 case。</p><p>6 月 10 日下午，我把一个新工具 <strong>kuiniu（夔牛）</strong> 的 PRD 丢给 Claude Code，让它生成 8 个 issue 卡到 GitHub 仓库。然后我敲了一句 <code>/loop-it</code>，然后离开了。</p><p>一个小时后打开仓库一看，<a href="https://github.com/baidu/nettools/issues?q=is:issue%20state:closed">8 个 issue 全 closed</a>，对应的 PR 全 merge 了。main 分支上多出了 client、server、codec、bitflip 检测、丢包统计、命令行入口、Makefile&#x2F;goreleaser 集成，还顺手抽出了一个 <code>util/rotate_writer.go</code>。</p><span id="more"></span><p>我没有 prompt 任何一步。我只<strong>触发</strong>了一个循环。</p><img src="/2026/06/17/loop-engineering-batch-8-issues-kuiniu-tool/image-20260611042700262.png" class=""><hr><h2 id="02-Loop-到底是什么：从-ReAct-到编排"><a href="#02-Loop-到底是什么：从-ReAct-到编排" class="headerlink" title="02 Loop 到底是什么：从 ReAct 到编排"></a>02 Loop 到底是什么：从 ReAct 到编排</h2><p>很多人吵这个词，是因为 &quot;loop&quot; 这个词其实裹着至少 5 种不同的东西。我在上一篇公众号文章里梳理过，这里再重复一遍背景。</p><p>Addy Osmani 把这个历史讲得很清楚：</p><table><thead><tr><th>阶段</th><th>时间</th><th>形态</th><th>谁在驱动</th></tr></thead><tbody><tr><td>ReAct 循环</td><td>2022</td><td>学术 while 循环，一个模型一个循环</td><td>人盯着</td></tr><tr><td>AutoGPT</td><td>2023</td><td>给目标让它自己 prompt</td><td>著名地空转</td></tr><tr><td>Ralph Loop</td><td>2025</td><td>bash 单行，每次重置上下文到锚定文件</td><td>终端开着</td></tr><tr><td>&#x2F;goal 产品化</td><td>2026 春</td><td>小模型验证停止条件</td><td>人启动一次</td></tr><tr><td>编排式 Loop</td><td>2026 现在</td><td>Loop 监督 Loop，调度+持久化</td><td>基础设施时间</td></tr></tbody></table><p>最早的 Ralph Loop 是 Geoffrey Huntley 在 2025 年 7 月发布的，朴素得过分：一个 bash one-liner 把同一个 prompt 反复管道给 agent。但它只花 297 美元就构建出了一个完整的编程语言。Ralph 真正的创新是纪律，每次迭代都把上下文重置回固定的锚定文件，而不是让对话无限增长。</p><p>到了 2026 年，Codex 和 Claude Code 都内置了 <code>/goal</code> 命令，这就是产品化的 Ralph 循环。跑到一个可验证的停止条件成立为止，由一个独立的小模型来判断&quot;做完了没&quot;。写代码的 agent 不批改自己的作业，这是 loop 第一次有了 split verifier。</p><p>Boris 和 Steinberger 真正在说的是再上一层的&quot;编排式 Loop&quot;。和 Ralph 比，它把 loop 当成工作的单位而不是某个任务，让 loop 之间互相监督、并发地跑，靠调度而不是人工启动来发起，最关键的一点是把状态显式存到 git 里，崩溃可以恢复。</p><p>一句话总结：Ralph 假设你的终端开着；2026 版本的 Loop 假设你的终端关着。</p><hr><h2 id="03-我自己的-loop-it：把这套东西塞进一个-Skill"><a href="#03-我自己的-loop-it：把这套东西塞进一个-Skill" class="headerlink" title="03 我自己的 loop-it：把这套东西塞进一个 Skill"></a>03 我自己的 loop-it：把这套东西塞进一个 Skill</h2><p>理论看完，问题来了：我怎么在自己的项目里跑一个 loop？</p><p>我做了一套叫 <strong>goal-workflow</strong> 的开源工作流，托在 GitHub：<a href="https://github.com/smallnest/goal-workflow">smallnest&#x2F;goal-workflow</a>。一行命令装：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx skills add smallnest/goal-workflow</span><br></pre></td></tr></table></figure><p>整个 pipeline 是这样的：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/prd  →  /prd-to-spec (可选)  →  /to-issues  →  /loop-it (→ /goal → /review-it → /note-it → /ship-it)×N</span><br></pre></td></tr></table></figure><p>前三步还得我亲自上：<code>/prd</code> 和我聊一遍，把脑子里的需求整成 PRD；<code>/prd-to-spec</code> 可选，把 PRD 变成技术设计；<code>/to-issues</code> 把 PRD&#x2F;SPEC 拆成一堆带 acceptance criteria 和 dependencies 的 issue 卡，自动 push 到 GitHub。</p><p>然后 <code>/loop-it</code> 接过去，进入 loop。每个 issue 走完整 4 步：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">/goal       ← 实现</span><br><span class="line">/review-it  ← 自我代码评审</span><br><span class="line">/note-it    ← 记录设计决策</span><br><span class="line">/ship-it    ← 提 commit、PR、merge、关 issue</span><br></pre></td></tr></table></figure><p>写这套东西时，我把 Addy Osmani 那张表和 Boris 的&quot;五条建议&quot;翻来覆去看了好几遍。最后落地的几个关键点都是冲着 loop engineering 那几个老坑去的。</p><p>第一件事是磁盘里的 state file。每个 loop 都需要一个仓库外的记忆。我用一个 <code>.loop-state.json</code>：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;version&quot;</span><span class="punctuation">:</span> <span class="number">1</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;repo&quot;</span><span class="punctuation">:</span> <span class="string">&quot;baidu/nettools&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;total_issues&quot;</span><span class="punctuation">:</span> <span class="number">8</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;issues&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;23&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;shipped&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;branch&quot;</span><span class="punctuation">:</span> <span class="string">&quot;feat/issue-23-config&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;attempts&quot;</span><span class="punctuation">:</span> <span class="number">1</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;24&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;shipped&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;branch&quot;</span><span class="punctuation">:</span> <span class="string">&quot;feat/issue-24-codec&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;attempts&quot;</span><span class="punctuation">:</span> <span class="number">1</span> <span class="punctuation">&#125;</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;25&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span> <span class="attr">&quot;status&quot;</span><span class="punctuation">:</span> <span class="string">&quot;in_progress&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;phase&quot;</span><span class="punctuation">:</span> <span class="string">&quot;review&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;attempts&quot;</span><span class="punctuation">:</span> <span class="number">1</span> <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>每次状态翻页就立刻写盘。崩溃了？重新跑 <code>/loop-it</code>，它读 state file，跳过已 shipped 的，从断点继续。这一招看起来傻得不行，但每个跑长链路 agent 的人都掉过同一个坑：没有磁盘记忆的 loop 一崩就归零。</p><img src="/2026/06/17/loop-engineering-batch-8-issues-kuiniu-tool/image-20260611042939259.png" class=""><p>第四件事是分级错误恢复。跑 loop 一定会撞墙，build 挂、测试红、merge conflict、rate limit、CI 红，没一个能躲。每种我都给了固定的恢复策略和最大重试次数：</p><table><thead><tr><th>错误类别</th><th>信号</th><th>恢复</th><th>上限</th></tr></thead><tbody><tr><td>build_failure</td><td>编译错误、type 错误</td><td>读错误、改代码、再 build</td><td>3</td></tr><tr><td>test_failure</td><td>断言失败</td><td>读输出、改实现、再测</td><td>3</td></tr><tr><td>lint_failure</td><td>lint 红</td><td><code>lint --fix</code>、再 lint</td><td>2</td></tr><tr><td>merge_conflict</td><td>CONFLICT 标记</td><td>rebase main、解、push</td><td>2</td></tr><tr><td>ci_failure</td><td><code>gh pr checks</code> 失败</td><td>读 CI 日志、本地修、push</td><td>2</td></tr><tr><td>rate_limit</td><td>secondary abuse</td><td>等 60s</td><td>3</td></tr><tr><td>unknown</td><td>其他</td><td>标记 failed、跳过</td><td>0</td></tr></tbody></table><p>撞死了不会无限重试，标记 <code>failed</code>、保留分支不删（让我事后看）、写检查点、跑下一个。</p><p>最后一件事是每一步都打 checkpoint：<code>pending → in_progress → goal_done → review_done → note_done → shipped</code>。每次状态翻页都写盘。中间断电下次接得上。这点是 ralph loop 没有的，ralph 假设你终端开着，loop-it 假设你笔记本盖着。</p><p>我实现的这个<code>loop-it</code> 相比较Boris说的工作流，还属于一个比较简单的，Boris说的Loop中的逻辑完全由Agent决定，而我实现的<code>loop-it</code>基本上是一个比较明确的工作流，按照github issues顺序去执行，虽然issues之间可能有依赖，但是在loop之前，<code>to-issues</code>已经把它们编排好了。而且<code>loop-it</code>是一个agent驱动去执行的，Boris的工作流可能是几百个Agent去执行，更宏大。 </p><p>轻量级有轻量级的好处，使用起来更轻巧，也可以及时的让人类进行切入，token花费也少，下面是我昨天使用这种方式的一个实战，这也是我在Loop Engineering方向的一个探索，没有固定答案，只有摸索和经验的总结。</p><hr><h2 id="04-实战：一小时批量实现-kuiniu-的-8-个-feature"><a href="#04-实战：一小时批量实现-kuiniu-的-8-个-feature" class="headerlink" title="04 实战：一小时批量实现 kuiniu 的 8 个 feature"></a>04 实战：一小时批量实现 kuiniu 的 8 个 feature</h2><p>正如 Matthew Berman 说的，&quot;nobody knows but him and boris.&quot;Loop Engineering 现在还没一个清晰的公认定义，但已经有不少人在解读和实践。我自己在 goal-workflow 里实现的 <code>loop-it</code> 算是其中一种轻量解读，加了点 harness engineering 的约束，走顺序迭代而不是 Claude Code workflow 那种宏大的多 agent 编排，省钱省心。</p><p>OK 现在回到开头那个 case。</p><p>百度内部多年沉淀的高性能网络监控工具集 <a href="https://github.com/baidu/nettools">nettools</a> 这个月在密集开源，bitflip、baize、lidar 都已经放出来了。下一个准备开源的是 <strong>kuiniu（夔牛）</strong>，专门做 GPU 网络的丢包和时延探测，每个 GPU 8 张卡 8 个 IP，通过 GPU 收发包来探测。</p><p>这是一个有清晰约束的工程问题，正好适合 loop。</p><img src="/2026/06/17/loop-engineering-batch-8-issues-kuiniu-tool/image-20260611043211480.png" class=""><h3 id="Step-1：写一份-PRD（人肉-30-分钟）"><a href="#Step-1：写一份-PRD（人肉-30-分钟）" class="headerlink" title="Step 1：写一份 PRD（人肉 30 分钟）"></a>Step 1：写一份 PRD（人肉 30 分钟）</h3><p>跑 <code>/prd kuiniu</code>，和 Claude 聊清楚几件事：</p><ul><li>同号卡探测（卡 i 探卡 i）</li><li>发包 GPU、收包 CPU 的不对称路径</li><li>payload 必须携带源目 IP&#x2F;Port</li><li>重点是丢包率和时延</li><li>复用 baize 已有的 codec&#x2F;bitflip 检测能力</li></ul><p>输出一份 <code>tasks/prd-kuiniu.md</code>。</p><h3 id="Step-2：拆-issue（人肉-5-分钟过审）"><a href="#Step-2：拆-issue（人肉-5-分钟过审）" class="headerlink" title="Step 2：拆 issue（人肉 5 分钟过审）"></a>Step 2：拆 issue（人肉 5 分钟过审）</h3><p>跑 <code>/to-issues</code>，自动拆出 8 个：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">#23  kuiniu: 创建配置格式与 GPU 拓扑校验</span><br><span class="line">#24  kuiniu: 实现 codec 扩展 — payload 携带源目 IP 和端口</span><br><span class="line">#25  kuiniu: 实现 Sender/Receiver 接口与 UDP 实现</span><br><span class="line">#26  kuiniu: 实现客户端同号卡探测逻辑</span><br><span class="line">#27  kuiniu: 实现服务端 GPU 收包 + CPU 回包</span><br><span class="line">#28  kuiniu: 实现 bitflip 检测</span><br><span class="line">#29  kuiniu: 实现丢包和时延统计输出</span><br><span class="line">#30  kuiniu: 命令行入口与构建集成</span><br></pre></td></tr></table></figure><p>每个卡片都自带 acceptance criteria、dependencies、type、priority。push 到 <a href="https://github.com/baidu/nettools/issues?q=is:issue%20state:closed">baidu&#x2F;nettools</a>。</p><h3 id="Step-3：-loop-it（人肉-3-秒）"><a href="#Step-3：-loop-it（人肉-3-秒）" class="headerlink" title="Step 3：/loop-it（人肉 3 秒）"></a>Step 3：<code>/loop-it</code>（人肉 3 秒）</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/loop-it</span><br></pre></td></tr></table></figure><p>剩下的全是 loop 的事。我去喝杯枸杞茶。</p><h3 id="一小时后检查进度"><a href="#一小时后检查进度" class="headerlink" title="一小时后检查进度"></a>一小时后检查进度</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</span><br><span class="line">📊 Loop Complete — Summary</span><br><span class="line">━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</span><br><span class="line">  ✅ Shipped:   8 issues  (#23, #24, #25, #26, #27, #28, #29, #30)</span><br><span class="line">  ⏭️  Skipped:   0 issues</span><br><span class="line">  🔒 Blocked:   0 issues</span><br><span class="line">  ❌ Failed:    0 issues</span><br><span class="line">  📋 Total:     8 issues</span><br><span class="line">━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━</span><br></pre></td></tr></table></figure><p>8 个 issue 全 closed，PR 全 merge 进 main。中间循环跑 <code>/goal → /review-it → /note-it → /ship-it</code> 共 32 次小循环，加上 review 阶段的修复迭代，实际触发的 agent turn 接近 60 次。<code>/review-it</code> 在 #25 和 #27 上分别挑出了一些边界 case 没处理，loop 自己改完又跑了一遍 review 才进 ship。</p><p>整个过程我没有 prompt 任何一步。我只在第二天打开 GitHub 看了眼 PR 列表，扫了下 review 笔记，确认每个 PR 的设计决策合理之后，那些代码就已经在主干上了。</p><p>跑完这一晚我才信一件事：loop 的难点不是 loop 本身，是放进去那个能说&quot;不&quot;的东西。没有真正检查的 loop，只是 agent 在反复自我同意。</p><p>最后冷静一句：不是所有任务都值得 loop。CI 分诊、依赖升级、lint-fix、强测试覆盖的 issue-to-PR（比如这次的 kuiniu）适合先上手；架构重写、认证支付、生产部署暂时不要碰。</p><hr><p><strong>项目链接</strong>：</p><ul><li>goal-workflow: <a href="https://github.com/smallnest/goal-workflow">github.com&#x2F;smallnest&#x2F;goal-workflow</a></li><li>kuiniu (夔牛) — loop-it 实战产物: <a href="https://github.com/baidu/nettools/issues?q=is:issue%20state:closed">baidu&#x2F;nettools</a></li><li>Addy Osmani 原文: <a href="https://addyosmani.com/blog/loop-engineering/">Loop Engineering</a></li><li>Matt Van Horn 长读: <a href="https://x.com/mvanhorn/article/2063865685558903149">WTF Is a Loop? Steinberger vs. Cherny</a></li></ul>]]>
    </content>
    <id>https://colobu.com/2026/06/17/loop-engineering-batch-8-issues-kuiniu-tool/</id>
    <link href="https://colobu.com/2026/06/17/loop-engineering-batch-8-issues-kuiniu-tool/"/>
    <published>2026-06-16T20:00:24.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>I don&#39;t talk to an agent anymore, I talk to a loop or a routine.<br>——Boris Cherny</p>
</blockquote>
<img src="/2026/06/17/loop-engineering-batch-8-issues-kuiniu-tool/image-20260611034205752.png" class="">

<p>先讲一个真实的 case。</p>
<p>6 月 10 日下午，我把一个新工具 <strong>kuiniu（夔牛）</strong> 的 PRD 丢给 Claude Code，让它生成 8 个 issue 卡到 GitHub 仓库。然后我敲了一句 <code>/loop-it</code>，然后离开了。</p>
<p>一个小时后打开仓库一看，<a href="https://github.com/baidu/nettools/issues?q=is:issue%20state:closed">8 个 issue 全 closed</a>，对应的 PR 全 merge 了。main 分支上多出了 client、server、codec、bitflip 检测、丢包统计、命令行入口、Makefile&#x2F;goreleaser 集成，还顺手抽出了一个 <code>util/rotate_writer.go</code>。</p>]]>
    </summary>
    <title>Loop Engineering 实践：一次批量实现 8 个 issue，完成夔牛工具的开发</title>
    <updated>2026-06-22T00:21:16.011Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="Go" scheme="https://colobu.com/categories/Go/"/>
    <category term="Go,Rust" scheme="https://colobu.com/tags/Go-Rust/"/>
    <content>
      <![CDATA[<p>一次几乎全自动的库开发实验：从一份 PRD 出发，15 个 issue 串成流水线，让 Agent 一路 <code>实现 → 审查 → 记录 → 发布</code>，最后我只在真机上验证。本文复盘整个过程，验证了Loop Engineering和实际的花费。</p><h2 id="0-缘起"><a href="#0-缘起" class="headerlink" title="0. 缘起"></a>0. 缘起</h2><p>我想要一个 Go 语言的 RDMA 库。</p><p>从去年我们做高性能网络的黑盒监控起，就开始尝试用 RDMA 做探测。但我们的技术栈是 Go，找了几个库，实现得不好也不稳定；换成 C 语言技术栈对团队同学来说成本太高；自己实现当时觉得挺有挑战，于是这件事就搁下了，最后还是退回到用普通 UDP 协议探测。</p><span id="more"></span><p>RDMA for Go 库市面上的选择不多：要么是某个公司内部、绑定很深的封装，要么是年久失修的 binding。而我要的东西很明确——地道的 Go API，封装 <code>libibverbs</code> + <code>librdmacm</code>（也就是 <a href="https://github.com/linux-rdma/rdma-core">rdma-core</a> 的用户态库），支持 RC&#x2F;UD 两种传输、Send&#x2F;Recv 和 RDMA Read&#x2F;Write，再配一套对标 <a href="https://github.com/linux-rdma/perftest">perftest</a> 的 <code>ib_send_bw/lat</code>、<code>ib_write_bw/lat</code>、<code>ib_read_bw/lat</code> 的 Go 版工具。</p><p>今年不同了，AI Coding 技术飞速发展，我也有信心去做移植这件事。尤其本周 Loop Engineering 这种工作方式大家讨论得很热烈，我也在自己的 <a href="https://goal.rpcx.io/index_cn.html">goal workflow</a> 里加了个 <code>loop-it</code> 技能，实现了一个轻量级的 Loop Engineering，正好拿来试试手。</p><p>还有，大家普遍担心 Loop Engineering 实践起来 token 花费太高，我也想实际看看到底花费几何。</p><p>移植RDMA到Go生态圈是个典型的「工作量不小、但每一步都不算难」的活儿。cgo 封装 verbs 是体力活：几十个 C 结构体、状态机、字节序、资源生命周期，错一个字段编译就挂。正是这种结构清晰、可分解、可验证的任务，最适合拿来做一次 Loop Engineering 实验。</p><p>所谓 Loop Engineering，核心就一句话：不要让 Agent 一口气写完一个大项目，而是把工作拆成带依赖的小单元，让它在一个可恢复、可观测的循环里逐个吃掉，每一步都有验证关卡。</p><img src="/2026/06/17/loop-engineering-rdma-port-to-go-239-yuan/image-20260612095840690.png" class=""><p>这是我的锅，实现的<code>loop-it</code>有问题。后来在执行过程中我发现了这个问题，修复了 <code>loop-it</code>。</p><hr><h2 id="4-补救：真正的审查发现了什么"><a href="#4-补救：真正的审查发现了什么" class="headerlink" title="4. 补救：真正的审查发现了什么"></a>4. 补救：真正的审查发现了什么</h2><p>最后我还是做了补救，让 CC「现在跑一次真正的审查」。这次 CC 调起了能用的 <code>/code-review</code>（high effort：多个独立角度并行找问题，再逐条对抗式验证），对已合并的全部代码做了一轮真正的 correctness 审查。</p><p>结果很扎心——8 个确认&#x2F;可信的问题，其中两个是致命的编译错误：</p><ol><li><p><strong><code>cq_linux.go</code>：<code>c.imm_data undefined</code></strong><br><code>struct ibv_wc</code> 里 <code>imm_data</code> 在一个匿名 union 里，cgo 根本无法用 <code>.imm_data</code> 访问；而且它是 <code>__be32</code>（网络字节序）。</p></li><li><p><strong><code>device_linux.go</code>：<code>ibv_query_port</code> 类型不匹配</strong><br>现代 rdma-core 把 <code>ibv_query_port</code> 做成 static inline，转发到一个收 <code>_compat_ibv_port_attr*</code> 的真实符号，cgo 直接调会类型不符。</p></li></ol><p>还有 6 个运行时&#x2F;逻辑问题，例如：</p><ol start="3"><li><strong>rdma_cm 路径的 <code>Endpoint.Peer</code> 永远没赋值</strong> → 所有走 <code>-R</code> 的 Write&#x2F;Read 都会立刻报 <code>errNoPeer</code>。</li><li><strong><code>write.go</code> 的 busy-wait 没有内存屏障</strong> → <code>for buf[last] != expect &#123;&#125;</code> 读的是 NIC 通过 RDMA 写入、Go 运行时看不见的内存，编译器可能把这次 load 提到循环外，造成死循环或读到陈旧值。</li><li><strong><code>imm_data</code> 发送侧字节序</strong> → 没 <code>htonl</code>，对端会读到字节翻转的立即数。</li><li><strong>rdma_cm 错误路径资源泄漏</strong>、<strong>errno 读取时机不对</strong>、<strong>setup 忽略了 <code>-c/-d/-i/-x</code> 参数</strong>……</li></ol><p>最刺眼的事实是：这两个编译错误意味着，整个 cgo 核心从来没在 Linux 上编译过。我全程在 macOS 上开发，走的是 stub 路径，<code>go build</code> 一路绿灯，但那只证明了「桩实现能编译」，完全没碰到真正的 verbs 代码。</p><hr><h2 id="5-修复阶段：一步步把真相逼出来"><a href="#5-修复阶段：一步步把真相逼出来" class="headerlink" title="5. 修复阶段：一步步把真相逼出来"></a>5. 修复阶段：一步步把真相逼出来</h2><p>接下来的过程，本质上是让真实环境替我做验证。这部分非常有意思，因为它展示了 Agent 的盲区如何被一台真机一点点照亮。</p><p>我还是做了一点点额外的工作，以下是我手工触发的。</p><p>第一步，质量清理（<code>/simplify</code>）。4 个角度并行审查：reuse &#x2F; simplification &#x2F; efficiency &#x2F; altitude。修了几处真问题：</p><ul><li><code>stats.go</code> 的延迟统计，原来每打印一行 summary 要 sort 3 次（<code>Min/Max/Percentile</code> 各自 sort 一遍），直方图路径甚至 5 次。改成 <code>Stats()</code> 一次排序算出 min&#x2F;avg&#x2F;max&#x2F;p99。</li><li>带宽测试的「保持 TxDepth 个请求在途」的循环，在 send&#x2F;write&#x2F;read 里三份几乎一样的拷贝 → 抽成一个 <code>runBWPipeline(cfg, cq, post)</code>，用闭包传 opcode。</li><li>4 个工具 main 里重复的 <code>-R</code>&#x2F;UD 拒绝逻辑 → 收敛成一个 <code>Config.RequireOneSidedTCP()</code>。</li><li>删掉过时的 <code>var _ = C.IBV_QPT_RC</code> cgo 占位 anchor。</li></ul><p>第二步，工程化。加了 <code>Makefile</code>（<code>vet/build/test/tools/cross/stub/integration/lint/fmt</code>，硬件相关的 <code>integration</code> 用 <code>GORDMA_HW=1</code> 闸住），加了 <code>make fmt</code> 和 <code>make lint</code>。</p><p><code>make lint</code> 一跑，golangci-lint 报了 20 个问题：16 个 errcheck（没检查的 <code>defer Close()</code> 返回值等）、2 个 unused、以及 2 个 staticcheck <code>SA5002</code>，正是 <code>write.go</code> 那个 busy-wait race。静态分析工具独立地撞上了 <code>/code-review</code> 早就指出的内存模型 bug。</p><p>这里有个小插曲值得一记：修这个 race 时我第一反应是用 <code>atomic.LoadUint8</code>——结果 Go 根本没有 8 位原子操作，编译直接挂。最后改成：定位包含该字节的 4 字节对齐 word，用 <code>atomic.LoadUint32</code> + <code>CompareAndSwapUint32</code> 只改其中一个字节（代价是要求 <code>Size &gt;= 4</code>）。Agent 也会想当然，差别只在于有没有一个会立刻打脸的编译器。</p><p>第三步，真机连环打脸。用户把代码弄到一台真正的 H20 GPU 服务器上（装了 RDMA 网卡），开始跑 <code>make</code>：</p><ul><li>第一次：<code>fatal error: rdma/rdma_cma.h: No such file or directory</code>，缺 <code>librdmacm-dev</code>。换成我的开发 docker，上面已经装好了相应依赖库。</li><li>第二次：<code>c.imm_data undefined</code> + <code>ibv_query_port</code> 类型不匹配，就是 <code>/code-review</code> 预言的那两个编译错误，这下在真机上真实地复现了。CC 按 libibverbs 的正确用法修：给 <code>imm_data</code> 写 C 辅助函数 <code>wc_imm_data()</code>（<code>ntohl</code> 转主机序）；给 <code>ibv_query_port</code> 包一层 C wrapper <code>gordma_query_port()</code> 让 inline 在 C 里展开；发送侧 <code>imm_data</code> 补 <code>htonl</code>。</li><li>第三次：<code>device_linux.go:67: possible misuse of unsafe.Pointer</code>，<code>go vet</code> 嫌弃把指针存进 <code>uintptr</code> 再做算术。改用 <code>unsafe.Add</code>，让指针运算全程保持 <code>unsafe.Pointer</code> 形式。</li></ul><img src="/2026/06/17/loop-engineering-rdma-port-to-go-239-yuan/image-20260612100035693.png" class=""><p>因为这些都没进 Loop，所以不适合放在循环里，而是在循环之外执行的。起始后续<code>/simplify</code> 也可以加在循环中。</p><hr><h2 id="6-完整的提交时间线"><a href="#6-完整的提交时间线" class="headerlink" title="6. 完整的提交时间线"></a>6. 完整的提交时间线</h2><p>如果把 16 个功能 PR 之后的修复也算上，整条提交线是这样的（节选）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">edd44e2 docs: add badges to README</span><br><span class="line">4fa8b9c fix: use unsafe.Add to walk device list (go vet unsafe.Pointer)   ← 真机第3次</span><br><span class="line">f0b0453 fix: cgo compile errors (imm_data, ibv_query_port) + imm byte order ← 真机第2次</span><br><span class="line">d253e36 Chore/simplify cleanups (#31)                                     ← 质量清理+工程化</span><br><span class="line">f2519af docs: README usage, godoc, and CI workflow (#15)                  ← loop-it 最后一个 issue</span><br><span class="line">...</span><br><span class="line">d15513b 项目骨架：go.mod、cgo 构建配置、非 Linux stub (#1)                  ← loop-it 第一个 issue</span><br><span class="line">56514ff Initial commit</span><br></pre></td></tr></table></figure><p><code>#1</code> 到 <code>#15</code> 是 loop-it 自动跑出来的，干净利落。<code>d253e36</code> 之后的几个 fix，则是<strong>人把 Agent 拽回现实</strong>的痕迹。</p><hr><h2 id="7-成本：那-239-块钱"><a href="#7-成本：那-239-块钱" class="headerlink" title="7. 成本：那 239 块钱"></a>7. 成本：那 239 块钱</h2><p>说说标题里的钱。</p><p>我一直盯着Claude Code中钱的消耗，最后算下来一共花了 239 元。239 这个数字是按整轮交互（一份 PRD、15 个 issue 的实现、一轮 high-effort code-review、一轮 4 角度 simplify，外加多轮真机修复往返）的量级估的。我不想在这篇复盘里编一张逐项发票出来——那本身就违背了全文的主旨。</p><p>但即便按这个量级看，几个判断是成立的：</p><ul><li>这是「一个有经验的工程师几天的活儿」：cgo 封装整套 verbs、写 6 个 perftest 工具、跨平台 stub、CI、文档。按工时折算，几百块的 API 成本对应的是数千块的人力成本。</li><li>真正贵的不是「写」，是「来回」。第一遍实现其实很快、很便宜，大约100块。烧钱的是后面那些本可以避免的往返——如果一开始就在有网卡的 Linux 上开发，那两个编译错误根本不会漏到合并之后，也不会有「真机连环打脸」的三轮修复。</li><li>跳过审查省下的钱，会在后面加倍还回来。loop-it 静默降级掉的 <code>/review-it</code>，最终是用一轮独立的 <code>/code-review</code> + 多轮真机调试补回来的。省一步，赔三步。。</li></ul><hr><h2 id="附：项目信息"><a href="#附：项目信息" class="headerlink" title="附：项目信息"></a>附：项目信息</h2><ul><li>仓库：<code>github.com/smallnest/gordma</code></li><li>规模：52 个 Go 文件 &#x2F; ~3981 行</li><li>能力：Device&#x2F;Context&#x2F;PD&#x2F;MR&#x2F;CQ&#x2F;QP&#x2F;AH 全套 verbs；RC + UD；TCP 握手 + rdma_cm 两种建连；6 个 perftest 风格工具</li><li>构建：Linux+cgo 真实实现，非 Linux&#x2F;<code>CGO_ENABLED=0</code> stub 实现</li><li>状态：cgo 编译错误已在真机修复；硬件数据通路的端到端验证已在 RoCE v2 真机上验证</li></ul>]]>
    </content>
    <id>https://colobu.com/2026/06/17/loop-engineering-rdma-port-to-go-239-yuan/</id>
    <link href="https://colobu.com/2026/06/17/loop-engineering-rdma-port-to-go-239-yuan/"/>
    <published>2026-06-16T20:00:24.000Z</published>
    <summary>
      <![CDATA[<p>一次几乎全自动的库开发实验：从一份 PRD 出发，15 个 issue 串成流水线，让 Agent 一路 <code>实现 → 审查 → 记录 → 发布</code>，最后我只在真机上验证。本文复盘整个过程，验证了Loop Engineering和实际的花费。</p>
<h2 id="0-缘起"><a href="#0-缘起" class="headerlink" title="0. 缘起"></a>0. 缘起</h2><p>我想要一个 Go 语言的 RDMA 库。</p>
<p>从去年我们做高性能网络的黑盒监控起，就开始尝试用 RDMA 做探测。但我们的技术栈是 Go，找了几个库，实现得不好也不稳定；换成 C 语言技术栈对团队同学来说成本太高；自己实现当时觉得挺有挑战，于是这件事就搁下了，最后还是退回到用普通 UDP 协议探测。</p>]]>
    </summary>
    <title>Loop Engineering 实践：我把 RDMA 开发库移植到 Go 语言，花费 239 块钱</title>
    <updated>2026-06-22T00:21:15.895Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="Rust" scheme="https://colobu.com/categories/Rust/"/>
    <category term="Rust,网络" scheme="https://colobu.com/tags/Rust-%E7%BD%91%E7%BB%9C/"/>
    <content>
      <![CDATA[<p>用 Go 写 RDMA，到底能有多简单？又能有多快？这篇带你从零跑到 400 Gb&#x2F;s。</p><h2 id="开篇：一个让人又爱又怕的技术"><a href="#开篇：一个让人又爱又怕的技术" class="headerlink" title="开篇：一个让人又爱又怕的技术"></a>开篇：一个让人又爱又怕的技术</h2><p>如果你做过高性能网络，一定听过 <strong>RDMA</strong> 这个词。它是 AI 训练集群里 GPU 之间狂飙数据的底层、是分布式存储压榨延迟的杀手锏、是金融交易系统微秒必争的武器。</p><img src="/2026/06/17/rdma-high-performance-networking-400gbps/image-20260616064940096.png" class=""><h3 id="两种传输-两种操作"><a href="#两种传输-两种操作" class="headerlink" title="两种传输 &amp; 两种操作"></a>两种传输 &amp; 两种操作</h3><ul><li><p><strong>RC</strong>(可靠连接，类比 TCP):有序可靠，支持双边和单边操作</p></li><li><p><strong>UD</strong>(不可靠数据报，类比 UDP):无连接，一对多</p></li><li><p><strong>双边操作</strong>(Send&#x2F;Recv):接收方要先挂好接收请求，双方 CPU 都参与</p></li><li><p><strong>单边操作</strong>(RDMA Write&#x2F;Read):发起方直接读写对端内存，<strong>对端 CPU 完全不参与</strong>——这是 RDMA 最&quot;魔法&quot;的地方</p></li></ul><span id="more"></span><h2 id="二-·-先用-perftest-摸清家底"><a href="#二-·-先用-perftest-摸清家底" class="headerlink" title="二 · 先用 perftest 摸清家底"></a>二 · 先用 perftest 摸清家底</h2><p>在写代码之前，先得知道你的网卡能跑多快。业界标准是 <strong>perftest</strong>(linux-rdma 出品的 C 版基准工具)。gordma 贴心地用 Go 复刻了一套对标工具，放在 <code>cmd/</code> 下:</p><table><thead><tr><th>工具</th><th>对标</th><th>测什么</th></tr></thead><tbody><tr><td><code>go_send_bw / lat</code></td><td><code>ib_send_bw/lat</code></td><td>双边 Send 的带宽 &#x2F; 延迟</td></tr><tr><td><code>go_write_bw / lat</code></td><td><code>ib_write_bw/lat</code></td><td>单边 Write</td></tr><tr><td><code>go_read_bw / lat</code></td><td><code>ib_read_bw/lat</code></td><td>单边 Read</td></tr><tr><td><code>go_rdmanet_bw / lat</code></td><td>—(高级)</td><td>测 gordma 高级 API</td></tr></tbody></table><p>命名规律很简单:<strong>操作(send&#x2F;write&#x2F;read) + 指标(bw 带宽 &#x2F; lat 延迟)</strong>。每个工具<strong>不带地址就是服务端，带对端地址就是客户端</strong>。</p><p>跑一把带宽测试:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">go build -o bin/ ./cmd/...</span><br><span class="line"></span><br><span class="line"><span class="comment"># 服务端(不带地址)</span></span><br><span class="line">./bin/go_send_bw -s 65536 -n 1000000 -d mlx5_1 -x 3</span><br><span class="line"></span><br><span class="line"><span class="comment"># 客户端(带服务端 IP)</span></span><br><span class="line">./bin/go_send_bw -s 65536 -n 1000000 -d mlx5_1 -x 3 33.0.226.25:18515</span><br></pre></td></tr></table></figure><p>输出:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">#bytes    #iterations   BW average[MB/s]   MsgRate[Mpps]</span><br><span class="line">65536     1000000       48996.54           0.747628</span><br></pre></td></tr></table></figure><p><strong>48996 MB&#x2F;s ≈ 392 Gb&#x2F;s</strong>(注意单位:<code>go_send_bw</code> 输出的是 <strong>MB&#x2F;s</strong>&#x3D;10⁶ 字节&#x2F;秒，×8÷1000 才是 Gb&#x2F;s)，这就是这张 400G 网卡的实力基准。记住这个数，后面要拿它当标尺。</p><blockquote><p>⚠️ <strong>单位是个大坑</strong>:三个常用工具输出单位<strong>各不相同</strong>,直接比原始数会差出 8 倍——C 版 <code>ib_send_bw</code> 是 <strong>MiB&#x2F;s</strong>(2²⁰ 字节)、Go 版 <code>go_send_bw</code> 是 <strong>MB&#x2F;s</strong>(10⁶ 字节)、gordma 的 <code>--raw</code> 是 <strong>MiB&#x2F;s</strong>(已对齐 C 版)。本文所有数字都统一换算到 <strong>Gb&#x2F;s</strong>(10⁹ bit) 再比较。</p></blockquote><blockquote><p>💡 小贴士:命令里的 IP 是服务端 <strong><code>-d</code> 指定的那张 RoCE 网卡</strong>绑定的 IP，<strong>不是CPU网络&#x2F;SSH 那个 IP</strong>。这是新手最容易连不上的坑。两端的 <code>-d</code>(设备)和 <code>-x</code>(GID 索引，RoCE v2 常用 3，  可以使用show_gids查看)要对齐同一张物理网络。</p></blockquote><hr><h2 id="三-·-底层-API-完全掌控-但要写够样板"><a href="#三-·-底层-API-完全掌控-但要写够样板" class="headerlink" title="三 · 底层 API:完全掌控,但要写够样板"></a>三 · 底层 API:完全掌控,但要写够样板</h2><p>gordma 的底层包 <code>gordma</code> 一比一映射了 RDMA 的对象模型。想要完全掌控每个工作请求、每个 QP 参数，用它。代价是:你得自己走完那七步。</p><img src="/2026/06/17/rdma-high-performance-networking-400gbps/image-20260616065212884.png" class=""><p>来看一个<strong>完整可跑</strong>的 RC 回显(用 rdma_cm 建连，省掉手写状态机):</p><p><strong>服务端:收一条,回显</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">server</span><span class="params">(addr <span class="type">string</span>)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">    ln, _ := gordma.Listen(addr)          <span class="comment">// rdma_cm 监听</span></span><br><span class="line">    <span class="keyword">defer</span> ln.Close()</span><br><span class="line">    cm, _ := ln.Accept()                  <span class="comment">// QP 已在 RTS 状态</span></span><br><span class="line">    <span class="keyword">defer</span> cm.Close()</span><br><span class="line">    qp, cq, pd := cm.QP(), cm.CQ(), cm.PD()</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 注册接收缓冲区——网卡只能 DMA 已注册内存</span></span><br><span class="line">    mr, _ := pd.RegMRBuffer(<span class="number">4096</span>, gordma.AccessLocalWrite)</span><br><span class="line">    <span class="keyword">defer</span> mr.Close()</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 收之前必须先挂 recv,否则对端发来会 RNR</span></span><br><span class="line">    sge := gordma.SGEFromMR(mr, <span class="number">0</span>, <span class="number">4096</span>)</span><br><span class="line">    qp.PostRecv(gordma.RecvWR&#123;WRID: <span class="number">1</span>, SGList: []gordma.SGE&#123;sge&#125;&#125;)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 轮询完成队列</span></span><br><span class="line">    wc := <span class="built_in">make</span>([]gordma.WorkCompletion, <span class="number">1</span>)</span><br><span class="line">    pollOne(cq, wc)</span><br><span class="line">    msg := mr.Bytes()[:wc[<span class="number">0</span>].ByteLen]</span><br><span class="line">    fmt.Printf(<span class="string">&quot;got %q\n&quot;</span>, msg)</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 原样发回</span></span><br><span class="line">    <span class="built_in">copy</span>(mr.Bytes(), msg)</span><br><span class="line">    qp.PostSend(gordma.SendWR&#123;</span><br><span class="line">        WRID: <span class="number">2</span>, Opcode: gordma.OpSend,</span><br><span class="line">        SGList:   []gordma.SGE&#123;gordma.SGEFromMR(mr, <span class="number">0</span>, <span class="built_in">len</span>(msg))&#125;,</span><br><span class="line">        Signaled: <span class="literal">true</span>,</span><br><span class="line">    &#125;)</span><br><span class="line">    pollOne(cq, wc)                       <span class="comment">// 等发送完成</span></span><br><span class="line">    <span class="keyword">return</span> <span class="literal">nil</span></span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="comment">// 忙轮询 CQ 直到取到一个完成</span></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">pollOne</span><span class="params">(cq *gordma.CQ, wc []gordma.WorkCompletion)</span></span> &#123;</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        <span class="keyword">if</span> n, err := cq.Poll(wc); err != <span class="literal">nil</span> || n &gt; <span class="number">0</span> &#123;</span><br><span class="line">            <span class="keyword">return</span></span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>每一行都对应一个 RDMA 概念:<strong>注册内存 → 先挂 recv → 轮询 CQ → post send</strong>。底层 API 的好处是<strong>没有任何隐藏行为</strong>,你能做单边 Write&#x2F;Read、能精调 QP 容量、能复刻 perftest——坏处是,样板真的多。</p><hr><h2 id="四-·-高级-API-像写-net-一样写-RDMA"><a href="#四-·-高级-API-像写-net-一样写-RDMA" class="headerlink" title="四 · 高级 API:像写 net 一样写 RDMA"></a>四 · 高级 API:像写 net 一样写 RDMA</h2><p>如果你只是想<strong>写业务</strong>,不想碰 MR、WR、CQ 这些——用 <code>rdmanet</code> 子包。它把上面那一大坨全收进了 <code>Dial / Listen / SendMsg / RecvMsg</code>。</p><img src="/2026/06/17/rdma-high-performance-networking-400gbps/image-20260616072533414.png" class=""><p>来看同样的事,高级怎么写。一个 <strong>RPC 服务</strong>:</p><p><strong>服务端</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">serve</span><span class="params">(addr <span class="type">string</span>, opts []rdmanet.Option)</span></span> <span class="type">error</span> &#123;</span><br><span class="line">    ln, err := rdmanet.Listen(addr, opts...)</span><br><span class="line">    <span class="keyword">if</span> err != <span class="literal">nil</span> &#123; <span class="keyword">return</span> err &#125;</span><br><span class="line">    <span class="keyword">defer</span> ln.Close()</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        conn, err := ln.Accept()</span><br><span class="line">        <span class="keyword">if</span> err != <span class="literal">nil</span> &#123; <span class="keyword">return</span> err &#125;</span><br><span class="line">        <span class="keyword">go</span> handle(conn)               <span class="comment">// 每个连接一个 goroutine</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">handle</span><span class="params">(conn *rdmanet.Conn)</span></span> &#123;</span><br><span class="line">    <span class="keyword">defer</span> conn.Close()</span><br><span class="line">    <span class="keyword">for</span> &#123;</span><br><span class="line">        req, err := conn.RecvMsg()    <span class="comment">// 收一条完整请求</span></span><br><span class="line">        <span class="keyword">if</span> err == io.EOF &#123; <span class="keyword">return</span> &#125;   <span class="comment">// 客户端关闭,正常结束</span></span><br><span class="line">        <span class="keyword">if</span> err != <span class="literal">nil</span> &#123; <span class="keyword">return</span> &#125;</span><br><span class="line">        conn.SendMsg(process(req))    <span class="comment">// 处理并回复</span></span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>客户端</strong></p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">conn, _ := rdmanet.Dial(<span class="string">&quot;33.0.226.25:18515&quot;</span>,</span><br><span class="line">    rdmanet.WithDevice(<span class="string">&quot;mlx5_1&quot;</span>), rdmanet.WithGIDIndex(<span class="number">3</span>))</span><br><span class="line"><span class="keyword">defer</span> conn.Close()</span><br><span class="line">conn.SendMsg([]<span class="type">byte</span>(<span class="string">&quot;hello&quot;</span>))</span><br><span class="line">reply, _ := conn.RecvMsg()           <span class="comment">// 阻塞等响应</span></span><br></pre></td></tr></table></figure><p><strong>没有 MR、没有 WR、没有 CQ 轮询、没有状态机。</strong> 是不是和标准库 <code>net</code> 一模一样?</p><p><code>rdmanet</code> 还提供了一整套实用能力:</p><ul><li><strong>消息语义</strong> <code>SendMsg</code>&#x2F;<code>RecvMsg</code>:保留边界,大消息自动分片重组</li><li><strong>字节流适配器</strong> <code>Read</code>&#x2F;<code>Write</code>:<code>Conn</code> 直接满足 <code>io.ReadWriteCloser</code>,能配 <code>io.Copy</code> 传文件</li><li><strong>批量 I&#x2F;O</strong> <code>SendBatch</code>&#x2F;<code>RecvBatch</code>:摊薄每次调用开销</li><li><strong>UD 数据报</strong> <code>PacketConn</code>:无连接、一对多</li><li><strong>地址注册表</strong> <code>Registry</code>:带外发现对端</li></ul><p>仓库里还附带了 <strong>17 个按功能拆分的示例</strong>(<code>examples/</code> 目录),从最小回显到全双工聊天、文件传输、一对多广播,一个功能一个目录,照着抄就行。</p><hr><h2 id="五-·-RawConn-既要-net-风格-又要榨干网卡"><a href="#五-·-RawConn-既要-net-风格-又要榨干网卡" class="headerlink" title="五 · RawConn:既要 net 风格,又要榨干网卡"></a>五 · RawConn:既要 net 风格,又要榨干网卡</h2><p>高级 <code>Conn</code> 好用,但有个问题:它为了&quot;保留消息边界 + 流控 + 易用&quot;付出了固定成本——封帧、信用流控、bounce 缓冲拷贝、后台 poller goroutine 的跨线程交接。这些叠加起来,让它在 64KB 大包上<strong>只能跑到约 28 Gb&#x2F;s</strong>,远没喂满 400G 网卡。</p><p>于是 gordma 给了第三个选择:<strong><code>RawConn</code></strong>。</p><img src="/2026/06/17/rdma-high-performance-networking-400gbps/image-20260616065325484.png" class=""><p>它的理念很直接:<strong>把所有花哨的东西全剥掉</strong>,直接暴露&quot;注册内存 + 投递 WR + 自己轮询 CQ&quot;,在同一个 goroutine 里 post + busy-poll,无封帧、无流控、无交接。这正是 perftest 打满线速的那套循环。</p><p>最省事的用法是内置的 <code>PipelineBatch</code>,保持 N 个请求 <strong>in-flight</strong>(同时在网卡里跑),每完成一个补一个:</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">rc, _ := rdmanet.DialRaw(addr,</span><br><span class="line">    rdmanet.WithDevice(<span class="string">&quot;mlx5_1&quot;</span>),</span><br><span class="line">    rdmanet.WithGIDIndex(<span class="number">3</span>),</span><br><span class="line">    rdmanet.WithQueueDepth(<span class="number">128</span>))</span><br><span class="line"><span class="keyword">defer</span> rc.Close()</span><br><span class="line"></span><br><span class="line">mr, _ := rc.RegisterMemory(size * txDepth)</span><br><span class="line"><span class="keyword">defer</span> mr.Close()</span><br><span class="line"></span><br><span class="line">rc.PipelineBatch(iters, txDepth, <span class="function"><span class="keyword">func</span><span class="params">(wrID <span class="type">uint64</span>)</span></span> gordma.SendWR &#123;</span><br><span class="line">    slot := <span class="type">int</span>(wrID) % txDepth</span><br><span class="line">    <span class="keyword">return</span> gordma.SendWR&#123;</span><br><span class="line">        WRID:     wrID,</span><br><span class="line">        Opcode:   gordma.OpSend,</span><br><span class="line">        SGList:   []gordma.SGE&#123;gordma.SGEFromMR(mr, slot*size, size)&#125;,</span><br><span class="line">        Signaled: <span class="literal">true</span>,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p><code>RawConn</code> 还支持:</p><ul><li><strong>单边 Write&#x2F;Read</strong>:走 TCP 握手交换了对端 rkey&#x2F;地址,可以直接做&quot;对端 CPU 不参与&quot;的远程读写</li><li><strong>批量提交 <code>PostSendBatch</code></strong>:用 WR 链表一次 <code>ibv_post_send</code> 提交多个请求,把 cgo 跨界开销从&quot;每个 WR 一次&quot;降到&quot;每批一次&quot;，小包消息率因此能提升约一个数量级</li><li><strong>逃生舱</strong> <code>QP()</code>&#x2F;<code>CQ()</code>&#x2F;<code>PD()</code>:需要时随时下沉到底层自己驱动</li></ul><p>代价当然有:<code>RawConn</code> <strong>不替你保留消息边界、不做流控(得自己控制 in-flight 数,否则 RNR)、不托管缓冲区</strong>。一句话：<strong>先用 <code>Conn</code>，确实要榨干网卡时再上 <code>RawConn</code></strong>。</p><blockquote><p>🔬 <strong>顺带破一个误解</strong>:很多人(包括我一开始)以为&quot;Go 经 cgo 调 RDMA 一定比 C 慢一截&quot;。我用 <code>GORDMA_PROBE=1</code> 把发送循环拆成&quot;提交 WR(post)&quot;和&quot;忙等完成(poll)&quot;两段实测,结论反直觉:<strong>一次 <code>ibv_post_send</code> 含 cgo 跨界约 300ns,只占总时间 ~15%,而且 <code>go_send_bw</code> 和 <code>RawConn</code> 两者完全相同</strong>。也就是说——cgo 提交开销真实存在但很小,<strong>不是</strong>性能差距的主因。后面第六节会看到,<code>go_send_bw</code> 状态好时能直接追平 C 版 <code>ib_send_bw</code>,根本没有&quot;Go 追不上 C&quot;的固有差距。</p></blockquote><hr><h2 id="六-·-真刀真枪-带宽压测对比"><a href="#六-·-真刀真枪-带宽压测对比" class="headerlink" title="六 · 真刀真枪:带宽压测对比"></a>六 · 真刀真枪:带宽压测对比</h2><p>理论讲完,上数据。同一对 400G RoCE v2 节点,64KB 大包,100 万条消息,实测:</p><img src="/2026/06/17/rdma-high-performance-networking-400gbps/image-20260616070038206.png" class=""><p><strong>结论很清楚:</strong></p><ul><li>从 <code>Conn</code> 到 <code>RawConn</code>,同一个库、同一张卡,吞吐 <strong>暴涨约 8 倍</strong>，证明那 28 Gb&#x2F;s 的天花板就是高级那套便利机制的固定成本。</li><li><code>RawConn</code> 用纯 Go(加薄薄一层 cgo)把吞吐推到了 <strong>230+ Gb&#x2F;s 的量级</strong>,已经和同一个库的底层 <code>go_send_bw</code> 在同一个数量级。</li></ul><h3 id="一个反直觉的发现-差距不在-cgo-而且不是固定的"><a href="#一个反直觉的发现-差距不在-cgo-而且不是固定的" class="headerlink" title="一个反直觉的发现:差距不在 cgo,而且不是固定的"></a>一个反直觉的发现:差距不在 cgo,而且不是固定的</h3><p>我原本想搞清&quot;<code>RawConn</code>(232) 为什么比 <code>go_send_bw</code>(392) 慢约 1.7 倍&quot;,于是做了一组<strong>同机、同口径、交替跑</strong>的实验(锁核 <code>taskset</code> + 性能调频,尽量压住抖动),用 <code>GORDMA_PROBE=1</code> 拆出 post&#x2F;poll。结果挖出三件事:</p><p><strong>① cgo 提交不是瓶颈。</strong> 两个工具的 post(提交 WR)都是 <strong>~300 ns&#x2F;WR、占比 ~15%,完全相同</strong>。所谓&quot;每个 WR 一次 cgo 跨界拖慢了 Go&quot;,在这个负载上<strong>站不住</strong>——提交很便宜,而且两边一样便宜。</p><p><strong>② Go 能追平 C。</strong> 锁核后 <code>go_send_bw</code> 实测峰值 <strong>0.748 Mpps,和 C 版 <code>ib_send_bw</code> 完全一致</strong>。早先看到的&quot;go_send_bw 只有 ~314 Gb&#x2F;s&quot;是机器状态差时的数,不是 cgo 的锅。</p><p><strong>③ 差距是&quot;可变&quot;的,不是固定缺陷。</strong> 交替跑 3 轮,<code>go_send_bw</code> 在 <strong>0.414 &#x2F; 0.748 &#x2F; 0.414 Mpps</strong> 之间<strong>离散双峰跳变</strong>,而 <code>RawConn</code> 稳定在 <del>0.42。也就是说:<code>go_send_bw</code> 状态差的那几轮,<strong>和 RawConn 几乎持平</strong>;两者差距在 **1.05×</del>1.76× 之间晃**,取决于那一轮 <code>go_send_bw</code> 能不能抢到干净的网卡&#x2F;CPU 窗口。</p><p>差距的真正位置在 <strong>poll(忙等完成到达)</strong>:<code>go_send_bw</code> 的 poll 在 0.75~1.33 µs&#x2F;WR 间大幅波动(状态好就打满线速),<code>RawConn</code> 则被稳定压在 ~1.40 µs。考虑到这是一台<strong>共享 GPU 机、400G 链路被其他租户竞争</strong>,最合理的解释是<strong>环境竞争</strong>,而非 RawConn 有独立的代码缺陷——两个工具走的是同一套 QP 建立和 CQ 轮询路径,逐行核对没有能让 RawConn 单独变慢的差异。</p><blockquote><p>🧭 <strong>给读者的实用结论</strong>:① 不要迷信&quot;Go+cgo 必慢于 C&quot;,在大包带宽场景两者能打平;② cgo 的固定开销真实但小,真正要省它得靠<strong>批量提交 + 忙轮询</strong>(见下文小包测试);③ 想认真比性能,务必<strong>锁核、独占机器、多次取中位数</strong>,共享机上的单次数字会骗你。</p></blockquote><h3 id="小包更能看出批量提交的威力"><a href="#小包更能看出批量提交的威力" class="headerlink" title="小包更能看出批量提交的威力"></a>小包更能看出批量提交的威力</h3><p>64KB 大包很容易撞带宽上限,看不出 CPU 侧的优化。换成 1KB 小包(消息率受限场景):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">./bin/go_rdmanet_bw --raw -s 1024 -n 5000000 -d mlx5_1 -x 3 -b 128 33.0.226.25:18515</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">raw-batch Send(txDepth=128): 5000000 x 1024 bytes in 0.85s: 47.92 Gb/s, 5.850 Mpps</span><br></pre></td></tr></table></figure><p><strong>5.85 Mpps</strong>——批量提交(<code>PostSendBatch</code>)在小包上把消息率拉高了一个数量级。这正是榨干高频小消息场景的关键。</p><hr><h2 id="尾声-三个档位-按需取用"><a href="#尾声-三个档位-按需取用" class="headerlink" title="尾声:三个档位,按需取用"></a>尾声:三个档位,按需取用</h2><p>gordma 最打动我的,是它没有逼你在&quot;易用&quot;和&quot;性能&quot;之间二选一,而是给了一条平滑的升级路径:</p><table><thead><tr><th>你的需求</th><th>用哪个</th><th>心智负担</th></tr></thead><tbody><tr><td>写业务,要 net 风格</td><td><code>rdmanet.Conn</code></td><td>像写 socket,几行搞定</td></tr><tr><td>既要简单又要极限吞吐</td><td><code>rdmanet.RawConn</code></td><td>自己管内存,几十行</td></tr><tr><td>完全掌控每个细节</td><td>底层 <code>gordma</code> 包</td><td>复刻 perftest 的程度</td></tr></tbody></table><p>而且全部代码<strong>在任何平台都能编译</strong>(macOS&#x2F;Windows 走 stub 桩实现,RDMA 调用优雅返回 <code>ErrNotSupported</code>),只有真正运行时才需要 Linux + RDMA 硬件。这意味着你可以在 MacBook 上写代码、跑单元测试,真要压测时再丢到带卡的机器上，开发体验和门槛都友好得多。</p><p>如果你正在被 RDMA 编程劝退,或者想给你的 Go 服务接上高性能网络,不妨试试 gordma:</p><blockquote><p>🔗 <strong>github.com&#x2F;smallnest&#x2F;gordma</strong></p></blockquote><p>从 <code>go run ./examples/echo-msg</code> 跑通第一个 RDMA 程序开始,你会发现——<strong>原来 RDMA 也可以这么&quot;傻瓜&quot;。</strong></p><hr><p><em>本文所有性能数据均为同一对 400G RoCE v2 节点上的实测结果,会随硬件与配置不同而变化。完整教程、API 文档、17 个示例和 8 个压测工具均在仓库中。</em></p>]]>
    </content>
    <id>https://colobu.com/2026/06/17/rdma-high-performance-networking-400gbps/</id>
    <link href="https://colobu.com/2026/06/17/rdma-high-performance-networking-400gbps/"/>
    <published>2026-06-16T20:00:24.000Z</published>
    <summary>
      <![CDATA[<p>用 Go 写 RDMA，到底能有多简单？又能有多快？这篇带你从零跑到 400 Gb&#x2F;s。</p>
<h2 id="开篇：一个让人又爱又怕的技术"><a href="#开篇：一个让人又爱又怕的技术" class="headerlink" title="开篇：一个让人又爱又怕的技术"></a>开篇：一个让人又爱又怕的技术</h2><p>如果你做过高性能网络，一定听过 <strong>RDMA</strong> 这个词。它是 AI 训练集群里 GPU 之间狂飙数据的底层、是分布式存储压榨延迟的杀手锏、是金融交易系统微秒必争的武器。</p>
<img src="/2026/06/17/rdma-high-performance-networking-400gbps/image-20260616064940096.png" class="">

<h3 id="两种传输-两种操作"><a href="#两种传输-两种操作" class="headerlink" title="两种传输 &amp; 两种操作"></a>两种传输 &amp; 两种操作</h3><ul>
<li><p><strong>RC</strong>(可靠连接，类比 TCP):有序可靠，支持双边和单边操作</p>
</li>
<li><p><strong>UD</strong>(不可靠数据报，类比 UDP):无连接，一对多</p>
</li>
<li><p><strong>双边操作</strong>(Send&#x2F;Recv):接收方要先挂好接收请求，双方 CPU 都参与</p>
</li>
<li><p><strong>单边操作</strong>(RDMA Write&#x2F;Read):发起方直接读写对端内存，<strong>对端 CPU 完全不参与</strong>——这是 RDMA 最&quot;魔法&quot;的地方</p>
</li>
</ul>]]>
    </summary>
    <title>傻瓜式RDMA高性能网络开发：从零跑到 400 Gb每秒</title>
    <updated>2026-06-22T00:21:15.700Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="网络" scheme="https://colobu.com/categories/%E7%BD%91%E7%BB%9C/"/>
    <category term="网络" scheme="https://colobu.com/tags/%E7%BD%91%E7%BB%9C/"/>
    <content>
      <![CDATA[<p>目前顶尖的云服务商都包含百万台服务器、数十甚至上百个机房、上万台网络设备、百万级网络链路。单单一个GPU集群，就有上万卡的级别。对这些网络和服务器的监控，一个分钟级别的故障，可能就是上百万资产的损失。</p><p>这不是一个ping能解决的问题。</p><p>今天，我们将百度物理网络黑盒监控方向的工具集 nettools 开源了（<a href="https://github.com/baidu/nettools%EF%BC%89%EF%BC%8C%E7%AC%AC%E4%B8%80%E6%89%B9%E6%94%BE%E5%87%BA%E7%9A%84%E6%98%AF">https://github.com/baidu/nettools），第一批放出的是</a> bitflip 和 bitflip6，用于检测网络丢包和比特翻转，在百度内部跑了很长时间了。</p><span id="more"></span><p>后续还有更多工具和SDK正在整理中，包括骨干网fullmesh监控、网关设备监控、连接客户内部机房设备的监控、定位工具等等，还有对巨量监控数据的处理。百度物理网络黑盒监控团队积累了一大批经验、产品和工具，后续逐步整理开源出来，欢迎关注。</p><h2 id="为什么不是一个ping？"><a href="#为什么不是一个ping？" class="headerlink" title="为什么不是一个ping？"></a>为什么不是一个ping？</h2><p>在大规模物理网络中，ping几乎没有用。</p><p>下面是一个集群简化的示意图。实际上层级比这更多，同一层级的网络设备数量远远大于图中画的，一个交换机有几十个端口而不是图中的几根连线，一个ToR交换机可能连接几十台服务器……两台服务器之间可走的链路有很多种可能。</p><img src="/2026/06/11/baidu-internal-tool-opensourced-network-monitoring/image-20260602030620414.png" class=""><p>bitflip用4种salt填充模式来覆盖各种跳变场景：</p><table><thead><tr><th>模式</th><th>值</th><th>作用</th></tr></thead><tbody><tr><td>全1</td><td><code>0xFF</code></td><td>检测 1→0 跳变</td></tr><tr><td>全0</td><td><code>0x00</code></td><td>检测 0→1 跳变</td></tr><tr><td>固定</td><td><code>0x5A</code></td><td>检测混合模式跳变</td></tr><tr><td>互补交替</td><td><code>0xAAAA</code>&#x2F;<code>0x5555</code></td><td>专门检测checksum盲区的互补跳变</td></tr></tbody></table><p>每个包按序列号选择一种模式，服务端用相同模式验证，精确到哪个字节、哪个bit翻转了。</p><p>这个场景非常讨厌。可能一年就遇到一次，遇到了就特别麻烦，影响还大，错误数据被当成正常数据保存了。客户发现还算容易，因为除了能绕过checksum的那部分，大部分坏包会被服务器的checksum校验drop掉，通过采集监控服务器的checksum指标，理论上容易发现（实际还有一些坏包干扰）。</p><p>但定位起来就头疼了。客户报过来说好几个机房有改包现象，到底是哪个机房哪一层设备的哪个板卡？</p><p>这时候用bitflip工具，如果能复现，就可以通过找改包五元组的共同路径，定位到故障的设备和端口。这要靠单向监控，双向的话就像前面说的，有可能误导。</p><h2 id="工程细节：raw-socket-BPF"><a href="#工程细节：raw-socket-BPF" class="headerlink" title="工程细节：raw socket + BPF"></a>工程细节：raw socket + BPF</h2><p>在百度的规模下，每秒要发送数千甚至上万个探测包。普通的UDP socket不够用：</p><ul><li>需要自由构造IP头（设置TOS&#x2F;DSCP等网络参数）</li><li>需要用极少的socket覆盖上百个端口对</li><li>需要精确控制发包速率</li></ul><p>bitflip客户端用raw socket直接构造IP+UDP包，通过BPF过滤收包，只接收目标端口范围内、特定TOS值的回包。读侧按端口范围切分为最多8个goroutine，每个绑定独立的BPF过滤器。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">portRangeBPF</span><span class="params">(minPort, maxPort, tos <span class="type">int</span>)</span></span> []bpf.Instruction &#123;</span><br><span class="line">    <span class="keyword">return</span> []bpf.Instruction&#123;</span><br><span class="line">        bpf.LoadIndirect&#123;Off: <span class="number">9</span>, Size: <span class="number">1</span>&#125;,</span><br><span class="line">        bpf.JumpIf&#123;Cond: bpf.JumpEqual, Val: <span class="type">uint32</span>(<span class="number">17</span>), SkipFalse: <span class="number">4</span>&#125;,</span><br><span class="line">        bpf.LoadIndirect&#123;Off: <span class="number">1</span>, Size: <span class="number">1</span>&#125;,</span><br><span class="line">        bpf.JumpIf&#123;Cond: bpf.JumpEqual, Val: <span class="type">uint32</span>(tos), SkipFalse: <span class="number">4</span>&#125;,</span><br><span class="line">        bpf.LoadAbsolute&#123;Off: <span class="number">22</span>, Size: <span class="number">2</span>&#125;,</span><br><span class="line">        bpf.JumpIf&#123;Cond: bpf.JumpGreaterOrEqual, Val: <span class="type">uint32</span>(minPort), SkipFalse: <span class="number">2</span>&#125;,</span><br><span class="line">        bpf.JumpIf&#123;Cond: bpf.JumpLessOrEqual, Val: <span class="type">uint32</span>(maxPort), SkipFalse: <span class="number">1</span>&#125;,</span><br><span class="line">        bpf.RetConstant&#123;Val: <span class="number">0xffff</span>&#125;,</span><br><span class="line">        bpf.RetConstant&#123;Val: <span class="number">0x0</span>&#125;,</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="双向对比判断故障方向"><a href="#双向对比判断故障方向" class="headerlink" title="双向对比判断故障方向"></a>双向对比判断故障方向</h2><p>bitflip同时支持客户端和服务端两侧统计。对比两侧丢包：</p><ul><li>仅服务端有丢包：故障在正向路径（Client → Server）</li><li>仅客户端有丢包，服务端正常：故障在回程路径（Server → Client）</li><li>两端都有丢包：需要进一步分析</li></ul><p>结合traceroute获取的链路拓扑，分析丢包五元组的公共端口&#x2F;设备，就能定位到具体哪台设备、哪个板卡、哪个端口。</p><h2 id="快速体验"><a href="#快速体验" class="headerlink" title="快速体验"></a>快速体验</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 克隆并编译</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/baidu-sys/nettools.git</span><br><span class="line"><span class="built_in">cd</span> nettools &amp;&amp; make build</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在远端启动服务端（自动检测IP，自动注册客户端）</span></span><br><span class="line">./bitflip</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在本地启动客户端</span></span><br><span class="line"><span class="built_in">sudo</span> ./bitflip -r client -s &lt;server_ip&gt;</span><br><span class="line"></span><br><span class="line"><span class="comment"># 以每秒10000包的速率探测60秒</span></span><br><span class="line"><span class="built_in">sudo</span> ./bitflip -r client -s &lt;server_ip&gt; --rate 10000 --duration 60s</span><br><span class="line"></span><br><span class="line"><span class="comment"># 开启详细模式，丢包时输出具体五元组</span></span><br><span class="line"><span class="built_in">sudo</span> ./bitflip -r client -s &lt;server_ip&gt; --verbose</span><br></pre></td></tr></table></figure><p>IPv6环境使用 <code>bitflip6</code>，用法一致。</p><p>使用手册查看：<a href="https://nettools.rpcx.io/bitflip.html">bitflip使用指南</a></p><h2 id="后续规划"><a href="#后续规划" class="headerlink" title="后续规划"></a>后续规划</h2><p>nettools 目前开源的是 bitflip&#x2F;bitflip6。后续还有更多工具和SDK在整理中：</p><ul><li>更多探测场景的工具</li><li>通用的网络探测SDK</li><li>定位分析相关的工具</li></ul><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><p>在百度这种体量的物理网络中做监控和定位，不是写几个socket程序就能搞定的。为什么用UDP不用ICMP和TCP？为什么有时候又采用TCP syn探测？为什么需要单向探测？为什么协议头要带上一个窗口的发送信息？为什么salt要用4种模式？每个选择背后都有对应的故障场景和教训。</p><p>这些东西以前都在内部，现在开源出来了。</p><p>项目地址：<a href="https://github.com/baidu/nettools">https://github.com/baidu/nettools</a></p><p>欢迎Star、试用、提Issue和PR。</p>]]>
    </content>
    <id>https://colobu.com/2026/06/11/baidu-internal-tool-opensourced-network-monitoring/</id>
    <link href="https://colobu.com/2026/06/11/baidu-internal-tool-opensourced-network-monitoring/"/>
    <published>2026-06-11T00:10:28.000Z</published>
    <summary>
      <![CDATA[<p>目前顶尖的云服务商都包含百万台服务器、数十甚至上百个机房、上万台网络设备、百万级网络链路。单单一个GPU集群，就有上万卡的级别。对这些网络和服务器的监控，一个分钟级别的故障，可能就是上百万资产的损失。</p>
<p>这不是一个ping能解决的问题。</p>
<p>今天，我们将百度物理网络黑盒监控方向的工具集 nettools 开源了（<a href="https://github.com/baidu/nettools%EF%BC%89%EF%BC%8C%E7%AC%AC%E4%B8%80%E6%89%B9%E6%94%BE%E5%87%BA%E7%9A%84%E6%98%AF">https://github.com/baidu/nettools），第一批放出的是</a> bitflip 和 bitflip6，用于检测网络丢包和比特翻转，在百度内部跑了很长时间了。</p>]]>
    </summary>
    <title>大厂的内部工具居然开源了! 一窥百度物理网络秒级监控定位的秘密</title>
    <updated>2026-06-22T00:21:15.806Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="网络" scheme="https://colobu.com/categories/%E7%BD%91%E7%BB%9C/"/>
    <category term="网络" scheme="https://colobu.com/tags/%E7%BD%91%E7%BB%9C/"/>
    <content>
      <![CDATA[<p>上一个工具 bitflip&#x2F;baize 解决的是丢包和改包持续检测，在百度baize常常用在点到点之间的常态检测中，比如机房内集群间的监控，专线的检测, 新网络方案测试和灰度观察、核心网络设备的切回前检测等场景。</p><p>今天介绍的lidar工具，区别于传统的pingmesh探测方案，是我来百度后创造的第一个特殊的底层网络方案，我将其称之为lidar(激光扫描)方案，是一个很形象的比喻，我会话专门一节详细介绍它的优缺点，在这之前，我们介绍传统的赫赫有名的机房大规模的网络监控方案 pingmesh。</p><p><a href="https://nettools.rpcx.io/">https://nettools.rpcx.io</a> </p><h2 id="pingmesh-探测以及为什么我们不用它？"><a href="#pingmesh-探测以及为什么我们不用它？" class="headerlink" title="pingmesh 探测以及为什么我们不用它？"></a>pingmesh 探测以及为什么我们不用它？</h2><p>PingMesh（&quot;Pingmesh: A Large-Scale System for Data Center Network Latency Measurement and Analysis&quot;）是微软在SIGCOMM 2015上发表的论文。作者团队来自微软研究院和Azure网络部门。</p><span id="more"></span><p>一句话概括：<strong>在所有服务器之间互相ping，测量每一对服务器之间的延迟和丢包，然后做异常检测和根因定位。</strong></p><p>听起来很笨。几千台服务器，两两之间全部ping一遍，消息复杂度 O(n²)，谁敢这么干？</p><p>微软敢，而且早在2015年之前就已经在全量数据中心里跑了。当前他们也做了一些优化，实际并不是所有的服务器之间fullmesh探测，只是保证同一层级之间的设备之间fullmesh，比如同一层级的IB之间的设备都互有探测。</p><p>论文作者大部分是微软研究院的。其中最值得关注的是几位：</p><ul><li><strong>Chuanxiong Guo（郭传雄）</strong>：微软研究院资深研究员，PingMesh是他主导的项目之一。后来曾任字节跳动人工智能实验室（AI Lab）总监，2022年因对数据中心网络设计的贡献当选IEEE Fellow，在数据中心网络领域有较高知名度。2023离开字节创业。</li><li><strong>Lihua Yuan</strong>：微软Azure网络团队核心成员，今年四月去了OpenAI。他和团队把PingMesh从论文推进到了Azure全量部署。</li><li>其他几位作者现在散落各大厂——Google、Meta等等。PingMesh的影响力从作者们的去向就能看出来，论文里的思想被带着在各个云厂商落地。</li></ul><p>你可以从微软网站上下载这篇经典论文，它提供了一个很好的设计和分析网络故障的方案，它的探测分析图也深深影响我设计lidar的分析方法。</p><p>这个pingmesh设计也没众多的云厂商使用并演化，比如腾讯等，几乎成了机房内网络监控的标配。</p><p>百度也在2016年开发了类似的监控系统 netradar, 使用常态的TCP Client&#x2F;Server进行探测， 支撑了百度网络的黑盒监控多年，不过在我来百度的时候相关的开发人员基本都走了，系统也面临的众多的问题：</p><ul><li>常态机房间和机房内都超过了阈值，大盘是常态飘红</li><li>准确率不足，要么是误报、要么是漏报</li><li>时效性太慢，基本2，3分钟才可能有告警</li><li>TCP的重试机制会导致偶发丢包发现不了</li><li>误告严重，经常服务器的上下线就导致了误报。尤其6.18、双十一这些节日涉及到大量的服务器的上下线</li></ul><p>还有一个重要的问题，一旦Agent有个bug修复或者想扩展一个功能，全网所有的服务器要升级一遍的话，至少需要一个季度才能完成，甚至半年，因为需要小范围的灰度才能逐步的扩展，还得涉及到变更窗口的审批，另外RD和运维也很痛苦，因为这些变更都</p><h2 id="lidar-探测"><a href="#lidar-探测" class="headerlink" title="lidar 探测"></a>lidar 探测</h2><p>2020&#x2F;2021年，百度迫切需要一个高时效高准确率的黑盒探测方案，去面对日益增长的云客户的对故障发现和处理的业务需求。</p><p>我恰巧刚刚来到百度，我还是第一次接触到众多的网络概念：机房、交换机、路由器、CLOS架构、骨干网、ADC、DC、IB、IC、TOR、ECMP等，对我来说一切都很新奇。面对当前探测的需求，我初始的想法是在前人的基础上(netradar)优化，先来快速解决当前棘手的问题。也做了一些尝试、比如netradar探测频率的提升、聚合程序整体引流和替换等等，在两三个月之后，虽然比最初的情况好些了，但是还是没有从根因上解决问题。</p><p>同时，我们团队也在研发骨干网的监控，在百度称之为B1的网络。当时我设计一个我称之为『sonar』的探测方案，采用UDP + Rawsocket的方式构造不同的五元组进行探测，为了解决rawsocket性能低下的问题(它会拦截和复制服务器全部的UDP包)，我也是各大论坛寻师问友，后来在slack有人告诉我可以使用bpf，基本上单个服务器rawsocket可以达到6万pps不丢包的性能，当然如果不使用rawsocket而是采用普通的UDP服务器，可以稳定实现80万pps的探测频率，但是又没办法实现狗构造成千上万的PPS。 这都是我们在前期的一些经验的总结，也很有意思，这些都是工程化的过程中才可能获得的宝贵经验，最终我们同学在这个方案的基础上实现了B1骨干网的监控，而且效果非常不错。</p><p>这给我了很大的信心。也就是我们也能根据百度自己的网络特点，设计出符合我们自己的网络监控方案。下一步就是解决DCN的网络监控了，我准备替换netradar。彼时netradar还没有移交到我这边的团队。设计的最重要的一点，就是可以快速的验证和部署，时不我待，当时的情况和压力已经不允许我们花半年的时间才去部署一套还不确定的探测方案。</p><p>和pingmesh不同，lidar采用一台探测机就可以探测机房内所有的服务器。作为冗余和交叉验证，我们采用机房内一台探测机，机房外一台探测机。如果资源充足，探测机的数量再多一些就更好。</p><p>这台探测机从topo数据库中拉取服务器信息，然后选取高质量的服务器作为目标服务器，通过发送TCP SYN包就】进行探测。测试效果不错，后来我们基于这个方案实现了机房内的网络探测1.0。</p><img src="/2026/06/11/baidu-network-monitor-lidar-beyond-pingmesh/image-20260608051137102.png" class=""><p>我们以这个简易的网络架构为例。我们在<strong>服务器1</strong>部署lidar探测程序，它去探测其他服务器的447端口。实际上我们还会从其他机房不是一个探测程序，探测这个机房的这些服务器。</p><p>这样，我们在很短的时间就完成了开发工作并部署，并又花了一个季度的时间进行打磨和调优，当然这些优化我们可以快速的进行更新部署，一次变更就完成了。</p><h2 id="原理：三种响应，一个答案"><a href="#原理：三种响应，一个答案" class="headerlink" title="原理：三种响应，一个答案"></a>原理：三种响应，一个答案</h2><p>lidar 发一个 SYN 出去，目标的内核 TCP 协议栈会自动响应——这是 TCP 协议栈的底层行为，不需要安装任何软件。也是大家都已经熟悉的三次握手阶段的第一步。</p><p>收到什么，就知道什么状态：</p><table><thead><tr><th>响应</th><th>含义</th></tr></thead><tbody><tr><td>SYN-ACK</td><td>端口开放，服务正常</td></tr><tr><td>RST</td><td>端口关闭或被防火墙拒绝，但网络连通</td></tr><tr><td>没响应</td><td>不可达，或中途丢包</td></tr></tbody></table><p>就这么简单。不需要 agent，不需要 server 端部署，不需要任何配置。给个 IP 和端口，立刻告诉你结果。</p><blockquote><p>当前最好的方式是探测一个监听的端口，比如 ssh 端口 22等。<br>实际测试探测一个不存在的端口，有些服务器并不会为每一个SYN请求都返回一个RST，导致误报。<br>即使是探测一个监听的端口，服务器上可能设置了防止SYN Flooding攻击的策略，也不允许你高频的探测，实际在 20 ~ 100 PPS之间是合理的。</p></blockquote><p>输出长这样：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">21:37:14, [192.168.1.14 -&gt; 74.48.173.243], sent: 10, received: 10 (SYN-ACK: 10, RST: 0), timeout: 0</span><br></pre></td></tr></table></figure><p>每秒一个统计窗口，sent&#x2F;received&#x2F;SYN-ACK&#x2F;RST&#x2F;timeout 一目了然。丢包了打 <code>[WARN]</code>，正常打 <code>[INFO]</code>。</p><p>你会不会担心服务器建立很多的半连接的TCP,会不会影响性能。实际是不会的，首先服务器有防止SYN Flooding的策略，如果收到太多的SYN会丢弃的，其次为了避免被策略丢弃，我们针对每台服务器的探测频率并不是很高，基本上在20pps ~ 100pps之间。</p><p>虽然单个服务器才100pps,但是因为一个TOR下我们会选取至少三台服务器，所以只对这些服服务器连接的ToR，它的探测频率是哦300pps, 如果更往上一层Leaf设备，它的探测频率又是几十倍，更往上Spine设备和DC，那探测频率是几万pps到几十万pps了。所以这种方式层级越高，探测的精度越高。</p><p>同时，还有一个很有意思的现象：在建立TCP握手的时候，目标服务器不是回传一个TCP+SYN的响应吗，探测服务器收到这个响应，发现它本机并没有监听这个端口(我们使用rawsocket进行旁路处理的)，然后TCP&#x2F;IP协议栈就会自动发一个RST给目标服务器，目标服务器就会把这个半连接关闭掉。所以实际上，这个半连接很快就被关闭了。这都是自动的。</p><h2 id="macOS-和-Linux-的收包差异，踩了坑"><a href="#macOS-和-Linux-的收包差异，踩了坑" class="headerlink" title="macOS 和 Linux 的收包差异，踩了坑"></a>macOS 和 Linux 的收包差异，踩了坑</h2><p>lidar 的开发过程中，最头疼的不是发包，是收包。</p><p>发 SYN 用 raw socket（<code>SOCK_RAW + IPPROTO_RAW + IP_HDRINCL</code>），macOS 和 Linux 行为一致，一套代码搞定。</p><p>收包就不一样了。</p><p><strong>Linux 上</strong>，raw socket（<code>SOCK_RAW + IPPROTO_TCP</code>）直接就能收到 TCP 响应包。内核处理 TCP 的同时，会把副本投递给 raw socket。简单直接。</p><p><strong>macOS 上</strong>，同样的代码，一个包都收不到。</p><p>原因是 macOS 的内核 TCP 协议栈会&quot;拦截&quot;TCP 报文。raw socket 注册了 <code>IPPROTO_TCP</code>，但内核觉得&quot;这是我的 TCP 连接&quot;，不给 raw socket 副本。BPF 设备（Berkeley Packet Filter）是唯一的出路——在链路层抓包，绕过 TCP 协议栈。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// macOS: 打开 /dev/bpf* 设备，在链路层抓包</span></span><br><span class="line"><span class="comment">// 1. 打开 /dev/bpf* 设备</span></span><br><span class="line"><span class="comment">// 2. ioctl BIOCSETIF 绑定到出接口</span></span><br><span class="line"><span class="comment">// 3. 设置 immediate mode + promiscuous mode</span></span><br><span class="line"><span class="comment">// 4. 读取 BPF 数据包（BPF 头 + 链路层头 + IP + TCP）</span></span><br><span class="line"><span class="comment">// 5. 剥离链路层头，解析 IP/TCP</span></span><br></pre></td></tr></table></figure><p>拿到的是原始链路层帧，带 14 字节的以太网头。得手动剥离，再解析 IP 和 TCP。</p><p>Linux 上拿到的直接就是 IP 包，没有链路层头。同一套解析逻辑，入口不同。</p><table><thead><tr><th></th><th>macOS</th><th>Linux</th></tr></thead><tbody><tr><td>收包方式</td><td>BPF 设备（链路层抓包）</td><td>raw socket</td></tr><tr><td>数据格式</td><td>以太网头 + IP + TCP</td><td>IP + TCP</td></tr><tr><td>为什么</td><td>内核 TCP 栈拦截，raw socket 拿不到</td><td>内核投递副本给 raw socket</td></tr></tbody></table><p>这是两个操作系统内核的设计差异，没有谁对谁错。但写代码的人得知道。</p><p>我们内部都是Linux服务器，所以没有这个问题，但是开源的版本我希望更通用些，方便使用苹果本的同学也能测试和使用，所以代码做了兼容。</p><h2 id="在高吞吐服务器上，BPF-过滤器救了命"><a href="#在高吞吐服务器上，BPF-过滤器救了命" class="headerlink" title="在高吞吐服务器上，BPF 过滤器救了命"></a>在高吞吐服务器上，BPF 过滤器救了命</h2><p>Linux 上用 raw socket 收包有个隐患：<code>SOCK_RAW + IPPROTO_TCP</code> 会收到<strong>所有</strong> TCP 报文。</p><p>在万兆网卡、几十万 pps 的服务器上，内核会把海量无关的 TCP 包拷贝到用户态，导致性能低下，吞吐率不高设置丢包。</p><p>解法是内核层过滤。<code>SO_ATTACH_FILTER</code> 挂载一个 classic BPF（cBPF）程序，在内核里就把不相关的包丢掉，只投递匹配探测端口的报文：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">A = packet[14] &amp; 0x0f * 4          // 从 IP IHL 算 TCP 头偏移</span><br><span class="line">A = packet[A+0..1]                  // 取 TCP srcPort（目标端口）</span><br><span class="line">if A != serverPort → reject          // 不是我们要的端口？丢掉</span><br><span class="line">A = packet[A+2..3]                  // 取 TCP dstPort（探测源端口）</span><br><span class="line">if A &lt; localPort → reject</span><br><span class="line">if A &gt;= localPort + count → reject</span><br><span class="line">accept</span><br></pre></td></tr></table></figure><p>12 条 cBPF 指令，硬编码在代码里。内核只把匹配的极少数报文投递上来，用户态几乎零开销。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">func</span> <span class="title">buildBPFProbeFilter</span><span class="params">(serverPort <span class="type">int</span>, srcPort <span class="type">uint16</span>, portCount <span class="type">uint16</span>)</span></span> []sendrecv.BPFInstruction &#123;</span><br><span class="line">    <span class="comment">// 手工构造 BPF 指令序列</span></span><br><span class="line">    <span class="comment">// 在内核层完成 srcPort 和 dstPort 的过滤</span></span><br><span class="line">    <span class="comment">// ...</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><h2 id="源端口轮转，覆盖-ECMP-多路径"><a href="#源端口轮转，覆盖-ECMP-多路径" class="headerlink" title="源端口轮转，覆盖 ECMP 多路径"></a>源端口轮转，覆盖 ECMP 多路径</h2><p>和 bitflip 一样，lidar 也面临多路径覆盖的问题。</p><p>两个固定 IP 之间，TCP 五元组如果固定（源端口不变），哈希结果始终相同，永远只走一条链路。怎么覆盖所有可能路径？</p><p>lidar 的做法是源端口轮转。每次发完一轮（所有目标各发一个 SYN），源端口 +1。配置了 100 个源端口，就跑 100 轮，每条可能的 ECMP 路径都有概率被覆盖到。</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 每轮所有目标各发一个 SYN 后，源端口 +1</span></span><br><span class="line">s.currentPort++</span><br><span class="line"><span class="keyword">if</span> s.currentPort &gt;= s.srcPort+s.portCount &#123;</span><br><span class="line">    s.currentPort = s.srcPort  <span class="comment">// 回绕</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p>这不是完美的&quot;全覆盖&quot;——你没法精确知道哪个端口走哪条路——但 100 个源端口在统计意义上足够覆盖典型的多路径拓扑。</p><h2 id="用起来"><a href="#用起来" class="headerlink" title="用起来"></a>用起来</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 探测单个目标的 80 端口</span></span><br><span class="line"><span class="built_in">sudo</span> ./lidar -t 10.0.0.2 -p 80</span><br><span class="line"></span><br><span class="line"><span class="comment"># 多目标探测</span></span><br><span class="line"><span class="built_in">sudo</span> ./lidar -t 10.0.0.2,10.0.0.3,10.0.0.4 -p 22</span><br><span class="line"></span><br><span class="line"><span class="comment"># 高速率探测 30 秒</span></span><br><span class="line"><span class="built_in">sudo</span> ./lidar -t 10.0.0.2 -p 80 --rate 100 -d 30s</span><br><span class="line"></span><br><span class="line"><span class="comment"># 发送固定 1000 个包</span></span><br><span class="line"><span class="built_in">sudo</span> ./lidar -t 10.0.0.2 -p 80 -n 1000</span><br><span class="line"></span><br><span class="line"><span class="comment"># 攻击队友好帮手：Verbose 模式看丢包端口</span></span><br><span class="line"><span class="built_in">sudo</span> ./lidar -t 10.0.0.2 -p 80 -v</span><br></pre></td></tr></table></figure><p>也支持配置文件：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;target_addrs&quot;</span><span class="punctuation">:</span> <span class="string">&quot;10.0.0.1,10.0.0.2&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;server_port&quot;</span><span class="punctuation">:</span> <span class="number">80</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;rate&quot;</span><span class="punctuation">:</span> <span class="number">10</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;span&quot;</span><span class="punctuation">:</span> <span class="string">&quot;1s&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;delay&quot;</span><span class="punctuation">:</span> <span class="string">&quot;3s&quot;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">sudo</span> ./lidar -c lidar.json</span><br></pre></td></tr></table></figure><h2 id="三个工具，三件不同的事"><a href="#三个工具，三件不同的事" class="headerlink" title="三个工具，三件不同的事"></a>三个工具，三件不同的事</h2><p>bitflip 做 UDP 丢包和改包检测，baize 做常态的丢包和改包检查，lidar 做 TCP SYN 端口可达性探测。三个工具解决的问题不同，但底层技术栈是相通的：raw socket 构造报文、BPF 过滤收包、时间桶统计、速率控制。</p><p>注意， lidar没办法进行改包检测，你知道为什么吗？所以下一个要开源的工具是mping。</p><blockquote><p><strong>冷知识</strong><br>最早是从radar这个名字开始，为我们的工具起名 sonar、lidar。<br>后来我们的平台就用中国古代神话人物和神兽命令：比如离娄、白泽和飞鸿。 </p></blockquote><p>这只是 nettools 的冰山一角。后续还有网关设备监控、evr的监控工具、定位工具，以及巨量监控数据的处理方案。</p><p>项目地址：<a href="https://github.com/baidu/nettools">https://github.com/baidu/nettools</a></p><p>欢迎 Star、试用、提 Issue 和 PR。</p>]]>
    </content>
    <id>https://colobu.com/2026/06/11/baidu-network-monitor-lidar-beyond-pingmesh/</id>
    <link href="https://colobu.com/2026/06/11/baidu-network-monitor-lidar-beyond-pingmesh/"/>
    <published>2026-06-11T00:10:28.000Z</published>
    <summary>
      <![CDATA[<p>上一个工具 bitflip&#x2F;baize 解决的是丢包和改包持续检测，在百度baize常常用在点到点之间的常态检测中，比如机房内集群间的监控，专线的检测, 新网络方案测试和灰度观察、核心网络设备的切回前检测等场景。</p>
<p>今天介绍的lidar工具，区别于传统的pingmesh探测方案，是我来百度后创造的第一个特殊的底层网络方案，我将其称之为lidar(激光扫描)方案，是一个很形象的比喻，我会话专门一节详细介绍它的优缺点，在这之前，我们介绍传统的赫赫有名的机房大规模的网络监控方案 pingmesh。</p>
<p><a href="https://nettools.rpcx.io/">https://nettools.rpcx.io</a> </p>
<h2 id="pingmesh-探测以及为什么我们不用它？"><a href="#pingmesh-探测以及为什么我们不用它？" class="headerlink" title="pingmesh 探测以及为什么我们不用它？"></a>pingmesh 探测以及为什么我们不用它？</h2><p>PingMesh（&quot;Pingmesh: A Large-Scale System for Data Center Network Latency Measurement and Analysis&quot;）是微软在SIGCOMM 2015上发表的论文。作者团队来自微软研究院和Azure网络部门。</p>]]>
    </summary>
    <title>百度网络监控工具开源第三弹：lidar — 不只是 pingmesh</title>
    <updated>2026-06-22T00:21:15.724Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="网络" scheme="https://colobu.com/categories/%E7%BD%91%E7%BB%9C/"/>
    <category term="网络" scheme="https://colobu.com/tags/%E7%BD%91%E7%BB%9C/"/>
    <content>
      <![CDATA[<blockquote><p>5000 包&#x2F;秒高频探测 + 无需时钟同步的单向丢包检测 + 全路径覆盖。内部跑了多年，现在开源了。</p></blockquote><hr><p>先讲一个实际case。</p><p>线上服务突然超时，用户投诉电话打爆了。打开监控大盘，一切正常——没有任何告警。折腾两小时，最后发现是某条链路间歇性轻微丢包，丢包率 0.3‰，传统监控压根抓不到。</p><img src="/2026/06/11/baidu-network-monitor-baize-open-source/image-20260605044902307.png" class=""><p>百度内部，baize 跑了多年：</p><ul><li><strong>集群间高频探测</strong>：机房内跨集群链路fullmesh监控</li><li><strong>机房间fullmesh探测</strong>：机房间,LCC机房链路fullmesh监控()</li><li><strong>混合云高频探测</strong>：A区和C区之间的混合云链路监控，5000 pps，秒级发现异常</li><li><strong>专线 SLA 监控</strong>：运营商专线质量持续监测，为 SLA 考核提供数据支撑</li><li><strong>网络改造保障</strong>：设备割接、链路升级期间持续监控，改造前后对比一目了然</li><li><strong>故障回切验证</strong>：从灾备切回主链路后，确认回切路径无丢包、无 bitflip后再切流</li></ul><hr><h2 id="06-开源与社区"><a href="#06-开源与社区" class="headerlink" title="06 开源与社区"></a>06 开源与社区</h2><p>baize 是百度 <strong>nettools</strong> 工具集的第二个开源工具，MIT 协议。</p><ul><li>GitHub: <a href="https://github.com/baidu/nettools">https://github.com/baidu/nettools</a></li><li>使用指南：<a href="https://nettools.rpcx.io/baize.html">https://nettools.rpcx.io/baize.html</a></li><li>语言：Go 1.26+</li><li>平台：Linux &#x2F; macOS，AMD64 &#x2F; ARM64</li></ul><p>内部版还支持从数据库拉配置、推数据到 Kafka 聚合，开源版做了简化，但留了可插拔的 Sender 接口——你可以自己实现，把数据发到 ClickHouse、Prometheus 或者任意后端。</p><hr><p>网络监控这件事，不是能不能做的问题，是做得够不够细的问题。</p><p><strong>每一条链路、每一个端口、每一个比特，都值得被监控。</strong> 这是我们在百度内部坚持的标准，今天开源出来，希望对你有用。</p><p>被间歇性轻微丢包折磨过的话，去 GitHub 点个 Star，试试 baize。</p>]]>
    </content>
    <id>https://colobu.com/2026/06/11/baidu-network-monitor-baize-open-source/</id>
    <link href="https://colobu.com/2026/06/11/baidu-network-monitor-baize-open-source/"/>
    <published>2026-06-11T00:10:28.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>5000 包&#x2F;秒高频探测 + 无需时钟同步的单向丢包检测 + 全路径覆盖。内部跑了多年，现在开源了。</p>
</blockquote>
<hr>
<p>先讲一个实际case。</p>
<p>线上服务突然超时，用户投诉电话打爆了。打开监控]]>
    </summary>
    <title>百度物理网络监控工具开源第二弹：毫秒级监控工具 baize，让你的网络问题无处遁形</title>
    <updated>2026-06-22T00:21:15.953Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="网络" scheme="https://colobu.com/categories/%E7%BD%91%E7%BB%9C/"/>
    <category term="网络" scheme="https://colobu.com/tags/%E7%BD%91%E7%BB%9C/"/>
    <content>
      <![CDATA[<blockquote><p>Go 网络编程，大家第一反应就是 gopacket。但如果你用过 Scapy，你会发现 gopacket 的 API 繁琐得让人抓狂。goscapy 把 Scapy 的优雅搬到了 Go 里——流式构建、自动校验和、协议自动推断、一行代码搞定数据包，还能嗅探、发送、收响应。</p></blockquote><hr><p>最早接触到python生态圈的<a href="https://scapy.net/">scapy</a>是两年前，在和交换机的同学搞交换机探针的时候，他写了几行python代码就实现了一个发包探测程序，我立马就被scapy吸引了，居然写网络测试程序可以这么简单？</p><img src="/2026/06/11/beyond-gopacket-powerful-network-lib/image-20260608233117805.png" class=""><h2 id="03-数据包解析：自动协议推断"><a href="#03-数据包解析：自动协议推断" class="headerlink" title="03 数据包解析：自动协议推断"></a>03 数据包解析：自动协议推断</h2><p>构造包只是故事的一半，解析包是另一半。</p><p>goscapy 的 <code>Dissect</code> 能从原始字节自动推断出完整的协议栈：</p><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">raw := []<span class="type">byte</span>&#123;<span class="number">0xff</span>, <span class="number">0xff</span>, <span class="number">0xff</span>, <span class="number">0xff</span>, <span class="number">0xff</span>, <span class="number">0xff</span>, <span class="number">0xaa</span>, <span class="number">0xbb</span>, ...&#125;</span><br><span class="line">pkt, _ := packet.DissectByProto(raw, <span class="string">&quot;Ethernet&quot;</span>)</span><br><span class="line">fmt.Println(pkt.String()) <span class="comment">// &quot;Ethernet / IP / ICMP&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment">// 访问具体层</span></span><br><span class="line">ipLayer := pkt.GetLayer(<span class="string">&quot;IP&quot;</span>)</span><br><span class="line">srcIP, _ := ipLayer.Get(<span class="string">&quot;src&quot;</span>) <span class="comment">// net.IP&#123;192, 168, 1, 1&#125;</span></span><br></pre></td></tr></table></figure><p>解析引擎靠注册表驱动。每一层解析完后，查 <code>keyField</code> 找到下一层——Ethernet 看看 <code>type</code> 字段（0x0800 → IP），IP 看看 <code>proto</code> 字段（6 → TCP），TCP 看看 <code>dport</code>（80 → HTTP）。VXLAN 这种隧道协议还能递归解析内层包，最多支持 8 层嵌套。</p><p>启发式规则也注册了一大堆：UDP 53 端口 → DNS，TCP 80 → HTTP，UDP 4789 → VXLAN，IP proto 47 → GRE……抓到的包基本都能自动识别到应用层。</p><img src="/2026/06/11/beyond-gopacket-powerful-network-lib/image-20260608233529079.png" class=""><h2 id="04-嗅探和收发包：Scapy-的-sr-srp-到-Go"><a href="#04-嗅探和收发包：Scapy-的-sr-srp-到-Go" class="headerlink" title="04 嗅探和收发包：Scapy 的 sr&#x2F;srp 到 Go"></a>04 嗅探和收发包：Scapy 的 sr&#x2F;srp 到 Go</h2><p>写过 Scapy 的人一定对 <code>sr()</code> 和 <code>sr1()</code> 不陌生——发一个包，自动等响应，还能做协议级匹配。goscapy 把这套逻辑完整搬过来了。</p><h3 id="发包"><a href="#发包" class="headerlink" title="发包"></a>发包</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 构造结构化的 Packet 对象（不是 Build 出 []byte）</span></span><br><span class="line">pkt := goscapy.NewEthernet().</span><br><span class="line">    DstMAC(<span class="string">&quot;ff:ff:ff:ff:ff:ff&quot;</span>).</span><br><span class="line">    Over(goscapy.NewIP().SrcIP(<span class="string">&quot;192.168.1.1&quot;</span>).DstIP(<span class="string">&quot;8.8.8.8&quot;</span>)).</span><br><span class="line">    Over(goscapy.NewICMP().Type(<span class="number">8</span>).Code(<span class="number">0</span>)).</span><br><span class="line">    Packet()</span><br><span class="line"></span><br><span class="line">sendrecv.Send(pkt, <span class="string">&quot;eth0&quot;</span>)   <span class="comment">// L3 发送（IP 层，OS 补 Ethernet）</span></span><br><span class="line">sendrecv.Sendp(pkt, <span class="string">&quot;eth0&quot;</span>)  <span class="comment">// L2 发送（完整以太网帧）</span></span><br></pre></td></tr></table></figure><h3 id="发包收响应"><a href="#发包收响应" class="headerlink" title="发包收响应"></a>发包收响应</h3><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 类似 Scapy 的 sr1()：发 ICMP Echo，等第一个响应</span></span><br><span class="line">sent, reply, err := sendrecv.Sr1(pkt, <span class="string">&quot;eth0&quot;</span>, <span class="number">3</span>*time.Second, <span class="literal">nil</span>)</span><br><span class="line"><span class="keyword">if</span> reply != <span class="literal">nil</span> &#123;</span><br><span class="line">    ipLayer := reply.GetLayer(<span class="string">&quot;IP&quot;</span>)</span><br><span class="line">    srcIP, _ := ipLayer.Get(<span class="string">&quot;src&quot;</span>) <span class="comment">// 8.8.8.8 的回复</span></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><code>DefaultMatch</code> 自动匹配响应包——ICMP Echo Request 配 Echo Reply（ID 匹配），TCP SYN 配 SYN-ACK（端口翻转 + ack &#x3D; seq+1），UDP 配端口翻转，DNS 匹 transaction ID，ARP 配 IP 交换。不用写一行匹配逻辑。</p><img src="/2026/06/11/beyond-gopacket-powerful-network-lib/image-20260608234240098.png" class=""><p><strong>gopacket 更适合</strong>：需要解析冷门协议、对 pcap 文件读写有强需求、已经重度依赖 libpcap 生态的项目。</p><p><strong>goscapy 更适合</strong>：网络工具开发、安全扫描、协议测试、网络监控探测——任何需要快速构造和收发数据包的场景。纯 Go 部署简单，API 用起来舒服。</p><h2 id="10-性能：零拷贝序列化"><a href="#10-性能：零拷贝序列化" class="headerlink" title="10 性能：零拷贝序列化"></a>10 性能：零拷贝序列化</h2><p>goscapy 在序列化上做了不少优化：</p><ul><li><strong><code>SerializeInto</code></strong>：直接写入目标 buffer，无额外堆分配</li><li><strong><code>BuildInto</code></strong>：用户提供 buffer，整个包一次序列化完成</li><li><strong><code>RecvInto</code></strong>：收包直接读入用户 buffer，减少一次拷贝</li><li><strong>校验和零拷贝</strong>：<code>checksumIPv4Pseudo</code> 直接折叠多个内存区域，不拼接</li><li><strong><code>WireSize</code></strong>：预计算序列化大小，一次分配精确大小的 buffer</li></ul><figure class="highlight go"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 零拷贝发送</span></span><br><span class="line">buf := <span class="built_in">make</span>([]<span class="type">byte</span>, <span class="number">1500</span>) <span class="comment">// MTU 大小的 buffer</span></span><br><span class="line">result, err := pkt.BuildInto(buf) <span class="comment">// 直接写入，无额外分配</span></span><br></pre></td></tr></table></figure><p>还有 Linux 特有的高性能接收模式——<code>AF_PACKET</code> mmap、零拷贝 (<code>PACKET_QDISC_BYPASS</code>)、<code>io_uring</code> 原始套接字——适合高频探测场景。这些在 examples 目录里有完整示例（23-packet-mmap、21-zerocopy、22-uring-raw-socket）。</p><h2 id="11-丰富的示例库"><a href="#11-丰富的示例库" class="headerlink" title="11 丰富的示例库"></a>11 丰富的示例库</h2><p>goscapy 自带了 50+ 个示例，覆盖从基础到高级的几乎所有场景：</p><ul><li>基础：ping、traceroute、TCP SYN 扫描、ARP 扫描</li><li>协议：DNS 客户端、DHCP 客户端、NTP 客户端</li><li>隧道：VXLAN 封装、GRE 隧道、ERSPAN</li><li>高级：PCAP 读写、TCP 流重组、BPF 过滤</li><li>性能：零拷贝收包、io_uring、packet mmap、批量发送</li><li>无线：802.11 WiFi 帧、蓝牙 HCI&#x2F;L2CAP、Zigbee、LoRaWAN</li><li>安全：p0f 指纹、端口扫描、ARP 扫描</li></ul><p>每个示例都是可编译运行的小程序，直接 <code>go run</code> 就能跑。</p><hr><p>项目地址：<a href="https://github.com/smallnest/goscapy">github.com&#x2F;smallnest&#x2F;goscapy</a></p><p>纯 Go，MIT 协议，零 C 依赖。<code>go get github.com/smallnest/goscapy</code> 就能用。</p><p>如果你在做网络工具、安全扫描、协议测试、监控探测——试试 goscapy，可能会让你重新爱上 Go 网络编程。</p>]]>
    </content>
    <id>https://colobu.com/2026/06/11/beyond-gopacket-powerful-network-lib/</id>
    <link href="https://colobu.com/2026/06/11/beyond-gopacket-powerful-network-lib/"/>
    <published>2026-06-11T00:10:28.000Z</published>
    <summary>
      <![CDATA[<blockquote>
<p>Go 网络编程，大家第一反应就是 gopacket。但如果你用过 Scapy，你会发现 gopacket 的 API 繁琐得让人抓狂。goscapy 把 Scapy 的优雅搬到了 Go 里——流式构建、自动校验和、协议自动推断、一行代码搞定数据包，]]>
    </summary>
    <title>别只盯着gopacket了，看看这个强大的网络库</title>
    <updated>2026-06-22T00:21:15.669Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="AI" scheme="https://colobu.com/categories/AI/"/>
    <category term="AI" scheme="https://colobu.com/tags/AI/"/>
    <content>
      <![CDATA[<p>2026年5月28日 · Mike Piccolo, iii 创始人兼 CEO</p><hr><img src="/2026/06/11/build-your-own-agent-runtime/image-20260602000705691.png" class=""><p>大多数 agent 团队不构建运行时。他们采用一个。LangChain、LangGraph、OpenAI Agents SDK、Anthropic SDK、CrewAI、AutoGen——循环、工具、记忆、编排，都是作为一个单一决策从货架上挑选的。运行时是一个你 import 的框架。如果里面的什么东西不合适，你就 fork 它、跟它斗争、或者绕过它。</p><span id="more"></span><p>我认为这种形态是错的，这就是为什么每个长期运行的 agent 团队最终都会从头重写它的运行时。运行时不是一个东西。它是十到十二个不同的东西被捆绑在一起，因为周围的生态系统没有给你组合它们的方式。Pi agent 的包化走在了正确的路上，但它们仍然处在&quot;再添加一个服务并与所有其他服务集成&quot;的范式中。iii 引擎将所有 worker 一视同仁，完全移除了集成逻辑。provider 路由器、凭证保管库、策略引擎、审批网关、模型目录、会话存储、预算追踪器、调用后 hook 扇出、持久化的 turn 循环——这些都是独立的关注点。它们全都可以与你的队列、HTTP&#x2F;API 服务器、流式传输、甚至浏览器 worker 互操作。一个把它们当作整体发布的框架，是在卖给你一个你本不必做的权衡。</p><p>iii 底层的赌注是：它们不应该是一个整体。应该有一组 worker 运行在共享引擎上，每一个都可替换，每一个可独立版本化，每一个通过一个单一原语连接起来：一个触发器（<code>iii.trigger()</code>），其他每个 worker 也都使用它。运行时变成一组可安装的 worker 堆栈，&quot;构建你自己的&quot;不再意味着&quot;fork 一个框架&quot;，而是意味着&quot;替换几个 worker&quot;。</p><p>本文带你看看这实际上是什么样的。今天驱动一个 iii agent turn 的完整技术栈，为什么每一层都是自己的 worker，以及你如何替换其中任何一层。</p><h2 id="Agent-运行时必须完成的-15-项工作"><a href="#Agent-运行时必须完成的-15-项工作" class="headerlink" title="Agent 运行时必须完成的 15 项工作"></a>Agent 运行时必须完成的 15 项工作</h2><p>如果你把一个生产级 agent 运行时剥离回它的职责，你会得到大致像这样的一个列表：</p><ol><li><p>接受来自客户端的 turn 请求并持久化它</p></li><li><p>为被调用的模型 provider 解析凭证</p></li><li><p>查询所选模型实际能做什么（视觉、工具、流式传输、上下文窗口）</p></li><li><p>驱动每个 turn 的状态机：预配、流式传输 assistant、运行工具、引导、清理</p></li><li><p>加载并提供 skill 描述体，说明每个函数的请求格式、错误码和使用说明</p></li><li><p>组装系统提示词：模式段落、身份前言、工作目录、默认 skill 附录</p></li><li><p>当模型产生 token 时将 token 流式推送回客户端</p></li><li><p>在运行前检查每个工具调用（这只是一个函数）是否符合策略</p></li><li><p>暂停需要人工决策的工具调用，并将决策结果路由回正确的 turn</p></li><li><p>按工作空间或 agent 跟踪 LLM 支出</p></li><li><p>在工具调用前后运行 hook（日志记录、脱敏、自定义副作用）</p></li><li><p>将会话持久化为分支树，以便 fork 和恢复正常工作</p></li><li><p>当上下文窗口填满时压缩会话历史</p></li><li><p>发出 UI 订阅的事件流</p></li><li><p>我看到每家 agent 公司构建中都缺失的一块：在每一步携带一条 OpenTelemetry 追踪，这样你才能调试它</p></li></ol><p>每个严肃的 agent 运行时处理其中大多数。昂贵的处理全部。廉价的走捷径，然后在进入生产环境后重新构建那些捷径部分。框架将它们捆绑成一个单体，并发布每样东西的一个版本。最后这一点是让你付出代价的部分，因为一年后，你会发现你想要的策略引擎不是框架自带的那个策略引擎，而替换它意味着替换整个运行时。</p><p>iii 运行时将这十三项工作中的每一项都作为独立的 worker 部署在 workers.iii.dev 注册中心上。每个使用相同的 WebSocket 协议。每个在相同的引擎总线上注册函数和触发器。每一个都可以 <code>iii worker add</code>、可替换、可用 SDK 以任何语言编写。</p><h2 id="按-worker-拆解技术栈"><a href="#按-worker-拆解技术栈" class="headerlink" title="按 worker 拆解技术栈"></a>按 worker 拆解技术栈</h2><p>以下是来自 iii-hq&#x2F;workers 单体仓库的实际生产级技术栈，每个 worker 的职责用一句话概括。整个代码包发布在 github.com&#x2F;iii-hq&#x2F;workers&#x2F;harness：</p><table><thead><tr><th>Worker</th><th>职责</th></tr></thead><tbody><tr><td><code>iii-directory</code></td><td>Skill 和提示词注册中心。Worker 以 <code>iii://&lt;worker&gt;/&lt;function&gt;</code> 发布 skill；agent 通过 <code>directory::skills::get</code> 按需获取。随 iii 引擎一同发布（Rust）。</td></tr><tr><td><code>harness</code></td><td>Meta-worker。加载 <code>iii-permissions.yaml</code>。暴露 <code>policy::check_permissions</code> 和 <code>ui::*</code> 平面。将 <code>agent::events</code> 推送给订阅的浏览器。</td></tr><tr><td><code>turn-orchestrator</code></td><td>驱动每个 agent turn 的持久化 11 状态 FSM。拥有 <code>run::start</code>、<code>turn::step</code>、<code>turn::get_state</code>。还在预配阶段组装系统提示词。</td></tr><tr><td><code>approval-gate</code></td><td>操作员决策的总线入口点。将 <code>approval::resolve</code> 路由到编排器注册的每个调用的恢复函数。</td></tr><tr><td><code>session</code></td><td>分支会话存储。<code>session-tree::*</code> 用于父链接条目树；<code>session-inbox::*</code> 用于每个会话的队列。</td></tr><tr><td><code>llm-budget</code></td><td>工作空间 + agent 支出上限。14 个 <code>budget::*</code> 函数，包括检查、记录、告警、预测、周期切换。</td></tr><tr><td><code>hook-fanout</code></td><td>在流主题上通用发布并收集。每个 iii hook 构建于其上的模式。</td></tr><tr><td><code>auth-credentials</code></td><td><code>auth::*</code> 下基于文件的 provider 凭证保管库。</td></tr><tr><td><code>models-catalog</code></td><td>静态模型能力目录。<code>models::list</code>、<code>models::get</code>、<code>models::supports</code>。</td></tr><tr><td><code>provider-anthropic</code></td><td>Anthropic Messages API SSE 流式推送到 iii channel。</td></tr><tr><td><code>provider-openai</code></td><td>OpenAI Chat Completions SSE 流式推送到 iii channel。</td></tr><tr><td><code>provider-kimi</code></td><td>Kimi（Moonshot）Chat Completions SSE。</td></tr><tr><td><code>provider-lmstudio</code></td><td>用于桌面开发的本地 LM Studio SSE。</td></tr><tr><td><code>context-compaction</code></td><td>可选的 <code>agent::events</code>，在 token 数超过阈值时压缩会话历史。</td></tr></tbody></table><p>十一个 worker。一个引擎。每个都有发布的版本。每个都可以作为独立进程运行（开发时 <code>pnpm dev:&lt;worker&gt;</code>，作为发布二进制时 <code>iii worker add &lt;specific-worker&gt;</code>），或者作为将它们一起启动的组合入口点的一部分。</p><p>这之所以重要：表中的每个框都是一个别人可以递给你一个不同 worker、而你可以保留其余部分的地方。不喜欢静态模型目录？换一个注册了 <code>models::list</code> 并从实时 API 读取的 worker。不喜欢基于文件的凭证？换一个注册了 <code>auth::get_token</code> 并从密钥管理器读取的 worker。想要一个针对不同分支 workflow 的不同 turn FSM？替换 <code>turn-orchestrator</code>——每个依赖方通过相同的总线调用 <code>run::start</code> 并读取 <code>turn_state</code>，所以技术栈的其余部分不会改变。</p><h2 id="循环的实际运行方式"><a href="#循环的实际运行方式" class="headerlink" title="循环的实际运行方式"></a>循环的实际运行方式</h2><p>一个 turn 的形态如下，按 worker 的触发顺序逐一遍历。</p><p>浏览器、CLI 或聊天客户端通过 <code>harness::trigger</code> POST 一个 turn，携带 <code>&#123;session_id, message_id, payload&#125;</code>。harness meta-worker 将 payload 转发给 <code>run::start</code>。这一跳的存在是为了让 OpenTelemetry span 包装器可以将 session ID 和 message ID 作为 baggage 植入，传播到技术栈中每个 worker 的每个嵌套 <code>iii.trigger</code> 调用。另一端的追踪树是一个连接的图。</p><p><code>run::start</code> 落在 turn-orchestrator 上。它持久化运行请求，在 iii state 的 <code>session/&lt;sid&gt;/turn_state</code> 处植入初始的 TurnStateRecord，然后立即返回。实际工作在持久化的每状态机内部完成，由发布到 turn-step FIFO 的消息唤醒。</p><p>两个终止状态是 <code>stopped</code>（通过 <code>finishSession()</code> 正常退出）和 <code>failed</code>（未预期的处理程序抛出被路由到此处，ack 队列使其停止重试，并发出 <code>message_complete&#123;stop_reason:&#39;error&#39;&#125;</code> 加上 <code>agent_end</code>，以便 UI 显示原因）。Teardown 是一个内联的 <code>finishSession()</code> 调用，从任何 turn 结束路径调用，而不是单独的入队步骤。</p><p><code>provisioning</code> 做三件事。如果运行需要隔离执行，它启动一个 iii-sandbox 微虚拟机。它为 <code>system_default_skills</code>（默认为 <code>[&quot;iii://iii-directory/index&quot;]</code>）中的每个命名空间调用 <code>directory::skills::download</code>，使 iii-directory 预先缓存运行启动时所需的 skill 描述体。然后它分三层组装系统提示词：从 <code>run_request.mode</code> 中选取的模式段落（<code>plan</code>、<code>ask</code> 或 <code>agent</code>），iii 身份前言教给模型 <code>agent_trigger</code> 约定和 <code>directory::skills::get</code> 按需发现模式，以及 agent 启动时附带的默认 skill 索引。调用方可以通过在 <code>run::start</code> 上传递 <code>system_prompt</code> 来覆盖整个提示词；否则由编排器构建它。函数 schema 来自实时引擎目录。</p><p><code>assistant_streaming</code> 在匹配此次运行的 provider 字段的 provider worker 上调用 <code>provider::&lt;name&gt;::stream</code>。provider worker 通过 <code>auth::get_token</code>（auth-credentials）拉取凭证，将模型的 SSE 响应流式推送到 iii channel 中，编排器消费该 channel，在 <code>agent::events</code> 上发出 <code>message_update</code> 事件供 UI 扇出。Channel 创建和读取循环位于 <code>provider-stream.ts</code> 中基于拉取的 MessagePump 之后，因此流式状态专注于状态转换。</p><p>当 assistant 返回工具调用时，FSM 进入 <code>function_execute</code>。每个工具调用都经过 <code>dispatchWithHook</code>——编排器中唯一的卡控点。<code>consultBefore</code> 直接调用 <code>policy::check_permissions</code>，带有 5 秒超时。策略 worker（在默认技术栈中，就是 harness meta-worker）读取 <code>iii-permissions.yaml</code>，将调用的 <code>function_id</code> 与规则集进行匹配，返回三种结果之一：</p><ul><li><code>allow</code>：继续分发；编排器触发目标函数并写入结果</li><li><code>deny</code>：以 DenialEnvelope 短路分发，结果成为一条拒绝记录</li><li><code>needs_approval</code>：单个调用被停放到 turn 的 <code>awaiting_approval</code> 列表中。批次的其余部分继续分发。仅当有一个或多个待审批条目时，turn 才会转换到 <code>function_awaiting_approval</code></li></ul><p>审批唤醒是响应式且共享的。编排器在 scope <code>approvals</code> 上注册了正好一个 <code>turn::on_approval</code> 状态触发器。当控制台调用 <code>approval::resolve</code> 时，approval-gate worker 将 <code>approvals/&lt;sid&gt;/&lt;cid&gt; = &#123;decision, reason&#125;</code> 写入 iii state。该写入触发 <code>turn::on_approval</code>，推进受影响的会话。<code>function_awaiting_approval</code> 只读取刚刚到达的决策，在每个决策到达时分发它（<code>allow</code> 成为预批准的分发，<code>deny</code> 或 <code>aborted</code> 成为合成的拒绝），并在 <code>awaiting_approval[]</code> 为空时推进。无需为每个调用注册恢复函数。无需启动时重新扫描来恢复待处理的审批。一个触发器覆盖所有会话。</p><p>默认拒绝是构造性的：如果策略 worker 不可达，或者 5 秒超时触发，<code>consultBefore</code> 以 <code>gate_unavailable</code> 信封拒绝调用。如果 <code>iii::durable::publish</code> 本身出错，hook fanout 返回 <code>publish_failed: true</code>，编排器将其视为拒绝。</p><p>这种形态带来了几项延迟优化。当没有持久化订阅者为该主题注册时，函数调用后 hook 通过订阅者存在缓存短路 <code>publish_collect</code>，每个执行的函数调用移除大约 500 毫秒。<code>tearing_down</code> 被内联到 <code>finishSession()</code> 中，每个 turn 移除一次持久化队列跃点。<code>context-compaction</code> 订阅了编排器在 turn 边界发出的专用 <code>agent::turn_end</code> 流，因此压缩器的唤醒是每个 turn 一次而非每个事件一次。session-create 扇出状态触发器仅通过 scope 进行门控并在进程内匹配，因此之前每次写入的 <code>harness::session::is_create_event</code> RPC 已经消失。</p><p>批次完成后，<code>steering_check</code> 决定是继续、停止还是达到 <code>max_turns</code>。如果继续，循环回 <code>assistant_streaming</code>。如果停止或达到上限，<code>finishSession()</code> 内联运行：发出 <code>agent_end</code>，释放 sandbox，转换到 <code>stopped</code>。</p><p>在整个运行过程中，每个参与的 worker 发出的 OTel span 都带有 <code>iii.session.id</code>、<code>iii.message.id</code> 和 <code>iii.function.id</code> 标签。这些标签正是引擎的 <code>engine::traces::group_by</code> 读取的内容，用于在追踪 UI 中填充&quot;按会话分组&quot;&#x2F;&quot;按消息分组&quot;&#x2F;&quot;按函数分组&quot;。埋点是自动的：<code>src/runtime/worker.ts</code> 将每个 <code>registerFunction</code> 包装在 Proxy 中，因此不必有任何 worker 代码记住要添加 span。</p><h2 id="构建你自己的"><a href="#构建你自己的" class="headerlink" title="构建你自己的"></a>构建你自己的</h2><p>有趣的部分在于，以上所有 worker 都不是特殊的。每一个都是一个打开 WebSocket 连接到引擎、注册一些函数和触发器并运行的进程。这个契约与每个应用 worker 使用的契约完全相同。运行时构建在与你的业务逻辑相同的原语之上。</p><p>这意味着&quot;构建你自己的运行时&quot;分解成与&quot;编写任何 worker&quot;相同的操作。你选择你想替换的层，你写一个在总线上注册相同函数的 worker，你 <code>iii worker add</code> 它，技术栈的其余部分就开始使用你的 worker。</p><p>有两个层没有出现在上述 worker 表中，但对运行时的行为很重要。Skills 是每个 worker 告知其函数功能的机制。每个 worker 可以在 <code>iii://&lt;worker&gt;/&lt;function&gt;</code> 处发布一个 skill，agent 在首次调用该函数之前通过 <code>directory::skills::get</code> 获取它。系统提示词在每个 turn 由模式段落、iii 身份前言以及运行配置的默认 skill 描述体组装而成。两者都是总线驱动的：skill 由 iii-directory worker 提供服务，系统提示词由 turn-orchestrator 组装。两者都可替换。</p><p>五个具体示例。</p><p><strong>用实时 API 替换模型目录。</strong> 写一个 worker，注册 <code>models::list</code>、<code>models::get</code>、<code>models::supports</code>。让它每 N 分钟从你 provider 的目录端点获取数据并缓存。发布它。<code>iii worker add your-org/dynamic-models-catalog</code>。停止静态的 models-catalog worker。turn-orchestrator 感知不到任何差异。它调用 <code>iii.trigger(&#39;models::list&#39;)</code>，引擎路由到最近注册了该函数 ID 的任意 worker。</p><p><strong>添加新的 provider。</strong> <code>provider-kimi</code> 和 <code>provider-lmstudio</code> 已经证明了这种形态。每个都是一个 worker，注册 <code>provider::&lt;name&gt;::stream</code> 和 <code>provider::&lt;name&gt;::complete</code>，将来自上游 API 的 SSE 流导入 iii channel，并通过 <code>budget::record</code> 将其模型用量写入 llm-budget。添加第五个 provider 就是写一个文件夹，包含一个 <code>iii.worker.yaml</code> 和一个 <code>register.ts</code>。发布到注册中心，或保留在本地。turn-orchestrator 通过每次运行的 provider 字段选择 provider；新 provider 在 worker 连接的瞬间即可使用。</p><p><strong>从私有制品库提供 skill。</strong> 写一个 worker，注册 <code>directory::skills::get</code> 和 <code>directory::skills::list</code>，后端是内部文档系统或私有 S3 存储桶。断开或重命名默认的 iii-directory worker。编排器的 bootstrap 为每个命名空间调用 <code>directory::skills::download</code>；你的 worker 来响应。agent 的&quot;在调用新函数前获取每个函数的 skill&quot;模式保持不变，因为 wire 格式相同。</p><p><strong>完全覆盖系统提示词。</strong> <code>run::start</code> 接受一个可选的 <code>system_prompt</code> 字段。传入它，编排器将逐字使用你的字符串，跳过模式段落 + 身份前言 + skill 附录的组装。当你有一个已有的提示词资产，想让运行时原封不动地遵守时，这很有用。Skill 下载仍在 bootstrap 中运行，因此 agent 即使使用自定义提示词也保留 <code>directory::skills::get</code> 按需发现能力。</p><p><strong>替换审批网关的 UI 界面。</strong> 默认的 approval-gate worker 注册 <code>approval::resolve</code>。wire 模式是一次函数调用：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">iii.trigger(&#x27;approval::resolve&#x27;, &#123;</span><br><span class="line">  session_id: &#x27;...&#x27;,</span><br><span class="line">  function_call_id: &#x27;...&#x27;,</span><br><span class="line">  decision: &#x27;allow&#x27; | &#x27;deny&#x27; | &#x27;aborted&#x27;,</span><br><span class="line">  reason: &#x27;optional human text&#x27;,</span><br><span class="line">&#125;)</span><br></pre></td></tr></table></figure><p>处理程序将 <code>approvals/&lt;sid&gt;/&lt;cid&gt; = &#123;decision, reason&#125;</code> 持久化到 iii state。编排器唯一的 <code>turn::on_approval</code> 状态触发器捕获该写入并唤醒正确的会话。如果你想从 Slack 而非控制台驱动审批，写一个 Slack worker，监听 <code>/approve &lt;id&gt;</code> 和 <code>/deny &lt;id&gt;</code> 斜杠命令，然后用正确的 payload 调用 <code>approval::resolve</code>。编排器感知不到任何差异。整个 approval-gate worker 保持原封不动。你添加了一个新 worker；你没有替换已有的那个。</p><p>如果你想要不同的策略引擎（OPA、Cedar、你自己的 DSL），写一个 worker，注册 <code>policy::check_permissions</code>，返回 <code>&#123; decision, rule_id?, matched_constraint? &#125;</code>。断开默认的策略 worker（它包含在 harness meta-worker 中，所以你需要禁用那个处理程序或者运行一个精简版的 meta-worker）。turn-orchestrator 的 <code>consultBefore</code> 感知不到任何差异。相同的 5 秒超时、相同的 fail-closed 语义、相同的 wire 格式。</p><p>这些示例的重点不在于具体的替换项。而在于操作的形态。iii 技术栈中的每个运行时层都可以通过总线上的一两个函数 ID 访问。替换一个层就是写一个注册了这些 ID 的 worker。系统的其余部分保持不变。</p><h2 id="运行时是一个滑块，而不是分岔路口"><a href="#运行时是一个滑块，而不是分岔路口" class="headerlink" title="运行时是一个滑块，而不是分岔路口"></a>运行时是一个滑块，而不是分岔路口</h2><p>经典的运行时争论以薄 vs 厚的框架来表述。Anthropic 的薄循环 vs LangGraph 的显式 DAG。这种框架假设你选择一边并接受它。</p><p>当运行时由同一总线上的 worker 组合而成时，薄 vs 厚只是你安装了多少个 worker 的计数。薄运行时是 <code>turn-orchestrator</code> 加上 <code>provider-anthropic</code> 加上 <code>auth-credentials</code> 加上最小化的 harness meta-worker。仅此而已。没有审批、没有预算、没有策略引擎、没有 hook fanout。运行任何东西。信任模型。适用于自主研究 agent、实验性循环、任何内部用途。</p><p>厚运行时是所有 worker 加上 <code>context-compaction</code> 加上自定义策略 worker 加上自定义 approval-gate 加上 Slack 集成的审批界面加上按工作空间执行支出上限的预算 worker。适用于运行面向客户 workflow 的 agent，其中每个工具调用都需要可审计，每次模型支出都需要汇总到财务仪表板。</p><p>薄与厚之间的架构距离不是重写。它是一个配置更改。相同的原语、相同的 wire 协议、相同的追踪格式、相同的可观测性方案。通过在 <code>config.yaml</code> 中添加和移除 worker 来移动滑块。其他一切保持不变。</p><p>这也适用于单个 worker 内部。turn-orchestrator 刚刚发布了一个重构，将其 FSM 从十一个状态压缩为七个，删除了每个调用的 <code>turn::approval_resume::&lt;sid&gt;/&lt;cid&gt;</code> 机制，代之以在 scope <code>approvals</code> 上的一个响应式 <code>turn::on_approval</code> 状态触发器，并将 <code>tearing_down</code> 内联到 <code>finishSession()</code> 调用中。技术栈中的其他每个 worker（approval-gate、session、llm-budget、providers、models-catalog、auth-credentials、hook-fanout、context-compaction）保持不变。<code>approval::resolve</code> wire 格式没有变化。契约保持住了。这就是组合性带给你的特性：一个 worker 的重大内部重写是自包含的变更，因为每个邻居都通过总线级的函数 ID 与它通信。</p><p>这是框架模型无法给你的部分。框架替你选择了滑块上的一个位置并锁定你。worker 模型将滑块留在你手中。</p><h2 id="这在实际中意味着什么"><a href="#这在实际中意味着什么" class="headerlink" title="这在实际中意味着什么"></a>这在实际中意味着什么</h2><p>如果你一直在某个框架之上运行 agent，并且感受到大多数团队在规模化时遇到的相同的边界问题，那么答案很可能不是&quot;用我们自己的框架重写运行时&quot;。策略引擎无法按你需要的方式扩展。审批 UI 被捆绑在框架的聊天界面里。凭证存储无法与你的密钥管理器对话。预算追踪器位于追踪无法看到的 sidecar 数据库中。答案是切换到一个运行时从一开始就被解耦的基座。</p><p>最快感受到这个论点的方法是 clone github.com&#x2F;iii-hq&#x2F;workers，<code>pnpm install</code>，<code>pnpm build</code>，然后运行组合入口点。你将获得指向 iii 引擎的完整十四 worker 运行时。你可以通过从启动列表中移除 worker 条目来禁用任何 worker。你可以通过写一个注册了相同函数 ID 的替代品来替换任何 worker。你可以通过向其 hook 主题添加订阅者来扩展任何 worker。<code>hook-fanout::publish_collect</code> 是每个 iii hook 构建于其上的通用机制。</p><p>文档在 iii.dev&#x2F;docs。引擎在 github.com&#x2F;iii-hq&#x2F;iii。Worker 注册中心在 workers.iii.dev。运行时代码包在 github.com&#x2F;iii-hq&#x2F;workers&#x2F;harness。</p><h2 id="赌注"><a href="#赌注" class="headerlink" title="赌注"></a>赌注</h2><p>运行时不是你安装的东西。运行时是你的系统为了让一个 agent 持久化、安全、可观测地运行而必须做的一组工作。框架时代之所以将这些工作捆绑在一起，是因为底层没有任何东西给你组合它们的方式。</p><p>iii 的赌注是：一个原语——一个 worker 通过 WebSocket 连接到引擎并注册函数和触发器——小到足以分别吸收这每一项工作，并且由此产生的技术栈比任何框架都更有用，因为每一层都可以独立替换。</p><p>你不是&quot;采用&quot; iii 运行时。你安装你想要的 worker，编写你需要的 worker，最终获得一个完全匹配你系统形态的运行时。每一层相同的协议。每一次调用相同的追踪。从注册中心拿来的组件和你自行发布的组件，使用同样的 <code>iii worker add</code>。</p><p>这就是当基座形态正确时，&quot;构建你自己的 agent 运行时&quot;该有的样子。选择 worker。编写缺失的。组合。运行时就是这种组合。</p><p>加入我们，一起构建现代世界所需的完美 agent 运行时：discord.gg&#x2F;iiidev</p><p>iii 是开源的。从 iii.dev&#x2F;docs 开始。运行时 worker 在 github.com&#x2F;iii-hq&#x2F;workers，引擎在 github.com&#x2F;iii-hq&#x2F;iii。</p><p>— Mike Piccolo, 创始人兼 CEO</p>]]>
    </content>
    <id>https://colobu.com/2026/06/11/build-your-own-agent-runtime/</id>
    <link href="https://colobu.com/2026/06/11/build-your-own-agent-runtime/"/>
    <published>2026-06-11T00:10:28.000Z</published>
    <summary>
      <![CDATA[<p>2026年5月28日 · Mike Piccolo, iii 创始人兼 CEO</p>
<hr>
<img src="/2026/06/11/build-your-own-agent-runtime/image-20260602000705691.png" class="">

<p>大多数 agent 团队不构建运行时。他们采用一个。LangChain、LangGraph、OpenAI Agents SDK、Anthropic SDK、CrewAI、AutoGen——循环、工具、记忆、编排，都是作为一个单一决策从货架上挑选的。运行时是一个你 import 的框架。如果里面的什么东西不合适，你就 fork 它、跟它斗争、或者绕过它。</p>]]>
    </summary>
    <title>如何构建你自己的 Agent 运行时</title>
    <updated>2026-06-22T00:21:15.830Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="AI" scheme="https://colobu.com/categories/AI/"/>
    <category term="AI" scheme="https://colobu.com/tags/AI/"/>
    <content>
      <![CDATA[<p>你有没有过这样的经历：接手一个&quot;跑了三年没人敢动&quot;的项目，打开代码仓库一看——</p><p><code>src/</code> 下面 200 多个文件平铺在一个目录里，没有分层，没有模块边界。一个叫 <code>UserService</code> 的类 1800 行，发邮件、对接支付、状态管理全塞在里面，还挂着三个 <code>TODO</code> 标着&quot;后面要重构&quot;。业务逻辑全堆在 Service 层，Model 类只剩 getter 和 setter，贫血得像张纸。数据库查询藏在 for 循环里，每循环一次发一条 SQL。你问老员工这模块谁负责，得到一句：&quot;这个……已经没人记得了。&quot;</p><p>Martin Fowler 把这类问题叫做&quot;代码坏味道&quot;（Code Smell）。Brian Foote 和 Joseph Yoder 在 1997 年的论文里给了一个更直白的名字：Big Ball of Mud（大泥球）。</p><span id="more"></span><p>祖传的手搓代码是这样。到了 vibe coding 时代，AI 生成的代码也没好到哪去，能力差一些的模型甚至更糟糕。</p><p>闻到臭味容易，定位臭源难。更难的是知道该先处理哪个、怎么处理。</p><p>今天介绍一个叫 <code>/smell</code> 的开源 Skill，专门干这件事。</p><img src="/2026/06/11/code-smell-detector-ai-skill/image-20260528054646357.png" class=""><blockquote><p>它不是为你的新项目设计架构，而是对你已有的项目的分析架构，找到不合理的反模式的设计。</p></blockquote><h2 id="它是干什么的"><a href="#它是干什么的" class="headerlink" title="它是干什么的"></a>它是干什么的</h2><p><code>/smell</code> 是一个可以嵌入 Claude Code、Codex、Cursor 等 AI 编程 Agent 的 Skill。在项目里输入 <code>/smell</code> 或&quot;帮我找找代码里的坏味道&quot;，它会扫描代码库，从架构层面到代码层面做一轮完整检测，最后输出一份 Markdown 报告。</p><p>报告精确到文件和行号：哪个模块是 God Object，哪里有循环依赖，哪段代码是 N+1 查询，哪个类违反了单一职责。每个问题都附带代码证据和重构建议。</p><p>知识库整合了软件架构领域几十年的积累：</p><ul><li>Martin Fowler 的《重构》和《分析模式》</li><li>Robert C. Martin 的 SOLID 原则</li><li>Eric Evans 的领域驱动设计（DDD）</li><li>Foote &amp; Yoder 的 Big Ball of Mud 论文</li><li>Clean Architecture、Onion Architecture、Hexagonal Architecture 等主流架构风格</li><li>一套算法复杂度反模式检测规则</li></ul><p>相当于把一个资深架构师十几年攒下的&quot;闻臭味&quot;经验，塞进了一个命令里。</p><h2 id="八个检测维度"><a href="#八个检测维度" class="headerlink" title="八个检测维度"></a>八个检测维度</h2><img src="/2026/06/11/code-smell-detector-ai-skill/image-20260528055245726.png" class=""><h3 id="1-架构级反模式"><a href="#1-架构级反模式" class="headerlink" title="1. 架构级反模式"></a>1. 架构级反模式</h3><p>最严重的层级。Big Ball of Mud（根本没有架构）、Distributed Monolith（声称是微服务，改一个功能得同时部署五个）、Anemic Domain Model（贫血模型，逻辑全在 Service 层，Domain 对象只剩 getter&#x2F;setter）、Violated Layer Boundaries（层级穿透，领域层直接 import 基础设施层）。</p><h3 id="2-耦合问题"><a href="#2-耦合问题" class="headerlink" title="2. 耦合问题"></a>2. 耦合问题</h3><p>循环依赖（A import B，B 又 import A）、Content Coupling（直接改另一个模块的内部状态）、Common Coupling（到处用的全局变量和单例）、Stamp Coupling（传一整个大对象进去只用两个字段）。</p><h3 id="3-内聚性问题"><a href="#3-内聚性问题" class="headerlink" title="3. 内聚性问题"></a>3. 内聚性问题</h3><p>God Object（一个类 800 行、30 个公开方法，管了十件不相关的事）、Shotgun Surgery（改一个需求要动 7 个文件）、Feature Envy（一个方法调别人家方法比调自己的还多）、Data Clumps（同样的参数组合反复出现在不同函数签名里）。</p><h3 id="4-设计原则违反"><a href="#4-设计原则违反" class="headerlink" title="4. 设计原则违反"></a>4. 设计原则违反</h3><p>SOLID 原则的各种违反场景、DRY 重复代码、KISS 过度工程化、YAGNI 为假设的未来需求提前写了一堆抽象。</p><h3 id="5-代码级坏味道"><a href="#5-代码级坏味道" class="headerlink" title="5. 代码级坏味道"></a>5. 代码级坏味道</h3><p>Long Method（超过 50 行的函数）、Long Parameter List（参数超过 4 个）、Primitive Obsession（用 <code>string</code> 表示 Email、<code>int</code> 表示金额）、Magic Numbers（<code>if (status == 3)</code> 到处都是）、Dead Code（注释掉的代码块和未使用的 import）。</p><h3 id="6-测试健康度"><a href="#6-测试健康度" class="headerlink" title="6. 测试健康度"></a>6. 测试健康度</h3><p>零测试覆盖的模块、测试耦合了实现细节（测私有方法、mock 内部调用）、依赖真实 I&#x2F;O 的慢测试。</p><h3 id="7-命名质量"><a href="#7-命名质量" class="headerlink" title="7. 命名质量"></a>7. 命名质量</h3><p>满屏的 <code>Manager</code>、<code>Handler</code>、<code>Util</code>、<code>Helper</code>、<code>Service</code>——等于没起名。还有 <code>snake_case</code> 和 <code>camelCase</code> 混用、同一概念在不同地方用不同名字。</p><h3 id="8-算法复杂度热点"><a href="#8-算法复杂度热点" class="headerlink" title="8. 算法复杂度热点"></a>8. 算法复杂度热点</h3><p>这部分我觉得最有价值。很多工具只管&quot;代码丑不丑&quot;，<code>/smell</code> 还管&quot;代码跑不跑得动&quot;：</p><ul><li>N+1 查询：循环里发数据库请求，100 条数据 &#x3D; 101 次 SQL</li><li>嵌套循环 O(n²)：双层 <code>for</code>，数据量一大就原地爆炸</li><li>循环内线性扫描：<code>includes()</code> 写在循环里，本该用 Set 做 O(1) 查找</li><li>循环内排序：每次迭代都 <code>sort()</code> 一遍，K × O(n log n)</li><li>渲染路径重计算：React&#x2F;Vue 组件里 <code>.filter().map().sort()</code> 没做 memoization</li><li>选错了数据结构：该用 Map 的地方用了 Array 做 O(n) 查找</li></ul><h2 id="怎么用"><a href="#怎么用" class="headerlink" title="怎么用"></a>怎么用</h2><p>在 Claude Code 中打开项目，输入：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/smell</span><br></pre></td></tr></table></figure><img src="/2026/06/11/code-smell-detector-ai-skill/image-20260528054357646.png" class=""><p>它会先问分析范围：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">你想分析哪个范围？</span><br><span class="line">  A. 整个项目（全面但耗时）</span><br><span class="line">  B. 指定模块/目录</span><br><span class="line">  C. 仅分析最近改动的文件（git diff）</span><br><span class="line">  D. 仅分析架构级问题（跳过代码级坏味道）</span><br></pre></td></tr></table></figure><p>选完之后它会启动多个并行探索任务：扫描项目结构、分析依赖关系、检测模块内聚性、识别反模式签名、评估测试覆盖，同时跑。</p><p>分析完成后在 <code>tasks/</code> 目录下生成报告，比如 <code>tasks/smell-report-2026-05-28-1430.md</code>，终端同时打印摘要：</p><img src="/2026/06/11/code-smell-detector-ai-skill/image-20260528054454478.png" class=""><p>看到摘要你就知道该从哪下手了。可以直接让 AI 按分析结果做架构优化：</p><img src="/2026/06/11/code-smell-detector-ai-skill/image-20260528054535740.png" class=""><p>重构完成后 AI 会给出修改报告：</p><img src="/2026/06/11/code-smell-detector-ai-skill/image-20260528054613646.png" class=""><h2 id="报告长什么样"><a href="#报告长什么样" class="headerlink" title="报告长什么样"></a>报告长什么样</h2><p><strong>Executive Summary</strong>：检测到的架构风格、整体健康评估、最关键的 3-5 个问题，三段话说完。</p><p><strong>架构风格识别</strong>：分层架构？模块化单体？微服务？六边形？Clean Architecture？还是 Big Ball of Mud。用一张表对比&quot;期望 vs 现实&quot;。</p><p><strong>严重度分级清单</strong>：🔴 Critical（必须修）、🟡 Warning（应该修）、🔵 Suggestion（建议修）。</p><p><strong>逐条问题分析</strong>：类别、严重度、反模式名称、文件行号、违反的原则、代码证据、重构建议。每一条都回答三个问题：这是什么坏味道？为什么它是坏味道？怎么改？</p><p><strong>依赖图分析</strong>：模块间依赖关系、循环依赖路径、耦合热点。</p><p><strong>模块健康度记分卡</strong>：每个模块的代码行数、God Object 风险、耦合度、内聚性、测试覆盖率。</p><p><strong>Smell 分布统计</strong>：八个维度各发现多少问题，按严重度排列。</p><p><strong>重构路线图</strong>：当前 Sprint 立即行动、1-3 个月短期改善、3-12 个月长期演进。分阶段治理，不用一口气全改完。</p><h2 id="一个实际场景"><a href="#一个实际场景" class="headerlink" title="一个实际场景"></a>一个实际场景</h2><p>接手一个迭代了两年的 Go 项目，不确定架构质量，跑一遍：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/smell 分析这个 Go 项目的架构质量</span><br></pre></td></tr></table></figure><p>它发现：</p><ol><li><code>internal/model/</code> 下的结构体全是贫血模型，只有字段没有方法，逻辑全在 <code>internal/service/</code>。典型的 Anemic Domain Model，应该把行为移回领域对象。</li><li><code>internal/service/order.go:233</code> 有 N+1 查询，遍历订单时逐条查商品。改成 <code>WHERE id IN (...)</code> 批量加载就完了。</li><li><code>pkg/auth</code> 和 <code>pkg/user</code> 互相导入，循环依赖。抽取一个公共接口可以打破循环。</li><li><code>internal/handler/</code> 下 6 个文件都有大段重复的参数校验逻辑，违反 DRY。提一个公共校验中间件。</li><li>23 处魔法数字，<code>if order.Status == 3</code> 随处可见。定义个 <code>const StatusCompleted = 3</code> 或者用枚举。</li></ol><p>报告附带了分阶段的重构路线图。你按优先级一步步来就行，不用面对整个代码库发呆。</p><h2 id="快捷模式"><a href="#快捷模式" class="headerlink" title="快捷模式"></a>快捷模式</h2><p>只想看致命伤，不等全面分析：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/smell 快速检查一下架构</span><br></pre></td></tr></table></figure><p>跳过代码级和命名级检测，只跑架构反模式、循环依赖、God Object、N+1 查询这些 Critical 级别。几分钟出结果。</p><p>也可以只关注一个维度：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/smell 只分析算法复杂度热点</span><br></pre></td></tr></table></figure><p>就只找嵌套循环、N+1 查询、选错数据结构这些性能问题。</p><h2 id="为什么需要它"><a href="#为什么需要它" class="headerlink" title="为什么需要它"></a>为什么需要它</h2><p>有人会说：&quot;这些坏味道我自己也能看出来啊。&quot;</p><p>如果你有十年架构经验、Fowler 和 Uncle Bob 的书都读过、反模式论文烂熟于心，当然可以。但大多数团队不是这样。</p><p>团队里的初中级开发看不出 Anemic Domain Model 有什么问题，他们觉得&quot;Service 写逻辑、Model 放数据&quot;天经地义。即使你自己有经验，一个 10 万行的项目你也看不过来。看了三天同一套代码，嗅觉已经疲劳了。Code Review 通常只关注功能正确性和代码风格，没人在 PR Review 的时候画依赖图、算圈复杂度。</p><p><code>/smell</code> 填补的就是这个空缺。它给你证据，你自己做决策。每个问题都标了严重度，你根据优先级和资源决定修哪些。</p><h2 id="安装"><a href="#安装" class="headerlink" title="安装"></a>安装</h2><p>这个 Skill 内置在 <a href="https://github.com/smallnest/goal-workflow">goal-workflow</a> 的 skills 集合中。已配置 goal-workflow 的直接用 <code>/smell</code>。</p><p>安装命令：</p><figure class="highlight sh"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npx skills add smallnest/goal-workflow --skill smell</span><br></pre></td></tr></table></figure><p>用 Claude Code 的话，也可以直接把 <code>SKILL.md</code> 放到 <code>.claude/skills/smell/</code> 目录下，立刻生效。</p><h2 id="写在最后"><a href="#写在最后" class="headerlink" title="写在最后"></a>写在最后</h2><img src="/2026/06/11/code-smell-detector-ai-skill/image-20260528055802460.png" class=""><p>代码坏味道是一天天堆出来的。每次赶 deadline 时的妥协，每次&quot;只加一个 if&quot;的侥幸，每次 copy-paste 省下的五分钟，都在给代码库增重。</p><p>治理的难点从来不是&quot;要不要做&quot;，而是&quot;从哪开始&quot;。<code>/smell</code> 给你一张问题地图，标清楚了臭在哪、有多严重、该按什么顺序清理。路还是得你自己走。</p><p>代码是给人读的，顺便让机器执行。如果你自己都不想读自己的代码，让 AI 先帮你闻闻吧。</p>]]>
    </content>
    <id>https://colobu.com/2026/06/11/code-smell-detector-ai-skill/</id>
    <link href="https://colobu.com/2026/06/11/code-smell-detector-ai-skill/"/>
    <published>2026-06-11T00:10:28.000Z</published>
    <summary>
      <![CDATA[<p>你有没有过这样的经历：接手一个&quot;跑了三年没人敢动&quot;的项目，打开代码仓库一看——</p>
<p><code>src/</code> 下面 200 多个文件平铺在一个目录里，没有分层，没有模块边界。一个叫 <code>UserService</code> 的类 1800 行，发邮件、对接支付、状态管理全塞在里面，还挂着三个 <code>TODO</code> 标着&quot;后面要重构&quot;。业务逻辑全堆在 Service 层，Model 类只剩 getter 和 setter，贫血得像张纸。数据库查询藏在 for 循环里，每循环一次发一条 SQL。你问老员工这模块谁负责，得到一句：&quot;这个……已经没人记得了。&quot;</p>
<p>Martin Fowler 把这类问题叫做&quot;代码坏味道&quot;（Code Smell）。Brian Foote 和 Joseph Yoder 在 1997 年的论文里给了一个更直白的名字：Big Ball of Mud（大泥球）。</p>]]>
    </summary>
    <title>寻找你代码中的臭味：一个让 AI 帮你嗅出架构腐化的开源 Skill</title>
    <updated>2026-06-22T00:21:15.665Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="AI" scheme="https://colobu.com/categories/AI/"/>
    <category term="AI" scheme="https://colobu.com/tags/AI/"/>
    <content>
      <![CDATA[<p>去年我还在折腾 langchain&#x2F;langgraph 开发智能体，弄了个 langgraphgo 项目，把 langgraph 往 Go 生态圈里搬。那会儿网上做智能体的，十个有八个用 langchain&#x2F;Crew AI。</p><p>一个阶段有一个阶段的玩法。</p><p>现在我看到了另一种路子：大家直接用 Claude Code、Codex、OpenCode、Pi 这些 coding agent &quot;套壳&quot;来实现智能体。</p><p>先说两个很多人搞混的点。</p><p>别觉得这些工具只能写代码。Claude Code、Codex 的架构走的是通用智能体模式，早就不止 coding 了。</p><p>也别把&quot;套壳&quot;当贬义词。Manus 刚火那阵，就有同事撇嘴说&quot;这不就是 Claude 的套壳&quot;。但你看，Claude、Codex、Antigravity 一个个都在推 SDK，巴不得你基于它们二次开发。牛顿怎么说的，站在巨人肩膀上不丢人。</p><span id="more"></span><p>百度厂内突然蹿红了一个叫 dodo 的应用比龙虾都火。就是靠&quot;套壳&quot;快速出产品原型，再慢慢长成大家离不开的工具。百度公众号和前几天的百度大会上也推了。</p><p>OpenClaw 是在 Pi coding agent 上搭起来的智能体产品，上半年火得不成样子。</p><p>这些东西免费（CC），有的还开源（Codex CLI、OpenCode、Pi），甚至设计了 harness engineering 这套东西。等于送你一个结实的产品基座，剩下的精力全押在产品和创意上。</p><img src="/2026/06/11/go-ai-agent-orchestration-framework/image-20260603065821797.png" class=""><p>一行命令就能跑：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 流式输出（文本→stdout，元数据→stderr）</span></span><br><span class="line">agent-wrapper run --provider claude-code <span class="string">&quot;解释这段代码&quot;</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># JSON 聚合输出（CI/脚本友好）</span></span><br><span class="line">agent-wrapper run --provider codex <span class="string">&quot;fix the bug&quot;</span> --json</span><br><span class="line"></span><br><span class="line"><span class="comment"># 带审批+预算</span></span><br><span class="line">agent-wrapper run --provider claude-code <span class="string">&quot;重构本项目&quot;</span> --approve-all --budget-tokens 50000</span><br><span class="line"></span><br><span class="line"><span class="comment"># NDJSON 管道输出</span></span><br><span class="line">agent-wrapper run --provider claude-code <span class="string">&quot;hello&quot;</span> --json --stream | jq .</span><br><span class="line"></span><br><span class="line"><span class="comment"># 恢复会话</span></span><br><span class="line">agent-wrapper run --provider claude-code --session-id abc123 <span class="string">&quot;继续&quot;</span></span><br></pre></td></tr></table></figure><p>但更核心的用法是当 Go 库用。go get 一下，import 进去，调 orch.Run。零额外进程，零协议开销。Claude Code、Codex、Pi、OpenCode，五个 provider，一套接口。</p><h2 id="套壳的真正价值"><a href="#套壳的真正价值" class="headerlink" title="套壳的真正价值"></a>套壳的真正价值</h2><p>套壳不丢人。</p><p>丢人的是换一次工具重写一遍胶水代码。丢人的是用着 AI Agent 还得手工管子进程生命周期。丢人的是一晚上 API 烧了几十美刀第二天看账单才后悔。</p><p>agent-wrapper 就是个壳。但解决的不是&quot;怎么调一次 Agent&quot;，是&quot;怎么让 Agent 安全地、长期地、按预算地、跨 provider 为你工作&quot;。把&quot;玩一下&quot;变成&quot;能上线&quot;。</p><p>项目地址：<a href="https://github.com/smallnest/agent-wrapper">https://github.com/smallnest/agent-wrapper</a></p><h2 id="最后说几句"><a href="#最后说几句" class="headerlink" title="最后说几句"></a>最后说几句</h2><p>大多数人用 Agent 还停留在&quot;命令行输一个 prompt&quot;。这没什么不对，但不够。</p><p>十年前我们从手写 SQL 走到满世界 ORM，从手动部署走到 k8s 编排。每一轮变化里，有人站在第一层骂套壳，有人站在第二层埋头套壳，还有人站在第三层——让所有想套壳的人套得更快。</p><p>Agent 时代，把壳套好，就是最好的手艺。</p>]]>
    </content>
    <id>https://colobu.com/2026/06/11/go-ai-agent-orchestration-framework/</id>
    <link href="https://colobu.com/2026/06/11/go-ai-agent-orchestration-framework/"/>
    <published>2026-06-11T00:10:28.000Z</published>
    <summary>
      <![CDATA[<p>去年我还在折腾 langchain&#x2F;langgraph 开发智能体，弄了个 langgraphgo 项目，把 langgraph 往 Go 生态圈里搬。那会儿网上做智能体的，十个有八个用 langchain&#x2F;Crew AI。</p>
<p>一个阶段有一个阶段的玩法。</p>
<p>现在我看到了另一种路子：大家直接用 Claude Code、Codex、OpenCode、Pi 这些 coding agent &quot;套壳&quot;来实现智能体。</p>
<p>先说两个很多人搞混的点。</p>
<p>别觉得这些工具只能写代码。Claude Code、Codex 的架构走的是通用智能体模式，早就不止 coding 了。</p>
<p>也别把&quot;套壳&quot;当贬义词。Manus 刚火那阵，就有同事撇嘴说&quot;这不就是 Claude 的套壳&quot;。但你看，Claude、Codex、Antigravity 一个个都在推 SDK，巴不得你基于它们二次开发。牛顿怎么说的，站在巨人肩膀上不丢人。</p>]]>
    </summary>
    <title>套壳不丢人！我用Go+AI搓了一个Agent统一编排框架，ClaudeCode-Codex-Pi全被我包了</title>
    <updated>2026-06-22T00:21:15.757Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="Go" scheme="https://colobu.com/categories/Go/"/>
    <category term="Go,AI" scheme="https://colobu.com/tags/Go-AI/"/>
    <content>
      <![CDATA[<p>你是不是也曾经盯着 pprof 火焰图发呆？</p><p>线上服务 P99 延迟从 50ms 飙到 800ms，Grafana 告警刷了满屏。CPU profile 翻来覆去看了三遍，热点函数是 <code>sync.Mutex.Lock</code> —— 锁竞争。但问题出在哪？锁粒度大、false sharing、还是并发模型本身选错了？脑子里闪过一堆可能性，每一条都够排查半小时。</p><p>如果有一个 Go语言的并发专家和性能专家在身边多好。</p><p>还有，对于有经验的老Go程序员，已经习惯了历史的Go语法和代码，对于新的特性反而没有新手更了解，如何保持与时俱进而不被新人所唾弃？</p><p>现在，这个愿望变成了现实：三个skill &#x3D; 顶级有活力的Go语言专家。</p><h2 id="三个-Skill，把-Go-的三座大山装进-AI"><a href="#三个-Skill，把-Go-的三座大山装进-AI" class="headerlink" title="三个 Skill，把 Go 的三座大山装进 AI"></a>三个 Skill，把 Go 的三座大山装进 AI</h2><p>做 Go 开发这些年，我发现 Go 程序员面对的最棘手的问题，几乎都可以归为三类：</p><p><strong>第一类：并发。</strong> 死锁、数据竞争、goroutine 泄漏、channel 关闭时机不对、WaitGroup 计数配不平、锁重入导致自死锁……你写过 Go，就一定被其中至少一个坑过。</p><span id="more"></span><p><strong>第二类：性能。</strong> 内存分配太多导致 GC 压力大、逃逸分析看不懂、编译器有没有帮你做 BEC 边界检查消除、sync.Pool 到底用对了没、struct 字段排列浪费了多少内存、CPU 缓存 false sharing 在拖累你的并发吞吐……</p><p><strong>第三类：代码现代化。</strong> Go 版本在狂飙，每个版本都带来新 API 和惯用法。<code>interface{}</code> 早该换成 <code>any</code> 了。<code>for i := 0; i &lt; n; i++</code> 能写成 <code>for i := range n</code>。手写的 <code>if a &lt; b { v = a }</code> 不如直接用 <code>min(a, b)</code>。<code>sort.Slice</code> 该退休了，换成 <code>slices.SortFunc</code>。但你不可能记住 Go 1.18 到 1.26 每一个版本的每一个变更——结果就是老代码里到处是&quot;上一代&quot;的写法。</p><p>这三类问题有一个共同点：<strong>答案分散在无数本书、无数篇博客、无数个 GitHub Issue 里，排查的时候你得像侦探一样在各种资料之间跳来跳去。</strong></p><p>我当时刚出版完《Go并发编程实战》，书里把 Go 标准库 sync 包的每一个原语、官方扩展并发库（信号量、SingleFlight、ErrGroup、限流）、13+ 种并发模式、第三方并发库（CyclicBarrier、断路器、WorkerPool）、甚至基于 etcd 的分布式同步原语全部拆开讲了一遍。300 多页，够系统，但——有几个程序员会随身带着一本书写代码？</p><p>所以我把这本书&quot;灌&quot;进了一个 AI Skill：<strong>chao-go-sync</strong>。</p><p>然后又想，并发问题解决了，性能呢？于是我又把 Dave Cheney 的 High Performance Go Workshop、dgryski 的 go-perfbook、Effective Go、Go 101 Optimizations 这些 Go 性能领域的&quot;圣经&quot;级资料整合成了第二个 Skill：<strong>chao-go-perf</strong>。</p><p>再然后，项目里的老代码看着越来越扎眼。Go 从 1.0 一路进化到 1.26，每一个版本都引入了更简洁、更高性能的 API 和语法糖——但你不可能手动把所有老项目翻一遍。于是我把 Go 团队 modernize 分析(已集成到go fix命令中)、社区最佳实践、以及 28 个版本感知的转换规则集成起来，做了第三个 Skill：<strong>modern-go</strong>。它就像一个智能版的 <code>go fix</code>，但不是只修语法——修复惯用法。</p><p>三个 Skill 合在一起：<strong>一个管并发，一个管性能，一个管现代化。Go 编程的铁三角，齐了。</strong></p><p><img src="/image-20260526231058945.png"></p><h2 id="它们到底能干什么？"><a href="#它们到底能干什么？" class="headerlink" title="它们到底能干什么？"></a>它们到底能干什么？</h2><p>说的很玄，来点实际的。这三个 Skill 嵌入 AI Coding Agent 之后，你的 AI 智能体会变成这样：</p><h3 id="chao-go-sync：Go-并发编程专家"><a href="#chao-go-sync：Go-并发编程专家" class="headerlink" title="&#x2F;chao-go-sync：Go 并发编程专家"></a>&#x2F;chao-go-sync：Go 并发编程专家</h3><p><img src="/image-20260526233552644.png"></p><p>你把一段并发代码或者整个项目告诉 AI，它会自动：</p><p><strong>诊断 Bug。</strong> 代码里有潜在的数据竞争？它不会只告诉你&quot;可能有 race condition&quot;，而是会指出来——这段代码里 <code>WaitGroup.Add</code> 放在 goroutine 内部了，这是典型的竞态模式；那把锁在两个函数里的获取顺序不一致，死锁只是时间问题；你 Copy 了一个 Mutex struct，<code>go vet</code> 能检测到但你没跑。</p><p><strong>推荐方案。</strong> &quot;这里保护共享变量，读写比例大概 95:5，应该用 RWMutex 而不是 Mutex&quot;，然后它会给出改写代码。甚至能告诉你 Go 1.21 新出的 <code>OnceValue[T]</code> 比你的手写双重检查锁更干净，Go 1.25 新加的 <code>WaitGroup.Go(f)</code> 能让你的编排代码少写 3 行。</p><p><strong>覆盖的广度。</strong> 从 sync 包的全部标准原语（Mutex、RWMutex、WaitGroup、Cond、Once、Pool、sync.Map、atomic、channel、context），到官方扩展库（Semaphore、SingleFlight、ErrGroup、限流），到 13+ 种并发模式（半异步半同步、Reactor、断路器、Per-CPU 等），到基于 etcd 的分布式同步原语（Leader 选举、分布式锁、分布式队列、STM），再到哲学家就餐、理发师问题这些经典并发场景——<strong>一本书的知识，全在你的 Copilot 里。</strong></p><p>即使是沉浸Go语言开发十多年的老兵，而且还出了 Go 并发实战专栏和相关图书的程序员，我写的代码也不能保证没有并发的问题。比如我用它分析我最近开源的 goscapy 网络库项目, 我在Claude Code中输入： <code>/chao-go-sync 分析这个项目有没有并发问题，把发现的文体summary给我即可</code>,它给我了一个详细的分析报告：<br><img src="/image-20260526232642521.png" alt="image-20260526232642521.png"></p><p>结果一目了然，非常详细而且值得学习，接下来你可以根据自己的需求，让它自动修复或者你自己手搓代码。</p><h3 id="chao-go-perf：Go-性能分析专家"><a href="#chao-go-perf：Go-性能分析专家" class="headerlink" title="&#x2F;chao-go-perf：Go 性能分析专家"></a>&#x2F;chao-go-perf：Go 性能分析专家</h3><p><img src="/image-20260526233810924.png"></p><p>性能问题更难排查，因为&quot;瓶颈在哪&quot;本身就是一个需要专业知识才能回答的问题。这个 Skill 解决的就是这个：</p><p><strong>数据驱动。</strong> 不再凭直觉猜瓶颈——它教你正确写 benchmark（sink 变量防优化消除、b.ResetTimer 去预热噪音）、用 benchstat 做统计验证（p &lt; 0.05 才叫显著），然后用 pprof 精准定位 CPU 热点和内存分配热点。</p><p><strong>编译器视角。</strong> 很多人写了十年 Go，没看过一次逃逸分析输出（<code>go build -gcflags=&quot;-m&quot;</code>），不知道编译器有没有帮你做 BCE（边界检查消除），不清楚哪些函数被内联了、为什么没被内联。这个 Skill 就补这一块——它懂编译器，你能让它帮你分析。</p><p><strong>硬件友好。</strong> CPU 缓存、cache line 对齐、false sharing 消除、struct 字段排序减少 padding、分支预测友好代码——这些&quot;高阶操作&quot;在 Skill 里都是速查项。</p><p><strong>版本感知。</strong> Go 1.12 到 1.27 每个版本的关键性能变更都记录了。从 Go 1.17 的寄存器传参（5-10% 性能提升），到 Go 1.20&#x2F;1.21 的 PGO（Profile-Guided Optimization），到 Go 1.24 sync.Map 重写为 hash-trie map——你的 Copilot 知道哪个版本能给你什么免费的性能提升。</p><p>我在Claude Code中输入： <code>/chao-go-perf 本项目有性能问题吗？</code>, 它给我了一个详细的性能分析报告：<br><img src="/image-20260526233007750.png"></p><p>它会给你它发现的每一个性能问题以及详细分析，还包括改进的建议，你可以决定要不要让AI自动优化，比如你可以选择高优先级的问题进行修复。</p><h3 id="modern-go：Go-代码现代化专家"><a href="#modern-go：Go-代码现代化专家" class="headerlink" title="&#x2F;modern-go：Go 代码现代化专家"></a>&#x2F;modern-go：Go 代码现代化专家</h3><p>![告别死锁和陈旧语法、告别性能瓶颈：三个开源 Skill，新手Gopher 秒变 Go 语言大神__assets&#x2F;image-20260527000517831.png](告别死锁和陈旧语法、告别性能瓶颈：三个开源 Skill，新手Gopher 秒变 Go 语言大神__assets&#x2F;image-20260527000517831.png)</p><p>这是三个 Skill 里最&quot;务实&quot;的一个。它不跟你讲原理，直接动手。</p><p>和<code>go fix</code>类似又有点不一样，它会根据你的<code>go.mod</code>选择的Go版本，将Go现代语法优化到这个版本，并提供详细的分析报告。</p><p>把项目交给它，它会自动：</p><p><strong>扫描 go.mod，确定目标版本。</strong> 你的项目声明的 Go 版本是多少，它就应用多少版本能用的转换。Go 1.21 的项目不会收到 Go 1.22+ 的规则——绝对不会引入兼容性问题。</p><p><strong>按版本从旧到新，逐条应用 28 种转换。</strong> <code>time.Now().Sub(start)</code> → <code>time.Since(start)</code>（Go 1.0）；<code>interface{}</code> → <code>any</code>（Go 1.18）；<code>for i := 0; i &lt; n; i++</code> → <code>for i := range n</code>（Go 1.22）；<code>b.N</code> 循环 → <code>b.Loop()</code>（Go 1.24）；<code>sync.WaitGroup.Add + go func</code> → <code>wg.Go(f)</code>（Go 1.25）；<code>errors.As</code> + 临时变量 → <code>errors.AsType</code> 直接表达式（Go 1.26）……覆盖了 Go 1.0 到 1.26 每一个有意义的惯用法升级。</p><p><strong>改完自动跑 goimports。</strong> 新增的 import（如 <code>slices</code>、<code>maps</code>、<code>cmp</code>）自动补齐，移除的 import 自动清理。代码不会因为改完而编译不过。</p><p><strong>输出清晰总结。</strong> 哪个文件改了什么转换、总共改了多少处、哪些转换被跳过（因为版本不够）。一目了然。</p><p>举个真实例子。这是我前段时间写的一个命令行“斗地主”扑克牌游戏，我在Claude Code中输入<code>/modern-go 本项目</code>, 它给了![告别死锁和陈旧语法、告别性能瓶颈：三个开源 Skill，新手Gopher 秒变 Go 语言大神__assets&#x2F;image-20260527002012982.png](告别死锁和陈旧语法、告别性能瓶颈：三个开源 Skill，新手Gopher 秒变 Go 语言大神__assets&#x2F;image-20260527002012982.png)2<img src="/%E5%91%8A%E5%88%AB%E6%AD%BB%E9%94%81%E5%92%8C%E9%99%88%E6%97%A7%E8%AF%AD%E6%B3%95%E3%80%81%E5%91%8A%E5%88%AB%E6%80%A7%E8%83%BD%E7%93%B6%E9%A2%88%EF%BC%9A%E4%B8%89%E4%B8%AA%E5%BC%80%E6%BA%90%20Skill%EF%BC%8C%E6%96%B0%E6%89%8BGopher%20%E7%A7%92%E5%8F%98%20Go%20%E8%AF%AD%E8%A8%80%E5%A4%A7%E7%A5%9E__assets/image-20260527004353285.png">53285.png)</p><p>它不但找出了陈旧的Go写法，还帮我更新到go.mod中定义的Go版本新语法。</p><h2 id="安装只需一行命令"><a href="#安装只需一行命令" class="headerlink" title="安装只需一行命令"></a>安装只需一行命令</h2><p>三个 Skill 都是 MIT 开源协议，完全免费。如果你用的是 Claude Code 或者支持 Skills 规范的 AI Coding Agent：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># Go 并发编程专家</span></span><br><span class="line">npx skills add smallnest/chao-go-sync</span><br><span class="line"></span><br><span class="line"><span class="comment"># Go 性能分析专家</span></span><br><span class="line">npx skills add smallnest/chao-go-perf</span><br><span class="line"></span><br><span class="line"><span class="comment"># Go 代码现代化专家</span></span><br><span class="line">npx skills add smallnest/goal-workflow --skill modern-go</span><br></pre></td></tr></table></figure><p>装完之后，不需要手动触发——当你的代码或对话涉及 Go 并发、性能或现代化相关话题时，对应的 Skill 会自动激活。你也可以手动调用：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">/chao-go-sync   # 提一个并发问题</span><br><span class="line">/chao-go-perf   # 分析一段代码的性能</span><br><span class="line">/modern-go      # 现代化你的 Go 代码</span><br></pre></td></tr></table></figure><hr><p>三个skill的GitHub 地址：</p><ul><li>chao-go-sync: <a href="https://github.com/smallnest/chao-go-sync">https://github.com/smallnest/chao-go-sync</a></li><li>chao-go-perf: <a href="https://github.com/smallnest/chao-go-perf">https://github.com/smallnest/chao-go-perf</a></li><li>modern-go: <a href="https://github.com/smallnest/modern-go">https://github.com/smallnest/modern-go</a></li></ul>]]>
    </content>
    <id>https://colobu.com/2026/06/11/three-open-source-skills-gopher-master/</id>
    <link href="https://colobu.com/2026/06/11/three-open-source-skills-gopher-master/"/>
    <published>2026-06-11T00:10:28.000Z</published>
    <summary>
      <![CDATA[<p>你是不是也曾经盯着 pprof 火焰图发呆？</p>
<p>线上服务 P99 延迟从 50ms 飙到 800ms，Grafana 告警刷了满屏。CPU profile 翻来覆去看了三遍，热点函数是 <code>sync.Mutex.Lock</code> —— 锁竞争。但问题出在哪？锁粒度大、false sharing、还是并发模型本身选错了？脑子里闪过一堆可能性，每一条都够排查半小时。</p>
<p>如果有一个 Go语言的并发专家和性能专家在身边多好。</p>
<p>还有，对于有经验的老Go程序员，已经习惯了历史的Go语法和代码，对于新的特性反而没有新手更了解，如何保持与时俱进而不被新人所唾弃？</p>
<p>现在，这个愿望变成了现实：三个skill &#x3D; 顶级有活力的Go语言专家。</p>
<h2 id="三个-Skill，把-Go-的三座大山装进-AI"><a href="#三个-Skill，把-Go-的三座大山装进-AI" class="headerlink" title="三个 Skill，把 Go 的三座大山装进 AI"></a>三个 Skill，把 Go 的三座大山装进 AI</h2><p>做 Go 开发这些年，我发现 Go 程序员面对的最棘手的问题，几乎都可以归为三类：</p>
<p><strong>第一类：并发。</strong> 死锁、数据竞争、goroutine 泄漏、channel 关闭时机不对、WaitGroup 计数配不平、锁重入导致自死锁……你写过 Go，就一定被其中至少一个坑过。</p>]]>
    </summary>
    <title>告别死锁和陈旧语法、告别性能瓶颈：三个开源 Skill，新手Gopher 秒变 Go 语言大神</title>
    <updated>2026-06-22T00:21:15.760Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category scheme="https://colobu.com/tags/undefined/"/>
    <content>
      <![CDATA[<p>PRD（产品需求文档）和 SPEC（设计&#x2F;技术规格说明书）是软件及硬件产品开发中两个不同阶段的核心文档，其核心区别在于：PRD 解释“做什么以及为什么做”（What &amp; Why），而 SPEC 解释“怎么做以及做成什么样”（How &amp; What exactly）。 </p><h2 id="核心区别概览"><a href="#核心区别概览" class="headerlink" title="核心区别概览"></a>核心区别概览</h2><table><thead><tr><th>维度</th><th>PRD (Product Requirement Document)</th><th>SPEC (Specification)</th></tr></thead><tbody><tr><td>中文名称</td><td>产品需求文档</td><td>规格说明书（产品&#x2F;技术&#x2F;功能规格）</td></tr><tr><td>核心回答</td><td>解决什么用户痛点？产品要实现哪些功能？</td><td>系统如何实现？输入输出的标准和边界是什么？</td></tr><tr><td>主要撰写者</td><td>产品经理 (PM) &#x2F; 产品负责人 (PO)</td><td>架构师 &#x2F; 技术主管 (Tech Lead) &#x2F; 资深工程师</td></tr></tbody></table><span id="more"></span><p>| 主要读者  | 设计师、开发人员、测试人员、业务方                  | 工程师、QA测试工程师、系统集成商              |<br>| 视角侧重  | 用户与商业视角（关注用户体验和业务逻辑）               | 技术与实现视角（关注工程可行性与边界）            |</p><hr><h2 id="1-PRD：阐述产品需求与业务逻辑"><a href="#1-PRD：阐述产品需求与业务逻辑" class="headerlink" title="1. PRD：阐述产品需求与业务逻辑"></a>1. PRD：阐述产品需求与业务逻辑</h2><p>PRD 是产品从概念走向现实的“第一张图纸”。它由产品经理主导，将商业需求（BRD）和市场需求（MRD）转化为具体的全景功能描述。 </p><ul><li>主要内容：</li><li>背景与目标：为什么要开发这个功能？期望达成什么业务指标？<ul><li>用户故事与场景：用户在什么情况下会使用这个功能？</li><li>功能列表 (Features)：需要包含哪些功能模块（如：登录、支付、分享）。</li><li>信息架构与流程图：页面的基本流转逻辑和用户操作主路径。</li></ul></li></ul><h2 id="2-SPEC：定义具体的执行标准与实现技术"><a href="#2-SPEC：定义具体的执行标准与实现技术" class="headerlink" title="2. SPEC：定义具体的执行标准与实现技术"></a>2. SPEC：定义具体的执行标准与实现技术</h2><p>SPEC 是指导研发人员写代码或生产硬件的“施工图纸”。它对 PRD 中的功能进行技术视角的精细化拆解，将抽象的需求落地为结构化的标准。</p><ul><li>主要类型：</li><li>Functional Spec (功能规格)：由产品或交互设计师撰写，极度细化到每个按钮的交互状态（如置灰、点击反馈）、极限字符限制等。<ul><li>Technical Spec (技术规格)：由工程师或 Tech Lead 撰写，包含数据库设计、接口 API 定义（输入&#x2F;输出）、系统架构设计等。</li></ul></li><li>重要趋势：在当前的开发环境中，尤其是随着 AI 的引入，行业正从“看 PRD 写代码”转向 <a href="https://zhuanlan.zhihu.com/p/2014956602726294447">规范驱动开发 (Spec-Driven Development, SDD)</a>。因为 AI 无法直接理解模糊的 PRD，但能高效执行高度结构化、包含边界条件与评估标准的 SPEC 文档。</li></ul><hr><h2 id="3-用一个“建造房屋”的例子来理解"><a href="#3-用一个“建造房屋”的例子来理解" class="headerlink" title="3. 用一个“建造房屋”的例子来理解"></a>3. 用一个“建造房屋”的例子来理解</h2><ul><li>PRD 就像“业主的需求清单”：</li></ul><p>“我需要一栋两层高的别墅。一楼要有一个大客厅用来招待朋友，二楼要有两间卧室。整体风格要温馨，预算控制在 50 万以内。”<br>(只定义了需求、目的和大概的功能区间)</p><ul><li>SPEC 就像“建筑工程师的施工图纸”：</li></ul><p>“一楼客厅面积为 45 平方米，承重墙使用 C30 混凝土，厚度 240mm；电线采用 4 平方毫米铜芯线；上下水管接口直径为 50mm；预留插座距离地面 30cm。”<br>(精确定义了尺寸、材料、接口和具体的实现指标)</p><p>在实际的敏捷开发流程中，通常先有 PRD 进行需求评审，大家达成共识后，技术团队再产出 SPEC 开始进入真正的编码和测试阶段。<br>您目前是在梳理业务阶段还是已经进入技术设计阶段了呢？如果需要，我可以为您提供一份标准的 PRD 模板大纲 或是 Tech Spec（技术设计文档）的框架。</p>]]>
    </content>
    <id>https://colobu.com/2026/05/24/spec-vs-prd-difference/</id>
    <link href="https://colobu.com/2026/05/24/spec-vs-prd-difference/"/>
    <published>2026-05-24T07:51:34.000Z</published>
    <summary>
      <![CDATA[<p>PRD（产品需求文档）和 SPEC（设计&#x2F;技术规格说明书）是软件及硬件产品开发中两个不同阶段的核心文档，其核心区别在于：PRD 解释“做什么以及为什么做”（What &amp; Why），而 SPEC 解释“怎么做以及做成什么样”（How &amp; What exactly）。 </p>
<h2 id="核心区别概览"><a href="#核心区别概览" class="headerlink" title="核心区别概览"></a>核心区别概览</h2><table>
<thead>
<tr>
<th>维度</th>
<th>PRD (Product Requirement Document)</th>
<th>SPEC (Specification)</th>
</tr>
</thead>
<tbody><tr>
<td>中文名称</td>
<td>产品需求文档</td>
<td>规格说明书（产品&#x2F;技术&#x2F;功能规格）</td>
</tr>
<tr>
<td>核心回答</td>
<td>解决什么用户痛点？产品要实现哪些功能？</td>
<td>系统如何实现？输入输出的标准和边界是什么？</td>
</tr>
<tr>
<td>主要撰写者</td>
<td>产品经理 (PM) &#x2F; 产品负责人 (PO)</td>
<td>架构师 &#x2F; 技术主管 (Tech Lead) &#x2F; 资深工程师</td>
</tr>
</tbody></table>]]>
    </summary>
    <title>SPEC和PRD的区别</title>
    <updated>2026-06-22T00:21:15.884Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category scheme="https://colobu.com/tags/undefined/"/>
    <content>
      <![CDATA[<h2 id="在软件研发流程中，方案设计（Design-Doc-Architecture-Proposal）-和-SPEC（规格说明书，特指-Tech-Spec）-经常被混用，但它们在阶段、目的以及确定性上有明显的区别。简单来说：方案设计是“开放式的论证题”（探讨可能性），而-SPEC-是“闭环式的施工图”（定义最终标准）。"><a href="#在软件研发流程中，方案设计（Design-Doc-Architecture-Proposal）-和-SPEC（规格说明书，特指-Tech-Spec）-经常被混用，但它们在阶段、目的以及确定性上有明显的区别。简单来说：方案设计是“开放式的论证题”（探讨可能性），而-SPEC-是“闭环式的施工图”（定义最终标准）。" class="headerlink" title="在软件研发流程中，方案设计（Design Doc &#x2F; Architecture Proposal） 和 SPEC（规格说明书，特指 Tech Spec） 经常被混用，但它们在阶段、目的以及确定性上有明显的区别。简单来说：方案设计是“开放式的论证题”（探讨可能性），而 SPEC 是“闭环式的施工图”（定义最终标准）。"></a>在软件研发流程中，方案设计（Design Doc &#x2F; Architecture Proposal） 和 SPEC（规格说明书，特指 Tech Spec） 经常被混用，但它们在阶段、目的以及确定性上有明显的区别。<br>简单来说：方案设计是“开放式的论证题”（探讨可能性），而 SPEC 是“闭环式的施工图”（定义最终标准）。</h2><h2 id="核心区别概览"><a href="#核心区别概览" class="headerlink" title="核心区别概览"></a>核心区别概览</h2><table><thead><tr><th>维度</th><th>方案设计 (Design Doc &#x2F; Proposal)</th><th>SPEC (Specification)</th></tr></thead><tbody><tr><td>所处阶段</td><td>技术调研与评审阶段（写代码前）</td><td>最终确认与开发阶段（写代码时）</td></tr><tr><td>核心目的</td><td>寻找解法，权衡利弊（Trade-offs）</td><td>统一标准，指导施工（作为交付契约）</td></tr><tr><td>内容特点</td><td>包含多种备选方案（方案A vs 方案B）</td><td>只有一种确定的、极度详细的最终方案</td></tr><tr><td>状态变化</td><td>动态的，讨论后会被修改或推翻</td><td>静态的，通过评审后作为基线，轻易不改</td></tr></tbody></table><span id="more"></span><p>| 回答的问题 | 为什么要用这个架构？怎么解决这个难题？ | 接口长什么样？表结构怎么建？边界在哪？ |</p><hr><h2 id="1-方案设计：重在“权衡与推演”"><a href="#1-方案设计：重在“权衡与推演”" class="headerlink" title="1. 方案设计：重在“权衡与推演”"></a>1. 方案设计：重在“权衡与推演”</h2><p>方案设计通常由高级工程师（Senior Engineer）或架构师撰写，发生在大规模编码之前。它的重点是逻辑推演和技术选型。</p><ul><li>它的核心是 Trade-offs（取舍）：方案设计一定会写：“为了解决高并发，我们有方案一（加缓存）和方案二（分库分表）。方案一成本低但有延迟，方案二改动大但彻底，我们最终选择方案一，理由是……”</li><li>它的受众是评委：方案设计是拿来给团队评审（Review）和争论的，目的是暴露盲点，达成共识。</li></ul><h2 id="2-SPEC：重在“精准与边界”"><a href="#2-SPEC：重在“精准与边界”" class="headerlink" title="2. SPEC：重在“精准与边界”"></a>2. SPEC：重在“精准与边界”</h2><p>当方案设计通过评审、定下结论后，负责人需要将结论实例化、细节化，这就变成了 SPEC。</p><ul><li>它是开发和测试的契约：SPEC 里不再有“备选方案”，也不再解释“为什么要这么做”，而是直接给出死命令。</li><li>它的受众是执行者：</li><li>前端&#x2F;下游开发：直接看 SPEC 里的 API 接口定义，不等后端写完代码，就可以根据 SPEC 规定的 JSON 格式开始写前端。<ul><li>测试 (QA)：直接根据 SPEC 里的字段限制、接口返回码（如 200, 400, 500）和边界条件去写测试用例。</li></ul></li></ul><hr><h2 id="3-用“造桥”的例子一目了然"><a href="#3-用“造桥”的例子一目了然" class="headerlink" title="3. 用“造桥”的例子一目了然"></a>3. 用“造桥”的例子一目了然</h2><ul><li>方案设计（桥梁设计提案）：</li></ul><p>“为了连接两岸，我们可以建悬索桥或者斜拉桥。悬索桥跨度大但造价高，斜拉桥省钱但对地质要求高。经过勘测，这里地质很好，所以我们决定采用斜拉桥方案。预计使用钢材X吨，工期6个月。”<br>(侧重：可行性分析、选型理由、宏观架构)</p><ul><li>SPEC（桥梁施工规格书）：</li></ul><p>“主斜拉索采用直径 70mm 的高强钢丝束；1号桥墩高度为 45.2 米，使用 C50 混凝土；桥面柏油厚度为 10cm；排水孔每隔 5 米设置一个，直径 15cm。”<br>(侧重：精准参数、接口尺寸、直接指导施工)</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>在实际工作中，很多团队为了追求敏捷，会把两者合并为一份文档：前半部分写方案调研与架构选型（方案设计），后半部分写具体的接口设计和表结构（SPEC）。</p><p>您目前手头的工作是处于需要向团队&#x2F;老板证明技术可行性的阶段（更需要方案设计），还是已经达成共识、需要给前后端和测试定对接标准的阶段（更需要 SPEC）呢？</p>]]>
    </content>
    <id>https://colobu.com/2026/05/24/spec-vs-solution-design-difference/</id>
    <link href="https://colobu.com/2026/05/24/spec-vs-solution-design-difference/"/>
    <published>2026-05-24T07:51:34.000Z</published>
    <summary>
      <![CDATA[<h2 id="在软件研发流程中，方案设计（Design-Doc-Architecture-Proposal）-和-SPEC（规格说明书，特指-Tech-Spec）-经常被混用，但它们在阶段、目的以及确定性上有明显的区别。简单来说：方案设计是“开放式的论证题”（探讨可能性），而-SPEC-是“闭环式的施工图”（定义最终标准）。"><a href="#在软件研发流程中，方案设计（Design-Doc-Architecture-Proposal）-和-SPEC（规格说明书，特指-Tech-Spec）-经常被混用，但它们在阶段、目的以及确定性上有明显的区别。简单来说：方案设计是“开放式的论证题”（探讨可能性），而-SPEC-是“闭环式的施工图”（定义最终标准）。" class="headerlink" title="在软件研发流程中，方案设计（Design Doc &#x2F; Architecture Proposal） 和 SPEC（规格说明书，特指 Tech Spec） 经常被混用，但它们在阶段、目的以及确定性上有明显的区别。简单来说：方案设计是“开放式的论证题”（探讨可能性），而 SPEC 是“闭环式的施工图”（定义最终标准）。"></a>在软件研发流程中，方案设计（Design Doc &#x2F; Architecture Proposal） 和 SPEC（规格说明书，特指 Tech Spec） 经常被混用，但它们在阶段、目的以及确定性上有明显的区别。<br>简单来说：方案设计是“开放式的论证题”（探讨可能性），而 SPEC 是“闭环式的施工图”（定义最终标准）。</h2><h2 id="核心区别概览"><a href="#核心区别概览" class="headerlink" title="核心区别概览"></a>核心区别概览</h2><table>
<thead>
<tr>
<th>维度</th>
<th>方案设计 (Design Doc &#x2F; Proposal)</th>
<th>SPEC (Specification)</th>
</tr>
</thead>
<tbody><tr>
<td>所处阶段</td>
<td>技术调研与评审阶段（写代码前）</td>
<td>最终确认与开发阶段（写代码时）</td>
</tr>
<tr>
<td>核心目的</td>
<td>寻找解法，权衡利弊（Trade-offs）</td>
<td>统一标准，指导施工（作为交付契约）</td>
</tr>
<tr>
<td>内容特点</td>
<td>包含多种备选方案（方案A vs 方案B）</td>
<td>只有一种确定的、极度详细的最终方案</td>
</tr>
<tr>
<td>状态变化</td>
<td>动态的，讨论后会被修改或推翻</td>
<td>静态的，通过评审后作为基线，轻易不改</td>
</tr>
</tbody></table>]]>
    </summary>
    <title>SPEC和方案设计有什么区别</title>
    <updated>2026-06-22T00:21:15.640Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category term="AI" scheme="https://colobu.com/categories/AI/"/>
    <category term="AI" scheme="https://colobu.com/tags/AI/"/>
    <content>
      <![CDATA[<hr><h2 id="title-从需求到上线，让-AI-管理你的整个研发流程-author-smallnest-publish-date-2026-05-17-summary-介绍-goal-workflow：AI-驱动的端到端研发工作流，覆盖-PRD-生成、需求拆解、代码审查、自动提交等全流程自动化"><a href="#title-从需求到上线，让-AI-管理你的整个研发流程-author-smallnest-publish-date-2026-05-17-summary-介绍-goal-workflow：AI-驱动的端到端研发工作流，覆盖-PRD-生成、需求拆解、代码审查、自动提交等全流程自动化" class="headerlink" title="title: &quot;从需求到上线，让 AI 管理你的整个研发流程&quot;author: &quot;smallnest&quot;publish_date: &quot;2026-05-17&quot;summary: &quot;介绍 goal-workflow：AI 驱动的端到端研发工作流，覆盖 PRD 生成、需求拆解、代码审查、自动提交等全流程自动化&quot;"></a>title: &quot;从需求到上线，让 AI 管理你的整个研发流程&quot;<br>author: &quot;smallnest&quot;<br>publish_date: &quot;2026-05-17&quot;<br>summary: &quot;介绍 goal-workflow：AI 驱动的端到端研发工作流，覆盖 PRD 生成、需求拆解、代码审查、自动提交等全流程自动化&quot;</h2><p>你是否曾经有过这样的经历：</p><ul><li><p>写了一篇 PRD，结果开发实现的时候完全跑偏</p></li><li><p>实现完代码后，发现还有一堆体力活要做：代码审查、写 commit、创建 PR、等待 CI 检查...</p></li><li><p>团队成员对同一个需求理解不一致，导致返工</p></li><li><p>多次开会同步需求进度，但最终代码还是和预期不一样</p></li><li><p>Issue 拆得太细或太粗，开发时常卡住不知道下一步该做什么</p></li><li><p>每次提交都要重新敲一遍规范的 commit message，累死了</p></li></ul><span id="more"></span><p>如果你也遇到过这些问题，不妨来了解一下 <strong>goal-workflow</strong>。这是一套 AI 驱动的研发工作流系统，覆盖从需求分析到代码交付的完整生命周期。</p><h2 id="什么是-goal-workflow？"><a href="#什么是-goal-workflow？" class="headerlink" title="什么是 goal-workflow？"></a>什么是 goal-workflow？</h2><p>goal-workflow 是一套专为 Claude Code 和其他 AI 代码编辑器设计的工作流工具集。它通过一系列 Skill（技能）模块，将产品规划、代码实现、代码审查、代码提交、文档优化等环节串联起来。</p><p>简单来说，它解决的是 <strong>&quot;从想法到发布，中间所有的繁琐工作&quot;</strong> 。</p><h2 id="四步闭环：从需求到交付"><a href="#四步闭环：从需求到交付" class="headerlink" title="四步闭环：从需求到交付"></a>四步闭环：从需求到交付</h2><p>goal-workflow 将软件开发拆分为四个标准化步骤，每一步由一个专属 Skill 驱动：</p><table><thead><tr><th>步骤</th><th>命令</th><th>输入</th><th>输出</th></tr></thead><tbody><tr><td>1. 规划</td><td><code>/prd</code></td><td>功能描述 &#x2F; 产品想法</td><td>PRD 文档 + Issue 卡片</td></tr><tr><td>2. 实现</td><td><code>/goal</code></td><td>一个 Issue 卡片</td><td>可运行的代码实现</td></tr><tr><td>3. 审查</td><td><code>/review-it</code></td><td>代码变更</td><td>通过审查的干净代码</td></tr><tr><td>4. 交付</td><td><code>/ship-it</code></td><td>已审查的代码</td><td>已合入的 PR + 已关闭的 Issue</td></tr></tbody></table><h3 id="Step-1：-prd-—-需求规划"><a href="#Step-1：-prd-—-需求规划" class="headerlink" title="Step 1：/prd — 需求规划"></a>Step 1：<code>/prd</code> — 需求规划</h3><p>传统的方式是先写文档，再开会评审需求，再拆解任务。在 goal-workflow 中，你只需要描述一个想要做的功能，比如：&quot;给任务管理系统加一个优先级功能&quot;，然后系统会自动提出 3-5 个关键澄清问题（带 A&#x2F;B&#x2F;C&#x2F;D 选项）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">1. 这个优先级功能主要解决什么问题？</span><br><span class="line">   A. 帮助团队聚焦重要任务</span><br><span class="line">   B. 自动排序任务列表</span><br><span class="line">   C. 支持紧急任务通知</span><br><span class="line">   D. 区分长期和短期任务</span><br></pre></td></tr></table></figure><p>你只需要回答&quot;1A, 2C, 3B&quot;这样的格式，系统就能生成结构化的 PRD 和对应的 Issue 列表。每个 Issue 都是独立可实施的，还会标注依赖关系：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">📋 Generated 4 Issues from PRD:</span><br><span class="line">#1: Add priority field to database (backend, high)</span><br><span class="line">#2: Display priority indicator (frontend, high) — depends on #1</span><br><span class="line">#3: Add priority selector (frontend, medium) — depends on #1</span><br><span class="line">#4: Filter tasks by priority (frontend, medium) — depends on #1, #2</span><br></pre></td></tr></table></figure><p>支持 GitHub Issues、本地 Markdown 文件和百度 iCafe 多种创建模式。</p><h3 id="Step-2：-goal-—-功能实现"><a href="#Step-2：-goal-—-功能实现" class="headerlink" title="Step 2：/goal — 功能实现"></a>Step 2：<code>/goal</code> — 功能实现</h3><p>选择一个 Issue 卡片，Agent 会理解验收标准并端到端实现：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">/goal #42          # 指定 GitHub Issue 编号</span><br><span class="line">/goal tasks/issue-001.md  # 指定本地 Issue 文件</span><br></pre></td></tr></table></figure><p>Agent 会自动读取 Issue 描述、分析代码库结构、编写实现代码、运行测试，逐条确认验收标准是否满足。一个 Issue，一次会话，完成一个功能。</p><h3 id="Step-3：-review-it-—-代码审查"><a href="#Step-3：-review-it-—-代码审查" class="headerlink" title="Step 3：/review-it — 代码审查"></a>Step 3：<code>/review-it</code> — 代码审查</h3><p>提交前自动审查代码，发现潜在问题并迭代修复。<code>/review-it</code> 的审查原则很有意思——它不盲从：</p><ul><li><strong>验证后执行</strong>：每个发现都通过读取真实代码验证，而非盲目应用</li><li><strong>拒绝噪音</strong>：拒绝不切实际的边界情况、投机性风险、过度重构</li><li><strong>迭代修复</strong>：修复后重新审查，直到无可操作发现</li><li><strong>最小变更</strong>：优先小修复，不做不必要的大重构</li></ul><p>它会自动检测你的工作区状态（未提交变更、分支差异等），选择合适的审查模式。</p><h3 id="Step-4：-ship-it-—-提交交付"><a href="#Step-4：-ship-it-—-提交交付" class="headerlink" title="Step 4：/ship-it — 提交交付"></a>Step 4：<code>/ship-it</code> — 提交交付</h3><p>代码审查通过后，<code>/ship-it</code> 一键完成收尾流程：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git commit → git push → gh pr create → gh pr merge → gh issue close</span><br></pre></td></tr></table></figure><p>具体来说：</p><ul><li>提交代码，commit message 自动关联 Issue 编号</li><li>推送分支并创建 PR（PR body 包含 <code>Closes #N</code>）</li><li>Squash merge 合入，自动删除远程分支</li><li>Issue 自动关闭（或手动关闭），添加实现总结</li></ul><p>遇到 CI 失败、合并冲突等常见问题也有对应的处理策略。</p><p>整个过程，四步闭环，除了写代码之外，几乎所有其他工作都是自动化的。</p><h2 id="技术特色"><a href="#技术特色" class="headerlink" title="技术特色"></a>技术特色</h2><ol><li><strong>端到端闭环</strong>：PRD → Issue → 实现 → 审查 → 提交 → 优化</li><li><strong>多工具兼容</strong>：支持 Claude Code、Codex、OpenCode、DeepSeek TUI</li><li><strong>双语支持</strong>：中英文触发词，适合中文开发者</li><li><strong>可信审查</strong>：验证后执行，而非盲目应用</li><li><strong>灵活部署</strong>：GitHub Issues、本地 Markdown、百度 iCafe 多平台支持</li></ol><h2 id="快速开始"><a href="#快速开始" class="headerlink" title="快速开始"></a>快速开始</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 全局安装</span></span><br><span class="line">npx skills add smallnest/goal-workflow -g</span><br><span class="line"></span><br><span class="line"><span class="comment"># 在项目中使用</span></span><br><span class="line">/prd 创建需求文档</span><br><span class="line">/goal 开始实现</span><br><span class="line">/review-it 代码审查</span><br><span class="line">/ship-it 提交发布</span><br></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>goal-workflow 的核心理念是：<strong>让 AI 做它最擅长的事情，人专注于创造性工作</strong>。</p><p>它不是要取代开发者，而是成为你的研发助理，处理那些重复、规则、耗时的工作，让你可以专注于真正有价值的事情。</p><p>如果你是个人开发者，可以用它加速 MVP 的开发；如果你是团队成员，可以用它标准化工作流程；如果你是开源项目维护者，可以用它自动化 Issue 处理。</p><hr><p><strong>项目地址</strong>：<a href="https://github.com/smallnest/goal-workflow">GitHub - smallnest&#x2F;goal-workflow</a></p><p><strong>安装</strong>：<code>npx skills add smallnest/goal-workflow</code></p><p>欢迎尝试，如果觉得有用，请给个 Star 支持一下。</p>]]>
    </content>
    <id>https://colobu.com/2026/05/20/ai-manage-entire-rd-process/</id>
    <link href="https://colobu.com/2026/05/20/ai-manage-entire-rd-process/"/>
    <published>2026-05-20T00:50:16.000Z</published>
    <summary>
      <![CDATA[<hr>
<h2 id="title-从需求到上线，让-AI-管理你的整个研发流程-author-smallnest-publish-date-2026-05-17-summary-介绍-goal-workflow：AI-驱动的端到端研发工作流，覆盖-PRD-生成、需求拆解、代码审查、自动提交等全流程自动化"><a href="#title-从需求到上线，让-AI-管理你的整个研发流程-author-smallnest-publish-date-2026-05-17-summary-介绍-goal-workflow：AI-驱动的端到端研发工作流，覆盖-PRD-生成、需求拆解、代码审查、自动提交等全流程自动化" class="headerlink" title="title: &quot;从需求到上线，让 AI 管理你的整个研发流程&quot;author: &quot;smallnest&quot;publish_date: &quot;2026-05-17&quot;summary: &quot;介绍 goal-workflow：AI 驱动的端到端研发工作流，覆盖 PRD 生成、需求拆解、代码审查、自动提交等全流程自动化&quot;"></a>title: &quot;从需求到上线，让 AI 管理你的整个研发流程&quot;<br>author: &quot;smallnest&quot;<br>publish_date: &quot;2026-05-17&quot;<br>summary: &quot;介绍 goal-workflow：AI 驱动的端到端研发工作流，覆盖 PRD 生成、需求拆解、代码审查、自动提交等全流程自动化&quot;</h2><p>你是否曾经有过这样的经历：</p>
<ul>
<li><p>写了一篇 PRD，结果开发实现的时候完全跑偏</p>
</li>
<li><p>实现完代码后，发现还有一堆体力活要做：代码审查、写 commit、创建 PR、等待 CI 检查...</p>
</li>
<li><p>团队成员对同一个需求理解不一致，导致返工</p>
</li>
<li><p>多次开会同步需求进度，但最终代码还是和预期不一样</p>
</li>
<li><p>Issue 拆得太细或太粗，开发时常卡住不知道下一步该做什么</p>
</li>
<li><p>每次提交都要重新敲一遍规范的 commit message，累死了</p>
</li>
</ul>]]>
    </summary>
    <title>从需求到上线，让 AI 管理你的整个研发流程！</title>
    <updated>2026-06-22T00:21:15.973Z</updated>
  </entry>
  <entry>
    <author>
      <name>smallnest</name>
    </author>
    <category scheme="https://colobu.com/tags/undefined/"/>
    <content>
      <![CDATA[<h2 id="Gemini-CLI-正式谢幕，Google-Antigravity-CLI-接棒登场"><a href="#Gemini-CLI-正式谢幕，Google-Antigravity-CLI-接棒登场" class="headerlink" title="Gemini CLI 正式谢幕，Google Antigravity CLI 接棒登场"></a>Gemini CLI 正式谢幕，Google Antigravity CLI 接棒登场</h2><p>2026 年 5 月 20 日，Google 在 I&#x2F;O 大会上发布了一连串重磅产品更新。其中最让我兴奋的，不是那个桌面版的 Antigravity 2.0，而是那个安安静静躺在终端里的 <strong>Antigravity CLI</strong>。</p><p>为什么？因为它正式宣告了 Gemini CLI 时代的结束——也宣告了 Google 在 AI 编程终端战场上的全新布局。</p><img src="/2026/05/20/antigravity-cli/image-20260520001947247.png" class=""><hr><h2 id="一、背景：从-Gemini-CLI-到-Antigravity-CLI"><a href="#一、背景：从-Gemini-CLI-到-Antigravity-CLI" class="headerlink" title="一、背景：从 Gemini CLI 到 Antigravity CLI"></a>一、背景：从 Gemini CLI 到 Antigravity CLI</h2><p>很多人对 Gemini CLI 并不陌生。2025 年 Google I&#x2F;O 上发布，开源，Apache 2.0 协议，免费 tier 给到 60 requests&#x2F;min 和 1000 requests&#x2F;day，一度是很多开发者入门 AI 编程终端工具的首选。</p><span id="more"></span><p>但 Gemini CLI 始终是一个&quot;独立项目&quot;的感觉——它有自己的 GitHub 仓库（google-gemini&#x2F;gemini-cli），有自己的 npm 包（@google&#x2F;gemini-cli），和 Google 的其他产品线之间总差了一口气。</p><p><strong>Antigravity CLI 改变了这一切。</strong></p><p>它不是一个新项目的 1.0，而是 Google 把 Gemini CLI <strong>收编进 Antigravity 产品家族</strong> 后的正式继任者。核心变化：</p><ul><li><strong>统一的 Agent 引擎</strong>：Antigravity CLI 和 Antigravity 2.0 桌面版共享同一个 Agent Harness。无论你在终端还是桌面 App 里和 Agent 对话，推理能力、工具调用、多步规划都是同一套引擎。</li><li><strong>共享设置</strong>：在 CLI 里改了配置，桌面版同步生效，反之亦然。</li><li><strong>一键迁移</strong>：如果你之前在用 Gemini CLI，onboarding 时支持一次性自动导入你已有的 extensions、skills 和 settings。</li><li><strong>配置文件路径</strong>：<code>~/.gemini/antigravity-cli/</code>，你能看到 Gemini 的影子还在，但产品身份已经是 Antigravity 了。</li></ul><img src="/2026/05/20/antigravity-cli/image-20260520002039722.png" class=""><p>Google 的思路很清晰：<strong>Antigravity 是品牌，CLI 是终端入口，2.0 是桌面入口</strong>。两者互补，不是竞争。</p><table><thead><tr><th></th><th>Antigravity CLI</th><th>Antigravity 2.0</th></tr></thead><tbody><tr><td>定位</td><td>终端优先、键盘效率、低开销</td><td>全面功能、可视编排、项目管理</td></tr><tr><td>适合场景</td><td>SSH 远程、服务器环境、键盘流</td><td>多项目并行、需要可视化监控</td></tr><tr><td>共享能力</td><td>同一 Agent 引擎、同一套设置</td><td>同一 Agent 引擎、同一套设置</td></tr></tbody></table><p>我觉得谷歌作为一个这么大的公司，能把 AI 开发者工具统一成一个Antigravity品牌，魄力还是很大的，当然也是形式所逼，Claude和Codex如日中天，谷歌再不行动就没人玩了。最终Antigravity品牌下的产品如下，其中Antigravity 2.0是一个桌面程序，也值得尝试下：</p><ul><li>Antigravity 2.0 (desktop app)</li><li>Antigravity CLI </li><li>Antigravity SDK</li><li>Antigravity IDE</li></ul><p>SDK已经开源。CLI是用Go写的，目前谷歌公司并没有开源它，但是它的开发者对开源还是很支持的，就看谷歌管理者的决定了。</p><hr><h2 id="二、核心特性"><a href="#二、核心特性" class="headerlink" title="二、核心特性"></a>二、核心特性</h2><h3 id="1-自然语言交互"><a href="#1-自然语言交互" class="headerlink" title="1. 自然语言交互"></a>1. 自然语言交互</h3><img src="/2026/05/20/antigravity-cli/agy-cli-prompt.gif" class=""><p>不需要记命令行参数。直接用自然语言告诉 Agent 你要做什么——编辑代码、编排工作流、构建项目，Agent 帮你搞定。</p><h3 id="2-Subagents（子智能体）"><a href="#2-Subagents（子智能体）" class="headerlink" title="2. Subagents（子智能体）"></a>2. Subagents（子智能体）</h3><img src="/2026/05/20/antigravity-cli/image-20260520002613581.png" class=""><p>这是 Antigravity CLI 最重量级的特性。主 Agent 可以自动派生子 Agent 来并行处理后台任务：</p><ul><li>查文档、跑构建、验证修复——这些事交给子 Agent 在后台跑</li><li>子 Agent 拥有完整的工具访问权限：代码搜索、文件编辑、终端命令、Web 搜索</li><li>主 Agent 控制子 Agent 的工具和权限范围，包括是否允许 MCP 工具、是否允许写文件</li></ul><p>输入 <code>/agents</code> 打开子 Agent 管理面板，可以查看状态（running &#x2F; done &#x2F; killed）、当前步骤、完整对话日志。</p><p><strong>快捷键</strong>：</p><ul><li><code>ctrl+k</code>：不用离开主对话，一键批准子 Agent 的权限请求</li><li><code>ctrl+j</code>：直接跳转到下一个等待你批准的子 Agent 详情页</li></ul><h3 id="3-终端沙箱"><a href="#3-终端沙箱" class="headerlink" title="3. 终端沙箱"></a>3. 终端沙箱</h3><p>安全机制。不是启动虚拟机或容器，而是利用操作系统原生能力做隔离：</p><ul><li>macOS：<code>sandbox-exec</code></li><li>Linux：<code>nsjail</code></li><li>Windows：<code>AppContainer</code></li></ul><p>零启动开销。在 <code>settings.json</code> 里一键开启：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;enableTerminalSandbox&quot;</span><span class="punctuation">:</span> <span class="literal"><span class="keyword">true</span></span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>开启后，Agent 每次要执行终端命令，你可以选择&quot;在沙箱内运行&quot;或&quot;跳过沙箱直接跑&quot;，灵活又不失安全。</p><h3 id="4-插件系统"><a href="#4-插件系统" class="headerlink" title="4. 插件系统"></a>4. 插件系统</h3><p>插件是一个打包好的命名空间，可以包含 skills、agents、rules、MCP servers、hooks——一次性部署。</p><p>安装后的文件结构：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">~/.gemini/antigravity-cli/</span><br><span class="line">├── plugins/</span><br><span class="line">│   └── &lt;plugin_name&gt;/</span><br><span class="line">│       ├── plugin.json         # 必需的标记文件</span><br><span class="line">│       ├── mcp_config.json     # 可选：MCP 服务器定义</span><br><span class="line">│       ├── hooks.json          # 可选：事件钩子</span><br><span class="line">│       ├── skills/             # 可选：技能</span><br><span class="line">│       ├── agents/             # 可选：子智能体</span><br><span class="line">│       └── rules/              # 可选：规则</span><br><span class="line">└── import_manifest.json        # 追踪清单</span><br></pre></td></tr></table></figure><p>通过斜杠命令直接访问插件组件。</p><hr><h2 id="三、安装"><a href="#三、安装" class="headerlink" title="三、安装"></a>三、安装</h2><p>一行命令搞定：</p><p><strong>macOS &#x2F; Linux：</strong></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://antigravity.google/cli/install.sh | bash</span><br></pre></td></tr></table></figure><p><strong>Windows PowerShell：</strong></p><figure class="highlight powershell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">irm</span> https://antigravity.google/<span class="built_in">cli</span>/install.ps1 | <span class="built_in">iex</span></span><br></pre></td></tr></table></figure><p><strong>Windows CMD：</strong></p><figure class="highlight cmd"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://antigravity.google/cli/install.<span class="built_in">cmd</span> -o install.<span class="built_in">cmd</span> &amp;&amp; install.<span class="built_in">cmd</span> &amp;&amp; <span class="built_in">del</span> install.<span class="built_in">cmd</span></span><br></pre></td></tr></table></figure><p>安装完成后，首次运行会引导你完成 Google 账号认证。</p><hr><h2 id="四、认证与启动"><a href="#四、认证与启动" class="headerlink" title="四、认证与启动"></a>四、认证与启动</h2><h3 id="认证方式"><a href="#认证方式" class="headerlink" title="认证方式"></a>认证方式</h3><p>Antigravity CLI 会尝试通过操作系统的安全密钥链静默认证。如果找不到已保存的会话：</p><ul><li><strong>本地机器</strong>：自动打开浏览器跳转 Google 登录页</li><li><strong>远程 SSH</strong>：检测到 SSH 环境后打印一个授权 URL，复制到本地浏览器登录，然后把授权码粘贴回来</li></ul><p>退出登录：在 CLI 里输入 <code>/logout</code>。</p><h3 id="启动"><a href="#启动" class="headerlink" title="启动"></a>启动</h3><p>安装好后直接在终端输入：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">antigravity</span><br></pre></td></tr></table></figure><p>或者缩写：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">agy</span><br></pre></td></tr></table></figure><p>进入 TUI 界面后，底部的提示框就是你的输入区域。直接开始对话。</p><img src="/2026/05/20/antigravity-cli/image-20260520002941404.png" class=""><hr><h2 id="五、三大核心斜杠命令"><a href="#五、三大核心斜杠命令" class="headerlink" title="五、三大核心斜杠命令"></a>五、三大核心斜杠命令</h2><p>Antigravity CLI 内置了大量斜杠命令（完整的命令列表见文末速查表），但其中最值得重点关注的是这三个：<strong><code>/goal</code></strong>、<strong><code>/schedule</code></strong> 和 <strong><code>/grill-me</code></strong>。它们分别解决了 Agent 工作流中三个最关键的痛点：<strong>自主执行</strong>、<strong>定时调度</strong> 和 <strong>方案对齐</strong>。</p><h3 id="1-goal-—-深度工作模式"><a href="#1-goal-—-深度工作模式" class="headerlink" title="1. /goal — 深度工作模式"></a>1. <code>/goal</code> — 深度工作模式</h3><blockquote><p>用于处理极其复杂或需要长时间运行的任务。</p></blockquote><p>你有没有遇到过这种情况：给 AI 一个大任务，它做到一半问你&quot;接下来要不要继续？&quot;，然后你不得不守在终端前一次次回复&quot;继续&quot;。更烦的是，它有时候会自作主张地跳过某些步骤，然后宣布&quot;完成了&quot;——结果你一看，漏了一半。</p><p><code>/goal</code> 就是来解决这个问题的。</p><p>输入 <code>/goal</code> 后，Agent 会进入一种&quot;深度工作&quot;状态：</p><ul><li><strong>自动批准自己的实施计划</strong>，不再反复请你确认</li><li><strong>不再中途问你要澄清</strong>，自己根据上下文推理</li><li><strong>持续工作直到目标完全达成</strong>，或者它判断目标确实无法完成</li><li>甚至可以<strong>彻夜运行</strong>，你早上起来看结果就行</li></ul><p><strong>用法示例</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/goal all tests pass and lint is clean</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/goal ship the auth flow without breaking existing sessions</span><br></pre></td></tr></table></figure><p>第一个锁定的是字面任务——测试全绿、lint 干净。第二个锁定的是<strong>意图</strong>——上线认证流程但不能破坏已有会话。两者都能用，但第二个在遇到模糊地带时更能做出正确判断。</p><p><strong>背后机制</strong>：每次工作模型完成一轮后，系统会将目标条件和当前上下文发给一个独立的小模型做评判——&quot;目标达成了吗？yes&#x2F;no + 理由&quot;。工作模型和评判模型分离，避免了&quot;自己给自己打分&quot;的利益冲突。</p><p><strong>和 Codex &#x2F; Claude Code 的 <code>/goal</code> 对比</strong>：</p><table><thead><tr><th>维度</th><th>Antigravity <code>/goal</code></th><th>Claude Code <code>/goal</code></th><th>Codex <code>/goal</code></th></tr></thead><tbody><tr><td>评判机制</td><td>Gemini 子模型评判</td><td>独立小模型（Haiku 级）</td><td>GPT-5.5 子 Agent 评判</td></tr><tr><td>预算控制</td><td>无显式预算</td><td>无显式预算</td><td>Token + 时间双重预算</td></tr><tr><td>跨会话</td><td>项目内持久化</td><td><code>/resume</code> 可恢复</td><td>SQLite 线程级</td></tr><tr><td>配套能力</td><td>Subagents + Scheduled Tasks</td><td><code>/loop</code> + Stop hooks</td><td><code>/goal pause/resume/clear</code></td></tr></tbody></table><p>Antigravity 的 <code>/goal</code> 没有像 Codex 那样精细的 token 预算治理，但它和 Subagents、Scheduled Tasks 深度集成——你可以让一个 <code>/goal</code> 任务在后台派生出多个子 Agent 并行推进，这是目前其他家做不到的。</p><h3 id="2-schedule-—-定时调度模式"><a href="#2-schedule-—-定时调度模式" class="headerlink" title="2. /schedule — 定时调度模式"></a>2. <code>/schedule</code> — 定时调度模式</h3><blockquote><p>设置定时任务或循环任务，让 Agent 自己&quot;打卡上班&quot;。</p></blockquote><p>这是一个被严重低估的命令。想想你日常有多少重复性的检查工作：</p><ul><li>每小时检查一次 CI 构建是否通过</li><li>每天下班前汇总今天的 PR 状态</li><li>每周一生成一份架构变更报告</li><li>部署后 5 分钟检查服务是否健康</li></ul><p>以前这些事要么自己写 cron 脚本，要么手动盯着。现在直接告诉 Agent 就行：</p><p><strong>一次性任务</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/schedule in 5 minutes check if the build has finished</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/schedule in 10 minutes verify the deployment is healthy</span><br></pre></td></tr></table></figure><p><strong>循环任务</strong>（Cron 表达式）：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/schedule every hour run the health check script, up to 3 times</span><br></pre></td></tr></table></figure><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">/schedule every day at 6pm summarize today&#x27;s merged PRs</span><br></pre></td></tr></table></figure><p><strong>背后机制</strong>：Scheduled Tasks 使用 Cron 调度，Agent 按计划在后台启动，对话出现在侧边栏。你可以继续做手头的事，也可以随时打开某个调度任务的对话查看进度、补充指令——从&quot;自动执行&quot;无缝切换到&quot;人工介入&quot;。</p><p>目前 Scheduled Tasks 固定使用 Gemini 3.5 Flash，后续会开放模型选择。</p><p><strong>实用场景举例</strong>：</p><table><thead><tr><th>场景</th><th>命令</th></tr></thead><tbody><tr><td>CI 守望</td><td><code>/schedule every 30 minutes check if CI is green, alert me if not</code></td></tr><tr><td>PR 盘点</td><td><code>/schedule every day at 5pm list all open PRs with status summary</code></td></tr><tr><td>部署验证</td><td><code>/schedule in 15 minutes run smoke tests on staging</code></td></tr><tr><td>依赖检查</td><td><code>/schedule every monday check for security updates in package.json</code></td></tr></tbody></table><h3 id="3-grill-me-—-方案对齐模式"><a href="#3-grill-me-—-方案对齐模式" class="headerlink" title="3. /grill-me — 方案对齐模式"></a>3. <code>/grill-me</code> — 方案对齐模式</h3><blockquote><p>在任务开始前进行深度方案对齐，让 AI 来&quot;面试&quot;你。</p></blockquote><p>这是一个非常有创意的设计。它解决的是 AI Agent 工作流中最隐蔽也最致命的问题：<strong>你的需求本身就是模糊的</strong>。</p><p>很多时候 Agent 产出不理想，不是模型能力不够，而是你给的信息不完整——你自己都没想清楚的细节、被忽略的边界条件、隐含的约束条件。但人很难发现自己的盲区。</p><p><code>/grill-me</code> 的做法是反过来——<strong>让 Agent 来问你</strong>。</p><p>输入 <code>/grill-me</code> 后，Agent 不会急着写代码，而是：</p><ol><li><strong>分析你的需求描述</strong>，识别模糊点和潜在风险</li><li><strong>逐个向你提问</strong>，覆盖你可能遗漏的关键细节</li><li><strong>挑战你的设计决策</strong>，提出替代方案和 trade-off 分析</li><li><strong>反复迭代直到方案对齐</strong>，确保双方对&quot;做什么&quot;和&quot;怎么做&quot;达成共识</li></ol><p>只有当你和 Agent 都满意了，才开始实施。</p><p><strong>典型对话</strong>：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">你：我想给现有的用户系统加上 OAuth 登录</span><br><span class="line"></span><br><span class="line">Agent（/grill-me 模式）：</span><br><span class="line">  - 现有的认证系统用的什么方案？Session 还是 JWT？</span><br><span class="line">  - 需要支持哪些 OAuth 提供商？Google、GitHub、还有其他的吗？</span><br><span class="line">  - 已有用户的账号怎么关联？同一个邮箱自动合并还是手动绑定？</span><br><span class="line">  - 如果用户用 OAuth 注册但邮箱已被占用，怎么处理？</span><br><span class="line">  - 需要支持企业级 SSO 吗？SAML？</span><br><span class="line">  - Token 过期策略是什么？需要 refresh token 吗？</span><br><span class="line">  ...</span><br></pre></td></tr></table></figure><p>你会发现，Agent 问出了很多你确实没想过的东西。</p><p><strong>和其他工具的对比</strong>：Claude Code 和 Codex 都没有对等的内置命令。在 Claude Code 中，你可以在 CLAUDE.md 里写一段&quot;开始前先问三个问题&quot;的指令来模拟类似效果，但 Antigravity 把它做成了一等公民——一个专门的斜杠命令，有独立的交互流程。</p><h3 id="三个命令的配合"><a href="#三个命令的配合" class="headerlink" title="三个命令的配合"></a>三个命令的配合</h3><p>这三个命令不是孤立的，它们构成了一个完整的 Agent 工作流闭环：</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">/grill-me  →  方案对齐，确保需求清晰</span><br><span class="line">    ↓</span><br><span class="line">/goal      →  深度执行，自主完成复杂任务</span><br><span class="line">    ↓</span><br><span class="line">/schedule  →  持续监控，定时检查和维护</span><br></pre></td></tr></table></figure><p>一个实际的项目流程可能是这样的：</p><ol><li><strong>周一早上</strong>：用 <code>/grill-me</code> 把本周的需求和 Agent 对齐</li><li><strong>对齐后</strong>：用 <code>/goal</code> 让 Agent 自主完成开发任务</li><li><strong>开发中</strong>：用 <code>/schedule</code> 设置每小时的 CI 检查</li><li><strong>部署后</strong>：用 <code>/schedule</code> 设置 5 分钟后的健康检查</li><li><strong>周五</strong>：用 <code>/schedule</code> 设置 PR 盘点汇总</li></ol><h3 id="其他常用命令速查"><a href="#其他常用命令速查" class="headerlink" title="其他常用命令速查"></a>其他常用命令速查</h3><p>除了三大核心命令，这些命令也经常用到：</p><table><thead><tr><th>命令</th><th>说明</th></tr></thead><tbody><tr><td><code>/resume</code>（别名 <code>/switch</code>）</td><td>打开会话选择器，恢复或切换之前的对话</td></tr><tr><td><code>/rewind</code>（别名 <code>/undo</code>）</td><td>回退对话历史到之前的检查点</td></tr><tr><td><code>/rename &lt;name&gt;</code></td><td>给当前对话线程重命名</td></tr><tr><td><code>/clear</code></td><td>清空当前对话，开始新会话</td></tr><tr><td><code>/fork</code></td><td>从当前对话的某个节点分叉出一个新工作空间</td></tr><tr><td><code>/config</code> 或 <code>/settings</code></td><td>打开全屏配置面板</td></tr><tr><td><code>/permissions</code></td><td>设置 Agent 自主级别（<code>request-review</code> &#x2F; <code>always-proceed</code> &#x2F; <code>strict</code>）</td></tr><tr><td><code>/model</code></td><td>选择默认推理模型（跨会话持久化）</td></tr><tr><td><code>/agents</code></td><td>打开子 Agent 管理面板</td></tr><tr><td><code>/tasks</code></td><td>监控、查看日志、终止后台任务</td></tr><tr><td><code>/skills</code></td><td>浏览本地和全局的 Agent 技能</td></tr><tr><td><code>/mcp</code></td><td>打开 MCP 服务器配置面板</td></tr><tr><td><code>/keybindings</code></td><td>打开快捷键编辑器</td></tr><tr><td><code>/statusline</code></td><td>自定义状态栏显示内容</td></tr><tr><td><code>/open &lt;path&gt;</code></td><td>在外部编辑器中打开文件</td></tr><tr><td><code>/logout</code></td><td>登出 Google 账号并清除凭证</td></tr><tr><td><code>?</code></td><td>查看帮助，列出所有斜杠命令</td></tr></tbody></table><h3 id="快捷技巧"><a href="#快捷技巧" class="headerlink" title="快捷技巧"></a>快捷技巧</h3><table><thead><tr><th>技巧</th><th>操作</th></tr></thead><tbody><tr><td>文件路径自动补全</td><td>输入 <code>@</code> 触发路径建议</td></tr><tr><td>清空输入框</td><td>按 <code>esc esc</code></td></tr><tr><td>直接跑终端命令</td><td>输入开头加 <code>!</code>，如 <code>!ls -la</code></td></tr><tr><td>换行不提交</td><td><code>shift+enter</code> 或 <code>alt+enter</code></td></tr><tr><td>降低工具调用噪音</td><td>在 <code>/config</code> 里把 verbosity 设为 <strong>low</strong></td></tr><tr><td>恢复上次会话</td><td>关闭 CLI 时自动打印恢复命令，直接复制粘贴即可</td></tr></tbody></table><hr><h2 id="六、高级配置"><a href="#六、高级配置" class="headerlink" title="六、高级配置"></a>六、高级配置</h2><p>对于进阶用户，<code>~/.gemini/antigravity-cli/settings.json</code> 支持更精细的控制。</p><p><strong>细粒度权限控制</strong>：</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;permissions&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;allow&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;command(git)&quot;</span><span class="punctuation">,</span> <span class="string">&quot;command(npm test)&quot;</span><span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line">    <span class="attr">&quot;deny&quot;</span><span class="punctuation">:</span> <span class="punctuation">[</span><span class="string">&quot;command(rm -rf)&quot;</span><span class="punctuation">]</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>不再需要全局 all-or-nothing，可以精确到每条命令。</p><p><strong>自定义状态栏</strong>：</p><p>你可以把 Agent 的实时元数据（当前目录、活跃模型、token 用量、状态等）通过 JSON 管道传给你自己的 shell 脚本，生成动态状态栏或终端窗口标题。</p><p><strong>自定义快捷键</strong>：</p><p>编辑 <code>~/.gemini/antigravity-cli/keybindings.json</code>。可以给一个动作绑定多个快捷键，设为空数组 <code>[]</code> 即可禁用（<code>cli.exit</code> 和 <code>cli.enter</code> 除外）。</p><hr><h2 id="七、和-Claude-Code、Codex-CLI-的定位对比"><a href="#七、和-Claude-Code、Codex-CLI-的定位对比" class="headerlink" title="七、和 Claude Code、Codex CLI 的定位对比"></a>七、和 Claude Code、Codex CLI 的定位对比</h2><p>Antigravity CLI 的发布，让 AI 编程终端工具的版图更加清晰：</p><table><thead><tr><th>维度</th><th>Antigravity CLI</th><th>Claude Code</th><th>Codex CLI</th></tr></thead><tbody><tr><td>背后模型</td><td>Gemini 3 系列</td><td>Claude Opus&#x2F;Sonnet&#x2F;Haiku</td><td>GPT-5 系列</td></tr><tr><td>开源</td><td>❌</td><td>❌</td><td>✅</td></tr><tr><td>子智能体</td><td>✅ 原生支持</td><td>✅ 通过 Agent 工具</td><td>✅ 通过 goal 系统</td></tr><tr><td>MCP 支持</td><td>✅</td><td>✅</td><td>✅</td></tr><tr><td>沙箱</td><td>✅ 原生 OS 级</td><td>✅ 容器级</td><td>✅ Docker 级</td></tr><tr><td>免费额度</td><td>✅ Google 账号</td><td>✅ 有但较少</td><td>需 API key</td></tr><tr><td>插件&#x2F;技能</td><td>✅ Plugin 系统</td><td>✅ Skills + Hooks</td><td>✅ Plugin marketplace</td></tr><tr><td>SSH 友好</td><td>✅ 设计初衷</td><td>✅</td><td>✅</td></tr></tbody></table><p>Antigravity CLI 的核心优势在于 <strong>Google 生态整合</strong> 和 <strong>免费额度</strong>。如果你已经在用 GCP、Google Workspace，或者想要一个零成本的 AI 编程终端工具，它是目前最好的选择之一。</p><hr><h2 id="八、定价：免费就能用最新大模型"><a href="#八、定价：免费就能用最新大模型" class="headerlink" title="八、定价：免费就能用最新大模型"></a>八、定价：免费就能用最新大模型</h2><p>这是我觉得 Antigravity CLI 最狠的一点——<strong>免费版就能用到最新、最强的大模型</strong>。</p><h3 id="定价体系"><a href="#定价体系" class="headerlink" title="定价体系"></a>定价体系</h3><p>Google 把 Antigravity 的定价分成四档：</p><table><thead><tr><th></th><th>For Individuals（免费）</th><th>Google AI Pro</th><th>Google AI Ultra</th><th>Organization</th></tr></thead><tbody><tr><td><strong>价格</strong></td><td>$0&#x2F;月</td><td>Google One 订阅</td><td>Google One 订阅</td><td>Google Cloud 按用量计费</td></tr><tr><td><strong>定位</strong></td><td>任何想用 AI 编程的人</td><td>体验 Agent 自动化工作流</td><td>把 Antigravity 当日常主力工具</td><td>企业级团队部署</td></tr></tbody></table><h3 id="免费-tier-包含什么？"><a href="#免费-tier-包含什么？" class="headerlink" title="免费 tier 包含什么？"></a>免费 tier 包含什么？</h3><p>重点来了。<strong>For Individuals</strong> 免费 tier 包含：</p><ul><li><strong>无限制的 Tab 补全</strong></li><li><strong>无限制的 Command 请求</strong></li><li><strong>慷慨的每周速率限制</strong></li></ul><p>最关键的是——<strong>模型阵容</strong>：</p><ul><li><strong>Gemini 3.5 Flash</strong>：Google 最新的高速模型</li><li><strong>Gemini 3.1 Pro</strong>：Google 的高性能推理模型</li><li><strong>Gemini 3 Flash</strong>：上一代快速模型</li><li><strong>Claude Sonnet &amp; Opus 4.6</strong>：Anthropic 的旗舰编程模型</li><li><strong>gpt-oss-120b</strong>：OpenAI 的开源大模型</li></ul><p>你没看错——<strong>Claude Opus 4.6 和 Gemini 3.5 Flash，免费白嫖</strong>。这在目前所有 AI 编程终端工具中是最慷慨的免费额度。Claude Code 的免费 tier 额度有限，Codex CLI 需要自带 API key。而 Antigravity CLI 只需要一个 Google 账号，这些顶级模型全部免费用。</p><h3 id="付费-tier-升级什么？"><a href="#付费-tier-升级什么？" class="headerlink" title="付费 tier 升级什么？"></a>付费 tier 升级什么？</h3><ul><li><strong>Google AI Pro</strong>：在免费基础上，获得更宽松的速率限制和灵活的 AI 积分池</li><li><strong>Google AI Ultra</strong>：在 Pro 基础上，解锁最新 Gemini 模型的扩展访问权限，适合把 Antigravity 当作日常驱动力</li><li><strong>Organization</strong>：面向 Google Cloud 企业客户，接入 Gemini Enterprise Platform，按 API 用量计费</li></ul><h3 id="和竞品的免费额度对比"><a href="#和竞品的免费额度对比" class="headerlink" title="和竞品的免费额度对比"></a>和竞品的免费额度对比</h3><table><thead><tr><th>工具</th><th>免费 tier</th><th>免费模型</th><th>速率限制</th></tr></thead><tbody><tr><td><strong>Antigravity CLI</strong></td><td>✅</td><td>Gemini 3.5 Flash &#x2F; 3.1 Pro &#x2F; 3 Flash + Claude Sonnet &amp; Opus 4.6 + gpt-oss-120b</td><td>慷慨的每周限制</td></tr><tr><td><strong>Claude Code</strong></td><td>✅ 有但较少</td><td>Claude 系列（额度有限）</td><td>较严格</td></tr><tr><td><strong>Codex CLI</strong></td><td>❌ 需 API key</td><td>取决于你的 API 余额</td><td>取决于 API tier</td></tr></tbody></table><p><strong>一句话总结</strong>：Antigravity CLI 的免费 tier 是目前 AI 编程终端工具里最值得&quot;薅羊毛&quot;的选择——零成本，顶级模型，无限 Tab 补全和 Command 请求。对个人开发者来说，免费 tier 的额度完全够用。</p><hr><h2 id="九、写在最后"><a href="#九、写在最后" class="headerlink" title="九、写在最后"></a>九、写在最后</h2><p>Gemini CLI 用了一年时间证明了&quot;AI 终端工具&quot;这个品类有价值。Antigravity CLI 则是 Google 把这个品类正式升级为产品线的标志。</p><p>它不再是实验项目，而是 Antigravity 家族的一等公民——和桌面版共享引擎、共享设置、共享生态。对于习惯在终端里工作的开发者来说，这是一个比 Gemini CLI 更成熟、更完整、也更值得投入时间的工具。</p><p><strong>如果你之前在用 Gemini CLI</strong>：迁移几乎是自动的。安装 Antigravity CLI，onboarding 时选择导入即可。</p><p><strong>如果你之前没接触过</strong>：一行命令装上试试：</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -fsSL https://antigravity.google/cli/install.sh | bash</span><br></pre></td></tr></table></figure><p>零成本，五分钟上手。不试试就亏了。</p><hr><p><em>参考：<a href="https://antigravity.google/docs/cli-overview">Antigravity CLI 官方文档</a> | <a href="https://antigravity.google/docs/cli-getting-started">Antigravity CLI 安装指南</a> | <a href="https://antigravity.google/docs/cli-features">Antigravity CLI 功能介绍</a></em></p>]]>
    </content>
    <id>https://colobu.com/2026/05/20/antigravity-cli/</id>
    <link href="https://colobu.com/2026/05/20/antigravity-cli/"/>
    <published>2026-05-20T00:50:16.000Z</published>
    <summary>
      <![CDATA[<h2 id="Gemini-CLI-正式谢幕，Google-Antigravity-CLI-接棒登场"><a href="#Gemini-CLI-正式谢幕，Google-Antigravity-CLI-接棒登场" class="headerlink" title="Gemini CLI 正式谢幕，Google Antigravity CLI 接棒登场"></a>Gemini CLI 正式谢幕，Google Antigravity CLI 接棒登场</h2><p>2026 年 5 月 20 日，Google 在 I&#x2F;O 大会上发布了一连串重磅产品更新。其中最让我兴奋的，不是那个桌面版的 Antigravity 2.0，而是那个安安静静躺在终端里的 <strong>Antigravity CLI</strong>。</p>
<p>为什么？因为它正式宣告了 Gemini CLI 时代的结束——也宣告了 Google 在 AI 编程终端战场上的全新布局。</p>
<img src="/2026/05/20/antigravity-cli/image-20260520001947247.png" class="">

<hr>
<h2 id="一、背景：从-Gemini-CLI-到-Antigravity-CLI"><a href="#一、背景：从-Gemini-CLI-到-Antigravity-CLI" class="headerlink" title="一、背景：从 Gemini CLI 到 Antigravity CLI"></a>一、背景：从 Gemini CLI 到 Antigravity CLI</h2><p>很多人对 Gemini CLI 并不陌生。2025 年 Google I&#x2F;O 上发布，开源，Apache 2.0 协议，免费 tier 给到 60 requests&#x2F;min 和 1000 requests&#x2F;day，一度是很多开发者入门 AI 编程终端工具的首选。</p>]]>
    </summary>
    <title>antigravity-cli</title>
    <updated>2026-06-22T00:21:15.715Z</updated>
  </entry>
</feed>
