Claude Code权限系统全流程深度拆解:工具调用需经过的几道安全关卡
一、核心结论先行
如果仅停留在表面观察,你会误以为 Claude Code 的权限体系不过是几种模式开关:默认模式弹出确认窗、危险模式直接放行、自动模式减少干扰。
然而,深入源码后会发现,真相远比表象复杂。
一次工具调用从发起到实际执行,至少需要穿越以下关卡:
- 规则引擎
- 工具自身的
checkPermissions() - 模式分支
- 安全检查
- AI 分类器
- Hooks / UI / 多 Agent 转发
更关键的是,这并不是一套简单的“if-else 单点”逻辑。内层的 hasPermissionsToUseToolInner() 负责主干裁决,外层的 hasPermissionsToUseTool() 则处理 dontAsk、auto、headless fallback 等模式化后处理。两层协作,构成了一个精密的决策架构。
在深入拆解前,必须厘清一个最容易混淆的概念:
- TypeScript 类型联合
InternalPermissionMode确实包含 7 个值:default、acceptEdits、bypassPermissions、plan、dontAsk、auto、bubble。 - 但切勿将这 7 个值全都视为“对外公开的可选模式”。
- SDK schema 对外暴露的仅有 5 个(
EXTERNAL_PERMISSION_MODES):default、acceptEdits、bypassPermissions、plan、dontAsk。 bubble只存在于类型定义中,既不在PERMISSION_MODES运行时验证集内,也没有对应的PERMISSION_MODE_CONFIG配置——它本质上是子 Agent 内部使用的占位符。auto受TRANSCRIPT_CLASSIFIERfeature gate 约束,只有在开启后才会被加入PERMISSION_MODES,并非所有构建版本都可用。
这个区别若不提前说明,后续整篇文章都会混淆“源码内部状态”与“产品公开能力”。
二、完整执行路径:6 阶段决策流水线
工具调用的权限主干判定位于 hasPermissionsToUseToolInner() 内。其决策流程可用下图概括:
flowchart TDStart["工具调用发起"] --> S1asubgraph Stage1["阶段1:规则检查 + 工具自检"]S1a["1a. Deny Rule 命中?"] -->|是| DENY1["❌ 直接拒绝"]S1a -->|否| S1bS1b["1b. Ask Rule 命中?"] -->|是| ASK1["⚠️ 需要确认"]S1b -->|否| S1cS1c["1c. tool.checkPermissions()"] -->|allow| ALLOW0["✅ 工具直接放行"]S1c -->|deny| DENY2["❌ 工具拒绝"]S1c -->|ask| S1eS1c -->|passthrough| Stage2S1e["1e. requiresUserInteraction?"] -->|是| ASK2["⚠️ 必须人工交互"]S1e -->|否| S1fS1f["1f. 内容级 Ask Rule?"] -->|是| ASK3["⚠️ 内容级确认"]S1f -->|否| S1gS1g["1g. Safety Check?"] -->|是| ASK4["⚠️ 安全检查"]S1g -->|否| Stage2endsubgraph Stage2["阶段2:模式 / Always Allow"]S2a["2a. bypassPermissions 或特殊 plan?"] -->|是| ALLOW1["✅ 放行"]S2a -->|否| S2bS2b["2b. Always Allow 命中?"] -->|是| ALLOW2["✅ 放行"]S2b -->|否| Stage3endsubgraph Stage3["阶段3:标准化结果"]S3["passthrough -> ask"] --> Stage4endsubgraph Stage4["阶段4:外层后处理"]S4a["dontAsk 模式?"] -->|是| DENY3["❌ 自动拒绝"]S4a -->|否| S4bS4b["auto / plan+auto 语义?"] -->|是| ClassifierS4b -->|否| S4cS4c["shouldA voidPermissionPrompts?"] -->|是| HookOrDeny["Hooks -> 否则拒绝"]S4c -->|否| UserConfirm["展示确认对话框"]endsubgraph Classifier["Auto 模式分类器"]C1["不可分类的 safetyCheck?"] -->|是| DENY4["❌ 拒绝或保留人工确认"]C1 -->|否| C2C2["acceptEdits 快速路径?"] -->|通过| ALLOW3["✅ 放行"]C2 -->|否| C3C3["安全工具 allowlist?"] -->|是| ALLOW4["✅ 放行"]C3 -->|否| C4C4["调用 YOLO Classifier"] --> C5C5["分类器判定"] -->|允许| ALLOW5["✅ 放行"]C5 -->|阻止| C6C6["Denial 限制检查"] -->|超限| UserConfirm2["回退到人工确认"]C6 -->|未超限| DENY5["❌ 分类器拒绝"]end
实际上,源码中已单独抽取出 checkRuleBasedPermissions(),将 1a-1g 这组纯规则逻辑复用到多个场景。但真正决定最终结果的,依然是上面这条主干路径。
阶段 1:规则检查并非单层,而是一组带优先级的闸门
1a. Deny Rule
这一层最严格。一旦匹配,就会直接拒绝,没有任何让步余地。
更值得关注的是,Deny Rule 不仅作用于“运行时拒绝”,在工具池构建阶段就已经将工具从候选集中剥离。看看这段代码:
export function filterToolsByDenyRules(tools, permissionContext) {return tools.filter(tool => !getDenyRuleForTool(permissionContext, tool))}
这意味着模型面对的不是“工具有但你不能用”,而是“这个工具根本不存在”。
这一设计大幅降低了模型尝试绕过限制的动机。
而 Deny / Ask / Allow 规则的来源远不止配置文件那几层。源码中共有 8 种规则来源:
userSettingsprojectSettingslocalSettingsflagSettingspolicySettingscliArgcommandsession
注意,别漏掉 command 这一层——它来自内置命令(如 /init、/config),与 cliArg(CLI 启动参数)完全不同。
1b. Ask Rule
如果工具整体命中了 Ask Rule,会先得到 ask 判定。
但这里有一个关键例外:Bash 的沙箱自动放行机制。
- 当
SandboxManager.isSandboxingEnabled()为真 - 且
autoAllowBashIfSandboxed开启 - 且当前命令确实会进入沙箱执行(
shouldUseSandbox(input)返回真) - 也就是没有
excludedCommand/dangerouslyDisableSandbox这类绕过沙箱的情况
满足所有这些条件时,Ask Rule 会被跳过,继续交给 Bash 自身的 checkPermissions() 进行更精细的检查。沙箱本身已提供额外安全层,无需再弹确认框。
1c. tool.checkPermissions()
这是 Claude Code 权限系统最重要的扩展点之一。
其默认实现并非 passthrough,而是直接返回 allow:
checkPermissions: (input) =>Promise.resolve({ beha vior: 'allow', updatedInput: input })
也就是说,默认语义是“无额外限制”。真正复杂的工具(如 Bash、PowerShell、文件读写、Notebook 编辑)都会重写这一层。
这里还隐藏着一个精巧的类型设计:PermissionResult 比 PermissionDecision 多了一个 passthrough 状态。
type PermissionResult =| PermissionDecision| { beha vior: 'passthrough', ... }
passthrough 的含义是“我不做决定,交回通用流程处理”,而非“允许”。
这样,每个工具作者无需硬着头皮在 allow / deny / ask 中选择,只需在自己真正有把握的部分做出判断。
1e-1g. ask 结果的再细分
一旦工具返回 ask,系统会继续细分为三种情况:
requiresUserInteraction():某些工具即使在 bypass 模式下也必须由人工确认。- 内容级 Ask Rule:例如
Bash(npm publish:*)这类规则。 safetyCheck:比如涉及.git/、.claude/、.vscode/、shell 配置文件、跨机器桥接消息等敏感操作。
这里的核心规则是:
- 内容级 Ask Rule 对
bypassPermissions免疫。 safetyCheck对bypassPermissions同样免疫。
换句话说,“危险模式”并非“跳过所有检查”,只是跳过后段的通用确认逻辑。前面的硬闸门依然牢固锁定。
阶段 2:模式与 Always Allow
通过阶段 1 后,系统会再检查两件事。
2a. bypassPermissions
这里不仅包含直接的 bypassPermissions,还包含一种容易被忽略的特殊情况:
- 当前模式是
plan。 - 但上下文里的
isBypassPermissionsModeA vailable仍然为真。
源码会将其视为“权限可旁路”的场景。
因此,不要把 plan 简单地理解成权限系统里的一个“只读 if 分支”。plan 更像是一个流程模式,其实际行为还与工具池、ExitPlanMode、auto 语义复用等机制紧密纠缠。
2b. Always Allow
这一层才是我们传统意义上理解的白名单。
但也不要将它简单理解为“用户点一次 always allow,就永远新增一条 allow 规则”。Claude Code 的 permission prompt 返回的是结构化的 suggestions,某些“永远允许”会被翻译成更高层级的权限更新,而不是简单地加一条裸 allow rule。
阶段 3:将 passthrough 标准化为 ask
走到这一步,如果工具仍未给出明确裁决,passthrough 会被统一转换为 ask。
这一步很重要,因为后续的 UI、Hooks、classifier 逻辑只处理三种确定状态:
allowdenyask
阶段 4:外层后处理才是真正复杂的部分
外层 hasPermissionsToUseTool() 拿到内层结果后,根据不同模式进行分流:
| 模式 | 行为 |
|---|---|
default | 进入交互式确认 |
acceptEdits | 文件系统编辑类操作更容易自动放行,其他工具仍可能询问 |
bypassPermissions | 跳过通用确认,但前面的硬检查依然生效 |
plan | 更像“规划流程模式”,并非单靠权限函数将所有写操作一刀切 |
dontAsk | 将 ask 直接转为 deny |
auto | 尝试用 transcript classifier 自动判断 |
bubble | 子 Agent 的权限请求冒泡给父 Agent 终端处理 |
再次强调:auto 并非所有构建版本都可见,bubble 更是纯内部模式。若将这 7 个值直接当作“用户可选模式”,会严重夸大产品表面能力。
这里还有第二个容易混淆的点:
dontAsk是一种 mode。shouldA voidPermissionPrompts是一个上下文字段。
它们并非同一回事。
许多异步子 Agent、后台 Agent 无法弹出 UI,并非因为它们处于 dontAsk 模式,而是因为上下文被打上了 shouldA voidPermissionPrompts: true 标记。此时,系统会先尝试运行 PermissionRequest Hooks,若 Hooks 也无法决定,则自动拒绝。
三、Auto 模式:并非所有 ask 都交给分类器
auto 是 Claude Code 最具创新性的权限特性之一。它不会直接弹窗,而是将“是否拦截”的决定交给另一个模型。
但它并非“所有 ask 都无脑调分类器”。源码中包含了大量短路和特殊分支处理。
快速路径 1:模拟 acceptEdits
如果某个操作在 acceptEdits 模式下本来就能放行,那 auto 模式就没必要再浪费一次 classifier 调用。
const acceptEditsResult = await tool.checkPermissions(parsedInput, {...context,getAppState: () => ({...state,toolPermissionContext: {...state.toolPermissionContext,mode: 'acceptEdits',},}),})
这条快速路径非常实用,尤其针对工作目录内的编辑操作。
但 Claude Code 刻意将 Agent 和 REPL 排除在外,因为在这两个模式下,操作太容易“看起来安全,但实际上绕过了 classifier”。
快速路径 2:安全工具 allowlist
源码中有一组 SAFE_YOLO_ALLOWLISTED_TOOLS,覆盖了所有只读、元数据、协调类工具:
- 文件只读:
FileRead - 搜索:
Grep、Glob、LSP、ToolSearch - MCP 资源:
ListMcpResources、ReadMcpResource - 任务管理:
TodoWrite、TaskCreate、TaskGet、TaskUpdate、TaskList、TaskStop、TaskOutput - 交互 / 流程:
AskUserQuestion、EnterPlanMode、ExitPlanMode - Swarm 协调:
TeamCreate、TeamDelete、SendMessage - 其他安全工具:
Sleep、Workflow(条件编译)、以及若干 ant-only 内部工具
这些工具不需要 transcript classifier 介入,可以直接放行。
快速路径 3:真正调用 classifyYoloAction()
只有前两层都不命中时,才会进入真正的 classifier。
此时,Claude Code 会将:
- 当前动作格式化成一个
action。 - 把对话历史压缩成 classifier transcript。
- 通过一个独立的 side query 来调用模型。
- 最终得到一个
shouldBlock结果及其原因。
值得注意的是,从 telemetry 代码来看,这个 classifier 实际上是两阶段(stage1 + stage2)的,拥有独立的 token 用量和延迟追踪。也就是说,它不是简单的一次 API 调用,内部有更复杂的判定链路。这部分设计非常工程化,因为它不仅“调个模型”,还充分考虑了多种失败分支:
- transcript 过长
- API 不可用
- fail-open / fail-closed 策略
- denial tracking(拒绝计数限制)
两个容易忽视的边界
边界 1:并非所有 safetyCheck 都一样
在 safetyCheck 中,有一个字段非常关键:
classifierApprovable: boolean
这意味着:
- 有些 safety check 可以交给 classifier,让其结合上下文决定。
- 有些则完全不行,必须人工确认或直接拒绝。
举例:
- 像
.git/、.claude/、shell 配置文件这类敏感文件路径,通常classifierApprovable为true。 - 而可疑的 Windows 路径模式、跨机器桥接消息这类更硬的场景,
classifierApprovable则会设为false。
换句话说,不是“auto 模式会吃掉 safety check”,而是“auto 模式只接手其中可被分类的那一部分”。
边界 2:PowerShell 默认根本不进 auto classifier
源码中还有一个特殊分支:PowerShell 默认要求显式人工确认,除非 ant-only 的 POWERSHELL_AUTO_MODE feature 被打开。
这表明,Claude Code 的权限系统虽然有一个统一主干,但并非“所有工具一视同仁”。工具自身的特性仍有能力将某些动作拉回更保守的路径。
容错机制
这部分有两个关键细节:
- 当 transcript 过长时,交互式场景会回退到人工确认;而 headless 场景则会直接
throw AbortError(这是一个永久条件,因为 transcript 只会递增,重试没有意义)。 - 当 classifier 连续拒绝 3 次,或者累计拒绝 20 次时,系统会降级到人工确认;如果是 headless 场景,同样会直接 abort。
export const DENIAL_LIMITS = {maxConsecutive: 3,maxTotal: 20,}
这套 denial tracking 非常有价值,因为它清晰地承认:分类器会误判,所以系统必须留好退路。
四、Bash:真正和确认对话框竞速的是专用 allow classifier
关于“Bash 命令的双重分类器竞速”这一说法,需要更精确地阐明。
更准确的事实是:
- Claude Code 中确实存在两套 classifier 家族。
- 一套是
auto模式下的 transcript classifier。 - 另一套是 Bash 专用的 allow classifier。
- 但它们不会在同一次 Bash 权限判定中同时上场。
因为 Bash 的推测性 allow classifier 有明确的守卫条件:
- 在
auto模式下不跑。 - 在
bypassPermissions下不跑。 - 没有提供 prompt allow descriptions 的时候也不跑。
真正会和用户确认对话框竞速的,其实是这个 Bash 专用的 allow classifier。
sequenceDiagramparticipant TE as toolExecutionparticipant B as BashTool.checkPermissionsparticipant SC as 推测性 Bash Allow Classifierparticipant IH as interactiveHandlerparticipant D as 确认对话框participant U as 用户TE->>SC: startSpeculativeClassifierCheck(更早阶段预启动)TE->>B: 执行权限检查B-->>TE: 返回 { ask, pendingClassifierCheck }TE->>IH: 进入交互式权限处理IH->>D: 展示确认对话框par 异步竞速SC->>IH: executeAsyncClassifierCheck 消费结果alt 高置信度允许 && 用户尚未交互IH-->>D: ✅ 自动放行,展示 ✓ 后移除对话框else 不匹配 / 低置信度IH-->>D: 清除 classifier 指示器endand 用户操作U->>D: 手动批准或拒绝(200ms 宽限期后生效)end
这块实现包含三个非常出色的细节:
- 推测性预执行:
startSpeculativeClassifierCheck()在toolExecution.ts的更早阶段启动(输入解析后、权限检查前),结果缓存进Map,后续由consumeSpeculativeClassifierCheck()消费。 - 纯异步竞速:没有固定超时时间——classifier 与用户操作 / Hooks / Bridge 完全并行竞争,谁先完成谁赢。通过
createResolveOnce()的claim()方法实现原子抢占。 - 200ms 交互宽限期:用户一旦碰到对话框,自动批准逻辑就会失效(
userInteracted = true)。但前 200ms 的误触碰不会取消 classifier,以此防止意外按键打断自动流程。
这意味着,Claude Code 并非简单地“弹个窗,顺便后台跑个异步任务”,而是在非常认真地尝试减少弹窗频率,同时避免 UI 抢走用户的控制权。
五、Hooks 和 Permission Suggestions:它并非 yes/no,而是可编排的权限口袋
Hooks 与权限系统的关系,需要精准地描述。
更准确地说:
PreToolUse/PostToolUse/PostToolUseFailure属于更广义的工具生命周期。- 真正直接插入权限决策流程的,是
PermissionRequest。
| Hook 事件 | 触发时机 | 能力 |
|---|---|---|
PreToolUse | 工具执行前 | 审计、修改输入、阻止执行 |
PostToolUse | 工具执行后 | 记录结果、追加自动化处理 |
PermissionRequest | 结果为 ask 时 | 自动 allow/deny、修改输入、更新权限 |
PermissionRequest 不只服务于 headless
容易将其理解为“headless agent 的兜底方案”,但实际上它在两种场景下都会运行:
交互式场景:对话框已显示,但
PermissionRequestHook 会在后台异步执行。如果 Hook 比用户点击按钮更快做出决定,就直接接管此次权限决策。headless / async 场景:无法展示 UI,于是先跑 Hook;若 Hook 也无法决定,才会自动拒绝。
对应的源码逻辑大致如下:
if (appState.toolPermissionContext.shouldA voidPermissionPrompts) {const hookDecision = await runPermissionRequestHooksForHeadlessAgent(...)if (hookDecision) return hookDecisionreturn { beha vior: 'deny', message: AUTO_REJECT_MESSAGE(tool.name) }}
所以,更准确的总结不是“dontAsk 模式下先给 Hook 一次机会”,而是:
dontAsk会将ask转为deny。shouldA voidPermissionPrompts会触发 headless fallback 流程。- 在这个 fallback 路径上,系统仍然优先给
PermissionRequestHook 一次接管机会。
Permission prompt 返回的不是布尔值,而是结构化更新
Claude Code 的 permission prompt 不只是一个简单的“允许 / 拒绝”开关,它还可能附带结构化的 suggestions。在文件权限场景中尤其明显:
const updates = shouldSuggestAcceptEdits? [{ type: 'setMode', mode: 'acceptEdits', destination: 'session' }]: []if (isOutsideWorkingDir) {updates.push({type: 'addDirectories',directories: dirsToAdd,destination: 'session',})}
这意味着,当用户点下“Always allow”时,系统不一定是在做“加一条 allow 规则”这么粗糙的事情。
它可能做的是:
- 将当前 session 切换到
acceptEdits模式。 - 将目标目录加入 working directories。
- 或者持久化一组
updatedPermissions。
这是一个非常重要的工程细节。它说明 Claude Code 不是在堆砌规则,而是在维护一份结构化的权限上下文。
六、多 Agent:同一套权限系统,被接成了三种分流模型
在 Claude Code 中,多 Agent 并非权限系统之外的附属功能,它直接改变了权限请求的去向。
1. Fork Subagent:bubble
Fork 出来的子 Agent 会继承父 Agent 的完整上下文,但会将权限模式设置为 bubble:
export const FORK_AGENT = {permissionMode: 'bubble',tools: ['*'],model: 'inherit',}
含义非常直接:
- 子 Agent 可以继续正常工作。
- 但它没有独立的权限 UI。
- 所有需要确认的请求,都会“冒泡”回到父终端。
2. Coordinator Worker:自动检查优先,弹窗靠后
在协调者模式下,worker 会先按顺序执行:
PermissionRequestHooks- Bash classifier
- 如果都没能决定,再回退到交互式对话框。
这对应着 awaitAutomatedChecksBeforeDialog: true 的语义。
为什么这里是顺序 await,而不是像主线程那样竞速?
因为 worker 自己没有独立的终端,先把自动化分支跑完,能最大程度地减少对主 Agent 的打扰。
3. Swarm Worker:先 classifier,再 mailbox 转发给 leader
Swarm worker 的路径也不同:
- 首先尝试 classifier 自动放行。
- 如果不成功,就把 permission request 通过 mailbox 转发给 leader。
- leader 这边会弹出和本地一模一样的确认 UI。
- 用户的决策结果再回传给 worker。
这一层设计说明,Claude Code 将“谁来确认”这个问题也抽象成了权限系统的一部分。
4. 反递归保护
多 Agent 最怕的不是某一次危险操作,而是无限的套娃。
Claude Code 也注意到了这一点:
- 外部构建默认限制了 Agent 工具递归嵌套的深度。
- Fork 出来的 child 会通过历史中的 fork 标记来检测递归。
也就是说,多 Agent 的权限设计不只是“把确认框转发一下”,还得顺手把递归爆炸的风险堵上。
七、与 VS Code Agent / Copilot 的差异
以下对比仅基于 2026 年 3 月官方公开文档,不猜测任何未公开实现:
| 维度 | Claude Code | VS Code / Copilot 公开模型 |
|---|---|---|
| 权限表达方式 | 运行时多阶段决策链 | 产品级模式 + 设置项 |
| 模式表达 | 源码内部 7 种,公开能力少于 7 种 | 公开文档强调 Edit automatically / Request approval / Plan,另有 bypass 类设置 |
| 自动化决策 | acceptEdits 快速路径 + 安全工具 allowlist + transcript classifier + Bash allow classifier + Hooks | 默认工作区编辑可自动批准,其他操作按模式和设置确认;公开文档未强调类似的 runtime classifier 链路 |
| 粒度 | 工具级 + 内容级规则 + 路径安全检查 | 更偏操作类别和产品级交互设置 |
| 多 Agent 权限分流 | bubble / coordinator / swarm 三种 | 公开文档更强调统一 session 体验,未看到同级别的权限冒泡 / mailbox 转发设计 |
如果只看产品界面,你会觉得两者都在做“权限模式”。
但深入源码后,差异会非常明显:
- VS Code 这类产品:更像是在做“用户可理解的权限档位”。
- Claude Code:更像是在做“给长时间自主运行的 Agent 用的 runtime permission engine”。
这也是为什么 Claude Code 会有那么多你在 UI 上根本看不到的内部状态,例如 passthrough、bubble、shouldA voidPermissionPrompts、awaitAutomatedChecksBeforeDialog。
八、给 Agent 开发者的设计启示
从源码中学习,Claude Code 权限系统最值得借鉴的不是某条具体规则,而是几条工程原则。
1. Deny 最好直接隐藏工具,而不是靠报错来教育模型
只要模型知道一个工具存在,它就会不断尝试靠近那条能力边界。直接从工具池中删除它,比“拒绝一次”有效得多。
2. passthrough 很有用
权限系统中许多复杂的麻烦,都源于工具被过早地逼着做决定。给工具一个“我没意见”的中间态,主干架构会变得干净很多。
3. safetyCheck 不该只有一个布尔值
Claude Code 用 classifierApprovable 将 safety check 又细分为两层:
- 可以交给 classifier,让其结合上下文判断。
- 不能交,必须由人工处理。
这比“所有敏感路径一律弹窗”更精细,也比“一律交给模型判断”更稳妥。
4. “Always allow” 不一定等于“加一条 allow rule”
许多系统的权限实现还停留在布尔开关上。但 Claude Code 会把用户的批准动作翻译成:
- 切换模式。
- 添加目录。
- 持久化规则。
这才像是在维护长期会话的权限状态,而不是在堆砌例外规则。
5. headless 需要单独建模
仅靠一个 dontAsk 是不够的。你还需要一个类似 shouldA voidPermissionPrompts 的上下文字段,明确告知系统:这个 Agent 到底有没有能力展示 UI。
6. classifier 一定要有退路
Claude Code 很清醒地承认:AI classifier 会误判、会超时、会超出上下文、会不可用。
因此它准备了:
- 快速路径,来减少不必要的调用次数。
- fail-open / fail-closed 策略。
- denial limit(拒绝次数限制)。
- headless abort 机制。
这比“调一个模型试试看”的做法成熟得多。
九、最终总结
如果必须用一句话概括 Claude Code 的权限系统,我们会说:
它不是一个“权限弹窗模块”,而是一套将规则引擎、工具的局部知识、模型分类器、Hooks、UI 和多 Agent 编排紧密结合在一起的运行时决策系统。
这套设计最厉害的地方,不在于某个具体的 classifier 或某条规则本身,而在于层与层之间分工非常清晰:
- 哪些判断必须前置。
- 哪些判断可以下放给工具。
- 哪些危险不能被绕过。
- 哪些场景该交给模型。
- 模型错了以后,又应该如何优雅地退回给人类。
从工程成熟度来看,这比“多加几条白名单规则”高了不止一个层级。
如果你正在设计 Agent 的安全架构,这篇源码中最值得借鉴的,并不是它的具体实现,而是这套清晰的分层思路。
