19-orchestrator-tool-isolation.md 5.66 KB

19. 编排型 Agent 的工具隔离机制 (Tool Isolation)

关键发现:仅靠 System Prompt 文字禁令(如 <CRITICAL> 标签)无法阻止 LLM 使用原生工具。 必须在 Claude CLI 启动参数层面 物理剥夺工具权限。


19.1 问题背景

现象

Innovator Master Agent 的 main_skill/SKILL.md 中明确标注了 <CRITICAL> 禁令:

❌ `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 <tools> 仅允许指定工具 claude --allowedTools "Read"
--disallowedTools <tools> 禁用指定工具 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() 方法新增了工具配置支持:

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 类型自动决定工具权限:

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 中的 <CRITICAL> 标签可以作为辅助提示保留,但不能作为唯一手段。


19.6 父类继承:AgentScaffold 内置工具策略

工具隔离策略已内置于 AgentScaffold 父类(packages/agent-core/AgentScaffold.js)。 所有通过脚手架创建的新 Agent 都会自动继承正确的工具权限,无需手动配置。

config.json 自动生成的 cli.toolPolicy 字段

{
  "id": "classical_poetry_writer",
  "type": "skill",
  "cli": {
    "toolPolicy": {}
  }
}
{
  "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 字段做兼容性回退。