ottagent: Otto + Hermes Agent = 一个提供陪伴感的物理 AI 助手
2026 湾大机器人作业个人项目 日期: 2026-06-20 项目仓库: https://github.com/kkLullaby/final_pro_xiaozhi_robot 工作流方法论开源: https://github.com/kkLullaby/agent-loop-evolution-spec (哪天有时间了写写这个)
摘要
本项目把课程发的 Otto 形态的 ESP32-S3 小智机器人,改造成开源 agent 框架 Hermes Agent 的"第一具物理化身"——它能说话、能转头摆动作、能感知环境,同时也能被飞书 IM 双向唤起。整个项目的创新点在于软件拓扑: 通过自写的 4 个桥接组件 (openai-shim / lark-mcp-server / lark-event-listener / ESP32 端 MCP tools) 把原本封闭的"语音玩具"解耦成 “hermes 大脑 + 多个channel” 的结构,任何新的 channel (邮件、Slack、网页) 都可以在hermes agent里面接入,无需改动机器人固件。
项目历时 5~7 个整天 (约 30-35 工时),由本人 + LLM 协作完成,产出 ~2500 行 代码、9 份架构决策记录 (ADR)、39 份跨角色协作 handoff、51 条工程踩坑笔记。最终落地 4 条端到端 demo,全部在物理机器人上真实验证。方法学副产物是一套 “planner / executor / auditor 三角色 agent 协作工作流”,已开源到 GitHub 作为独立 spec。
本项目联系到的关键概念: 具身智能 (embodied agent), 多 channel 拓扑, MCP (Model Context Protocol), ESP32 固件, LLM-assist 开发, 多角色 agent 工作流
1. 为什么做这个
1.1 课程任务的 “明面要求” 与agent时代浪潮的奇妙联想
课程要求做 Otto 小智机器人创新 demo。但在一开始我意识到: 我的硬件能力和开发时间都不算很充裕,想要做到足够好的产品,只能在软件上面下功夫。而课程套件里面的硬件已经够好 —— ESP32-S3 跑 Opus 编码、内置麦克风/喇叭、有舵机、有240×240 IPS 屏。上游查阅相关资料之后了解到的 78/xiaozhi-esp32 和 xinnan-tech/xiaozhi-esp32-server 已经把“语音对话玩具”做到很高的水准了。
所以,似乎我很难在硬件和简单的,非llm接入的环节里面做出足够大的突破了。怎么办呢?
如果只是按教程烧固件 + 起 docker + 选一个 LLM provider,项目就是一个 ‘你好,今天天气怎么样’ 的复读机,跟市面上买的 AI 桌宠没区别 —— 这种作业不值得做。那还有什么办法解决呢?
直到前段时间,学校开了一个介绍agent使用技巧的讲座,给了我灵感:为什么不能把开源的ai agent(hermes或者openclaw)接入xiaozhi机器人呢?我一开始组装好了机器人之后最大的感受就是实在是太不智能了,内置的ai只是简单的系统提示词加上云端api调用。云端大模型能力低下的原因:写死了是开源仓库里面的qwen是一个方面,另一个方面就是ai缺少一套实用成熟的框架以及对应的工具调用函数,最致命的问题是xiaozhi社区的开源生态范围太小了,不像openclaw和hermes一样有几十万的github stars的巨大影响力,上万名社区贡献者给开源agent项目搭建生态。于是,我萌发了将xiaozhi和hermes之类的agent工具结合起来的想法。
1.2 我看到的真问题:封闭后端
把上游服务跑起来后,我发现一个结构性问题: xinnan-tech 的 server 是"硬中心"—— LLM provider 写死在 .config.yaml,要换模型就要改配置重启 docker;要加新工具 (比如"机器人帮我发个飞书消息") 就得改 server 源码;要让机器人接入到"我自己的 agent 框架" 里,没有任何挂载点。
这不是 xinnan-tech 的问题,而是整个 “AI 玩具” 品类的设计取向: 把大脑焊死在 server 上。结果是用户被锁定 (字节豆包桌宠、Rabbit R1、小度智能屏全是同一个套路),开发者也无法把这台硬件拿来做有意思的事。
1.3 idea: 把 “大脑” 和 “化身” 解耦
那段时间我正在用 Hermes Agent (Nous Research 开源的多 channel agent 框架,本身已经支持 Discord/Slack/Email adapter)。一个想法冒出来:
如果把小智机器人当成 Hermes 的一个新 channel —— 像 Discord 那样 —— 而不是 一个独立的"产品",会怎样?
这不是"换个 LLM"的小改良,而是两个优秀开源项目的有机结合:
- 原: ESP32 → server → LLM (server 是中心,LLM 可替换但只是一项配置)
- 新: ESP32 → server (退化为音频/IO 网关) → hermes (中心) → 任意 tool / 任意其他 channel
这样做的副作用很大:
- 后端无关:hermes 想换 LLM 就换,小智固件零修改
- 工具可热加:
hermes mcp add一行命令给机器人加新能力,不重烧固件 - 多 channel 共享大脑:同一个 hermes 实例可以同时接 Discord、飞书、物理小智
- 可上游贡献:这个 channel adapter 可以作为 PR 提交给 Hermes 社区, 成为它"第一具物理化身"
1.4 为什么是 Hermes 而不是 LangChain / AutoGen
- LangChain: 是 SDK 不是 agent runtime,要自己写 channel/state/auth/CLI,工作量是 Hermes 的 5×
- AutoGen (微软): 多 agent 对话框架,适合"多 LLM 互辩",但单 agent + 多 channel 反而别扭
- Hermes: 自带 channel adapter ABC、自带 TUI、自带 MCP server 接入、单二进制
hermes -z PROMPT就能one-shot 跑 —— 它的工程姿态最接近"嵌入"
后面会看到,我甚至直接把 hermes 当 subprocess 来 spawn (hermes -z one-shot模式),这是只有 Hermes 这种CLI-friendly agent 才支持的玩法 (ADR-0006)。
1.5 idea 的演化 (从最初到最终)
不是一开始就想清楚的。最初的设计是"按 Hermes spec 写一个完整的 plugin"
(plugins/platforms/xiaozhi/),工作量估 5-6 天。中途几次架构转向:
| 阶段 | 时间 | 关键转向 | 触发原因 |
|---|---|---|---|
| 起点 | Day 1 | 写完整 Hermes plugin | 抄 Discord adapter |
| 转向 1 | Day 2 | M2 升级为 transcript 入口 (ADR-0005) | 发现 transcript 没有 MCP-client callback,走 OpenAI-compat 反而最短 |
| 转向 2 | Day 3 | shim 直接 spawn hermes -z 替代 LLM (ADR-0006) |
voice 调用 tool 才有"真智能",一拍即合 |
| 转向 3 | Day 4 | hybrid router (ADR-0007) | 上一步导致舵机不动了 (hermes 截了 tools) |
| 转向 4 | Day 4 | lark channel outbound (ADR-0008) | “channel 论点"需要不止一条腿 |
| 转向 5 | Day 5 | lark channel inbound 长连接 (ADR-0009) | 飞书发消息没人接,channel 不闭环 |
这 5 次转向被全程记录。ADR 史就是 idea 演化史。
2. 方法论:三角色 agent 协作工作流
这一节是本项目区别于"普通课程作业"的核心方法学,也是为什么我使用agent的效率和工程的质量比其他人好的原因。
2.1 为什么不直接拿 Claude Code 单窗口完成
项目预期会写 ~2500 行代码 + 跨 4-5 个技术栈 (C++/Python/Docker/Hermes/lark SDK)。我的初始评估是: 一个 LLM session 撑不到 1/3 ——
- 上下文窗口会爆 (实际 5 天里至少 compact 了 20+ 次)
- 单角色 LLM 既写代码又自审,会产生"自洽幻觉”(写完自己说"OK"但其实跑不通)
- 大架构转向无法留痕,3 天后回头看不知道当时为什么这么决定
需要一个工程化的工作流,把"写代码"和"决定写什么代码"和"验证代码" 分到不同的 session 里,让每一步留下文件痕迹。
更加重要的原因是:Claude Opus4.8的价格过于昂贵 ,但是其编码效果由于边际递减效益实际上和便宜一点的gpt5.5差别不算太大。(当然在使用gpt5.5(基于codex)的时候,我也遇到了诸如sandbox block了很多重要的命令的问题,在此略过)
2.2 三角色定义
基于本人在其他项目里总结出的 multi-window agent 工作流 (方法论已开源到 GitHub: https://github.com/kkLullaby/agent-loop-evolution-spec),本项目采纳:
- planner: 设计 + 写 handoff + 起草 ADR;不动源码
- executor: 按 handoff 实施 + 写测试;不做架构决策
- auditor: 按 rubric 黑盒验证;不读 planner 的设计动机
三角色跑在 3 个独立的 Claude Code 窗口 里,共享文件系统但不共享 session context。这条强约束保证了:
- planner 想偏了,executor 会卡住报错 (因为指令不能 compile)
- executor 写得跑得通但偏离设计,auditor 会按 rubric 判 fail
- auditor 不知道 planner 的"原意",只能按客观条件判,杜绝放水(本项目里面没有像我之前做的项目里面一样多的评估指标,所以auditor触发得不算太多)
2.3 协作单元: handoff 文件
每次跨角色流转,都写一个 docs/handoffs/active/<from>-to-<to>-NNN.md,
里面有:
- frontmatter (id / from / to / status / parent)
- Objective (一句话目标)
- Constraints (硬约束)
- Acceptance Criteria (可验证条件清单)
- Context Pointers (相关 ADR/历史 handoff/源码引用)
- 后续 executor 接收时追加 “What I’ll do” + 完成时追加 “What I Did”
- 后续 auditor 追加 “Verdict”
文件 append-only,改方向只能开新 handoff 并标 supersedes: <old-id>。
完成后从 active/ 移到 archive/。每一份 handoff 都是项目演化的考古层。
2.4 决策史: ADR
每次架构转向都写一份 ADR (Architecture Decision Record),编号永不复用, 状态 active → superseded-by-NNNN,旧 ADR 不删只标 superseded。本项目最终 产出 9 份 ADR (见附录 B)。ADR 史就是项目演化树,见 §4.6 图。
2.5 真实案例: H033 是怎么跑出来的
H033 (“HermesAgentBackend: voice → hermes-z → tool call → 喇叭真说”) 是 项目最大的高光。它的完整生命周期是:

这一个 handoff 的全生命周期里有 2 个 Claude 角色 + 1 个真人 (我) 参与, 全程留痕。后面要重做 / 演示 / 写报告 / 写 PR description
2.6 工作流的"成本"和"边界"
成本:
- 写 39 份 handoff 文件 + 9 份 ADR 大约花了全部工时的 15% (5-6 小时); 但这些时间不是浪费 —— 否则我会花 2 倍时间 debug 错的方向
- 每次切角色都要 spawn 新 Claude Code 窗口,有冷启动成本
- 为了harness 好我的agent,保证输出的质量,烧了大量的token,远超一般小项目的单个窗口花费。
边界:
- 单人项目里 auditor 用得少,auditor 真正值钱是在项目的评价指标多的时候(如日志分析等)
- 三角色对 < 1 人天的小任务过重,需要 light scope (项目
CLAUDE.md里专门写了 light scope 豁免) - LLM 协作能力随 task complexity 急剧下降,架构决策必须人做,这也是我的核心价值所在。这是本项目 35 小时里"人主导"那 20%-30% 时间在做的事
2.7 跟其他多角色 AI 工作流的区别
| 框架 | 协作介质 | 状态保存 | 适合场景 |
|---|---|---|---|
| ChatDev / MetaGPT | LLM 群聊 | 内存 (session 结束就丢) | 一次性 demo |
| AutoGen | LLM 对话 + 共享 state | 内存 + 可选持久化 | 短期实验 |
| Cursor / Aider | 单 agent + 人工 review | 代码本身 + git | 日常 coding |
| 本工作流 | 文件 handoff + ADR | 磁盘,git tracked | 跨周/月项目 |
核心差异: 别人是 “群聊式 AI 协作”,本工作流是 “软件工程式 AI 协作” —— 所有沟通沉淀到文件,git 可追溯,角色边界靠文件 ownership 强制隔离。
3. 系统设计
3.1 总体架构

关键设计原则:
- hermes 是唯一的中心节点;所有 channel 平等
- ESP32 ↔ xinnan-tech ↔ shim 这一段沿用上游既有协议,ESP32 固件零改动
- 新增能力都以 MCP server / channel adapter 形式接入,不动 hermes 核心
3.2 组件清单
| ID | 组件 | 形态 | 自写 | LOC | 角色 |
|---|---|---|---|---|---|
| M0 | xinnan-tech xiaozhi-esp32-server | Docker (v0.9.4) | × | - | 音频网关 (复用上游) |
| M1 | xiaozhi-mcp-adapter | Python pkg | ✓ | ~400 | ESP32 端 MCP tool 反向代理 |
| M2 | openai-shim (含 hybrid router) | FastAPI | ✓ | ~800 | OpenAI-compat 入口 + 路由 |
| M3 | hermes-xiaozhi-plugin | Python plugin | ✓ | ~400 | (M2 替代后保留作 stub) |
| M4 | otto-robot MCP tools | C++ (ESP-IDF) | ✓ | ~50 | ESP32 端硬件控制工具 |
| M5 | lark-mcp-server (outbound) | Python pkg | ✓ | ~500 | 飞书发消息 tool |
| M6 | lark-event-listener (inbound) | Python pkg | ✓ | ~330 | 飞书收消息长连接 |
| 合计自写 | ~2500 |
3.3 协议栈
| 层 | 起点 → 终点 | 协议 | 说明 |
|---|---|---|---|
| L1 | ESP32 ↔ xinnan-tech | xiaozhi 私有 WebSocket | Opus 音频 + JSON 控制 + MCP 子帧 |
| L2 | xinnan-tech ↔ openai-shim | OpenAI Chat Completions SSE | 让 server 误以为 shim 是 OpenAI |
| L3 | shim ↔ hermes | subprocess + stdout pipe | hermes -z PROMPT --yolo |
| L4 | hermes ↔ 各 tool | MCP stdio | 标准 MCP,无私有协议 |
| L5 | hermes ↔ 飞书 | lark OpenAPI HTTP (out) + ws 长连接 (in) | 双向 |
3.4 决策映射
| ADR | 决策 |
|---|---|
| 0001 | 采纳三角色 agent 协作 |
| 0003 | executor 跑主 Claude Code,不走 Task subagent |
| 0005 | M3 transcript 走 OpenAI-compat 而非自写 plugin |
| 0006 | shim LLM 整个替换为 hermes -z subprocess |
| 0007 | hybrid router: motor 走 raw + tools, 其余走 hermes |
| 0008 | lark-mcp-server outbound |
| 0009 | lark-event-listener inbound 长连接 (非 webhook) |
每条决策的"why"和"考虑过的备选"详见对应 ADR 文件 (docs/adr/000X-*.md)。
4. 实现过程纪实
5 天里发生了什么,什么时候卡住了,怎么转出来。。
4.1Week 0 基线 —— 把硬件先跑起来
目标: 烧 baseline 固件,接 docker server,语音能复读
实际发生:
- 安装 ESP-IDF v5.5.2 → submodule 没初始化导致
__component_get_requirements阶段崩 (后来沉淀为 bitter lesson #12) - OTTO_ROBOT 板 menuconfig 后 build 崩,因为上游 README 漏写需要 append 3 个
CONFIG_*(HTTPD_WS_SUPPORT / CAMERA_OV2640/3660) (lesson #13) - 烧录后 ESP32 卡在 download mode,以为是 flash 失败,实际是要按 RST 键(lesson #14)
- 串口认成
/dev/ttyUSB0,实际 ESP32-S3 用 USB-OTG 是/dev/ttyACM0(lesson #15) - xinnan-tech docker 起来后第一次说话崩溃,因为 FunASR 模型 893MB
model.pt没下载 (lesson #11)
这一天的时长: ~10 小时,全在跟环境搏斗,代码写了 0 行
收获: ADR-0002 (Week 0 基线修正), 5 条 bitter lessons,意识到必须有工作流来沉淀这些坑 —— 否则下次重装会再踩一遍
4.2 : 桥接层第一版 —— M1 + M2 走通
目标: 把 xinnan-tech 跟 hermes 接起来,先 echo 跑通
走的路线:
- 上午先研究 transcript egress —— 上游小智在哪里"吐出"用户说的话?
- 发现 transcript 没有 MCP-client callback (跟我的预想不一样),实际走的是 OpenAI-compatible
messages字段 - 架构转向 1: 不需要写完整 Hermes plugin (~5-6 天),改用 OpenAI-compat shim(~1-2 天)。写ADR-0005
代码:
- M1
xiaozhi-mcp-adapter: 反向连接 xinnan-tech 8004 mcp_endpoint, echo tool spike 跑通 - M2
openai-shim: FastAPI + SSE + EchoBackend, 9 个 pytest 全 PASS
晚上接 DeepSeek: 加 DeepSeekBackend, 也接 hermes 转发 (HermesBackend 包 EchoBackend, fire-and-forget POST 给 hermes webhook)
晚上 12 点: 物理 ESP32 voice “时间过得真快” → 喇叭 3 秒后说 “echoed: …” 第一次端到端跑通。还不是真 AI,但管道全连了。
4.3 让机器人真"用 hermes 思考" —— ADR-0006 的诞生
白天的诉求: 上一天的 echoed: xxx 只是回声,没意思。真想要的是: voice “ls 一下 final 目录” → 机器人真去跑 ls → 喇叭报结果。
转折点: 跟 LLM 聊到一半我意识到:
为什么不让 hermes 来当 LLM 本身? —— xinnan-tech 看到的"我"就是 shim,shim 看到的"AI"是 hermes -z subprocess。这样 hermes 调用任何 tool 的能力都自动暴露给了物理小智。
实施: HermesAgentBackend.stream_response() 用 asyncio.create_subprocess_exec
spawn hermes -z PROMPT --yolo --accept-hooks,捕获 stdout 流回 SSE。
ADR-0006 落地。物理验证:
voice: “帮我看看 final 文件夹里面有什么目录” ↓ 13s 喇叭: “final 文件夹里有五个目录: docs文档、esp固件、hermes小智插件、 openai-shim接口层、xiaozhi-mcp-adapter适配器”
这是项目最大的高光时刻。第一次 LLM-assist 工程 + agent 工作流 + 物理硬件 三者真正合一。
4.4 Day 4 早上: 舵机不动了 —— hybrid router 的诞生 (ADR-0007)
ADR-0006 跑通后我兴奋地说 “挥挥手”,结果机器人不动。
诊断: 上游 OTTO_ROBOT 板有 12 个 MCP tool (含 self.otto.action 这种
舵机控制),但走 shim → hermes 这条路径,tools 被 hermes 截走了,xinnan-tech
server 看不到 tool_call 帧。
两难:
- 都走 hermes → 通用,但舵机响应慢(13s 体验差) + 经常 hermes 不调 tool
- 都走 raw DeepSeek → 舵机灵敏,但失去 hermes 通用能力
- 选:hybrid
实施: router.py 看 user prompt:
- 包含 motor keyword (挥手/走/跳/…) 或 ESP32 自带 tools 已注册 → 走 raw DeepSeek (tools 透传给 server)
- 其余 → HermesAgentBackend
物理验证: “挥挥手” → ~2 秒舵机真挥;“ls 一下” → 13 秒 hermes 跑+报结果。 两条线并存,ADR-0007 落地。
4.5 Day 5: 飞书 channel —— “多 channel 论点"的物理化
动机: “hermes 多 channel 共享大脑"这个论点需要不止一条腿。物理 ESP32 是一条,需要再加一条对比 —— 飞书是个好选择 (我每天在用,且飞书 OpenAPI 完善)。
Day 4 下午: M5 lark-mcp-server (outbound)
- stdio MCP server,暴露
lark_send_message_to_selftool hermes mcp add lark注册- 物理: voice “给我发个飞书消息说一切正常” → 飞书私聊真到
期间踩了 8 条 bitter lessons (#35-#42),最戏剧性的是 #36: clash-verge fake-ip 池把 api.deepseek.com 解析成假 IP,导致 hermes 调 DeepSeek 全 timeout。对应改方案是给 hermes 子进程注入 ALL_PROXY=socks5://... + NO_PROXY=feishu,让飞书直连、DeepSeek 走代理。
Day 5: M6 lark-event-listener (inbound)
- 用户发现"机器人发出去了,但用户从飞书发回来,没人接”
- 写第二个独立 Python 包,用
lark_oapi.ws.Client长连接订阅 IM event - 每条消息 spawn
hermes -z,prompt 强制 hermes 调lark_send_message_to_self回复
期间踩了 4 条 bitter lessons (#48-#51),最戏剧性的是 #51 父子进程 proxy 传递: listener 自己得 unset proxy (飞书必须直连),但 subprocess.exec(env= {**os.environ}) 把空 proxy 原样传给 hermes,导致 hermes 调 DeepSeek 又 fake-ip 失败 —— #45 的同形复现,只是跨了一层进程边界。
修复: 显式重建 env,给 hermes 注入 ALL_PROXY + NO_PROXY=feishu,2 路代理策略各自正常。
最终飞书 → hermes → 回飞书 闭环 (D4 demo)。
4.6 项目演化树 (9 份 ADR)

无 superseded 节点,9 个全 active —— 架构没有"撤回”,每次转向都是叠加而非推翻,这是项目设计相对收敛的标志。
4.7 时间分布
总工时约 30-35 小时 / 5 个整天,粗略分布:
| 阶段 | 工时 | 占比 |
|---|---|---|
| Day 1 环境/依赖问题 | ~10h | 30% |
| Day 2-3 桥接层 (M1+M2+ADR-0005/0006) | ~8h | 25% |
| Day 4 ADR-0007 + lark outbound | ~7h | 22% |
| Day 5 lark inbound + 答辩准备 | ~6h | 18% |
| 写文档/handoff/ADR (横跨 5 天) | ~5h | 15% |
人 vs LLM 分工:
- LLM (Claude Code Opus 主导): 约 85% 的代码、95% 的 pytest、所有 handoff 和 ADR 的文字起草
- 人 (我):
- 顶层 idea (拓扑反转、把 hermes 当中心)
- 资料收集 (找上游 78/xiaozhi-esp32 + xinnan-tech 源码,挑出 transcript egress 在哪、MCP 帧长什么样)
- 架构设计 (5 次 ADR 转向都是我先意识到AI做错了方向)
- 落地决策 (转向和收手时机)
- 物理操作 (烧录、按 RST、接线、对着机器人说话)
5. 效果评估
5.1 4 条 demo 链路实测
| 路径 | 端到端延迟 | handoff |
|---|---|---|
| voice → 喇叭说话 (闲聊) | 3-6s | H022 ✓ |
| voice → 物理舵机动 (“挥挥手”) | ~2s | H035 ✓ |
| voice → hermes → tool → 喇叭报结果 | ~13s | H033 ✓ |
| 飞书消息 → hermes → 回飞书 | 10-30s | H039 ✓ |
具体的演示已经录制好了视频并且上传了。
5.2 资源占用
- ESP32 端:
xiaozhi.bin= 3.52 MiB / 4 MiB app partition = 88% 占用,11% 余量 (~440 KB,够再加 5-10 个 MCP tool) - 服务端 (笔记本): 3 个 Python 进程 (shim + lark-mcp + listener),常驻 RAM < 500 MB
- 网络: 单次 voice round-trip ~50 KB (Opus 压缩 + JSON 控制)
- 算力: 推理在 DeepSeek 云端,本地 CPU 几乎闲;hermes spawn ~3-5s 是Python 启动 + plugin 加载,可优化但 demo 不必
5.3 与上游原版对比
| 维度 | 上游原版 (小智 + xinnan-tech) | ottagent |
|---|---|---|
| 换 LLM provider | 改 .config.yaml + 重启 docker |
同 (无变化) |
| 加自定义工具 | 改 ESP32 C++ + 重烧固件 (~30min) | hermes mcp add 一行 (~5s) |
| 接其他 IM/channel | 不支持 | 已接飞书,其他都有完善的社区生态支持,很方便扩展 |
| 多 agent 共享 | 不支持 (server 单点) | hermes 单点 (但可换 agent 框架) |
| 上游 friendliness | 改 server 源码,与上游 diverge | 零侵入,ESP32 固件 + server 全 untouched |
5.4 我的一些主观体验
- 项目结束后,飞书 channel 用起来还算是顺手,可以给自己发送信息,当备忘录记录灵感
- 舵机响应快到可以"对话式"控制 (说"再来一次"它真的会再做一次动作)
- “voice 让机器人 ls 一个目录然后用喇叭报出来"具有演示冲击力和实用价值
6. 反思与改进
6.1 拓扑创新点 (核心论点回顾)
把"AI 玩具"重构成"hermes 的物理 channel"之所以值得做,本质是:单点中心 + 多 channel 比 多个产品各自带大脑 在工程上是 5-10× 的杠杆。每加一个 channel 不重新写 LLM 集成、不重训 prompt、不重做 tool 注册 ——只写一个 ~300 LOC 的 adapter。
这跟操作系统里 “kernel + driver” 的关系是同构的: 大脑是 kernel,channel 是driver,driver 接口标准化后,生态会自己长出来。
6.2 多角色工作流的价值与边界反思
值:
- handoff 文件让"5 天前的决策动机"今天还看得到 (传统agent交流做不到)
- ADR supersession 链让架构演化可追溯 (传统 commit message 做不到)
- bitter lessons 自动沉淀,下次遇到相似坑可以 3 秒内 grep 到(我的架构的独到之处)
不值:
- 写一份 handoff 平均 ~5 分钟,改一句话也要写;小任务过重
- auditor 在单人项目里少用 —— 我 39 份 handoff 里只调用 auditor 1 次 (H006)
- 三角色都跑在 Claude Code 主会话上,机器人项目的物理操作 (烧录/接线/按 RST) 这些 LLM 做不了,必须人去做,工作流并不消除人的体力劳动
- 对于简单项目,token消耗过大
6.3 LLM-assist 开发的边界
| 我做的 | LLM 做的 |
|---|---|
| 拓扑设计 (channel 思想) | 90% 代码起草 |
| ADR 决策 (转向时机) | 95% pytest 编写 |
| 上游 repo 啃读 (找接入点) | 几乎全部 handoff 和 ADR 文字起草 |
| 物理硬件操作 (烧录/串口/按键) | bug 定位 (前期主导,后期人接管) |
关键 spike 验证 (#48 grep @app.websocket) |
文档润色 |
| 跨 session 上下文衔接 (compact 时人来 brief) | 工作流脚手架 (start_all.sh / stop_all.sh) |
LLM 犯过的典型错误:
- 写测试时凭"印象"敲文件名 (e.g.
test_echo_tool.py实际叫test_pipe_e2e.py) - sandbox 不能 bind socket 时 stop-rule 过严 (cosmetic blocking)
- 51 条 bitter lessons 里约 30% 是 LLM 误判直接导致的
我必须自己想清楚不能交给 LLM 的事:
- 整体拓扑 (ADR-0005/0006/0007 的转向都是我先意识到方向不对)
- 安全纪律 (API key 不进 chat 这种,LLM 不会主动提醒)
- 物理 demo 的"做了吗"判断 —— LLM 看到"pytest PASS"就以为做完了, 实际硬件根本没测
6.4 51 条 bitter lessons 分类
| 类别 | 数量 | 典型例 |
|---|---|---|
| 协议/接口误读 | 15 | #48 /call/ 是 ws 不是 http |
| 环境/网络 | 12 | #45+#51 父子进程 proxy 传递 |
| 上游文档漏写 | 8 | OTTO_ROBOT 板 sdkconfig append |
| 工具链路径 | 10 | python 解释器 PlatformIO penv vs conda |
| Agent 工作流 | 6 | codex sandbox 不能 bind |
详见附录 D。
6.5 局限与发展空间
| 局限 | 为什么没解决 | 投入估计 |
|---|---|---|
| 飞书消息不触发物理喇叭 | mcp_endpoint /call/ 是 ws 端点,xinnan-tech 主 server 无主动 TTS 入口,改上游 out of scope |
中(需要写第二个 sidecar 反向占 slot) |
| ESP32 主动 push event 未实现 | 上游 PIR/sensor stub 不够 | 大(要加传感器 + 改 ESP32) |
| 无多轮上下文 (每条消息独立 hermes session) | hermes session 持久化没研究 | 小(hermes 已支持 --continue) |
| ASR 识别率低 (FunASR 本地版偏弱) | demo 时换 TencentASR 即可,改一行 config,不算技术债 | 极小 |
| hermes plugin 正式重构 (ADR-0005 路线) | 当前 OpenAI-compat 已够,plugin 是给社区贡献时再做 | 大(5-6 人天) |
6.6 未来工作 (按 ROI 排)
- ASR 换 TencentASR: 改
.config.yaml,识别率立刻翻倍 - hermes session 持久化 (3-5h): 加多轮上下文,显著提升对话连贯性
- 正式 PR 给 Hermes 社区 (5-10h): 把 lark-mcp-server + xiaozhi adapter规范化成 Hermes plugin,这是最大的"社区贡献"输出
- 加 PIR + 摄像头主动感知 (~2 周): 让机器人能"看到你回家了"主动招呼。
- 多机器人共享一个 hermes 大脑 (大): 不是单台扩展,是 spec 级新研究方向
6.7 可贡献社区的部分
lark-mcp-server(独立包,任何 hermes 用户能用)xiaozhi-mcp-adapter(M1, ESP32 端 MCP tool 反向代理模板)- 三角色协作工作流的
.claude/模板 (其他项目 ~10min 套用) - 51 条 bitter lessons 全文 (给"想接 ESP32+LLM"的人节省 ~2 周)
7. 结论
回到引言里的 3 个研究问题:
- RQ1 (能否不改上游接入任意 LLM 后端): 能。本项目 ESP32 端固件零自写代码 (除 §3.2 M4 的 ~50 LOC,且是上游官方
AddTool模板), hermes 后面 LLM 想换就换。 - RQ2 (桥接层长什么样最优): OpenAI-compat shim + MCP stdio 组合。shim 让 server 误以为自己在调 OpenAI 兼容 API,MCP 让 tool 接入标准化。比"写正式 Hermes plugin"工作量小 5x,功能等价。
- RQ3 (一人 + LLM 能否产出社区级代码): 能,但需要工作流。本项目~2500 行自写代码 + 9 ADR + 39 handoff,在 30-35 小时内完成,没有三角色 + ADR 工作流不可能 —— 单 session 长项目里 LLM 会自我幻觉、会忘记早期决策、会重蹈已踩过的坑。工作流是把 LLM 的"短时记忆"externalize 到文件系统的工程化手段。
回到标题: ottagent 是 Hermes Agent 的"第一具物理化身” —— 不是因为它最完美,而是因为它第一次用零私有协议、上游友好的方式把 LLM agent 框架跟实体硬件连起来。这条拓扑可以复制到任何"AI 玩具",也可以反向扩展成任何"为 hermes 加新 channel"的需求。
致谢
- 课程老师: 邓宁老师、何健飞老师
- 上游开源:
xinnan-tech/xiaozhi-esp32-server、78/xiaozhi-esp32 - Nous Research / Hermes Agent
- Anthropic Claude (本项目主要 LLM 开发助手)
- DeepSeek (推理后端)
- larksuite/oapi-sdk-python (飞书 SDK)
参考文献
- [1] xinnan-tech/xiaozhi-esp32-server v0.9.4. https://github.com/xinnan-tech/xiaozhi-esp32-server
- [2] 78/xiaozhi-esp32. https://github.com/78/xiaozhi-esp32
- [3] Nous Research, Hermes Agent. https://github.com/NousResearch/hermes-agent
- [4] Anthropic, Model Context Protocol. https://modelcontextprotocol.io/
- [5] larksuite/oapi-sdk-python. https://github.com/larksuite/oapi-sdk-python
- [6] DeepSeek. https://www.deepseek.com/
- [7] 李凯锋, agent-loop-evolution-spec. https://github.com/kkLullaby/Multi-agent_collaboration_workflow_design_specifications
- [8] ChatDev. … (若引)
- [9] MetaGPT. … (若引)
- [10] AutoGen. …
附录
A. 复现步骤 (一键启停)
git clone --recursive <repo-url>
cd final_pro_xiaozhi_robot
# ESP32 端: 见 .claude/memory/shared/global-commands.md (烧录步骤)
# 服务端:
bash scripts/start_all.sh # 8 step,全启 (docker / shim / sidecar / hermes / lark mcp / lark listener / ...)
bash scripts/stop_all.sh
B. 9 份 ADR 速查表
| ADR | 标题 | 日期 |
|---|---|---|
| 0001 | 采纳三角色 agent 协作 | 2026-06-16 |
| 0002 | Week 0 基线修正 | 2026-06-16 |
| 0003 | Executor 跑主会话 | 2026-06-16 |
| 0004 | voice callback 仅限 Discord | 2026-06-18 |
| 0005 | M3 走 OpenAI-compat transcript | 2026-06-19 |
| 0006 | HermesAgentBackend 替代 LLM | 2026-06-20 |
| 0007 | Hybrid backend router | 2026-06-20 |
| 0008 | lark-mcp-server outbound | 2026-06-20 |
| 0009 | lark-event-listener inbound 长连接 | 2026-06-20 |
C. 39 份 handoff 索引
见
docs/handoffs/INDEX.md,本附录略
D. 51 条 bitter lessons 全表
见项目内
docs/retros/*.md和各 ADR 末尾 “Bitter lessons” 段,本附录略