Kanban — 多代理配置协作
想要操作指南? 阅读 Kanban 教程 —— 包含仪表盘截图的四个用户故事(独立开发者、集群作业、带重试的角色流水线、断路器)。本页是参考文档;教程是叙事。
Hermes Kanban 是一个持久的工单板,在您的所有 Hermes 配置间共享,使多个命名的代理能够在工作中协作,无需脆弱的进程内子代理集群。每个任务都是 ~/.hermes/kanban.db 中的一行;每次交接都是任何人都可以读写的一行;每个工作线程都是一个拥有自己身份的完整 OS 进程。
两个界面:模型通过工具交互,您通过 CLI 交互
工单板有两个前门,都由同一个 ~/.hermes/kanban.db 支持:
- 代理通过专用的
kanban_*工具集驱动工单板 ——kanban_show、kanban_list、kanban_complete、kanban_block、kanban_heartbeat、kanban_comment、kanban_create、kanban_link、kanban_unblock。分发器在生成每个工作线程时已将这些工具置于其架构中;编排者配置也可以显式启用kanban工具集。模型读取任务并通过工具调用来路由任务。工具调用是整个工作线程生命周期的主要交互方式。 - 您通过 CLI、斜杠命令和 Web 仪表盘驱动工单板 ——
hermes kanban <verb>、/kanban <verb>、以及仪表盘的 Kanban 标签页。同样的kanban_db代码路径,同样的数据。
自动分类(Triage Specifier)
“分类细化器(Triage Specifier)“是一个辅助 LLM 步骤,它将粗略的想法(“重构限制器”)转换为带有目标、方法和验收标准的完整规范。它可以在分类列中的任务上触发(仪表盘上的 ✨ Specify 按钮,或 hermes kanban specify <id>),并直接将任务提升到待办!两个辅助任务控制此功能:auxiliary.kanban_decomposer(将任务扇出为子任务图)和 auxiliary.triage_specifier(为不需要扇出的任务细化规范)。有关更多详情,请参见自动与手动编排部分。
hermes kanban decompose
hermes kanban decompose 是分类细化器的编排对应物:它调用 auxiliary.kanban_decomposer 辅助 LLM 来读取粗略的想法、查看您的已安装配置及其描述,并生成一个 JSON 任务图——创建哪些子任务、分配给谁、以及哪些依赖哪些。原始的分类任务成为图中每个叶子节点的父节点,因此它保持存活直到整个图完成——然后编排者配置文件(或 kanban.default_assignee)获得一次判断的机会,以在完成之前添加更多任务。
与分类细化器一样,hermes kanban decompose 可以从仪表盘、CLI 和任何网关平台调用。当 kanban.auto_decompose: true 时,分发器也会在每个周期自动分解分类任务。有关相关配置和预期行为的完整说明,请参见自动与手动编排。
数据模型
工单板模型围绕六个状态和一个审计跟踪构建。
任务状态
triage → todo → ready → running → blocked
↓
done / archived
| 状态 | 含义 |
|---|---|
| triage(分类) | 粗略想法。尚未细化或分配。AI 可以自动分解或细化;人类可以手动操作 |
| todo(待办) | 已定义但等待依赖项或分配 |
| ready(就绪) | 已分配并等待分发器认领 |
| running(运行中) | 工作线程正积极处理 |
| blocked(阻塞) | 工作线程需要人工输入,或断路器跳闸 |
| done(完成) | 工作完成 |
| archived(已归档) | 从默认视图中隐藏 |
当父任务达到 done 时,待办任务自动提升到就绪状态。
任务字段
每个任务包含:
| 字段 | 类型 | 说明 |
|---|---|---|
id | str | t_ 前缀 + 8 字符 hex |
title | str | 人类可读标题 |
body | str | null | 长描述(Markdown) |
assignee | str | null | 分配给哪个 Hermes 配置 |
status | enum | 上述状态之一 |
priority | int | 1(中等)或 2(高)或 3(紧急);0(未设置)。CLI 上没有 --priority 0 标志——省略它即可 |
tenant | str | null | 租户命名空间(可选——用于多租户设置) |
parent_id | str | null | 父任务的 id |
children | list[str] | 依赖此任务的任务列表 |
workspace | str | null | 工作空间类型:scratch(临时)、worktree(git 工作树)、dir:<path>(特定目录) |
skills | list[str] | 加载到此任务工作线程中的额外技能 |
max_runtime_seconds | int | null | 每运行最大挂钟秒数 |
max_retries | int | null | 覆盖默认的电路破坏者限制 |
tags | list[str] | 用于搜索和过滤的自由文本标签 |
idempotency_key | str | null | (自动化)去重键 |
current_run_id | str | null | 指向 task_runs |
result | str | null | 最终结果摘要(从运行的摘要中更新) |
审计跟踪
每次状态更改都会在追加到 task_events 表的一行中捕获,带有序号 id、task_id、kind、特定于种类的 payload JSON、created_at 时间戳以及指向 task_runs 的可选 run_id。这意味着工单板提供完整的运行历史——不仅仅是当前状态。
工作空间
--workspace 标志控制分发器在工作线程上设置 $HERMES_KANBAN_WORKSPACE 的位置:
| 值 | 行为 |
|---|---|
scratch | 在 $HERMES_KANBAN_WORKSPACES_ROOT/<id>/ 创建临时目录(默认) |
worktree | 如果任务有 --branch,创建 git 工作树;否则回退到 scratch |
worktree:<path> | 在指定路径创建 git 工作树 |
dir:<path> | 使用现有目录——工作线程直接在那里运行 |
--branch 仅在 worktree 模式中有效;没有 --branch 的 worktree 回退到临时目录。
分板(多项目)
如果要隔离多个独立队列,请创建一个新分板:
hermes kanban init my-project
hermes kanban --board my-project create "..." --assignee worker每个分板都有自己的 SQLite 数据库在 ~/.hermes/kanban/boards/<slug>/kanban.db,自己的 CLI 前缀,自己的分发器实例,以及自己的仪表盘标签页。默认分板在 ~/.hermes/kanban.db 中。
网关嵌入式分发器
分发器现在内置于网关进程中,作为后台协程运行。当网关运行时,它会:
- 在可配置的间隔(
kanban.dispatch_interval_seconds,默认 60 秒)轮询ready任务 - 将每个可用任务分配给其被指派的配置
- 使用
hermes -p <profile> chat -q <prompt>生成子进程(或当hermes不在 PATH 上时使用等效的模块形式) - 通过
kanban_show等工具监控工作线程的完成情况 - 处理重试和故障
hermes gateway start # 🔀 分发器作为嵌入式看板守护进程运行将 hermes kanban daemon 作为独立进程运行已弃用;请改用网关。如果您确实无法运行网关(无头主机策略禁止长时间运行的服务等),--force 逃生舱口使旧的独立守护进程再存活一个发布周期,但对同一 kanban.db 同时运行网关嵌入式分发器和独立守护进程会导致认领竞争,不受支持。
幂等创建(用于自动化/webhooks)
# 首次调用创建任务。后续任何使用相同键的调用
# 返回现有任务 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,并且该环境变量会在模型的架构中启用专用的 kanban 工具集。同一工具集也可用于在其 toolsets 配置中启用了 kanban 的编排者配置。这些工具直接通过 Python kanban_db 层读取和修改工单板,与 CLI 相同。运行中的工作线程像调用任何其他工具一样调用这些工具;它永远不会看到或需要 hermes kanban CLI。
| 工具 | 用途 | 必需参数 |
|---|---|---|
kanban_show | 读取当前任务(标题、正文、先前尝试、父任务交接、评论、完整的预格式化 worker_context)。默认为环境中的任务 id。 | — |
kanban_list | 使用 assignee、status、tenant、归档可见性和限制过滤器列出任务摘要。用于编排者发现工单板工作。 | — |
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 |
一个典型的工作线程轮次看起来像:
# 模型的工具调用,按顺序:
kanban_show() # 无参数——使用 HERMES_KANBAN_TASK
# (模型读取返回的 worker_context,通过 terminal/file 工具工作)
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",
)
# → 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"], # 两者都完成时提升到 ready
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 技能强制执行)是工作线程配置不扇出或路由不相关的工作,编排者配置不执行实现工作。分发器生成的工作线程对于破坏性生命周期操作仍是任务作用域的,不能修改不相关的任务。
为什么用工具而不是 shell 到 hermes kanban
三个原因:
- 后端可移植性。 工作线程的 terminal 工具指向远程后端(Docker / Modal / Singularity / SSH)会在容器内部运行
hermes kanban complete,那里没有安装hermes,也没有挂载~/.hermes/kanban.db。kanban 工具在代理自己的 Python 进程中运行,无论 terminal 后端如何,始终能到达~/.hermes/kanban.db。 - 无 shell 引用脆弱性。 通过 shlex + argparse 传递
--metadata '{"files": [...]}'是潜在的隐患。结构化工具参数完全避免了它。 - 更好的错误信息。 工具结果是模型可以推理的结构化 JSON,而不是它必须解析的 stderr 字符串。
正常会话上零架构占用。 常规的 hermes chat 会话在其架构中有零个 kanban_* 工具,除非活动配置为编排工作显式启用了 kanban 工具集。分发器生成的任务工作线程获得任务作用域的工具,因为设置了 HERMES_KANBAN_TASK;编排者配置通过配置获得更广泛的路由界面。从不接触 kanban 的用户没有工具膨胀。
kanban-worker 和 kanban-orchestrator 技能教模型何时调用哪个工具以及按什么顺序调用。
推荐的交接证据
kanban_complete(summary=..., metadata={...}) 有意保持灵活:summary 是人类可读的结束语,metadata 是下游代理、审查者或仪表盘可以重用而无需解析散文的机器可读交接。
对于工程和审查任务,首选此可选元数据结构:
{
"changed_files": ["path/to/file.py"],
"verification": ["pytest tests/hermes_cli/test_kanban_db.py -q"],
"dependencies": ["父任务 ID 或外部 issue(如果有)"],
"blocked_reason": null,
"retry_notes": "之前失败的原因(如果是重试)",
"residual_risk": ["未测试或仍需人工审查的内容"]
}这些键是约定,而不是架构要求。有用的属性是每个工作线程留下足够的证据供下一个阅读者快速回答四个问题:
- 改了什么?
- 如何验证的?
- 如果失败,什么可以解除阻塞或重试?
- 还有哪些风险被故意保留?
将秘密、原始日志、令牌、OAuth 材料和无关转录保留在 metadata 之外。存储指针和摘要。如果任务没有文件或测试,在 summary 中明确说明,并使用 metadata 存放确实存在的证据,例如源 URL、issue ID 或人工审查步骤。
工作线程技能
任何应该能够处理 kanban 任务的配置必须加载 kanban-worker 技能。它教工作线程以工具调用而非 CLI 命令的方式处理完整生命周期:
- 生成时,调用
kanban_show()读取标题 + 正文 + 父任务交接 + 先前尝试 + 完整评论线程。 cd $HERMES_KANBAN_WORKSPACE(通过 terminal 工具)并在那里工作。- 在长时间操作期间每几分钟调用一次
kanban_heartbeat(note="...")。如果您的工作可能运行超过 1 小时,请至少每小时调用一次kanban_heartbeat——分发器会回收已在kanban.dispatch_stale_timeout_seconds(默认 4 小时)内运行且在过去一小时内无心跳的任务,假定工作线程已崩溃而未清理。回收是无害的(任务回到ready重新分发,不增加失败计数器),但您会丢失当前运行的进度。 - 用
kanban_complete(summary="...", metadata={...})完成,或用kanban_block(reason="...")如果卡住了。
最后的 kanban_complete / kanban_block 调用是工作线程协议的一部分。如果工作线程在任务仍为 running 状态时以退出码 0 退出,分发器会将其视为协议违规,发出 protocol_violation 事件,并在下一周期自动阻塞任务而不是重新生成它进入同一循环。这通常意味着模型写了纯文本答案并在未使用 Kanban 工具界面的情况下退出。
kanban-worker 是一个捆绑技能,在安装和更新时同步到每个配置——没有单独的 Skills Hub 安装步骤。验证它出现在您用于 kanban 工作线程的任何配置中(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 技能将其编码为工具调用模式:抗诱惑规则、第 0 步配置发现提示(分发器会在未知的分配者名称上静默失败,因此编排者必须将每张卡片基于您机器上实际存在的配置)、以及以 kanban_create / kanban_link / kanban_comment 为键的分解操作手册。
一个典型的编排者轮次(两名并行研究人员交接给一名作者):
# 用户目标:"起草一篇关于 ICP 投资格局的发布文章"
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 是一个捆绑技能。它在安装和更新时同步到每个配置,因此没有单独的 Skills Hub 安装步骤。验证它出现在您的编排者配置中:
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 # 一次性:如果尚未存在则创建 kanban.db
hermes dashboard # "Kanban" 标签页出现在导航中,在 "Skills" 之后插件提供的内容
- 一个 Kanban 标签页,每状态一列:
triage、todo、ready、running、blocked、done(以及archived,当切换打开时)。triage是粗略想法的停车列。默认情况下(kanban.auto_decompose: true),分发器会自动对落在此处的任务运行分解器——编排者配置读取粗略想法,查看您的配置名册(带描述),并将任务扇出到路由到最合适的专业人员的子任务小图。原始任务保持存活作为每个子任务的父级,因此当所有任务完成时编排者会再次唤醒以判断完成。翻转页面顶部的 Orchestration: Auto/Manual 药丸(或设置kanban.auto_decompose: false)切换到手动模式,在此模式下分类任务保持不动,直到您点击卡片上的 ⚗ Decompose 或运行hermes kanban decompose <id>。对于不需要扇出的任务(或没有编排者配置的设置),✨ Specify 按钮通过相同的 LLM 机制进行单任务规范重写(带有目标、方法和验收标准的标题 + 正文)。参见下面的自动与手动编排。
- 卡片显示任务 id、标题、优先级徽章、租户标签、分配的配置、评论/链接计数、进度药丸(当任务有依赖项时
N/M children done)和”创建于 N 前”。每张卡片的复选框支持多选。 - Running 内部的每配置通道——工具栏复选框切换 Running 列按分配者的子分组。
- 通过 WebSocket 进行实时更新——插件在短轮询间隔上追踪追加式的
task_events表;工单板在任意配置(CLI、网关或另一个仪表盘标签页)采取行动时立即反映更改。重新加载是去抖的,因此事件爆发触发一次重新获取。 - 各列之间的拖放以更改状态。拖放发送
PATCH /api/plugins/kanban/tasks/:id,通过与 CLI 相同的kanban_db代码路由——三个界面永远不会漂移。移入破坏性状态(done、archived、blocked)会提示确认。触屏设备使用基于指针的回退,因此工单板在平板电脑上也可用。 - 内联创建——点击任意列标题上的
+输入标题、分配者、优先级和(可选)从现有任务下拉列表中选择的父任务。按 Enter 创建任务,Shift+Enter 在标题字段中插入换行,Escape 取消。从 Triage 列创建会自动将新任务停在分类中。 - 多选与批量操作——Shift/ctrl 点击卡片或勾选其复选框将其添加到选择中。批量操作栏出现在顶部,具有批量状态转换、归档和重新分配(按配置下拉,或”(unassign)”)。破坏性批次先确认。每 ID 的部分失败会报告而不中止其余部分。
- 点击一张卡片(不带 shift/ctrl)打开侧边抽屉(Escape 或点击外部关闭):
- 可编辑标题——点击标题重命名。
- 可编辑分配者/优先级——点击元数据行重写。
- 可编辑描述——默认以 Markdown 渲染(标题、粗体、斜体、内联代码、围栏代码、
http(s)/mailto:链接、项目符号列表),带有在 textarea 中编辑的”编辑”按钮。Markdown 渲染是一个微小的、XSS 安全的渲染器——每次替换在 HTML 转义的输入上运行,只有http(s)/mailto:链接通过,并且始终设置target="_blank"+rel="noopener noreferrer"。 - 依赖编辑器——父任务和子任务的芯片列表,每个带
×以取消链接,以及在其他所有任务上的下拉列表以添加新父任务或子任务。循环尝试在服务端被拒绝并附带明确消息。 - 状态操作行(→ triage / → ready / → running / 阻塞 / 取消阻塞 / 完成 / 归档)带有破坏性转换的确认提示。对于 Triage 列中的卡片,该行还暴露两个 LLM 驱动的操作:⚗ Decompose 将任务扇出到按描述路由到专业配置的子任务图(编排者驱动路径),✨ Specify 进行单任务规范重写。当 LLM 认为任务不需要扇出时,Decompose 会回退到 specify 风格的提升,因此它是严格的超集。两者都可通过 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中的dashboard.kanban.default_tenant)、分配者下拉、“显示已归档”切换、“按配置分通道”切换、催促分发器按钮,这样您就不必等待下一个 60 秒周期。
视觉上,目标是熟悉的 Linear / Fusion 布局:深色主题、带计数的列标题、彩色的状态点、优先级和租户的药丸芯片。插件只读取主题 CSS 变量(--color-*、--radius、--font-mono 等),因此它会随着活动仪表盘主题自动重新换肤。
自动与手动编排
kanban 工单板有两种方式处理您放入 Triage 列的任务:
自动(默认)——kanban.auto_decompose: true。网关嵌入式分发器在每个周期运行分解器,由 kanban.auto_decompose_per_tick(默认每周期 3 个任务)限制,因此分类任务的批量加载不会爆发式消耗辅助 LLM。分解器读取粗略想法,查看您已安装的配置及其描述,并要求 LLM 生成 JSON 任务图:创建哪些子任务、分配给谁、以及哪些依赖哪些。原始分类任务成为图中每个叶子节点的父节点,因此它保持存活直到整个图完成——然后提升回 ready,以便其分配者(编排者配置)可以判断完成并在工作未完成时添加更多任务。这就是”扔一行描述,走开”的流程。
手动——kanban.auto_decompose: false。分类任务保持在分类中,直到您采取行动。点击卡片上的 ⚗ Decompose 按钮,运行 hermes kanban decompose <id>(或 --all),或从聊天中使用 /kanban decompose <id>。这与分解器出现之前的工单板行为匹配,当您想要完全控制什么何时运行时很有用。
从 kanban 页面顶部的 Orchestration: Auto/Manual 药丸(翠绿色 = 自动,柔灰色 = 手动)或直接编辑 config.yaml 在两种模式之间切换。两种模式都与 hermes kanban specify 共存——当您不想扇出时,它仍然可以作为单任务规范重写使用。
分解器的路由决策取决于配置描述,这是一个每配置标签原语,您可以通过 hermes profile create --description "..."、hermes profile describe <name> --text "..."、hermes profile describe <name> --auto(从配置已安装的技能 + 模型 LLM 生成)或仪表盘扩展的 Orchestration settings 面板中的每配置编辑器设置。没有描述的配置仍然出现在名册中——它们可以按名称路由,只是不那么精确。分解器从不将子任务落在 assignee=None:当 LLM 选择一个未知配置时,子任务被路由到 kanban.default_assignee(或如果未设置则路由到活动的默认配置)。
配置旋钮(全部在 ~/.hermes/config.yaml 中的 kanban: 下):
| 键 | 默认值 | 用途 |
|---|---|---|
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 严格是一个读取数据库 + 通过 kanban_db 写入的层面,没有自己的领域逻辑:
┌────────────────────────┐ WebSocket(追踪 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。每 ID 部分失败会报告而不中止兄弟姐妹 |
POST | /tasks/:id/comments | 追加评论 |
POST | /tasks/:id/specify | 运行分类细化器——辅助 LLM 充实任务正文并将其从 triage 提升到 todo。返回 {ok, task_id, reason, new_title};ok=false 附带人类可读原因(“不在分类中”/无辅助客户端/LLM 错误)是 200,不是 4xx |
POST | /tasks/:id/decompose | 运行 kanban 分解器——辅助 LLM 生成任务图,helper 原子地创建子任务 + 链接根任务 + 翻转 triage → todo。返回 {ok, task_id, reason, fanout, child_ids, new_title}。与 /specify 相同的 200-on-LLM-error 约定 |
GET | /profiles | 列出已安装的配置及其描述(由仪表盘的配置描述编辑器和编排者选择器消费) |
PATCH | /profiles/:name | 设置或清除配置的描述(用户撰写——description_auto: false)。返回 {ok, profile, description} |
POST | /profiles/:name/describe-auto | 通过 auxiliary.profile_describer 为配置生成描述。以 description_auto: true 持久化,以便仪表盘可以显示”审查”徽章 |
GET | /orchestration | 读取 kanban 编排设置(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 | 从 config.yaml 读取 dashboard.kanban 偏好——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 # 预选择租户过滤器
lane_by_profile: true # "按配置分通道"切换的默认值
include_archived_by_default: false
render_markdown: true # 设置为 false 以使用纯 <pre> 渲染每个键是可选的,回退到所示的默认值。
安全模型
仪表盘的 HTTP 认证中间件显式跳过 /api/plugins/——插件路由默认无认证,因为仪表盘绑定到 localhost。这意味着 kanban REST 接口可以从主机上的任何进程访问。
WebSocket 采取额外一步:它需要仪表盘的临时会话令牌作为 ?token=… 查询参数(浏览器无法在升级请求上设置 Authorization),与浏览器内 PTY 桥使用的模式匹配。
如果您运行 hermes dashboard --host 0.0.0.0,每个插件路由——包括 kanban——都变得可从网络访问。不要在共享主机上这样做。 工单板包含任务正文、评论和工作空间路径;攻击者到达这些路由可以读取您的整个协作界面,还可以创建/重新分配/归档任务。
~/.hermes/kanban.db 中的任务有意保持配置无关(这就是协调原语)。如果您用 hermes -p <profile> dashboard 打开仪表盘,工单板仍然显示主机上任何其他配置创建的任务。同一个用户拥有所有配置,但如果有多个身份共存,这一点值得了解。
实时更新
task_events 是一个仅追加的 SQLite 表,具有单调递增的 id。WebSocket 端点持有每个客户端最后看到的事件 id,并在新行到达时推送它们。当事件爆发到达时,前端重新加载(非常便宜的)工单板端点——比尝试从每种事件类型修补本地状态更简单、更正确。WAL 模式意味着读取循环永远不会阻塞分发器的 BEGIN IMMEDIATE 认领事务。
扩展它
插件使用标准 Hermes 仪表盘插件契约——参见扩展仪表盘获取完整清单参考、外壳插槽、页面作用域插槽和 Plugin SDK。无需 fork 此插件即可表达额外的列、自定义卡片样式、租户过滤布局或完整的 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|worktree:<path>|dir:<path>]
[--branch <name>]
[--priority N] [--triage] [--idempotency-key KEY]
[--max-runtime 30m|2h|1d|<seconds>]
[--max-retries N]
[--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] # 充实分类列中的想法
[--author NAME] [--json] # 到完整规范并提升到 todo
hermes kanban gc [--event-retention-days N] # 工作空间 + 旧事件 + 旧日志
[--log-retention-days N]
所有命令也可作为交互式 CLI 和消息网关中的斜杠命令使用(见下方 /kanban 斜杠命令)。
--max-retries 是分发器的每任务断路器覆盖。--max-retries 1 在首次不成功尝试时阻塞任务,而 --max-retries 3 允许两次重试并在第三次失败时阻塞。省略它以使用 config.yaml 中的 kanban.failure_limit,然后是内置默认值。
/kanban 斜杠命令 {#kanban-slash-command}
每个 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、/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 # 充实分类一行描述为完整规范
/kanban specify --all --tenant engineering # 清扫一个租户中的所有分类任务
多词参数用引号括起来,就像在 shell 上一样——run_slash 用 shlex.split 解析行的其余部分,因此 "..." 和 '...' 都有效。
运行中使用:/kanban 绕过运行中代理守卫
网关通常会排队斜杠命令和用户消息,而代理仍在思考——这就是阻止您在第一次飞行中意外启动第二轮的原因。/kanban 明确豁免于此守卫。 工单板存在于 ~/.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_show()中读取它。 - 您想知道您的集群在做什么而不停止编排者 →
/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 分钟后 …
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 长时间运行日志 | 相同配置 + 共享目录 + cron | Obsidian 保险库 |
| P5 人工参与 | 工作线程阻塞 → 用户评论 → 取消阻塞 | 模棱两可的决策 |
P6 @提及 | 从散文中内联路由 | @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 显式管理订阅——当脚本/cron 作业想要通知非其发起的聊天时很有用:
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,订阅会自动移除;无需清理。
运行——每尝试一行
任务是逻辑工作单元;运行是执行它的一次尝试。当分发器认领一个就绪任务时,它在 task_runs 中创建一行并将 tasks.current_run_id 指向它。当该次尝试结束时——完成、阻塞、崩溃、超时、生成失败、回收——运行行以一个 outcome 关闭,任务的指针清除。一个被尝试过三次的任务有三行 task_runs。
为什么需要两张表而不是仅仅改变任务:您需要完整的尝试历史用于真实世界的事后分析(“第二次审查尝试获得了批准,第三次合并了”),并且您需要一个干净的地方来挂载每尝试元数据——更改了哪些文件、运行了哪些测试、审查者注意到了哪些发现。这些是运行事实,不是任务事实。
运行也是结构化交接存在的地方。当工作线程完成任务时(通过 kanban_complete(...)),它可以传递:
summary(工具参数)/--summary(CLI)——人工交接;放在运行上;下游子任务在其build_worker_context中看到它metadata(工具参数)/--metadata(CLI)——运行上的自由格式 JSON 字典;子任务看到它与摘要一起序列化result(工具参数)/--result(CLI)——放在任务行上的短日志行(遗留字段,为了向后兼容保留)
下游子任务为每个父任务读取最近完成的运行的摘要 + 元数据。重试的工作线程读取其自己任务上的先前尝试(结果、摘要、错误),因此它们不会重复已经失败的路径。
# 工作线程实际做的——来自代理循环内部的工具调用:
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[] 数组)。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' 关闭,而不是成为孤儿。当 tasks.current_run_id 为 NULL 时,task_runs 行始终处于终止状态,反之亦然——这个不变性在 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 | — | todo → ready,因为所有父任务都命中 done。run_id 为 NULL |
claimed | {lock, expires, run_id} | 分发器原子性地认领了一个 ready 任务以进行生成 |
completed | {result_len, summary?} | 工作线程写了 --result / --summary 且任务命中 done。summary 是第一行交接(400 字符上限);完整版存在于运行行上。如果在从未认领的任务上调用 complete_task 并带有交接字段,会合成一个零持续时间运行,因此 run_id 仍然指向某物 |
blocked | {reason} | 工作线程或人类将任务翻转为 blocked。当在从未认领的任务上调用并带有 --reason 时,合成一个零持续时间运行 |
unblocked | — | blocked → ready,手动或通过 /unblock。run_id 为 NULL |
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)并重新排队 |
stale | {elapsed_seconds, last_heartbeat_at, heartbeat_age_seconds, timeout_seconds, pid, terminated} | 任务运行时间超过 kanban.dispatch_stale_timeout_seconds(默认 4 小时)且过去一小时内没有 kanban_heartbeat 到达。分发器 SIGTERM 主机本地工作线程(如果有),将任务重置为 ready 以重新分发。不增加失败计数器(stale 是分发器端缺席检测,而非工作线程故障)。运行长时间操作的工作线程应至少每小时调用一次 kanban_heartbeat 以避免此情况 |
respawn_guarded | {reason} | 分发器拒绝在本周期重新生成此就绪任务。原因:blocker_auth(上次失败是配额/认证/429 错误——等待速率窗口重置)、recent_success(过去一小时内发生了完成的运行——等待审查后再重新运行)、active_pr(最近的评论中出现 GitHub PR URL——之前的工作线程已打开了 PR)。任务保持在 ready;下一周期获得另一次生成机会。如果底层条件持续存在,正常的 consecutive_failures 断路器将在 failure_limit 次失败后通过 gave_up 自动阻塞 |
spawn_failed | {error, failures} | 一次生成尝试失败(缺少 PATH、工作空间不可挂载等)。计数器增加;任务回到 ready 以重试 |
protocol_violation | {pid, claimer, exit_code} | 工作线程在任务仍为 running 时成功退出,通常是因为它回答了而未调用 kanban_complete 或 kanban_block。分发器还发出 gave_up 并立即自动阻塞而不是重试 |
gave_up | {failures, effective_limit, limit_source, error} | 断路器在 N 次连续不成功尝试后跳闸。任务自动阻塞并带有最后一个错误。有效限制解析为 task max_retries,然后是 dispatcher failure_limit / kanban.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 之前请阅读该文件。