Current section
Files
Jump to
Current section
Files
lib/cmdc/sub_agent.ex
defmodule CMDC.SubAgent do
@moduledoc """
子代理声明式规格 struct。
在创建父 Agent 时通过 `CMDC.Options` 的 `:subagents` 字段声明。
字段值为 `nil` 表示**继承**父 Agent 的对应配置。
## 快速开始
subagent = CMDC.SubAgent.new!(
name: "coder",
description: "专门负责写代码的子代理",
model: "anthropic:claude-opus-4-5",
tools: [CMDC.Tool.ReadFile, CMDC.Tool.WriteFile, CMDC.Tool.Shell]
)
CMDC.Options.new!(
model: "anthropic:claude-sonnet-4-5",
subagents: [subagent]
)
## 字段说明
- `:name` — 必填,子代理唯一标识名,用于 `tool_task` 调用时指定目标
- `:description` — 对父 Agent 可见的描述,用于 Task Tool 的工具描述生成
- `:system_prompt` — 子代理的系统提示词;nil 则继承父 Agent 的系统提示词
- `:model` — 子代理使用的模型;nil 则继承父 Agent 的 model
- `:tools` — 子代理可用工具列表;nil 则继承父 Agent 的 tools
- `:plugins` — 子代理的 Plugin 列表;nil 则继承父 Agent 的 plugins
- `:skills_dirs` — 子代理的 Skills 目录;nil 则继承父 Agent 的 skills_dirs
- `:user_data` — 子代理的业务上下文 map;nil 则继承父 Agent 的 user_data
- `:prompt_mode` — 系统提示词模式;默认 `:task`(SubAgent 默认省 token),
可显式传入 `:full | :task | :minimal | :none` 覆盖,或传 `nil` 继承父 Agent
"""
@type plugin_spec :: module() | {module(), keyword()}
@type t :: %__MODULE__{
name: String.t(),
description: String.t() | nil,
system_prompt: String.t() | nil,
model: String.t() | nil,
tools: [module()] | nil,
plugins: [plugin_spec()] | nil,
skills_dirs: [String.t()] | nil,
user_data: map() | nil,
prompt_mode: CMDC.Options.prompt_mode() | nil
}
@enforce_keys [:name]
defstruct [
:name,
:description,
:system_prompt,
:model,
:tools,
:plugins,
:skills_dirs,
:user_data,
prompt_mode: :task
]
@schema NimbleOptions.new!(
name: [
type: :string,
required: true,
doc: "子代理唯一标识名。父 Agent 通过此名称在 Task Tool 中指定子代理。"
],
description: [
type: {:or, [:string, nil]},
default: nil,
doc: "对父 Agent 可见的子代理能力描述。用于 Task Tool 自动生成工具描述文本。"
],
system_prompt: [
type: {:or, [:string, nil]},
default: nil,
doc: "子代理的系统提示词。nil 则继承父 Agent 的系统提示词。"
],
model: [
type: {:or, [:string, nil]},
default: nil,
doc: "子代理使用的模型标识符。nil 则继承父 Agent 的 model 配置。"
],
tools: [
type: {:or, [{:list, :atom}, nil]},
default: nil,
doc: "子代理可用工具模块列表。nil 则继承父 Agent 的 tools 列表。"
],
plugins: [
type: {:or, [{:list, :any}, nil]},
default: nil,
doc: "子代理的 Plugin 列表。nil 则继承父 Agent 的 plugins 列表。"
],
skills_dirs: [
type: {:or, [{:list, :string}, nil]},
default: nil,
doc: "子代理的 Skills 目录列表。nil 则继承父 Agent 的 skills_dirs 列表。"
],
user_data: [
type: {:or, [:map, nil]},
default: nil,
doc: """
子代理的业务上下文 map。nil 则**继承**父 Agent 的 `user_data`;
传入非 nil map 则**覆盖**父级(不做合并),整张子代理 ctx.user_data
直接等于这里声明的值。用于细粒度替换租户/操作者信息。
"""
],
prompt_mode: [
type: {:or, [{:in, [:full, :task, :minimal, :none]}, nil]},
default: :task,
doc: """
系统提示词模式。
默认 `:task` — SubAgent 自动走精简 system prompt,相对 `:full`
省 30-50% token。显式传 `nil` 则继承父 Agent 的 `prompt_mode`。
详见 `CMDC.Options.prompt_mode` 段说明。
"""
]
)
# ==========================================================================
# Public API
# ==========================================================================
@doc """
从 keyword list 构建 SubAgent struct,校验失败时抛出异常。
## 示例
iex> CMDC.SubAgent.new!(name: "researcher", description: "负责信息收集")
%CMDC.SubAgent{name: "researcher", description: "负责信息收集", model: nil, ...}
"""
@spec new!(keyword()) :: t()
def new!(opts) when is_list(opts) do
validated = NimbleOptions.validate!(opts, @schema)
struct!(__MODULE__, validated)
end
@doc "从 keyword list 构建 SubAgent struct,返回 `{:ok, t()}` 或 `{:error, ...}`。"
@spec new(keyword()) :: {:ok, t()} | {:error, NimbleOptions.ValidationError.t()}
def new(opts) when is_list(opts) do
case NimbleOptions.validate(opts, @schema) do
{:ok, validated} -> {:ok, struct!(__MODULE__, validated)}
{:error, _} = error -> error
end
end
@doc """
将子代理规格与父 Agent Options 合并,nil 字段自动继承父值。
用于 Task Tool 启动子代理进程时构建完整 Options。
"""
@spec to_options(t(), CMDC.Options.t()) :: CMDC.Options.t()
def to_options(%__MODULE__{} = sub, %CMDC.Options{} = parent) do
%CMDC.Options{
parent
| model: sub.model || parent.model,
system_prompt: sub.system_prompt || parent.system_prompt,
tools: sub.tools || parent.tools,
plugins: sub.plugins || parent.plugins,
skills_dirs: sub.skills_dirs || parent.skills_dirs,
user_data: sub.user_data || parent.user_data,
prompt_mode: sub.prompt_mode || parent.prompt_mode,
# v0.6+:SubAgent 总是继承父 group_id(不暴露子级覆盖入口,保持单一职责)
group_id: parent.group_id
}
end
end