754 lines
18 KiB
Markdown
754 lines
18 KiB
Markdown
# A2A Multi-Agent 改造方案
|
||
|
||
## 1. 需求目标
|
||
|
||
当前 `spawn`/`sub-agent` 只有一种执行方式: 创建一个本地后台 subagent 去完成任务。
|
||
|
||
这次需求要改成:
|
||
|
||
1. 调用 `sub-agent` 时,不一定新建本地 subagent。
|
||
2. 先从“已添加的 Agent”里找可用目标。
|
||
3. 再从 skills 中声明的 `agent cards` 里找可用目标。
|
||
4. 通过 A2A 协议把任务发给对应 agent。
|
||
5. 支持一个任务发给多个 agent,形成 `agent group`,最后回到主 agent 汇总。
|
||
6. 保持现有 `spawn(task, label)` 兼容,不破坏已有行为。
|
||
|
||
结论先说:
|
||
|
||
- 最合适的做法不是继续把能力堆进 `SubagentManager`。
|
||
- 应该把“本地 subagent 执行”升级为“统一委派层”。
|
||
- `spawn` 工具继续保留,但语义从“创建 subagent”扩展为“委派给合适的 agent / agent group”。
|
||
|
||
## 2. 当前代码现状
|
||
|
||
### 2.1 当前触发链路
|
||
|
||
现有链路很单一:
|
||
|
||
1. `AgentLoop` 初始化 `SubagentManager`
|
||
- 位置: `nanobot/agent/loop.py:88-114`
|
||
2. `AgentLoop._register_default_tools()` 注册 `SpawnTool`
|
||
- 位置: `nanobot/agent/loop.py:116-138`
|
||
3. LLM 调用 `spawn(task, label)`
|
||
4. `SpawnTool.execute()` 直接转发给 `SubagentManager.spawn()`
|
||
- 位置: `nanobot/agent/tools/spawn.py:67-76`
|
||
5. `SubagentManager.spawn()` 创建本地 asyncio 后台任务
|
||
- 位置: `nanobot/agent/subagent.py:64-93`
|
||
6. `_run_subagent()` 用一个受限工具集运行本地子代理
|
||
- 位置: `nanobot/agent/subagent.py:95-195`
|
||
7. `_announce_result()` 把结果包装成 `channel="system"` 的消息回投主消息总线
|
||
- 位置: `nanobot/agent/subagent.py:197-230`
|
||
8. `AgentLoop._process_message()` 接到 `system` 消息,再整理成用户可见回复
|
||
- 位置: `nanobot/agent/loop.py:331-347`
|
||
|
||
### 2.2 当前已经有但没接入调度链路的能力
|
||
|
||
仓库里已经有两类“候选 agent 信息”,但没有进入实际调度:
|
||
|
||
1. Plugin agents
|
||
- `PluginLoader.find_agent()` 已能找 agent
|
||
- 位置: `nanobot/agent/plugins.py:83-91`
|
||
- `build_agents_summary()` 也已能汇总 agent 信息
|
||
- 位置: `nanobot/agent/plugins.py:100-121`
|
||
- 但当前 `AgentLoop` / `ContextBuilder` 并没有用它做调度
|
||
|
||
2. Skills
|
||
- `SkillsLoader` 已能枚举 / 读取 skill
|
||
- 位置: `nanobot/agent/skills.py:32-249`
|
||
- 但 skill 目前只被当作 prompt 资源,不会暴露成“可路由 agent”
|
||
|
||
### 2.3 当前缺口
|
||
|
||
当前缺少这几层:
|
||
|
||
1. 统一的 `Agent Registry`
|
||
2. A2A `agent card` 发现与缓存
|
||
3. A2A client 调用层
|
||
4. 统一的委派器,负责在“本地 subagent / plugin agent / skill agent card / agent group”之间做路由
|
||
5. group 级别的状态管理和结果聚合
|
||
|
||
## 3. 推荐总方案
|
||
|
||
推荐采用“保留 `spawn` 工具名,重构内部执行层”的方案。
|
||
|
||
### 3.1 核心思路
|
||
|
||
把当前:
|
||
|
||
- `SpawnTool -> SubagentManager -> 本地 subagent`
|
||
|
||
改成:
|
||
|
||
- `SpawnTool -> DelegationManager -> AgentResolver -> Executor(local/plugin/a2a/group)`
|
||
|
||
也就是:
|
||
|
||
1. `spawn` 不再等价于“必须创建 subagent”。
|
||
2. `spawn` 变成“委派任务”。
|
||
3. 真正执行方式由委派层动态决定。
|
||
|
||
### 3.2 为什么这样最合适
|
||
|
||
如果直接继续扩 `SubagentManager`,很快会出现这些问题:
|
||
|
||
1. 一个类同时负责本地 LLM 运行、A2A 网络调用、agent card 发现、group 并发、结果聚合。
|
||
2. 后续要支持 plugin agent、本地 named agent、A2A streaming 时会越来越乱。
|
||
3. 当前 `SubagentManager` 的职责本来就已经比较明确: “本地后台 subagent 执行器”。
|
||
|
||
所以更合理的拆法是:
|
||
|
||
1. `SubagentManager` 保留或下沉为 `LocalSubagentExecutor`
|
||
2. 新增 `DelegationManager` 作为统一入口
|
||
3. 新增 `AgentRegistry` / `AgentResolver`
|
||
4. 新增 `A2AClient`
|
||
|
||
## 4. 推荐模块拆分
|
||
|
||
### 4.1 新增 `DelegationManager`
|
||
|
||
建议新文件:
|
||
|
||
- `nanobot/agent/delegation.py`
|
||
|
||
职责:
|
||
|
||
1. 接收 `spawn` 请求
|
||
2. 根据参数和任务内容选择目标 agent
|
||
3. 决定执行方式
|
||
4. 对 group 做并发调度
|
||
5. 统一把结果回投主消息总线
|
||
|
||
建议接口:
|
||
|
||
```python
|
||
class DelegationManager:
|
||
async def dispatch(
|
||
self,
|
||
task: str,
|
||
label: str | None = None,
|
||
target: str | None = None,
|
||
targets: list[str] | None = None,
|
||
strategy: str = "auto",
|
||
origin_channel: str = "cli",
|
||
origin_chat_id: str = "direct",
|
||
) -> str: ...
|
||
```
|
||
|
||
### 4.2 保留本地执行器
|
||
|
||
当前 `nanobot/agent/subagent.py` 的 `_run_subagent()` 逻辑可以保留,但角色改为:
|
||
|
||
- `LocalSubagentExecutor`
|
||
|
||
也可以第一版不重命名文件,只把里面逻辑拆成:
|
||
|
||
1. `spawn_local()`
|
||
2. `_run_local_subagent()`
|
||
3. `_announce_local_result()`
|
||
|
||
这样可以最小改动落地。
|
||
|
||
### 4.3 新增 `AgentRegistry`
|
||
|
||
建议新文件:
|
||
|
||
- `nanobot/agent/agent_registry.py`
|
||
|
||
职责:
|
||
|
||
1. 汇总所有可调度 agent
|
||
2. 统一输出规范化 descriptor
|
||
3. 维护优先级和去重逻辑
|
||
|
||
统一后的 agent 来源:
|
||
|
||
1. workspace 中“已添加的 agent”
|
||
2. plugin agents
|
||
3. skill frontmatter 里声明的 `agent_cards`
|
||
4. 必要时 fallback 到本地 `local-subagent`
|
||
|
||
建议统一 descriptor:
|
||
|
||
```python
|
||
@dataclass
|
||
class AgentDescriptor:
|
||
id: str
|
||
name: str
|
||
description: str
|
||
source: str # workspace | plugin | skill | builtin
|
||
kind: str # local_prompt | a2a_remote | local_fallback
|
||
protocol: str | None # a2a | None
|
||
plugin_name: str | None = None
|
||
skill_name: str | None = None
|
||
model: str | None = None
|
||
endpoint: str | None = None
|
||
card_url: str | None = None
|
||
tags: list[str] = field(default_factory=list)
|
||
capabilities: dict[str, Any] = field(default_factory=dict)
|
||
metadata: dict[str, Any] = field(default_factory=dict)
|
||
```
|
||
|
||
### 4.4 新增 A2A client 层
|
||
|
||
建议新目录:
|
||
|
||
- `nanobot/a2a/client.py`
|
||
- `nanobot/a2a/cards.py`
|
||
- `nanobot/a2a/models.py`
|
||
|
||
职责:
|
||
|
||
1. 获取 agent card
|
||
2. 解析 card 能力
|
||
3. 对远端 agent 发 JSON-RPC 请求
|
||
4. 处理同步返回 / task 轮询 / streaming 兼容
|
||
|
||
## 5. 代码插入点
|
||
|
||
## 5.1 `nanobot/agent/loop.py`
|
||
|
||
### 插入点 A: `__init__`
|
||
|
||
当前:
|
||
|
||
- `self.subagents = SubagentManager(...)`
|
||
- 位置: `nanobot/agent/loop.py:88-102`
|
||
|
||
建议改成:
|
||
|
||
1. 初始化 `PluginLoader`
|
||
2. 初始化 `AgentRegistry`
|
||
3. 初始化 `DelegationManager`
|
||
4. `DelegationManager` 内部持有 `LocalSubagentExecutor` / `A2AExecutor`
|
||
|
||
推荐形态:
|
||
|
||
```python
|
||
self.plugins = PluginLoader(workspace)
|
||
self.agent_registry = AgentRegistry(workspace, plugins=self.plugins, ...)
|
||
self.delegation = DelegationManager(
|
||
provider=provider,
|
||
workspace=workspace,
|
||
bus=bus,
|
||
registry=self.agent_registry,
|
||
...
|
||
)
|
||
```
|
||
|
||
### 插入点 B: `_register_default_tools`
|
||
|
||
当前:
|
||
|
||
- 注册 `SpawnTool(manager=self.subagents)`
|
||
- 位置: `nanobot/agent/loop.py:130-134`
|
||
|
||
建议改成:
|
||
|
||
```python
|
||
self.tools.register(SpawnTool(manager=self.delegation))
|
||
```
|
||
|
||
### 插入点 C: `_set_tool_context`
|
||
|
||
当前会给 `spawn` 工具写 origin context:
|
||
|
||
- 位置: `nanobot/agent/loop.py:165-192`
|
||
|
||
这里逻辑可以继续保留,不需要大改,因为 A2A / group 结果最终也要回到原会话。
|
||
|
||
## 5.2 `nanobot/agent/tools/spawn.py`
|
||
|
||
当前 `SpawnTool` 参数只有:
|
||
|
||
- `task`
|
||
- `label`
|
||
|
||
位置:
|
||
|
||
- schema: `nanobot/agent/tools/spawn.py:49-65`
|
||
- execute: `nanobot/agent/tools/spawn.py:67-76`
|
||
|
||
建议扩成:
|
||
|
||
```python
|
||
{
|
||
"task": "string",
|
||
"label": "string?",
|
||
"target": "string?",
|
||
"targets": "string[]?",
|
||
"strategy": "auto|local|plugin|a2a|group"
|
||
}
|
||
```
|
||
|
||
兼容规则:
|
||
|
||
1. 老调用只传 `task/label` 时,等价于 `strategy="auto"`
|
||
2. `target` 表示单目标
|
||
3. `targets` 表示 group
|
||
4. `strategy="local"` 强制走本地 subagent
|
||
5. `strategy="a2a"` 强制只找 A2A 目标
|
||
|
||
## 5.3 `nanobot/agent/context.py`
|
||
|
||
当前 prompt 中只注入:
|
||
|
||
1. bootstrap
|
||
2. memory
|
||
3. skills summary
|
||
|
||
位置:
|
||
|
||
- `build_system_prompt()`: `nanobot/agent/context.py:38-76`
|
||
|
||
建议新增一段:
|
||
|
||
- `# Available Agents`
|
||
|
||
由 `AgentRegistry.build_agents_summary()` 生成,内容只放:
|
||
|
||
1. agent id / name
|
||
2. 简短 description
|
||
3. source
|
||
4. protocol
|
||
5. 是否支持 group / streaming
|
||
|
||
目标是让主 agent 知道:
|
||
|
||
1. 当前有哪些现成 agent 可用
|
||
2. 什么时候应该 `spawn(target=...)`
|
||
3. 哪些是 skill 暴露出来的 A2A agent
|
||
|
||
## 5.4 `nanobot/agent/skills.py`
|
||
|
||
这是 skill agent cards 的关键入口。
|
||
|
||
当前 skill frontmatter 已支持 `metadata` 字段,并会解析其中的 JSON:
|
||
|
||
- `_parse_nanobot_metadata()`: `nanobot/agent/skills.py:190-196`
|
||
- `_get_skill_meta()`: `nanobot/agent/skills.py:209-212`
|
||
|
||
最推荐的做法不是去扫 `SKILL.md` 正文里的自由文本,而是约定 skill frontmatter 的 `metadata.nanobot.agent_cards`。
|
||
|
||
建议新增:
|
||
|
||
```python
|
||
def list_skill_agent_cards(self) -> list[dict[str, Any]]: ...
|
||
```
|
||
|
||
推荐 skill 写法:
|
||
|
||
```md
|
||
---
|
||
name: github-research
|
||
description: GitHub research helper
|
||
metadata: '{"nanobot":{"agent_cards":[{"id":"repo-analyst","url":"https://example.com/.well-known/agent-card","tags":["github","research"],"auth_env":"REPO_AGENT_TOKEN"}]}}'
|
||
---
|
||
```
|
||
|
||
为什么推荐这样做:
|
||
|
||
1. 当前 frontmatter 解析已经存在
|
||
2. 不需要引入新的 skill 文件格式
|
||
3. 不需要解析自由文本
|
||
4. skill 打包/上传链路也不需要大改
|
||
|
||
## 5.5 `nanobot/agent/plugins.py`
|
||
|
||
当前 plugin agents 已能加载:
|
||
|
||
- `find_agent()`: `nanobot/agent/plugins.py:83-91`
|
||
- `_load_agents()`: `nanobot/agent/plugins.py:210-229`
|
||
|
||
建议:
|
||
|
||
1. `AgentRegistry` 直接复用 `PluginLoader`
|
||
2. plugin agent 作为“本地可执行 agent”来源之一
|
||
|
||
这里不建议把 plugin agent 强行转成 A2A。
|
||
|
||
更合理的处理是:
|
||
|
||
1. plugin agent 本地执行
|
||
2. skill agent cards 远程 A2A 调用
|
||
3. workspace 手动添加的 agent 也可走 A2A
|
||
|
||
## 5.6 `nanobot/config/schema.py`
|
||
|
||
当前 `ToolsConfig` 只有:
|
||
|
||
- `web`
|
||
- `exec`
|
||
- `restrict_to_workspace`
|
||
- `mcp_servers`
|
||
|
||
位置:
|
||
|
||
- `nanobot/config/schema.py:337-347`
|
||
|
||
建议新增:
|
||
|
||
```python
|
||
class A2AConfig(Base):
|
||
enabled: bool = True
|
||
timeout_seconds: int = 30
|
||
poll_interval_seconds: int = 2
|
||
card_cache_ttl_seconds: int = 300
|
||
max_parallel_agents: int = 4
|
||
allow_skill_cards: bool = True
|
||
allow_workspace_agents: bool = True
|
||
allowed_hosts: list[str] = Field(default_factory=list)
|
||
```
|
||
|
||
然后挂到:
|
||
|
||
```python
|
||
class ToolsConfig(Base):
|
||
...
|
||
a2a: A2AConfig = Field(default_factory=A2AConfig)
|
||
```
|
||
|
||
## 5.7 `nanobot/web/server.py`
|
||
|
||
当前 web API 有:
|
||
|
||
- `/api/skills`
|
||
- `/api/plugins`
|
||
|
||
位置:
|
||
|
||
- skills: `nanobot/web/server.py:702-843`
|
||
- plugins: `nanobot/web/server.py:1000-1037`
|
||
|
||
建议新增:
|
||
|
||
1. `GET /api/agents`
|
||
- 返回统一后的 agent registry
|
||
2. `POST /api/agents`
|
||
- 添加 workspace agent card
|
||
3. `DELETE /api/agents/{id}`
|
||
- 删除 workspace agent
|
||
4. `POST /api/agents/refresh`
|
||
- 刷新 card cache
|
||
|
||
这样“已添加的 Agent”才有明确的持久化来源。
|
||
|
||
## 6. 推荐的数据来源优先级
|
||
|
||
为了行为稳定,推荐 resolver 按以下优先级匹配:
|
||
|
||
1. workspace 手动添加的 agent
|
||
2. plugin agents
|
||
3. skill metadata 里的 agent cards
|
||
4. fallback 到本地 subagent
|
||
|
||
原因:
|
||
|
||
1. workspace 手动添加通常是用户明确希望接入的 agent
|
||
2. plugin agent 是本地稳定能力
|
||
3. skill card 往往是外部资源,可信度和可用性最弱
|
||
4. 本地 subagent 最后兜底,保证老行为不失效
|
||
|
||
## 7. A2A 协议接入建议
|
||
|
||
## 7.1 Agent Card 发现
|
||
|
||
建议支持 3 种入口:
|
||
|
||
1. 显式 `card_url`
|
||
2. `base_url + /.well-known/agent-card`
|
||
3. fallback `base_url + /.well-known/agent.json`
|
||
|
||
这样做的原因是:
|
||
|
||
1. 当前 A2A 文档和样例在 card 路径上存在新旧写法并存
|
||
2. 兼容性会更好
|
||
|
||
## 7.2 RPC 调用兼容层
|
||
|
||
建议客户端优先尝试:
|
||
|
||
1. `tasks/send`
|
||
2. 不支持时 fallback `message/send`
|
||
|
||
后续可选支持:
|
||
|
||
1. `tasks/sendSubscribe`
|
||
2. `message/sendStream`
|
||
3. `tasks/get`
|
||
4. `tasks/cancel`
|
||
|
||
推荐第一期先做:
|
||
|
||
1. 非流式发任务
|
||
2. 如果返回 `Task` 状态不是最终态,就轮询 `tasks/get`
|
||
|
||
这样能最小代价先打通。
|
||
|
||
## 7.3 发送给远端 agent 的上下文范围
|
||
|
||
不要把主会话完整 history 直接发给远端 agent。
|
||
|
||
建议第一版只发送:
|
||
|
||
1. 任务目标
|
||
2. 必要的结构化说明
|
||
3. 主 agent 整理好的最小上下文
|
||
|
||
原因:
|
||
|
||
1. 当前本地 subagent 也不共享主会话历史
|
||
2. 外部 A2A agent 不可信时,最小化数据泄漏面
|
||
3. 避免 token 膨胀
|
||
|
||
## 8. agent group 设计
|
||
|
||
## 8.1 什么时候触发 group
|
||
|
||
建议第一版只支持两种触发:
|
||
|
||
1. 用户明确指定多个 agent
|
||
2. LLM 在工具调用里显式传 `targets=[...]`
|
||
|
||
不建议第一版做“自动拆成多个 agent 并行”的强自动化。
|
||
|
||
原因:
|
||
|
||
1. 容易失控
|
||
2. 很难解释为什么调了这些 agent
|
||
3. 对成本和网络调用不可控
|
||
|
||
## 8.2 group 执行链路
|
||
|
||
推荐链路:
|
||
|
||
1. `SpawnTool.execute()` 收到 `targets`
|
||
2. `DelegationManager.dispatch()` 创建 `group_run_id`
|
||
3. `AgentRegistry` 解析出每个 target 的 descriptor
|
||
4. 按 executor 类型并发执行
|
||
5. `asyncio.gather(..., return_exceptions=True)` 收集结果
|
||
6. 统一做 group aggregation
|
||
7. `_announce_group_result()` 回投主消息总线
|
||
8. 主 agent 再生成最终用户回复
|
||
|
||
## 8.3 group 结果聚合
|
||
|
||
建议 group 执行器输出结构化结果:
|
||
|
||
```python
|
||
@dataclass
|
||
class AgentRunResult:
|
||
agent_id: str
|
||
status: str # ok | error | timeout | cancelled
|
||
summary: str
|
||
raw: dict[str, Any] | None = None
|
||
```
|
||
|
||
group 最终回投内容建议类似:
|
||
|
||
```text
|
||
[Agent group 'repo-check' completed]
|
||
|
||
Members:
|
||
- researcher: ok
|
||
- reviewer: ok
|
||
- planner: error
|
||
|
||
Results:
|
||
...
|
||
|
||
Summarize this naturally for the user. Mention disagreements if any.
|
||
```
|
||
|
||
这样能继续复用当前 `system -> main agent -> user` 的输出模式。
|
||
|
||
## 9. 推荐触发方式
|
||
|
||
## 9.1 用户显式触发
|
||
|
||
用户说法示例:
|
||
|
||
1. “把这个任务交给 `github-reviewer`”
|
||
2. “让 `researcher` 和 `reviewer` 一起处理”
|
||
3. “如果有现成 agent 就不要新建 subagent”
|
||
|
||
这时主 agent 应调用:
|
||
|
||
```json
|
||
{
|
||
"task": "...",
|
||
"target": "github-reviewer"
|
||
}
|
||
```
|
||
|
||
或者:
|
||
|
||
```json
|
||
{
|
||
"task": "...",
|
||
"targets": ["researcher", "reviewer"],
|
||
"strategy": "group"
|
||
}
|
||
```
|
||
|
||
## 9.2 模型自主触发
|
||
|
||
当主 agent 判断:
|
||
|
||
1. 任务独立可并行
|
||
2. 已有 agent 专长明显更匹配
|
||
3. 任务耗时长,适合后台执行
|
||
|
||
则调用 `spawn`,但不再默认认为一定是“新建本地 subagent”。
|
||
|
||
## 9.3 自动回退
|
||
|
||
如果没有找到匹配 agent:
|
||
|
||
1. `strategy=auto` -> fallback 本地 subagent
|
||
2. `strategy=a2a` -> 直接返回未找到
|
||
3. `strategy=group` 且部分目标不存在 -> 明确报错或只跑已解析目标,建议第一版严格报错
|
||
|
||
## 10. workspace 中“已添加 agent”的建议存储
|
||
|
||
建议新增:
|
||
|
||
- `workspace/agents/registry.json`
|
||
|
||
示例:
|
||
|
||
```json
|
||
[
|
||
{
|
||
"id": "github-reviewer",
|
||
"name": "GitHub Reviewer",
|
||
"description": "Review GitHub repository changes",
|
||
"protocol": "a2a",
|
||
"base_url": "https://reviewer.example.com/a2a",
|
||
"card_url": "https://reviewer.example.com/.well-known/agent-card",
|
||
"auth_env": "GITHUB_REVIEWER_TOKEN",
|
||
"enabled": true,
|
||
"tags": ["github", "review"]
|
||
}
|
||
]
|
||
```
|
||
|
||
为什么不用直接塞进 `config.json`:
|
||
|
||
1. 这是 workspace 维度资源,不是全局运行参数
|
||
2. web API 做增删改查更方便
|
||
3. 不要求用户每次改 agent 都改配置再重启
|
||
|
||
## 11. 推荐实施顺序
|
||
|
||
### Phase 1: 打通单 agent 路由
|
||
|
||
目标:
|
||
|
||
1. 引入 `AgentRegistry`
|
||
2. `spawn` 支持 `target`
|
||
3. 支持 workspace agent 和 skill agent card
|
||
4. 支持 A2A 单点调用
|
||
5. 找不到时 fallback 本地 subagent
|
||
|
||
### Phase 2: 接入 plugin agent 本地执行
|
||
|
||
目标:
|
||
|
||
1. plugin agent 进入统一 registry
|
||
2. plugin agent 可作为 `target`
|
||
3. 本地 prompt-based agent 与 A2A remote agent 共存
|
||
|
||
### Phase 3: group 并发和聚合
|
||
|
||
目标:
|
||
|
||
1. `targets=[...]`
|
||
2. 并发执行
|
||
3. group 级状态跟踪
|
||
4. 聚合后回投主 agent
|
||
|
||
### Phase 4: web 管理接口
|
||
|
||
目标:
|
||
|
||
1. `/api/agents`
|
||
2. 添加 / 删除 / 刷新 agent
|
||
3. 前端展示 unified registry
|
||
|
||
## 12. 兼容性要求
|
||
|
||
这次改造一定要保留以下兼容性:
|
||
|
||
1. 旧的 `spawn(task, label)` 调用仍然可用
|
||
2. 没有 A2A agent 时,行为和现在一致
|
||
3. skill 没写 `agent_cards` 时,skill 仍只是普通 skill
|
||
4. plugin agent 不参与调度时,现有 plugin 机制不受影响
|
||
|
||
## 13. 风险点
|
||
|
||
### 13.1 A2A 规范新旧写法并存
|
||
|
||
从当前公开文档和样例看,存在这些并行写法:
|
||
|
||
1. card 路径: `/.well-known/agent-card` 和 `/.well-known/agent.json`
|
||
2. RPC 方法: `tasks/send` 和 `message/send`
|
||
|
||
所以客户端必须做兼容适配,不能写死一种。
|
||
|
||
### 13.2 外部 agent 的安全边界
|
||
|
||
需要限制:
|
||
|
||
1. 白名单 host
|
||
2. 超时
|
||
3. card cache TTL
|
||
4. skill card 是否允许自动启用
|
||
|
||
### 13.3 远端 agent 无法直接访问本地 workspace
|
||
|
||
这意味着:
|
||
|
||
1. 不能把“去读本地文件然后处理”原样发给远端 A2A agent
|
||
2. 主 agent 需要先整理出必要上下文
|
||
3. 第一版最好只做文本级委派
|
||
|
||
## 14. 我建议的落地结论
|
||
|
||
如果要控制改动面,又要保证后续可扩展,推荐最终采用下面这个结构:
|
||
|
||
```text
|
||
AgentLoop
|
||
-> SpawnTool
|
||
-> DelegationManager
|
||
-> AgentRegistry / AgentResolver
|
||
-> LocalSubagentExecutor
|
||
-> PluginAgentExecutor
|
||
-> A2AExecutor
|
||
-> AgentGroupExecutor
|
||
-> announce_result() -> MessageBus(system) -> AgentLoop -> user
|
||
```
|
||
|
||
也就是说:
|
||
|
||
1. `spawn` 工具保留
|
||
2. `SubagentManager` 不再是唯一执行器
|
||
3. `DelegationManager` 成为真正总入口
|
||
4. skills 里的 `agent_cards` 用 frontmatter metadata 承载
|
||
5. workspace agent 单独持久化
|
||
6. group 通过并发 executor + 汇总消息实现
|
||
|
||
这是当前仓库里最稳妥、最符合现有架构的改法。
|
||
|
||
## 15. 外部参考
|
||
|
||
以下是我写这个方案时核对的 A2A 资料:
|
||
|
||
1. A2A Protocol Development Guide: https://a2aprotocol.ai/docs/guide/a2a-typescript-guide.html
|
||
2. Python A2A Tutorial: https://a2aprotocol.ai/docs/guide/python-a2a-tutorial-20250513
|
||
|
||
注意:
|
||
|
||
1. 当前公开文档里既能看到 `tasks/send`,也能看到 `message/send`
|
||
2. agent card 路径也能看到 `agent-card` 与 `agent.json` 两种写法
|
||
3. 所以实现时建议做兼容层,不要只押一种命名
|