跳到主要内容

Kanban — 多 Agent 配置文件协作

想要完整演示? 请阅读 Kanban 教程 — 四个用户故事(独立开发者、集群农场、带重试的角色流水线、熔断器),以及每个场景的仪表板截图。本页是参考手册;教程是叙事说明。

Hermes Kanban 是一个持久化任务看板,在你所有的 Hermes 配置文件之间共享,让多个命名 Agent 无需脆弱的进程内子 Agent 集群即可协作处理工作。每个任务都是 ~/.hermes/kanban.db 中的一行记录;每次交接都是任何人都可以读写的一行;每个工作者都是拥有独立身份的完整 OS 进程。

两个界面:模型通过工具操作,你通过 CLI 操作

看板有两个入口,背后都指向同一个 ~/.hermes/kanban.db

  • Agent 通过专用的 kanban_* 工具集驱动看板kanban_showkanban_completekanban_blockkanban_heartbeatkanban_commentkanban_createkanban_link。调度器在生成每个工作者时已将这些工具包含在其 schema 中;模型通过直接调用这些工具来读取任务并交接工作,而不是通过 shell 执行 hermes kanban。详见下方的工作者如何与看板交互
  • 你(以及脚本和定时任务)通过 CLI 上的 hermes kanban …/kanban … 斜杠命令或仪表板来驱动看板。这些供人类和自动化使用 — 即没有工具调用模型在背后的场景。

两个界面都通过同一个 kanban_db 层路由,因此读取结果一致,写入不会产生偏差。本页的其余部分使用 CLI 示例,因为它们便于复制粘贴,但每个 CLI 动词都有模型使用的等效工具调用形式。

以下工作负载是 delegate_task 无法覆盖的,Kanban 正好适合:

  • 研究分诊 — 并行研究员 + 分析师 + 作者,人在回路中。
  • 定时运维 — 每天定期生成简报,逐周积累日志。
  • 数字孪生 — 持久命名助手(inbox-triageops-review),随时间积累记忆。
  • 工程流水线 — 分解 → 在并行工作树中实现 → 审查 → 迭代 → 提 PR。
  • 集群工作 — 一个专家管理 N 个主题(50 个社交账户、12 个监控服务)。

完整的设计理念、与 Cline Kanban / Paperclip / NanoClaw / Google Gemini Enterprise 的对比分析,以及八种典型协作模式,请参见仓库中的 docs/hermes-kanban-v1-spec.pdf

Kanban 与 delegate_task 的对比

它们看起来相似,但本质上是不同的原语。

delegate_taskKanban
形态RPC 调用(fork → join)持久化消息队列 + 状态机
父任务阻塞直到子任务返回创建后即触发即忘
子任务身份匿名子 Agent有持久记忆的命名配置文件
可恢复性无 — 失败即失败阻塞 → 解除阻塞 → 重新运行;崩溃 → 回收
人在回路中不支持可在任意时刻评论/解除阻塞
每任务 Agent 数一次调用 = 一个子 Agent任务生命周期内可有 N 个 Agent(重试、审查、后续跟进)
审计记录上下文压缩时丢失永久保存在 SQLite 行中
协调方式层级制(调用方 → 被调用方)对等 — 任何配置文件都可读写任何任务

一句话区别: delegate_task 是函数调用;Kanban 是工作队列,每次交接都是任何配置文件(或人类)都可以查看和编辑的一行记录。

在以下情况使用 delegate_task:父 Agent 在继续前需要简短的推理答案,不涉及人类,结果返回到父 Agent 的上下文中。

在以下情况使用 Kanban:工作跨越 Agent 边界、需要在重启后保持、可能需要人工输入、可能由不同角色接手,或者事后需要可发现。

两者可以共存:看板工作者在运行期间可以在内部调用 delegate_task

核心概念

  • 看板(Board) — 拥有独立 SQLite DB、工作区目录和调度器循环的独立任务队列。单次安装可以有多个看板(例如每个项目、仓库或域各一个);参见下方的多项目看板。单项目用户保持使用 default 看板,在本文档部分之外永远不会看到"board"这个词。
  • 任务(Task) — 包含标题、可选正文、一个负责人(配置文件名)、状态(triage | todo | ready | running | blocked | done | archived)、可选租户命名空间、可选幂等键(用于重试自动化的去重)的行记录。
  • 链接(Link) — 记录父 → 子依赖关系的 task_links 行。当所有父任务变为 done 时,调度器会将 todo → ready 提升。
  • 评论(Comment) — Agent 间协议。Agent 和人类追加评论;当工作者(重新)生成时,它会将完整的评论线程作为上下文的一部分读取。
  • 工作区(Workspace) — 工作者的操作目录。三种类型:
    • scratch(默认)— 在 ~/.hermes/kanban/workspaces/<id>/ 下的全新临时目录(非默认看板在 ~/.hermes/kanban/boards/<slug>/workspaces/<id>/ 下)。
    • dir:<path> — 现有共享目录(Obsidian 保险库、邮件操作目录、每账户文件夹)。必须是绝对路径。 相对路径(如 dir:../tenants/foo/)在调度时会被拒绝,因为它们会相对于调度器恰好在的 CWD 解析,这具有歧义性且是混淆代理的逃逸向量。路径在其他方面是受信任的 — 这是你的机器、你的文件系统,工作者以你的 uid 运行。这是受信任本地用户威胁模型;kanban 按设计是单主机的。
    • worktree — 用于编码任务的 .worktrees/<id>/ 下的 git 工作树。工作者端的 git worktree add 创建它。
  • 调度器(Dispatcher) — 长期运行的循环,每 N 秒(默认 60 秒)执行一次:回收过期声明、回收崩溃的工作者(PID 消失但 TTL 尚未到期)、提升就绪任务、原子性声明、生成分配的配置文件。默认在网关内运行(kanban.dispatch_in_gateway: true)。每次 tick 时一个调度器扫描所有看板;工作者在生成时设置 HERMES_KANBAN_BOARD,因此它们无法看到其他看板。同一任务连续约 5 次生成失败后,调度器会自动以最后一次错误为原因阻塞该任务 — 防止在配置文件不存在、工作区无法挂载等无法完成的任务上无限循环。
  • 租户(Tenant) — 看板的可选字符串命名空间。一个专家集群可以服务多个业务(--tenant business-a),通过工作区路径和记忆键前缀进行数据隔离。租户是软性过滤器;看板是硬性隔离边界。

多项目看板

看板让你将不相关的工作流分离为隔离的队列 — 每个项目、仓库或域各一个。新安装只有一个名为 default 的看板(DB 位于 ~/.hermes/kanban.db,向后兼容)。只需要一个工作流的用户无需了解看板;该功能是选择性启用的。

每个看板的隔离是绝对的:

  • 每个看板有独立的 SQLite DB(~/.hermes/kanban/boards/<slug>/kanban.db)。
  • 独立的 workspaces/logs/ 目录。
  • 为任务生成的工作者只能看到自己看板的任务 — 调度器在子进程环境中设置 HERMES_KANBAN_BOARD,工作者可访问的每个 kanban_* 工具都会读取它。
  • 不允许跨看板链接任务(保持 schema 简单;如果你真的需要跨项目引用,使用自由文本提及并手动按 id 查找)。

通过 CLI 管理看板

# 查看磁盘上有什么。全新安装只显示 "default"。
hermes kanban boards list

# 创建新看板。
hermes kanban boards create atm10-server \
--name "ATM10 Server" \
--description "Minecraft modded server ops" \
--icon 🎮 \
--switch # 可选:将其设为活跃看板

# 在特定看板上操作而不切换。
hermes kanban --board atm10-server list
hermes kanban --board atm10-server create "Restart ATM server" --assignee ops

# 更改后续调用的"当前"看板。
hermes kanban boards switch atm10-server
hermes kanban boards show # 现在哪个是活跃的?

# 重命名显示名称(slug 是不可变的 — 它是目录名)。
hermes kanban boards rename atm10-server "ATM10 (Prod)"

# 归档(默认)— 将看板目录移至 boards/_archived/<slug>-<ts>/。
# 可通过移回目录恢复。
hermes kanban boards rm atm10-server

# 硬删除 — 对看板目录执行 `rm -rf`。无法恢复。
hermes kanban boards rm atm10-server --delete

看板解析顺序(优先级从高到低):

  1. CLI 调用上的显式 --board <slug>
  2. HERMES_KANBAN_BOARD 环境变量(由调度器在生成工作者时设置,因此工作者无法看到其他看板)。
  3. ~/.hermes/kanban/current — 由 hermes kanban boards switch 持久化的 slug。
  4. default

Slug 经过验证:小写字母数字 + 连字符 + 下划线,1-64 个字符,必须以字母数字开头。大写输入会自动转换为小写。其他任何内容(斜杠、空格、点、..)都在 CLI 层被拒绝,因此路径遍历技巧无法命名看板。

通过仪表板管理看板

hermes dashboard → Kanban 标签页在存在多个看板(或任何看板有任务)时,在顶部显示看板切换器。单看板用户只看到一个小的 + New board 按钮;切换器在不需要时会隐藏。

  • 看板下拉菜单 — 选择活跃看板。你的选择保存在浏览器的 localStorage 中,因此跨重新加载时保持不变,而不会把 CLI 的 current 指针从你打开的终端下移走。
  • + 新看板 — 打开一个模态框,询问 slug、显示名称、描述和图标。可选择自动切换到新看板。
  • 归档 — 仅在非 default 看板上显示。确认后,将看板目录移至 boards/_archived/

所有仪表板 API 端点接受 ?board=<slug> 进行看板范围限定。事件 WebSocket 在连接时固定到某个看板;在 UI 中切换会针对新看板打开一个新的 WS 连接。

快速开始

以下命令供(人类)设置看板和创建任务。一旦任务被分配,调度器就会将分配的配置文件作为工作者生成,从那时起模型通过 kanban_* 工具调用驱动任务,而不是 CLI 命令 — 参见工作者如何与看板交互

# 1. 创建看板(你)
hermes kanban init

# 2. 启动网关(托管内嵌调度器)
hermes gateway start

# 3. 创建任务(你 — 或通过 kanban_create 的编排 Agent)
hermes kanban create "research AI funding landscape" --assignee researcher

# 4. 实时观察活动(你)
hermes kanban watch

# 5. 查看看板(你)
hermes kanban list
hermes kanban stats

当调度器接手 t_abcd 并生成 researcher 配置文件时,该工作者模型做的第一件事就是调用 kanban_show() 来读取其任务。它不会运行 hermes kanban show t_abcd

网关内嵌调度器(默认)

调度器在网关进程内运行。无需安装,无需管理单独的服务 — 只要网关运行,就绪任务会在下一个 tick(默认 60 秒)时被接手。

# config.yaml
kanban:
dispatch_in_gateway: true # 默认
dispatch_interval_seconds: 60 # 默认

通过 HERMES_KANBAN_DISPATCH_IN_GATEWAY=0 在运行时覆盖配置标志以进行调试。标准网关监督适用:直接运行 hermes gateway start,或将网关配置为 systemd 用户单元(参见网关文档)。没有运行中的网关时,ready 任务会保持原状,直到有一个网关启动 — hermes kanban create 在创建时会对此发出警告。

hermes kanban daemon 作为单独进程运行已弃用;请使用网关。如果你确实无法运行网关(无头主机策略禁止长期运行服务等),一个 --force 逃生舱会在一个发布周期内保持旧的独立守护进程活着,但同时运行网关内嵌调度器和针对同一 kanban.db 的独立守护进程会导致声明竞争,这是不支持的。

幂等创建(用于自动化/Webhook)

# 第一次调用创建任务。使用相同键的任何后续调用
# 返回现有的任务 id,而不是重复创建。
hermes kanban create "nightly ops review" \
--assignee ops \
--idempotency-key "nightly-ops-$(date -u +%Y-%m-%d)" \
--json

批量 CLI 动词

所有生命周期动词都接受多个 id,因此你可以用一条命令清理一批:

hermes kanban complete t_abc t_def t_hij --result "batch wrap"
hermes kanban archive t_abc t_def t_hij
hermes kanban unblock t_abc t_def
hermes kanban block t_abc "need input" --ids t_def t_hij

工作者如何与看板交互

工作者不会通过 shell 执行 hermes kanban 当调度器生成工作者时,它会在子进程环境中设置 HERMES_KANBAN_TASK=t_abcd,该环境变量会在模型 schema 中开启专用的 kanban 工具集 — 七个工具,通过 Python kanban_db 层直接读取和修改看板,与 CLI 的做法相同。运行中的工作者像调用任何其他工具一样调用这些工具;它从来不需要看到或使用 hermes kanban CLI。

工具用途必需参数
kanban_show读取当前任务(标题、正文、之前的尝试、父任务交接、评论、完整格式化的 worker_context)。默认使用环境中的任务 id。
kanban_completesummary + metadata 结构化交接完成任务。summary / result 中至少一个
kanban_blockreason 上报给人类输入。reason
kanban_heartbeat在长时间操作中发信号表示存活。纯副作用。
kanban_comment向任务线程追加持久化备注。task_idbody
kanban_create(编排器)以 assignee、可选 parentsskills 等派生子任务。titleassignee
kanban_link(编排器)事后添加 parent_id → child_id 依赖边。parent_idchild_id

典型的工作者轮次如下:

# 模型的工具调用,按顺序:
kanban_show() # 无参数 — 使用 HERMES_KANBAN_TASK
# (模型读取返回的 worker_context,通过终端/文件工具完成工作)
kanban_heartbeat(note="halfway through — 4 of 8 files transformed")
# (更多工作)
kanban_complete(
summary="migrated limiter.py to token-bucket; added 14 tests, all pass",
metadata={"changed_files": ["limiter.py", "tests/test_limiter.py"], "tests_run": 14},
)

编排器工作者则进行扇出:

kanban_show()
kanban_create(
title="research ICP funding 2024-2026",
assignee="researcher-a",
body="focus on seed + series A, North America, AI-adjacent",
)
# → 返回 {"task_id": "t_r1", ...}
kanban_create(title="research ICP funding — EU angle", assignee="researcher-b", body="…")
# → 返回 {"task_id": "t_r2", ...}
kanban_create(
title="synthesize findings into launch brief",
assignee="writer",
parents=["t_r1", "t_r2"], # 当两者都完成时提升为 ready
body="one-pager, 300 words, neutral tone",
)
kanban_complete(summary="decomposed into 2 research tasks + 1 writer; linked dependencies")

三个"(编排器)"工具 — kanban_createkanban_link 和对外部任务的 kanban_comment — 对每个工作者都可用;约定(由 kanban-orchestrator 技能强制执行)是工作者配置文件不进行扇出,编排器配置文件不执行实现工作。

为什么使用工具而不是 shell 执行 hermes kanban

三个原因:

  1. 后端可移植性。 终端工具指向远程后端(Docker / Modal / Singularity / SSH)的工作者会在容器内运行 hermes kanban complete,而容器里没有安装 hermes,也没有挂载 ~/.hermes/kanban.db。kanban 工具在 Agent 自己的 Python 进程中运行,无论终端后端如何,始终能访问 ~/.hermes/kanban.db
  2. 无 shell 引号脆弱性。 通过 shlex + argparse 传递 --metadata '{"files": [...]}' 是一个潜在的陷阱。结构化工具参数完全绕过了这个问题。
  3. 更好的错误信息。 工具结果是模型可以推理的结构化 JSON,而不是需要解析的 stderr 字符串。

普通会话零 schema 开销。 普通的 hermes chat 会话在其 schema 中有零个 kanban_* 工具。每个工具上的 check_fn 只有在 HERMES_KANBAN_TASK 被设置时才返回 True,而这只在调度器生成此进程时才发生。对于从不使用 kanban 的用户来说,没有工具膨胀。

kanban-workerkanban-orchestrator 技能教会模型何时调用哪个工具以及按什么顺序。

推荐的交接证据

kanban_complete(summary=..., metadata={...}) 有意保持灵活性:summary 是人类可读的收尾,metadata 是机器可读的交接,下游 Agent、审查者或仪表板可以重用,无需解析正文文字。

对于工程和审查任务,推荐使用以下可选的 metadata 格式:

{
"changed_files": ["path/to/file.py"],
"verification": ["pytest tests/hermes_cli/test_kanban_db.py -q"],
"dependencies": ["parent task id or external issue, if any"],
"blocked_reason": null,
"retry_notes": "what failed before, if this was a retry",
"residual_risk": ["what was not tested or still needs human review"]
}

这些键是约定,不是 schema 要求。有用的特性是每个工作者都留下足够的证据,让下一个读者能快速回答四个问题:

  1. 改变了什么?
  2. 如何验证的?
  3. 如果失败,什么可以解除阻塞或重试?
  4. 哪些风险是故意留开的?

metadata 中不要放秘密、原始日志、token、OAuth 材料和无关记录。存储指针和摘要。如果任务没有文件或测试,在 summary 中明确说明,并在 metadata 中放入现有证据,如源 URL、issue id 或手动审查步骤。

worker 技能

任何应该能处理 kanban 任务的配置文件都必须加载 kanban-worker 技能。它通过工具调用(而非 CLI 命令)教给工作者完整的生命周期:

  1. 生成时,调用 kanban_show() 读取标题 + 正文 + 父任务交接 + 之前的尝试 + 完整评论线程。
  2. 通过终端工具 cd $HERMES_KANBAN_WORKSPACE 并在那里工作。
  3. 在长时间操作期间每几分钟调用 kanban_heartbeat(note="...")
  4. kanban_complete(summary="...", metadata={...}) 完成,如果卡住则调用 kanban_block(reason="...")

kanban-worker 是一个捆绑技能,在安装和更新时同步到每个配置文件 — 不需要单独的技能中心安装步骤。验证它是否存在于你用于 kanban 工作者的配置文件中(researcherwriterops 等):

hermes -p <your-worker-profile> skills list | grep kanban-worker

如果捆绑的副本丢失,为该配置文件恢复它:

hermes -p <your-worker-profile> skills reset kanban-worker --restore

调度器在生成每个工作者时也会自动传递 --skills kanban-worker,因此即使配置文件的默认技能配置不包含它,工作者始终有模式库可用。

将额外技能固定到特定任务

有时一个任务需要负责人配置文件默认不具备的专家上下文 — 需要 translation 技能的翻译工作、需要 github-code-review 的审查任务、需要 security-pr-audit 的安全审计。与其每次都编辑负责人的配置文件,不如直接将技能附加到任务上。

从编排器 Agent(通常的情况 — 一个 Agent 将工作路由到另一个),使用 kanban_create 工具的 skills 数组:

kanban_create(
title="translate README to Japanese",
assignee="linguist",
skills=["translation"],
)

kanban_create(
title="audit auth flow",
assignee="reviewer",
skills=["security-pr-audit", "github-code-review"],
)

从人类(CLI / 斜杠命令),为每个技能重复 --skill

hermes kanban create "translate README to Japanese" \
--assignee linguist \
--skill translation

hermes kanban create "audit auth flow" \
--assignee reviewer \
--skill security-pr-audit \
--skill github-code-review

从仪表板,在内联创建表单的 skills 字段中输入技能,以逗号分隔。

这些技能对内置的 kanban-worker附加的 — 调度器为每个技能(以及内置技能)发出一个 --skills <name> 标志,因此工作者生成时加载了所有技能。技能名称必须与负责人配置文件上实际安装的技能匹配(运行 hermes skills list 查看可用的技能);没有运行时安装。

orchestrator 技能

行为良好的编排器不应自己做工作。 它将用户的目标分解为任务,链接它们,将每个分配给专家,然后退出。kanban-orchestrator 技能将此编码为工具调用模式:反诱惑规则、标准专家花名册(researcherwriteranalystbackend-engreviewerops),以及以 kanban_create / kanban_link / kanban_comment 为关键点的分解手册。

典型的编排器轮次(两个并行研究员交接给一个作者):

# 来自用户的目标:"draft a launch post on the ICP funding landscape"
kanban_create(title="research ICP funding, NA angle", assignee="researcher-a", body="…") # → t_r1
kanban_create(title="research ICP funding, EU angle", assignee="researcher-b", body="…") # → t_r2
kanban_create(
title="synthesize ICP funding research into launch post draft",
assignee="writer",
parents=["t_r1", "t_r2"], # 当两个研究员都完成时提升为 'ready'
body="one-pager, neutral tone, cite sources inline",
) # → t_w1
# 可选:事后添加发现的跨切割依赖,无需重新创建任务
kanban_link(parent_id="t_r1", child_id="t_followup")
kanban_complete(
summary="decomposed into 2 parallel research tasks → 1 synthesis task; writer starts when both researchers finish",
)

kanban-orchestrator 是一个捆绑技能,在安装和更新时同步到每个配置文件,因此不需要单独的技能中心安装步骤。验证它是否存在于你的编排器配置文件中:

hermes -p orchestrator skills list | grep kanban-orchestrator

如果捆绑的副本丢失,为该配置文件恢复它:

hermes -p orchestrator skills reset kanban-orchestrator --restore

为了获得最佳结果,将其与工具集限制为看板操作(kanbangatewaymemory)的配置文件配对,这样即使编排器尝试,也无法执行实现任务。

仪表板(GUI)

/kanban CLI 和斜杠命令足以无界面运行看板,但对于人在回路中的场景(分诊、跨配置文件监督、阅读评论线程、在列之间拖动卡片),可视化看板通常是正确的界面。Hermes 将此作为捆绑仪表板插件plugins/kanban/ 中提供 — 不是核心功能,不是单独的服务 — 遵循扩展仪表板中描述的模型。

使用以下命令打开:

hermes kanban init # 一次性:如果 kanban.db 还不存在则创建
hermes dashboard # 导航栏中出现"Kanban"标签,在"Skills"之后

插件提供的功能

  • Kanban 标签页,每个状态显示一列:triagetodoreadyrunningblockeddone(启用切换时还有 archived)。
    • triage 是粗糙想法的停车列,预期由规格说明者来细化。使用 hermes kanban create --triage(或通过 Triage 列的内联创建)创建的任务会落在这里,调度器会让它们保持原状,直到人类或规格说明者将它们提升到 todo / ready。运行 hermes kanban specify <id> 可让辅助 LLM 将 triage 任务扩展为完整规格(标题 + 含目标、方案、验收标准的正文)并一步提升为 todo--all 可批量处理所有 triage 任务。在 config.yamlauxiliary.triage_specifier 下配置使用哪个模型。
  • 卡片显示任务 id、标题、优先级标记、租户标签、分配的配置文件、评论/链接数量、进度药丸(当任务有依赖项时显示 N/M 个子任务已完成),以及"N 前创建"。每张卡片的复选框支持多选。
  • Running 列内的按配置文件分道 — 工具栏复选框切换 Running 列按负责人的子分组。
  • 通过 WebSocket 实时更新 — 插件在短轮询间隔内追踪只追加的 task_events 表;看板在任何配置文件(CLI、网关或另一个仪表板标签)执行操作的瞬间反映变化。重新加载有防抖处理,因此一批事件只触发一次重新获取。
  • 拖放卡片在列之间移动以更改状态。拖放操作发送 PATCH /api/plugins/kanban/tasks/:id,路由通过与 CLI 使用的相同的 kanban_db 代码 — 三个界面永远不会产生偏差。移动到破坏性状态(donearchivedblocked)时会提示确认。触摸设备使用基于指针的回退,因此看板可以从平板电脑使用。
  • 内联创建 — 点击任何列标题上的 + 输入标题、负责人、优先级,以及(可选)从所有现有任务的下拉菜单中选择父任务。从 Triage 列创建会自动将新任务放入分诊状态。
  • 带批量操作的多选 — Shift/Ctrl 点击卡片或勾选其复选框将其添加到选择中。顶部出现一个带有批量状态转换、归档和重新分配(通过配置文件下拉菜单,或"(取消分配)")的操作栏。破坏性批量操作先确认。每 id 的部分失败会被报告而不中止其余操作。
  • 点击卡片(不带 Shift/Ctrl)打开侧边抽屉(Escape 或点击外部关闭),包含:
    • 可编辑标题 — 点击标题重命名。
    • 可编辑负责人/优先级 — 点击元数据行重写。
    • 可编辑描述 — 默认以 Markdown 渲染(标题、粗体、斜体、内联代码、围栏代码、http(s) / mailto: 链接、项目符号列表),带有一个"编辑"按钮切换为文本框。Markdown 渲染是一个小型、防 XSS 的渲染器 — 每次替换都在 HTML 转义的输入上运行,只有 http(s) / mailto: 链接通过,且 target="_blank" + rel="noopener noreferrer" 始终设置。
    • 依赖编辑器 — 父任务和子任务的芯片列表,每个芯片带有 × 取消链接,加上所有其他任务的下拉菜单,用于添加新的父任务或子任务。循环尝试在服务器端被拒绝,并附带清晰的消息。
    • 状态操作行(→ triage / → ready / → running / block / unblock / complete / archive),破坏性转换有确认提示。对于 Triage 列中的卡片,操作行还暴露一个 ✨ Specify 按钮,调用辅助 LLM(config.yaml 中的 auxiliary.triage_specifier)将一行描述扩展为完整规格(标题 + 含目标、方案、验收标准的正文)并将任务提升为 todo。同样的行为可通过 CLI(hermes kanban specify <id> / --all)、任何网关平台(/kanban specify <id>)以及编程方式 POST /api/plugins/kanban/tasks/:id/specify 访问。
    • 结果区(也以 Markdown 渲染)、带 Enter 提交的评论线程、最近 20 个事件。
  • 工具栏过滤器 — 自由文本搜索、租户下拉菜单(默认为 config.yaml 中的 dashboard.kanban.default_tenant)、负责人下拉菜单、"显示已归档"切换、"按配置文件分道"切换,以及推送调度器按钮,让你不必等待下一个 60 秒 tick。

视觉上的目标是熟悉的 Linear / Fusion 布局:深色主题、带计数的列标题、彩色状态点、用于优先级和租户的药丸芯片。插件只读取主题 CSS 变量(--color-*--radius--font-mono 等),因此它会随着任何活跃的仪表板主题自动重新换肤。

架构

GUI 严格是读通数据库 + 写通 kanban_db层,没有任何自己的域逻辑:

┌────────────────────────┐ WebSocket(追踪 task_events)
│ React SPA(插件) │ ◀──────────────────────────────────┐
│ HTML5 拖放 │ │
└──────────┬─────────────┘ │
│ REST over fetchJSON │
▼ │
┌────────────────────────┐ 写入直接调用 kanban_db.* │
│ FastAPI 路由器 │ 与 CLI /kanban 动词使用的 │
│ plugins/kanban/ │ 相同代码路径 │
│ dashboard/plugin_api.py │
└──────────┬─────────────┘ │
│ │
▼ │
┌────────────────────────┐ │
│ ~/.hermes/kanban.db │ ───── 追加 task_events ───────────┘
│ (WAL,共享) │
└────────────────────────┘

REST 接口

所有路由都挂载在 /api/plugins/kanban/ 下,并受仪表板的临时会话 token 保护:

方法路径用途
GET/board?tenant=<name>&include_archived=…按状态列分组的完整看板,加上用于过滤下拉菜单的租户和负责人
GET/tasks/:id任务 + 评论 + 事件 + 链接
POST/tasks创建(包装 kanban_db.create_task,接受 triage: boolparents: [id, …]
PATCH/tasks/:id状态/负责人/优先级/标题/正文/结果
POST/tasks/bulkids 中的每个 id 应用相同的 patch(状态/归档/负责人/优先级)。每 id 失败不中止兄弟任务
POST/tasks/:id/comments追加评论
POST/tasks/:id/specify运行 triage 规格化器 — 辅助 LLM 充实任务正文并将其从 triage 提升为 todo。返回 {ok, task_id, reason, new_title};"非 triage 状态"/无辅助客户端/LLM 错误时 ok=false 并附人类可读原因,返回 200 而非 4xx
POST/links添加依赖(parent_idchild_id
DELETE/links?parent_id=…&child_id=…移除依赖
POST/dispatch?max=…&dry_run=…推送调度器 — 跳过 60 秒等待
GET/configconfig.yaml 读取 dashboard.kanban 偏好设置 — default_tenantlane_by_profileinclude_archived_by_defaultrender_markdown
WS/events?since=<event_id>task_events 行的实时流

每个处理器都是薄包装 — 插件约 700 行 Python(路由器 + WebSocket 追踪 + 批量批处理器 + 配置读取器),不添加任何新的业务逻辑。一个小型 _conn() 辅助函数在每次读写时自动初始化 kanban.db,因此无论用户先打开仪表板、直接访问 REST API 还是运行 hermes kanban init,全新安装都能正常工作。

仪表板配置

~/.hermes/config.yamldashboard.kanban 下的任何这些键都会更改标签页的默认值 — 插件在加载时通过 GET /config 读取它们:

dashboard:
kanban:
default_tenant: acme # 预选租户过滤器
lane_by_profile: true # "按配置文件分道"切换的默认值
include_archived_by_default: false
render_markdown: true # 设置 false 以使用普通 <pre> 渲染

每个键都是可选的,回退到所示的默认值。

安全模型

仪表板的 HTTP 认证中间件明确跳过 /api/plugins/ — 插件路由按设计是未认证的,因为仪表板默认绑定到 localhost。这意味着 kanban REST 界面对主机上的任何进程都是可访问的。

WebSocket 额外采取了一个步骤:它要求将仪表板的临时会话 token 作为 ?token=… 查询参数(浏览器无法在升级请求上设置 Authorization),与浏览器内 PTY 桥使用的模式相同。

如果你运行 hermes dashboard --host 0.0.0.0,每个插件路由 — 包括 kanban — 都可以从网络访问。不要在共享主机上这样做。 看板包含任务正文、评论和工作区路径;访问这些路由的攻击者会获得对你整个协作界面的读取访问权限,还可以创建/重新分配/归档任务。

~/.hermes/kanban.db 中的任务是有意不区分配置文件的(这是协调原语)。如果你用 hermes -p <profile> dashboard 打开仪表板,看板仍然显示主机上任何其他配置文件创建的任务。同一用户拥有所有配置文件,但如果多个角色共存,这一点值得了解。

实时更新

task_events 是一个带有单调 id 的只追加 SQLite 表。WebSocket 端点保存每个客户端最后看到的事件 id,并在新行到达时推送。当一批事件到达时,前端重新加载(非常便宜的)看板端点 — 比尝试从每种事件类型修补本地状态更简单、更正确。WAL 模式意味着读取循环永远不会阻塞调度器的 BEGIN IMMEDIATE 声明事务。

扩展它

插件使用标准的 Hermes 仪表板插件合约 — 参见扩展仪表板获取完整的清单参考、shell 插槽、页面范围插槽和插件 SDK。额外的列、自定义卡片样式、租户过滤布局或完整的 tab.override 替换都可以表达,无需分叉此插件。

要禁用而不删除:在 config.yaml 中添加 dashboard.plugins.kanban.enabled: false(或删除 plugins/kanban/dashboard/manifest.json)。

范围边界

GUI 故意很薄。插件所做的一切都可以从 CLI 访问;插件只是让人类更舒适。自动分配、预算、治理关卡和组织图视图保留在用户空间 — 路由器配置文件、另一个插件,或 tools/approval.py 的重用 — 正如设计规范的超出范围部分所列出的那样。

CLI 命令参考

这是(或脚本、cron、仪表板)用来驱动看板的界面。在调度器内部运行的工作者使用 kanban_* 工具界面进行相同操作 — 这里的 CLI 和那里的工具都通过 kanban_db 路由,因此两个界面在构建上保持一致。

hermes kanban init # 创建 kanban.db + 打印守护进程提示
hermes kanban create "<title>" [--body ...] [--assignee <profile>]
[--parent <id>]... [--tenant <name>]
[--workspace scratch|worktree|dir:<path>]
[--priority N] [--triage] [--idempotency-key KEY]
[--max-runtime 30m|2h|1d|<seconds>]
[--skill <name>]...
[--json]
hermes kanban list [--mine] [--assignee P] [--status S] [--tenant T] [--archived] [--json]
hermes kanban show <id> [--json]
hermes kanban assign <id> <profile> # 或 'none' 以取消分配
hermes kanban link <parent_id> <child_id>
hermes kanban unlink <parent_id> <child_id>
hermes kanban claim <id> [--ttl SECONDS]
hermes kanban comment <id> "<text>" [--author NAME]

# 批量动词 — 接受多个 id:
hermes kanban complete <id>... [--result "..."]
hermes kanban block <id> "<reason>" [--ids <id>...]
hermes kanban unblock <id>...
hermes kanban archive <id>...

hermes kanban tail <id> # 追踪单个任务的事件流
hermes kanban watch [--assignee P] [--tenant T] # 将所有事件实时流到终端
[--kinds completed,blocked,…] [--interval SECS]
hermes kanban heartbeat <id> [--note "..."] # 长时操作的工作者存活信号
hermes kanban runs <id> [--json] # 尝试历史(每次运行一行)
hermes kanban assignees [--json] # 磁盘上的配置文件 + 每负责人任务计数
hermes kanban dispatch [--dry-run] [--max N] # 单次扫描
[--failure-limit N] [--json]
hermes kanban daemon --force # 已弃用 — 独立调度器(使用 `hermes gateway start` 代替)
[--failure-limit N] [--pidfile PATH] [-v]
hermes kanban stats [--json] # 按状态 + 按负责人的计数
hermes kanban log <id> [--tail BYTES] # 来自 ~/.hermes/kanban/logs/ 的工作者日志
hermes kanban notify-subscribe <id> # 网关桥接钩子(由网关中的 /kanban 使用)
--platform <name> --chat-id <id> [--thread-id <id>] [--user-id <id>]
hermes kanban notify-list [<id>] [--json]
hermes kanban notify-unsubscribe <id>
--platform <name> --chat-id <id> [--thread-id <id>]
hermes kanban context <id> # 工作者看到的内容
hermes kanban specify [<id> | --all] [--tenant T] # 将 triage 列想法充实为完整规格
[--author NAME] [--json] # 并提升为 todo
hermes kanban gc [--event-retention-days N] # 工作区 + 旧事件 + 旧日志
[--log-retention-days N]

所有命令也可以在交互式 CLI 中作为斜杠命令使用,以及在消息网关中使用(参见下方的 /kanban 斜杠命令)。

/kanban 斜杠命令

每个 hermes kanban <action> 动词也可以作为 /kanban <action> 访问 — 从交互式 hermes chat 会话内部以及从任何网关平台(Telegram、Discord、Slack、WhatsApp、Signal、Matrix、Mattermost、email、SMS)。两个界面调用完全相同的 hermes_cli.kanban.run_slash() 入口点,该入口点重用 hermes kanban argparse 树,因此参数界面、标志和输出格式在 CLI、/kanbanhermes kanban 之间是相同的。你不必离开聊天来驱动看板。

/kanban list
/kanban show t_abcd
/kanban create "write launch post" --assignee writer --parent t_research
/kanban comment t_abcd "looks good, ship it"
/kanban unblock t_abcd
/kanban dispatch --max 3
/kanban specify t_abcd # 将 triage 一行描述充实为完整规格
/kanban specify --all --tenant engineering # 批量处理某租户内所有 triage 任务

多词参数的引用方式与在 shell 上相同 — run_slashshlex.split 解析行的其余部分,因此 "..."'...' 都可以工作。

运行中使用:/kanban 绕过运行中 Agent 保护

网关通常在 Agent 仍在思考时将斜杠命令和用户消息排队 — 这就是防止你在第一轮进行中时意外开始第二轮的机制。/kanban 明确豁免于此保护。 看板位于 ~/.hermes/kanban.db 中,而不是运行中 Agent 的状态中,因此读取(listshowcontexttailwatchstatsruns)和写入(commentunblockblockassignarchivecreatelink…)都会立即执行,即使在轮次进行中。

这就是分离的全部意义:

  • 工作者阻塞等待同伴 → 你从手机发送 /kanban unblock t_abcd,调度器在下一个 tick 时接手同伴。被阻塞的工作者不会被中断 — 它只是不再被阻塞。
  • 你发现一张需要人工上下文的卡片 → /kanban comment t_xyz "use the 2026 schema, not 2025" 落在任务线程上,该任务的下一次运行将在 kanban_show() 中读取它。
  • 你想知道你的集群在做什么而不停止编排器 → /kanban list --mine/kanban stats 检查看板而不接触你的主对话。

/kanban create 上自动订阅(仅网关)

当你从网关用 /kanban create "…" 创建任务时,发起的聊天(平台 + 聊天 id + 线程 id)会自动订阅该任务的终端事件(completedblockedgave_upcrashedtimed_out)。你每个终端事件都会收到一条消息 — 包括 completed 时工作者结果摘要的第一行 — 而无需轮询或记住任务 id。

you> /kanban create "transcribe today's podcast" --assignee transcriber
bot> Created t_9fc1a3 (ready, assignee=transcriber)
(subscribed — you'll be notified when t_9fc1a3 completes or blocks)

… ~8 分钟后 …

bot> ✓ t_9fc1a3 completed by transcriber
transcribed 42 minutes, saved to podcast/2026-05-04.md

一旦任务达到 donearchived,订阅会自动移除。如果你用 --json(机器输出)脚本化创建,则会跳过自动订阅 — 假设是脚本化调用者希望通过 /kanban notify-subscribe 显式管理订阅。

消息中的输出截断

网关平台有实际的消息长度限制。如果 /kanban list/kanban show/kanban tail 产生超过约 3800 个字符的输出,响应会被截断,并附有 … (truncated; use \hermes kanban …` in your terminal for full output)` 尾注。CLI 界面没有此限制。

自动补全

在交互式 CLI 中,输入 /kanban 并按 Tab 会循环浏览内置子命令列表(listlsshowcreateassignlinkunlinkclaimcommentcompleteblockunblockarchivetaildispatchcontextinitgc)。CLI 参考中列出的其余动词(watchstatsrunslogassigneesheartbeatnotify-subscribenotify-listnotify-unsubscribedaemon)也有效 — 它们只是还不在自动补全提示列表中。

协作模式

看板无需任何新原语即支持这八种模式:

模式形态示例
P1 扇出N 个同级,相同角色"并行研究 5 个角度"
P2 流水线角色链:侦察员 → 编辑 → 作者每日简报组装
P3 投票/法定人数N 个同级 + 1 个汇总者3 个研究员 → 1 个审查者选择
P4 长期运行日志相同配置文件 + 共享目录 + 定时任务Obsidian 保险库
P5 人在回路中工作者阻塞 → 用户评论 → 解除阻塞模糊决策
P6 @提及从正文中内联路由@reviewer look at this
P7 线程范围工作区线程中的 /kanban here每项目网关线程
P8 集群农场一个配置文件,N 个主题50 个社交账户
P9 分诊规格说明者粗糙想法 → triagehermes kanban specify 扩展正文 → todo"把这个单行描述变成规格化任务"

每种模式的实际示例,参见 docs/hermes-kanban-v1-spec.pdf

多租户使用

当一个专家集群服务多个业务时,用租户标记每个任务:

hermes kanban create "monthly report" \
--assignee researcher \
--tenant business-a \
--workspace dir:~/tenants/business-a/data/

工作者收到 $HERMES_TENANT 并按前缀对其记忆写入进行命名空间处理。看板、调度器和配置文件定义都是共享的;只有数据是有范围的。

网关通知

当你从网关(Telegram、Discord、Slack 等)运行 /kanban create … 时,发起的聊天会自动订阅新任务。网关的后台通知器每隔几秒轮询 task_events,并将每个终端事件(completedblockedgave_upcrashedtimed_out)的一条消息发送到该聊天。完成的任务还会发送工作者 --result 的第一行,让你看到结果而无需 /kanban show

你可以从 CLI 显式管理订阅 — 当脚本/定时任务想要通知一个它没有发起的聊天时很有用:

hermes kanban notify-subscribe t_abcd \
--platform telegram --chat-id 12345678 --thread-id 7
hermes kanban notify-list
hermes kanban notify-unsubscribe t_abcd \
--platform telegram --chat-id 12345678 --thread-id 7

一旦任务达到 donearchived,订阅会自动移除;无需清理。

运行记录 — 每次尝试一行

任务是一个逻辑工作单元;运行是一次执行尝试。当调度器声明一个就绪任务时,它在 task_runs 中创建一行,并将 tasks.current_run_id 指向它。当该尝试结束时 — 完成、阻塞、崩溃、超时、生成失败、回收 — 运行行以 outcome 关闭,任务的指针清空。已尝试三次的任务有三行 task_runs 记录。

为什么要两个表而不是直接修改任务:你需要用于实际情况事后分析的完整尝试历史("第二次审查尝试到了批准,第三次合并了"),并且你需要一个干净的地方存放每次尝试的元数据 — 哪些文件改变了、哪些测试运行了、审查者注意到了什么。这些是运行事实,不是任务事实。

运行也是结构化交接所在的地方。当工作者完成任务时(通过 kanban_complete(...)),它可以传递:

  • summary(工具参数)/ --summary(CLI)— 人类交接;放在运行上;下游子任务在其 build_worker_context 中看到它。
  • metadata(工具参数)/ --metadata(CLI)— 运行上的自由格式 JSON 字典;子任务在旁边与摘要一起看到它序列化的内容。
  • result(工具参数)/ --result(CLI)— 放在任务行上的简短日志行(旧字段,向后兼容保留)。

下游子任务读取每个父任务最近完成运行的摘要 + 元数据。重试工作者读取自己任务上的之前尝试(结果、摘要、错误),以便不重复已经失败的路径。

# 工作者实际做的 — 来自 Agent 循环内部的工具调用:
kanban_complete(
summary="implemented token bucket, keys on user_id with IP fallback, all tests pass",
metadata={"changed_files": ["limiter.py", "tests/test_limiter.py"], "tests_run": 14},
result="rate limiter shipped",
)

当你(人类)需要关闭工作者无法完成的任务时,同样的交接可以从 CLI 访问 — 例如被放弃的任务,或者你从仪表板手动标记为完成的任务:

hermes kanban complete t_abcd \
--result "rate limiter shipped" \
--summary "implemented token bucket, keys on user_id with IP fallback, all tests pass" \
--metadata '{"changed_files": ["limiter.py", "tests/test_limiter.py"], "tests_run": 14}'

# 查看重试任务的尝试历史:
hermes kanban runs t_abcd
# # OUTCOME PROFILE ELAPSED STARTED
# 1 blocked worker 12s 2026-04-27 14:02
# → BLOCKED: need decision on rate-limit key
# 2 completed worker 8m 2026-04-27 15:18
# → implemented token bucket, keys on user_id with IP fallback

运行记录在仪表板(抽屉中的运行历史部分,每次尝试一行彩色行)和 REST API 上公开(GET /api/plugins/kanban/tasks/:id 返回 runs[] 数组)。带有 {status: "done", summary, metadata}PATCH /api/plugins/kanban/tasks/:id 将两者都转发到内核,因此仪表板的"标记完成"按钮等效于 CLI。task_events 行携带它们所属的 run_id,因此 UI 可以按尝试分组显示,completed 事件在其有效载荷中嵌入第一行摘要(限制为 400 个字符),使网关通知器无需第二次 SQL 往返即可渲染结构化交接。

批量关闭注意事项。 hermes kanban complete a b c --summary X 被拒绝 — 结构化交接是每次运行的,因此将相同的摘要复制到 N 个任务几乎总是错误的。不带 --summary / --metadata 的批量关闭对于常见的"我完成了一堆管理任务"情况仍然有效。

来自状态更改的回收运行。 如果你在仪表板中将一个运行中的任务从 running 拖走(回到 ready,或直接到 todo),或者归档一个仍在运行的任务,进行中的运行以 outcome='reclaimed' 关闭,而不是被孤立。task_runs 行在 tasks.current_run_idNULL 时始终处于终端状态,反之亦然 — 该不变量在 CLI、仪表板、调度器和通知器之间保持。

从未声明完成的合成运行。 完成或阻塞一个从未被声明的任务(例如,人类从仪表板关闭一个带摘要的 ready 任务,或 CLI 用户运行 hermes kanban complete <ready-task> --summary X)否则会丢失交接。相反,内核插入一个零持续时间运行行(started_at == ended_at),携带摘要/元数据/原因,使尝试历史保持完整。completed / blocked 事件的 run_id 指向该行。

实时抽屉刷新。 当仪表板的 WebSocket 事件流报告用户当前查看的任务有新事件时,抽屉会自动刷新(通过线程到其 useEffect 依赖列表中的每任务事件计数器)。不再需要关闭和重新打开才能看到运行的新行或更新的结果。

向前兼容性

tasks 上有两个可为空的列保留用于 v2 工作流路由:workflow_template_id(此任务所属的模板)和 current_step_key(该模板中活跃的步骤)。v1 内核忽略它们用于路由,但允许客户端写入它们,因此 v2 版本可以添加路由机制而无需另一次 schema 迁移。

事件参考

每次转换都会向 task_events 追加一行。每行携带一个可选的 run_id,使 UI 可以按尝试分组事件。类型分为三个集群,便于过滤(hermes kanban watch --kinds completed,gave_up,timed_out):

生命周期(关于任务作为逻辑单元发生了什么变化):

类型有效载荷时机
created{assignee, status, parents, tenant}任务插入。run_idNULL
promotedtodo → ready,因为所有父任务都达到了 donerun_idNULL
claimed{lock, expires, run_id}调度器原子性地声明了一个 ready 任务用于生成。
completed{result_len, summary?}工作者写入了 --result / --summary 且任务达到 donesummary 是第一行交接(400 个字符限制);完整版本在运行行上。如果 complete_task 在从未声明的任务上以交接字段调用,则合成一个零持续时间运行,使 run_id 仍指向某个东西。
blocked{reason}工作者或人类将任务切换为 blocked。当在从未声明的任务上以 --reason 调用时合成一个零持续时间运行。
unblockedblocked → ready,手动或通过 /unblockrun_idNULL
archived从默认看板隐藏。如果任务仍在运行,携带作为副作用被回收的运行的 run_id

编辑(非转换的人类驱动更改):

类型有效载荷时机
assigned{assignee}负责人更改(包括取消分配)。
edited{fields}标题或正文更新。
reprioritized{priority}优先级更改。
status{status}仪表板拖放直接写入了状态(例如 todo → ready)。当从 running 拖走时携带被回收的运行的 run_id;否则 run_id 为 NULL。

工作者遥测(关于执行过程,而非逻辑任务):

类型有效载荷时机
spawned{pid}调度器成功启动了工作者进程。
heartbeat{note?}工作者在长时间操作期间调用 hermes kanban heartbeat $TASK 发出存活信号。
reclaimed{stale_lock}声明 TTL 在没有完成的情况下到期;任务回到 ready
crashed{pid, claimer}工作者 PID 不再存活,但 TTL 尚未到期。
timed_out{pid, elapsed_seconds, limit_seconds, sigkill}max_runtime_seconds 超出;调度器发送 SIGTERM(然后在 5 秒宽限期后发送 SIGKILL)并重新排队。
spawn_failed{error, failures}一次生成尝试失败(PATH 丢失、工作区无法挂载…)。计数器递增;任务回到 ready 重试。
gave_up{failures, error}连续 N 次 spawn_failed 后触发熔断器。任务以最后一次错误自动阻塞。默认 N = 5;通过 --failure-limit 覆盖。

hermes kanban tail <id> 显示单个任务的这些事件。hermes kanban watch 在整个看板范围内流式传输它们。

超出范围

Kanban 故意是单主机的。~/.hermes/kanban.db 是一个本地 SQLite 文件,调度器在同一台机器上生成工作者。不支持跨两台主机运行共享看板 — "主机 A 上的工作者 X、主机 B 上的工作者 Y"没有协调原语,崩溃检测路径假设 PID 是主机本地的。如果你需要多主机,每台主机运行一个独立的看板,并使用 delegate_task / 消息队列连接它们。

设计规范

完整的设计 — 架构、并发正确性、与其他系统的比较、实施计划、风险、未解决问题 — 在 docs/hermes-kanban-v1-spec.pdf 中。在提交任何行为更改 PR 之前请先阅读它。