> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openkova.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 子代理能力模型

> 工具、技能、MCP、知识源、记忆——五个正交维度，一条纪律：未声明即不可达。

这里只讲一条纪律，其余都是它的推论：

<Warning>
  **未声明即不可达。** 不是「给了再拒绝」——定义里没写的东西，
  根本不会出现在它的工具表和提示词里。这样模型看不见不存在的能力，
  也就不会去试；真试了，拿到的也是一句明确的「你没有这个」。
</Warning>

## 一份完整的定义

```yaml theme={null}
name: customer-service
description: 处理退款咨询与售后政策查询
tools: [read, glob, grep, WebFetch]
skills: [refund-policy, tone-guide]
mcp:
  servers: [crm, notion]
knowledge:
  - name: 产品手册
    path: ./docs/**/*.md
memory: private
maxTurns: 60
prompt: |
  你是售后专员……
```

一份这样的定义，让一个只会读文件跑命令的子代理，变成够得着业务知识库的售后专员。
下面逐个维度说清楚它到底拿到了什么。

## 维度一：工具

白名单扩容过一轮——以前只有 `bash / read / write / edit / glob / grep` 六个，
业务工具一个都够不到。现在可授予的是一张\*\*「允许表的允许表」\*\*：

| 分组 | 工具 |
| - | - |
| 内置编码工具 | `bash` `read` `write` `edit` `glob` `grep` |
| 委派自身 | `task_output` `task_stop` |
| 网络 | `WebFetch` `WebSearch` |
| 能力授予目标 | `use_skill` `todo` |

**刻意缺席的工具**，缺席理由和「白名单扩容」同等重要：

| 工具 | 为什么不给 |
| - | - |
| `Question` | 子代理问不了用户，它的系统提示词里已写明这件事 |
| `mcp` / `memory_*` | 只能经对应维度挂载，按裸工具名声明会绕过作用域与隔离 |
| `browser` `screenshot` `imagegen` | 主代理交互面，子代理没有 UI 承载 |
| `open_file` `open_panel` | 靠事件打开面板，子代理没有面板 |
| `subagents_*` `skills_*` `plugins_*` `scheduler_*` | 管理面，本就不在会话工具表里 |

**声明大小写不敏感，落库一律是规范注册名。** 写 `webfetch` 能解析成 `WebFetch`。
这条是被真实 bug 逼出来的：会话工具注册名大小写不统一（`WebFetch` 是驼峰，
`use_skill` 是蛇形），而旧解析器对每个工具名无条件小写化，
于是驼峰注册名永远匹配不上——工具静默解析为空，且没有任何报错。

运行时还有第二个条件：声明的工具必须**确实存在于当前会话的工具表**。
两个条件都满足才授予，否则记一条诊断告诉你缺了什么，而不是悄悄不给。

## 维度二：技能

`skills` 是按名白名单，解析复用主代理同一套分层发现
（工作区 > 生态·工作区 > 系统 > 生态·用户 > 插件）。

* 没声明 `skills` → 不注入任何技能目录，`use_skill` 也不可用；
* 声明了但某个技能在当前作用域不存在 → **记诊断，那一条从目录里消失**，
  不静默：用户需要知道自己声明的东西没生效；
* 目录里只有 name / description / location 三行，**正文经 `use_skill` 按需加载**。

技能上限 16 个——每个都会占系统提示词目录块的一行。

## 维度三：MCP 服务器

子代理拿到的不是共享网关，而是**作用域化网关**：

* `search` / `describe`：工具索引按声明过滤，只列已声明服务器的工具；
* `call` 一个未声明的服务器 → **立即拒绝**，并告诉它「你被授权了这些」；
* `call` 一个已声明的服务器 → 命中该服务器的审批白名单就直接执行；
  没命中则**把审批卡转发到父线程**，你在正在看的那个会话里看到并裁决。

最后一条是本设计里唯一让子代理能**阻塞在人**的地方。选它而不是「一律拒绝」的理由：
审批白名单默认不配置，一律拒绝等于任何未预配置的 MCP 服务器对子代理都不可用，
垂直业务代理形同虚设。父线程审批卡复用现有机制，不需要新建审批系统——
子代理挂在父卡片上，是它在无法自行询问用户时唯一诚实的选项。

**不止 MCP：子代理的 `write` / `edit` / `bash` 走的是同一条父会话审批链。**
这些内置工具当初是从父会话的工具表里按定义取的，却没有接审批钩子——
"把活交给带写入能力的子代理"于是等于把权限放大一档。现在判定收口在同一个函数里：
档位（变更前确认 / 工作区内自动 / 自动编辑 / 完全访问）、工作区边界与可写根清单、
配置类工具的确认、无人值守的即时裁决，对子代理与主代理一视同仁，卡显示在父会话。
`Task` 本身不作为审批项——它不写盘、无副作用。

代价已明确接受：一个等待裁决的后台子代理会挂起，`TaskStop` 可以中止它（中止时它
名下挂起的卡按拒绝结算，子代理不会卡在那次调用上）。已知残留：父会话**没有活跃
请求**时（主代理这一轮已跑完、子代理还在后台），直播通道送不出卡，卡片要等下一次
快照/挂载拉取才现身。

> 声明了但服务器当前没启用/没配置时，网关**照样挂载**，只记一条诊断。
> 因为「能力不存在」和「配置还没到位」的下一步动作完全不同——
> 后者该去设置页配服务器，而不是让模型以为自己没这项本事。

## 维度四：知识源

一份知识源就是一份文档：名称 + 工作区相对 glob，正文留在磁盘。

**永不预加载。** 系统提示词只拿到每源一行的目录，检索走 `kb_search`：
纯内存逐行扫描打分，返回排好序的 `path:line` 命中，子代理再自己 `read` 打开感兴趣的文件。
命中是一行，不是答案——工具描述里就这么写着。

<Columns cols={3}>
  <Card title="8 MiB">单次检索最多读取的字节数，覆盖常规产品手册规模。</Card>
  <Card title="50 条">单次返回命中上限，比 `grep` 的 200 更紧。</Card>
  <Card title="400 字符">单条命中行长度上限，与 `grep` 一致。</Card>
</Columns>

超预算会**显式标注截断**，不静默——静默截断会让模型误以为「库里只有这些内容」。

**设置页用目录选择器**：选完目录自动收敛成工作区相对 glob。
手打 `./docs/**/*.md` 不该是「给 agent 一个知识库」的前置知识。
选到工作区之外的目录会被直接拒绝（检索侧按 `path.join(cwd, rel)` 解析，
绝对路径会拼成无意义的串，静默搜不到任何东西）。

**声明知识源就必须同时授予 `read`**，否则它拿到 `path:line` 却打不开文件。
解析层只警告（不赔掉整份定义），保存路径直接拒绝——填完才被拒太晚了，
所以编辑时就 inline 提示。

<Note>
  外部系统（飞书表格、Notion、数据库）**不走知识源**，由 `mcp.servers` 授予，
  agent 直接调那些服务器的工具。把 MCP 也做进知识源等于同一件事说两遍，
  还多一套要维护的类型分支。
</Note>

## 维度五：记忆

三档互斥，缺省即「无」：

| 档位 | 语义 | 目录 |
| - | - | - |
| 无（缺省） | 不注入、不给工具，每次委派冷启动 | — |
| 私有 | 私有命名空间，跨委派累积，只有它自己看得见 | `<工作区>/.kova/agent-memory/<名字>/` |
| 共享 | 与主代理共享工作区作用域记忆 | `<工作区>/.kova/memory/` |

**私有是推荐档。** 子代理写不进用户主记忆，结构上不可能污染；
它生成的内容可能是幻觉，本就不该进主代理的下一次提示词。
共享档保留是因为「业务 agent 与主代理共享一条经验」确有场景——
UI 上会附一行知情说明，不是阻止，是知情。

三件套工具 `memory_write` / `memory_read` / `memory_search`，仅在非「无」时挂载。
关键一条：**`scope` 参数不进工具 schema**，目录在闭包里钉死，
子代理在参数里伪造 `scope: "global"` 无门可过——隔离是结构性的，不是运行时校验参数值。

投递方式与主记忆同构的两层：根级 `*.md` 视为常驻记忆，经提示词段注入
（逐文件 4K、整段 12K 预算，超预算的文件整体略去并留一行说明）；
`daily/*.md` 只参与关键词检索。

并发上，同一子代理的多次委派写同一目录——按目录串行 append，
不用文件锁（sidecar 是单进程，进程内串行就够）。
主记忆的全局开关**不控制**子代理记忆：那是主代理提示词的缓存纪律，
用一个默认关闭的开关去否决你显式声明的 `memory: private` 是错的耦合。

> `knowledge` 与 `memory` 的分工：`knowledge` 是**外部权威资料**（产品手册、政策库），
> 只读；`memory` 是**agent 自己攒下的经验**，可读可写。
> 一个是「世界告诉它的」，一个是「它自己记住的」。

## 提示词怎么拼

顺序固定：**框架 → 能力目录 → 定义正文**。
能力目录的段序也固定：技能 → 知识源 → MCP → 记忆。
定义正文放最后，让它对「怎么干活」有最后发言权。

<Note>
  **不变量**：每个维度未声明时，该段整体省略——
  所以既有定义的输出提示词**字节级不变**，
  provider 侧的 prompt cache 才命中。这条不变式进了测试。
</Note>

## 设置页怎么改

编辑器是**二级页面**而不是弹窗。加了能力维度后，内容远超一个
`sm:max-w-2xl` 弹窗能从容承载的高度——表单被挤出视口，保存按钮要滚到底才够得着。
改成整页后：顶部返回、内容区独立滚动、操作条常驻底部，两列布局让高度回到一屏内。

表单分五组：基本信息 / 系统提示词 / 可用工具 / 能力授予 / 知识源。

几条实现上的取舍值得单独说：

* **三个选择器都用真实候选，不给自由文本**——工具取自 sidecar 的可授予清单，
  技能取自技能清单，MCP 服务器取自 MCP 配置。
  能力声明是**引用**既有实体，不是新定义；给自由文本框只会让人写出不存在的名字。
  后端是唯一事实源，旧 sidecar 缺这个字段时回落旧 6 项，不白屏。
* **引用了不存在的名字，以警示色 chip 显式出现**，hover 说明、点击可移除，
  不静默丢弃。
* **YAML 原文页签走同一套解析校验**，手写的四个能力键原样生效，
  表单页签的内容不会覆盖这里的编辑。
* **内置定义的只读弹窗也列出它的能力**（技能 / MCP / 知识源 / 记忆档位）——
  否则「为什么这个 agent 够不到我的 Notion」无从排查。
  「复制为系统级」完整携带四个维度，不做静默丢字段。

## 本期明确不做

<Warning>
  这些是刻意留在边界外的，不是遗漏：

  * **向量 RAG / embedding / 切分**：仓库目前无此基础设施。
    真需要时它是 `kb_search` 的实现替换，不影响 schema；
  * **业务 API（HTTP）工具**：`WebFetch` / `WebSearch` 无鉴权、无路径约束，
    不新建受约束的 `api_call`；业务 API 走 MCP 通道；
  * **子代理嵌套委派**：维持现状，delegate 不能继续 `Task`；
  * **把私有记忆导入主记忆**：需要时人工复制文件即可，
    加了反而引入不可逆的合并语义；
  * **跨工作区共享记忆**：私有记忆绑在 `<工作区>` 下，
    同一定义在两个工作区各有一份。
</Warning>

<Card title="回到" icon="arrow-left" href="/features/subagents">
  四个调度工具、四层发现与活动回放。
</Card>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.