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-esp32xinnan-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

这样做的副作用很大:

  1. 后端无关:hermes 想换 LLM 就换,小智固件零修改
  2. 工具可热加:hermes mcp add 一行命令给机器人加新能力,不重烧固件
  3. 多 channel 共享大脑:同一个 hermes 实例可以同时接 Discord、飞书、物理小智
  4. 可上游贡献:这个 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 总体架构

总体架构

关键设计原则:

  1. hermes 是唯一的中心节点;所有 channel 平等
  2. ESP32 ↔ xinnan-tech ↔ shim 这一段沿用上游既有协议,ESP32 固件零改动
  3. 新增能力都以 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 跑通

走的路线:

  1. 上午先研究 transcript egress —— 上游小智在哪里"吐出"用户说的话?
  2. 发现 transcript 没有 MCP-client callback (跟我的预想不一样),实际走的是 OpenAI-compatible messages 字段
  3. 架构转向 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_self tool
  • 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 排)

  1. ASR 换 TencentASR: 改 .config.yaml,识别率立刻翻倍
  2. hermes session 持久化 (3-5h): 加多轮上下文,显著提升对话连贯性
  3. 正式 PR 给 Hermes 社区 (5-10h): 把 lark-mcp-server + xiaozhi adapter规范化成 Hermes plugin,这是最大的"社区贡献"输出
  4. 加 PIR + 摄像头主动感知 (~2 周): 让机器人能"看到你回家了"主动招呼。
  5. 多机器人共享一个 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-server78/xiaozhi-esp32
  • Nous Research / Hermes Agent
  • Anthropic Claude (本项目主要 LLM 开发助手)
  • DeepSeek (推理后端)
  • larksuite/oapi-sdk-python (飞书 SDK)

参考文献


附录

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” 段,本附录略