The Hermes Kanban: A Complete Guide to Multi-Agent Task Orchestration#
kanban_complete doesn’t yet accept the artifacts parameter — everything else applies today.If you’ve used Hermes Agent for more than a weekend, you’ve probably discovered delegate_task. It’s the obvious tool when you need a subagent to handle a subtask: fork work to a child, wait for it to return, collect the result. It looks like a function call. It works like a function call. And for one-shot reasoning subtasks, it’s the right tool.
But delegate_task has a blind spot, and you won’t notice until you try to use it for something it wasn’t designed for. Coordinating a research team of parallel specialists. Running a recurring ops workflow that needs to survive a restart. Building a pipeline where a PM writes a spec, an engineer implements it, a reviewer rejects it, the engineer retries, and the reviewer approves. All with a full audit trail of every attempt.
This is exactly what the Hermes Kanban system was built for: a durable SQLite-backed task board shared across every one of your Hermes profiles. It takes multi-agent coordination from fragile subagent swarms and turns it into a reliable message-passing system with crash recovery, dependency resolution, and a full run history that never gets compressed away.
I’ve been deep in the Hermes codebase, and I want to share everything the kanban system can do, including the parts you won’t find in the docs unless you read the source. By the end, you’ll know not just how to use it, but how it works under the hood.
The Problem delegate_task Can’t Solve#
delegate_task in Hermes is a synchronous RPC call. Your agent forks a subagent, hands it a goal, waits for it to return, and gets a summary back. It’s fast, it’s useful, and it’s the right tool for quick reasoning subtasks like “review this error log” or “search the codebase for a pattern.”
But notice what happens when you try to use it for anything more:
| Dimension | delegate_task | What you actually want |
|---|---|---|
| Persistence | The child's work vanishes when the parent's context compresses | Survive restarts, reboots, and context windows |
| Identity | Anonymous subagent, no persistent memory | Named profile with its own memory and skills |
| Human-in-loop | Not supported — the parent blocks until the child returns | Read progress, add context, unblock at any point |
| Retry | Failed subtasks don't retry automatically | Detect crashes, re-spawn, keep attempt history |
| Audit trail | Lost when the parent compresses context | Durable rows forever |
| Coordination | Hierarchical — caller decides everything | Peer — any profile can read or write any task |
| Parallelism | Sequential delegation stack | N workers pulling from a shared queue |
| Scope | One parent, one child, one conversation | Multiple specialists, multiple stages, dependency graphs |
These are not edge cases. They are the normal shape of any non-trivial multi-agent workflow. And they are exactly the problems the kanban system was built to solve.
The Core Architecture#
The Hermes Kanban is surprisingly simple at its core. Strip away the dashboard, the CLI, and the tools, and you get three things:
- A SQLite database at
~/.hermes/kanban.db(or~/.hermes/kanban/boards/<slug>/kanban.dbfor non-default boards) that stores tasks, links, comments, events, and run history - A dispatcher loop embedded in the Hermes gateway that ticks every 60 seconds, reclaiming stale claims, detecting crashed workers, promoting dependencies, and spawning new workers
- A tool surface of
kanban_*tools that let running workers read and mutate the board directly from within their agent loop — no shelling out to the CLI, no HTTP calls, just direct Python calls to the samekanban_dblayer the CLI uses
That’s it. Everything else — the dashboard, the CLI verbs, the skills — is a user interface on top of these three primitives.
Let’s walk through each one.
The State Machine#
Every task on a kanban board passes through seven statuses:
--triage flag.kanban_block.kanban_complete.hermes kanban archive.The Dependency DAG#
Tasks connect through parent-child links that form a directed acyclic graph. The rule is simple: a task stays in todo until every one of its parent tasks is done. Then it automatically promotes to ready and becomes available for the dispatcher.
A very basic example:
This creates a natural pipeline: Schema → API → Tests. The dispatcher picks up Schema first. When Schema completes, the dependency engine promotes API to ready on the next tick. When API completes, Tests promotes. No manual coordination required.
The beauty is that this works for fan-out patterns too: multiple parallel research tasks feeding into a single synthesis task, or a PM spec and an engineer implementation running concurrently with a reviewer waiting for both.
Kanban vs. delegate_task#
Let me be precise about when each tool fits, because they look similar until you examine the details:
Use delegate_task when the parent agent needs a short reasoning answer before continuing, no humans are involved, and the result can safely go back into the parent’s context.
Use Kanban when work crosses agent boundaries, needs to survive restarts, might need human input, could be picked up by a different role, or needs to be discoverable after the fact.
They coexist. A kanban worker commonly calls delegate_task internally during its run for quick subtasks, and an orchestrator agent might call delegate_task for the analysis phase before deciding how to decompose work onto the board.
The Dispatch Loop#
The dispatcher is the engine of the kanban system. It runs inside the Hermes gateway process and ticks every 60 seconds by default. Each tick executes six phases in sequence:
Here’s what happens in each phase, drawn directly from the dispatcher source in hermes_cli/kanban_db.py:
Phase 1: Reclaim Stale Claims#
Every task in running status carries a claim lock with an expiration timestamp (default 15 minutes: DEFAULT_CLAIM_TTL_SECONDS = 900). If a worker’s claim has expired, the dispatcher releases it and transitions the task back to ready so another worker can pick it up.
The claim uses SQLite’s compare-and-swap pattern: UPDATE tasks SET ... WHERE id = ? AND claim_lock = ?. Only one dispatcher tick can win the race for any given task because SQLite serializes writers through its WAL lock. No distributed consensus, no external coordination service — just the database.
Phase 2: Detect Crashed Workers#
A live worker process shows up as a PID on the task row. The dispatcher calls kill(pid, 0) — a POSIX probe that checks whether the process exists without sending a signal. If the PID is gone (worker segfaulted, OOM-killed, or the systemd service stopped), the task goes back to ready with a crash recorded.
This runs before the TTL expiry catches it, which means a crashed worker is detected and the task is re-queued within one tick cycle (60 seconds), not 15 minutes.
The dispatcher also reaps zombie children via os.waitpid(-1, os.WNOHANG) and records worker exit statuses. A worker that exits cleanly (exit code 0) but never called kanban_complete or kanban_block is treated as a protocol violation and auto-blocked — the circuit breaker’s first trip.
Phase 3: Auto-Decompose Triage Tasks (New)#
Before promoting dependencies, the dispatcher checks the triage column for tasks that haven’t been decomposed yet. For each one, it calls an auxiliary LLM (configured under auxiliary.kanban_decomposer) that turns the one-liner into a full task graph.
The decomposer receives a roster of all available profiles along with their descriptions (from profile.yaml, auto-generated via auxiliary.profile_describer). It produces a JSON structure with multiple child tasks, each assigned to a specialist profile with a clear description and dependency edges linking them into a pipeline. The root task stays alive gated by every child — it only promotes to ready when the entire graph completes, at which point the orchestrator profile wakes up to judge the result or spawn more work.
Unknown assignee names returned by the LLM are rewritten to kanban.default_assignee at decompose time, so no task ever lands with assignee=None. The dispatcher caps this phase with kanban.auto_decompose_per_tick (default 3) to avoid burst-spending the auxiliary LLM on a bulk triage load.
You can also trigger decomposition manually: hermes kanban decompose <task_id> or --all to sweep the entire triage column. The dashboard adds a ⚗ Decompose button on triage card drawers alongside the existing ✨ Specify button.
Phase 4: Promote Dependencies#
The dispatcher queries for tasks whose parents are all done and transitions them from todo to ready. This is the recompute_ready() function, and it’s the only automatic state transition in the system: every other transition requires either a worker tool call or a human CLI command.
The SQL is straightforward: for each task in todo, check task_links for parent records, then verify every parent is done. If so, promote.
Phase 5: Claim and Spawn Workers#
For each task in ready with an assignee set:
- Skip the task if its assignee is not a valid Hermes profile name (this allows future external worker lanes — CLI tools like OpenCode, Codex, or Claude Code — to pull tasks manually)
- Skip if the concurrency cap is reached (
max_spawnlimits total running tasks across the board) - Atomically claim via
UPDATE tasks SET status='running', claim_lock=... WHERE id=? AND status='ready' AND claim_lock IS NULL - Resolve the workspace (scratch tmp directory, git worktree, or user-specified
dir:<path>) - Spawn the worker via
_default_spawn: runshermes -p <assignee> chat -q <prompt>with an extensive set of environment variables
The claim uses start_new_session=True on the spawned subprocess to detach it from the controlling TTY while remaining a child of the dispatcher process (so zombie reaping works).
Phase 6: Wait#
The dispatcher sleeps for the configured interval (default 60s, configurable via kanban.dispatch_interval_seconds in config.yaml), then starts again.
You can trigger a manual tick at any point from the dashboard with the “Nudge Dispatcher” button or from the CLI with hermes kanban dispatch.
The Two Surfaces#
The kanban board has two front doors, both backed by the same SQLite database:
The CLI (For You)#
You interact with the board through the hermes kanban command. There are over two dozen verbs covering the full task lifecycle:
# Setup
hermes kanban init # Create the DB (idempotent)
hermes gateway start # Start the gateway (hosts the dispatcher)
# Task lifecycle
hermes kanban create "Research X" --assignee researcher # Create and assign
hermes kanban list # See the board
hermes kanban show t_abcd # Full task details
hermes kanban complete t_abcd --summary "Done" # Mark done
hermes kanban block t_abcd "Need input" # Block for human
hermes kanban unblock t_abcd # Unblock
hermes kanban archive t_abcd # Archive
hermes kanban comment t_abcd "Context here..." # Add a comment
# Task graph
hermes kanban link parent_id child_id # Add dependency
hermes kanban unlink parent_id child_id # Remove dependency
# Triage (new)
hermes kanban specify t_abcd # Specify single task
hermes kanban decompose t_abcd # Decompose into task graph
hermes kanban decompose --all # Sweep triage column
hermes profile describe researcher --text "..." # Set profile description
hermes profile describe researcher --auto # LLM-generate description
# Monitoring
hermes kanban watch # Live event stream
hermes kanban runs t_abcd # Attempt history
hermes kanban tail t_abcd # Follow live
hermes kanban diagnostics # Board health
hermes kanban stats # Per-status counts
# Multi-board
hermes kanban boards create my-project --name "My Project"
hermes kanban boards switch my-project
hermes kanban --board my-project list
The Tool Surface (For the Model)#
Workers do not shell out to hermes kanban. When the dispatcher spawns a worker, it sets HERMES_KANBAN_TASK=t_abcd in the child’s environment. This env var flips on a dedicated set of kanban_* tools that appear in the model’s schema:
| Tool | Purpose | Used by |
|---|---|---|
kanban_show() | Read task details, worker context, parent handoffs, comments | Workers |
kanban_list() | List tasks with filters (assignee, status, tenant) | Orchestrators |
kanban_complete(summary, metadata, artifacts) | Finish with structured handoff and optional artifact file paths | Workers |
kanban_block(reason) | Block for human input | Workers |
kanban_heartbeat(note) | Signal liveness during long ops | Workers |
kanban_comment(body) | Append a durable note to the task thread | Workers, Orchestrators |
kanban_create(title, assignee, parents) | Fan out into child tasks | Orchestrators |
kanban_link(parent_id, child_id) | Add a dependency edge after the fact | Orchestrators |
kanban_unblock(task_id) | Move a task back to ready | Orchestrators |
- Backend portability. A worker whose terminal points at Docker/Modal/SSH would run
hermes kanban completeinside the container, wherehermesisn’t installed andkanban.dbisn’t mounted. Tools run in the agent’s own Python process and always reach the DB. - No shell-quoting footguns. Passing
--metadata '{"files": [...]}'through shlex+argparse is fragile. Structured tool args skip it entirely. - Better error handling. Tool results return structured JSON the model can reason about, not stderr strings it has to parse.
The check_fn on each tool only returns True when HERMES_KANBAN_TASK is set or the profile has the kanban toolset enabled. A regular hermes chat session sees zero kanban tools — zero schema bloat for users who never touch it.
The Worker Lifecycle#
When the dispatcher spawns a worker, the very first thing in that agent’s system prompt is a block called KANBAN_GUIDANCE (from agent/prompt_builder.py in the Hermes source). It defines six steps:
Orient. Call
kanban_show()first (no args — it defaults to your task). The response includes title, body, parent-task handoffs (summary + metadata), any prior attempts if you’re a retry, the full comment thread, and a pre-formattedworker_context.Work inside the workspace.
cd $HERMES_KANBAN_WORKSPACEbefore any file operations. The workspace is yours for this run.Heartbeat on long operations. Call
kanban_heartbeat(note=...)every few minutes during long subprocesses. Skips for tasks under about two minutes.Block on genuine ambiguity. If you need a human decision you cannot infer, call
kanban_block(reason="...")and stop. Don’t guess.Complete with structured handoff. Call
kanban_complete(summary=..., metadata=...). Summary is 1-3 human-readable sentences. Metadata is machine-readable facts. Never put secrets or tokens in either.If follow-up work appears, create it; don’t do it. Use
kanban_createto spawn a child task for the appropriate specialist profile instead of scope-creeping.
Plus a hard rule: do not shell out to hermes kanban <verb> for board operations — always use the tools.
The worker also gets the kanban-worker skill auto-loaded (the dispatcher passes --skills kanban-worker on every spawn), which adds deeper detail on good handoff shapes, retry diagnostics, and edge cases.
A Concrete Worker Turn#
Here’s what a real worker looks like from the inside: the tool calls, not the CLI commands:
kanban_show()
# → reads title, body, parent handoffs, worker_context
# (model reads worker_context, does the actual work via terminal/file tools)
kanban_heartbeat(note="migrated 4 of 8 files to token-bucket pattern")
# (more work ; tests, validation)
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,
"tests_passed": 14,
"decisions": ["user_id primary, IP fallback for unauthenticated"]
}
)
The worker never knows its own task id — it’s in $HERMES_KANBAN_TASK, and kanban_show() defaults to it.
The Env Vars a Worker Gets#
When the dispatcher spawns a worker, it injects these environment variables so the worker always knows its context:
| Variable | What it carries |
|---|---|
HERMES_KANBAN_TASK | The task id the worker is operating on |
HERMES_KANBAN_DB | Absolute path to the per-board SQLite file |
HERMES_KANBAN_BOARD | Board slug (workers can't see other boards) |
HERMES_KANBAN_WORKSPACE | Absolute path to this task's workspace |
HERMES_KANBAN_WORKSPACES_ROOT | Root of the board's workspace tree |
HERMES_KANBAN_RUN_ID | Current run's id (for lifecycle gate enforcement) |
HERMES_KANBAN_CLAIM_LOCK | The claim lock string (host:pid:uuid) |
HERMES_PROFILE | The worker's own profile name |
HERMES_TENANT | Tenant namespace, if set |
The Orchestrator Pattern#
An orchestrator is a special kind of kanban profile. Its job isn’t to do work — it’s to decompose high-level goals into task graphs and step back. The canonical pattern is built into the kanban-orchestrator skill and the KANBAN_GUIDANCE system prompt block.
The rules are strict:
- Do not execute the work yourself. Your job is routing, not implementation.
- For any concrete task, create a kanban task and assign it. Every time.
- Split multi-lane requests before creating cards. Extract independent workstreams.
- Run independent lanes in parallel. No link = no dependency.
- Never create dependent work as independent ready cards. Use
parents=[...]. - If no specialist fits the available profiles, ask the user.
- Decompose, route, summarize — that’s the whole job.
A canonical orchestrator turn:
# Discover profiles first (Step 0)
hermes profile list
# → available: "researcher-a", "researcher-b", "writer"
# Fan out parallel research tasks
kanban_create(title="research ICP funding 2024-2026, NA", assignee="researcher-a")
# → returns t_r1
kanban_create(title="research ICP funding, EU angle", assignee="researcher-b")
# → returns t_r2
# Create synthesis task that depends on both
kanban_create(
title="synthesize findings into blog brief",
assignee="writer",
parents=["t_r1", "t_r2"],
body="one-pager, 300 words, neutral tone, cite sources"
)
# Complete own task
kanban_complete(
summary="decomposed into 2 parallel research lanes → 1 synthesis",
metadata={"task_graph": {"t_r1": {...}, "t_r2": {...}, "t_w1": {...}}}
)
For best results, pair the orchestrator with a profile whose toolsets are restricted to board operations (kanban, gateway, memory) so it literally cannot execute implementation tasks even if it tries.
Safety Engineering#
This is where the kanban system really shines, and where the source code reveals details the documentation only hints at.
The Task Ownership Gate#
A process spawned by the dispatcher has HERMES_KANBAN_TASK set to its own task id. The _enforce_worker_task_ownership() function in tools/kanban_tools.py checks every destructive tool call (complete, block, heartbeat) against this env var. If a buggy or prompt-injected worker tries to mutate a foreign task, the tool returns a structured error:
worker is scoped to task t_abcd; refusing to mutate t_efgh. Use kanban_comment to hand off information to other tasks, or kanban_create to spawn follow-up work.
This is defense against a real failure mode: a model hallucinating that it completed someone else’s task. The gate ensures every worker stays in its lane.
The Hallucinated Cards Gate#
When a worker creates child tasks and then calls kanban_complete with created_cards=["t_x", "t_y"], the system verifies that:
- Each claimed card actually exists in the database
- Each card was created by this worker’s profile
If a card id is phantom ; never created, or created by a different profile ; the completion is rejected entirely. The task stays in-flight with no state change, and the phantom ids are reported so the worker can retry with a corrected list. An audit event is already recorded in the DB.
This prevents a failure pattern where a model hallucinates task ids in its completion summary and claims credit for work that doesn’t exist. The gate runs before the write transaction, so the parallel check is safe and the rejection leaves the task mutable.
The Circuit Breaker#
A real world worker fails. Missing credentials, transient network errors, OOM kills. The system tracks how many consecutive times a task has failed (via consecutive_failures on the task row). The counter increments on:
- Spawn failure ; the dispatcher couldn’t launch the worker process
- Crash ; the worker PID vanished (segfault, OOM, signal)
- Timeout ; the worker exceeded
max_runtime_seconds
The counter only resets to 0 on a successful kanban_complete. After N consecutive failures (default: 2, configurable via kanban.failure_limit), the breaker trips and auto-blocks the task with the last error as its reason. No more retries until a human unblocks it.
A per-task override via --max-retries on kanban create lets you tune this for tasks that should fail fast (a deploy task with missing credentials should block immediately) versus tasks that should be retried more aggressively (a flaky API call).
Crash Recovery#
Between circuit breaker trips, individual crashes are handled gracefully. The flow:
- Dispatcher detects dead PID (
kill(pid, 0)returns nonzero) - The claim is released and the task goes back to
ready - The failed attempt is recorded in
task_runswith outcomecrashedand the error text consecutive_failuresincrements- Next tick: the task is claimed again (if under the failure limit)
- The new worker calls
kanban_show()and sees the prior attempt’s error in itsworker_context; so it knows what went wrong and can try a different approach
The attempt history is never lost. Every crash, every retry, every outcome is a row in task_runs forever.
Run History and the Structured Handoff#
Every time a task is claimed, a new row is created in the task_runs table. The run carries the claim state, PID, heartbeat timestamps, and runtime cap. When the worker finishes (via kanban_complete or kanban_block or crash), the run is closed with an outcome, a summary, and metadata.
This means a task that was blocked, retried, crashed, and finally completed shows up with four runs:
# OUTCOME PROFILE ELAPSED SUMMARY
1 blocked backend-dev 0s review-required: password strength check missing
2 crashed backend-dev 47s OOM at row 2.3M
3 completed backend-dev 82s added zxcvbn, single-use reset tokens, 11 tests
Each run is a separate row with its own outcome, summary, and metadata. When a new worker spawns and calls kanban_show(), the worker_context includes the full run history ; so a retrying worker doesn’t repeat the same mistakes.
The Handoff Convention#
The summary + metadata pair on kanban_complete is the primary communication channel between stages of a pipeline. For engineering tasks, the recommended shape is:
{
"changed_files": ["path/to/file.py"],
"verification": ["pytest tests/ -x"],
"dependencies": ["t_abc (parent task)"],
"decisions": ["token-bucket over sliding-window"],
"residual_risk": ["edge case in IP fallback not tested"]
}
Downstream workers read these fields via their own kanban_show() call, which surfaces parent task results structurally. A reviewer worker, for example, sees the changed files list and test results without having to re-read the implementation’s long output.
Artifact Delivery (New)#
Workers can now attach files directly to completions. Pass absolute paths in the artifacts parameter of kanban_complete:
kanban_complete(
summary="generated quarterly report with charts",
metadata={"rows_processed": 14230},
artifacts=["/path/to/report.pdf", "/path/to/chart.png"],
)
The gateway detects these paths, partitions them by type (images inline, documents as uploads), and delivers them as native platform attachments on Telegram, Discord, Slack, and other messaging platforms. Supported formats include PDFs, spreadsheets (csv/xlsx), documents (docx), presentations (pptx), archives (zip/tar.gz), images, video, and audio.
This closes the gap between “the agent produced a file” and “the user received it” — no more retrieving files from remote workspaces or asking the agent to re-explain what it generated.
For tasks that need human review, the review-required convention is to block instead of complete: drop the structured metadata (changed files, test counts, diff path) into a comment first, then end with kanban_block(reason="review-required: <one-line summary>").
Multi-Board Isolation#
One Hermes install, many kanban boards. Each board is a completely isolated queue:
- Separate SQLite DB per board (
~/.hermes/kanban/boards/<slug>/kanban.db) - Separate workspace and log directories
- Workers can’t see other boards ; the dispatcher pins
HERMES_KANBAN_BOARDin the child env - No cross-board links ; task dependencies are per-board
Managing boards from the CLI:
# Create a board for a different project
hermes kanban boards create atm10-server \
--name "ATM10 Server" \
--description "Minecraft modded server ops" \
--icon "🎮"
# Switch to it for subsequent commands
hermes kanban boards switch atm10-server
# Or scope a single command to a specific board
hermes kanban --board atm10-server list
Board resolution follows a precedence chain:
- Explicit
--board <slug>flag HERMES_KANBAN_BOARDenv var~/.hermes/kanban/current(persisted byhermes kanban boards switch)defaultboard
The default board keeps its legacy path (~/.hermes/kanban.db) for back-compatibility. All other boards live in the boards/ directory.
If you’re managing multiple repos or domains, boards give you isolated queues without needing separate Hermes installs or profiles.
The Triage Specifier and Decomposer#
Not every task starts with a fully-defined spec, and not every idea fits in one card. The triage column now has two ways to promote rough ideas into work, depending on whether you need a single task or a full pipeline.
The Specifier (Single Task)#
For ideas that become a single concrete task, the triage specifier turns a one-liner into a full spec. A task created with --triage lands in the triage column:
hermes kanban create "Investigate Postgres migration costs" --triage
Run the specifier to expand it:
hermes kanban specify t_abcd # specify one task
hermes kanban specify --all # sweep the whole triage column
The specifier is an auxiliary LLM (configured under auxiliary.triage_specifier in config.yaml). It produces JSON with a tightened title and a body containing Goal, Approach, Acceptance criteria, and Out of scope. After specifying, the task promotes from triage to todo.
The Decomposer (Task Graph) (New)#
For ideas that need multiple specialists working in parallel — a research task that needs a writer, a designer, and a reviewer — the decomposer produces a full task graph with dependency edges:
hermes kanban decompose t_abcd # decompose one triage task
hermes kanban decompose --all # sweep the entire triage column
The decomposer receives a roster of all available profiles along with their descriptions and produces a JSON task graph with child tasks routed to the right specialists. It also runs automatically during each dispatcher tick (Phase 3), capped by kanban.auto_decompose_per_tick (default 3). Drop a triage task and within 60 seconds the orchestrator has decomposed it, the dispatcher is spawning workers, and the pipeline is running.
Unknown assignee names are rewritten to kanban.default_assignee so no task ever lands unassigned. The root triage task stays in todo gated by every child — when the whole graph finishes, the orchestrator wakes up to judge completion.
Profile Descriptions#
For the decomposer to route effectively, profiles need descriptions. Set them manually:
hermes profile describe researcher-a --text "Research specialist: reads papers, benchmarks models, writes briefs"
hermes profile describe writer --text "Writer: produces blog posts, documentation, and technical tutorials"
Or let the LLM generate one from the profile’s skills, model, and name:
hermes profile describe researcher-a --auto
These descriptions are stored in each profile’s profile.yaml and used by the decomposer to match task requirements to the right profile.
The dashboard adds both ✨ Specify and ⚗ Decompose buttons on triage card drawers, plus an Orchestration settings panel for configuring the orchestrator profile, default assignee, and auto-decompose toggle.
Configuration Reference#
The new features add several knobs in your ~/.hermes/config.yaml:
| Key | Default | What it controls |
|---|---|---|
kanban.orchestrator_profile | "" (active profile) | Profile that runs decomposition when a triage task lands |
kanban.default_assignee | "" (active profile) | Fallback assignee when the LLM decomposer returns an unknown profile name |
kanban.auto_decompose | true | Whether the dispatcher auto-decomposes triage tasks before each tick |
kanban.auto_decompose_per_tick | 3 | Maximum triage tasks to decompose in a single tick (burst protection) |
auxiliary.kanban_decomposer | (none) | Auxiliary LLM slot for the decomposer — set provider/model here |
auxiliary.profile_describer | (none) | Auxiliary LLM slot for auto-generating profile descriptions |
Set them with hermes config set:
hermes config set kanban.orchestrator_profile orchestrator
hermes config set kanban.default_assignee researcher
hermes config set kanban.auto_decompose_per_tick 5
Or edit directly: hermes config edit.
The kanban system turns Hermes from a single-agent tool into a multi-agent platform. The SQLite-backed board, the dispatcher’s six-phase tick, the worker lifecycle with crash recovery and circuit breakers — these aren’t features you’ll find in most agent frameworks. They’re the difference between fragile subagent swarms and reliable multi-agent coordination.
The best way to understand it is to use it. Create a board, drop a triage task, watch the dispatcher decompose and spawn. Block a task and see what happens. Crash a worker on purpose. The system is designed to survive exactly these experiments.
And now you know how it works under the hood — not just the CLI commands, but the SQLite compare-and-swap claims, the kill(pid, 0) crash probes, the hallucinated-cards gate, and the structured handoff that makes pipelines possible. Use it well.
