440 lines
13 KiB
Markdown
440 lines
13 KiB
Markdown
|
|
# 云枢·Agent 会议室架构计划书
|
|||
|
|
|
|||
|
|
> **生成日期**:2026-05-11
|
|||
|
|
> **目的**:从单 Agent 架构升级为"会议室模式"(1 主持 + N 领域专家 + 共享黑板)
|
|||
|
|
> **最终目标**:在云枢上验证通过后,移植到 HxClaw(河虾 Claw)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、架构总览
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户
|
|||
|
|
│
|
|||
|
|
┌──────▼──────────────────────────────────────┐
|
|||
|
|
│ 主持者(dialog-agent)type: main │
|
|||
|
|
│ 人格 + 调度规则 + task + memory 工具 │
|
|||
|
|
│ 唯一入口,用户只和它对话 │
|
|||
|
|
└──────┬───────────────────────────────────────┘
|
|||
|
|
│ task("weather", {city: "北京"})
|
|||
|
|
│ task("earthquake", {region: "通州"})
|
|||
|
|
│ task("memory", {action: "read", ...})
|
|||
|
|
▼
|
|||
|
|
┌───────────────────────────────────────────┐
|
|||
|
|
│ 发言人(领域子 Agent)type: sub │
|
|||
|
|
│ weather / earthquake / memory / narrator │
|
|||
|
|
│ 被调才说话,返回文本 + 可选缓存数据 │
|
|||
|
|
│ 各自的 cache / skills / tools 互相隔离 │
|
|||
|
|
└───────────────────────────────────────────┘
|
|||
|
|
│ 读写
|
|||
|
|
▼
|
|||
|
|
┌───────────────────────────────────────────┐
|
|||
|
|
│ 记录者(记忆系统) │
|
|||
|
|
│ 共享黑板:用户画像、偏好、异常记录 │
|
|||
|
|
│ memory Agent 负责从对话中提取有价值信息 │
|
|||
|
|
│ 所有 Agent 只读,仅 memory Agent 写入 │
|
|||
|
|
└───────────────────────────────────────────┘
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、角色定义
|
|||
|
|
|
|||
|
|
### 2.1 主持者:dialog-agent(type: main)
|
|||
|
|
|
|||
|
|
**职责**:
|
|||
|
|
- 用户的唯一入口
|
|||
|
|
- 有血有肉的个人助理,能闲聊
|
|||
|
|
- 识别用户意图,调度对应的子 Agent
|
|||
|
|
- 读/写记忆(用户画像、上下文摘要)
|
|||
|
|
|
|||
|
|
**工具列表**:
|
|||
|
|
- `task` — 调度子 Agent
|
|||
|
|
- `memory.read` — 读长期记忆
|
|||
|
|
- `memory.write` — 写长期记忆
|
|||
|
|
|
|||
|
|
**System Prompt 包含**:
|
|||
|
|
- 人格(从 `memory.personality` 加载)
|
|||
|
|
- 调度规则(何时调哪个子 Agent)
|
|||
|
|
- 不含任何领域知识
|
|||
|
|
|
|||
|
|
**System Prompt 不包含**:
|
|||
|
|
- 天气知识、地震知识等
|
|||
|
|
- `http-get`、`geocode` 等具体工具
|
|||
|
|
|
|||
|
|
**Session**:
|
|||
|
|
- `session.json` 只存 user ↔ dialog 的对话轮次
|
|||
|
|
- 子 Agent 内部的 tool_calls 不写入
|
|||
|
|
|
|||
|
|
### 2.2 发言人:weather-sub(type: sub)
|
|||
|
|
|
|||
|
|
**职责**:
|
|||
|
|
- 响应天气查询
|
|||
|
|
- 被 `task` 调才执行,不直接面对用户
|
|||
|
|
- 返回显示文本 + 可选缓存数据
|
|||
|
|
|
|||
|
|
**工具列表**:`http-get`, `geocode`, `skill`
|
|||
|
|
|
|||
|
|
**Frontmatter**:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
name: weather
|
|||
|
|
type: sub
|
|||
|
|
description: 天气查询专家
|
|||
|
|
cache:
|
|||
|
|
ttl: 7200
|
|||
|
|
keys: ["city", "forecast_type"]
|
|||
|
|
tools:
|
|||
|
|
- http-get
|
|||
|
|
- geocode
|
|||
|
|
- skill
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.3 发言人:earthquake-sub(type: sub,预留)
|
|||
|
|
|
|||
|
|
**职责**:响应地震信息查询
|
|||
|
|
|
|||
|
|
**Frontmatter**:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
name: earthquake
|
|||
|
|
type: sub
|
|||
|
|
description: 地震信息查询
|
|||
|
|
cache:
|
|||
|
|
ttl: 300
|
|||
|
|
keys: ["region", "time_range"]
|
|||
|
|
tools:
|
|||
|
|
- http-get
|
|||
|
|
- skill
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.4 记录者:memory-sub(type: sub)
|
|||
|
|
|
|||
|
|
**职责**:
|
|||
|
|
- 阅读对话历史,提取用户画像
|
|||
|
|
- 把有价值的信息写入长期记忆数据库
|
|||
|
|
- 响应其他 Agent 的记忆查询
|
|||
|
|
- 记录子 Agent 的异常(如 API 失效)
|
|||
|
|
|
|||
|
|
**工具列表**:`memory.read`, `memory.write`, `read-file`, `write-file`
|
|||
|
|
|
|||
|
|
**Frontmatter**:
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
name: memory
|
|||
|
|
type: sub
|
|||
|
|
description: 记忆管理员
|
|||
|
|
tools:
|
|||
|
|
- memory.read
|
|||
|
|
- memory.write
|
|||
|
|
- read-file
|
|||
|
|
- write-file
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2.5 汇报员:narrator-sub(type: sub,成熟期)
|
|||
|
|
|
|||
|
|
**职责**:把结构化数据翻译成个性化回答
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
name: narrator
|
|||
|
|
type: sub
|
|||
|
|
description: 个性化回答生成器
|
|||
|
|
tools:
|
|||
|
|
- memory.read
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、核心工具:task
|
|||
|
|
|
|||
|
|
### 3.1 职责
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
task(agent_name, arguments)
|
|||
|
|
│
|
|||
|
|
├── 1. 加载 {agent_name}-sub.md Frontmatter
|
|||
|
|
│ ├── name, type, tools, cache
|
|||
|
|
│ ├── cache.keys → ["city", "forecast_type"]
|
|||
|
|
│ └── cache.ttl → 7200
|
|||
|
|
│
|
|||
|
|
├── 2. 拼缓存 key
|
|||
|
|
│ ├── 遍历 cache.keys → 从 arguments 提取值
|
|||
|
|
│ ├── 拼接 → "city=北京&forecast_type=today"
|
|||
|
|
│ └── hash → "a1b2c3d4e5f6"
|
|||
|
|
│
|
|||
|
|
├── 3. 读缓存文件 ~/.config/yunshu/cache/{agent_name}.json
|
|||
|
|
│ ├── HIT → cache_data = {temp: 25, ...}
|
|||
|
|
│ └── MISS → cache_data = null
|
|||
|
|
│
|
|||
|
|
├── 4. 调子 Agent LLM
|
|||
|
|
│ ├── system = {agent_name}-sub.md 内容
|
|||
|
|
│ ├── user = {
|
|||
|
|
│ │ "args": arguments,
|
|||
|
|
│ │ "cache_data": cache_data // 有缓存传数据,没有传 null
|
|||
|
|
│ │ }
|
|||
|
|
│ └── 子 Agent 返回文本 + 可选 ---CACHE--- + JSON
|
|||
|
|
│
|
|||
|
|
├── 5. 处理子 Agent 返回
|
|||
|
|
│ ├── 有 ---CACHE--- → 提取后面的 JSON → 写缓存
|
|||
|
|
│ └── 无 ---CACHE--- → 只传文本
|
|||
|
|
│
|
|||
|
|
└── 6. 返回显示文本给 Host(dialog Agent 的 LLM)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3.2 缓存文件格式
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
// ~/.config/yunshu/cache/{agent_name}.json
|
|||
|
|
{
|
|||
|
|
"<hash>": {
|
|||
|
|
"created_at": "2026-05-11T06:00:00+08:00",
|
|||
|
|
"ttl": 7200,
|
|||
|
|
"data": {
|
|||
|
|
"temp": 25,
|
|||
|
|
"condition": "晴"
|
|||
|
|
},
|
|||
|
|
"raw": {
|
|||
|
|
"city": "北京",
|
|||
|
|
"forecast_type": "today"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- hash 由 `cache.keys` 从 `arguments` 中提取值 → 拼接 → SHA256 取前 12 位
|
|||
|
|
- `raw` 存原始参数,方便调试和遍历
|
|||
|
|
- 每次读缓存时惰性清理过期条目
|
|||
|
|
|
|||
|
|
### 3.3 传给子 Agent 的参数
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"args": {
|
|||
|
|
"city": "北京",
|
|||
|
|
"forecast_type": "today",
|
|||
|
|
"units": "C"
|
|||
|
|
},
|
|||
|
|
"cache_data": {
|
|||
|
|
"temp": 25,
|
|||
|
|
"condition": "晴"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
- `args` 是 dialog 传过来的原始参数
|
|||
|
|
- `cache_data` 是缓存的数据(有缓存时),子 Agent 据此直接回答,省一次 API 调用
|
|||
|
|
- 两者都是原始数据,不是处理过的文本
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、记忆系统
|
|||
|
|
|
|||
|
|
### 4.1 存储位置
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
~/.config/yunshu/memory.db (或 memory.json,MVP 阶段)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.2 数据模型
|
|||
|
|
|
|||
|
|
```json
|
|||
|
|
{
|
|||
|
|
"personality": "你是个幽默风趣的北京大妞,说话带点贫",
|
|||
|
|
"user_profile": {
|
|||
|
|
"location": "北京通州",
|
|||
|
|
"unit": "C",
|
|||
|
|
"allergies": ["花粉"],
|
|||
|
|
"interests": ["户外"],
|
|||
|
|
"mood_today": null
|
|||
|
|
},
|
|||
|
|
"agent_errors": {
|
|||
|
|
"weather": ["msn_api_500 at 2026-05-11T06:00:00"],
|
|||
|
|
"earthquake": []
|
|||
|
|
},
|
|||
|
|
"dialog_context": {
|
|||
|
|
"last_agent": "weather",
|
|||
|
|
"last_topic": "北京天气",
|
|||
|
|
"summary": "用户问了北京天气"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 读写规则
|
|||
|
|
|
|||
|
|
| 操作 | 谁做 | 时机 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| `memory.read` | dialog / 子 Agent | 需要画像时 |
|
|||
|
|
| `memory.write` | 只有 memory Agent | 从对话中提取画像后 |
|
|||
|
|
| `memory.write("dialog_context")` | dialog | 每次回答后 |
|
|||
|
|
|
|||
|
|
### 4.4 memory Agent 的工作流
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户: "我住北京通州,最近花粉过敏厉害"
|
|||
|
|
→ dialog 聊天回应
|
|||
|
|
→ dialog: task("memory", {action: "extract", text: "用户说住通州、花粉过敏"})
|
|||
|
|
→ memory: 提取 → memory.write("user_profile.location", "北京通州")
|
|||
|
|
memory.write("user_profile.allergies", ["花粉"])
|
|||
|
|
|
|||
|
|
用户: "今天天气怎么样?"
|
|||
|
|
→ dialog: task("memory", {action: "read_context"}) → 有 location
|
|||
|
|
→ dialog: task("weather", {city: "北京通州"})
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、文件结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
yunshu/
|
|||
|
|
├── main.go # CLI 入口
|
|||
|
|
├── types.go # 核心类型(AgentDef, ToolDef 等)
|
|||
|
|
├── loader.go # .md 解析(Frontmatter + Body)
|
|||
|
|
├── registry.go # Agent 注册中心(扫描 + 按 type 分类)
|
|||
|
|
├── llm.go # LLM API 封装
|
|||
|
|
├── tool.go # 工具注册表 + ExecuteTool
|
|||
|
|
├── runtime.go # RunAgent 主循环
|
|||
|
|
│
|
|||
|
|
├── agents/
|
|||
|
|
│ ├── dialog-agent.md # type: main — 主持者
|
|||
|
|
│ ├── weather-sub.md # type: sub — 天气
|
|||
|
|
│ ├── earthquake-sub.md # type: sub — 地震(预留)
|
|||
|
|
│ ├── memory-sub.md # type: sub — 记忆管理员
|
|||
|
|
│ └── narrator-sub.md # type: sub — 汇报员(成熟期)
|
|||
|
|
│
|
|||
|
|
├── skills/
|
|||
|
|
│ ├── msn-weather-api/SKILL.md
|
|||
|
|
│ └── geocoding/SKILL.md
|
|||
|
|
│
|
|||
|
|
├── docs/
|
|||
|
|
│ ├── taolun.md
|
|||
|
|
│ ├── 会议室架构计划书.md
|
|||
|
|
│ ├── changelog.md
|
|||
|
|
│ ├── architecture.md
|
|||
|
|
│ └── AGENTS.md
|
|||
|
|
│
|
|||
|
|
└── pkg/
|
|||
|
|
├── mdprint/
|
|||
|
|
├── style/
|
|||
|
|
└── termui/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 用户配置目录
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
~/.config/yunshu/
|
|||
|
|
├── config.yaml # LLM 配置
|
|||
|
|
├── session.json # 对话历史(仅 user ↔ dialog)
|
|||
|
|
├── agents/
|
|||
|
|
│ ├── dialog-agent.md # 用户可覆盖对话 Agent
|
|||
|
|
│ └── weather-sub.md # 用户可覆盖天气子 Agent
|
|||
|
|
├── skills/ # 用户可扩展知识
|
|||
|
|
├── cache/
|
|||
|
|
│ ├── weather.json
|
|||
|
|
│ ├── earthquake.json
|
|||
|
|
│ └── ...
|
|||
|
|
├── data/
|
|||
|
|
│ └── weather/ # 子 Agent 自己的数据目录
|
|||
|
|
└── memory.db # 长期记忆数据库
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、调用流程示例
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
用户: "北京明天多少度?"
|
|||
|
|
|
|||
|
|
HOST(runtime.go):
|
|||
|
|
1. 加载 dialog-agent.md → system prompt
|
|||
|
|
2. 读 session.json → 恢复上下文
|
|||
|
|
3. 调 LLM(session + system + tools)
|
|||
|
|
4. LLM 返回 tool_call: task("weather", {city: "北京", forecast_type: "tomorrow"})
|
|||
|
|
|
|||
|
|
task 工具:
|
|||
|
|
1. 加载 weather-sub.md Frontmatter
|
|||
|
|
→ cache.keys: ["city", "forecast_type"], ttl: 7200
|
|||
|
|
2. 拼 key → "city=北京&forecast_type=tomorrow" → hash
|
|||
|
|
3. 查 weather.json → MISS(首次查明天)
|
|||
|
|
4. 调子 Agent LLM:
|
|||
|
|
system = weather-sub.md
|
|||
|
|
user = {args: {city: "北京", forecast_type: "tomorrow"}, cache_data: null}
|
|||
|
|
5. 子 Agent:
|
|||
|
|
├── geocode("北京") → (39.9, 116.4)
|
|||
|
|
├── skill("msn-weather-api") → 接口参数
|
|||
|
|
├── http-get(URL) → JSON
|
|||
|
|
└── 返回: "北京明天 18-31°C,晴"
|
|||
|
|
---CACHE---
|
|||
|
|
{temp_lo: 18, temp_hi: 31, condition: "晴"}
|
|||
|
|
6. task 提取 CACHE → 写 weather.json
|
|||
|
|
7. 返回 "北京明天 18-31°C,晴" 给 dialog
|
|||
|
|
|
|||
|
|
HOST(runtime.go):
|
|||
|
|
1. tool 返回 → LLM 继续
|
|||
|
|
2. LLM 生成最终回答:
|
|||
|
|
"北京明天 18到31度,大晴天,适合出去浪~"
|
|||
|
|
3. dialog: task("memory", {action: "update_context", agent: "weather", city: "北京"})
|
|||
|
|
4. 追加 session.json
|
|||
|
|
5. 输出给用户
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、实施阶段
|
|||
|
|
|
|||
|
|
### 阶段一:基础架构(当前 → 1周)
|
|||
|
|
|
|||
|
|
| 任务 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| 1.1 Frontmatter 扩展 | 解析 `type: main\|sub`、`cache` 字段 |
|
|||
|
|
| 1.2 Agent 注册中心 | `registry.go` 扫描 `agents/` 和 `~/.config/yunshu/agents/`,按 type 分类 |
|
|||
|
|
| 1.3 `task` 工具 | 实现子 Agent 加载、LLM 调用、缓存读写 |
|
|||
|
|
| 1.4 Cache 系统 | `cache/` 目录管理、JSON 文件读写、过期清理 |
|
|||
|
|
| 1.5 `memory.read/write` 工具 | 简单的 JSON 文件读写 |
|
|||
|
|
| 1.6 dialog-agent.md | 重写为主持者(极薄:人格 + 调度规则) |
|
|||
|
|
| 1.7 weather-sub.md | 从旧 weather-agent.md 改造 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
### 阶段二:记忆系统(阶段一完成后)
|
|||
|
|
|
|||
|
|
| 任务 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| 2.1 memory-sub.md | 记忆管理员 Agent(从对话提取画像) |
|
|||
|
|
| 2.2 记忆数据库 | 结构化存储(画像、偏好、异常记录) |
|
|||
|
|
| 2.3 画像自动提取 | memory Agent 定期从对话中提取有用信息 |
|
|||
|
|
|
|||
|
|
|
|||
|
|
### 阶段三:扩展(可选)
|
|||
|
|
|
|||
|
|
| 任务 | 说明 |
|
|||
|
|
|------|------|
|
|||
|
|
| 3.1 earthquake-sub | 地震信息查询 |
|
|||
|
|
| 3.2 narrator-sub | 个性化回答生成 |
|
|||
|
|
| 3.3 更多数据源 | 台风、核电、火山... |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、与 PicoClaw 的对比
|
|||
|
|
|
|||
|
|
| 维度 | PicoClaw | 云枢·会议室模式 |
|
|||
|
|
|------|----------|----------------|
|
|||
|
|
| 入口 | 单 Agent(用户直接对话) | 对话 Agent(唯一入口)+ 背后一堆子 Agent |
|
|||
|
|
| 上下文 | 所有轮次 + 系统 prompt 混在一起 | session 只存 user↔dialog,子 Agent 用完即毁 |
|
|||
|
|
| 知识 | 预置或长 prompt | skill 按需加载 |
|
|||
|
|
| 工具 | 所有工具混着用 | 按角色过滤,dialog 只有 task + memory |
|
|||
|
|
| 记忆 | 无 | 共享黑板,memory Agent 管写入 |
|
|||
|
|
| 扩展 | 改代码或改 prompt | 加一个 .md 文件 |
|
|||
|
|
| 失败隔离 | 坏 tool_call 可能污染全部 | 子 Agent 独立,坏就坏一个 |
|
|||
|
|
| 用户自定义 | 不可能 | 在 `~/.config/yunshu/agents/` 放 .md 即可 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 九、设计原则
|
|||
|
|
|
|||
|
|
1. **主持者保持极薄** — 只有人格 + 调度规则,不做领域知识
|
|||
|
|
2. **子 Agent 不自知** — 不知道缓存存在、不管理自己的 session,只回答当前问题
|
|||
|
|
3. **机械化的不做 LLM** — 缓存 key 拼装、文件读写都是 Go 代码,LLM 不参与
|
|||
|
|
4. **数据隔离** — 子 Agent 的 cache 文件、data 目录互相独立
|
|||
|
|
5. **记忆共享** — 黑板机制,所有 Agent 可读,仅 memory Agent 可写
|
|||
|
|
6. **一个入口** — 用户永远只和 dialog-agent 对话,感受不到子 Agent 的存在
|