ZeroClaw Git 事件驱动的 SOP Fan-In:从 Git Forge 事件到 SOP 运行的全链路触发指南
【免费下载链接】zeroclawFast, small, and fully autonomous AI personal assistant infrastructure, any OS, any platform — deploy anywhere, swap anything 🦀项目地址: https://gitcode.com/gh_mirrors/ze/zeroclaw
导读
本文讲解 ZeroClaw 的Git SOP Fan-In:如何让 GitHub、Gitea、Forgejo 等 Git 平台的仓库事件(PR 打开、Issue 创建、评论、CI 失败、版本发布等)自动启动一条 Standard Operating Procedure(标准操作规程,简称 SOP)运行。你将掌握事件如何从 Forge 路由到 SOP 入口(ingress)、channel触发器如何匹配事件、condition如何精确筛选事件,以及如何配置、触发、排查和审批这类自动化流程。读完本文,你可以直接落地“PR 自动分诊”“CI 失败自动处理”“Issue 自动归类”等事件驱动的自动化运维管线。
本文是 SOP Fan-In 系列中 Git 源头的专门指南。传输层(provider、Forge 认证、轮询、仓库范围、事件路由表)在 Git 通道文档 中配置;本文聚焦于触发器这一侧——哪些事件类型能进入 SOP 入口,完全由通道的逐事件
events路由表决定。
一、核心机制:Forge 事件如何变成 SOP 运行
Git Forge 事件可以启动 SOP 运行。当一个事件类型在通道的事件表(eventstable)中被路由到sop时,Git 通道会把标准化后的 Forge 事件提升(lift)为一个 SOP 事件,并派发给 SOP 引擎:
- 事件主题(topic):
git.<alias>:<event_type>,例如git.main:pull_request.opened; - 结构化 JSON 载荷:携带仓库(repo)、Issue/PR 编号、作者、标题、正文等字段;
- 分发目标:SOP 引擎(engine)的
dispatch_sop_event入口。
从源码看,这条链路在 crates/zeroclaw-channels/src/git/events.rs 的event_to_sop_message中完成:Git 通道把标准化事件包进一个ChannelMessage信封,主题通过ChannelSopTopic::build(channel_key, alias, event_type)生成,载荷由event_payload序列化为结构化 JSON,并设置内部标记internal_sop_event,由编排器(orchestrator)识别并送入 SOP 入口,而不是作为聊天消息进入 LLM 循环。值得注意的是,该内部标记只有 Git 生产者会设置,因此外部用户无法通过伪造消息subject来冒充 SOP 事件。
在 crates/zeroclaw-channels/src/git/channel.rs 的dispatch_event中,路由决策由router::resolve_route(event.event_type(), &self.cfg.events)完成,三种结果分别是:Ignore(静默丢弃)、Message(按对话消息投递)、Sop { sop }(投递到指定 SOP 入口)。
触发与匹配概览
| 环节 | 机制 | 说明 |
|---|---|---|
| 事件来源 | Forge REST API 轮询 | 通道按since游标轮询,无需公网 URL 或 Webhook 暴露,可运行在 NAT 之后 |
| 事件标准化 | GitEvent枚举 | 跨 Forge 统一为 9 种事件类型(见下文),与 provider 无关 |
| 路由决策 | events路由表 | 逐事件类型指定message或sop = "<name>" |
| SOP 入口 | dispatch_sop_event | 单一匹配器评估所有已加载 SOP 的触发器,命中即启动运行 |
| 主题 | git.<alias>:<event_type> | 通道生成的 SOP 事件主题,不是触发器字段 |
| 触发器 | channel触发器 | 匹配通道种类git与可选alias |
二、Mention Gate 与作者检查:哪些事件被放行
SOP 路由在闸门上与对话路径有明确差异:
- Mention 门槛仅对
sop路由的生存周期事件放宽:例如一个pull_request.opened被路由到分诊 SOP 后,无论作者是否在评论中@提及应用,都会触发 SOP。这由源码中的gate_mentions参数控制——对话路径传true,SOP 路径传false(见 crates/zeroclaw-channels/src/git/events.rs 与 crates/zeroclaw-channels/src/git/channel.rs)。 - 作者检查仍然适用于每一次投递(无论 SOP 还是对话):
- 来自应用自身账户的事件一律丢弃(
admit_author中对bot_login的大小写不敏感比较); - 来自其他 Bot 的事件按
listen_to_bots决定是否放行; - 对 actor 的 login 强制实施 peer-group 允许名单(allowlist)。
- 来自应用自身账户的事件一律丢弃(
- 无作者的事件(例如没有 actor 的 workflow run)直接丢弃,避免绕过作者过滤器。
一句话概括:sop路由绕过mention_only,但不绕过自我/Bot 过滤,也不绕过作者允许名单。
相关测试见 crates/zeroclaw-channels/src/git/events.rs:unmentioned_comment_dropped_only_on_the_gated_path(未提及的评论只在门控路径被丢弃,SOP 路径放行)、own_bot_events_always_dropped_even_ungated(自身 Bot 事件即使未门控也始终丢弃)、foreign_bot_respects_listen_to_bots(外部 Bot 遵守listen_to_bots)。
三、触发器与匹配规则(Trigger & Matching)
一个channel触发器匹配:
channel:通道种类,固定为git;- 可选
alias:配置的实例名,例如default或main; - 没有
topic字段:哪些事件类型能进入 SOP 入口完全由通道的逐事件events路由表决定。
因此:触发器选择来源(source),路由表选择事件(event)。日志中看到的git.<alias>:<event_type>字符串是通道生成的事件主题,不是触发器字段。如果想把触发器限定到单个事件类型,用condition对载荷做测试,例如$.event_type == "pull_request.opened"。
事件的结构化 JSON 载荷会被转发进 SOP 事件,可用于可选的触发器condition;步骤上下文(step context)接收的是**截断、净化、加框(capped, sanitized, framed)**后的形式——未受信任的外部文本不会直接拼入模型上下文。一个 JSON-path 形式的condition如$.repo == "octo/repo"可以把 SOP 收窄到单个仓库。
已知事件类型
以下 9 种是已知事件类型,与 crates/zeroclaw-channels/src/git/types.rs 中GitEvent枚举的变体一一对应:
| 事件类型 | 说明 | 载荷关键字段 |
|---|---|---|
issue_comment.created | Issue/PR 上的新评论 | repo,number,comment_id,author,body |
issues.opened | 新 Issue 的开帖 | repo,number,issue_id,author,title,body |
pull_request.opened | 新 PR 的开帖 | repo,number,author,title,body |
pull_request.closed | PR 未合并关闭 | repo,number,author,title,html_url |
pull_request.merged | PR 已合并 | repo,number,author,title,html_url |
pull_request_review_comment.created | 行内评审评论 | repo,number,comment_id,author,body |
workflow_run.completed | CI 工作流运行成功完成 | repo,run_id,attempt,name,branch,run_number,pr_number,actor,html_url |
workflow_run.failed | CI 工作流运行失败 | 同上(verdict 为 failed) |
release.published | 发布新版本 | repo,release_id,tag,name,author,body,html_url |
此外,所有 SOP 事件载荷都包含公共字段:source(恒为"channel")、channel(git)、channel_alias、provider(如github)、sop(目标 SOP 名)、topic(git.<alias>:<event_type>)、event_type、dedup_id、created_at、target。这一形状由 crates/zeroclaw-channels/src/git/events.rs 的common_payload构造,并有测试sop_message_carries_reserved_subject_and_structured_payload逐一断言这些字段(见 crates/zeroclaw-channels/src/git/events.rs)。
四、配置实战:把事件路由到 SOP
完整的触发链路由两部分配置组成:通道侧的事件路由+SOP 定义侧的触发器。
4.1 通道侧:events路由表
在 Git 通道配置中,为每个事件类型设置路由。GitEventRoute结构定义于 crates/zeroclaw-config/src/schema.rs,只有两个字段:
message: bool:把事件作为普通通道消息投递到 agent 循环;sop: Option<String>:把事件路由到指定名称的 SOP 入口。
一个事件条目sop与message都没设置时,显式禁用该事件类型。配置示例:
[channels.git.main] enabled = true provider = "github" # ... 认证相关配置 ... [channels.git.main.events.pull_request.opened] sop = "pr-triage" # PR 打开 → 分诊 SOP [channels.git.main.events.issues.opened] sop = "issue-triage" # Issue 打开 → 归类 SOP [channels.git.main.events.issue_comment.created] message = true # 普通评论仍走对话路径 [channels.git.main.events.workflow_run.failed] sop = "ci-failure" # CI 失败 → 自动处理 SOP [channels.git.main.events.release.published] message = true # 版本发布通知 agent路由决策逻辑在 crates/zeroclaw-channels/src/git/router.rs 的resolve_route中:sop非空时优先(即使同时写了message = true),其次message,否则Ignore。
关键设计:路由即订阅。通道从路由表推导要轮询哪些 API 端点(TransportPlan::from_routes,见 crates/zeroclaw-channels/src/git/router.rs):评审评论、Releases、Actions 运行等端点只有在其事件类型被路由后才会被轮询。因此:
- 未配置的通道成本与之前完全一样;
- 列表
workflow_run.failed不会关闭对话(未在非空表中列出的事件类型沿用默认行为:issue_comment.created、issues.opened、pull_request.opened按对话投递,其余忽略); - 只有被路由的事件类型才被轮询(poll)。
启动时validate_routes(crates/zeroclaw-channels/src/git/router.rs)会对未知事件类型拼写错误和空的sop = ""条目给出启动警告。
4.2 SOP 侧:channel触发器
加载一个 SOP,其channel触发器声明通道(channel = "git",可选alias)。SOP.toml放在sops_dir(默认未设置,需在配置中显式开启,默认文档值为shared/sops)下的子目录中:
# <sops_dir>/pr-triage/SOP.toml [sop] name = "pr-triage" description = "Triage newly opened pull requests" version = "1.0.0" [[triggers]] type = "channel" channel = "git" alias = "main" condition = '$.event_type == "pull_request.opened"'关于sops_dir:默认未设置(运行时 SOP 执行默认关闭,直到运维显式开启);相对值基于安装根目录解析,文档化值shared/sops解析为<install>/shared/sops。用zeroclaw sop validate <name>校验 SOP。
4.3condition表达式:精确筛选事件
触发器condition与步骤when:守卫共用同一套表达式语法(完整语法见 SOP 语法参考)。触发器条件针对事件载荷求值,fail-closed(条件无效、载荷缺失、JSON path 无法解析、数值比较双方非数字时都视为不匹配);空条件无条件匹配。
常用 Git 事件筛选示例:
| 表达式 | 效果 |
|---|---|
$.event_type == "pull_request.opened" | 只匹配 PR 打开事件 |
$.repo == "octo/repo" | 只匹配octo/repo仓库 |
$.repo == "octo/repo" && ... | 不支持!单个条件只允许一次比较,无AND/OR/NOT |
$.number > 100 | 按编号数值筛选(数值比较) |
$.author.login == "dependabot[bot]" | 按作者 login 筛选 |
语法要点:
- 以
$开头的 JSON path 形式:$.path.to.field <op> <value>,点分路径,数组元素用数字段($.readings.1),不支持括号语法; - 比较运算符:
==、!=、>、>=、<、<=(单一运算符,解析器最长优先匹配 token); - 字符串字面量要加双引号(
$.status == "critical");JSON 布尔值会被转换为字符串"true"/"false",因此写$.active == "true"; - 数值比较:两侧都能解析为数字时按数值比较,否则按字符串比较。
五、触发一次运行(Fire it)
按以下三步即可触发一次 SOP 运行:
- 路由事件类型到 SOP:在通道配置的
events表中写一个带sop = "<name>"的条目; - 加载匹配的 SOP:SOP 的
channel触发器声明channel = "git"(可选alias); - 制造 Forge 事件:打开或评论一个 Issue/PR、发布一个 Release、或让一次 workflow run 结束。
通道随后会:标准化事件 → 筛选载荷安全性(作者检查 + peer-group 允许名单)→ 分发给每个已加载且channel触发器匹配、condition(如有)成立的 SOP。路由一个事件类型同时也订阅了对应的 Forge 端点,所以只有被路由的事件类型会被轮询。
排查:如果什么都没启动
| 症状 | 检查点 |
|---|---|
| 事件被当作对话消息而不是 SOP | 确认事件类型被路由到sop(而不是留在对话默认值) |
| SOP 触发器不匹配 | 确认 SOP 的channel触发器写了channel = "git"及正确的alias |
| 条件不成立 | 对照载荷核对condition表达式 |
| 事件从未到达 | 确认事件类型已在路由表中订阅(路由即订阅),且通道在运行轮询 |
更多症状与对策参见 Fan-In 总览的排查表。特别地,sop路由仍然受作者允许名单约束——空/错误配置的 allowlist 会吞掉所有事件(包括sop路由的 PR 生命周期事件),首次丢弃会以 WARN 日志提示“dropping events from sender outside the peer allowlist”(见 crates/zeroclaw-channels/src/git/channel.rs)。
六、审批与观察(Approve and observe)
SOP 运行到达检查点(checkpoint)时会暂停为WaitingApproval。两类方式处理:
CLI 方式:
zeroclaw sop list # 列出运行状态,找出 WaitingApproval zeroclaw sop approve # 批准暂停的运行 zeroclaw sop pending # 查看所有暂停待审的运行带外(out-of-band)Gateway API 方式:通过 Gateway API 的审批端点:
GET /admin/sop/pending:列出暂停待人工审批的运行(WaitingApproval);POST /admin/sop/approve:批准;POST /admin/sop/deny:拒绝(取消)。
这些路由在 crates/zeroclaw-gateway/src/lib.rs 中注册,处理实现位于 crates/zeroclaw-gateway/src/api_sop.rs。审批端点需要 gateway 配对认证(paired-token bearer 认证),与 Webhook 路由共享限流器。
七、深入原理:从 Forge 事件到引擎分发的完整调用链
把上文各环节串起来,一条pull_request.opened事件的完整路径是:
- 轮询:Git 通道按
TransportPlan(由路由表推导)轮询 Forge REST API,命中issues端点族(因为pull_request.opened被路由); - 标准化:provider 把原生载荷映射为统一的
GitEvent::PullRequestOpened(crates/zeroclaw-channels/src/git/events.rs),事件类型为pull_request.opened,去重 ID 为ghpr_octo/repo#12(PR 没有传输稳定的对象 ID,以owner/repo#number作为身份键); - 路由决策:
dispatch_event调用resolve_route得到RouteAction::Sop { sop: "pr-triage" }(crates/zeroclaw-channels/src/git/channel.rs); - SOP 事件构造:
event_to_sop_message生成主题git.main:pull_request.opened、结构化 JSON 载荷与internal_sop_event标记,经通道监听总线送达编排器; - 引擎分发:编排器把事件交给 SOP 引擎的
dispatch_sop_event(crates/zeroclaw-runtime/src/sop/dispatch.rs),单一匹配器对每个已加载 SOP 的触发器求值; - 运行启动:匹配成功则启动运行,通过
SopAuditLogger持久化运行启动审计;在非 agent 循环上下文中,ExecuteStep动作记录为 pending 而非静默执行(headless 安全,见 Fan-In 总览)。
引擎侧测试dispatch_to_named_sop_filters_matching_channel_triggers(crates/zeroclaw-runtime/src/sop/dispatch.rs)验证了channel = "git"、alias = "main"的触发器精确匹配行为。
八、实操示例:一个完整的 PR 分诊 SOP
以下是一个可直接落地的完整示例:收到octo/repo仓库的新 PR 后,自动生成评审摘要并请求人工审批。
1. 通道配置(config.toml):
[channels.git.main] enabled = true provider = "github" # App ID / private key 等认证配置见 Git 通道文档 [channels.git.main.events.pull_request.opened] sop = "pr-triage"2. SOP 清单(<sops_dir>/pr-triage/SOP.toml):
[sop] name = "pr-triage" description = "Triage newly opened pull requests in octo/repo" version = "1.0.0" [[triggers]] type = "channel" channel = "git" alias = "main" condition = '$.repo == "octo/repo" && $.event_type == "pull_request.opened"'注意:
condition只允许单次比较,不支持&&。上面的写法会产生无效条件(fail-closed 不匹配)。正确做法是只保留一个条件,或在 SOP 内用步骤when:做多级判断。例如用$.event_type == "pull_request.opened"触发,再在SOP.md步骤中用when:守卫($.steps.1.repo == "octo/repo")过滤。
3. SOP 步骤(<sops_dir>/pr-triage/SOP.md):
# PR triage Classify a newly opened PR and prepare an operator-facing summary. ## Steps 1. **Classify** - Inspect the PR payload and extract severity and area. - output: {"type":"object","required":["repo","number","severity"],"properties":{"repo":{"type":"string"},"number":{"type":"integer"},"severity":{"type":"string"}}} - next: 2 2. **Approval gate** - Require explicit approval before any action. - kind: checkpoint - requires_confirmation: true - next: 3 3. **Post summary** - Comment the triage summary back to the PR thread. - kind: capability - capability: forge.comment - with: { repo = $.steps.1.repo, number = $.steps.1.number, body = "Triage summary..." }forge.comment能力步骤通过 Git 通道的出站路径把评论发回 Issue/PR(provider 无关:GitHub / Gitea / Forgejo),与检查点组合可构成无头(headless)评审管线(详见 SOP 语法参考)。这些注入式适配器只在daemon / channel-start路径注入;独立 agent 运行与 CLI 校验会 fail-closed。
4. 校验与运行:
zeroclaw sop validate pr-triage # 校验 SOP zeroclaw daemon # 或 zeroclaw channel start,驱动轮询与 SOP 维护在仓库打开一个 PR 后,SOP 运行即被触发;到达检查点时用zeroclaw sop list/zeroclaw sop approve或 Gateway API 审批。
九、安全基线
- 公共仓库的 Issue/PR 评论是不可信输入:保持
mention_only = true(对话路径)、用 peer group 门控发送者(空 peer 集合拒绝所有人,["*"]接受任何人),公开仓库把 autonomy 保持在Supervised或更低。 - SOP 入口对不可信输入有多层防护(见 Fan-In 总览的安全默认值):主题与载荷文本被截断、归一化、prompt-guard 筛查、加框后才进入模型上下文;
untrusted_input_guard = "block"可拒绝不安全的不可信事件(BlockedUnsafe),默认warn审计放行;无头分发只记录运行进度而不自动执行ExecuteStep。 - 作者闸门永远生效:
sop路由跳过的是mention_only,自我/Bot 过滤与作者 allowlist 对每次投递仍然适用。
相关文档
- Git 通道:provider、Forge 认证、轮询、事件路由
- Fan-In 总览
- SOP 语法参考:
SOP.toml/SOP.md格式与condition语法 - SOP 如何运行
- Gateway API:审批端点
【免费下载链接】zeroclawFast, small, and fully autonomous AI personal assistant infrastructure, any OS, any platform — deploy anywhere, swap anything 🦀项目地址: https://gitcode.com/gh_mirrors/ze/zeroclaw
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考