---
title: "harness - 能力控制：Talent 启用禁用与工具权限"
---


本文专门讲**能力的开关控制**：一个内置 Talent 要被模型看见、能被调用，到底要过哪几道闸门；`toolsAdd(...)` 与 `disallowedToolsAdd(...)` 的真实语义；以及哪些开关"即时生效"、哪些必须重建主代理。

> 相关文章：《配置参考》给出全部配置项的默认值；《工具名映射与权限解析参考》给出工具名到 Talent 的完整映射；《子代理定义与任务委派》讲定义级（front matter）的 `tools` / `disallowedTools`；《挂载机制详解》讲单技能与挂载级别的禁用。本文只讲**引擎层**的启用禁用与工具权限。

### 1、能力的四道闸门

一次请求里，某个工具能否出现，要依次通过四道闸门：

| 闸门 | 判定位置 | 判定内容 |
|---|---|---|
| A 工具清单 | `HarnessEngine.Builder.toolsAdd(...)` / `AgentDefinition` 的 `tools` | 该工具名是否被声明 |
| B 禁用名单 | `AgentFactory.toolAddDo()` | 是否被**定义级**或**全局级** `disallowedTools` 命中 |
| C Talent 启用位 | `TalentUtil.activeTalents()` | 承载它的 Talent `isEnabled()` 是否为 `true` |
| D 运行前置条件 | `TalentUtil.activeTalents()` | `isSupported(prompt)` 是否成立（有无 provider / 服务器 / 可用工具） |

前两道闸门发生在**构建 Agent 时**（决定工具是否进入注册表）；后两道发生在**每次请求时**（决定这次是否注入工具与指令）。

```java
// AgentFactory.create：先展开快捷权限，再逐个走 toolAddDo
for (String toolName : metadata.getTools()) {
    if ("**".equals(toolName)) { /* 展开 TOOL_ALL_FULL 后逐个 toolAddDo */ }
    else if ("*".equals(toolName)) { /* 展开 TOOL_ALL_PUBLIC 后逐个 toolAddDo */ }
    else if ("pi".equals(toolName)) { /* 展开 TOOL_PI 后逐个 toolAddDo */ }
    else { toolAddDo(engine, builder, terminalTalentProxy, metadata, toolName); }
}
```

```java
// AgentFactory.toolAddDo：闸门 B —— 两个层级的禁用名单，命中即 return
if (metadata.getDisallowedTools().contains(toolName)) return;   // 定义级
if (engine.getDisallowedTools().contains(toolName)) return;     // 全局级

// switch 里对应到具体 Talent / 拦截器，例如：
case "task":     engine.getTaskTalent().setEnabled(engine.isSubagentEnabled());
                 builder.defaultTalentAdd(engine.getTaskTalent()); break;
case "hitl":     engine.getHitlInterceptor().setEnabled(engine.isHitlEnabled());
                 builder.defaultInterceptorAdd(engine.getHitlInterceptor()); break;
```

```java
// TalentUtil.activeTalents：闸门 C / D —— 请求时过滤，跳过者既不注入指令也不部署工具
if (talent.isEnabled() == false || talent.isSupported(prompt) == false) {
    continue;
}
```

### 2、内置 Talent 与开关对照

| 能力 | 工具名 | 承载者 | 关闭方式 | 生效时机 |
|---|---|---|---|---|
| 文件与终端 | `read` `write` `edit` `glob` `grep` `ls`(`list`) `bash` | `TerminalTalentProxy` → `TerminalTalent` | 从工具清单移除，或加入禁用名单 | 工具权限变更需重建主代理 |
| 异步终端 | `bash_start` `bash_wait` `bash_stop` | 同上 | 同上；**或** `bashAsyncEnabled(false)`（默认 false） | 开关即时生效（`getTools()` 时过滤） |
| 当前时间 | `now` | `ClockTalent` | **只能** `engine.getClockTalent().setEnabled(false)` | 即时生效 |
| 任务清单 | `todo`(`todoread` `todowrite`) | 主代理走 `TodoTalent`（文件模式）；子代理走 `planningMode(true)`（内存模式） | 从工具清单移除 | 需重建 |
| 子代理委派 | `task`(`subagent`) | `TaskTalent` | `subagentEnabled(false)`，或从工具清单移除 | 即时生效 |
| 动态子代理 | `generate` | `GenerateTalent` | `subagentEnabled(false)`，或从工具清单移除 | 即时生效 |
| 心智记忆 | `memory` | `MemoryTalent` | `memoryEnabled(false)`（需已配置 `memoryProvider`） | 即时生效 |
| 人工介入 | `hitl` | `HITLInterceptor`（拦截器，非 Talent） | `hitlEnabled(false)`，或工具清单不含 `hitl` | 即时生效（改的是拦截器实例） |
| 专家技能 | `skill` | `SkillTalent` | 工具名移除；单技能用 `disallowSkill("@alias/path")` | 单技能禁用即时生效 |
| 编码指引 | `code` | `CodeTalent` | 从工具清单移除 | 需重建 |
| 网页/搜索 | `webfetch` `websearch` `codesearch` | `WebfetchTalent` / `WebsearchTalent` / `CodeSearchTalent` | 从工具清单移除 | 需重建 |
| LSP | `lsp` | `LspTalent` | 工具名移除；无服务器时不注入（`isSupported = hasServers()`） | 有服务器才生效 |
| MCP | `mcp` | `McpGatewayTalent` | 工具名移除；服务级 `McpServerParameters.isEnabled(false)` | 无可用工具即不注入 |
| OpenAPI | `openapi`(`restapi`) | `OpenApiGatewayTalent` | 工具名移除；源级 `ApiSource.isEnabled(false)` | 无可用工具即不注入 |

几个默认值（与《配置参考》一致）：`memoryEnabled=true`、`subagentEnabled=true`、`sandboxEnabled=true`、`sandboxAllowUserHome=true`、`sandboxSystemRestrict=false`、`hitlEnabled=false`、`bashAsyncEnabled=false`、`multitaskMaxTasks=5`。

### 3、`toolsAdd` 与 `disallowedToolsAdd`

两个方法各有三个重载，**没有 `String...` 重载**：

```java
public Builder toolsAdd(ToolName... tools)
public Builder toolsAdd(Collection<String> tools)
@Deprecated public Builder toolsAdd(ToolPermission... tools)   // 4.0.4 起弃用

public Builder disallowedToolsAdd(ToolName... tools)
public Builder disallowedToolsAdd(Collection<String> tools)
@Deprecated public Builder disallowedToolsAdd(ToolPermission... tools)
```

语义差异（`HarnessOptions`）：

- `ToolName...` / `ToolPermission...`：只把枚举的 `getName()` 放进 Set，**不做任何展开、不做校验**；`ToolName.TOOL_ALL_FULL` 进去的就是字符串 `"**"`，真正的展开发生在 `AgentFactory.create`。
- `Collection<String>`：直接 `addAll`，连枚举归一都没有，写什么就是什么。
- 需要字符串混写时用集合：

```java
.toolsAdd(Arrays.asList("read", "glob", "grep", "ls"))
.disallowedToolsAdd(Arrays.asList("bash", "write", "edit"))
```

#### 3.1 快捷权限的展开表

| 代号 | 展开结果 |
|---|---|
| `pi` | `read` `write` `edit` `bash` `bash_start` `bash_wait` `bash_stop` |
| `*` | `read` `write` `edit` `glob` `grep` `ls` `bash` `bash_start` `bash_wait` `bash_stop` `skill` `todo` `code` `codesearch` `websearch` `webfetch` `task` `lsp` |
| `**` | `*` 的全部 + `generate` `mcp` `openapi` `hitl` `lsp` `memory`（共 23 项） |

展开是**逐项**调用 `toolAddDo` 的，所以展开出来的每一项仍会单独过禁用名单——这就是"全给但刨掉几个"的写法：

```java
HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
        .sessionProvider(sessionProvider)
        .toolsAdd(ToolName.TOOL_ALL_FULL)                   // 先全给
        .disallowedToolsAdd(ToolName.TOOL_BASH,             // 再刨掉危险项
                            ToolName.TOOL_WRITE,
                            ToolName.TOOL_EDIT)
        .build();
```

### 4、静态开关与动态开关

`HarnessEngine` 上与之相关的方法，按"是否需要重建主代理"分两类（重建统一走 `agentLock` + `createMainAgent()`）：

| 类别 | 方法 | 说明 |
|---|---|---|
| 需重建主代理 | `allowTool(name)` / `disallowTool(name)` | 同时维护白名单与黑名单：授权时从黑名单移除并加入白名单；禁用反之 |
| 需重建主代理 | `allowToolReset(tools)` / `disallowToolReset(tools)` | 只重置一侧 |
| 需重建主代理 | `toolPermissionReset(tools, disallowedTools)` | 一次性重置两侧，**只重建一次**（避免连续两次重置造成双倍抖动） |
| 需重建主代理 | `refreshMainAgent()` | 手工兜底：置空后下次 `getMainAgent()` 懒加载重建 |
| 需重建主代理 | `addExtension` / `removeExtension` / `addLspServer` / `removeLspServer` | 扩展与 LSP 服务器会改变工具集 |
| 即时生效 | `setMemoryEnabled` / `setSubagentEnabled` / `setBashAsyncEnabled` / `setHitlEnabled` | 直接改 Talent / 拦截器实例的 enabled 位 |
| 即时生效 | `setSandboxEnabled` / `setSandboxAllowUserHome` / `setSandboxSystemRestrict` | 改 `TerminalTalent` 行为，不增减工具 |
| 即时生效 | `setMultitaskMaxTasks` / `setCompressionThreshold` / `setMemoryRelevanceCount` / `setMemoryPriorityCount` / `setMemorySummaryLength` | 参数类 |
| 即时生效 | `disallowSkill(aliasPath)` / `allowSkill(aliasPath)` / `disallowSkillReset(...)` | 单技能粒度 |

```java
// 运行时收紧：把 bash 从白名单挪进黑名单，并重建主代理使其立即生效
engine.disallowTool("bash");

// 运行时放宽：重新授权
engine.allowTool("bash");

// 一次性重置两侧（只重建一次）
engine.toolPermissionReset(
        Arrays.asList("read", "write", "edit", "glob", "grep", "ls"),
        Arrays.asList("bash"));
```

> 子代理是**按需构建**的（`engine.getAgentOrMain(name)` 每次都走 `AgentFactory.create(...).build()`），因此上述动态改动对之后构建的子代理同样生效。

### 5、容易踩的细节

1. **禁用名单只认"具体工具名"，不认快捷代号**。禁用判断发生在展开之后，`disallowedToolsAdd("pi")`（或 `"*"`、`"**"`）永远不会命中任何一项，等于没写。
2. **别名与规范名不互通**。禁用判断比较的是原始字符串，`case` 归一只发生在判断之后：`toolsAdd("list")` + `disallowedToolsAdd("ls")` 拦不住 `list`。两侧请用同一写法。
3. **`edit` 的附带项会被单独拦截**。`case "edit"` 会递归补 `read` 和 `write`，这两个子项各自过一次禁用名单——所以可以出现"`edit` 在、`write` 被刨掉"的组合。
4. **`now` 不受工具清单与禁用名单控制**。只要工具清单非空（哪怕全部被禁用），`AgentFactory` 都会追加 `ClockTalent`，且这一步不查禁用名单。想去掉只能 `engine.getClockTalent().setEnabled(false)`。
5. **`task` / `generate` / `hitl` 的 enabled 位是"构建期同步"的**。`toolAddDo` 里会用引擎当前值覆盖 Talent / 拦截器状态，所以在 Builder 上重复设置无意义，运行时请用 `setSubagentEnabled` / `setHitlEnabled`。
6. **`memory` 需要 `memoryProvider`**。未配置时 `engine.getMemoryTalent()` 返回 `null`，把 `memory` 写进工具清单会在构建代理时因 `talentAdd` 取 `t.getName()` 抛 NPE；此时调用 `setMemoryEnabled(...)` 同样会 NPE（该方法没有 null 守卫）。先配 provider（或不要声明 `memory`）。
7. **"删工具名"和"关 Talent"不是一回事**。前者让工具不进入注册表，但必须重建主代理才生效；后者工具名仍在，只是请求时被 `TalentUtil` 跳过（既不注入指令也不部署工具），是即时生效的软开关。
8. **沙盒三开关不减少工具**。它们只改 `TerminalTalent` 的执行行为（绝对路径受限等），`read`/`bash` 依然存在。
9. **定义级与全局级优先级相同**。两者都是"命中即 return"，没有谁覆盖谁；全局名单影响所有代理（含子代理），定义级只影响自己。
10. **MCP / OpenAPI 是两层开关**。`mcp` / `openapi` 工具名决定"这一族是否注册"，服务级 `McpServerParameters.isEnabled` / `ApiSource.isEnabled` 决定"这个服务是否接入"，`isSupported()` 再看是否有可用工具。

### 6、场景配方

**只读助手**（只给看，不给改）：

```java
HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
        .sessionProvider(sessionProvider)
        .toolsAdd(Arrays.asList("read", "glob", "grep", "ls"))
        .build();
```

**全给但收口危险项**：

```java
HarnessEngine engine = HarnessEngine.of(workspace, harnessHome)
        .sessionProvider(sessionProvider)
        .toolsAdd(ToolName.TOOL_ALL_FULL)
        .disallowedToolsAdd(ToolName.TOOL_BASH, ToolName.TOOL_WRITE, ToolName.TOOL_EDIT)
        .bashAsyncEnabled(false)
        .build();
```

**关掉子代理与记忆**（省 token，不重建）：

```java
engine.setSubagentEnabled(false);   // task / generate 不再注入
engine.setMemoryEnabled(false);     // 需已配置 memoryProvider
```

**保留全部工具但禁掉某几个技能**：

```java
engine.disallowSkill("@my-skills/expensive-analysis");
```
