Hermes 旁路视图系统

Wingman僚机

爸爸跟 Hermes 聊天时,每个话题会刷几十条工具调用的中间态。僚机把「当前话题在干啥 / 进度 / 完工了没 / 在等谁拍板」实时提炼出来,存进 SQLite,由网页驾驶舱呈现,关键里程碑还会浏览器语音播报。

前后端合一 monorepo · 两个进程,一份代码,一份契约 · 驾驶舱 http://localhost:7421

它解决的问题

✗ 没有僚机时

Hermes 在一个话题里疯狂刷 tool_call 中间态,几十条滚动。你很难一眼看出「这事到底进行到哪了、做完没、是不是在等我拍板」。

✓ 有了僚机

每 5 秒提炼一次:一句话摘要 + 状态判定 + 里程碑 + 待你决定的选项。多个话题/任务的真实进度,一屏看全,重要节点还会念给你听。

数据流 · 从飞书到驾驶舱

飞书云 · 你和 Hermes 的对话
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。