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 字段做兼容性回退。