Skip to content
比较研究
待周评
Agent 有工具权限,为什么还要为每一次执行重新核对?从 GitHub MCP 到任务证据链
数字员工 · 工程研究

Agent 有工具权限,为什么还要为每一次执行重新核对?从 GitHub MCP 到任务证据链

Agent 拥有什么工具回答的是原则上能做什么;一次具体执行是否应该发生,还要核对当前任务、目标、版本、批准与结果。

EBR-20260826-01工程研究 · 2026-08-26English →

Agent 有工具权限,为什么还要为每一次执行重新核对?从 GitHub MCP 到任务证据链

Agent 拥有什么工具,回答的是“原则上能做什么”;一次具体执行是否应该发生,回答的是“这一次、对这个目标、依据什么,可以做什么”。两者之间,需要一条可以重新核对的证据链。

先说结论

我们最近在 CodeFlowMu 里修复了一个看起来很小的问题:一张原本可以继续执行的任务,被安全门禁错误挡住了。

CodeFlowMu 是一个本地运行的多 Agent 协作系统,用任务、角色、门禁、报告与审批,把多个 Agent 的工作组织成可追踪、可恢复、可验证的执行链。

这次问题并不是 Agent 没有工具权限,也不是任务真的越界。真正发生的是:一条本来只应该提醒负责人的信息,被运行时错误地升级成了“禁止执行”。

这次事故让我们重新检查一个更基础的问题:

如果 Agent 已经拥有 Git、Shell 或其他工具能力,为什么系统还需要在每一次具体执行发生时重新核对?

与此同时,GitHub 官方 MCP Server 也在另一个授权层上处理非常相似的问题。GitHub MCP 面对的是 OAuth 权限,CodeFlowMu 面对的是任务执行资格。两者不是同一个系统,也不是同一种授权模型,但它们正在从不同方向碰到同一个边界:

静态能力 → 当前调用 → 当前上下文 → 本次授权判断

也就是说:“有能力使用某个工具”,不能自动推出“这一次调用就有权执行”。

本文不是要宣布 CodeFlowMu 已经建立完整的 Agent 授权体系。我们更关心的是一种正在真实工程中出现的收敛:两个独立系统,都开始把授权判断从“主体拥有什么能力”,推进到“当前这一次执行是否仍然有资格发生”。 CodeFlowMu 这一边,不只有观点,还有真实失败、修复过程、回归数据和现场检查留下来的证据。

1. 一道安全检查,为什么反而挡住了合法任务?

CodeFlowMu 的一条典型工作链可以简化为:

需求 → PM → 任务 → DEV / OPS / QA → 报告 → 审核 / 批准 → 完成

这样的系统当然需要门禁。根任务已经关闭、目标超出任务范围、明确要求的正式批准还没有发生、必须满足的依赖仍然缺失,这些都可能构成合理的拒绝理由。

问题出在另一类信息。例如“最好补一份验收说明”或者“建议先把计划写得更完整”。这些信息同样有价值,但它们表达的是建议,不是禁令

这次 CodeFlowMu 的问题恰好发生在这里:一条本来只有提醒意义的信息,被运行时解释成了硬门禁。本来应该是“发现信息不完整 → 提醒负责人 → 继续由有权的人判断”,最终却变成了“发现信息不完整 → 判定条件不满足 → 禁止继续执行”。

Agent 没有越权,任务也没有越界。真正越过边界的,是安全逻辑自己。

这也是为什么我们后来发现,真正需要区分的并不是简单的“通过 / 不通过”,而是什么事实有资格直接产生拒绝

条件性质运行时行为
根任务已经关闭硬约束拒绝
目标超出任务范围硬约束拒绝
明确要求的正式批准缺失硬约束拒绝
必需依赖未满足硬约束拒绝
计划说明不完整建议性条件提醒
建议补充验收说明建议性条件提醒
建议负责人再次确认复核信号转交判断

一个系统拥有拦截能力,并不意味着它拥有对所有“不理想状态”作出拒绝的权力。所以这次事故最终留下的原则很简单:

建议不等于禁令。只有真正拥有拒绝权的事实,才应该足以产生拒绝。

真正可靠的安全系统,不只要知道什么时候应该关门,还必须知道什么时候自己没有资格关门。

2. 为什么 87 / 87 通过以后,我们仍然没有宣布修好了?

第一轮定向检查很快通过:87 / 87。如果目标只是拿到一个漂亮数字,到这里似乎已经可以结束。但一个门禁不是孤立函数,它会沿着完整链路传播:

规则 → Runtime → 接口 / Shell → 真实进程 → 页面

任何一层仍然保留旧语义,真实系统就可能继续给出错误结果。这次修复过程恰好证明了这一点:

阶段结果暴露的问题
定向派工检查87 / 87已覆盖的目标路径通过
Runtime 首轮全量FAIL旧任务仍被新门禁机械阻断
Runtime 修正后1702 / 1702指定 Runtime 回归集通过
Shell 首轮全量FAIL页面相关测试仍保留旧状态语义
Shell 修正后936 / 936指定 Shell 回归集通过
第一次现场检查FAILAPI 缺少页面仍依赖的兼容字段
修正并再次受控重启PASS运行时、接口和页面重新对齐

这里最值得注意的不是 1702,而是:1702 / 1702 通过以后,真实系统仍然存在问题。

运行时已经给出了正确状态,但 API 没有继续输出页面仍然需要的兼容字段,结果页面把“缺少字段”显示成了“冲突”。所以:测试通过,不等于系统正确。

这也是为什么我们保留第一次失败、第二次失败以及现场检查记录。如果只留下最终 PASS,很多真正有价值的工程信息反而会消失。

3. 这不是一个孤立 Bug,而是一串持续出现的边界问题

如果只有这一次误拦,很容易把它归结成“某个判断条件写错了”。但 CodeFlowMu 最近一段时间的工程记录并不是这样。

围绕任务治理,我们连续处理过:建议性计划不能自动成为否决条件;新治理规则不能机械阻断历史任务重放;批准历史怎样进入治理视图;批准记录怎样保持身份和内容一致;Session 恢复以后哪些旧事实仍然有效;Runtime 和页面怎样保持同一个任务状态解释;Retry / Recovery 以后旧状态能不能直接继续使用。

这些问题表面不同,却不断指向同一个更深的结构:一次动作能不能发生,不能只由 Agent 的角色和工具列表决定。 它还取决于当前任务、当前目标、当前版本、之前的批准是否仍然有效、当前条件究竟是建议还是硬拒绝,以及发生重试时是否可能再次产生副作用。

所以 CodeFlowMu 的研究过程不是“先提出完整理论,再做一个 Demo”,而更接近:

真实问题 → 定位原因 → 修改规则 → 实现修复 → 回归测试 → 现场验证 → 暴露新边界 → 继续研究

这也是我们把它称为工程研究的原因。不是先有结论,再找例子;而是系统真的撞到了边界,我们修复它、留下数据,再从重复出现的问题中逐步抽象模型。

4. GitHub MCP 给了一个很有价值的外部参照

GitHub 官方 MCP Server 面对的不是 CodeFlowMu 的任务治理,它主要处理 GitHub Tool 与当前凭据之间的权限关系,但它没有把所有授权问题处理成一种模式。

例如,GitHub MCP Server 的工具会声明自己可能需要的 OAuth 权限范围。不同工具需要的权限并不相同:有的需要 repo,有的需要 read:org,安全相关工具还可能需要 security_events。这意味着 Tool 本身可以声明一个基础授权边界。

更有意思的是,GitHub 对不同认证方式采用了不同策略。对于 classic PAT,可以在启动时读取 token 已有的 OAuth scope,并据此过滤 Tool:当前凭据明显没有相应权限时,对应工具可以不展示。

但 remote OAuth 并不只做这一层检查。真正调用 Tool 时,如果当前 OAuth token 缺少这一次调用所需要的权限,Server 可以返回权限范围挑战(scope challenge)

Tool 已经可用 → 发起当前调用 → 检查当前 OAuth 权限 → 足够则执行,不足则请求追加授权

GitHub MCP Server 的官方文档明确区分了这两种机制:classic PAT 是启动时的 Tool filtering;remote OAuth 则可以在具体 Tool Call 发生时重新检查 scope,当前授权不足时再发起 challenge。

GitHub 的 HTTP Server 还提供显式的 --scope-challenge 模式。在权限不足时,可以返回 403 ForbiddenWWW-Authenticate,指出当前请求需要的权限范围。

这里最重要的并不是 OAuth 本身,而是授权判断发生的位置:

当前这一次 Tool Call,本身可以成为授权检查点。

授权没有在“这个客户端已经连接了 GitHub MCP”或者“这个 Tool 已经对 Agent 可见”的时候永久结束。真正调用发生时,系统仍然可以重新问:当前已有授权,对这一次调用够不够?

这和 CodeFlowMu 在任务治理中遇到的问题非常接近。

5. 每一次重新核对,不等于每一次重新让用户批准

这里很容易产生误解。如果每执行一条命令,都弹一次“是否批准”,那不是一个成熟的授权系统,只是把所有判断重新推给用户。

本文说的“每一次重新核对”,真正指的是:每一次准备产生副作用时,系统都重新检查当前已经存在的授权事实是否仍然足够。

例如,任务仍然有效、目标仍在范围内、版本没有变化、批准仍然有效、当前动作仍被允许,那么系统完全可以自动继续执行,不需要用户重新点击一次批准。只有当某些事实发生变化,或者当前所需授权不足时,才进入追加授权、复核或拒绝。

所以:重新核对 ≠ 重新批准。

GitHub MCP 的 scope challenge 本身就是这个思路:每次调用都可以重新检查,但只有当前 scope 不足时,才请求追加授权。

6. GitHub MCP 和 CodeFlowMu,到底哪里相同?

两者解决的不是同一层问题。

GitHub MCPCodeFlowMu
当前 Tool当前 Agent
当前 Tool Call当前具体动作
当前调用参数当前任务与目标
当前 credential当前任务版本与批准
检查 OAuth scope 是否足够检查任务证据是否足够
Challenge / Execute拒绝 / 复核 / 执行

GitHub MCP 主要回答:“当前 credential 是否拥有本次 Tool Call 所需要的权限?”CodeFlowMu 需要回答:“当前 Agent 在当前任务上下文里,有没有资格让这次动作产生副作用?”

两者不能画等号,但它们共享一个结构性问题:静态能力不够。

GitHub MCP:已有 Tool → 当前 Tool Call → 核对当前 scope → 追加授权 / 执行

CodeFlowMu:Agent 已有 Tool → 当前具体动作 → 核对任务证据 → 拒绝 / 复核 / 执行

真正相同的是:当前上下文必须重新进入判断,静态能力不能单独决定执行资格。 这不是谁借鉴谁,也不是 GitHub MCP 已经实现了 CodeFlowMu 的任务授权,而是两个独立工程系统从不同授权层面碰到了同一种边界。

PR #3128 的位置

GitHub MCP Server 后续合并的 PR #3128 又向前走了一步:同一个 Tool,因为本次调用参数不同,所需要的 OAuth scope 也可能不同。

例如,写普通仓库文件和修改 GitHub Actions workflow 文件,即使经过同一个文件写入 Tool,后者仍可能需要额外的 workflow scope。这进一步说明:真正影响授权判断的,不只是 Tool 名字,还包括这一次调用到底在做什么。

但 CodeFlowMu 的工程问题不是从 #3128 才开始。我们的任务门禁、批准和状态问题此前已经在真实运行里持续出现,并留下了独立记录。因此 #3128 在本文中的位置是进一步的工程参照,而不是 CodeFlowMu 的实现来源,也不是本文提出任务级执行授权问题的前提。

7. CodeFlowMu 真正需要重新核对的,是任务事实

GitHub MCP 主要核对 OAuth scope。CodeFlowMu 需要重新核对的,则是任务上下文,至少有五类事实不能省略。

第一,任务绑定。 当前动作属于哪张任务?TASK-102 允许修改 repo-A,不能自动推出 DEV 可以顺手修改 repo-B。Agent 的工具能力属于角色,行动资格属于当前任务。

第二,目标绑定。 即使都在同一个仓库,repo-A / feature-xrepo-A / main 也可能对应完全不同的风险和批准要求。所以“DEV 有 Git”远远不够回答“这一次 push 是否允许”。

第三,当前有效性。 假设 10:00 时 Task=T1、Version=V1、Approval=valid;到了 10:15,Task 仍然是 T1,但 Version 已经变成 V2,那么之前的批准是否仍然有效就需要重新判断。重新核对不是不信任 Agent,而是不把针对旧事实作出的授权自动延伸成新权限。

第四,决策性质。 运行时至少需要分清“允许、建议、需要复核、必须批准、拒绝”。一句“最好补一份验收说明”,不能仅凭自己就推出“禁止执行”。

第五,重试与副作用。 即使授权完全正确,也不能推出外部副作用恰好发生一次。一次 push 超时以后,第一次到底根本没有执行,还是已经执行只是响应丢失,是两个完全不同的结果。因此调用级授权最终还需要和本次动作、本次尝试、幂等性以及外部执行结果建立联系。

这五类问题共同说明:授权正确 ≠ 外部副作用已经正确发生。

8. 从“工具权限表”,走向“任务证据链”

静态权限表通常可以写成:DEV → Git | OPS → Shell | QA → Test。它回答的是“谁原则上拥有什么能力”,但无法回答“为什么 DEV 现在可以对 repo-A/main 执行这一条 push”。

真正需要的,是更具体的任务证据链:

Agent 能力 → 当前任务 → 当前动作 → 当前目标 → 当前版本 → 当前批准 → 授权判断 → 实际执行 → 外部结果

这就是标题里“从 GitHub MCP 到任务证据链”真正想表达的内容。GitHub MCP 在 Tool Call 时重新核对当前 credential 的 OAuth scope;CodeFlowMu 则需要继续追问:这一次动作在当前任务上下文里的执行资格,到底来自哪里?

这也是为什么我们开始研究一种更窄的对象:执行授权回执(Execution Authorization Receipt)。概念上,它可能包含:

json
{
  "task_id": "TASK-...",
  "occurrence_id": "CALL-...",
  "actor": "DEV",
  "tool": "git",
  "action": "push",
  "target": {
    "repository": "...",
    "branch": "..."
  },
  "task_version": "...",
  "decision": "ALLOW",
  "evidence": ["..."],
  "expires_at": "...",
  "idempotency_key": "...",
  "effect_ref": "..."
}

字段名称还不是重点,真正重要的是:不同事实不能互相冒充。 Agent 有 Git,不等于这一次 push 已获授权;Agent 说“用户已经批准”,不等于批准证据真实有效;授权结果是 ALLOW,不等于外部动作已经发生;Tool 返回成功,也不等于业务结果已经验收;存在幂等键,同样不等于外部系统已经保证恰好执行一次。

这就是任务证据链比静态权限表多出来的东西。

9. 现在已经证明了什么,又还没有证明什么?

V1.9.6 已有工程证据支持:历史任务被新门禁机械阻断的问题确实出现过;建议性条件与 Runtime 硬门禁的边界被重新处理;调度相关定向检查达到 87 / 87;Runtime 指定全量回归达到 1702 / 1702;Shell 首轮仍存在旧语义断言;修正后 Shell 指定全量达到 936 / 936;第一次现场检查又发现 API → UI 状态兼容问题;修正以后再次完成全量回归和受控重启。

但这些证据不能推出:所有 Tool Call 都已经拥有执行授权回执;所有高风险动作都已经完成调用级授权;所有批准都具备完整主体身份验证;所有授权都有密码学来源证明;Retry / Resume / Delegation 都已经完整重新核对;所有外部副作用都已经获得“恰好一次”保证;CodeFlowMu 已经拥有完整通用的授权账本。

这些仍然属于下一阶段工程工作。

为什么要把这个边界写得这么清楚?因为真实工程过程已经告诉我们:87 / 87 之后仍然可能有 Runtime 全量失败;1702 / 1702 之后仍然可能有 Shell 问题;936 / 936 之后第一次现场检查仍然可能发现 API / UI 不一致。

所以真正有价值的不是某个最终数字,而是:失败 → 定位 → 修复 → 回归 → 再次暴露问题 → 再次修复 → 现场验证

我们也不会把 87、1702 和 936 相加,制造一个所谓“整个系统成功率”。它们属于不同测试集合,每一个 PASS 只能证明自己覆盖的那部分问题。这与本文讨论的授权问题遵循的是同一个原则:

一个事实能证明什么,就只让它证明什么。

最后:为什么有了工具权限,还要重新核对?

现在可以回到标题。Agent 已经有工具权限,为什么还要为每一次执行重新核对?

因为“有工具权限”只回答:“它原则上能不能使用这种能力?”而真正执行时还存在另一个问题:为什么这一次、对这个目标、在当前任务状态下,可以让这种能力产生副作用?

GitHub MCP 在 OAuth 层给出的答案之一是:当前 Tool Call 再检查当前 token scopes,不够就 challenge。CodeFlowMu 在任务治理层正在形成的答案则是:当前动作重新核对当前任务证据,不能因为 Agent 已经拥有 Tool,就自动认为本次执行具有资格。

两个系统处理的授权层不同,但它们都指向同一个边界:静态能力 ≠ 当前执行授权。

这次 CodeFlowMu 的误拦没有证明我们已经解决完整的 Agent Authorization 问题。它让我们更清楚地看见了另一件事:

真正可靠的 Runtime,不仅要防止 Agent 做它无权做的事,也必须防止安全逻辑替真正的决策者拒绝一件本来有权发生的事。

而持续的失败记录、回归数据和现场验证,正在把这个问题从一个 Bug,逐渐变成一个可以研究、可以测试、也可以继续实现的执行边界模型。

最终,一个成熟的 Agent Runtime 应该能够回答:谁,在什么任务里,准备执行什么动作,针对什么目标,依据什么仍然有效的授权事实,为什么现在可以执行,以及最终实际发生了什么。

所以:

能力属于 Agent;授权属于具体执行;证据必须跟着执行走。

公开证据

来源与证据边界

GitHub MCP Server

  • GitHub MCP Server:PAT Scope Filtering / OAuth Scope Challenges
    本文引用其对启动时 Tool filtering 与调用时 scope challenge 的区分。remote OAuth 场景中,具体 Tool Call 可以重新检查当前 scope;当前授权不足时,再请求追加授权。

  • GitHub MCP Server:Streamable HTTP Server
    HTTP 模式支持 --scope-challenge;权限不足时可以返回 403 ForbiddenWWW-Authenticate,指出所需权限范围。

  • Sam Morrow 的 GitHub MCP Server PR #3128
    2026-08-26 复核。作为进一步工程参照,该 PR 将部分 scope 判断推进为按本次调用参数计算:同一个 Tool 可以因为调用目标不同而需要不同 scope。本文不把它视为 CodeFlowMu 的实现来源,也不以它单独证明任务级执行授权模型。

CodeFlowMu

本文关于 CodeFlowMu V1.9.6 的工程结论,仅适用于本文所述版本、测试集合和受控现场检查。

公开 Execution Boundary 数据包保留了测试集合、首次失败、修复、再次验证、汇总口径、脱敏现场观察以及明确的“不支持什么”边界。更完整的内部研发过程还包括设计任务、Runtime / Shell 回归、治理修复和运行记录;公开材料只使用能够脱敏并独立说明问题的部分。

原始日志、任务正文和本机路径不公开。

本文不是独立安全审计,也不是通用 Agent 授权证明;它不证明所有 Tool Call 已经完成调用级授权,不证明完整主体身份验证或密码学授权来源,也不证明外部副作用的“恰好一次”执行,更不构成对未来版本行为的保证。

研究结论应始终与对应版本、测试集合和工程证据一起阅读。

Last updated: