Hermes 旁路视图系统
Wingman僚机
爸爸跟 Hermes 聊天时,每个话题会刷几十条工具调用的中间态。僚机把「当前话题在干啥 / 进度 / 完工了没 / 在等谁拍板」实时提炼出来,存进 SQLite,由网页驾驶舱呈现,关键里程碑还会浏览器语音播报。
前后端合一 monorepo · 两个进程,一份代码,一份契约 · 驾驶舱 http://localhost:7421
它解决的问题
✗ 没有僚机时
Hermes 在一个话题里疯狂刷 tool_call 中间态,几十条滚动。你很难一眼看出「这事到底进行到哪了、做完没、是不是在等我拍板」。
✓ 有了僚机
每 5 秒提炼一次:一句话摘要 + 状态判定 + 里程碑 + 待你决定的选项。多个话题/任务的真实进度,一屏看全,重要节点还会念给你听。
数据流 · 从飞书到驾驶舱
▼
Hermes gateway · fanout hook
分支 wingman-fanout,把每条事件旁路写到 ~/.hermes/logs/wingman-fanout.jsonl
▼ tail -F
daemon/main.py · 后端守护(纯 stdlib)
① tail jsonl → upsert 进 SQLite ② 每 5s tick → 调 haiku 提炼出 summary / 判定 / 里程碑 / 变更 / 待拍板选项 / 完工
▼ 写 / 读 同一份
🗄 SQLite · ~/.hermes/wingman.db
▲ 只读 ▼
web/app.py · FastAPI 只读驾驶舱 :7421
React + Vite 前端:宽屏双栏卡片墙 + 关键里程碑 speechSynthesis 语音播报 + 本地 CC 会话浏览 / transcript
架构核心 · Pipeline 编排三层
Source
三路输入
- hermes 飞书话题
- pi Pi 会话
- cc Claude Code 任务
Summarizer
提炼
- haiku 大模型
- summary / verdict
- milestones / choices
Notifier
通知
- lark_card 交互卡片
- lark_text 完工通知
- once-only done
shared/pipeline.py 每个 tick 串起:Source.poll() 灌进 ThreadStore → 对每个话题 plan_tick 闸门(skip / force_done / 调 haiku)→ build_decision → 触发 Notifier → apply_tick 落库。层级纯净:只 import shared.{store, sources, summarizers, notifiers}。
两个深模块 · 把知识收口
ThreadStore
独占一个 SQLite 连接 + 一把锁,对外只暴露领域动词与类型化的 ThreadState。所有读写都经它、内部串行化——「写必须持锁」不再靠约定。JSON 列在边界解码,调用方不碰 json。开 WAL 让只读的 web 不撞 daemon 的写锁。
Lark adapter
拥有每一次 lark-cli 调用:subprocess 形状、rc/json/code 三道校验、message_id 提取。失败抛 LarkError。「怎么调」的知识在此收口,「失败怎么办」的策略留给调用方——daemon 记日志继续,web 翻成 HTTP 异常。
tick 决策 · 把判断和副作用分开
daemon 每 5s 对一个 active 话题跑一次 tick。决策是两个纯函数的产物,全是数据、不含 I/O,被夹在副作用中间——这样「误判完工 / 漏锁」这类编排 bug 可以单测。
plan_tick(st, now)
闸门:状态分类 + stalled 频率节流 + token 节流,决定该 skip、force_done 还是调 haiku。
build_decision(st, plan, haiku)
把闸门结果和 haiku 输出映射成 Decision(含 summary 兜底),再由 ThreadStore.apply_tick 落库。
话题状态机:
active 进行中
stalled 卡住
done 完工
idle 空闲
运行模型
两个服务都从 repo 根以「包」形式运行,from shared... 在 daemon / web / 测试里一致解析。
后端守护(纯 stdlib,系统 python):python3 -m daemon.main
网页驾驶舱(venv 内 uvicorn):uvicorn web.app:app --port 7421
前端(React+Vite):改完 build 到 web/static/dist/,已提交,部署无构建步。
常驻:launchd — com.leway.wingman + com.leway.cockpit。