Skip to content

Commit 27572bb

Browse files
teknium1jquesnelle
authored andcommitted
feat(kanban): durable multi-profile collaboration board (NousResearch#17805)
Salvage of PR NousResearch#16100 onto current main (after emozilla's NousResearch#17514 fix that unblocks plugin Pydantic body validation). History preserved on the standing `feat/kanban-standing` branch; this squashes the 22 iterative commits into one clean landing. What this lands: - SQLite kernel (hermes_cli/kanban_db.py) — durable task board with tasks, task_links, task_runs, task_comments, task_events, kanban_notify_subs tables. WAL mode, atomic claim via CAS, tenant-namespaced, skills JSON array per task, max-runtime timeouts, worker heartbeats, idempotency keys, circuit breaker on repeated spawn failures, crash detection via /proc/<pid>/status, run history preserved across attempts. - Dispatcher — runs inside the gateway by default (`kanban.dispatch_in_gateway: true`). Ticks every 60s, reclaims stale claims, promotes ready tasks, spawns `hermes -p <assignee> chat -q "work kanban task <id>"` with HERMES_KANBAN_TASK + HERMES_KANBAN_WORKSPACE env. Auto-loads `--skills kanban-worker` plus any per-task skills. Health telemetry warns on stuck ready queue. - Structured tool surface (tools/kanban_tools.py) — 7 tools (kanban_show, kanban_complete, kanban_block, kanban_heartbeat, kanban_comment, kanban_create, kanban_link). Gated on HERMES_KANBAN_TASK via check_fn so zero schema footprint in normal sessions. - System-prompt guidance (agent/prompt_builder.py KANBAN_GUIDANCE) injected only when kanban tools are active. - Dashboard plugin (plugins/kanban/dashboard/) — Linear-style board UI: triage/todo/ready/running/blocked/done columns, drag-drop, inline create, task drawer with markdown, comments, run history, dependency editor, bulk ops, lanes-by-profile grouping, WS-driven live refresh. Matches active dashboard theme via CSS variables. - CLI — `hermes kanban init|create|list|show|assign|link|unlink| claim|comment|complete|block|unblock|archive|tail|dispatch|context| init|gc|watch|stats|notify|log|heartbeat|runs|assignees` + `/kanban` slash in-session. - Worker + orchestrator skills (skills/devops/kanban-worker + kanban-orchestrator) — pattern library for good summary/metadata shapes, retry diagnostics, block-reason examples, fan-out patterns. - Per-task force-loaded skills — `--skill <name>` (repeatable), stored as JSON, threaded through to dispatcher argv as one `--skills X` pair per skill alongside the built-in kanban-worker. Dashboard + CLI + tool parity. - Deprecation of standalone `hermes kanban daemon` — stub exits 2 with migration guidance; `--force` escape hatch for headless hosts. - Docs (website/docs/user-guide/features/kanban.md + kanban-tutorial.md) with 11 dashboard screenshots walking through four user stories (Solo Dev, Fleet Farming, Role Pipeline, Circuit Breaker). - Tests (251 passing): kernel schema + migration + CAS atomicity, dispatcher logic, circuit breaker, crash detection, max-runtime timeouts, claim lifecycle, tenant isolation, idempotency keys, per- task skills round-trip + validation + dispatcher argv, tool surface (7 tools × round-trip + error paths), dashboard REST (CRUD + bulk + links + warnings), gateway-embedded dispatcher (config gate, env override, graceful shutdown), CLI deprecation stub, migration from legacy schemas. Gateway integration: - GatewayRunner._kanban_dispatcher_watcher — new asyncio background task, symmetric with _kanban_notifier_watcher. Runs dispatch_once via asyncio.to_thread so SQLite WAL never blocks the loop. Sleeps in 1s slices for snappy shutdown. Respects HERMES_KANBAN_DISPATCH_IN_GATEWAY=0 env override for debugging. - Config: new `kanban` section in DEFAULT_CONFIG with `dispatch_in_gateway: true` (default) + `dispatch_interval_seconds: 60`. Additive — no \_config_version bump needed. Forward-compat: - workflow_template_id / current_step_key columns on tasks (v1 writes NULL; v2 will use them for routing). - task_runs holds claim machinery (claim_lock, claim_expires, worker_pid, last_heartbeat_at) so multi-attempt history is first- class from day one. Closes NousResearch#16102. Co-authored-by: emozilla <emozilla@nousresearch.com>
1 parent 6f1e6f5 commit 27572bb

48 files changed

Lines changed: 17420 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

agent/prompt_builder.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,64 @@ def _strip_yaml_frontmatter(content: str) -> str:
182182
"Skills that aren't maintained become liabilities."
183183
)
184184

185+
KANBAN_GUIDANCE = (
186+
"# You are a Kanban worker\n"
187+
"You were spawned by the Hermes Kanban dispatcher to execute ONE task from "
188+
"the shared board at `~/.hermes/kanban.db`. Your task id is in "
189+
"`$HERMES_KANBAN_TASK`; your workspace is `$HERMES_KANBAN_WORKSPACE`. "
190+
"The `kanban_*` tools in your schema are your primary coordination surface — "
191+
"they write directly to the shared SQLite DB and work regardless of terminal "
192+
"backend (local/docker/modal/ssh).\n"
193+
"\n"
194+
"## Lifecycle\n"
195+
"\n"
196+
"1. **Orient.** Call `kanban_show()` first (no args — it defaults to your "
197+
"task). The response includes title, body, parent-task handoffs (summary + "
198+
"metadata), any prior attempts on this task if you're a retry, the full "
199+
"comment thread, and a pre-formatted `worker_context` you can treat as "
200+
"ground truth.\n"
201+
"2. **Work inside the workspace.** `cd $HERMES_KANBAN_WORKSPACE` before "
202+
"any file operations. The workspace is yours for this run. Don't modify "
203+
"files outside it unless the task explicitly asks.\n"
204+
"3. **Heartbeat on long operations.** Call `kanban_heartbeat(note=...)` "
205+
"every few minutes during long subprocesses (training, encoding, crawling). "
206+
"Skip heartbeats for short tasks.\n"
207+
"4. **Block on genuine ambiguity.** If you need a human decision you cannot "
208+
"infer (missing credentials, UX choice, paywalled source, peer output you "
209+
"need first), call `kanban_block(reason=\"...\")` and stop. Don't guess. "
210+
"The user will unblock with context and the dispatcher will respawn you.\n"
211+
"5. **Complete with structured handoff.** Call `kanban_complete(summary=..., "
212+
"metadata=...)`. `summary` is 1–3 human-readable sentences naming concrete "
213+
"artifacts. `metadata` is machine-readable facts "
214+
"(`{changed_files: [...], tests_run: N, decisions: [...]}`). Downstream "
215+
"workers read both via their own `kanban_show`. Never put secrets / "
216+
"tokens / raw PII in either field — run rows are durable forever.\n"
217+
"6. **If follow-up work appears, create it; don't do it.** Use "
218+
"`kanban_create(title=..., assignee=<right-profile>, parents=[your-task-id])` "
219+
"to spawn a child task for the appropriate specialist profile instead of "
220+
"scope-creeping into the next thing.\n"
221+
"\n"
222+
"## Orchestrator mode\n"
223+
"\n"
224+
"If your task is itself a decomposition task (e.g. a planner profile given "
225+
"a high-level goal), use `kanban_create` to fan out into child tasks — one "
226+
"per specialist, each with an explicit `assignee` and `parents=[...]` to "
227+
"express dependencies. Then `kanban_complete` your own task with a summary "
228+
"of the decomposition. Do NOT execute the work yourself; your job is "
229+
"routing, not implementation.\n"
230+
"\n"
231+
"## Do NOT\n"
232+
"\n"
233+
"- Do not shell out to `hermes kanban <verb>` for board operations. Use "
234+
"the `kanban_*` tools — they work across all terminal backends.\n"
235+
"- Do not complete a task you didn't actually finish. Block it.\n"
236+
"- Do not assign follow-up work to yourself. Assign it to the right "
237+
"specialist profile.\n"
238+
"- Do not call `delegate_task` as a board substitute. `delegate_task` is "
239+
"for short reasoning subtasks inside your own run; board tasks are for "
240+
"cross-agent handoffs that outlive one API loop."
241+
)
242+
185243
TOOL_USE_ENFORCEMENT_GUIDANCE = (
186244
"# Tool-use enforcement\n"
187245
"You MUST use your tools to take action — do not describe what you would do "

cli.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6087,6 +6087,27 @@ def _handle_curator_command(self, cmd: str):
60876087
except Exception as exc:
60886088
print(f"(._.) curator: {exc}")
60896089

6090+
def _handle_kanban_command(self, cmd: str):
6091+
"""Handle the /kanban command — delegate to the shared kanban CLI.
6092+
6093+
The string form passed here is the user's full ``/kanban ...``
6094+
including the leading slash; we strip it and hand the remainder
6095+
to ``kanban.run_slash`` which returns a single formatted string.
6096+
"""
6097+
from hermes_cli.kanban import run_slash
6098+
6099+
rest = cmd.strip()
6100+
if rest.startswith("/"):
6101+
rest = rest.lstrip("/")
6102+
if rest.startswith("kanban"):
6103+
rest = rest[len("kanban"):].lstrip()
6104+
try:
6105+
output = run_slash(rest)
6106+
except Exception as exc: # pragma: no cover - defensive
6107+
output = f"(._.) kanban error: {exc}"
6108+
if output:
6109+
print(output)
6110+
60906111
def _handle_skills_command(self, cmd: str):
60916112
"""Handle /skills slash command — delegates to hermes_cli.skills_hub."""
60926113
from hermes_cli.skills_hub import handle_skills_slash
@@ -6332,6 +6353,8 @@ def process_command(self, command: str) -> bool:
63326353
self._handle_cron_command(cmd_original)
63336354
elif canonical == "curator":
63346355
self._handle_curator_command(cmd_original)
6356+
elif canonical == "kanban":
6357+
self._handle_kanban_command(cmd_original)
63356358
elif canonical == "skills":
63366359
with self._busy_command(self._slow_command_status(cmd_original)):
63376360
self._handle_skills_command(cmd_original)

docs/hermes-kanban-v1-spec.pdf

209 KB
Binary file not shown.

0 commit comments

Comments
 (0)