Hermes Agent Kanban — Multi-Agent Profile Collaboration
Hermes Agent ships a built-in Kanban board — a durable SQLite-backed task queue for coordinating work across multiple named agent profiles. Unlike delegate_task (a single RPC-style fork-join), the Kanban is a persistent message bus where tasks survive restarts, support human-in-the-loop blocks, and leave a permanent audit trail.
Why Kanban over delegate_task?
| Dimension | delegate_task | Kanban |
|---|---|---|
| Shape | RPC fork-join | Durable message queue + state machine |
| Parent | Blocks until child returns | Fire-and-forget after create |
| Child identity | Anonymous subagent | Named profile with persistent memory |
| Resumability | None | Block → unblock → re-run |
| Audit trail | Lost on compression | Permanent SQLite rows |
| Coordination | Hierarchical (parent/child) | Peer — any profile reads/writes any task |
| Human-in-loop | Not supported | Block reason + unblock flow |
| Survives restart | No | Yes (disk-backed) |
Rule of thumb: delegate_task is a function call; Kanban is a work queue where every handoff is a row anyone can read and write.
Core Concepts
Board — a standalone SQLite DB. The default is ~/.hermes/kanban.db. You can create multiple boards (one per project) — workers physically cannot see tasks on other boards.
Task — a row with title, body, assignee (a profile name), and a status. Status flow:
triage → todo → ready → running → blocked → done → archivedTasks support parent-child dependencies, priority levels, tags, file attachments, and structured metadata.
Workspace — the working directory a spawned worker operates in:
scratch(default) — ephemeral temp dir, deleted on completionworktree— a git worktree for coding tasksdir:<path>— an existing shared directory
Dispatcher — a loop running inside the gateway process. Every N seconds it: reclaims stale claims, promotes ready tasks when their parents complete, atomically claims ready tasks, and spawns the assigned profile as a worker.
Comment thread — an inter-agent protocol. Every agent and human can append comments. Workers read the full thread on spawn.
Two Surfaces
You drive the board through three interfaces:
- CLI:
hermes kanban <verb> - Slash command:
/kanban <verb>from any gateway chat - Web dashboard:
hermes dashboard→ Kanban tab
Workers drive the board through a dedicated kanban_* toolset — the model calls tools directly, never hermes kanban CLI commands:
| Tool | Purpose |
|---|---|
kanban_show() | Read current task (title, body, parent handoffs, prior runs, comments) |
kanban_list() | List tasks with filters (orchestrator profiles) |
kanban_complete(summary, metadata) | Finish with structured handoff |
kanban_block(reason) | Stop and request human input |
kanban_heartbeat(note) | Signal liveness during long ops |
kanban_comment(task_id, body) | Append a durable note |
kanban_create(title, assignee, ...) | Fan out into child tasks |
kanban_link(parent, child) | Add a dependency link |
kanban_unblock(task_id) | Move a blocked task back to ready |
Dashboard
The web dashboard (hermes dashboard → Kanban) shows:
- Six columns: Triage → Todo → Ready → Running (grouped by profile) → Blocked → Done
- Cards with id, title, priority, assignee, comment/link counts
- Profile lanes inside Running — see at a glance what each worker is doing
- Side drawer on click: editable title/body, dependency editor, status actions, comment thread, run history with per-attempt outcomes, file attachments
- Live WebSocket updates — board refreshes instantly on any change
- Nudge Dispatcher button — force an immediate dispatch tick
- Auto triage — drop rough ideas in Triage; the decomposer LLM reads your profile roster and produces a graph of child tasks routed to the best-fit specialists
Run History
Every attempt on a task is recorded as a task_run with:
- Outcome:
completed,blocked,spawn_failed,crashed,timed_out,gave_up - Summary — the worker’s structured handoff text
- Metadata — arbitrary JSON (changed files, decisions, test results)
- Error — crash/error details
- Duration, timestamps, worker identity
Retry history is the primary representation — not a conceptual afterthought. A retrying worker sees prior attempts in its kanban_show() context and knows exactly what went wrong.
Setup
# One-time init (auto-runs on first command)
hermes kanban init
# Start gateway (hosts the dispatcher)
hermes gateway start
# Open the dashboard
hermes dashboardWorkflows
Solo Dev — Feature with Dependencies
Create tasks with parent links so dependencies auto-promote:
SCHEMA=$(hermes kanban create "Design auth schema" \
--assignee backend-dev --body "Design the user/session/token schema" --json | jq -r .id)
API=$(hermes kanban create "Implement auth API endpoints" \
--assignee backend-dev --parent $SCHEMA \
--body "POST /register, /login, /refresh, /logout" --json | jq -r .id)
hermes kanban create "Write auth integration tests" \
--assignee qa-dev --parent $API \
--body "Happy path, wrong password, expired token, concurrent refresh"Only SCHEMA starts in ready. When it completes, API auto-promotes to ready. When API completes, the test task follows. The dispatcher spawns workers in sequence automatically.
The backend-dev worker’s loop:
# Worker: kanban_show() → sees task + parent handoffs
# (designs schema, writes migrations)
kanban_heartbeat(note="schema drafted, writing migrations now")
kanban_complete(
summary="users(id, email, pw_hash), sessions(id, user_id, jti, expires_at)",
metadata={
"changed_files": ["migrations/001_users.sql", "migrations/002_sessions.sql"],
"decisions": ["bcrypt for hashing", "JWT for session tokens"],
},
)The downstream API worker reads the schema task’s summary + metadata in its kanban_show() context — zero context-switching overhead.
Fleet Farming — Parallel Specialists
Drop a batch of independent tasks and walk away — the dispatcher spawns all specialist profiles in parallel:
for lang in Spanish French German; do
hermes kanban create "Translate homepage to $lang" --assignee translator
done
for i in 1 2 3 4 5; do
hermes kanban create "Transcribe Q3 customer call #$i" --assignee transcriber
done
for sku in 1001 1002 1003 1004; do
hermes kanban create "Generate product description: SKU-$sku" --assignee copywriter
doneThree workers drain their queues concurrently. The dashboard shows In Progress grouped by profile. No further human input needed.
Role Pipeline with Retry
A three-stage pipeline with a rejection + retry:
# PM worker completes spec with acceptance criteria in metadata
kanban_complete(
summary="spec approved; POST /forgot-password sends email...",
metadata={"acceptance": [
"expired token returns 410",
"reused last-3 password returns 400",
"successful reset invalidates all active sessions",
]},
)
# Engineer (attempt 1) — reads spec, implements, reviewer rejects
kanban_block(reason="password strength check missing, reset link isn't single-use")
# Human reads block reason, unblocks from dashboard / CLI / Telegram
# hermes kanban unblock $IMPL
# Engineer (attempt 2) — sees prior block reason in context, fixes both issues
kanban_complete(
summary="added zxcvbn strength check, reset tokens are now single-use",
metadata={"changed_files": ["auth/reset.py", "auth/tests/test_reset.py"],
"review_iteration": 2},
)The dashboard shows both runs on the same task card: Run 1 (blocked → why), Run 2 (completed → summary with metadata). The reviewer worker then picks up the child “Review password reset PR” task and sees the engineer’s handoff metadata before looking at a diff.
Orchestrator Decomposition
An orchestrator fan-out pattern — split a goal into routed sub-tasks:
kanban_create(title="research ICP funding, NA angle",
assignee="researcher-a",
body="Seed + Series A, North America, AI-adjacent")
kanban_create(title="research ICP funding, EU angle",
assignee="researcher-b",
body="Seed + Series A: UK, Germany, France focused")
kanban_create(
title="synthesize findings into launch brief",
assignee="writer",
parents=["t_r1", "t_r2"],
body="one-pager, 300 words, neutral tone")
kanban_complete(summary="decomposed into 2 research → 1 synthesis")Auto Triage (Default)
Drop a rough idea into the Triage column. With kanban.auto_decompose: true (default), the dispatcher auto-runs the decomposer: it reads your installed profiles + descriptions, asks an LLM to produce a task graph, and creates child tasks routed to the best-fit specialists.
hermes kanban create "draft a launch post on ICP funding landscape" --triage
# → decomposer produces children routed to researcher-a, researcher-b, writerFlip the Auto/Manual pill on the dashboard to switch modes. Or use ✨ Specify for a single-task spec rewrite without fan-out.
Goal-Mode Cards (Multiple Turns)
For open-ended tasks needing several iterations, pass --goal:
hermes kanban create "Translate the docs site to French" \
--body "Acceptance: every page translated, no English left, links intact." \
--assignee linguist --goal --goal-max-turns 15The worker runs in a loop with an auxiliary judge checking output against the acceptance criteria. Keeps going until the judge agrees, the worker terminates, or the turn budget is exhausted.
Dispatcher Safeguards
- Circuit breaker — auto-blocks a task after N consecutive spawn failures (
kanban.failure_limit, default 2, or per-task--max-retries) - Crash detection — the dispatcher polls
kill(pid, 0)and reclaims tasks whose worker process died mid-flight - TTL enforcement — workers must heartbeat or the dispatcher reclaims the task
- Gateway notifications —
gave_upevents fire through Telegram/Discord/Slack so you hear about outages without checking the board
Structured Handoff
The critical design principle: kanban_complete(summary, metadata) is not decoration — it’s the primary handoff channel between stages.
When a worker on task B spawns and calls kanban_show(), its worker_context includes:
- B’s prior attempts — previous run outcomes, summaries, errors, metadata (so retrying workers don’t repeat failed paths)
- Parent task results — for each parent, the most-recent completed run’s summary + metadata (so downstream workers see why and how upstream work was done)
This replaces the “dig through comments and output” dance. A PM writes acceptance criteria in the spec’s metadata; the engineer’s worker reads it structurally in the parent handoff. An engineer records which tests passed; the reviewer has that list in hand before opening a diff.
Quickstart
# 1. Create the board
hermes kanban init
# 2. Start the dispatcher (embedded in gateway)
hermes gateway start
# 3. Drop a task
hermes kanban create "Experiment with Hermes Kanban" --assignee default
# 4. Watch it in the dashboard
hermes dashboard
# 5. Inspect from CLI
hermes kanban list
hermes kanban show <task-id>
hermes kanban runs <task-id>
hermes kanban watch --kinds completed,gave_up,timed_out