mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-18 04:41:56 +00:00
Closes the architectural-pin part of #19931. Most of what that issue asked for is already implemented (logs under kanban root, env-pinned workspace, dispatcher routing of unknown assignees, lifecycle ownership, structured handoff conventions). What was missing: 1. A written contract integrators can point at when adding a new worker lane shape, and 2. The "code-changing workers should not auto-promote success to done" convention. This commit ships both as docs+convention layered on existing primitives. No kernel changes — the kanban_complete / kanban_block / kanban_comment surfaces already support the review-required pattern; we just hadn't written it down or made it visible to workers. Changes: - `agent/prompt_builder.py::KANBAN_GUIDANCE`: append the review-required exception to step 5 of the lifecycle. Workers get the cue auto-injected into their system prompt — drop structured metadata into a kanban_comment first, then end with kanban_block(reason="review-required: <summary>") instead of kanban_complete when the work needs review. Total prompt size went from ~3000 to ~3275 chars; well under the 4096 budget enforced by test_kanban_guidance_size. - `skills/devops/kanban-worker/SKILL.md`: add a worked example to the existing "Good summary + metadata shapes" section between the Coding-task and Research-task examples. Same shape as the others (kanban_comment with structured handoff JSON, then kanban_block with the human-readable reason). Plus a one-line guide on when to use kanban_complete vs the review-required pattern. - `website/docs/user-guide/features/kanban-worker-lanes.md` (new): the integrator-facing contract. Covers the hierarchy, the three things every lane must provide (assignee, spawn mechanism, lifecycle terminator), the env vars the dispatcher injects, the review-required convention, the failure modes the kernel handles for free, and an explicit "external CLI worker lane" deferred- pending-concrete-asker section that links to #19931 and #19924. - `website/sidebars.ts`: link the new page under user-guide/features. The "specialist worker lanes for external CLI tools (Codex / Claude Code / OpenCode)" runner is NOT shipped here. The dispatcher's spawn_fn parameter already supports plugin-shaped extension; the per-CLI integration work (auth, sandbox policy, exit-code mapping) needs a concrete asker. The new docs page tells would-be integrators the contract any such lane must satisfy. Refs #19931
184 lines
9.8 KiB
Markdown
184 lines
9.8 KiB
Markdown
---
|
|
name: kanban-worker
|
|
description: Pitfalls, examples, and edge cases for Hermes Kanban workers. The lifecycle itself is auto-injected into every worker's system prompt as KANBAN_GUIDANCE (from agent/prompt_builder.py); this skill is what you load when you want deeper detail on specific scenarios.
|
|
version: 2.0.0
|
|
platforms: [linux, macos, windows]
|
|
metadata:
|
|
hermes:
|
|
tags: [kanban, multi-agent, collaboration, workflow, pitfalls]
|
|
related_skills: [kanban-orchestrator]
|
|
---
|
|
|
|
# Kanban Worker — Pitfalls and Examples
|
|
|
|
> You're seeing this skill because the Hermes Kanban dispatcher spawned you as a worker with `--skills kanban-worker` — it's loaded automatically for every dispatched worker. The **lifecycle** (6 steps: orient → work → heartbeat → block/complete) also lives in the `KANBAN_GUIDANCE` block that's auto-injected into your system prompt. This skill is the deeper detail: good handoff shapes, retry diagnostics, edge cases.
|
|
|
|
## Workspace handling
|
|
|
|
Your workspace kind determines how you should behave inside `$HERMES_KANBAN_WORKSPACE`:
|
|
|
|
| Kind | What it is | How to work |
|
|
|---|---|---|
|
|
| `scratch` | Fresh tmp dir, yours alone | Read/write freely; it gets GC'd when the task is archived. |
|
|
| `dir:<path>` | Shared persistent directory | Other runs will read what you write. Treat it like long-lived state. Path is guaranteed absolute (the kernel rejects relative paths). |
|
|
| `worktree` | Git worktree at the resolved path | If `.git` doesn't exist, run `git worktree add <path> <branch>` from the main repo first, then cd and work normally. Commit work here. |
|
|
|
|
## Tenant isolation
|
|
|
|
If `$HERMES_TENANT` is set, the task belongs to a tenant namespace. When reading or writing persistent memory, prefix memory entries with the tenant so context doesn't leak across tenants:
|
|
|
|
- Good: `business-a: Acme is our biggest customer`
|
|
- Bad (leaks): `Acme is our biggest customer`
|
|
|
|
## Good summary + metadata shapes
|
|
|
|
The `kanban_complete(summary=..., metadata=...)` handoff is how downstream workers read what you did. Patterns that work:
|
|
|
|
**Coding task:**
|
|
```python
|
|
kanban_complete(
|
|
summary="shipped rate limiter — token bucket, keys on user_id with IP fallback, 14 tests pass",
|
|
metadata={
|
|
"changed_files": ["rate_limiter.py", "tests/test_rate_limiter.py"],
|
|
"tests_run": 14,
|
|
"tests_passed": 14,
|
|
"decisions": ["user_id primary, IP fallback for unauthenticated requests"],
|
|
},
|
|
)
|
|
```
|
|
|
|
**Coding task that needs human review (review-required):**
|
|
|
|
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.
|
|
|
|
```python
|
|
import json
|
|
|
|
kanban_comment(
|
|
body="review-required handoff:\n" + json.dumps({
|
|
"changed_files": ["rate_limiter.py", "tests/test_rate_limiter.py"],
|
|
"tests_run": 14,
|
|
"tests_passed": 14,
|
|
"diff_path": "/path/to/worktree", # or PR url if pushed
|
|
"decisions": ["user_id primary, IP fallback for unauthenticated requests"],
|
|
}, indent=2),
|
|
)
|
|
kanban_block(
|
|
reason="review-required: rate limiter shipped, 14/14 tests pass — needs eyes on the user_id/IP fallback choice before merging",
|
|
)
|
|
```
|
|
|
|
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.
|
|
|
|
**Research task:**
|
|
```python
|
|
kanban_complete(
|
|
summary="3 competing libraries reviewed; vLLM wins on throughput, SGLang on latency, Tensorrt-LLM on memory efficiency",
|
|
metadata={
|
|
"sources_read": 12,
|
|
"recommendation": "vLLM",
|
|
"benchmarks": {"vllm": 1.0, "sglang": 0.87, "trtllm": 0.72},
|
|
},
|
|
)
|
|
```
|
|
|
|
**Review task:**
|
|
```python
|
|
kanban_complete(
|
|
summary="reviewed PR #123; 2 blocking issues found (SQL injection in /search, missing CSRF on /settings)",
|
|
metadata={
|
|
"pr_number": 123,
|
|
"findings": [
|
|
{"severity": "critical", "file": "api/search.py", "line": 42, "issue": "raw SQL concat"},
|
|
{"severity": "high", "file": "api/settings.py", "issue": "missing CSRF middleware"},
|
|
],
|
|
"approved": False,
|
|
},
|
|
)
|
|
```
|
|
|
|
Shape `metadata` so downstream parsers (reviewers, aggregators, schedulers) can use it without re-reading your prose.
|
|
|
|
## Claiming cards you actually created
|
|
|
|
If your run produced new kanban tasks (via `kanban_create`), pass the ids in `created_cards` on `kanban_complete`. The kernel verifies each id exists and was created by your profile; any phantom id blocks the completion with an error listing what went wrong, and the rejected attempt is permanently recorded on the task's event log. **Only list ids you captured from a successful `kanban_create` return value — never invent ids from prose, never paste ids from earlier runs, never claim cards another worker created.**
|
|
|
|
```python
|
|
# GOOD — capture return values, then claim them.
|
|
c1 = kanban_create(title="remediate SQL injection", assignee="security-worker")
|
|
c2 = kanban_create(title="fix CSRF middleware", assignee="web-worker")
|
|
|
|
kanban_complete(
|
|
summary="Review done; spawned remediations for both findings.",
|
|
metadata={"pr_number": 123, "approved": False},
|
|
created_cards=[c1["task_id"], c2["task_id"]],
|
|
)
|
|
```
|
|
|
|
```python
|
|
# BAD — claiming ids you don't have captured return values for.
|
|
kanban_complete(
|
|
summary="Created remediation cards t_a1b2c3d4, t_deadbeef", # hallucinated
|
|
created_cards=["t_a1b2c3d4", "t_deadbeef"], # → gate rejects
|
|
)
|
|
```
|
|
|
|
If a `kanban_create` call fails (exception, tool_error), the card was NOT created — do not include a phantom id for it. Retry the create, or omit the id and mention the failure in your summary. The prose-scan pass also catches `t_<hex>` references in your free-form summary that don't resolve; these don't block the completion but show up as advisory warnings on the task in the dashboard.
|
|
|
|
## Block reasons that get answered fast
|
|
|
|
Bad: `"stuck"` — the human has no context.
|
|
|
|
Good: one sentence naming the specific decision you need. Leave longer context as a comment instead.
|
|
|
|
```python
|
|
kanban_comment(
|
|
task_id=os.environ["HERMES_KANBAN_TASK"],
|
|
body="Full context: I have user IPs from Cloudflare headers but some users are behind NATs with thousands of peers. Keying on IP alone causes false positives.",
|
|
)
|
|
kanban_block(reason="Rate limit key choice: IP (simple, NAT-unsafe) or user_id (requires auth, skips anonymous endpoints)?")
|
|
```
|
|
|
|
The block message is what appears in the dashboard / gateway notifier. The comment is the deeper context a human reads when they open the task.
|
|
|
|
## Heartbeats worth sending
|
|
|
|
Good heartbeats name progress: `"epoch 12/50, loss 0.31"`, `"scanned 1.2M/2.4M rows"`, `"uploaded 47/120 videos"`.
|
|
|
|
Bad heartbeats: `"still working"`, empty notes, sub-second intervals. Every few minutes max; skip entirely for tasks under ~2 minutes.
|
|
|
|
## Retry scenarios
|
|
|
|
If you open the task and `kanban_show` returns `runs: [...]` with one or more closed runs, you're a retry. The prior runs' `outcome` / `summary` / `error` tell you what didn't work. Don't repeat that path. Typical retry diagnostics:
|
|
|
|
- `outcome: "timed_out"` — the previous attempt hit `max_runtime_seconds`. You may need to chunk the work or shorten it.
|
|
- `outcome: "crashed"` — OOM or segfault. Reduce memory footprint.
|
|
- `outcome: "spawn_failed"` + `error: "..."` — usually a profile config issue (missing credential, bad PATH). Ask the human via `kanban_block` instead of retrying blindly.
|
|
- `outcome: "reclaimed"` + `summary: "task archived..."` — operator archived the task out from under the previous run; you probably shouldn't be running at all, check status carefully.
|
|
- `outcome: "blocked"` — a previous attempt blocked; the unblock comment should be in the thread by now.
|
|
|
|
## Do NOT
|
|
|
|
- Call `delegate_task` as a substitute for `kanban_create`. `delegate_task` is for short reasoning subtasks inside YOUR run; `kanban_create` is for cross-agent handoffs that outlive one API loop.
|
|
- Modify files outside `$HERMES_KANBAN_WORKSPACE` unless the task body says to.
|
|
- Create follow-up tasks assigned to yourself — assign to the right specialist.
|
|
- Complete a task you didn't actually finish. Block it instead.
|
|
|
|
## Pitfalls
|
|
|
|
**Task state can change between dispatch and your startup.** Between when the dispatcher claimed and when your process actually booted, the task may have been blocked, reassigned, or archived. Always `kanban_show` first. If it reports `blocked` or `archived`, stop — you shouldn't be running.
|
|
|
|
**Workspace may have stale artifacts.** Especially `dir:` and `worktree` workspaces can have files from previous runs. Read the comment thread — it usually explains why you're running again and what state the workspace is in.
|
|
|
|
**Don't rely on the CLI when the guidance is available.** The `kanban_*` tools work across all terminal backends (Docker, Modal, SSH). `hermes kanban <verb>` from your terminal tool will fail in containerized backends because the CLI isn't installed there. When in doubt, use the tool.
|
|
|
|
## CLI fallback (for scripting)
|
|
|
|
Every tool has a CLI equivalent for human operators and scripts:
|
|
- `kanban_show` ↔ `hermes kanban show <id> --json`
|
|
- `kanban_complete` ↔ `hermes kanban complete <id> --summary "..." --metadata '{...}'`
|
|
- `kanban_block` ↔ `hermes kanban block <id> "reason"`
|
|
- `kanban_create` ↔ `hermes kanban create "title" --assignee <profile> [--parent <id>]`
|
|
- etc.
|
|
|
|
Use the tools from inside an agent; the CLI exists for the human at the terminal.
|