{"repo":"NousResearch/hermes-agent","public_id":"19931","display_label":"Kanban worker lanes, review transitions, and dispatch","summary":"These items concern adding first-class worker lane support, a request_review transition (running→review), and dispatch hooks to a Kanban system, along with questions and docs about the review column's intended scope and contract.","subsystem":"agent","counts":{"members":13,"open_issues":4,"open_issues_distinct":4,"open_prs":1,"closed_issues":4,"merged_prs":2,"attempt_prs":3},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":23483},"resolution":{"state":"partially_resolved","by":"pr-23483"},"freshness":{"state":"verified","last_verified_at":"2026-08-10T19:43:47+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.10564102564102565},"fix_edges":[{"issue_number":19931,"pr_number":23483,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true},{"issue_number":42896,"pr_number":83412,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":54823,"pr_number":83412,"tier":"verified","basis":"superseded_ref","confidence":0.9,"recoverable_from_timeline":false},{"issue_number":67963,"pr_number":83412,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":19931,"title":"Architecture: specialist worker lanes under Hermes Kanban orchestration","url":null,"state":"closed"},{"kind":"issue","number":20157,"title":"Feature Request: First-Class Persistent Kanban Worker Lanes","url":null,"state":"closed"},{"kind":"issue","number":29171,"title":"Kanban needs first-class waiting states for human, approval, and review gates","url":null,"state":"closed"},{"kind":"issue","number":36079,"title":"External worker-lane plugin API for AgentPlane-style supervisors","url":null,"state":"open"},{"kind":"issue","number":38783,"title":"Kanban review-column dispatch lacks a first-class transition path and references missing sdlc-review skill","url":null,"state":"closed"},{"kind":"issue","number":42896,"title":"Kanban review status exists but has no first-class request-review transition","url":null,"state":"open"},{"kind":"issue","number":46898,"title":"Question: Is the Kanban `review` pipeline intended for general agentic-work verification, or specifically for code-review / SDLC?","url":null,"state":"open"},{"kind":"issue","number":55267,"title":"Feature request: support worker-lane running→review Kanban handoff from external shell/CLI","url":null,"state":"open"},{"kind":"pr","number":23483,"title":"docs(kanban): worker lane contract + review-required convention (closes #19931)","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["agent/prompt_builder.py","skills/devops/kanban-worker/SKILL.md","website/docs/user-guide/features/kanban-worker-lanes.md","website/sidebars.ts"],"fix_hunks":[{"file":"agent/prompt_builder.py","hunks":["@@ -216,7 +216,15 @@ def _strip_yaml_frontmatter(content: str) -> str:\n     \"artifacts. `metadata` is machine-readable facts \"\n     \"(`{changed_files: [...], tests_run: N, decisions: [...]}`). Downstream \"\n     \"workers read both via their own `kanban_show`. Never put secrets / \"\n-    \"tokens / raw PII in either field — run rows are durable forever.\\n\"\n+    \"tokens / raw PII in either field — run rows are durable forever. \"\n+    \"Exception: if your output is a code change that needs human review \"\n+    \"before counting as merged/done (most coding tasks), drop the \"\n+    \"structured metadata (changed_files / tests_run / diff_path) into a \"\n+    \"`kanban_comment` first, then end with \"\n+    \"`kanban_block(reason=\\\"review-required: <one-line summary>\\\")` so a \"\n+    \"reviewer can approve+unblock or request changes. Reviewing-then-\"\n+    \"completing is more honest than auto-completing work that still needs \"\n+    \"eyes on it.\\n\"\n     \"6. **If follow-up work appears, create it; don't do it.** Use \"\n     \"`kanban_create(title=..., assignee=<right-profile>, parents=[your-task-id])` \"\n     \"to spawn a child task for the appropriate specialist profile instead of \""]},{"file":"skills/devops/kanban-worker/SKILL.md","hunks":["@@ -47,6 +47,29 @@ kanban_complete(\n )\n ```\n \n+**Coding task that needs human review (review-required):**\n+\n+For most code-changing tasks, the work isn't truly *done* until a human reviewer has eyes on it. Block instead of complete, with `reason` prefixed `review-required: ` so the dashboard surfaces the row as needing review. Drop the structured metadata (changed files, test counts, diff/PR url) into a comment first, since `kanban_block` only carries the human-readable reason — comments are the durable annotation channel. Reviewer either approves and runs `hermes kanban unblock <id>` (which re-spawns you with the comment thread for any follow-ups) or asks for changes via another comment.\n+\n+```python\n+import json\n+\n+kanban_comment(\n+    body=\"review-required handoff:\\n\" + json.dumps({\n+        \"changed_files\": [\"rate_limiter.py\", \"tests/test_rate_limiter.py\"],\n+        \"tests_run\": 14,\n+        \"tests_passed\": 14,\n+        \"diff_path\": \"/path/to/worktree\",  # or PR url if pushed\n+        \"decisions\": [\"user_id primary, IP fallback for unauthenticated requests\"],\n+    }, indent=2),\n+)\n+kanban_block(\n+    reason=\"review-required: rate limiter shipped, 14/14 tests pass — needs eyes on the user_id/IP fallback choice before merging\",\n+)\n+```\n+\n+Use `kanban_complete` only when the task is genuinely terminal — e.g. a one-line typo fix, a docs change with no functional consequences, or a research task where the artifact IS the writeup itself.\n+\n **Research task:**\n ```python\n kanban_complete("]},{"file":"website/docs/user-guide/features/kanban-worker-lanes.md","hunks":["@@ -0,0 +1,113 @@\n+# Kanban worker lanes\n+\n+A **worker lane** is a class of process that the kanban dispatcher can route tasks to. Each lane has an identity (the assignee string), a spawn mechanism, and a contract for what it must do with the task once spawned.\n+\n+This page is the contract. It exists for two audiences:\n+\n+- **Operators** picking which lanes to wire into a board (which profiles to create, which assignees to use).\n+- **Plugin / integration authors** wanting to add a new lane shape (a CLI worker that wraps Codex / Claude Code / OpenCode, a containerised review worker, a non-Hermes service that pulls tasks via the API).\n+\n+If you're writing the worker code itself — the agent that runs *inside* a lane — the [`kanban-worker`](https://github.com/NousResearch/hermes-agent/blob/main/skills/devops/kanban-worker/SKILL.md) skill is the deeper procedural detail.\n+\n+## The hierarchy\n+\n+```text\n+Hermes Kanban  =  canonical task lifecycle + audit trail\n+Worker lane    =  implementation executor for one assigned card\n+Reviewer       =  human or human-proxy that gates \"done\"\n+GitHub PR      =  upstreamable artifact (optional, for code lanes)\n+```\n+\n+Hermes Kanban owns lifecycle truth — `ready` → `running` → `blocked` / `done` / `archived`. Worker lanes execute work but never own that truth; everything they do flows back through the kanban kernel via the `kanban_*` tools (or, for non-Hermes external workers, via the API). Reviewers gate the transition from \"code change written\" to \"task done.\"\n+\n+## What a lane provides\n+\n+To be a kanban worker lane, an integration must provide three things:\n+\n+### 1. An assignee string\n+\n+The dispatcher matches `task.assignee` against either a Hermes profile name (the default lane shape) or a registered non-spawnable identifier (the plugin lane shape — see [Adding an external CLI worker lane](#adding-an-external-cli-worker-lane) below). Tasks whose assignee doesn't resolve are left on `ready` with a `skipped_nonspawnable` event so a board operator can fix them; they are not silently dropped or executed by an arbitrary fallback.\n+\n+### 2. A spawn mechanism\n+\n+For Hermes profile lanes, the dispatcher's `_default_spawn` runs `hermes -p <assignee> chat -q <prompt>` (or the equivalent module form when the `hermes` shim isn't on `$PATH`) inside the task's pinned workspace, with these env vars set:\n+\n+| Variable | Carries |\n+|---|---|\n+| `HERMES_KANBAN_TASK` | the task id the worker is operating on |\n+| `HERMES_KANBAN_DB` | absolute path to the per-board SQLite file |\n+| `HERMES_KANBAN_BOARD` | board slug |\n+| `HERMES_KANBAN_WORKSPACES_ROOT` | root of the board's workspace tree |\n+| `HERMES_KANBAN_WORKSPACE` | absolute path to *this* task's workspace |\n+| `HERMES_KANBAN_RUN_ID` | the current run's id (for the lifecycle gate) |\n+| `HERMES_KANBAN_CLAIM_LOCK` | the claim lock string (`<host>:<pid>:<uuid>`) |\n+| `HERMES_PROFILE` | the worker's own profile name (for `kanban_comment` author attribution) |\n+| `HERMES_TENANT` | tenant namespace, if the task has one |\n+\n+For non-Hermes lanes (registered via a plugin), the plugin supplies its own `spawn_fn` callable that gets `task`, `workspace`, and `board` and returns an optional pid for crash detection.\n+\n+### 3. A lifecycle terminator\n+\n+Every claim must end in exactly one of:\n+\n+- `kanban_complete(summary=..., metadata=...)` — task succeeds, status flips to `done`.\n+- `kanban_block(reason=...)` — task waits for human input, status flips to `blocked`. The dispatcher respawns when `kanban_unblock` runs.\n+- The worker process exits without a tool call. The kernel reaps it and emits `crashed` (PID died) or `gave_up` (consecutive-failure breaker tripped) or `timed_out` (max_runtime exceeded). This is the failure path; healthy workers don't end here.\n+\n+The kanban kernel enforces that exactly one of these terminates each run. A worker that calls neither and exits normally is treated as crashed.\n+\n+## Outputs and the review-required convention\n+\n+For most code-changing tasks, the work isn't truly *done* the moment the worker finishes — it needs a human reviewer. The kanban kernel doesn't enforce this distinction (a \"code-changing task\" is fuzzy and forcing block-instead-of-complete on every code worker would break flows where no review is wanted). It's a convention layered on top:\n+\n+- **Block instead of complete**, with `reason` prefixed `review-required: ` so the dashboard / `hermes kanban show` surfaces the row as awaiting review.\n+- **Drop structured metadata into a `kanban_comment` first** since `kanban_block` only carries the human-readable `reason`. Comments are the durable annotation channel — every audit-relevant field (changed_files, tests_run, diff_path or PR url, decisions) belongs there.\n+- **Reviewer either approves and unblocks**, which respawns the worker with the comment thread for follow-ups; or asks for changes via another comment, which the next worker run sees as part of `kanban_show`'s context.\n+\n+The [`kanban-worker`](https://github.com/NousResearch/hermes-agent/blob/main/skills/devops/kanban-worker/SKILL.md) skill has worked examples for both `kanban_complete` (truly terminal tasks — typo fixes, docs changes, research writeups) and the `review-required` block pattern.\n+\n+## Logs and audit trail\n+\n+The dispatcher writes per-task worker stdout/stderr to `<board-root>/logs/<task_id>.log`. Logs are auditable from kanban metadata:\n+\n+- `task_runs` rows carry the `log_path`, exit code (where available), summary, and metadata.\n+- `task_events` rows carry every state transition (`promoted`, `claimed`, `heartbeat`, `completed`, `blocked`, `gave_up`, `crashed`, `timed_out`, `reclaimed`, `claim_extended`).\n+- `kanban_show` returns both, so a reviewer (or a follow-up worker) reading the task gets the full history without needing dashboard access.\n+\n+The dashboard renders run history with summaries, metadata blocks, and exit-status badges. CLI users can run `hermes kanban tail <task_id>` to follow live, or `hermes kanban runs <task_id>` for the historical attempt list.\n+\n+## Existing lane shapes\n+\n+### Hermes profile lane (default)\n+\n+The shape every kanban worker takes today: the assignee is a profile name, the dispatcher spawns `hermes -p <profile>`, the worker auto-loads the [`kanban-worker`](https://github.com/NousResearch/hermes-agent/blob/main/skills/devops/kanban-worker/SKILL.md) skill plus the `KANBAN_GUIDANCE` system-prompt block, and uses the `kanban_*` tools to terminate the run. No setup beyond defining the profile.\n+\n+When you create profiles for your fleet, choose names that match the *role* you want the orchestrator to route to. The orchestrator (when there is one) discovers your profile names via `hermes profile list` — there's no fixed roster the system assumes (see the [`kanban-orchestrator`](https://github.com/NousResearch/hermes-agent/blob/main/skills/devops/kanban-orchestrator/SKILL.md) skill for the orchestrator side of the contract).\n+\n+### Orchestrator profile lane\n+\n+A specialisation of the profile lane: an orchestrator is a Hermes profile whose toolset includes `kanban` but excludes `terminal` / `file` / `code` / `web` for implementation. Its job is decomposing a high-level goal into child tasks via `kanban_create` + `kanban_link` and stepping back. The orchestrator skill encodes the anti-temptation rules.\n+\n+## Adding an external CLI worker lane\n+\n+Wiring a non-Hermes CLI tool (Codex CLI, Claude Code CLI, OpenCode CLI, a local coding-model runner, etc.) as a kanban worker lane is *not yet a paved path*. The dispatcher's spawn function is pluggable (`spawn_fn` is a parameter on `dispatch_once`), and a plugin could register its own `spawn_fn` for a non-Hermes assignee, but the surrounding integration work — wrapping the CLI's exit code into `kanban_complete` / `kanban_block` calls, mapping the CLI's workspace/sandbox conventions onto the dispatcher's `HERMES_KANBAN_WORKSPACE` env, handling auth and per-CLI policy — is still per-integration design work.\n+\n+If you're considering adding a CLI lane, open an issue describing the specific CLI and the workflow you're trying to enable. The contract above is the constraints any such lane must satisfy; the implementation shape (one plugin per CLI vs a generic CLI-runner plugin parameterised by config) is open.\n+\n+The historical issue for this is [#19931](https://github.com/NousResearch/hermes-agent/issues/19931) and the closed-not-merged Codex-specific PR [#19924](https://github.com/NousResearch/hermes-agent/pull/19924) — those describe the original architecture proposal but didn't land a runner.\n+\n+## Failure modes the dispatcher handles\n+\n+So lane authors don't have to reimplement these:\n+\n+- **Stale claim TTL** — a worker that claims and then never heartbeats / completes / blocks gets reclaimed after `DEFAULT_CLAIM_TTL_SECONDS` (15 min default) — but only if the worker process has actually died. A live worker (slow model spending 20+ min in one tool-free LLM call) gets the claim *extended* instead of killed; only a dead PID is reclaimed.\n+- **Crashed worker** — a worker whose host-local PID has vanished is detected by `detect_crashed_workers` and reaped; the task increments `consecutive_failures` and may auto-block when the breaker trips.\n+- **Run-level retry** — when a task is retried (post-block, post-crash, post-reclaim), the worker can use the `expected_run_id` parameter on terminating tools to fail fast if its own run was already superseded.\n+- **Per-task max runtime** — `task.max_runtime_seconds` hard-caps wall-clock time per run, regardless of PID liveness. Catches genuinely-deadlocked workers that the live-PID extension would otherwise keep running.\n+\n+## Related\n+\n+- [Kanban overview](./kanban) — the user-facing intro.\n+- [Kanban tutorial](./kanban-tutorial) — walkthrough with the dashboard open.\n+- [`kanban-worker`](https://github.com/NousResearch/hermes-agent/blob/main/skills/devops/kanban-worker/SKILL.md) — the skill the worker process loads.\n+- [`kanban-orchestrator`](https://github.com/NousResearch/hermes-agent/blob/main/skills/devops/kanban-orchestrator/SKILL.md) — the orchestrator side."]},{"file":"website/sidebars.ts","hunks":["@@ -68,6 +68,7 @@ const sidebars: SidebarsConfig = {\n             'user-guide/features/delegation',\n             'user-guide/features/kanban',\n             'user-guide/features/kanban-tutorial',\n+            'user-guide/features/kanban-worker-lanes',\n             'user-guide/features/goals',\n             'user-guide/features/code-execution',\n             'user-guide/features/hooks',\n"]}]},{"kind":"pr","number":37659,"title":"feat(kanban): add native worker lane dispatch hooks","url":null,"state":"open","close_reason":{"refs":[{"kind":"commit","value":"1c68f6f81","number":0}],"category":"uncategorized","implication":"needs_llm"},"merged":false},{"kind":"pr","number":43060,"title":"feat(kanban): add request_review transition (running → review)","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"a98aee47ce","number":0},{"kind":"pr","value":"#83412","number":83412},{"kind":"pr","value":"#75451","number":75451}],"category":"implemented_elsewhere","implication":"superseded"},"merged":false},{"kind":"pr","number":49368,"title":"feat(kanban): add request_review transition + review lifecycle","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"a98aee47ce","number":0},{"kind":"pr","value":"#83412","number":83412},{"kind":"pr","value":"#75451","number":75451}],"category":"superseded_by_pr","implication":"superseded"},"merged":false},{"kind":"pr","number":83412,"title":"fix(kanban): first-class review lifecycle — salvage of #75451 with review fixes","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["AGENTS.md","acp_adapter/tools.py","agent/prompt_builder.py","agent/transports/hermes_tools_mcp_server.py","cli-config.yaml.example","cli.py","contributors/emails/[redacted:email]","gateway/kanban_watchers.py","hermes_cli/config_defaults.py","hermes_cli/goals.py","hermes_cli/kanban.py","hermes_cli/kanban_db.py","hermes_cli/kanban_diagnostics.py","hermes_cli/kanban_swarm.py","plugins/kanban/dashboard/dist/index.js","plugins/kanban/dashboard/dist/style.css","plugins/kanban/dashboard/plugin_api.py","skills/devops/sdlc-review/SKILL.md","tests/hermes_cli/test_kanban_notify.py","tests/hermes_cli/test_kanban_parent_reopen_invalidation.py","tests/hermes_cli/test_kanban_review_lifecycle.py","tests/hermes_cli/test_kanban_review_lifecycle_complete.py","tests/hermes_cli/test_kanban_review_surfaces.py","tests/hermes_cli/test_kanban_swarm.py","tests/plugins/test_kanban_dashboard_plugin.py","tests/skills/test_sdlc_review_skill.py","tools/kanban_tools.py","toolsets.py","website/docs/reference/cli-commands.md","website/docs/reference/tools-reference.md","website/docs/reference/toolsets-reference.md","website/docs/user-guide/features/kanban-tutorial.md","website/docs/user-guide/features/kanban-worker-lanes.md","website/docs/user-guide/features/kanban.md","website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/kanban-tutorial.md","website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/kanban-worker-lanes.md","website/i18n/zh-Hans/docusaurus-plugin-content-docs/current/user-guide/features/kanban.md"]}],"trust":{"default_tier":"verified","includes_suggested":false}}