# 19. 编排型 Agent 的工具隔离机制 (Tool Isolation) > **关键发现**:仅靠 System Prompt 文字禁令(如 `` 标签)无法阻止 LLM 使用原生工具。 > 必须在 **Claude CLI 启动参数层面** 物理剥夺工具权限。 --- ## 19.1 问题背景 ### 现象 Innovator Master Agent 的 `main_skill/SKILL.md` 中明确标注了 `` 禁令: ``` ❌ `Bash` — 禁止运行任何终端命令 ❌ `Write` / `Read` — 禁止读写任何文件 ``` 然而在实际运行中,Innovator 仍然频繁调用了 `Bash`、`Write`、`Read`、`Skill`、`TodoWrite`、`TaskOutput` 等原生工具(共 15+ 次违规),**完全无视** prompt 层面的禁令。 ### 根因分析 Claude Code CLI 的原生工具(Bash、Write、Read 等)是模型底层自带的能力。在模型的决策优先级中: ``` 原生工具能力 (内置) >> System Prompt 文字禁令 (注入) ``` 当模型面对"帮我创建一个写诗的 agent"这类明确需要文件操作的任务时,它会**本能地**、**不可抗拒地**选择自己最擅长的原生工具来执行,而忽略 prompt 中的文字禁令。这不是 prompt 写得不够强的问题,而是**架构层面的权限控制缺失**。 --- ## 19.2 解决方案:CLI 启动参数级工具封印 Claude CLI 原生支持以下工具控制参数: | 参数 | 作用 | 用法示例 | |------|------|---------| | `--tools ""` | **禁用全部原生工具** | `claude --tools ""` | | `--allowedTools ` | 仅允许指定工具 | `claude --allowedTools "Read"` | | `--disallowedTools ` | 禁用指定工具 | `claude --disallowedTools "Bash Write"` | ### 对编排型 Agent 的应用 编排型 Agent(如 Innovator)不需要直接操作文件系统,它的唯一职责是输出 `[CALL: agent_name]` 文本块。因此在启动其 Claude CLI 进程时,传入 `--tools ""`,**从操作系统层面物理剥夺所有原生工具权限**。 被剥夺工具后,模型只能输出纯文本。后端的 `parser.js` 从纯文本中正则捕获 `[CALL:]...[/CALL]` 块,然后启动对应的子 Agent 进程(子 Agent 保留完整工具权限)。 --- ## 19.3 实现架构 ### ClaudeCliProtocol 层 `packages/acp-core/protocol/ClaudeCliProtocol.js` 的 `connect()` 方法新增了工具配置支持: ```javascript constructor(agentInfo = {}, options = {}) { // options.allowedTools = '' → --tools "" // options.disallowedTools = [...] → --disallowedTools ... } async connect(transport, workingDir) { const args = ['--input-format', 'stream-json', ...]; // 编排型 Agent 工具封印 if (this.options.allowedTools === '') { args.push('--tools', ''); } } ``` ### Agent Chat 路由层 `server/routes/agent-chat.js` 的 `getOrCreateProtocol()` 根据 `config.json` 中的 Agent 类型自动决定工具权限: ```javascript const agentConfig = loadAgentConfig(agentDir); if (agentConfig.type === 'orchestrator' || agentConfig.type === 'meta') { cliOptions.allowedTools = ''; // 禁用全部原生工具 } const protocol = new ClaudeCliProtocol({ name }, cliOptions); ``` ### Agent 类型与工具权限映射 | Agent Type | config.json `type` | 工具权限 | 说明 | |------------|-------------------|---------|------| | **编排型** (Innovator) | `meta` / `orchestrator` | ❌ 全部禁用 | 只能输出 `[CALL:]` 文本块 | | **执行型** (meta_hunt 等) | `skill` / `executor` | ✅ 全部开放 | 可用 Bash、Read、Write 等 | --- ## 19.4 验证标志 当系统正确运行时,控制台会打印: ``` [AgentChat] 🔒 Innovator 启用编排模式: 原生工具已全部禁用 [ClaudeCliProtocol] 启动: claude --input-format stream-json ... --tools "" ``` 如果日志中没有 `--tools ""`,说明工具封印未生效,需要检查 `config.json` 的 `type` 字段。 --- ## 19.5 设计原则总结 > **Prompt 是建议,参数是法律。** > > 对于安全关键的权限控制,永远不要依赖 System Prompt 文字禁令。 > 必须在进程启动层面通过 CLI 参数或 API 配置进行强制约束。 > Prompt 中的 `` 标签可以作为**辅助提示**保留,但不能作为**唯一手段**。 --- ## 19.6 父类继承:AgentScaffold 内置工具策略 工具隔离策略已内置于 `AgentScaffold` 父类(`packages/agent-core/AgentScaffold.js`)。 **所有通过脚手架创建的新 Agent 都会自动继承正确的工具权限**,无需手动配置。 ### config.json 自动生成的 `cli.toolPolicy` 字段 ```json { "id": "classical_poetry_writer", "type": "skill", "cli": { "toolPolicy": {} } } ``` ```json { "id": "innovator-001", "type": "meta", "cli": { "toolPolicy": { "allowedTools": "" } } } ``` ### 类型与策略映射表(定义在 AgentScaffold._writeConfig) | Agent Type | `cli.toolPolicy` | 效果 | |------------|-----------------|------| | `master` | `{ allowedTools: '' }` | 禁用全部原生工具 | | `meta` | `{ allowedTools: '' }` | 禁用全部原生工具 | | `orchestrator` | `{ allowedTools: '' }` | 禁用全部原生工具 | | `cxo` | `{ allowedTools: '' }` | 禁用全部原生工具 | | `role` | `{}` | 保留全部工具 | | `skill` | `{}` | 保留全部工具 | | `executor` | `{}` | 保留全部工具 | ### 运行时读取链路 ``` AgentScaffold._writeConfig() → config.json 写入 cli.toolPolicy ↓ agent-chat.js: getOrCreateProtocol() → 读取 config.cli.toolPolicy ↓ ClaudeCliProtocol.connect() → 拼接 --tools "" 或 --disallowedTools ↓ claude CLI 进程 → 物理层面工具权限已确定 ``` > 旧版 Agent(config.json 中没有 `cli` 字段)通过 type 字段做兼容性回退。