用 GitHub Agentic Workflows 自动化跨仓库文档更新
“文档在哪儿?”这是产品团队里没人喜欢回答的问题。诚实的回答通常是某种“还没写完”。写文档的人盯着一个已关闭的 pull request,努力逆向排查改了什么。而 PR 的作者早已转去忙别的事了。等文档真正发布时,功能其实早就上线了,有时候甚至不止一次。这就是我们 Aspire 团队(一个 10 人小团队,为分布式应用构建开发者工具)过去的状态。几个月前,我们想搞清楚如何安全地把 AI 引入已经信任的自动化流程里,就在那时发现了 GitHub Agentic Workflows。我开始把原型塞进 microsoft/aspire。以下是从 GitHub 里直接拉出来的一组数字:Aspire 13.3 和 13.4 有 82 个功能文档 PR 被合并,中位时间在产品 PR 之后 44.8 小时,每个 PR 都由发布该功能的工程师审阅过。没增加人手,也没有重新培训流程,只是换了个方式回答“谁来写这个?”
🔒 约束条件:跨仓库自动化才是难点
我们的产品在 microsoft/aspire,文档站点在 microsoft/aspire.dev——不同的仓库、不同的部署目标、不同的审查链。大多数团队很快就能搞定同仓库的自动化;跨仓库的自动化才是真正棘手的地方。大范围的仓库级 token 应该进博物馆,任何负责任的安全策略(包括我们的)都会相应限制它们。这是好事,但如果写文档的地方和写代码的地方不是同一个仓库,就会成为真正的瓶颈。多年来默认工作流是这样的:工程师在 microsoft/aspire 里发布一个新功能;几周后文档作者才注意到;文档作者打开 PR,读 diff,然后联络工程师确认改了什么;工程师已经在做下一个功能了,隐约有点印象,回复了半幅画面;文档草稿发布,有时甚至对应的是已经发布的版本。这就是“逆向工程税”。我们需要的自动化既要能跨仓库,又不能让代理拿到一个到处可写的 token。GitHub Agentic Workflows 最终解决了这个问题。
🤖 为什么用 GitHub Agentic Workflows
GitHub Agentic Workflows 是 GitHub Next 团队推出的一个项目。我经常跟人这样介绍它:“它就像 GitHub Actions,只不过用模型来当任务处理器,同时加了能满足安全审查的护栏。”这个说法有点简化,但八九不离十。
它的工作方式是这样的:你用一个 markdown 文件来写工作流(.github/workflows/my-thing.md),文件顶部是 YAML 风格的 frontmatter,下面是一段英文提示词。运行 GitHub Agentic Workflows compile 之后,它会生成一个同级的 .lock.yml 文件(一个普通的 GitHub Actions 工作流),你把这个文件一起提交就行。运行时,工作流会根据你的提示词,在一个受限的工具集里运行一个 agent。
关键点在于:这个 agent 不会直接写 GitHub。它只输出“意图”——一个 JSON 数据块,描述它想创建哪些 pull request、issue 和评论。然后,一个独立的、范围极窄的任务(叫 safe-outputs handler)会根据这个意图,通过按工作流配置的 GitHub App 把这些操作落地。最后这一点才是真正的解锁之处:agent 只有读取权限和一段提示词,写操作全部走一条小型可验证的流水线,并带有明确的白名单。安全审查点头通过,我们就能交付。💚
💚 一个小插曲:同源技术栈
我很喜欢一种感觉:用来构建的工具,本身也是用同样的工具构建出来的。GitHub Agentic Workflows 的文档是用 Astro 和 Starlight 做的,aspire.dev 也一样——基于 Astro 和 Starlight,再配上更丰富的 Starlight 插件生态(astro-mermaid、starlight-llms-txt、starlight-sidebar-topics、starlight-image-zoom,还有漂亮的 @catppuccin/starlight 主题等)。在这里特别感谢 Chris Swithinbank 和 Starlight 的维护者们,整个生态让人感觉是真正用心的人设计出来的。这种同源感很重要:我们用来自动化文档的工具,和被自动化出来的文档站点,共享着同一套基础。方便之处在于,下一节里的 Mermaid 时序图,在两边渲染出来的效果完全一样。
端到端流水线
这是我们最终采用的流程。主角是一个名叫 pr-docs-check.md 的工作流,放在 microsoft/aspire 仓库里。一次运行由 pull_request 的 closed 事件触发,针对 main 或 release/* 分支,并且要求 merged == true 才继续。
紧接着,在 agent 正式启动之前,工作流先用纯 bash 脚本跑了一个确定性的目标分支解析器,规则如下:
- 优先看 PR 关联的里程碑标题(例如 13.4 → 对应 aspire.dev 仓库的 release/13.4 分支)
- 然后看关联 issue 的里程碑标题(从 PR 正文中解析 Fixes/Closes/Resolves #N,拉取每个 issue,取第一个非空里程碑)
- 如果 PR 的基础分支本身匹配 release/X.Y[.Z] 格式,就直接用它
- 以上都不满足,回退到 main 分支
这一步是整个流程的关键。产品仓库里的里程碑能干净地对应到文档仓库里的发布分支。等 agent 真正开始干活时,它已经清楚文档该落到哪个分支,完全不需要自己猜测或编造目标分支。
接下来,agent 读取 diff,扫描关联的 issue,然后判断:这个改动需要文档吗?如果需要,它就在已检出的 microsoft/aspire.dev 工作区里,按照我们现有的文档编写技能(语气、MDX 规范、Starlight 组件)起草实际内容。写完后,它输出一个安全的 create_pull_request 结果,然后交棒给后续处理器。
这个 safe-outputs 处理器接手后,依次设置:
- 标题前缀:
[docs] - 标签:
docs-from-code - draft: true(我们从不自动合并)
- 基础分支:由 agent 提供,但限定只能是 main 或 release/*
- 目标仓库:microsoft/aspire.dev
- 评审人:从源代码 PR 的评审记录里识别出来的 SME(领域专家)——也就是说,当初产品团队信任谁来批准这个功能,现在就请谁来批准对应的文档。
与此同时,还有一个配套任务会在源 PR 上回帖,附上文档 PR 的链接。如果重新运行,它还会压缩旧的 pr-docs-check 评论。刚点了 Merge 的工程师在几分钟内就会收到通知:“文档草稿已生成,要不要看一眼?”
🔐 safe-outputs 契约
整个安全机制,最终归结到一小段不起眼的 frontmatter 上:
tools:
github:
toolsets: [repos, issues, pull_requests]
min-integrity: approved # only run pinned, integrity-checked actions
allowed-repos:
- microsoft/*
github-app:
app-id: ${{ secrets.ASPIRE_BOT_APP_ID }}
private-key: ${{ secrets.ASPIRE_BOT_PRIVATE_KEY }}
owner: "microsoft"
repositories: ["aspire.dev", "aspire"]
safe-outputs:
create-pull-request:
title-prefix: "[docs] "
labels: [docs-from-code]
draft: true # human-in-the-loop, always
base-branch: main
allowed-base-branches: [main, release/*]
target-repo: "microsoft/aspire.dev"
protected-files: blocked # AGENTS.md, manifests, security config: hands off
fallback-as-issue: true。这就是简化版的全貌:Agent 获得一个 GitHub App token,其安装范围恰好限定在两个仓库——产品仓库和文档仓库——组织内其他任何东西都碰不到。它只能向 main 或 release/* 分支提交 PR。AGENTS.md 和依赖清单按策略禁止修改。如果创建 PR 失败(网络抖动、冲突或任何原因),框架会自动转成创建一个 issue,所以不会有任何东西被悄悄丢弃。这正是安全评审真正喜欢的部分:Agent 的推理是模糊的,但它的操作面不是。
📊 数据一览
以下是滚动 30 天窗口(2026 年 5 月 3 日至 6 月 2 日)的统计数据,覆盖 Aspire 13.3 发布的收尾阶段和 13.4 的筹备期:
| 指标 | 数值 |
|---|---|
| microsoft/aspire 中合并的产品 PR 数 | 396(338 main / 50 release/13.3 / 8 release/13.2) |
| pr-docs-check 工作流运行次数 | 396 |
| 在 microsoft/aspire.dev 上创建的文档草稿 PR 数 | 82 —— 已合并 82(100%)—— 未合并关闭 0 —— 仍打开 0 |
| 文档 PR 的目标分支 | 52 → release/13.3, 27 → release/13.4, 3 → main |
| 文档 PR 合并中位时间 | 44.8 小时 |
| 24 小时内 / 7 天内合并的比例 | 38% / 96% |
注:数据为撰写时统计;工作流仍在运行,总数只会增加。
有几个数字值得细看:396 次运行产生 82 个 PR,这并不是缺陷。工作流在每一个合并的 PR 上都会运行;其中大多数是内部重构、测试修复或依赖升级,没有面向用户的变化。Agent 说了 300 多次“无需文档”,这恰恰是特性。100% 的合并率说明 Agent 对文档的判断是准确的。我们在 v1 误报阶段之后收紧的 prompt 正在发挥作用。
✅ 有效的做法,❌ 无效的做法
有效的做法
✅ 里程碑 → 发布分支的映射。这是我们做过的最具杠杆作用的一个决定。工程师本来就会在 PR 和 issue 上设置里程碑;我们因此免费获得了准确的目标分支路由。
✅ 仅草稿 PR + SME 作为评审者。Agent 永远不会合并。发布该功能的工程师才是确认文档是否正确的人。我们不再需要在文档层通过反向工程去理解功能了。
工程师只需在他们已经待着的地方,告诉文档草稿该写什么。
✅ 每个工作流都有独立的 GitHub App 作用域。每个工作流都有专属的 App token,并明确限定仓库和权限范围。安全审查通过,我们也认可;第一次需要轮换密钥时更是如此。
✅ 受保护文件已拦截。代理无法触碰 AGENTS.md、包清单或仓库安全配置。就这么简单。
一开始没做好的地方
❌ 第一版中,代理对“这值得写进文档吗?”的判断过于宽松。它为纯内部改动(比如 CI 调整、日志重构)生成了拉取请求。结果是 69 个拉取请求中有 9 个被关闭(约 13%)。于是我们收紧了提示词里“面向用户的变更”的定义,并加入了明确的负面示例(CI、内部辅助函数、仅测试改动)。目前这个比例正在下降。
❌ 跨仓库拉取请求需要一种镜像检出模式,而文档里并没有写明白。代理在一个仓库中工作;safe-outputs 需要找到目标仓库来推送分支。我们通过两次检出 microsoft/aspire.dev 解决了这个问题——一次作为当前工作区,一次放在 _repos/aspire.dev 下——这样 safe-outputs 处理器就能确定性地重新找到它。
❌ 大型 diff 会撑爆提示词的预算。我们在 pre-agent-steps 的 bash 里预先提取拉取请求的元数据(关联 issue、里程碑、基础 ref),这样代理拿到的是一份小而结构化的摘要,而不是巨大的载荷。这是 GitHub Agentic Workflow 里设计好的模式,也确实有效。
总结
我们做的这些改动改变了思路。一个功能在文档完成之前,都不算真正完成。文档不再像罐子后面拖着的锡罐一样跟在功能后面。工程师的评审是关卡,机器人负责打字。
关键是,这并不会取代文档编写者,而是给他们减负。过去,我们的编写者大部分时间都在逆向工程功能。现在,他们可以把时间花在只有人类才能做好的事情上:叙述性页面、示例程序、概念讲解,以及那些不会从 diff 里自然冒出来的文档部分。机器人则接手那些机械性的工作——“添加了这个新选项,把参考页面更新一下”这类没人觉得有趣的事。
自动化的秘诀:安全约束让系统更可信
非常感谢 GitHub Next 团队打造了 GitHub Agentic Workflows(特别是将安全输出(safe-outputs)作为设计中的一等公民),也感谢 Chris Swithinbank 和 Starlight 维护者们为我们提供了这套文档平台。同时还要真诚感谢安全团队的同事们,正是他们的安全护栏促使我们在第一时间就用正确的方式设计了这套方案。好的自动化背后有一个平淡无奇的秘诀:严格的安全约束反而让系统更值得信赖、更正确。
如果你在一个仓库里开发产品、在另一个仓库里发布文档——尤其是当你要在某种复杂的安全边界内完成这一切时——GitHub Agentic Workflows 值得你认真了解一下。从一个工作流开始,比如 pr-docs-check,然后观察你的文档发布中位时间会发生什么变化。
🔗 其他几个工作流
pr-docs-check 是这篇文章的主角,但它并不是孤军奋战。如果你对其他的工作流感兴趣,源码都是公开的:
-
milestone-changelog.md:每两小时运行一次,拾取当前里程碑中 newly merged 的拉取请求,维护一个 13.x-Changelog wiki 页面(新功能、改进、值得注意的 bug 修复),并附带一个对应的编辑反馈 issue。目前已经运行了 346 次。
-
release-update-support-mdx.md:在 Aspire 稳定版本发布时,在 aspire.dev 上创建一个 [support] 拉取请求,更新支持策略页面(提升新版本、降级旧版本、刷新“最后更新”徽章)。
-
update-integration-data.md:位于文档仓库中;每天运行
pnpm update:all,刷新 NuGet 元数据、GitHub 统计和示例数据,并打开一个chore: Update integration data的 PR,带有对过期运行的 supersede-and-close 逻辑。已运行 27 次,合并了 8 个拉取请求。 -
repo-pulse.md:一个滚动更新的三天仓库动态面板,固定在单个 issue 上并就地更新:最近的合并、等待审查的 PR、新 issue、讨论动态。一个 issue,始终保持新鲜。
自动化愉快,朋友们!🤖🚀