From fda66c488b0e2907c312fba197b905008a686db6 Mon Sep 17 00:00:00 2001 From: Gille <4317663+helix4u@users.noreply.github.com> Date: Sat, 6 Jun 2026 15:26:56 -0600 Subject: [PATCH] docs(kanban): clarify decomposer profile roles --- hermes_cli/config.py | 10 +++++----- hermes_cli/kanban_decompose.py | 4 ++-- plugins/kanban/dashboard/dist/index.js | 4 ++-- plugins/kanban/dashboard/plugin_api.py | 2 +- website/docs/reference/cli-commands.md | 2 +- website/docs/user-guide/features/kanban-tutorial.md | 2 +- website/docs/user-guide/features/kanban.md | 10 ++++++---- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/hermes_cli/config.py b/hermes_cli/config.py index 76f008e3d3a..ce385869353 100644 --- a/hermes_cli/config.py +++ b/hermes_cli/config.py @@ -2016,11 +2016,11 @@ DEFAULT_CONFIG = { # raise these to keep more early failure evidence. "worker_log_rotate_bytes": 2 * 1024 * 1024, "worker_log_backup_count": 1, - # Profile that decomposes tasks in the Triage column. When unset, - # falls back to the default profile (the one `hermes` launches with - # no -p flag). Set this to a dedicated 'orchestrator' profile if you - # want decomposition to use a different model/skills from your main - # working profile. + # Profile assigned to the root/orchestration task after Triage + # decomposition. When unset, falls back to the default profile (the + # one `hermes` launches with no -p flag). This does not control the + # decomposer prompt, model, or skills; configure that LLM path under + # auxiliary.kanban_decomposer. "orchestrator_profile": "", # Where a child task lands if the orchestrator can't match an # assignee to any installed profile. When unset, falls back to the diff --git a/hermes_cli/kanban_decompose.py b/hermes_cli/kanban_decompose.py index dec7c0b7c72..8af2da9114f 100644 --- a/hermes_cli/kanban_decompose.py +++ b/hermes_cli/kanban_decompose.py @@ -20,7 +20,7 @@ Design notes * The system prompt sees the *configured* profile roster — names plus descriptions plus the default fallback. Profiles without a - description are still listed (with a note) so the orchestrator can + description are still listed (with a note) so the decomposer can match on name as a fallback, but the user has an obvious incentive to describe them. @@ -178,7 +178,7 @@ def _load_config() -> dict: def _resolve_orchestrator_profile(cfg: dict) -> str: - """Resolve which profile owns decomposition. + """Resolve which profile owns the root/orchestration task after fan-out. Falls back to the active default profile when ``kanban.orchestrator_profile`` is unset, so a task is never stranded for lack of an orchestrator. diff --git a/plugins/kanban/dashboard/dist/index.js b/plugins/kanban/dashboard/dist/index.js index 5195c50c9f1..4eec726ddf0 100644 --- a/plugins/kanban/dashboard/dist/index.js +++ b/plugins/kanban/dashboard/dist/index.js @@ -3744,9 +3744,9 @@ }, specifyBusy ? "Specifying…" : "✨ Specify") : null; - // "Decompose" is the orchestrator-driven fan-out. Like Specify, only + // "Decompose" is the built-in decomposer fan-out. Like Specify, only // makes sense on triage-column tasks — elsewhere the backend short- - // circuits with ok:false. When the orchestrator returns fanout:false + // circuits with ok:false. When the decomposer returns fanout:false // we render the same single-task message as Specify; when it fans // out we report the child count for quick at-a-glance verification. const decomposeButton = (task.status === "triage" && props.onDecompose) diff --git a/plugins/kanban/dashboard/plugin_api.py b/plugins/kanban/dashboard/plugin_api.py index 0a49172a86b..e340aba97bf 100644 --- a/plugins/kanban/dashboard/plugin_api.py +++ b/plugins/kanban/dashboard/plugin_api.py @@ -2204,7 +2204,7 @@ def auto_describe_profile(profile_name: str, payload: DescribeAutoBody): # --------------------------------------------------------------------------- -# Decompose endpoint (orchestrator-driven fan-out) +# Decompose endpoint (built-in decomposer fan-out) # --------------------------------------------------------------------------- class DecomposeBody(BaseModel): diff --git a/website/docs/reference/cli-commands.md b/website/docs/reference/cli-commands.md index 0e5c719522a..7c37e9d8144 100644 --- a/website/docs/reference/cli-commands.md +++ b/website/docs/reference/cli-commands.md @@ -557,7 +557,7 @@ Multi-profile, multi-project collaboration board. Each install can host many boa | `dispatch` | One dispatcher pass on the active board. Flags: `--dry-run`, `--max N`, `--failure-limit N`, `--json`. | | `context ` | Print the full context a worker would see (title + body + parent results + comments). | | `specify ` / `specify --all` | Flesh out a triage-column task into a concrete spec (title + body with goal, approach, acceptance criteria) via the auxiliary LLM, then promote it to `todo`. Flags: `--tenant` (scope `--all` to one tenant), `--author`, `--json`. Configure the model under `auxiliary.triage_specifier` in `config.yaml`. | -| `decompose ` / `decompose --all` | Fan a triage-column task out into a graph of child tasks routed to specialist profiles by description (the orchestrator-driven path). Falls back to specify-style single-task promotion when the LLM decides the task doesn't benefit from fan-out. Same flags as `specify`. Configure the model under `auxiliary.kanban_decomposer` in `config.yaml`. Also runs automatically every dispatcher tick when `kanban.auto_decompose: true` (the default). See [Auto vs Manual orchestration](/user-guide/features/kanban#auto-vs-manual-orchestration). | +| `decompose ` / `decompose --all` | Fan a triage-column task out into a graph of child tasks routed to specialist profiles by description. Falls back to specify-style single-task promotion when the LLM decides the task doesn't benefit from fan-out. Same flags as `specify`. Configure the decomposer model under `auxiliary.kanban_decomposer` in `config.yaml`; `kanban.orchestrator_profile` only controls who owns the root/orchestration task after fan-out. Also runs automatically every dispatcher tick when `kanban.auto_decompose: true` (the default). See [Auto vs Manual orchestration](/user-guide/features/kanban#auto-vs-manual-orchestration). | | `gc` | Remove scratch workspaces for archived tasks. | Examples: diff --git a/website/docs/user-guide/features/kanban-tutorial.md b/website/docs/user-guide/features/kanban-tutorial.md index 94a01fc36b1..7224ce5cbb1 100644 --- a/website/docs/user-guide/features/kanban-tutorial.md +++ b/website/docs/user-guide/features/kanban-tutorial.md @@ -22,7 +22,7 @@ Throughout the tutorial, **code blocks labelled `bash` are commands *you* run.** Six columns, left to right: -- **Triage** — raw ideas. By default the dispatcher auto-runs the **decomposer** (orchestrator-driven fan-out) on tasks here: it reads your profile roster + descriptions and produces a graph of child tasks routed to the best-fit specialists, with the original task held alive as the parent so the orchestrator wakes back up to judge completion when everything finishes. Flip the **Orchestration: Auto/Manual** pill at the top of the kanban page to switch modes. In Manual mode (or for setups without an orchestrator profile) click **⚗ Decompose** on a card, or run `hermes kanban decompose ` / `/kanban decompose `. For single tasks that don't need fan-out, **✨ Specify** does a one-shot spec rewrite (goal, approach, acceptance criteria) and promotes to `todo`. Configure the models under `auxiliary.kanban_decomposer` and `auxiliary.triage_specifier` in `config.yaml`. See [Auto vs Manual orchestration](./kanban#auto-vs-manual-orchestration) in the main Kanban guide. +- **Triage** — raw ideas. By default the dispatcher auto-runs the **decomposer** on tasks here: the built-in decomposer uses `auxiliary.kanban_decomposer`, reads your profile roster + descriptions, and produces a graph of child tasks routed to the best-fit specialists. The original task is held alive as the parent so its assignee (`kanban.orchestrator_profile`, or the active default profile when unset) wakes back up to judge completion when everything finishes. Flip the **Orchestration: Auto/Manual** pill at the top of the kanban page to switch modes. In Manual mode click **⚗ Decompose** on a card, or run `hermes kanban decompose ` / `/kanban decompose `. For single tasks that don't need fan-out, **✨ Specify** does a one-shot spec rewrite (goal, approach, acceptance criteria) and promotes to `todo`. Configure the models under `auxiliary.kanban_decomposer` and `auxiliary.triage_specifier` in `config.yaml`. See [Auto vs Manual orchestration](./kanban#auto-vs-manual-orchestration) in the main Kanban guide. - **Todo** — created but waiting on dependencies, or not yet assigned. - **Ready** — assigned and waiting for the dispatcher to claim. - **In progress** — a worker is actively running the task. With "Lanes by profile" on (the default), this column sub-groups by assignee so you can see at a glance what each worker is doing. diff --git a/website/docs/user-guide/features/kanban.md b/website/docs/user-guide/features/kanban.md index 4c8ae55e8fc..73a7b88a107 100644 --- a/website/docs/user-guide/features/kanban.md +++ b/website/docs/user-guide/features/kanban.md @@ -495,7 +495,7 @@ hermes dashboard # "Kanban" tab appears in the nav, after "Skills" ### What the plugin gives you - A **Kanban** tab showing one column per status: `triage`, `todo`, `ready`, `running`, `blocked`, `done` (plus `archived` when the toggle is on). - - `triage` is the parking column for rough ideas. By default (`kanban.auto_decompose: true`), the dispatcher auto-runs the **decomposer** on tasks that land here — the orchestrator profile reads the rough idea, looks at your profile roster (with descriptions), and fans the task out into a small graph of child tasks routed to the best-fit specialists. The original task stays alive as the parent of every child so the orchestrator wakes back up to judge completion when everything finishes. Flip the **Orchestration: Auto/Manual** pill at the top of the page (or set `kanban.auto_decompose: false`) to switch to manual mode, where triage tasks stay put until you click **⚗ Decompose** on a card or run `hermes kanban decompose `. For tasks that don't need fan-out (or for setups without an orchestrator profile), the **✨ Specify** button does a single-task spec rewrite (title + body with goal, approach, acceptance criteria) via the same LLM machinery. See [Auto vs Manual orchestration](#auto-vs-manual-orchestration) below. + - `triage` is the parking column for rough ideas. By default (`kanban.auto_decompose: true`), the dispatcher auto-runs the **decomposer** on tasks that land here. The built-in decomposer uses the `auxiliary.kanban_decomposer` model path, reads your profile roster (with descriptions), and fans the task out into a small graph of child tasks routed to the best-fit specialists. The original task stays alive as the parent of every child so its assignee (`kanban.orchestrator_profile`, or the active default profile when unset) wakes back up to judge completion when everything finishes. Flip the **Orchestration: Auto/Manual** pill at the top of the page (emerald = Auto, muted gray = Manual), or by editing `config.yaml` directly. Both modes coexist with `hermes kanban specify` - that's still available as a single-task spec rewrite when you don't want fan-out. - Cards show the task id, title, priority badge, tenant tag, assigned profile, comment/link counts, a **progress pill** (`N/M` children done when the task has dependents), and "created N ago". A per-card checkbox enables multi-select. - **Per-profile lanes inside Running** — toolbar checkbox toggles sub-grouping of the Running column by assignee. - **Live updates via WebSocket** — the plugin tails the append-only `task_events` table on a short poll interval; the board reflects changes the instant any profile (CLI, gateway, or another dashboard tab) acts. Reloads are debounced so a burst of events triggers a single refetch. @@ -507,7 +507,7 @@ hermes dashboard # "Kanban" tab appears in the nav, after "Skills" - **Editable assignee / priority** — click the meta row to rewrite. - **Editable description** — markdown-rendered by default (headings, bold, italic, inline code, fenced code, `http(s)` / `mailto:` links, bullet lists), with an "edit" button that swaps in a textarea. Markdown rendering is a tiny, XSS-safe renderer — every substitution runs on HTML-escaped input, only `http(s)` / `mailto:` links pass through, and `target="_blank"` + `rel="noopener noreferrer"` are always set. - **Dependency editor** — chip list of parents and children, each with an `×` to unlink, plus dropdowns over every other task to add a new parent or child. Cycle attempts are rejected server-side with a clear message. - - **Status action row** (→ triage / → ready / → running / block / unblock / complete / archive) with confirm prompts for destructive transitions. For cards in the **Triage** column the row also exposes two LLM-driven actions: **⚗ Decompose** fans the task out into a graph of child tasks routed to specialist profiles by description (the orchestrator-driven path), and **✨ Specify** does a single-task spec rewrite. Decompose falls back to specify-style promotion when the LLM decides the task doesn't benefit from fan-out, so it's a strict superset. Both are reachable from the CLI (`hermes kanban decompose ` / `specify ` / `--all`), from any gateway platform (`/kanban decompose `), and programmatically via `POST /api/plugins/kanban/tasks/:id/decompose` and `…/specify`. Configure the models under `auxiliary.kanban_decomposer` and `auxiliary.triage_specifier` in `config.yaml`. + - **Status action row** (→ triage / → ready / → running / block / unblock / complete / archive) with confirm prompts for destructive transitions. For cards in the **Triage** column the row also exposes two LLM-driven actions: **⚗ Decompose** fans the task out into a graph of child tasks routed to specialist profiles by description, and **✨ Specify** does a single-task spec rewrite. Decompose falls back to specify-style promotion when the LLM decides the task doesn't benefit from fan-out, so it's a strict superset. Both are reachable from the CLI (`hermes kanban decompose ` / `specify ` / `--all`), from any gateway platform (`/kanban decompose `), and programmatically via `POST /api/plugins/kanban/tasks/:id/decompose` and `…/specify`. Configure the models under `auxiliary.kanban_decomposer` and `auxiliary.triage_specifier` in `config.yaml`. - Result section (also markdown-rendered), comment thread with Enter-to-submit, the last 20 events. - **Toolbar filters** — free-text search, tenant dropdown (defaults to `dashboard.kanban.default_tenant` from `config.yaml`), assignee dropdown, "show archived" toggle, "lanes by profile" toggle, and a **Nudge dispatcher** button so you don't have to wait for the next 60 s tick. @@ -517,7 +517,7 @@ Visually the target is the familiar Linear / Fusion layout: dark theme, column h The kanban board has two ways to handle a task you drop into the Triage column: -**Auto (default)** — `kanban.auto_decompose: true`. The gateway-embedded dispatcher runs the **decomposer** on each tick, capped by `kanban.auto_decompose_per_tick` (default 3 tasks per tick) so a bulk-load of triage tasks doesn't burst-spend the auxiliary LLM. The decomposer reads the rough idea, looks at your installed profiles + their descriptions, and asks the LLM to produce a JSON task graph: which tasks to spawn, who they go to, and which depend on which. The original triage task becomes the parent of every leaf in the graph, so it stays alive until the whole graph completes — and then promotes back to `ready` so its assignee (the orchestrator profile) can judge completion and add more tasks if the work isn't done. This is the "drop a one-liner, walk away" flow. +**Auto (default)** — `kanban.auto_decompose: true`. The gateway-embedded dispatcher runs the **decomposer** on each tick, capped by `kanban.auto_decompose_per_tick` (default 3 tasks per tick) so a bulk-load of triage tasks doesn't burst-spend the auxiliary LLM. The decomposer uses the built-in decomposition prompt plus the `auxiliary.kanban_decomposer` model path, reads your installed profiles + their descriptions, and asks the LLM to produce a JSON task graph: which tasks to spawn, who they go to, and which depend on which. The original triage task becomes the parent of every leaf in the graph, so it stays alive until the whole graph completes - and then promotes back to `ready` so its assignee (`kanban.orchestrator_profile`, or the active default profile when unset) can judge completion and add more tasks if the work isn't done. This is the "drop a one-liner, walk away" flow. **Manual** — `kanban.auto_decompose: false`. Triage tasks stay in triage until you act. Click the **⚗ Decompose** button on a card, run `hermes kanban decompose ` (or `--all`), or use `/kanban decompose ` from a chat. This matches the pre-decomposer behavior of the board, useful when you want full control over what runs when. @@ -525,13 +525,15 @@ Flip between the two modes from the **Orchestration: Auto/Manual** pill at the t The decomposer's routing decisions depend on profile descriptions, which is a per-profile labeling primitive you set with `hermes profile create --description "..."`, `hermes profile describe --text "..."`, `hermes profile describe --auto` (LLM-generates from the profile's installed skills + model), or the dashboard's per-profile editor in the expanded **Orchestration settings** panel. Profiles without a description still appear in the roster — they're routable by name, just less precisely. The decomposer NEVER lands a child task with `assignee=None`: when the LLM picks an unknown profile, the child gets routed to `kanban.default_assignee` (or the active default profile if that's unset). +`kanban.orchestrator_profile` does not load that profile's prompt, skills, or custom logic into the decomposition call. It controls who owns the root/orchestration task after fan-out. To change the decomposer's model/provider, configure `auxiliary.kanban_decomposer`. To use a profile's custom task-splitting logic instead of the built-in decomposer, switch to Manual mode and have that profile create or decompose tasks explicitly. + Config knobs (all under `kanban:` in `~/.hermes/config.yaml`): | Key | Default | Purpose | |---|---|---| | `auto_decompose` | `true` | Dispatcher auto-runs the decomposer every tick. | | `auto_decompose_per_tick` | `3` | Cap on decompositions per dispatcher tick. Excess defers to the next tick. | -| `orchestrator_profile` | `""` | Profile that owns decomposition. Empty = fall back to active default profile. | +| `orchestrator_profile` | `""` | Profile assigned to the root/orchestration task after decomposition. Empty = fall back to active default profile. | | `default_assignee` | `""` | Where a child task lands when the LLM picks an unknown profile. Empty = fall back to active default. | And the two auxiliary LLM slots: