子代理(Subagents)¶
Synapse 的内置子代理(researcher / tester / reviewer)通过 deepagents 的 task
工具暴露,采用 manager 模式(agent-as-tool):主 Agent 保留最终回答所有权,子 Agent
做有界任务、返回 summary。
本页描述子代理系统的三层架构、自定义扩展方式,以及为未来 handoff 与 workflow 编排预留的演进基础。
三层架构¶
子代理系统把「定义」「注册」「编译」三层解耦,使未来的编排拓扑(handoff、显式图编排) 可以复用同一套定义与注册,而不需要改动解析与加载逻辑。
flowchart TB
subgraph SRC["来源层"]
B["内置定义<br/>researcher / tester / reviewer"]
U["~/.synapse/agents/*.md"]
P["workspace/.synapse/agents/*.md"]
E["custom_agents_dirs"]
end
subgraph DEF["描述层(与拓扑无关)"]
D["SubAgentDefinition<br/>name · description · system_prompt · model<br/>tools · disallowed_tools<br/>ownership · output_schema · enabled"]
end
subgraph REG["注册层(唯一事实来源)"]
R["SubagentRegistry<br/>分层合并 · 同名覆盖 · 禁用 · name 唯一"]
end
subgraph CMP["编译层(可插拔,按拓扑模式)"]
C1["compile_task_specs<br/>现状:agent-as-tool"]
C2["compile_handoffs<br/>未来:所有权转移"]
C3["compile_workflow<br/>未来:图节点 / 边"]
end
subgraph RT["运行时"]
T["deepagents task 工具"]
H["handoff 原语"]
G["LangGraph StateGraph"]
end
B --> D
U --> D
P --> D
E --> D
D --> R
R --> C1
R --> C2
R --> C3
C1 --> T
C2 --> H
C3 --> G
- 描述层:
SubAgentDefinition是拓扑无关的声明式描述,只回答「这个子代理是谁、能干什么」。 - 注册层:
SubagentRegistry是「有哪些子代理」的唯一事实来源。 - 编译层:每个拓扑模式一个编译器;当前只有
compile_task_specs,未来新增compile_handoffs/compile_workflow时定义层与注册层零改动。
自定义子代理¶
在用户层 ~/.synapse/agents/*.md 或项目层 <workspace>/.synapse/agents/*.md 放置
Markdown 文件即可新增子代理。YAML frontmatter 提供元数据,文件正文是子代理的
system prompt。
首次启动时(AGENT_ENABLE_CUSTOM_SUBAGENTS=true),Synapse 会在
~/.synapse/agents/ 自动生成 researcher.md / tester.md / reviewer.md 三个
内置定义的种子文件。编辑这些文件即可覆盖对应内置角色;已存在的文件不会被覆盖或
重复生成(除非删除后重启)。同名文件存在但未显式写 model 时,
AGENT_SUBAGENT_*_MODEL 仍会生效;显式写 model(或 model: inherit)后由文件接管。
---
name: security-reviewer
description: Use after security-sensitive changes. Reviews for injection and secret leaks.
model: inherit # 或 "provider:model-name"
tools: [read_file, search_files, find_files, execute] # 非空即最终严格白名单
disallowed_tools: [write_file, edit_file] # 可选 denylist
ownership: task # 预留字段,当前仅支持 task
---
You are a security reviewer. Inspect diffs for...
frontmatter 字段¶
| 字段 | 必填 | 说明 |
|---|---|---|
name |
是 | 唯一标识;与内置同名时覆盖内置定义 |
description |
是 | 主 Agent 据此决定何时委托(路由规则,写具体触发条件) |
| (正文) | 是 | 子代理 system prompt |
model |
否 | inherit 或 provider:model-name;缺省继承主 Agent 模型 |
reasoning_effort |
否 | off/minimal/low/medium/high/max;缺省继承主 Agent 当前推理级别 |
tools |
否 | 见下文「tools 语义」:null 继承 find_files/search_files/patch;[] 仅用框架内置工具(仍受全局排除);[names] 为最终严格白名单 |
disallowed_tools |
否 | denylist,作用于继承/allowlist 之后的工具集 |
ownership |
否 | task 或 handoff(预留);当前仅 task 参与编译 |
output_schema |
否 | 预留:未来 workflow 节点间结构化契约 |
enabled |
否 | false 时跳过编译 |
tools 语义¶
tools 决定子代理最终能看到的工具集合。无论取哪种值,全局排除始终优先(见下文
「工具排除规则」),disallowed_tools 也在继承 / 白名单之上生效。
| 取值 | 语义 |
|---|---|
省略 / null |
继承主 Agent 的默认白名单 find_files / search_files / patch;deepagents 框架内置工具(ls/glob/grep/read_file/write_file/edit_file/execute)照常注入,再由全局排除与 disallowed_tools 收敛 |
[] |
legacy 行为:只保留框架内置工具,不继承任何主 Agent 工具;全局排除与 disallowed_tools 仍然生效,不会因 [] 而放开被全局禁止的工具 |
[names] |
非空显式列表现在是最终严格白名单:框架内置工具同样受其约束,只有被点名的工具会暴露给模型 |
- 白名单里的名字必须能解析(继承的主 Agent 工具或框架内置工具);无法解析的名字不会 自动获得对应能力,只会在日志里记录 warning。
- 被全局排除(settings 的
excluded_tools、minimal_filesystem_tools、readonly等) 或被disallowed_tools禁止的名字,即使写进显式白名单也不会被放开。 - 内置
tester的默认值已由[]调整为null,因此默认继承find_files/search_files/patch;需要旧的「仅框架内置工具」语义时,在定义文件中 显式写tools: []。 - 已经生成的用户层
tester.md不会被自动覆盖;若其中仍有tools: [],移除该字段或 改成tools: null才会采用新的默认继承方式。全局排除仍适用于这些旧文件。 - 修改定义或设置后需重建 Agent(例如重新启动);已编译的任务不会在执行中切换策略。
合并与加载流程¶
扫描顺序:用户层 → 可执行目录层 → 项目层 → custom_agents_dirs。后出现的定义覆盖
同名的先出现定义(项目覆盖用户)。解析失败的文件被跳过并记录 warning,不会导致启动失败。
flowchart LR
A["layered_agents_dirs<br/>user → exe → project"] --> S["扫描 *.md"]
S --> F{"parse 成功?"}
F -- "否" --> W["记录 warning 并跳过<br/>(degradation,不崩溃)"]
F -- "是" --> N{"name 已存在?"}
N -- "是" --> O["后者覆盖前者<br/>project 覆盖 user"]
N -- "否" --> I["加入 registry"]
O --> I
I --> B{"与内置同名?"}
B -- "是" --> C["覆盖内置定义"]
B -- "否" --> K["作为自定义追加"]
C --> DD{"name 在<br/>disable_builtin_subagents?"}
K --> DD
DD -- "是" --> X["移除"]
DD -- "否" --> OUT["输出最终定义集"]
编译流程¶
compile_task_specs 把 SubAgentDefinition 编译为 deepagents SubAgent dict
(task 工具消费)。ownership != task 与 enabled = false 的定义被跳过,留给未来的
handoff / workflow 编译器。
flowchart TB
R["SubagentRegistry"] --> D["遍历 definitions"]
D --> OWN{"ownership?"}
OWN -- "handoff" --> H["跳过,留给 compile_handoffs"]
OWN -- "task" --> T{"tools 取值?"}
T -- "None" --> I2["继承 find_files / search_files / patch<br/>+ 框架内置工具"]
T -- "[]" --> E2["仅框架内置工具<br/>(legacy,仍受全局排除)"]
T -- "[names]" --> F["最终严格白名单<br/>(框架内置也受约束)"]
I2 --> X2["全局排除优先<br/>blocked 覆盖白名单"]
E2 --> X2
F --> X2
X2 --> M["model 解析"]
M --> MD{"model == inherit?"}
MD -- "是" --> NS["不设置 spec.model"]
MD -- "否" --> SS["写入 provider:model"]
NS --> MW["附加 middleware<br/>环境与项目规则 + intent + 工具权限校验"]
SS --> MW
MW --> OUT["deepagents SubAgent dict"]
OUT --> TG["task 工具 spec"]
工具排除规则(build_tool_exclusion_middleware):
- 全局排除优先:settings 的
excluded_tools、minimal_filesystem_tools展开的 排除集,以及readonly的只读排除,会和disallowed_tools、内置搜索工具、write_todos/todo_write/todos(产品级隔离)一起进入 blocked 集合。blocked 永远 覆盖继承与白名单,[]或显式白名单都无法把被全局禁止的工具重新放开。 - 请求过滤与执行拦截共用同一判定:被排除的工具既不会出现在模型请求里,强行 / 伪造的 调用也会在真正执行前被拒绝并返回 permission denied,而不是绕过限制。
- 继承 / 白名单主 Agent 工具时,额外隐藏内置搜索工具
ls/glob/grep(避免与find_files/search_files重复)。独立使用编译器时可显式选择未被全局禁止的内置搜索 工具;正常应用装配始终全局排除这三个工具,白名单不能恢复它们。 - 非空白名单会作为 guard 的
allowed_tools传入,使被点名的框架内置工具通过请求过滤, 但 blocked 仍在其上生效。
运行时时序(task 模式)¶
sequenceDiagram
participant M as 主 Agent
participant TT as task 工具
participant R as Registry(编译产物)
participant SA as Subagent(独立 context)
M->>TT: task(subagent_type=reviewer, ...)
TT->>R: 查找 reviewer spec
R-->>TT: system_prompt / model / tools / middleware
TT->>SA: 独立 context 启动运行
SA-->>TT: 返回 summary 结果
TT-->>M: 结果回传,主 agent 保留最终回答所有权
运行环境与隔离¶
子代理在独立 context 中启动,不继承主对话历史;它拿到的 system prompt 由定义正文、 不可覆盖的文件工具路径规则、以及共享环境段落组成:
- 共享 workspace:主 Agent 与子代理看到同一个真实工作目录(host root)与同一套
虚拟路径映射(
/→ host root),文件工具调用行为一致。 - AGENTS 项目规则:子代理通过 agent-md middleware 注入与主 Agent 相同的
AGENTS.md项目约定,并复用虚拟路径规范化 middleware。显式工作区缺少该文件时不回退到进程 cwd 中另一个项目的AGENTS.md。 - 可用 shell:只有子代理实际能调用
execute时才注入 shell 指引;否则明确告知该 context 不能运行 shell 命令。execute在 host 上执行,初始 cwd 为 workspace root; 这不是 OS sandbox,允许的 shell 命令仍能访问当前用户权限内的其他位置。 - 路径范围:默认只调查当前 workspace,除非用户明确授权其他位置。shell 中的
.表示工作目录,不能将文件工具的虚拟根/当成 shell 工作目录。 - 能力边界:
## Scope limits明确区分工具调用权限和工具内部行为。工具级拒绝由 middleware 强制执行,但不会沙箱化已允许的 shell 或其他工具。当任务需要子代理不具备 的能力时,要求如实报告,而不是尝试绕过。 - 工具定义一致:主子代理复用同一 backend、工具 schema、intent 注入和描述精简逻辑; 子代理不注入已隐藏的 todo / 框架文件系统使用提示。
配置字段¶
| 变量 | 默认值 | 说明 |
|---|---|---|
AGENT_ENABLE_SUBAGENTS |
true |
启用子代理 |
AGENT_SUBAGENT_TESTER_MODEL |
— | Tester 子代理模型 |
AGENT_SUBAGENT_REVIEWER_MODEL |
— | Reviewer 子代理模型 |
AGENT_SUBAGENT_RESEARCHER_MODEL |
— | Researcher 子代理模型 |
AGENT_ENABLE_CUSTOM_SUBAGENTS |
true |
加载 .synapse/agents/*.md 自定义子代理 |
AGENT_CUSTOM_AGENTS_DIRS |
[] |
额外扫描目录(JSON 数组) |
AGENT_DISABLE_BUILTIN_SUBAGENTS |
[] |
禁用的内置子代理名(JSON 数组) |
AGENT_SUBAGENT_DEFAULT_MODEL |
— | 子代理全局默认模型 |
AGENT_SUBAGENT_DEFAULT_REASONING_EFFORT |
— | 子代理全局默认推理级别 |
AGENT_SUBAGENT_MODEL_OVERRIDES_JSON |
{} |
按子代理名覆盖模型(JSON 对象) |
AGENT_SUBAGENT_REASONING_EFFORT_OVERRIDES_JSON |
{} |
按子代理名覆盖推理级别(JSON 对象) |
模型与推理级别解析优先级¶
TUI 中使用 /subagent 打开模型配置页,可以设置全局默认与按名覆盖;这些值持久化到分层
settings.json。编译时按以下优先级(高 → 低)解析每个子代理的模型与推理级别:
- 按名覆盖(TUI 中某个子代理的独立配置)
- 定义文件 frontmatter 中的
model/reasoning_effort - 全局默认(
(Global defaults)) - 全部未配置 → 继承主 Agent 当前模型与推理级别(
inherit)
两条轴(模型、推理级别)独立解析:只覆盖其中一条不会影响另一条。当显式指定模型
(或只覆盖推理级别)时,子代理使用独立构建的模型实例;推理级别为 off 时关闭
thinking,为具体级别时以对应 reasoning_effort 开启。未指定推理级别时继承主 Agent
当前会话设置。保存配置后当前 Agent 会立即重建,无需重启。
inherit 表示“该层未配置”,等价于删除该层的值:TUI 编辑框中的 inherit 不会写入
任何覆盖,因此该角色的 frontmatter 或全局默认仍然生效。按名覆盖始终优先于
frontmatter;只有选择 inherit(或删除覆盖)时才会回退到
frontmatter → 全局默认 → 主 Agent。reasoning_effort 的合法值为
off/minimal/low/medium/high/max/inherit,配置与 frontmatter 均校验。
演进路径:handoff 与 workflow¶
当前只实现了 task 模式(manager 模式)。定义层的 ownership 与 output_schema 字段、
注册层的唯一事实来源,为两种未来拓扑预留了接入点;届时只需新增编译器。
flowchart TB
R["SubagentRegistry<br/>唯一事实来源"] --> C1["compile_task_specs"]
R --> C2["compile_handoffs"]
R --> C3["compile_workflow"]
C1 --> O1["task 工具<br/>manager 模式(现状)"]
C2 --> O2["handoff 原语<br/>所有权转移(未来)"]
C3 --> O3["图节点 + 条件边<br/>显式编排(未来)"]
O1 --> M["混合拓扑<br/>三种模式共存于同一 StateGraph"]
O2 --> M
O3 --> M
style C2 stroke-dasharray: 5 5
style C3 stroke-dasharray: 5 5
style O2 stroke-dasharray: 5 5
style O3 stroke-dasharray: 5 5
| 未来模式 | 语义 | 复用本次基础 |
|---|---|---|
| handoff | 子代理接管下一轮响应所有权(OpenAI Agents SDK / Swarm 语义) | 同一 Definition/Registry;ownership=handoff 字段已就位 |
| workflow | 显式图编排:节点 + 条件边 + 结构化 state 契约(LangGraph 语义) | 同一 Registry;output_schema 字段已就位;底层 StateGraph 原生支持 |
兼容性与降级¶
- 无
agents/目录时,build_default_subagents()仍输出内置三个子代理;但内置tester的tools默认值已由[]调整为null,默认继承find_files/search_files/patch,需要旧语义时显式写tools: []。 - 兼容性收紧:显式非空
tools: [names]现在按最终严格白名单处理(框架内置工具也受 约束,未知名字不自动获得能力),不再是早期的「仅按名过滤主 Agent 工具」。 - 单个定义文件解析失败只跳过该文件,不影响其余定义与启动。
permissions字段(deepagents FilesystemPermission)与 shell backend 不兼容,本层不暴露, 隔离通过工具排除 middleware + system prompt 实现,而非 OS sandbox。