权限系统如何影响 AgentTool

目标

这页回答:

在 Claude Code 里,AgentTool 为什么不只是“启动子 agent”,而是深受 permission system 影响?权限系统到底怎样塑造 agent 行为?

相关文件:

  • src/tools/AgentTool/AgentTool.tsx
  • src/tools/AgentTool/runAgent.ts
  • src/services/tools/toolExecution.ts
  • src/utils/forkedAgent.ts

一句话结论

AgentTool 不是一个绕过权限系统的超级入口,反而是 最深度受权限系统支配的工具之一

因为一旦启动子 agent,问题就不再只是“这个工具能不能调”,而是:

  • 子 agent 的权限模式是什么
  • 谁来弹权限提示
  • 异步 agent 能不能弹提示
  • 允许哪些工具继续下钻
  • permission decision 是否继承给子 agent
  • hooks / classifier 是否继续介入

也就是说,权限系统不是挂在 AgentTool 外面的一层壳, 而是直接进入了子 agent 的运行模型。


1. 第一层:AgentTool 自己首先也是一个 Tool

这意味着 AgentTool 本身也要经过普通工具协议:

  • checkPermissions()
  • canUseTool
  • runToolUse()
  • permission decision

这点很关键

主 agent 想调用 AgentTool 先要被权限系统允许。

所以“是否允许再起一个 agent”本身,就是一个被治理的问题。


2. AgentTool 自己的 checkPermissions() 很保守

AgentTool.tsx 里能看到:

  • 某些模式下直接 allow
  • auto mode 下会走 passthrough / prompt 路线

它不是简单粗暴地总 allow。

这说明什么

系统认为:

  • 启动 subagent 不是无代价动作
  • 它会带来更多工具调用、更多副作用、更多上下文扩散

所以 AgentTool 自己就是需要权限边界的。


3. 子 agent 的 permission mode 不是天然继承父级

runAgent.ts 有一个关键函数:

  • agentGetAppState()

里面会动态重写:

  • toolPermissionContext.mode
  • shouldAvoidPermissionPrompts
  • awaitAutomatedChecksBeforeDialog
  • alwaysAllowRules

这意味着

子 agent 不是简单拿父 AppState 照抄。 而是会基于:

  • agentDefinition.permissionMode
  • 是否 async
  • 是否 bubble mode
  • 是否传了 allowedTools

重新构造自己的权限视图。

这就是为什么我说权限系统已经深入 agent runtime,而不是停留在入口处。


4. bubble permission mode 的意义

fork / 某些 agent 会使用:

permissionMode: 'bubble'

它代表什么?

不是“无权限控制”,而是:

权限提示不要在子 agent 自己那里消费,而是冒泡给父终端 / 父交互层。

为什么需要这种模式?

因为子 agent 往往:

  • 没有自己的 UI
  • 没有自己的独立交互面板
  • 但又不能直接绕过权限

所以 bubble mode 很像一种“代理审批模式”:

  • 子 agent 想做事
  • 权限决策由更高一层交互界面承接

这非常适合:

  • fork child
  • 某些同步交互型 subagent

5. async agent 与 sync agent 在权限行为上完全不同

这是最值得注意的一点之一。

runAgent.ts

会根据:

  • isAsync
  • canShowPermissionPrompts
  • agentPermissionMode === 'bubble'

推导出:

shouldAvoidPermissionPrompts

默认规律可以粗暴理解为:

sync / interactive agent

可以让权限提示浮到界面上

async / background agent

通常应该避免权限提示

为什么?

因为后台 agent 没法像前台 agent 那样稳稳占着终端等人点确认。

所以权限系统必须区分:

  • 谁可以等用户交互
  • 谁必须走自动化或拒绝路径

这就说明权限系统并不是静态规则集, 而是和执行模式强耦合的。


6. shouldAvoidPermissionPrompts 是权限治理里的关键开关

这个字段在多个地方都很重要。

它控制的是

子 agent 在遇到需要许可的工具时, 到底:

  • 能不能弹权限提示
  • 还是必须走自动决策 / 直接拒绝

createSubagentContext()

默认子 agent 的 getAppState() 还会被包一层,强制把:

shouldAvoidPermissionPrompts: true

打开。

这进一步说明默认设计哲学是:

子 agent 默认不该拥有自己的权限交互面。

除非上层显式把它塑造成一个可交互 agent。


7. awaitAutomatedChecksBeforeDialog 说明权限系统并不想太早打扰用户

runAgent.ts 里对 async but can-show-prompts 的情况,还会设:

awaitAutomatedChecksBeforeDialog: true

这说明什么

系统希望:

  • 先让 classifier / hooks / 自动规则尽量消化权限问题
  • 只有这些都解决不了,才真的弹对话框打断用户

这是很成熟的治理思路:

  • 自动化先行
  • 人类审批兜底

而不是一遇到潜在敏感动作就立刻把用户炸出来。


8. allowedTools 会把子 agent 权限面收窄

runAgent.ts 中如果传了 allowedTools,会重写:

toolPermissionContext.alwaysAllowRules.session = [...allowedTools]

同时保留 CLI 级的 allow rules。

含义

这不是简单“继承父级已批准的所有工具”, 而是允许某些子 agent 被约束成:

你只准用这一小撮工具。

这很关键,尤其对:

  • 技能型 fork
  • 特定命令型 agent
  • 最小权限执行

因为 agent 一旦能再起 agent、再调 bash,权限面会迅速膨胀。

所以 allowedTools 是很重要的收口阀门。


9. canUseTool 不会因为进了子 agent 就失效

runAgent() 调用 query() 时,还是会把:

canUseTool

继续传进去。

这意味着

子 agent 后续每一次工具调用:

  • 仍然要走权限判断
  • 仍然可能触发 classifier
  • 仍然可能被 hooks 改写 / 阻断
  • 仍然可能产生 allow / deny / ask 决策

这点非常重要。

因为如果 AgentTool 一旦起了子 agent,子 agent 内部就可以无限绕过治理, 那整个权限模型就废了。

但这套代码明显不是这么干的。


10. toolExecution.ts 里能看到权限治理的完整执行链

单次工具调用的权限链大致是:

  1. runPreToolUseHooks(...)
  2. resolveHookPermissionDecision(...)
  3. canUseTool(...)
  4. classifier / mode / rule / hook 综合出 permissionDecision
  5. allow / deny / ask
  6. 必要时再跑 denied hooks / failure hooks

这说明什么

权限系统在这里不是一个简单 if 判断, 而是一条独立的执行管线。

而 AgentTool 及其子 agent,完全生活在这条管线之内。


11. 为什么说 AgentTool 是最容易引发权限复杂性的工具?

因为普通工具只带来一次动作。

但 AgentTool 会带来:

  • 一整个新的 query loop
  • 一整套新的 tool permissions
  • 可能的后台执行
  • 可能的 remote execution
  • 可能的 forked child
  • 可能的 further delegation(如果不拦)

所以权限系统必须回答的,不再只是:

这次调用 AgentTool 允不允许?

而是:

这个子 agent 启动后,接下来会在什么权限边界内继续活着?

这才是 AgentTool 特别的地方。


12. 这套设计的核心哲学

如果把它说成人话,大概是这样:

  • 主 agent 想 delegate,可以
  • 但 delegate 本身要被批准
  • 子 agent 可以运行
  • 但它不能默认拥有和主 agent 一样的交互权
  • 子 agent 可以调工具
  • 但这些工具仍然要被持续治理
  • 某些 agent 可以更交互
  • 某些 agent 必须更自动、更克制

也就是说:

权限不是一次性发证,而是伴随 agent 生命周期持续生效。


13. 为什么这对理解整个 runtime 很重要

因为很多人看到 agent runtime,会把重点只放在:

  • prompt
  • tool calling
  • task orchestration

但真正决定一个系统是否“可上线”的,往往是治理层:

  • 权限
  • 审批
  • hooks
  • 限权
  • 自动/人工边界

Claude Code 在这块明显花了很多心思。 而 AgentTool 正是能把这些复杂性全部暴露出来的那个模块。


当前结论

权限系统对 AgentTool 的影响,不是“启动前多问一句确认”这么浅。

而是深入到:

  • 子 agent 的 permission mode
  • 提示是否可见
  • 自动检查是否先行
  • 允许哪些工具继续执行
  • 子 agent 后续每轮工具调用是否持续受控

所以如果你把 AgentTool 理解成“delegate orchestration”, 那权限系统就是它的 治理骨架

没有这层骨架,AgentTool 只会变成一个无限放大副作用的危险入口。