看板 — 多代理配置协同工作
想要快速上手? 阅读 看板教程 — 包含四个用户故事(单人开发者、舰队耕作、带重试的角色流水线、熔断器)以及每个场景的仪表盘截图。本页为参考文档;教程提供完整叙事。
Hermes 看板是一个持久化的任务看板,跨所有你的 Hermes 配置共享,允许多个命名代理在无需脆弱的内存内子代理集群的情况下协同工作。每个任务是一行记录在 ~/.hermes/kanban.db 中;每次交接都是一行任何人都可读写的内容;每位工作者都是一个独立的操作系统进程,拥有自己的身份。
两个界面:模型通过工具对话,你通过 CLI 对话
看板有两个入口,均由相同的 ~/.hermes/kanban.db 支撑:
-
代理通过专用的
kanban_*工具集驱动看板 ——kanban_show、kanban_list、kanban_complete、kanban_block、kanban_heartbeat、kanban_comment、kanban_create、kanban_link、kanban_unblock。调度器在启动每个工作者时已将其工具集预加载到其模式中;协调者配置也可显式启用kanban工具集。模型通过直接调用工具来读取和路由任务,而非通过 shell 调用hermes kanban。详见下方 工作者如何与看板交互。 -
你(以及脚本、cron)通过 CLI 的
hermes kanban …、Slash 命令/kanban …或仪表盘驱动看板。这些是供人类和自动化使用的接口——没有背后工具调用模型的参与。
两个界面均通过同一层 kanban_db 路由,因此读取看到一致视图,写入不会产生漂移。本文其余部分以 CLI 示例为主,因为易于复制粘贴,但每个 CLI 命令都有模型所使用的等效工具调用形式。
这是覆盖 delegate_task 无法处理的工作负载的形态:
- 研究分流 —— 并行研究人员 + 分析师 + 写作者,支持人工介入。
- 定时运维 —— 每日例行简报,持续数周积累成日志。
- 数字孪生 —— 持久命名的助手(
inbox-triage、ops-review),随时间累积记忆。 - 工程流水线 —— 分解 → 并行实现于多个工作树 → 审查 → 迭代 → 提交 PR。
- 舰队任务 —— 一名专家管理 N 个对象(50 个社交账号、12 个监控服务)。
关于完整的设计理念、与 Cline Kanban / Paperclip / NanoClaw / Google Gemini Enterprise 的对比分析,以及八种经典协作模式,请参阅仓库中的 docs/hermes-kanban-v1-spec.pdf。
看板 vs. delegate_task
它们外观相似,但并非同一基础原语。
delegate_task | 看板 | |
|---|---|---|
| 形态 | RPC 调用(fork → join) | 持久消息队列 + 状态机 |
| 父级行为 | 阻塞等待子级返回 | 发送后即丢弃,之后不再关注 |
| 子级身份 | 匿名子代理 | 命名配置,具备持久记忆 |
| 可恢复性 | 无 —— 失败即失败 | 可阻塞 → 解除 → 重跑;崩溃 → 重新接管 |
| 人工介入 | 不支持 | 任意时刻可评论或解除阻塞 |
| 每任务代理数 | 一次调用 = 一个子代理 | 任务生命周期中可有多个代理(重试、审查、后续跟进) |
| 审计追踪 | 在上下文压缩时丢失 | 永久存储于 SQLite 的行记录 |
| 协调方式 | 层级化(调用者 → 被调用者) | 平等 —— 任何配置均可读写任意任务 |
一句话区分: delegate_task 是函数调用;看板是工作队列,每一次交接都是一行可供任何配置(或人类)查看和编辑的记录。
使用 delegate_task 当:父代理需要在继续前获取一个简短推理结果,无人工参与,结果将回传至父代理上下文。
使用看板当:工作跨越代理边界,需在重启后仍能存活,可能需要人工输入,可能被不同角色接手,或事后需可发现。
二者共存:一个看板工作者可在运行期间内部调用 delegate_task。
核心概念
- 看板 —— 一个独立的任务队列,拥有自己的 SQLite 数据库、工作区目录和调度循环。单次安装可包含多个看板(例如每个项目、仓库或领域一个);详见下方 看板(多项目)。仅需单一项目流的用户始终使用
default看板,且在本文以外不会看到“看板”一词。 - 任务 —— 一行记录,包含标题、可选正文、一个分配者(配置名称)、状态(
triage | todo | ready | running | blocked | done | archived)、可选租户命名空间、可选幂等性键(用于重试自动化去重)。 - 链接 —— 记录父任务 → 子任务依赖关系的
task_links行。当所有父任务变为done状态时,调度器会提升该子任务。 - 评论 —— 代理间通信协议。代理和人类均可追加评论;当工作者(重新)启动时,会将其作为上下文的一部分读取完整的评论线程。
- 工作区 —— 工作者操作的目录。分为三种类型:
scratch(默认)—— 在~/.hermes/kanban/workspaces/<id>/下的临时目录(非默认看板则为~/.hermes/kanban/boards/<slug>/workspaces/<id>/)。dir:<path>—— 已存在的共享目录(如 Obsidian 库、邮件操作目录、按账号划分的文件夹)。必须为绝对路径。相对路径如dir:../tenants/foo/会被调度器拒绝,因为其路径解析取决于调度器当前的工作目录,这会造成歧义并成为“混淆委派”攻击向量。路径一旦确认即被信任——这是你的盒子、你的文件系统,工作者将以你的 uid 运行。这是受信本地用户威胁模型;看板设计为单主机。worktree—— 位于.worktrees/<id>/下的 git 工作树,用于编码任务。工作端的git worktree add会自动创建它。
- 调度器 —— 一个长期运行的循环,每 N 秒(默认 60 秒)执行一次:回收过期的占用、回收崩溃的工作者(PID 已消失但 TTL 未过期)、提升就绪任务、原子性地占用、启动分配的配置。默认运行在网关内(
kanban.dispatch_in_gateway: true)。一个调度器每轮扫描所有看板;工作者启动时固定了HERMES_KANBAN_BOARD,使其无法访问其他看板。若同一任务连续出现kanban.failure_limit次启动失败(默认值:2),调度器将自动阻塞该任务,并以最后一次错误作为原因——防止因配置不存在、工作区无法挂载等问题导致的反复尝试。 - 租户 —— 看板内的可选字符串命名空间。一个专业团队可为多个业务(
--tenant business-a)服务,通过工作区路径和内存键前缀实现数据隔离。租户是软过滤器;看板才是硬隔离边界。
看板(多项目)
看板允许你将无关的工作流——每个项目、仓库或领域一个——分离为独立的队列。新安装默认只有一个名为 default 的看板(数据库位于 ~/.hermes/kanban.db,用于向后兼容)。仅需一条工作流的用户无需了解看板功能;该特性为可选。
每看板的隔离是绝对的:
- 每个看板拥有独立的 SQLite 数据库(
~/.hermes/kanban/boards/<slug>/kanban.db)。 - 每个看板拥有独立的
workspaces/和logs/目录。 - 为某任务启动的工作者只能看到其看板的任务——调度器会在子进程环境中设置
HERMES_KANBAN_BOARD,且所有工作者可用的kanban_*工具都会读取该变量。 - 不允许跨看板链接任务(保持模式简单;若真需跨项目引用,可使用自由文本提及并手动按 ID 查找)。
从 CLI 管理看板
# See what's on disk. Fresh installs show only "default".
hermes kanban boards list
# Create a new board.
hermes kanban boards create atm10-server \
--name "ATM10 Server" \
--description "Minecraft modded server ops" \
--icon 🎮 \
--switch # optional: make it the active board
# Operate on a specific board without switching.
hermes kanban --board atm10-server list
hermes kanban --board atm10-server create "Restart ATM server" --assignee ops
# Change which board is "current" for subsequent calls.
hermes kanban boards switch atm10-server
hermes kanban boards show # who's active right now?
# Rename the display name (the slug is immutable — it's the directory name).
hermes kanban boards rename atm10-server "ATM10 (Prod)"
# Archive (default) — moves the board's dir to boards/_archived/<slug>-<ts>/.
# Recoverable by moving the dir back.
hermes kanban boards rm atm10-server
# Hard delete — `rm -rf` the board dir. No recovery.
hermes kanban boards rm atm10-server --delete
看板解析顺序(优先级从高到低):
- CLI 命令中显式指定的
--board <slug>。 HERMES_KANBAN_BOARD环境变量(由调度器在启动工作者时设置,因此工作者无法看到其他看板)。~/.hermes/kanban/current—— 由hermes kanban boards switch命令保存的别名。default。别名(Slugs)已验证:仅允许小写字母、数字、连字符和下划线,长度为1-64个字符,必须以字母或数字开头。输入中的大写字母会自动转为小写。
任何其他字符(如斜杠、空格、点号,或..)都会在 CLI 层被拒绝,因此无法通过路径遍历技巧命名 board。
从仪表板管理 board
hermes dashboard → 当存在多个 board(或任意 board 中有任务)时,Kanban 标签页顶部会显示一个 board 切换器;单 board 用户仅看到一个小型的 + New board 按钮,切换器仅在需要时才显示。
- Board 下拉菜单 — 选择当前活跃的 board。你的选择将保存到浏览器的
localStorage中,实现跨刷新持久化,避免终端中正在运行的任务因 CLI 的current指针偏移而中断。 - + 新建 board — 弹出模态框,要求输入别名(slug)、显示名称、描述和图标。可选是否自动切换至新创建的 board。
- 归档 — 仅对非
default的 board 显示。确认后,将该 board 目录移动至boards/_archived/。
所有仪表板 API 端点均接受 ?board=<slug> 进行 board 范围限定。事件 WebSocket 在连接时即绑定到某个 board;UI 中切换 board 会建立与新 board 的全新 WebSocket 连接。
快速入门
以下命令由你(人类)执行,用于设置 board 并创建任务。一旦任务被分配,调度器将启动对应的 profile 作为 worker,此后 模型通过 kanban_* 工具调用驱动任务流程,而非使用 CLI 命令 — 参见 Worker 如何与 board 交互。
# 1. Create the board (you)
hermes kanban init
# 2. Start the gateway (hosts the embedded dispatcher)
hermes gateway start
# 3. Create a task (you — or an orchestrator agent via kanban_create)
hermes kanban create "research AI funding landscape" --assignee researcher
# 4. Watch activity live (you)
hermes kanban watch
# 5. See the board (you)
hermes kanban list
hermes kanban stats
当调度器拾取 t_abcd 并启动 researcher profile 时,该 worker 的模型首先调用 HERMES_KANBAN_TASK=t_abcd 来读取其任务内容。它不会运行 hermes kanban show t_abcd。
内嵌于网关的调度器(默认)
调度器运行在网关进程中,无需额外安装,也无需管理独立服务。只要网关运行,待处理的任务将在下一个周期(默认 60 秒)被拾取。
# config.yaml
kanban:
dispatch_in_gateway: true # default
dispatch_interval_seconds: 60 # default
可通过 HERMES_KANBAN_DISPATCH_IN_GATEWAY=0 在运行时覆盖配置标志以进行调试。标准网关监督机制适用:直接运行 hermes gateway start,或将其配置为 systemd 用户单元(参见网关文档)。若无运行中的网关,ready 任务将原地等待,直到网关启动——hermes kanban create 会在创建任务时发出警告。
单独运行 hermes kanban daemon 的方式已被弃用;请使用网关。若确实无法运行网关(例如主机策略禁止长期服务),提供了一个 --force 逃生通道,可在本版本周期内维持旧的独立守护进程,但同时运行网关内嵌调度器与独立守护进程会导致任务争抢,不被支持。
幂等式创建(适用于自动化 / webhook)
# First call creates the task. Any subsequent call with the same key
# returns the existing task id instead of duplicating.
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
Worker 如何与 board 交互
Worker 不会调用 hermes kanban shell 命令。 当调度器启动 worker 时,会设置 HERMES_KANBAN_TASK=t_abcd 环境变量,该变量激活模型 schema 中的专用 kanban 工具集。同一工具集也对启用了 kanban 的协调者 profile 可用。这些工具通过 Python 的 kanban_db 层直接读写 board,与 CLI 使用相同的底层逻辑。运行中的 worker 以调用普通工具的方式使用它们,根本无需感知或依赖 hermes kanban CLI。
| 工具 | 用途 | 必需参数 |
|---|---|---|
kanban_show | 读取当前任务(标题、正文、先前尝试、父级移交、评论、完整预格式化的 worker_context) | — |
kanban_list | 列出任务摘要,支持按 assignee、status、tenant、归档可见性及数量限制过滤。专用于协调者发现 board 上的工作。 | — |
kanban_complete | 完成任务并提交 summary + metadata 结构化移交 | 至少一个 summary / result |
kanban_block | 升级请求人工介入,附带 reason 说明 | reason |
kanban_heartbeat | 长操作期间发送存活信号,纯副作用 | — |
kanban_comment | 向任务线程追加持久化备注 | task_id、body |
kanban_create | (协调者)分发子任务,附带 assignee,可选 parents、skills 等 | title、assignee |
kanban_link | (协调者)事后添加 parent_id → child_id 依赖关系 | parent_id、child_id |
kanban_unblock | (协调者)将阻塞任务重新移回 ready | task_id |
典型 worker 一次操作如下:
# Model's tool calls, in order:
kanban_show() # no args — uses HERMES_KANBAN_TASK
# (model reads the returned worker_context, does the work via terminal/file tools)
kanban_heartbeat(note="halfway through — 4 of 8 files transformed")
# (more work)
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},
)
一个 协调者 worker 会进行分发:
kanban_show()
kanban_create(
title="research ICP funding 2024-2026",
assignee="researcher-a",
body="focus on seed + series A, North America, AI-adjacent",
)
# → returns {"task_id": "t_r1", ...}
kanban_create(title="research ICP funding — EU angle", assignee="researcher-b", body="…")
# → returns {"task_id": "t_r2", ...}
kanban_create(
title="synthesize findings into launch brief",
assignee="writer",
parents=["t_r1", "t_r2"], # promotes to ready when both complete
body="one-pager, 300 words, neutral tone",
)
kanban_complete(summary="decomposed into 2 research tasks + 1 writer; linked dependencies")
“(协调者)”工具 —— kanban_list、kanban_create、kanban_link、kanban_unblock、kanban_comment(用于外部任务)—— 均通过相同工具集提供。该约定(由 kanban-orchestrator 技能强制执行)规定:worker profile 不应分发无关工作,协调者 profile 不应执行具体实现任务。调度器启动的 worker 仍受任务范围限制,不能修改无关任务。
为何使用工具而非调用 hermes kanban
三个原因:
- 后端可移植性。在远程后端(Docker / Modal / Singularity / SSH)运行的 worker,其终端内部可能没有安装
hermes kanban complete,且hermes未挂载。而 kanban 工具运行在 agent 自身的 Python 进程中,始终能访问~/.hermes/kanban.db,不受终端后端影响。 - 无 shell 转义脆弱性。通过
shlex+argparse传递--metadata '{"files": [...]}'存在潜在陷阱。结构化工具参数完全跳过此环节。 - 更好的错误处理。工具返回的是模型可推理的结构化 JSON,而非需解析的 stderr 字符串。
正常会话零 schema 占用。常规 hermes chat 会话中,模型 schema 里没有任何 kanban_* 工具。每个工具的 check_fn 仅在 HERMES_KANBAN_TASK 设置时返回 True,而这只在调度器启动该进程时发生。未接触 kanban 的用户不会受到工具膨胀影响。
kanban-worker 和 kanban-orchestrator 技能教会模型何时、如何调用哪些工具。
推荐的移交证据格式
kanban_complete(summary=..., metadata={...}) 故意设计灵活:
- 摘要(summary)是供人阅读的收尾说明;
metadata是机器可读的移交信息,可供下游 agent、评审者或仪表板重用,无需抓取自然语言。
对于工程与评审类任务,推荐使用以下可选元数据结构:
{
"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。其核心价值在于:每个 worker 留下的证据足够让下一任读者快速回答四个问题:
- 什么发生了变化?
- 如何验证了变更?
- 若失败,如何解除阻塞或重试?
- 哪些风险是故意保留的?
请勿在 metadata 中包含密钥、原始日志、令牌、OAuth 数据或无关对话记录。应存储指针和摘要。若任务无文件或测试,请在 summary 中明确说明,并使用 metadata 提供存在的证据,如源码 URL、工单 ID 或手动评审步骤。
Worker 技能任何能够处理看板任务的配置文件都必须加载 kanban-worker 技能。该技能教会工作者完整的生命周期操作流程,以 工具调用(tool calls) 为准,而非命令行指令:
- 在启动时,调用
kanban_show()读取标题、正文、父级交接信息、先前尝试记录以及完整的评论线程。 - 通过终端工具调用
cd $HERMES_KANBAN_WORKSPACE进入工作环境并执行任务。 - 在长时间运行的操作中,每隔几分钟调用一次
kanban_heartbeat(note="...")。 - 完成任务时调用
kanban_complete(summary="...", metadata={...});若卡住则调用kanban_block(reason="...")。
kanban-worker 是一个内置技能,在安装和更新时自动同步到每个配置文件中——无需额外的“技能库”安装步骤。请验证您用于看板工人的配置文件中是否包含此技能(如 researcher、writer、ops 等):
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 的安全审计任务。此时无需频繁修改分配者的配置文件,可直接将所需技能附加到任务上。
- 从协调代理发起(常见场景:一个代理向另一个代理分派工作),使用
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 查看可用技能列表);系统不支持运行时安装。
协调代理技能
一个行为规范良好的协调代理不应自行执行任务。它应将用户的最终目标拆解为多个子任务,建立任务间的依赖关系,将每项任务分配给已配置的某个配置文件,并随即退出。kanban-orchestrator 技能以工具调用模式编码了这一行为规范:包含防诱惑规则、Step-0 配置文件发现提示(调度器对未知分配者名称会静默失败,因此协调代理必须确保所有卡片关联的配置文件均真实存在于本地)、以及基于 kanban_create / kanban_link / kanban_comment 的分解手册。
一个典型的协调代理回合示例(两名并行研究员向写作者交接):
# Goal from user: "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"], # promoted to 'ready' when both researchers complete
body="one-pager, neutral tone, cite sources inline",
) # → t_w1
# Optional: add cross-cutting deps discovered later without re-creating tasks
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
为获得最佳效果,建议将其与仅限于看板操作的配置文件搭配使用(如 kanban、gateway、memory),从而确保协调代理即使有意也无法执行具体实现任务。
仪表板(GUI)
/kanban CLI 和斜杠命令足以无头运行看板,但对人工介入场景而言,可视化看板通常是更合适的界面:用于分类处理、跨配置文件监督、阅读评论线程,以及拖拽卡片在列间移动。Hermes 将其作为 内置仪表板插件 提供于 plugins/kanban/ —— 不是核心功能,也不是独立服务,而是遵循 扩展仪表板 中所描述的架构模型。
通过以下方式打开:
hermes kanban init # one-time: create kanban.db if not already present
hermes dashboard # "Kanban" tab appears in the nav, after "Skills"
该插件提供的功能- 一个 看板(Kanban) 页签,每种状态对应一列:triage、todo、ready、running、blocked、done(当开关开启时还包括 archived)。
-
triage是用于存放初步想法的“停放列”。默认情况下(kanban.auto_decompose: true),调度器会自动对落入此列的任务运行 分解器(decomposer) —— 协调器配置文件读取粗略想法,结合你的角色名单(含描述),将任务拆分为一个小型子任务图,并分配给最合适的专家。原任务作为所有子任务的父任务保持活跃,以便协调器在所有子任务完成后唤醒并判断是否完成。通过页面顶部的 编排模式:自动/手动 切换按钮(或设置kanban.auto_decompose: false),可切换至手动模式,在该模式下,待处理任务将停留在原位,直到你点击卡片上的 ⚗ 分解 按钮,或执行hermes kanban decompose <id>命令。对于无需分发(或未配置协调器配置文件)的任务,✨ 指定 按钮会通过相同的 LLM 机制,对单个任务进行规格重写(标题 + 正文,包含目标、方法、验收标准)。详见下方 自动与手动编排。 -
卡片显示任务 ID、标题、优先级徽章、租户标签、指派角色、评论/链接数量、一个 进度徽章(当任务有依赖项时,显示已完成的子任务数),以及“创建于 N 小时前”。每个卡片旁的复选框支持多选。
-
“运行中”列内的按角色分组 —— 工具栏中的复选框可切换是否按指派者对“运行中”列进行子分组。
-
通过 WebSocket 实时更新 —— 插件以短轮询间隔尾随只追加的
task_events表;任何角色(CLI、网关或另一个仪表板标签)的操作都会立即反映在看板上。刷新操作已去抖,因此事件爆发只会触发一次重新获取。 -
拖拽卡片 在列之间移动以更改状态。拖放操作发送
PATCH /api/plugins/kanban/tasks/:id,该请求通过与 CLI 使用的相同kanban_db代码路径路由——三个界面永远不会出现不同步。移入破坏性状态(done、archived、blocked)会弹出确认提示。触控设备使用基于指针的备用方案,确保平板设备也可用。 -
内联创建 —— 点击任意列标题上的
+,即可输入标题、指派者、优先级,以及(可选地)从下拉菜单中选择一个现有任务作为父任务。从“待处理”列创建的任务会自动停靠在待处理区。 -
多选与批量操作 —— 按住 Shift/Ctrl 点击卡片或勾选其复选框以加入选择。顶部会出现批量操作栏,支持批量状态转换、归档和重新指派(通过角色下拉菜单,或“(取消指派)”)。破坏性批量操作需先确认。若存在个别失败,系统会报告失败详情但不会中断其余操作。
-
点击卡片(不按 Shift/Ctrl)可打开侧边抽屉(按 Escape 键或点击外部区域关闭),内容包括:
- 可编辑标题 —— 点击标题即可重命名。
- 可编辑指派者 / 优先级 —— 点击元信息行可修改。
- 可编辑描述 —— 默认以 Markdown 渲染(支持标题、加粗、斜体、行内代码、代码块、
http(s)/mailto:链接、项目符号列表),并提供“编辑”按钮切换为文本域。Markdown 渲染采用轻量级、防 XSS 的引擎——所有替换均基于 HTML 转义输入,仅允许http(s)/mailto:链接通过,且target="_blank"+rel="noopener noreferrer"始终启用。 - 依赖关系编辑器 —— 以芯片列表形式展示父任务与子任务,每个条目均有
×用于解除关联,并提供下拉菜单以添加新的父任务或子任务。循环依赖尝试会在服务端被拒绝,并返回清晰提示。 - 状态操作行(→ 待处理 / → 准备就绪 / → 运行中 / 阻塞 / 取消阻塞 / 完成 / 归档),对破坏性状态变更提供确认提示。对于位于 待处理 列的卡片,该行还提供两个由 LLM 驱动的操作:⚗ 分解 将任务拆分为一个由描述驱动的子任务图,路由至专家角色(协调器驱动路径);✨ 指定 执行单任务规格重写。当 LLM 判断任务不适合分发时,分解操作会回退至指定风格的提升逻辑,因此这是一种严格超集。两者均可通过 CLI(
hermes kanban decompose <id>/specify <id>/--all)、任意网关平台(/kanban decompose <id>)以及通过POST /api/plugins/kanban/tasks/:id/decompose和…/specify编程调用。在config.yaml中配置模型参数,位于auxiliary.kanban_decomposer和auxiliary.triage_specifier下。 - 结果区域(同样以 Markdown 渲染)、评论线程(按 Enter 提交)、最近 20 条事件记录。
-
工具栏筛选器 —— 自由文本搜索、租户下拉菜单(默认值来自
config.yaml)、指派者下拉菜单、“显示已归档”开关、“按角色分列”开关,以及一个 催促调度器 按钮,避免等待下一个 60 秒周期。
视觉设计目标是熟悉的 Linear / Fusion 布局:深色主题、带计数的列头、彩色状态圆点、优先级与租户的胶囊式标签。该插件仅读取主题 CSS 变量(--color-*、--radius、--font-mono 等),因此能自动适配当前仪表板的主题。
自动与手动编排
看板在你将任务拖入“待处理”列后有两种处理方式:
自动(默认) —— kanban.auto_decompose: true。嵌入网关的调度器在每个周期运行 分解器,受 kanban.auto_decompose_per_tick 限制(默认每周期最多 3 个任务),防止大量待处理任务瞬间耗尽辅助 LLM 资源。分解器读取粗略想法,结合已安装的角色及其描述,调用 LLM 生成 JSON 任务图:确定要创建哪些子任务、分配给谁、依赖关系如何。原始待处理任务成为图中所有叶子节点的父任务,因此保持活跃直至整个任务图完成——随后提升至 ready,使指派角色(协调器角色)能够判断完成情况,并在工作未结束时继续添加新任务。这是“丢一个一句话,转身走开”的工作流。
手动 —— kanban.auto_decompose: false。待处理任务将一直停留在待处理列,直到你主动操作。点击卡片上的 ⚗ 分解 按钮,执行 hermes kanban decompose <id>(或 --all),或通过聊天界面使用 /kanban decompose <id> 均可触发。这与分解器引入前的看板行为一致,适用于你需要完全掌控任务执行时机的场景。
可通过看板页面顶部的 编排模式:自动/手动 切换按钮(翡翠绿 = 自动,灰暗 = 手动)或直接编辑 config.yaml 来切换两种模式。两种模式均可共存于 hermes kanban specify —— 该功能仍可用于无需分发的单任务规格重写。
分解器的路由决策依赖于角色描述,这是一个由你通过 hermes profile create --description "..."、hermes profile describe <name> --text "..."、hermes profile describe <name> --auto(LLM 根据角色已安装技能 + 模型自动生成)或在展开的 编排设置面板 中的仪表板角色编辑器设定的每角色标签机制。没有描述的角色仍可在角色名单中出现——它们可按名称路由,但精度较低。分解器 绝不会 将子任务分配给未知角色(assignee=None):当 LLM 选择了未知角色时,子任务将被路由至 kanban.default_assignee(或当前激活的默认角色,若未设置则为空)。
配置选项(全部位于 kanban: 下,置于 ~/.hermes/config.yaml 中):| 键 | 默认值 | 用途 |
|---|---|---|
| auto_decompose | true | 调度器每轮自动运行分解器。 |
| auto_decompose_per_tick | 3 | 每轮调度器中允许的最大分解数量。超出部分将推迟到下一轮。 |
| orchestrator_profile | "" | 拥有分解权限的配置文件。空值 = 回退到当前默认配置文件。 |
| default_assignee | "" | 当 LLM 选择未知配置文件时,子任务的落点位置。空值 = 回退到当前默认配置文件。 |
以及两个辅助 LLM 通道:
| 键 | 用途 |
|---|---|
auxiliary.kanban_decomposer | 生成任务图的模型(由 Decompose 调用)。设置 provider/model 可覆盖主聊天模型。 |
auxiliary.profile_describer | 自动生成配置文件描述的模型(由 hermes profile describe --auto 调用)。 |
架构
GUI 严格作为 读取数据库 + 写入看板数据库 的一层,自身不包含任何领域逻辑:
┌────────────────────────┐ WebSocket (tails task_events)
│ React SPA (plugin) │ ◀──────────────────────────────────┐
│ HTML5 drag-and-drop │ │
└──────────┬─────────────┘ │
│ REST over fetchJSON │
▼ │
┌────────────────────────┐ writes call kanban_db.* │
│ FastAPI router │ directly — same code path │
│ plugins/kanban/ │ the CLI /kanban verbs use │
│ dashboard/plugin_api.py │
└──────────┬─────────────┘ │
│ │
▼ │
┌────────────────────────┐ │
│ ~/.hermes/kanban.db │ ───── append task_events ──────────┘
│ (WAL, shared) │
└────────────────────────┘
REST 接口层
所有路由均挂载在 /api/plugins/kanban/ 下,并通过仪表板的临时会话令牌进行保护:
| 方法 | 路径 | 用途 |
|---|---|---|
GET | /board?tenant=<name>&include_archived=… | 完整看板,按状态列分组,同时提供租户和负责人用于筛选下拉框 |
GET | /tasks/:id | 任务 + 评论 + 事件 + 链接 |
POST | /tasks | 创建任务(封装 kanban_db.create_task,接受 triage: bool 和 parents: [id, …]) |
PATCH | /tasks/:id | 修改状态 / 负责人 / 优先级 / 标题 / 正文 / 结果 |
POST | /tasks/bulk | 对 ids 中每个 ID 应用相同的变更(状态 / 归档 / 负责人 / 优先级)。单个失败不会中断其他操作,而是单独报告 |
POST | /tasks/:id/comments | 追加一条评论 |
POST | /tasks/:id/specify | 执行分类指定器 — 辅助 LLM 填充任务正文并将其从 triage 提升至 todo。返回 {ok, task_id, reason, new_title};若“不在分类阶段”/无辅助客户端/LLM 错误,则返回 200 状态码而非 4xx |
POST | /tasks/:id/decompose | 执行看板分解器 — 辅助 LLM 生成任务图,助手原子性地创建子任务 + 关联根任务 + 切换 triage → todo 状态。返回 {ok, task_id, reason, fanout, child_ids, new_title}。与 /specify 一样采用“LLM 出错也返回 200”的约定 |
GET | /profiles | 列出已安装的配置文件及其描述(供仪表板的配置文件描述编辑器和编排选择器使用) |
PATCH | /profiles/:name | 设置或清除某个配置文件的描述(用户编写内容 — description_auto: false)。返回 {ok, profile, description} |
POST | /profiles/:name/describe-auto | 通过 auxiliary.profile_describer 为配置文件生成描述。持久化保存于 description_auto: true,以便仪表板显示“待审阅”徽章 |
GET | /orchestration | 读取看板编排设置(orchestrator_profile, default_assignee, auto_decompose)及经过回退处理后的实际生效值 |
PUT | /orchestration | 更新 config.yaml 中的一个或多个编排键。验证非空的配置文件名确实存在 |
POST | /links | 添加依赖关系(parent_id → child_id) |
DELETE | /links?parent_id=…&child_id=… | 移除依赖关系 |
POST | /dispatch?max=…&dry_run=… | 推动调度器 — 跳过 60 秒等待时间 |
GET | /config | 从 dashboard.kanban 读取 config.yaml 的偏好设置 — 包括 default_tenant, lane_by_profile, include_archived_by_default, render_markdown |
WS | /events?since=<event_id> | 实时推送 task_events 行的流式更新 |
每个处理器都是一个轻量封装 — 插件本身约 700 行 Python 代码(路由器 + WebSocket 尾部 + 批量处理器 + 配置读取器),未引入新的业务逻辑。一个微小的 _conn() 辅助工具会在每次读写时自动初始化 kanban.db,因此无论用户是先打开仪表板、直接调用 REST API,还是运行 hermes kanban init,新安装都能正常工作。
仪表板配置
在 ~/.hermes/config.yaml 中,dashboard.kanban 下的任意键更改都会影响标签页的默认值 — 插件通过 GET /config 在加载时读取这些配置:
dashboard:
kanban:
default_tenant: acme # preselects the tenant filter
lane_by_profile: true # default for the "lanes by profile" toggle
include_archived_by_default: false
render_markdown: true # set false for plain <pre> rendering
每个键均为可选,若未设置则回退到显示的默认值。
安全模型
仪表板的 HTTP 认证中间件 显式跳过 /api/plugins/ — 插件路由设计为无需认证,因为仪表板默认绑定到本地回环地址。这意味着看板 REST 接口可被主机上的任意进程访问。
WebSocket 额外增加一步:需要在查询参数中提供仪表板的临时会话令牌(浏览器无法在升级请求中设置 Authorization),其模式与浏览器内 PTY 桥接所用一致。
如果你运行 hermes dashboard --host 0.0.0.0,所有插件路由(包括看板)都将对网络开放。请勿在共享主机上执行此操作。 看板包含任务正文、评论和工作区路径;攻击者一旦访问这些接口,即可读取你整个协作空间,并能创建、重新分配或归档任务。
~/.hermes/kanban.db 中的任务故意设计为与配置文件无关(这是协调原语)。如果你以 hermes -p <profile> dashboard 方式打开仪表板,看板仍会显示主机上其他配置文件创建的任务。同一用户拥有所有配置文件,但若存在多个身份共存的情况,这一点值得知晓。
实时更新
task_events 是一个仅追加的 SQLite 表,带有单调递增的 id。WebSocket 端点会记录每个客户端最后看到的事件 ID,并在新行到达时推送。当大量事件涌入时,前端会重新加载(非常廉价的)看板端点 — 比尝试从每种事件类型中修补本地状态更简单、更正确。WAL 模式确保读取循环永远不会阻塞调度器的 BEGIN IMMEDIATE 事务获取。
扩展方式
该插件使用标准的 Hermes 仪表板插件契约 — 详见 扩展仪表板 获取完整清单参考、外壳槽位、页面作用域槽位及插件 SDK。新增列、自定义卡片样式、租户过滤布局,或完全替换 tab.override,均可在不 fork 此插件的情况下实现。
如需禁用而不删除:在 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 # create kanban.db + print daemon hint
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> # or 'none' to unassign
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]
# Bulk verbs — accept multiple ids:
hermes kanban complete <id>... [--result "..."]
hermes kanban block <id> "<reason>" [--ids <id>...]
hermes kanban unblock <id>...
hermes kanban archive <id>...
hermes kanban tail <id> # follow a single task's event stream
hermes kanban watch [--assignee P] [--tenant T] # live stream ALL events to the terminal
[--kinds completed,blocked,…] [--interval SECS]
hermes kanban heartbeat <id> [--note "..."] # worker liveness signal for long ops
hermes kanban runs <id> [--json] # attempt history (one row per run)
hermes kanban assignees [--json] # profiles on disk + per-assignee task counts
hermes kanban dispatch [--dry-run] [--max N] # one-shot pass
[--failure-limit N] [--json]
hermes kanban daemon --force # DEPRECATED — standalone dispatcher (use `hermes gateway start` instead)
[--failure-limit N] [--pidfile PATH] [-v]
hermes kanban stats [--json] # per-status + per-assignee counts
hermes kanban log <id> [--tail BYTES] # worker log from ~/.hermes/kanban/logs/
hermes kanban notify-subscribe <id> # gateway bridge hook (used by /kanban in the gateway)
--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> # what a worker sees
hermes kanban specify [<id> | --all] [--tenant T] # flesh out a triage-column idea
[--author NAME] [--json] # into a full spec and promote to todo
hermes kanban gc [--event-retention-days N] # workspaces + old events + old logs
[--log-retention-days N]
所有命令也可作为交互式 CLI 中的斜杠命令,以及消息网关中的命令(见下方 /kanban 斜杠命令)。
/kanban 斜杠命令
每个 hermes kanban <action> 命令也可通过 /kanban <action> 访问 — 无论是身处交互式 hermes chat 会话中,还是通过任意网关平台(Telegram、Discord、Slack、WhatsApp、Signal、Matrix、Mattermost、电子邮件、短信)。这两个界面调用的是完全相同的 hermes_cli.kanban.run_slash() 入口点,复用相同的 hermes kanban argparse 树结构,因此命令行接口(CLI)、/kanban 和 hermes 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 # flesh out a triage one-liner into a real spec
/kanban specify --all --tenant engineering # sweep every triage task in one tenant
多词参数的引用方式与 shell 相同 — run_slash 会使用 shlex.split 解析整行内容,因此 "..." 和 '...' 都能正常工作。
运行中使用:/kanban 绕过正在运行的代理保护机制
网关通常会在代理仍在思考时将斜杠命令和用户消息排队,以防止你在前一个任务尚未完成时意外启动新一轮。但 /kanban 明确被排除在此保护机制之外。 由于任务板(board)存在于 ~/.hermes/kanban.db 中,而非运行中的代理状态,因此所有读操作(list、show、context、tail、watch、stats、runs)和写操作(comment、unblock、block、assign、archive、create、link,……)均可立即执行,即使在任务中途也无延迟。
这正是分离设计的核心价值所在:
- 当某个工作节点因等待远程节点而阻塞时 → 你从手机发送
/kanban unblock t_abcd,调度器将在下一周期接起该任务。被阻塞的工作节点不会被打断,只是不再处于阻塞状态。 - 你发现某张卡片需要人工判断 →
/kanban comment t_xyz "use the 2026 schema, not 2025"被注入到任务线程中,下一次该任务运行时便会读取这条指令。 - 你想了解你的任务集群当前状态,又不想中断主协调流程 → 使用
/kanban list --mine或/kanban stats检查任务板,完全不影响你的主对话。
在 /kanban create 上自动订阅(仅限网关)
当你通过网关使用 /kanban create "…" 创建任务时,原始聊天(平台 + 聊天 ID + 线程 ID)会自动订阅该任务的终端事件(completed、blocked、gave_up、crashed、timed_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 minutes later …
bot> ✓ t_9fc1a3 completed by transcriber
transcribed 42 minutes, saved to podcast/2026-05-04.md
一旦任务进入 done 或 archived 状态,订阅将自动解除。如果你使用 --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 键,可循环浏览内置子命令列表(list、ls、show、create、assign、link、unlink、claim、comment、complete、block、unblock、archive、tail、dispatch、context、init、gc)。其余在 CLI 参考文档中列出的命令(watch、stats、runs、log、assignees、heartbeat、notify-subscribe、notify-list、notify-unsubscribe、daemon)同样可用 —— 只是尚未加入自动补全提示列表。
协作模式
任务板支持以下八种协作模式,无需引入新原语:
| 模式 | 形态 | 示例 |
|---|---|---|
| P1 并行分发 | N 个同角色子任务 | “并行研究 5 个角度” |
| P2 流水线 | 角色链:侦察 → 编辑 → 写作 | 每日简报生成 |
| P3 投票 / 多数决 | N 个同角色子任务 + 1 个聚合者 | 3 名研究员 → 1 名审稿人选择 |
| P4 长期运行日志 | 同一角色 + 共享目录 + 定时任务 | Obsidian 仓库 |
| P5 人在回路 | 工作节点阻塞 → 用户评论 → 解除阻塞 | 模糊决策场景 |
P6 @mention | 从自然语言中内联路由 | @reviewer look at this |
| P7 线程作用域工作区 | /kanban here 在线程内使用 | 每个项目专属的网关线程 |
| P8 舰队耕作 | 一个角色,N 个目标对象 | 50 个社交账号管理 |
| P9 优先级筛选器 | 初步想法 → triage → hermes 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,将每个终端事件(completed、blocked、gave_up、crashed、timed_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
订阅会在任务达到 done 或 archived 状态后自动移除,无需手动清理。
运行记录 —— 每次尝试对应一行
任务是一个逻辑工作单元;而 运行(run) 是一次执行尝试。当调度器获取一个就绪任务时,会在 task_runs 中创建一行记录,并将 tasks.current_run_id 指向它。当此次尝试结束(成功、阻塞、崩溃、超时、启动失败、被回收)时,运行行将关闭并附带一个 outcome,任务指针也随之清空。一个已尝试三次的任务将拥有三条 task_runs 记录。
为何使用两个表而非直接修改任务?因为你需要 完整的尝试历史 来进行真实世界的复盘分析(例如:“第二次评审尝试获得了批准,第三次完成了合并”),同时还需要一个清晰的位置来存放每次尝试的元数据 —— 哪些文件被更改、运行了哪些测试、评审员记录了哪些发现。这些属于“运行事实”,而非“任务事实”。
运行记录也是 结构化交接 的所在。当一个工作节点完成任务(通过 kanban_complete(...))时,它可以传递以下信息:
summary(工具参数)/--summary(CLI)—— 人工交接;记录在运行中;下游子任务可在其build_worker_context中读取。metadata(工具参数)/--metadata(CLI)—— 自由格式的 JSON 字典,保存在运行记录中;子任务将以序列化形式读取。result(工具参数)/--result(CLI)—— 一段简短的日志行,存于任务行(遗留字段,为兼容性保留)。下游子任务会读取每个父任务最新一次成功运行的摘要和元数据。重试的工作节点会自行查看之前尝试的结果(执行结果、摘要、错误信息),以避免重复走已经失败的路径。
# What a worker actually does — a tool call, from inside the agent loop:
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}'
# Review the attempt history on a retried task:
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[] 数组)。PATCH /api/plugins/kanban/tasks/:id 与 {status: "done", summary, metadata} 同时传递给内核,因此仪表板上的“标记完成”按钮与 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_id 为 NULL 时始终处于终止状态,反之亦然——这一不变性在 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 版本可在无需额外数据库迁移的情况下添加路由机制。
事件参考
每一次状态转换都会向 task_events 追加一条记录。每条记录可选地携带 run_id,以便 UI 按尝试分组。事件类型分为三类,便于过滤(hermes kanban watch --kinds completed,gave_up,timed_out):
生命周期事件(关于任务作为逻辑单元的变化):
| 类型 | 载荷 | 触发时机 |
|---|---|---|
created | {assignee, status, parents, tenant} | 任务首次插入。run_id 为 NULL。 |
promoted | — | 因所有父任务均进入 done 状态而触发。run_id 为 NULL。 |
claimed | {lock, expires, run_id} | 调度器原子性地为启动创建认领了一个 ready 任务。 |
completed | --result / --summary | 工作节点写入 --result / --summary 并使任务进入 done 状态。summary 为第一行交接摘要(400 字符上限);完整版本存储于运行记录行中。若在从未被认领的任务上调用 complete_task 且携带交接字段,则会合成一条零时长运行,确保 run_id 仍能指向有效记录。 |
blocked | {reason} | 工作节点或人类将任务切换至 blocked 状态。在从未被认领的任务上调用且携带 --reason 时,会合成一条零时长运行。 |
unblocked | — | 任务被手动或通过 /unblock 完成。run_id 为 NULL。 |
archived | — | 隐藏于默认看板。若任务仍在运行,则携带因拖拽离开 running 而被回收的运行的 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 则在全板范围内流式传输这些事件。
不在范围内的功能
看板设计为单主机模式。~/.hermes/kanban.db 是本地 SQLite 文件,调度器在同一台机器上启动工作节点。跨两台主机共享看板不受支持——缺乏“主机 A 上的工作节点 X,主机 B 上的工作节点 Y”的协调机制,且崩溃检测路径假设 PID 是主机本地的。如需多主机部署,请为每台主机运行独立的看板,并使用 delegate_task 或消息队列进行桥接。
设计规范
完整的系统设计——包括架构、并发正确性、与其他系统的对比、实施计划、风险评估、待解决问题——详见 docs/hermes-kanban-v1-spec.pdf。提交任何行为变更的 PR 前,请务必阅读该文档。