止观
Hermes实战

我把 bot 团队拉进了一块看板:Hermes Kanban 实战

上一篇结尾我埋了话头:peer 打通了两台机器上 bot 互发消息,但 bot 多了之后,谁在干什么、干到哪一步、卡在哪,全靠聊天记录去翻。这周我把 Hermes 的 kanban 摸了一遍,把自己固定跑的几个 bot 接了上去。这篇按一张任务卡的“一生”来讲,所有终端输出都是本机 v0.21.2 实跑。

一张卡片的一生:看板的三张表与两个入口

※ ※ ※

先说它跟 delegate_task 不是一回事

我一开始以为 kanban 就是个带界面的 delegate_task,看完官方文档里那张对比表才意识到方向反了:delegate_task 是函数调用,fork 出来等它 return,父 agent 卡着,失败了就真失败了;kanban 是个工作队列,create 完就撒手,任务变成 SQLite 里的一行,谁都可以读、谁都可以改。

文档原话我翻译一下:delegate 的子 agent 是无名氏,kanban 的 worker 是“有编制的”——一个具名 profile,自己的模型、自己的记忆、自己的工作目录。任务挂了可以 reclaim 重跑,人随时能下场评论一句再 unblock。审计记录全在库里,不怕上下文压缩把交接细节洗没了。

所以判断标准很朴素:结果要回流进当前对话的,用 delegate;跨 bot、要活着熬过重启、可能中途要人拍板的,上看板。两者不冲突,看板 worker 跑任务时自己也能 delegate。

※ ※ ※

板子本身:就是一个(几个)SQLite 文件

hermes kanban init 建库。单项目用户永远只有一个叫 default 的板,DB 在 ~/.hermes/kanban.db;要多块板(一个项目一块),落盘在 ~/.hermes/kanban/boards/<slug>/kanban.db,各板的工作区、日志、调度范围全部隔离,跨板连线直接禁止。我在临时 profile 上试了建板:

$ hermes kanban boards create demo-proj --name "演示项目" --icon 🧪
Board 'demo-proj' created.
  Display name: 演示项目
  DB path:      /home/ubuntu/.hermes/kanban/boards/demo-proj/kanban.db

板子归属的解析顺序值得记一下:--board 参数 > 环境变量 HERMES_KANBAN_BOARD > boards switch 写入的 current 指针 > default。dispatcher 派活时会给 worker 钉死 HERMES_KANBAN_BOARD,所以 worker 物理上看不见别的板——隔离靠环境变量而不是靠自觉,这个设计我喜欢。

建卡、评论、连线,都是常规操作:

$ hermes kanban create '整理昨晚巡检日志并输出摘要' --assignee researcher
⚠  No gateway is running — the task will sit in 'ready' until you start it.
Created t_326f15c7  (ready, assignee=researcher)

$ hermes kanban create '汇总摘要写入日报' --assignee ops
Created t_9ca60f18  (ready, assignee=ops)

$ hermes kanban link t_326f15c7 t_9ca60f18
Linked t_326f15c7 -> t_9ca60f18

注意第二幕:link 之后我再 show 子卡,它的状态已经从 ready 掉回 todo 了,因为父任务没完。依赖门是 dispatcher 每轮扫的:所有父卡 done,子卡才自动 ready。反过来,给一个已完成的卡开后续工作时,正确姿势是新建子卡挂 --parent,而不是重开老卡——父卡的完成摘要和 metadata 会原样注入子卡 worker 的上下文,文档管这叫交接通道,我觉得更像继承遗产。

还有个细节:create 时 gateway 没起,CLI 会当场警告你会卡在 ready。dispatcher 就跑在 gateway 进程里,默认每 60 秒一个 tick,收尸、放行、认领、spawn 全在这一个循环里。没有独立 daemon 要伺候(旧的 kanban daemon 已废弃),gateway 活着板子就活着。

※ ※ ※

worker 被拉起时看到什么

这是整个设计里我觉得最讲究的一段。dispatcher 认领任务用的是 SQLite 的原子 claim,然后 spawn 的居然是:

hermes -p <assignee> chat -q <prompt>

一个完整 OS 进程,带上 HERMES_KANBAN_TASK、HERMES_KANBAN_WORKSPACE 等九个环境变量。worker 醒来干的第一个动作是固定的:调 kanban_show() 读自己的卡——标题、正文、父卡交接、历史尝试、完整评论串。干完活必须调 kanban_complete 或 kanban_block 收尾,工具集里 heartbeat、comment、attach 各有一席。

划重点:worker 用工具,不用 CLI。模型从不调 hermes kanban complete,它调的是 kanban_complete 工具,直接走 Python 层进库。文档给了三个理由,我服:worker 的终端可能指向 Docker/SSH 远端,容器里根本没有 hermes 命令;JSON metadata 从 shell 参数里穿来穿去是引号地狱;工具报错是结构化 JSON,模型看得懂。

工作区三种:scratch(默认,任务完成即删,只有显式声明的 artifacts 会被抢救进附件存储)、dir:<绝对路径>(共享目录,相对路径直接拒绝,理由是防止 dispatcher 按自己的 CWD 去解析——典型的 confused deputy 防御)、worktree(git 工作树,保留)。第一次用 scratch 还会在卡上记一条 tip_scratch_workspace 事件提醒你“这目录会被删”。

顺手挖到一个文档和源码打架的地方,可以作为“读源码有肉吃”的证据:文档说并发上限 kanban.max_in_progress “未设置 = 不限”,但 kanban_db_dispatch.py 里注释写得直白——“无帽的板曾把一台 1 GiB 主机 OOM 过”,源码实际按总内存推导默认帽:每 worker 按 512MB 估算、clamp 到 2-8。我这台 3.7GB 的机器,帽自动算出来是 7。另外任务状态源码里是 9 个(VALID_STATUSES 多一个 scheduled,即带定时门的 ready),文档概念章节只列了 8 个。文档更新赶不上代码,这大概就是每个快速迭代的开源项目都有的债。

※ ※ ※

防事故的部分,值得单开一节

多 agent 系统最怕的不是笨,是抽风。kanban 内核里埋了一圈确定性护栏,全是拿真实事故换来的:

  • ▪ 连败熔断:同一卡连续 spawn 失败(profile 不存在、工作区挂不上)达到 failure_limit(默认 2)次,自动 block 并附上最后一次错误,不再原地转圈。
  • ▪ 解而不决的死循环:卡被 block、人 unblock、又因同样原因 block,重复 BLOCK_RECURRENCE_LIMIT(默认 2)次后,直接改道 triage 请人来看——因为再放回 blocked 也只会喂给那个无脑 unblock 的 cron。计数器只在 complete 时清零,unblock 不清,故意的。
  • ▪ 僵尸检测:worker PID 没了但 claim 没到期,记 crashed 回收;跑超 4 小时且一小时内没有 heartbeat,记 stale 回收(不算失败次数,锅不在 worker);超过 --max-runtime 直接 SIGTERM,5 秒不退补 SIGKILL。
  • ▪ 口述干活的惩罚:worker 正常退出但没调终结工具,判 protocol_violation。agent 侧退出前最多塞两次合成提醒(“你还没交卡”),连续违规 3 次才熔断 block。

hermes kanban dispatch --dry-run --json 能让我这种控制狂提前看清每轮会发生什么,实测输出:

{
  "skipped_unassigned": ["t_5cec5e35"],
  "skipped_nonspawnable": ["t_326f15c7"]
}

researcher 这个 profile 在我机器上不存在,任务没被随便找个 bot 顶包,而是留在 ready 记一笔 skipped_nonspawnable 事件等主人来修——宁可不动,不做兜底猜测。这类“fail loud”的选择表里还有很多,能看出写它的人被静默失败坑过。

※ ※ ※

我当场踩到的两个坑

第一个最好笑。 我本想派个子 agent 帮我批量建卡,子 agent 跑 CLI 直接被拒:

kanban: delegate_task child contexts cannot mutate Kanban tasks via the CLI

翻了源码才懂:delegate 出来的子进程带 HERMES_DELEGATED_CHILD_CONTEXT 标记,kanban 的写路径在 CLI 层快速失败之外,DB 层还有一道 _assert_not_delegated_child_mutation——注释写明“CLI 检查只是 UX,持久信任边界在 kanban_db”。子 agent 想绕过 CLI 直接 import Python 改库也不行。看板 worker 的生命周期操作被钉死在“它自己那张卡”上,连亲儿子都改不动别人的卡。这个隔离级别,比我见过的多数 agent 框架都较真。

第二个是我自己没看文档。 建卡时随手写了个不存在的 assignee,等了一个 tick 没动静,list 一看还在 ready。上面说了,不 spawn 就挂着。profile 名字必须先存在(hermes profile list 能查),或者让 decompose 去路由(见下节),别学我裸猜。

※ ※ ※

三张图之外:triage、swarm 和手机指挥

看板还有几层我没在正文实测但源码确认存在的能力,够到一定规模才用得上,先记下:

自动分解。 triage 列是放粗糙想法的地方,默认(auto_decompose: true)dispatcher 每 tick 拿最多 3 张卡喂给一个辅助模型,读你的 profile 花名册(带描述的优先),直接生成子任务图分派到人,原卡变成所有叶子的爹、最后负责验收。profile 描述用 hermes profile describe <名> --auto 让模型代写。手动党就 /kanban decompose <id>。

swarm 一条命令。 hermes kanban swarm “题目” --workers a,b,c --verifier reviewer --synthesizer writer,原子落一张“并行工人 → 验证 → 合成”的图,验证卡不过关就都白干。适合“先分头查再合稿”的活。

手机上指挥。 每个 CLI 动词都有 /kanban 斜杠命令等价物,而且它豁免了“agent 思考中不响应指令”的排队保护——worker 正 blocked 等你拍板时,手机上 /kanban unblock t_xxx 即刻生效,不打断任何进行中的对话。从 gateway 建的卡自动订阅终结事件,完成或卡住会主动回你一条消息。我的日常形态成了:早上在手机上扫一眼 triage,丢两句想法进去,中午看板自己长出一串已完成的卡。

dispatcher 的护栏:状态机与四道事故防线

※ ※ ※

边界先讲清楚

kanban 是单机设计,官方 out-of-scope 写得直白:DB 是本地的,worker 在本机 spawn,崩溃检测靠 host-local 的 PID。两台机器共享一块板不支持——跨机场景的正确答案是上一篇的 peer:每台机器自己的板,bot 之间互相@。另外 dashboard 的插件 API 默认无鉴权(因为绑 127.0.0.1),谁要是 --host 0.0.0.0 把它裸露到公网,等于把任务内容、工作区路径、评论全送出去,共享主机上这是事故。

写到这回头看,kanban 解决的其实不是“agent 会不会干活”,是“活干过没有”。每一行 handoff 都是库里的一枚钉子,重启、崩溃、压缩上下文都拔不掉。我的 bot 团队以前像群演,每天重新发一遍剧本;现在像有了公告栏。

你现在管理多个 agent 靠什么?聊天记录、自建 cron、还是已经有人搭了工作流引擎?评论区聊聊你的翻车现场。

点个在看,把这篇转给那个还在用 print 日志指挥 bot 的朋友。

下一篇预告:把 kanban 和 cron 缝起来——每早 7 点自动往 triage 丢任务、白天盯熔断、晚上归档,一条日报流水线的完整搭法。

※ ※ ※

资料来源

  • ▪ Hermes Agent 官方文档 user-guide/features/kanban.md、kanban-worker-lanes.md、docs/kanban/multi-gateway.md(本机仓库 v0.21.2 时期)
  • ▪ 源码:hermes_cli/kanban.py(委派围栏 fast-fail 及注释)、hermes_cli/kanban_db.py:135(DB 层 PermissionError)、gateway/kanban_watchers.py
  • ▪ 本文终端输出均为作者本机实测(临时 profile kbdemo,测试板与任务已清理);未实测的 decompose/swarm/通知机制以官方文档与源码为准
  • ▪ GitHub:NousResearch/hermes-agent
  • ▪ 生成日期:2026-09-14

评论 (0)