AI Agent 审批链设计:用证据快照、版本哈希和失效条件避免误执行
一篇活动稿已经完成审核。运营负责人确认标题、正文和两张配图无误,点击“同意发布”。
十分钟后,另一个人替换了一张图、调整了标题和可见范围。定时任务仍读取到 approved=true,于是把修改后的版本发了出去。
审批人没有批准过这个版本,系统却认为它已经批准。这类问题的根因通常不是模型判断错误,而是审批数据模型太弱:它只保存“同意”,没有保存“同意了什么”。
GitHub 2026 年 9 月 1 日发布的 Copilot Pull Request 审批能力,给了一个很有价值的工程参照。官方明确区分两件事:
- approval assessment 是 Copilot 对“是否可批准”的判断,本身不计入合并要求;
- 真正可计入规则的 approval 默认关闭,必须由管理员显式授权。
权限还可以在企业、组织、仓库层配置,并限制允许审批的文件路径。Copilot 批准后如果出现新提交,原批准会被解除,需要重新评审。
这篇文章不讨论 Copilot 的评审质量,而是把这套边界抽象成一个通用的 Agent 审批链:适用于内容发布、付款、客户外联、生产变更和批量文件处理。

一、先拆开 assessment、authorization 和 execution
最危险的模型通常长这样:
{
"taskId": "publish-42",
"approved": true
}
它把三个不同主体的责任压进了一个字段:
| 层级 | 回答的问题 | 典型主体 |
|---|---|---|
| Assessment | 当前证据看起来是否满足要求? | 模型、规则引擎、质检 Agent |
| Authorization | 谁有权允许这个动作? | 业务负责人、权限策略服务 |
| Execution | 当前版本是否仍可安全执行? | 发布器、付款器、部署器 |
更合适的数据结构是分别记录:
type Assessment = {
decision: "PASS" | "FAIL" | "UNCERTAIN";
confidence?: number;
evidenceIds: string[];
rationale: string;
};
type Authorization = {
state: "PENDING" | "APPROVED" | "REJECTED" | "STALE";
approver: string;
policyVersion: string;
allowedAction: string;
};
type Execution = {
state: "READY" | "RUNNING" | "SUCCEEDED" | "FAILED" | "RESULT_UNKNOWN";
idempotencyKey: string;
resultEvidence?: string[];
};
模型可以生成 assessment,但只有拥有权限的主体才能签发 authorization。执行器则必须重新检查 authorization 是否仍适用于当前对象。
二、把 approval 设计成“证据租约”
审批不是永久事实,而是在有限时间、有限版本、有限动作上有效的一份租约。
一份最小可用的 ApprovalLease 可以这样表示:
type ApprovalLease = {
approvalId: string;
subject: {
type: "content_post" | "payment" | "deployment" | "outreach";
id: string;
version: string;
};
assessmentHash: string;
evidenceHash: string;
policyVersion: string;
approver: {
principalId: string;
role: string;
};
allowedAction: {
name: string;
resource: string;
constraints: Record<string, string | number | boolean>;
};
issuedAt: string;
expiresAt: string;
invalidateOn: InvalidationReason[];
};
type InvalidationReason =
| "SUBJECT_CHANGED"
| "EVIDENCE_CHANGED"
| "POLICY_CHANGED"
| "ACCOUNT_CHANGED"
| "SCOPE_CHANGED"
| "LEASE_EXPIRED";
这里最重要的不是字段数量,而是五个绑定关系:
- 绑定对象版本,而不是只绑任务 ID;
- 绑定审批时看到的证据;
- 绑定当时生效的权限策略;
- 绑定允许执行的具体动作与范围;
- 明确什么变化会让批准失效。
三、为内容发布建立可重复计算的版本哈希
代码评审可以直接绑定 commit SHA。内容发布没有天然的提交哈希,需要先建立规范化清单。
subject:
platform: csdn
accountId: writer-001
title: "AI Agent 审批链设计"
bodySha256: "..."
summarySha256: "..."
imageSha256:
- "cover:..."
- "body-1:..."
- "body-2:..."
tags:
- 人工智能
- AI Agent
articleType: original
visibility: public
syndication: false
计算前必须规范化:
- 标签排序,避免顺序不同造成无意义变更;
- 换行统一为
\n; - 去掉正文末尾无意义空白;
- 图片使用内容哈希或稳定资源 ID,不能只用临时 URL;
- 布尔值和枚举使用固定表示;
- 不把页面时间戳等易变字段放进哈希。
function canonicalize(input: PublishManifest): string {
const stable = {
...input,
title: input.title.trim(),
body: input.body.replace(/\r\n/g, "\n").trimEnd(),
tags: [...input.tags].sort(),
images: [...input.images]
.map(({ role, sha256 }) => ({ role, sha256 }))
.sort((a, b) => a.role.localeCompare(b.role)),
};
return stableJson(stable);
}
function subjectVersion(input: PublishManifest): string {
return sha256(canonicalize(input));
}
这样,“审批后换图”“审批后改可见范围”“审批后换账号”都会得到新的 subjectVersion。
四、证据快照不能只保存一句“检查通过”
审批人真正看到的内容,要变成可回读的证据:
{
"evidenceVersion": 1,
"capturedAt": "2026-09-03T10:42:00+08:00",
"source": "real-platform-page",
"checks": [
{ "name": "account", "expected": "writer-001", "actual": "writer-001", "ok": true },
{ "name": "title", "actualHash": "sha256:...", "ok": true },
{ "name": "bodyImages", "expected": 2, "actual": 2, "ok": true },
{ "name": "cover", "actual": "loaded", "ok": true },
{ "name": "visibility", "actual": "public", "ok": true },
{ "name": "syndication", "actual": false, "ok": true }
]
}
不要只信预填接口返回 ok=true。如果真正执行发生在第三方页面,就应从真实页面读取标题、正文首尾、图片状态和发布选项。工具返回证明“自动化尝试过”,页面快照才证明“用户将要提交什么”。
五、状态机:对象一变,批准立刻过期

推荐状态机:
DRAFT
→ ASSESSED
→ WAITING_APPROVAL
→ APPROVED
→ EXECUTING
→ SUCCEEDED
APPROVED
→ STALE_APPROVAL # 对象、证据、策略或范围变化
→ WAITING_APPROVAL # 重新构建证据后再审
EXECUTING
→ FAILED
→ RESULT_UNKNOWN # 已点击,但无法确认平台结果
关键约束是:任何修改事件都不能直接把状态留在 APPROVED。
ApprovalLease invalidateIfChanged(
ApprovalLease lease,
String currentSubjectVersion,
String currentEvidenceHash,
String currentPolicyVersion) {
if (!lease.subject().version().equals(currentSubjectVersion)) {
return lease.stale("SUBJECT_CHANGED");
}
if (!lease.evidenceHash().equals(currentEvidenceHash)) {
return lease.stale("EVIDENCE_CHANGED");
}
if (!lease.policyVersion().equals(currentPolicyVersion)) {
return lease.stale("POLICY_CHANGED");
}
if (Instant.now().isAfter(Instant.parse(lease.expiresAt()))) {
return lease.stale("LEASE_EXPIRED");
}
return lease;
}
这和 GitHub 在新提交后解除旧批准的逻辑相似:批准针对的是当时那份 diff,不是这个 PR 名称未来产生的所有内容。
六、执行前使用 compare-and-act
仅在页面回读后比较哈希还不够。如果“校验”和“点击”之间还能被并发修改,就存在 TOCTOU(检查时与使用时不一致)问题。
执行器至少应完成:
1. 读取真实目标状态
2. 重新计算 subjectVersion / evidenceHash
3. 校验 approval lease
4. 原子领取执行权
5. 执行动作
6. 回读平台结果
7. 写入结果证据
数据库层可以用条件更新防止重复领取:
UPDATE approval_execution
SET state = 'EXECUTING',
started_at = CURRENT_TIMESTAMP
WHERE approval_id = :approvalId
AND subject_version = :subjectVersion
AND state = 'APPROVED'
AND expires_at > CURRENT_TIMESTAMP;
受影响行数必须等于 1。若为 0,可能是批准已被消费、已过期或版本不匹配,应停止而不是重试点击。
幂等键建议包含:
sha256(approval_id + subject_version + allowed_action + target_account)
它的作用不是保证第三方平台一定幂等,而是防止本系统把同一份批准并发消费两次。
七、按风险定义失效矩阵
不是所有变化都要找人重审。可以先建立确定性的失效矩阵:
| 变化 | 低风险动作 | 高风险动作 |
|---|---|---|
| 仅格式空格变化 | 自动重新计算并留痕 | 规则允许时自动续签 |
| 同哈希资源重新上传 | 自动确认 | 自动确认 |
| 标题、正文语义变化 | 重新评审 | 重新评审 |
| 目标账号变化 | 重新授权 | 重新授权 |
| 可见范围变化 | 重新授权 | 重新授权 |
| 金额、收件人、生产环境变化 | 不适用 | 必须重新授权 |
| 工具权限扩大 | 重新授权 | 阻断并升级 |
| 结果无法确认 | 停在 RESULT_UNKNOWN | 停在 RESULT_UNKNOWN |
“自动续签”只适合规则能确定证明等价的变化。不要让另一个 LLM 自己判断“这次修改应该不影响审批”,否则审批边界又回到了概率输出。
八、错误码要让操作人员知道下一步
建议至少提供:
[
"APPROVAL_SUBJECT_CHANGED",
"APPROVAL_EVIDENCE_CHANGED",
"APPROVAL_POLICY_CHANGED",
"APPROVAL_SCOPE_MISMATCH",
"APPROVAL_EXPIRED",
"APPROVAL_ALREADY_CONSUMED",
"EXECUTION_RESULT_UNKNOWN"
]
每个错误码都应配最小恢复动作:
APPROVAL_SUBJECT_CHANGED:重新构建快照并请求评审;APPROVAL_SCOPE_MISMATCH:不能扩大动作范围,必须重新授权;EXECUTION_RESULT_UNKNOWN:只读回查结果,不得盲目重复提交。
九、这也是我们做 Tipkay 时的一个取舍
我们在 Tipkay 的博客发布助手里,把研究、写稿、配图、平台预填和最终提交拆成不同阶段。助手可以把内容送到编辑器,但在最后提交前仍要从真实页面回读账号、标题、正文、图片、标签和可见范围。
这不是为了多一个确认步骤,而是让批准绑定实际交付物。生成完成不等于工作完成,工具返回成功也不等于平台里的当前版本已被正确提交。
Tipkay 面向小微企业、一人公司和小团队,提供按实际使用量计费的垂类 AI 员工;每个助手带着自己的岗位经验、流程、Skill、MCP 和工具。对小团队而言,岗位分工真正有价值的前提,是每次交接都有明确对象、证据和责任边界。
一个人的生意,也能有一支专业团队。但专业团队不会把一句“没问题”当作永久授权。
十、可直接执行的落地顺序
- 列出所有会产生外部副作用的 Agent 动作;
- 将 assessment、authorization、execution 拆成独立状态;
- 为每类对象定义规范化 manifest 和版本哈希;
- 保存真实执行目标的证据快照;
- 定义权限策略版本、批准主体和动作范围;
- 列出对象、证据、策略、账户与范围的失效条件;
- 用条件更新或锁原子领取执行权;
- 提交后回读真实结果,无法确认时进入 RESULT_UNKNOWN;
- 用变更后旧批准失效、重复提交、并发领取和超时场景做契约测试。
最后验收时,不要只问“审批按钮能不能点”。应验证:
- 批准后改标题,旧批准是否失效;
- 批准后换账号,执行是否被阻断;
- 同一批准并发执行两次,是否只有一次领取成功;
- 点击后页面没有明确结果,系统是否停止而非再次点击;
- 审计日志能否还原批准时对象、证据、策略和权限。
这些测试通过,approval 才从界面上的布尔值,变成执行器可以信任的工程对象。
参考资料
- GitHub Changelog, Copilot code review can now approve pull requests: https://github.blog/changelog/2026-09-01-copilot-code-review-can-now-approve-pull-requests/
- GitHub Docs, Configuring code review by GitHub Copilot: https://docs.github.com/en/copilot/how-tos/copilot-on-github/set-up-copilot/configure-code-review
- GitHub Docs, Available rules for rulesets: https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-rulesets/available-rules-for-rulesets
- GitHub Docs, REST API endpoints for pull request reviews: https://docs.github.com/en/rest/pulls/reviews
更多推荐

所有评论(0)