docs: 会议室架构规划 + MSN hourlyforecast 端点更新

- 新增 docs/会议室架构计划书.md 完整架构方案(主持者+子Agent+task+cache+记忆)
- 更新 taolun.md 追加 2026-05-11 讨论历史
- 更新 AGENTS.md 规范(type, cache 字段)
- 更新 architecture.md 后续演进章节
- 更新 changelog.md 架构规划里程碑
- 修复 MSN 天气接口文档:新增 hourlyforecast,标记 weathertrends 已失效
- 更新 skills/msn-weather-api/SKILL.md 新增 hourlyforecast 端点
- 更新 agents/weather-agent.md 支持逐小时查询
This commit is contained in:
titor
2026-05-11 08:32:30 +08:00
parent ab6ec38cec
commit 0898188086
8 changed files with 774 additions and 26 deletions

View File

@@ -18,6 +18,8 @@ tools:
2. **获取坐标** — 调用 `geocode` 工具获取城市经纬度
3. **加载 API 知识** — 调用 `skill("msn-weather-api")` 获取 MSN 天气 API 的请求参数
4. **请求数据** — 用获取到的坐标和 API 参数,通过 `http-get` 请求天气数据
- 一般查询:调用 `current` + `dailyforecast`days=10
- 逐小时询问(如"今天几点下雨""下午热不热"):额外调用 `hourlyforecast`
5. **分析回答** — 解析 JSON 并给出清晰、有用的回答
## 追问处理
@@ -25,10 +27,12 @@ tools:
- 如果用户追问(如"适合穿什么?""风大不大?"),优先基于已有数据回答,无需重复 API 调用
- 如果用户问另一个城市,重新执行完整流程
- 如果数据明显过时(超过 2 小时),重新请求
- 如果之前只请求了日预报,用户转而问逐小时问题,额外调用 `hourlyforecast`
## 输出规范
回答要清晰友好,包含关键信息:
- 当前温度、体感温度、天气状况
- 湿度、风速、空气质量
- 逐小时回答时标明具体时间点,如"13:00 约 25°C多云"
- 根据天气给出实用建议(如"建议带伞""适合户外"等)

View File

@@ -24,34 +24,70 @@
## Agent 定义规范(.md 文件)
- 必须包含 YAML frontmatter`---` 包裹)
- frontmatter 必需字段:`name`, `description`, `tools`
- frontmatter 必需字段:`name`, `description`, `type`, `tools`
- `type` 可选值:`main`(主持者/对话 Agent唯一入口`sub`(领域专家,被 task 调)
- `cache` 可选字段:`ttl`(过期秒数)、`keys`(从 args 中提取的缓存 key 字段列表)
- tools 为数组,声明 agent 需要的工具名(在 tool.go 中注册)
- body 为 system prompt**只定义行为逻辑**(角色、工作流程、输出规范)
- **关键技术细节URL、apiKey、请求头、JSON 路径等)不要 inline 在 agent skill 中**,改为:
- 放到 `skills/*/SKILL.md` 中,由 agent 调用 `skill("name")` 按需加载
- 或注册为 tool确定性操作由 agent 声明 tools 即可调用
- session 文件存在 `~/.config/weather-cli/session.json`,不污染项目目录
- session 文件存在 `~/.config/yunshu/session.json`,不污染项目目录
### 示例
### 主持者示例type: main
```markdown
---
name: weather-agent
description: 天气情报官
name: dialog
type: main
description: 个人助理,负责闲聊和调度
tools:
- task
- memory.read
- memory.write
---
# 对话助理
你是用户的私人助理...
你可以调度以下子 Agent
- weather: 天气查询
- earthquake: 地震信息
用 task("agent_name", {args}) 调度。
不自己回答领域问题。
```
### 子 Agent 示例type: sub
```markdown
---
name: weather
type: sub
description: 天气查询专家
cache:
ttl: 7200
keys: ["city", "forecast_type"]
tools:
- http-get
- geocode
- skill
---
# 天气情报官
你是专业的天气情报官
# 天气专家
你是天气领域的专家。被调时才回答
## 工作流程
1. 识别城市 → 调用 geocode 获取坐标
2. 调用 skill("msn-weather-api") 获取 API 参数
3. 调用 http-get 请求天气数据
4. 分析并输出
被调时你会收到:
- args: 查询参数
- cache_data: 上次缓存的原始数据(有时)
有 cache_data 且未过期 → 直接回答,不调 API。
无 cache_data → 调 http-get 获取新数据。
返回格式:
你的回答文本
---CACHE---(只有数据更新时带)
{原始 JSON 数据}
```
## Session 规范
@@ -127,3 +163,15 @@ tools:
2. **ANSI 光标移动需要启用输出 VT 处理**`\033[A`(光标上移)/ `\033[J`(清屏)等输出序列需要输出句柄设置 `ENABLE_VIRTUAL_TERMINAL_PROCESSING`0x0004才能生效。只设置输入句柄的 mode 不够,输入输出句柄是两个独立的控制台句柄。
3. **ReadLineWithCompletion / Completer 类型已移除**`completer.go` 清空,`main.go` 回到 `termui.ReadLine()``cmdCompleter``commonPrefix`、相关测试一并移除。`input.go``ReadConsoleInputW` 相关的 `keyEventRecord`/`inputRecord` 结构和 proc 也一并清理。
### 2026-05-11
1. **MSN 天气存在 `hourlyforecast` 端点**`https://assets.msn.cn/service/weather/hourlyforecast`,返回未来 10 天逐小时预报数据,参数和 dailyforecast 一致。之前文档遗漏了该端点。`weathertrends` 端点已失效500 Internal Server Error
2. **MSN 的 `assets.msn.cn` 国内城市接口正常**:用经纬度查询 `assets.msn.cn/service/weather/current` 对国内城市返回正确数据。之前 Agent 误报"返回也门数据"是因为用了 `api.msn.cn` 的城市名接口(该接口本就有文档标注的问题:城市名匹配不可靠)。**用 `assets.msn.cn` + 经纬度即可正常获取国内城市数据,不需要切换到 wttr.in。**
3. **会议室架构决策**:确定从单 Agent 升级为 1 主持dialog, type:main+ N 领域专家type:sub+ 共享黑板memory的架构。主持者保持极薄只有人格 + 调度规则 + `task` + `memory` 工具),子 Agent 只做领域工作,用完即毁。详见 `docs/会议室架构计划书.md`
4. **Cache 设计原则**Frontmatter 声明 `cache.keys``cache.ttl``task` 工具机械化从 args 取值拼 key、查/写缓存。子 Agent 不感知缓存存在,只需在回答末尾可选带 `---CACHE---` + JSON 供 task 存储。一个 Agent 一个缓存 JSON 文件MD5 hash 做 key。
5. **记忆系统规则**:共享黑板模式,所有 Agent 可读,仅 memory Agent 可写。dialog-agent 是最小写入者(只写 `dialog_context`memory Agent 负责从对话中提取用户画像写入长期记忆。

View File

@@ -19,13 +19,14 @@
| 接口 | URL | 功能 | 稳定性 |
|------|-----|------|--------|
| **当前天气** | `https://assets.msn.cn/service/weather/current` | 获取实时天气 | ✅ 稳定可用 |
| **每日预报** | `https://assets.msn.cn/service/weather/dailyforecast` | 未来10天预报 | ✅ 稳定可用 |
| **天气趋势** | `https://assets.msn.cn/service/weather/weathertrends` | 历史+趋势+日历 | ✅ 可用(参数复杂) |
| **当前天气** | `https://assets.msn.cn/service/weather/current` | 获取实时天气+nowcasting | ✅ 稳定可用 |
| **逐小时预报** | `https://assets.msn.cn/service/weather/hourlyforecast` | 未来10天逐小时预报 | ✅ 稳定可用(新增) |
| **每日预报** | `https://assets.msn.cn/service/weather/dailyforecast` | 未来10天每日汇总 | ✅ 稳定可用 |
| **天气趋势** | `https://assets.msn.cn/service/weather/weathertrends` | 历史+趋势+日历 | ❌ 已失效500错误 |
| api.msn.cn 当前 | `https://api.msn.cn/weather/current` | 用城市名获取 | ✅ 可用(但城市名不准) |
| api.msn.cn 预报 | `https://api.msn.cn/weather/forecast` | 预报 | ❌ 500错误 |
**推荐**:只用 `assets.msn.cn`个接口即可满足大部分需求。
**推荐**:只用 `assets.msn.cn`个接口current + hourlyforecast + dailyforecast即可满足大部分需求。
---
@@ -90,6 +91,16 @@ foreach ($day in $days) {
$d = $day.daily
Write-Host " $($d.valid.ToString().Substring(0,10)): $($d.tempLo)-$($d.tempHi)C, 降水$($d.precip)%, 风速$($d.windMax)km/h"
}
# 获取逐小时预报(今天剩余小时 + 后续几天)
$uri_hourly = "https://assets.msn.cn/service/weather/hourlyforecast?apiKey=$apiKey&lat=39.904172&lon=116.407417&units=C&locale=zh-cn"
$hourlyResp = Invoke-RestMethod -Uri $uri_hourly -Headers $headers
$todayHourly = $hourlyResp.value[0].responses[0].weather[0].days[0].hourly
Write-Host "`n今天逐小时预报:"
foreach ($h in $todayHourly) {
Write-Host " $($h.valid.ToString("HH:mm")): $($h.temp)C, $($h.cap), 体感$($h.feels)C, 降水$($h.precip)%, 湿度$($h.rh)%, 风速$($h.windSpd)km/h"
}
```
### curl 示例
@@ -104,6 +115,11 @@ curl -H "User-Agent: Mozilla/5.0" \
curl -H "User-Agent: Mozilla/5.0" \
-H "Referer: https://www.msn.com/zh-cn/weather" \
"https://assets.msn.cn/service/weather/dailyforecast?apiKey=j5i4gDqHL6nGYwx5wi5kRhXjtf2c5qgFX9fzfk0TOo&lat=39.904172&lon=116.407417&units=C&locale=zh-cn&days=7"
# 逐小时预报(今天剩余+未来几天)
curl -H "User-Agent: Mozilla/5.0" \
-H "Referer: https://www.msn.com/zh-cn/weather" \
"https://assets.msn.cn/service/weather/hourlyforecast?apiKey=j5i4gDqHL6nGYwx5wi5kRhXjtf2c5qgFX9fzfk0TOo&lat=39.904172&lon=116.407417&units=C&locale=zh-cn"
```
---
@@ -180,6 +196,52 @@ curl -H "User-Agent: Mozilla/5.0" \
}
```
### hourlyforecast 接口响应
```json
{
"@odata.context": "api.msn.com/weather/$metadata#hourlyforecast",
"value": [{
"responses": [{
"weather": [{
"days": [
{
// days[0] = 今天从当前小时开始到23点
// days[1..9] = 未来9天每天24个整点
"hourly": [
{
"valid": "2026-05-11T07:00:00+08:00", // 时间
"temp": 19.0, // 温度 °C
"feels": 23.0, // 体感温度 °C
"cap": "晴", // 天气描述
"precip": 0.0, // 降水概率 %
"rh": 61.0, // 相对湿度 %
"baro": 1009.0, // 气压 hPa
"windSpd": 4.0, // 风速 km/h
"windDir": 355, // 风向(度)
"windGust": 18.0, // 阵风 km/h
"uv": 1.0, // 紫外线指数
"cloudCover": 6.0, // 云量 %
"vis": 10.0, // 能见度 km
"dewPt": 11.0, // 露点 °C
"rainAmount": 0.0, // 降雨量 mm
"snowAmount": 0.0, // 降雪量 mm
"icon": 1, // 图标代码
"symbol": "d000", // 天气符号
"sky": "CLR" // 天空状况代码
}
// ... 更多小时
]
}
]
}]
}]
}]
}
```
> **注意**`days[0].daily` 为 null今天尚未结束逐小时数据从 `days[0].hourly` 获取。
---
## 六、多城市验证结果
@@ -246,15 +308,21 @@ function Get-MSNWeather {
$forecastUri = "https://assets.msn.cn/service/weather/dailyforecast?apiKey=$apiKey&lat=$Lat&lon=$Lon&units=C&locale=$Locale&days=7"
$forecast = Invoke-RestMethod -Uri $forecastUri -Headers $headers
# 逐小时预报
$hourlyUri = "https://assets.msn.cn/service/weather/hourlyforecast?apiKey=$apiKey&lat=$Lat&lon=$Lon&units=C&locale=$Locale"
$hourly = Invoke-RestMethod -Uri $hourlyUri -Headers $headers
return @{
Current = $current.value[0].responses[0].weather[0].current
Forecast = $forecast.value[0].responses[0].weather[0].days
Hourly = $hourly.value[0].responses[0].weather[0].days
}
}
# 使用示例
$weather = Get-MSNWeather -Lat 39.904172 -Lon 116.407417
$weather.Current.temp # 当前温度
$weather.Hourly[0].hourly # 今天逐小时数据
```
---
@@ -290,8 +358,15 @@ http://img-s-msn-com.akamaized.net/tenant/amp/entityid/AAehR3S.img
| 是否有免费 API | ✅ 有(非公开内部接口) |
| 国内速度 | ✅ 快msn.cn 国内节点) |
| 稳定性 | ⚠️ 未知(非官方,随时可能变) |
| 数据完整性 | ✅ 完整(当前+预报+AQI+紫外线 |
| 数据完整性 | ✅ 完整(当前+逐小时+每日预报+AQI+紫外线+nowcasting |
| 推荐用途 | 个人项目、内部工具、原型开发 |
| 不推荐用途 | 商业产品、长期运行服务 |
## 现状更新
**2026-05-11 更新:**
- `weathertrends` 接口已失效500 Internal Server Error
- 新发现 `hourlyforecast` 接口,提供未来 10 天逐小时预报数据,与 `current``dailyforecast` 同样稳定
- 云图(卫星/雷达)无 REST API 可用MSN 网页使用 tile 图片服务(`assets.msn.com/weathermapdata/`),不适合程序化调用
**建议**如果用于生产环境推荐同时准备备用方案如和风天气、OpenWeatherMap 等)。

View File

@@ -69,15 +69,47 @@ pkg/
| geocode | 城市名 → 坐标 | Go调 wttr.in |
| read-file | 读取文件 | Go |
## 当前 tools
| 工具名 | 作用 | 实现 |
|--------|------|------|
| http-get | HTTP GET 请求 | Go |
| skill | 按需加载知识 | Go |
| geocode | 城市名 → 坐标 | Go调 wttr.in |
| read-file | 读取文件 | Go |
| task | 调度子 Agent含缓存管理 | Go阶段一新增 |
| memory.read | 读长期记忆 | Go阶段一新增 |
| memory.write | 写长期记忆 | Go阶段一新增 |
## 后续演进
### 当前(单 Agent
```
云枢·Agent (三层分离+单agent)
河虾 claw (三层分离+主-从)
├─ master: 意图识别+任务分发
├─ weather-subagent
├─ tts-subagent
├─ asr-subagent
└─ ...更多 subagent
yunshu (三层分离+单agent)
└─ weather-agent.md (type: main既是入口也是天气专家)
```
### 阶段一(会议室架构基础)
```
yunshu (会议室架构)
├── dialog-agent.md (type: main入口+调度)
├── weather-sub.md (type: sub天气领域)
├── memory-sub.md (type: sub记忆管理)
└── narrator-sub.md (type: sub汇报员成熟期)
```
### 阶段二(多领域扩展)→ 河虾 Claw
```
yunshu / hxclaw (多领域主-从)
├── dialog-agent.md (type: main入口+调度)
├── weather-sub.md (type: sub天气)
├── earthquake-sub.md (type: sub地震)
├── volcano-sub.md (type: sub火山)
├── nuclear-sub.md (type: sub核电监测)
├── memory-sub.md (type: sub记忆)
└── narrator-sub.md (type: sub汇报)
```
## 架构文档
详细架构计划见 `docs/会议室架构计划书.md`

View File

@@ -2,6 +2,42 @@
> 坐看云卷云舒,静听花开花落
## [2.0.0-planning] - 2026-05-11
### 架构规划:会议室模式
完成从单 Agent 到"会议室架构"的完整设计,核心变更:
**新增角色体系**
- `type: main` — 主持者(对话 Agent唯一用户入口
- `type: sub` — 发言人(领域子 Agent`task` 调才说话
**新增工具**(待实现):
- `task` — 调度子 Agent + 缓存管理
- `memory.read` / `memory.write` — 长期记忆读写
**新增 Cache 机制**
- 子 Agent Frontmatter 声明 `cache.ttl` + `cache.keys`
- `task` 工具机械化拼 key、查/写缓存
- 一个 Agent 一个缓存 JSON 文件,子 Agent 无感知
**设计文档**
- `docs/会议室架构计划书.md` — 完整架构方案
- `docs/architecture.md` — 更新后续演进章节
- `docs/AGENTS.md` — 更新 Agent 定义规范type, cache 字段)
- `docs/taolun.md` — 追加 2026-05-11 讨论历史
**MSN 天气接口更新**
- 新增 `hourlyforecast` 端点文档
- 标记 `weathertrends` 为已失效
- 更新 `skills/msn-weather-api/SKILL.md``agents/weather-agent.md`
### 技术细节
- Frontmatter 新增 `type` 字段main/sub
- Frontmatter 新增 `cache` 字段(`{ttl: int, keys: [string]}`
- 用户配置目录 `~/.config/yunshu/` 下可选覆盖 agents/
- 详见 `docs/会议室架构计划书.md`
## [1.1.0] - 2026-05-09
### 发布摘要

View File

@@ -242,3 +242,90 @@ Markdown 渲染器完成 AST 解析后,需要确定标题的终端展示风格
### 验证
- 19/19 单元测试通过
- 构建成功,二进制运行正常
---
## 2026-05-11 会议室架构:从单 Agent 到主-从调度
### 背景
用户发现 `weathertrends` 接口失效,逐小时数据缺失。在排查中意外发现 `hourlyforecast` 端点存在且正常工作,文档之前遗漏了。同时回顾了与天气 Agent 的对话,发现 Agent 汇报 MSN 接口"国内城市不可用"的判断有误——实际 `assets.msn.cn` 按经纬度查一直正常Agent 用的是 `api.msn.cn` 城市名接口(其文档本就标注"北京返回了也门萨那")。
这暴露了单 Agent 架构的局限性Agent 的自我判断不可靠,上下文一多就容易出偏差。
### 讨论历程
#### 从"PicoClaw 为什么崩"出发
| PicoClaw 痛点 | 原因 |
|--------------|------|
| 上下文污染 | 所有知识/工具/历史全混在同一个 system prompt |
| Skill 污染 | skill 内联大量技术细节,退化为长 prompt |
| 逃避执行 | LLM 倾向"自己回答"而非调工具 |
| 扩展困难 | 加能力 = 改代码或改长 prompt |
用户想从 0 实现一个干净的架构,先在云枢上验证,再移植到 HxClaw河虾Claw
#### 方案演进
| 轮次 | 方案 | 问题 |
|------|------|------|
| 1 | CLI 切换主 Agent`--agent weather/earthquake` | 家庭用户记不住命令,跨域查询(火山附近天气)没法做 |
| 2 | 唯一对话入口 + task 调度子 Agent | 但担心记忆管理和上下文混乱 |
| 3 | 命名空间隔离记忆 | 无法共享用户画像(住通州 → 查地震也要知道住通州) |
| 4 | **会议室模式**(最终方案) | 共享黑板(记忆)+ 主持者 + 发言人,角色隔离而非数据隔离 |
#### 关键设计决策
1. **主 Agent 即对话 Agent**`type: main`),用户唯一入口,扮演"个人助理"角色
2. **子 Agent**`type: sub`)是领域专家,被 `task` 工具调才说话,不直接面对用户
3. **`task` 工具**负责:加载子 Agent → 查/写缓存 → 调子 Agent LLM → 返回文本
4. **Cache 机制**Frontmatter 声明 `cache.keys``task` 工具机械化拼 key、查/写文件
5. **记忆管理员**`memory` 子 Agent负责从对话中提取用户画像写入记忆数据库
6. **所有子 Agent 回答经过对话 Agent 返回给用户**,保持单一入口
#### 最终角色定义
| Agent | type | 职责 | 工具 | 缓存 |
|-------|------|------|------|------|
| dialog | main | 入口 + 聊天 + 调度 | `task`, `memory.read/write` | 无(本身就是对话历史) |
| weather | sub | 查天气 | `http-get`, `geocode`, `skill` | `keys: [city, forecast_type]`, ttl: 7200 |
| earthquake | sub | 查地震 | `http-get`, `skill` | `keys: [region]`, ttl: 300 |
| memory | sub | 管理画像/长期记忆 | 读 memory.db | 无 |
| narrator | sub成熟期 | 格式化回答 | `memory.read` | 无 |
#### Cache 设计
```json
// ~/.config/yunshu/cache/weather.json
{
"<hash_of_keys>": {
"created_at": "2026-05-11T06:00:00+08:00",
"ttl": 7200,
"data": {...}, // 原始 API 数据
"raw": {"city": "北京", "forecast_type": "today"} // 原始参数
}
}
```
- 子 Agent 每次回答末尾可选带 `---CACHE---` + JSON只在数据更新时带
- `task` 工具查缓存HIT → 把 `cache.data` 作为 `cache_data` 传给子 AgentMISS → 子 Agent 自己查 API
- 一个 Agent 一个缓存 JSON 文件,里面一个 mapkey 是 hash
#### 会话存储
| 类型 | 位置 | 内容 | 生命周期 |
|------|------|------|---------|
| 对话历史 | `session.json` | 只存 user <-> dialog 的消息 | 每次启动清空 |
| 子 Agent 内部 | 临时 | tool_calls、LLM 调用 | 用完即毁 |
| 长期记忆 | 记忆数据库 | 用户画像、偏好、异常记录 | 持久 |
#### 对比结论
| | PicoClaw | 新方案 |
|---|---|---|
| 架构 | 单 Agent 全能 | 1 主持 + N 领域专家 |
| 上下文 | 全混在 system prompt | Host 只有人格+调度Sub 只有领域 |
| 扩展 | 改代码或改长 prompt | 加一个 `.md` 文件 |
| 记忆 | 无 | 共享黑板Memory Agent 管写入 |
| 工具污染 | 所有工具混在一起 | 按角色过滤 |
| 失败影响 | 一个坏 tool_call 可能污染全部 | 子 Agent 用完即毁 |

View File

@@ -0,0 +1,439 @@
# 云枢·Agent 会议室架构计划书
> **生成日期**2026-05-11
> **目的**:从单 Agent 架构升级为"会议室模式"1 主持 + N 领域专家 + 共享黑板)
> **最终目标**:在云枢上验证通过后,移植到 HxClaw河虾 Claw
---
## 一、架构总览
```
用户
┌──────▼──────────────────────────────────────┐
│ 主持者dialog-agenttype: main │
│ 人格 + 调度规则 + task + memory 工具 │
│ 唯一入口,用户只和它对话 │
└──────┬───────────────────────────────────────┘
│ task("weather", {city: "北京"})
│ task("earthquake", {region: "通州"})
│ task("memory", {action: "read", ...})
┌───────────────────────────────────────────┐
│ 发言人(领域子 Agenttype: sub │
│ weather / earthquake / memory / narrator │
│ 被调才说话,返回文本 + 可选缓存数据 │
│ 各自的 cache / skills / tools 互相隔离 │
└───────────────────────────────────────────┘
│ 读写
┌───────────────────────────────────────────┐
│ 记录者(记忆系统) │
│ 共享黑板:用户画像、偏好、异常记录 │
│ memory Agent 负责从对话中提取有价值信息 │
│ 所有 Agent 只读,仅 memory Agent 写入 │
└───────────────────────────────────────────┘
```
---
## 二、角色定义
### 2.1 主持者dialog-agenttype: 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-subtype: 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-subtype: sub预留
**职责**:响应地震信息查询
**Frontmatter**
```yaml
name: earthquake
type: sub
description: 地震信息查询
cache:
ttl: 300
keys: ["region", "time_range"]
tools:
- http-get
- skill
```
### 2.4 记录者memory-subtype: 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-subtype: 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. 返回显示文本给 Hostdialog 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.jsonMVP 阶段)
```
### 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 # 长期记忆数据库
```
---
## 六、调用流程示例
```
用户: "北京明天多少度?"
HOSTruntime.go:
1. 加载 dialog-agent.md → system prompt
2. 读 session.json → 恢复上下文
3. 调 LLMsession + 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
HOSTruntime.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 的存在

View File

@@ -11,6 +11,7 @@ description: MSN 天气 API 详细知识
|------|-----|
| 当前天气 | `https://assets.msn.cn/service/weather/current` |
| 每日预报 | `https://assets.msn.cn/service/weather/dailyforecast` |
| 逐小时预报 | `https://assets.msn.cn/service/weather/hourlyforecast` |
## 必须参数
@@ -21,7 +22,7 @@ description: MSN 天气 API 详细知识
## 可选参数
- `days`: 预报天数(最大 10
- `days`: 预报天数(最大 10,仅 dailyforecast 和 hourlyforecast 可用
## 必须请求头
@@ -49,6 +50,32 @@ value[].responses[].weather[].days[].daily.{
}
```
### hourlyforecast 接口
返回未来 10 天每天逐小时预报(今天从当前小时开始,后续每天 24 个点)。
```
value[].responses[].weather[].days[].hourly[].{
valid, // ISO 时间戳,如 "2026-05-11T07:00:00+08:00"
temp, // 温度 °C
feels, // 体感温度 °C
cap, // 天气描述(中文)
precip, // 降水概率 %
rh, // 相对湿度 %
baro, // 气压 hPa
windSpd, // 风速 km/h
windDir, // 风向(度)
windGust, // 阵风 km/h
uv, // 紫外线指数
cloudCover, // 云量 %
vis, // 能见度 km
dewPt, // 露点 °C
rainAmount, // 降雨量 mm
snowAmount, // 降雪量 mm
icon, symbol, // 天气图标代码
sky, wx // 天空状况代码
}
```
## 注意事项
- 数据源为微软 MSN 天气后台接口
- 国内访问速度快msn.cn 国内节点)