## 核心功能 - 双记忆系统合并:picoclaw MEMORY.md + hxclaw 会话摘要 - 独立上下文系统:不依赖 picoclaw session - 向量检索:硅基流动 BGE-M3 API - 三重检测:关键词/向量相似度/命令 ## 数据库 - libSQL (TursoDB) 存储 - sessions + chats 表设计 - 向量存储使用 binary 编码 ## 查询场景 - RecallHistory: 查询所有会话摘要 - RecallTopic: 按话题向量检索 - RecallSession: 指定会话详情 - RecallWithinSession: 会话内检索 ## 导出 - MongoDB 风格:~/.config/hxclaw/export-data.json - chats 嵌套在 sessions 下 - 增量导出,同 session 累加 ## UI 优化 - 合并状态显示(耗时 · 状态 · 消息数) - 颜色设计:金色图标 + 暗绿色/暗红色状态 ## 配置项 - memory.recall: keywords, auto_recall, similarity_threshold - memory.vector: max_search_results - memory.auto_export
11 KiB
hxclaw 更新日志
版本记录
v0.3.0 (2026-04-27)
-
双记忆系统合并
- 读取 picoclaw 的 MEMORY.md 作为长期记忆
- 合并到 hxclaw 的会话摘要上下文
- AI 同时看到长期记忆和会话摘要
-
独立上下文系统
- 创建 GetContextPrompt() 返回会话摘要
- 注入到 ProcessDirect() 调用前
- 不再依赖 picoclaw session 管理
- 修复 recall 结果污染 session summary 问题
-
数据库层完善
- 集成 libSQL (TursoDB)
- 创建 sessions 和 chats 表
- 实现 CRUD 操作
- 数据库保存在
~/.config/hxclaw/hxclaw.db - 向量存储使用 binary 编码(float32)
-
向量检索功能
- 硅基流动 BGE-M3 API 集成
- 向量生成和存储
- Cosine Similarity 计算
- SearchSimilar() 函数实现
- 4 个查询场景(RecallHistory, RecallTopic, RecallSession, RecallWithinSession)
-
三重检测机制
- 关键词匹配(之前、聊过、记得等)
- 向量相似度自动检测(auto_recall + 阈值)
- /recall 命令强制触发
- 配置项:keywords, auto_recall, similarity_threshold, max_results
-
MongoDB 风格导出
- 固定路径:
~/.config/hxclaw/export-data.json - chats 嵌套在 sessions 下
- 增量导出,同 session 累加
- 版本控制(version 字段)
- 固定路径:
-
UI 优化
- 合并状态显示到单行(耗时 · 状态 · 消息数)
- 颜色设计:金色图标 + 灰色文字
- 暗绿色"会话已保存" / 暗红色"会话保存异常"
-
配置项更新
- memory.recall 配置
- memory.vector.max_search_results
- memory.auto_export(替换 export_on_exit)
- 默认 max_search_results = 10
v0.2.1
- 修复 TTS JSON 请求格式,兼容 Windows daemon
- 发送格式改为
{"text": "内容"}
v0.2.0
- 新增 TTS 语音朗读功能
- 集成 mimo-tts client 功能,通过 TCP 连接本地 daemon
- 支持配置文件开关(tts.enabled)
- 支持命令行切换(/tts on/off/status)
- 支持临时 TTS 前缀(
T 消息临时开启) - 动态提示符显示 TTS 状态(👀 🔊)
- 静默失败处理(网络异常时警告日志)
v0.1.0
- 创建 hxclaw 项目
- 实现流式输出功能
- Markdown 渲染(glamour,自动代码高亮)
- 项目配置化(project.config.yml)
待实现功能
v0.2.0 (当前)
- TTS 语音朗读功能
- 集成 mimo-tts client (TCP 连接)
- 配置文件开关 (tts.enabled)
- 命令行切换 (/tts on/off/status)
- 临时 TTS 前缀 (T 消息)
- 动态提示符显示状态
- 静默失败处理
v0.3.0 (当前)
- 双记忆系统合并(picoclaw MEMORY.md + hxclaw 会话摘要)
- 数据库层集成(libSQL)
- 独立上下文系统(不再依赖 picoclaw session)
- 会话摘要注入
- UI 优化(合并显示、颜色设计)
- 向量检索(硅基流动 API)
- 4 个查询场景(RecallHistory, RecallTopic...)
- 三重检测机制
- MongoDB 风格导出
待实现功能
v0.4.0 (计划)
- 命令行参数支持(--theme, --tts 等)
- 多语言支持
- /new 命令开始新会话
- /memory list|show 命令
目前进度
- 创建项目目录结构
- 编写讨论记录(taolun.md)
- 编写更新日志(changelog.md)
- 编写 AI 行为指南(agents.md)
- 创建 go.mod
- 实现 main.go 入口
- 实现流式输出核心逻辑
- 编译成功,生成 hxclaw 二进制
- 添加 spinner 加载动画组件
- 实现 Markdown 渲染(glamour)
- 实现项目配置化(project.config.yml)
- 实现 TTS 语音朗读功能
- 集成 libSQL 数据库
- 实现独立上下文系统
- UI 状态合并显示
认知纠正(踩坑记录)
Go replace 机制不需要发布到 registry
问题:最初担心需要像 npm 那样发布到 registry 才能被其他项目引用
纠正:Go 的 replace 机制可以直接指向:
- 本地路径(如
../picoclaw) - GitHub 仓库 + tag(如
github.com/sipeed/picoclaw v0.2.4)
知识点:Go 模块不需要发布到任何 registry,GitHub 就是事实上的 registry
hxclaw 不需要实现全部 picoclaw 功能
问题:最初担心需要自己实现 onboard、tools、mcp 等全部功能
纠正:hxclaw 是 CLI 增强层,只替换交互逻辑。picoclaw 的核心功能(agent loop、tools、mcp、skills)通过导入其 pkg 即可复用
知识点:采用组合优于继承的设计,需要什么功能就导入对应的包
流式输出需要判断 Provider 是否支持
问题:不是所有 Provider 都支持流式输出
纠正:需要使用类型断言判断 Provider 是否实现 providers.StreamingProvider 接口:
if sp, ok := provider.(providers.StreamingProvider); ok {
// 使用 ChatStream
} else {
// 使用普通 Chat
}
知识点:picoclaw 的 Provider 设计使用了接口分离原则,流式是可选能力
终端渲染使用 charmbracelet 库
问题:如何实现 Markdown 终端渲染
纠正:使用 charmbracelet 家族:
- lipgloss:样式定义
- glow:代码高亮
知识点:charmbracelet 是 Go 终端UI 的事实标准,API 设计优雅
独立二进制部署方式
问题:hxclaw 和 picoclaw 的关系
纠正:hxclaw 作为独立二进制,用户可以同时保留两个命令:
picoclaw agent使用原版hxclaw使用增强版
知识点:通过 go.mod replace 实现依赖绑定,用户无需安装 picoclaw 源码
AgentRegistry 没有 BuildMessages 方法
问题:最初尝试调用 agentLoop.GetRegistry().BuildMessages() 构建消息
纠正:BuildMessages 属于 ContextBuilder,不是 AgentRegistry:
// 正确方式
agentInstance.ContextBuilder.BuildMessages(history, summary, input, media, channel, chatID, senderID, senderDisplayName)
知识点:picoclaw 代码结构中,ContextBuilder 负责消息构建,AgentRegistry 负责 agent 管理
ToolDefinitions 获取方式
问题:如何获取可用的工具定义列表
纠正:通过 ToolRegistry 的 ToProviderDefs 方法:
toolDefs := agentInstance.Tools.ToProviderDefs()
知识点:ToolRegistry 维护工具注册,ToProviderDefs 转换为 provider 可用的格式
流式输出实时刷新
问题:流式输出时字符不是实时显示,要等很久才一次性出现
纠正:在 onChunk 回调中添加 os.Stdout.Sync() 强制刷新 stdout:
func(token string) {
fmt.Print(token)
os.Stdout.Sync() // 强制刷新
}
知识点:Go 的 fmt.Print 使用缓冲输出,需要手动刷新才能实时显示
Session 历史消息获取
问题:如何获取会话历史用于流式调用
纠正:通过 SessionStore 接口:
history := agentInstance.Sessions.GetHistory(sessionKey)
summary := agentInstance.Sessions.GetSummary(sessionKey)
知识点:AgentInstance.Sessions 实现了 SessionStore 接口,支持 GetHistory 和 GetSummary 方法
流式调用后的消息保存
问题:流式调用绕过了 agent loop,消息没有保存到 session
纠正:流式调用后手动保存消息:
agentInstance.Sessions.AddMessage(sessionKey, "user", input)
agentInstance.Sessions.AddMessage(sessionKey, "assistant", result)
知识点:SessionStore 接口提供 AddMessage 方法,支持 "user" 和 "assistant" 角色
onChunk 回调接收累积文本导致重复输出
问题:picoclaw 的 StreamingProvider 接口定义:
onChunk func(accumulated string)
注释说明:"onChunk receives the accumulated text so far (not individual deltas)"。每次回调时参数是累积的完整文本(如 "你好" → "你好!再次" → "你好!再次见到"),而不是增量。
纠正:使用 printedLen 跟踪已打印位置,只打印新增部分:
var printedLen int
func(accumulated string) {
if len(accumulated) > printedLen {
fmt.Print(accumulated[printedLen:])
printedLen = len(accumulated)
}
}
知识点:picoclaw 故意设计为累积文本,这样可以在任意时刻获取完整内容用于调试
尝试 uilive 库但只显示最后一行
问题:为了实现同行流动效果,尝试使用 github.com/gosuri/uilive 库
现象:该库会覆盖每一行,只显示最后一行内容
纠正:移除 uilive,直接使用 fmt.Print + os.Stdout.Sync(),让终端自然处理换行
知识点:uilive 适用于进度条等场景,不适合长文本流式输出
流式输出期望同行流动但实际换行显示
问题:用户期望像 ollama 那样在同行逐字符流动
最终方案:
fmt.Print(accumulated[printedLen:])
os.Stdout.Sync()
效果:
- 字符串自然累积增长
- 终端自动处理换行(满一行自动 wrap)
- 保留所有历史输出
- 每次刷新缓冲区确保立即显示
知识点:最简单的方案就是最有效的方案,不需要额外库
spinner 组件的 model 更新必须使用返回值
问题:spinner 动画不动
现象:调用 spinner.Update(msg) 后动画不更新
纠正:spinner model 是值类型,需要使用返回值更新:
// 错误写法
func (s *Spinner) tick() {
msg := s.spinner.Tick()
if msg, ok := msg.(spinner.TickMsg); ok {
s.spinner.Update(msg) // 动画不会动!
}
}
// 正确写法
func (s *Spinner) tick() {
msg := s.spinner.Tick()
if msg, ok := msg.(spinner.TickMsg); ok {
s.spinner, _ = s.spinner.Update(msg) // 必须使用返回值更新
}
}
知识点:bubbletea v2 的组件遵循 TEA 架构模式,Update 方法返回更新后的 model,需要显式使用返回值。
spinner 和流式输出在同一行的冲突问题
问题:spinner 使用 \r 回到行首刷新,流式输出也在同一行打印,导致内容混在一起
现象:
⠋ 回答中... 好
⠋ 回答中... 注于
纠正:在第一个 token 时停止 spinner,让 spinner 输出 "思考完成." 并换行,然后再开始流式打印:
if firstToken && len(accumulated) > 0 {
spinner.Stop() // 停止 spinner,会打印 "思考完成."
firstToken = false
}
知识点:spinner 和流式输出需要分时工作,不能同时占用同一行。
spinner 动画位置和换行策略
问题:用户期望动画在前,文字在后,且需要正确的换行
效果:
思考中... ⠋ -> 用户期望 ⠋ 思考中...
思考完成. -> 用户期望 ⠋ 思考完成.
纠正:
- 动画在前,文字在后:
fmt.Printf("\r%s %s", s.spinner.View(), s.text) - 换行:"思考完成.\n" + 流式输出后 "fmt.Println()\n"
知识点:终端输出需要精确控制位置和换行,否则会导致格式错乱。