Files
YunShu/docs/changelog.md
titor c4a0e3ef53 feat: v2.3.0 流式输出 + 日志系统 + 会议室架构全面升级
- 流式输出: SSE 逐 token 接收, \\n\n\ 段落缓冲后 mdprint 彩色渲染
- 日志系统: charmbracelet/log v2 双写(stderr + log.yml), yunshu log 命令
- 会议室架构: dialog(main) + weather/profile/note(sub) 多 Agent 编排
- 泛型工具注册: NewTool[T] 反射推导 JSON Schema, 类型安全
- 安全加固: safeMemoryPath 三段校验(EvalSymlinks+Rel), maxToolCalls=2
- 性能优化: sync.Once 延迟加载, note 一步完成, obs/summary 合并
- Prompt 适配: 流式输出原则(先调工具不说话), 单 Agent 查询跳过 obs+summary
- 文档: AGENTS.md + architecture.md + changelog.md 全部同步至 v2.3.0
2026-05-16 17:21:29 +08:00

20 KiB
Raw Blame History

云枢·Agent 版本变更日志

坐看云卷云舒,静听花开花落

[2.3.0] - 2026-05-16

日志系统 + 性能优化 + 安全加固

日志系统

引入 charmbracelet/log v2 作为结构化日志库,取代零散的 log.Printf / fmt.Fprintln(os.Stderr, ...)

组件 说明
logger.go charmbracelet/log 全局实例,输出到 stderr
log.go log.yml 持久化YAML 序列追加)、双写 wrapper、yunshu log 命令

日志流向:

warnLog/errorLog/infoLog
  ├─ logToStderr ? → charmbracelet/log → stderr交互模式默认关闭/log on 开启)
  └─ 始终写入 → appendLog → log.ymlYAML 序列,位于 ~/.config/yunshu/log.yml

新增日志点:

[INFO] task(weather) 开始
[INFO] 子 Agent 开始  agent=weather
[INFO] LLM 调用完成  tokens=1420  duration=3.2s
[INFO] task(weather) 完成
[WARN] 解析缓存失败  agent=weather  err=...

yunshu log 子命令:

yunshu log              → 全量显示(时间倒序)
yunshu log --top N      → 只看最后 N 条
yunshu log --level warn → 过滤级别
yunshu log --clear      → 清空
yunshu log --watch      → 监听模式2s 轮询)

流式输出

LLM 响应改为 SSE 流式输出,按 \n\n 段落边界缓冲后经 mdprint 渲染到 stdout

组件 文件 说明
SSE 类型 llm.go sseChunk/sseDelta/sseToolCallDelta
CallLLMStream llm.go 请求加 stream: truebufio.Reader 逐行解析
段落缓冲 llm.go blockBuf + tryFlushBlocks:检测最后一个 \n\n,完成块过 mdprint残段续传
流结束刷残段 llm.go 末尾 mdprint.Print(blockBuf) 兜底
重建响应 llm.go tool_call 按 index 累积 + SSE 碎片合并

关键设计:

LLM SSE → blockBuf += content
          tryFlushBlocks:
            ├─ 有 \n\n → 之前的部分 mdprint.Print(complete)
            │            → 剩余部分留在 blockBuf
            └─ 无 \n\n → stay
流结束 → mdprint.Print(blockBuf)

Prompt 适配(agents/dialog-agent.md

  • 新增"流式输出原则":先调工具,不要先说话
  • 调度表 回应 → task(profile) 改为 静默调 task拿到结果后再回应

性能优化

优化 改动 效果
note-sub 一步完成 prompt 收紧read→write→立即返回 note 保存从 ~50s 降至 ~10s
dialog 合并 obs+summary 单 Agent 查询跳过观察和摘要;综合查询合并为一轮 节省 ~30s
maxToolCalls=2 RunSubAgent 超限兜底 防止死循环

代码加固

改动 文件 说明
LLM 延迟加载 llm.go init()sync.Once--help 不再读 config
路径安全检查 tool.go safeMemoryPath()Clean + EvalSymlinks + Rel 三段校验
静默错误 → warnLog 8 处 across runtime/tool/registry 所有 json.Unmarshal / yaml.Unmarshal 吞掉的错误改为结构化日志
对话记忆修剪 runtime.go LoadSession 只返回最近 40 条消息
Onboard 补齐 onboard.go ensureUserConfig() 创建 user.md/soul.md/notes/
热加载 main.go 交互模式每轮 ScanAgents(),新增 agent 即时生效

UX 改进

  • 交互模式默认不显示日志(/log on 开启)
  • --help 移除环境变量章节,增加 log 命令说明
  • 单次查询模式保留日志显示

[2.2.0] - 2026-05-16

存储重组session 目录 + .yml 统一后缀

目录结构调整

旧路径 新路径 原因
session.json session/session.json 对话会话文件归入 session 目录
context/dialog.yaml session/dialog.yml context 语义模糊,与 session 合并;.yml 后缀
config.yaml config.yml 统一后缀
log.yaml log.yml 统一后缀
context/ 已删除 文件移至 session/

所有 yaml 文件统一使用 .yml 后缀。配置文件、对话摘要、日志文件全部一致性调整。

代码改动

文件 改动
runtime.go sessionPath() 改为 session/session.json
config.go LoadConfig/SaveConfig 读写 config.yml
onboard.go 提示信息改为 config.yml
main.go 新增 migrateFilePaths() 处理所有旧路径迁移
tool.go 描述字符串更新 context/session/.yaml.yml
agents/dialog-agent.md 所有 context/dialog.yamlsession/dialog.yml
docs/ 存储树、写入策略、编码规范全部同步更新

迁移逻辑

migrateFilePaths() 在启动时自动处理:

  • config.yaml → 复制到 config.yml,删除旧文件
  • session.json → 复制到 session/session.json,删除旧文件
  • context/dialog.yaml → 复制到 session/dialog.yml,删除旧文件及空目录
  • log.yaml → 复制到 log.yml,删除旧文件

所有迁移仅在目标文件不存在时执行,支持幂等。


[2.1.0] - 2026-05-16

用户画像 + 备忘录系统

拆分 memory.json

旧的 memory.json 一锅烩,现在拆成按用途分文件

旧文件 新文件 格式 维护者
memory.json["personality"] config/soul.md Markdown 用户手动编辑
memory.json["dialog_context"] context/dialog.yaml YAML dialog 每轮写入
memory.json["agent_errors"] log.yaml YAML 系统追加
(不存在) config/user.md Markdown profile-sub 维护
(不存在) notes.md + notes/*.md Markdown note-sub 维护

迁移逻辑在 main.go:migrateMemoryJSON(),启动时自动检测并迁移,确认完成后再删除 memory.json

新增子 Agent

Agent 文件 用途
profile-sub agents/profile-sub.md 从对话中提取用户画像,增量合并到 config/user.md
note-sub agents/note-sub.md 笔记管理。默认 notes.md 列表,复杂内容可存为 notes/{name}.md

memory.read/write 工具改造

从旧的 flat JSON key-value 改为路径路由:

  • .md 文件 → 全文覆写(memory.write("config/user.md", markdown_str)
  • .yaml 文件 → 合并写入(memory.write("context/dialog.yaml", {topic, last_agent})
  • 目录 → 返回文件列表
  • 安全检查:拦截 .. 遍历和绝对路径
  • 目录自动创建

dialog-agent.md 更新

  • 新增调度规则:检测用户透露个人信息 → 调 task("profile", ...) 提取画像
  • 新增调度规则:检测"帮我记住" → 调 task("note", ...) 存备忘录
  • 对话摘要改为写入 context/dialog.yaml,而非旧 memory.json["dialog_context"]
  • 用户画像改为读取 config/user.md
  • 新增观察规则:每轮回复后向 ## AI观察到 段写入语气/情绪/性格观察

heading-aware merge

memory.write.md 文件由全文覆写改为按 ## 标题合并:

  • memory.write("config/user.md", "## 画像\n...") 只替换 ## 画像 段,## AI观察到 段不受影响
  • 同一 writer 可多次写入同一标题,每次覆盖该段内容
  • 不同 writer 写不同标题,互不干扰
  • 底层函数 mdMerge()## 拆分 → 标题匹配 → 重组

profile-sub.md 更新

  • 改为写 ## 画像 段(不再写整篇 user.md
  • 文档规范化:给出 ## 画像 格式示例
  • 强调不破坏其他段(## AI观察到 等)

发布会:会议室架构核心引擎就绪

架构规划:会议室模式(阶段一全部完成,超计划交付)

核心引擎

组件 文件 说明
Agent 注册中心 registry.go ScanAgents()type(main/sub) 分类,用户目录覆盖
子 Agent 隔离运行 runtime.go: RunSubAgent 隔离 LLM 循环,返回 ---RESULT---/---TEXT---
主 Agent 循环 runtime.go: RunAgent session 持久化 + mdprint 渲染
缓存系统 runtime.go: cache 辅助 SHA256[:6] 拼 key惰性过期
工具目录生成 catalog.go GenerateToolsYAMLBuildSubAgentPrompt

新增工具

工具 注册方式 说明
task NewTool[TaskInput] 调度子 Agent + 缓存管理
memory.read NewTool[MemoryReadInput] ~/.config/yunshu/memory.json
memory.write NewTool[MemoryWriteInput] 写长期记忆value 支持任意 JSON 类型

实际子 Agent

Agent 文件 状态
dialog-agent.md agents/dialog-agent.md 主持者type:main含多步骤编排指令
weather-sub.md agents/weather-sub.md 天气子 Agenttype:subMarkdown 排版 + 生活建议

计划外新增

泛型 + 反射工具注册(toolschema.go

受 Charmbracelet/Fantasy 启发,引入 NewTool[T any]() 泛型构造函数:

  • 输入结构体 json/description/enum tags → 自动反射生成 JSON Schema
  • 消除 ~120 行手写 Schema 模板代码(旧的 ToolParameter/ToolProperty 类型已删除)
  • handler 内参数为类型安全的结构体字段,无需 args["x"].(string) 类型断言
  • 支持嵌套结构体、slice、map、基础类型、interface{} 类型

多步骤编排runtime 改造)

移除 capturedOutput 覆写机制,子 Agent 结果作为普通工具响应留在对话上下文中:

  • 主 Agent 可以连续多次调 task()weather → train → hotel
  • 每次返回后 LLM 继续推理,决定下一步
  • 信息收集完毕再综合回答,不再被 capturedOutput 截断
  • 单步骤查询如纯天气行为不变LLM 按 prompt 指令直接输出子 Agent 结果

其他变更

  • http-get.headers 从 JSON 字符串改为 map[string]stringLLM 直接传对象
  • memory.write.valuestring 改为 interface{},直接存储任意 JSON 值
  • types.go:删除旧的 ToolParameter/ToolProperty 结构体,新增 Schema(map[string]any) 类型
  • catalog.gobuildToolList 适配新版 Schema
  • agents/dialog-agent.md:加入多步骤编排指令 + 数据传递说明

文档更新

  • docs/architecture.md:更新工具列表、核心流程、当前状态
  • docs/AGENTS.md:工具注册规范更新为 NewTool[T] 方式
  • docs/会议室架构计划书.md:添加实现状态标记、多步骤编排章节、泛型注册章节

架构规划:会议室模式

完成从单 Agent 到"会议室架构"的完整设计,核心变更:

新增角色体系

  • type: main — 主持者(对话 Agent唯一用户入口
  • type: sub — 发言人(领域子 Agenttask 调才说话

新增工具(待实现):

  • 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.mdagents/weather-agent.md

技术细节

  • Frontmatter 新增 type 字段main/sub
  • Frontmatter 新增 cache 字段({ttl: int, keys: [string]}
  • 用户配置目录 ~/.config/yunshu/ 下可选覆盖 agents/
  • 详见 docs/会议室架构计划书.md

[1.1.0] - 2026-05-09

发布摘要

第二版发布。核心变化Markdown 渲染器从"一行流"重构为 AST 架构,新增终端视觉系统(标题符号 + Monet 配色Go 版本升级至 1.25,项目结构从 src/ 扁平目录重组为根目录 + pkg/ 子包架构。


新增pkg/mdprint — Markdown → ANSI 渲染引擎

从头编写的 AST 架构渲染引擎,替代原来的"一行流"字符串匹配逻辑:

块级解析parse.go):有限状态机逐行扫描,识别 7 种块级元素

类型 语法 解析策略
Heading # ~ ###### 前缀匹配,记录级别
CodeBlock ``` fence 状态切换,支持语言标识
Blockquote > 前缀 前缀剥离,递归解析
List - / * / 1. 前缀匹配,自动编号检测
Table ` ` 分隔
ThematicBreak --- 独占一行 精确匹配
Paragraph 默认兜底 连续非空文本块合并

行内解析inline.go):递归下降扫描,支持 4 种行内元素嵌套

类型 语法 特性
Bold **text** 可嵌套 Italic / Code / Link
Italic *text* 可嵌套 Bold / Code / Link
Code `text` 原始文本(内部不解析)
Link [text](url) text 可嵌套 Bold / Italic

ANSI 渲染render.gotype switch 按节点类型分发,标题按级别分配颜色和符号

测试覆盖19 个单元测试,覆盖所有块级/行内类型的正常、边界和嵌套场景


新增:标题视觉系统

用符号 / 替代 Markdown 原生 # 前缀,视觉效果更接近"标题"而非"标记"

级别 符号 字体 色彩Monet 睡莲) 色值
H1 加粗 雾蓝灰 #6B8E9B
H2 加粗 鼠尾草绿 #89A894
H3 加粗 薄荷青 #A6C0B5
H4 加粗 淡紫粉 #C3B1BD
H5 加粗 暖灰绿 #7B8E8A
H6 加粗(Dim) 浅灰 继承

排版规则所有标题前插空行H1 前后各插空行,--- 横线前后空行,响应与输入之间空行,输出末尾空行。


新增pkg/style — 终端颜色样式库

在原有 8 色 ANSI 基础上新增 24-bit 真彩色支持:

  • Fg(color) / Bg(color):保留原有 8 色 API
  • FgHex("#RRGGBB") / BgHex("#RRGGBB"):新增真彩色,输出 \033[38;2;R;G;Bm 格式
  • 所有 .Render(text) 使用统一占位板 .codes []string,颜色和样式可组合
  • NO_COLOR / TERM=dumb 环境变量自动禁用颜色

新增pkg/termui — 终端输入组件

提供三个交互式输入函数,统一使用 bufio.NewReader(os.Stdin) + ensureLineMode() 解决 Windows 控制台输入问题:

函数 用途 特性
ReadLine() 基础行输入 去除 \r\n
TextInput() 文本输入 支持默认值、必填校验、自定义验证器
PasswordInput() 密码输入 输入后隐藏回显,用 * 遮盖
Confirm() 确认提示 支持 Y/n / y/N 默认值

修复

  1. 行内解析器未闭合分隔符死循环:扫描 * / ` 分隔符时未处理文件末尾无闭合标记的情况,改为找到匹配闭合或到达末尾时终止
  2. 代码 fence 不识语言标识```go```json 等带语言的 fence 被当作文本行,改为行首 ``` 后允许非空白后缀
  3. Windows 控制台输入模式冲突bufio.Scanner 在 Windows 控制台因 ENABLE_WINDOW_INPUT 标志导致 Scan() 永久阻塞。改为每轮输入前调用 GetConsoleMode + SetConsoleMode 确保模式为 ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT,并使用 bufio.NewReader.ReadString('\n') 替代 Scanner
  4. 尝试自实现 Tab 补全未成功后放弃raw mode + ReadConsoleInputW 方案遇到光标位置计算不一致问题,最终回退到简单 ReadLine()。后续如需补全功能引入第三方库

变更

  • 项目结构重组src/ 目录删除,全部 .go 文件移至根目录;新增 pkg/ 子包,按职责分离为 mdprint/style/termui/
  • 模块名修改yunshuhub.gaomia.site/titor/YunShu
  • Go 版本升级1.211.25.0
  • Completer 类型和 ReadLineWithCompletion 函数移除completer.go 清空,相关测试删除
  • .gitignore 修复yunshu.exeyunshu.exe*(避免调试版本遗漏)

技术栈

  • 语言Go 1.25.0
  • 依赖:仅 gopkg.in/yaml.v3
  • 默认 LLM:豆包(火山引擎)doubao-seed-2-0-pro-260215
  • 数据源MSN 天气非公开 APIassets.msn.cn
  • 运行平台Windows 10+(基于 kernel32 控制台 APIANSI VT 处理)
  • 输入编码UTF-8通过 SetConsoleOutputCP(65001) 设置控制台代码页)

[1.0.0] - 2026-05-08

发布说明

第一个稳定发布版本。云枢·Agent 作为可独立运行的 Agent 系统,支持通过 LLM + 工具注册表 + 外挂 Agent 定义实现自然语言驱动的天气查询。

功能

  • 三层分离架构Agent Skill行为↔ 普通 Skill知识↔ Tool确定性执行
  • 外挂 Agent 定义:.md 文件即 AgentYAML frontmatter + Markdown body
  • 4 个内置工具:http-getskillread-filegeocode
  • Session 会话管理:~/.config/yunshu/session.json 持久化对话历史
  • 交互模式 + 单次查询双模式运行
  • onboard 交互式初始化向导
  • 双路径搜索:项目目录优先,~/.config/yunshu/ 后备
  • 旧配置自动迁移:~/.config/weather-cli/~/.config/yunshu/
  • LLM 配置:支持配置文件 + 环境变量双重配置,兼容 OpenAI Chat Completion API

技术栈

  • 语言Go 1.21
  • 依赖:仅 gopkg.in/yaml.v3
  • 默认 LLM豆包火山引擎doubao-seed-2-0-pro-260215
  • 数据源MSN 天气非公开 APIassets.msn.cn

[1.0.0-rc.1] - 2026-05-07

重大变更

  • 项目更名weather-cia → 云枢·Agent(英文名 YunShu / yunshu
  • 配置目录迁移~/.config/weather-cli/~/.config/yunshu/(自动迁移)
  • 二进制名称改为 yunshu

[0.3.0] - 2026-05-07

新增

  • geocode 工具:通过 wttr.in 查询城市坐标,支持中文和英文城市名
  • skills/geocoding/SKILL.md:地理编码验证规则(同名城市检测、国家核对)
  • 架构分离agent skill 只放行为,普通 skill 只放知识tool 负责确定性执行

变更

  • agents/weather-agent.md 精简为纯行为定义(去掉所有 MSN API 内联细节,改为按需加载 skill
  • 城市定位方式:从静态 cities.json 查表 → 调用 geocode 工具实时查询
  • agents/weather-agent.md tools 新增 geocode
  • session 文件从项目目录移至 ~/.config/weather-cli/session.json

[0.2.0] - 2026-05-07

新增

  • onboard 子命令:交互式初始化向导,引导用户配置 LLM 连接信息
  • 全局配置文件 ~/.config/weather-cli/config.yaml,存储 LLM host/model/key
  • 双路径搜索机制:项目目录优先,~/.config/weather-cli/ 后备
  • 首次运行检测:未配置时提示用户运行 weather-cia onboard

变更

  • 项目重命名为 weather-cia
  • 配置加载改为:配置文件 → 环境变量(环境变量优先级更高)
  • Agent/skill 搜索路径扩展:项目目录 → 全局配置目录
  • onboard 自动复制默认 agents/skills/data 到全局配置目录

[0.1.0] - 2026-05-07

新增

  • 项目初始化,基于 Go 实现的轻量级 agent 框架
  • 核心架构:.md 文件定义 agent 行为,代码只负责加载和执行
  • 工具系统声明式注册http-get, skill, read-file
  • Session 会话管理session.json 记录对话历史,支持上下文追问
  • 天气情报官 agentweather-agent.md通过 MSN 天气 API 查询实时天气和预报
  • MSN 天气 API Skillmsn-weather-api/SKILL.mdAPI 知识按需加载
  • 内置 42 个中国城市经纬度数据库data/cities.json
  • 支持单次查询和交互模式两种运行方式
  • 默认集成豆包火山引擎LLM通过环境变量可切换

技术细节

  • 语言Go 1.21
  • 依赖:仅 gopkg.in/yaml.v3用于解析 frontmatter
  • API 兼容 OpenAI Chat Completion 格式
  • 环境变量:OPENAI_API_KEY(必填)、LLM_ENDPOINTLLM_MODEL