发布与分发智能体

9 分钟读完

U28
实战手册 · 编码与计算机操作智能体

发布与分发智能体。

编码智能体的其他每一项任务都是可撤销的:坏补丁来一个 revert 提交,坏迁移回滚掉,坏的 CI 改动下一次推送就修了。而一个已发布的版本,是你的智能体能产出的、谁也收不回去的那一个输出——npm 的撤回窗口只有 72 小时,版本号永不可复用,而每一个锁文件已经解析到它的使用者,无论如何都永远拿着它了。所以发布智能体的设计重心不是自动化(那部分很容易),而是签名边界与那条向前的路。让智能体把一切准备工作做完,绝不让它持有发布凭据,并测量那个决定「自动化发布是否安全」的唯一数字:你要花多久才能用一个新版本盖掉一个坏版本。

STEP 1

撤回不是回滚,而登记中心的规则把这一点说得很明白。

先看生态实际允许什么,因为大多数工程师脑子里带的那个模型——「我们随时可以 unpublish」——错得会改变设计。

在 npm 上,撤回被限定在 72 小时的窗口内,而一个版本号一旦存在过就永不可复用。PyPI 根本不允许你重新上传同一个文件名。容器登记中心允许你删掉一个 tag,却无法让已拉取的那次「未拉取」。而在每一种情形里,登记中心都是较小的那个问题:在你发现之前那八分钟里解析到你这个版本的使用者,此刻已经把它钉在锁文件里、缓存在 CI 镜像里、烤进他们发给自己客户的容器里。删掉那个产物,一个也碰不到。

所以一次发布只有一个倒挡,而那个倒挡是向前的:发布一个修正版,并把人们迁到它上面。这把整个问题重新框定了。问题不是「万一智能体发布了错的东西怎么办」——它终将会,就像有史以来每一套发布流程一样。问题是,那个修正版要多快才能成为默认安装所解析到的那一个。

正是这项性质让发布区别于编码智能体架构里的其他一切。一次合入 main 对你的团队可见;一次发布对所有下游立刻可见,而他们的钉定才是让它变成永久的那件事。请把发布这一步当作你的智能体全部行为里唯一具有无界影响半径的动作,因为它是唯一一个效果住在别人基础设施里的动作。

STEP 2

把「准备」和「发布」切开,并把凭据放在远的那一侧。

切一个版本的绝大部分工作都是准备,而准备既可撤销又可评审。那正是智能体赚回自己身价的地方。发布则是不可逆的一瞬,而它应当是唯一需要人来授权的那一部分。

# The agent does all of this — every line reversible

version bump          per the diff's actual surface change
changelog entry       one line per user-visible change
migration notes       for every breaking item it detected
release notes draft   from the diff, not from the PR titles
tag candidate         annotated, pushed to a branch, NOT to refs/tags
dry-run publish       --dry-run, with the file list diffed vs last release

# A human does exactly one thing

approve               a workflow_dispatch on a protected ref

# The credential exists only inside that run

OIDC exchange         short-lived, run-scoped, nothing stored

最后一行不是一条你得自己去强制的策略;生态已经替你安排好了。npm 在 2025 年 11 月停止了经典令牌的创建,并在 2025 年 12 月 9 日把它们全部吊销。新创建的、带写权限的细粒度令牌默认 7 天失效,上限 90 天。可信发布(trusted publishing)走得更远:一次 CI 运行用一个 OIDC 身份换取一枚短时效、按运行限定的令牌,于是任何地方都没有存着一个 npm 令牌、供智能体去读、去外带,或被诱导着拿去用。

请照着这个来设计。如果你的发布通路里没有任何长期有效的发布密钥,那么「智能体不得单方面发布」就不再是提示里的一条规则——那条规则要跟任务竞争,而且会输——它变成了系统的一项性质。这跟面向智能体的受限凭据是同一个论证,而难得地有一个格外干净的实现可用。

不要把人工审批建模成一次对话里的确认。请把它绑到产物上:审批应当点明提交 SHA,以及那次 dry run 产出的压缩包摘要;而发布作业应当在两者中任一发生变化时拒绝执行。否则你批准的是一个意图,不是一件产物——这正是审批与确认体验里编目的那个失败模式。

STEP 3

溯源证明的是「在哪里构建」,不是「它是对的」。

可信发布会自动附上溯源,而这份证明确实很有力:它把产物绑定到一个仓库、一个工作流文件和一个提交,由代码托管平台的身份签名,而不是由某个人手里的密钥。相对于密钥库里塞一枚令牌,这是一次很大的改进;而它也经常被过度解读。

溯源所主张的是一个构建位置。它并不主张产物内容对应于任何人评审过的源码、不主张构建是确定性的,也不主张被构建的那个提交就是某个人批准过的那个提交。这些是各自独立的主张,而其中只有最后一条是便宜就能拿到的。

由此得出唯一一个值得专门设计去防的、智能体特有的失败模式。如果你的智能体能向可信发布所构建的那个分支推送,那么溯源会忠实地用你所在组织的身份为这个智能体的提交签名,而每一个下游的校验方都会报告「通过」。这份证明是有效的,而它说的恰恰是错的那件事:它告诉使用者,产物来自你的仓库——这是真的——而他们真正想知道的那件事,也就是「有一个人同意把它发出去」,从头到尾都不在范围内。

# The invariant that makes the attestation mean what readers think

release workflow triggers ONLY on a protected ref
protected ref is NOT writable by any agent identity
agent identity can open PRs; it cannot push to the ref
tag creation is a side effect of the release job, not an input

# Check it the way an attacker would

can the agent's token push to refs/heads/release?
can it edit .github/workflows/publish.yml on that ref?
can it change which ref the workflow trusts?
   any yes -> provenance is attesting your agent's intent

请拿智能体真正在用的那枚令牌去核验这一点,而不是拿写下来的策略。这是智能体供应链安全里「你就是那个供应方」的那一支,而你发出的那份证明,正承载着别人做风险判断的重量——也就是披露与内容溯源里属于溯源的那一半。

STEP 4

发布说明是模型擅长的那一部分——也是真有正确性检验的那一部分。

从 diff 起草发布说明几乎是一项理想的智能体任务:机械、量大,而且枯燥到人类往往做得很差。它还有一样大多数智能体输出所缺的东西:一项你真跑得起来的正确性检验。那项检验考的是覆盖,不是文风:diff 里出现的每一项用户可见的行为变化,是否都在说明里的某处出现了?

这就把对说明的评审从「凭感觉过一遍」变成了一个可核查的主张。请机械地构造那个覆盖集合——导出的 API 表面 diff、公开 schema 的 diff、配置默认值的 diff、新增的迁移文件——并在该集合中某一项没有对应条目时让发布失败。散文由智能体写;而「必须被提到什么」由检测器决定。

有一个顺序上的细节,比它看起来更重要。请让说明在一个能看到 diff、而看不到作者智能体的推理过程的上下文里被起草。一个模型去总结它刚刚做出的改动时,写出来的说明会与它的意图保持一致;而一份描述意图而非行为的发布说明,比没有说明更糟——因为它告诉读者这次升级是安全的。请用一次单独的运行,只以 diff 为输入——这是生成者—核验者落差里论证过的那种生成与核查分离,用到散文上。

同样的切分适用于破坏性变更的判定。不要问模型某项改动是不是破坏性的;把表面 diff 给它,让它解释每一处移除。判定是一个有确定性答案的 schema 问题,而把一个确定性问题交给模型,正是你在周五凌晨两点拿到一个像真的错答案的方式。

STEP 5

「盖掉所需时间」才是那个指标。在第一次发布之前就把向前的路建好。

发布频率是团队们上了仪表的那个数字,而对于「要不要让智能体靠近这条流水线」这个决定,它是错的那一个。真正起决定作用的数字是:从你知道出了问题的那一刻起,端到端算,你要花多久才能把一个修正版送到默认安装面前。

# Time-to-supersede, decomposed — measure each leg once

detect        a consumer tells you, or your canary does
decide        who can say "ship the fix" at 02:00?
build         is the fix branch buildable without the broken release?
publish       same approval path, or a documented break-glass?
propagate     dist-tag moved, old version deprecated, advisory filed

# The leg that is always worst is the last one

   npm         dist-tag latest -> fixed; deprecate the bad version
   PyPI        yank the release (resolvable only if explicitly pinned)
   containers  retag; the digest stays pullable forever
   OS packages measured in days, not minutes

请在一个用完即弃的包上,拿一个故意做坏的补丁版本,把这整条路有意地跑一次。那次演练才是把上面那份清单从散文变成你的智能体能执行之物的东西,也是唯一能发现哪一段真正慢的办法——实践中,那一段几乎从来不是构建。

然后请把这条「盖掉」通路作为工具交给智能体,而不是作为文档。「发一个 revert 掉提交 X 的补丁版本、把 dist-tag 挪过去、用一条指向修复版的说明把坏版本标为弃用」是五次调用的序列,而智能体在凌晨两点做它,比人更快也更可靠;而且每一步都是只能向前的,所以第 1 步里那个不可逆的论证在这里并不适用。这是整条发布流程里唯一「更多自主性严格更安全」的地方,它与修复智能体的副作用相配,用于那些已经跑出去的部分。

STEP 6

在让智能体碰发布之前该先就位的东西。

六项前置条件。它们是有意乏味的,而每一项都有一个你这周就能核查的「是或否」答案。

  • 不存在任何长期有效的发布凭据。用可信发布或等价的 OIDC 交换,于是没有任何东西可供智能体持有。如果你还留着一枚存下来的令牌,那就是第一个该删的东西——见面向智能体的密钥管理。
  • 发布所用的 ref 对任何智能体身份都不可写。拿令牌去核查,不是拿策略文档;而且要连工作流文件、以及工作流所信任的那个 ref 一起查。
  • 审批绑定到一个摘要上。当提交或压缩包摘要与被批准的那个不一致时,作业拒绝发布。
  • 有一个机械的破坏性变更检测器为说明把关。表面 diff、schema diff、配置默认值 diff。模型负责写;检测器负责决定必须覆盖什么。
  • 「盖掉所需时间」是被测量过的,不是被估计的。在一个用完即弃的包上演练一次坏发布,并给传播那一段计时。
  • 「盖掉」序列是智能体能调用的一个工具。只能向前,所以它不需要审批关卡,而这正是自动化回报最高的那一步。

范围说明:这一页讲的是把产物发到登记中心。把一次改动灰度到你自己正在运行的服务上是另一个问题,有另一个倒挡——那是灰度与版本化。而上游那一半,也就是你的智能体消费别人的发布而不是生产发布,那是依赖升级智能体。

今天就做这件事:查一下你的编码智能体那枚令牌,能不能向你的发布工作流所构建的那个分支推送。如果能,那么你此刻已经在发布这样的产物了——它们的溯源用你所在组织的身份为无人批准过的提交作证;而这份证明在每一个下游校验方眼里,比一次未签名的发布看起来更好。在自动化这一页上任何别的东西之前,先把那一项权限修掉;这是一次十分钟的改动,也是这里唯一一件正在让你的供应链「看起来比实际更安全」的事。