Skip to content

feat(kanban): orchestrator-driven auto-decomposition on triage#27572

Merged
teknium1 merged 4 commits into
mainfrom
hermes/hermes-58d845e6
May 17, 2026
Merged

feat(kanban): orchestrator-driven auto-decomposition on triage#27572
teknium1 merged 4 commits into
mainfrom
hermes/hermes-58d845e6

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

Drop a one-liner into the Kanban Triage column and an orchestrator profile decomposes it into a graph of child tasks routed to specialist profiles by description — matching the original vision the kanban system was pitched on but never quite delivered on.

This closes priority #1 of the kanban feedback Teknium gathered on 5/11 ("the way I initially envisioned the kanban was this: User puts an issue on triage block, main orchestrator agent then splits/creates actual tasks from it and doles them out to each agent…").

Changes

Profile descriptions (new labeling primitive)

  • hermes_cli/profiles.py: new description + description_auto fields on ProfileInfo, persisted in <profile_dir>/profile.yaml. Helpers read_profile_meta / write_profile_meta.
  • hermes_cli/profile_describer.py: new module — auto-generate a 1-2 sentence description from skills + model + name via auxiliary.profile_describer.
  • hermes profile create --description "..." flag.
  • hermes profile describe [name] [--text "..." | --auto | --all --auto] subcommand.

Decomposer

  • hermes_cli/kanban_db.py: new decompose_triage_task atomic helper. Creates N child tasks, links the root as a child of every leaf (root waits for the whole graph), flips root triage → todo, records audit comment + decomposed event — all in a single write_txn.
  • hermes_cli/kanban_decompose.py: new module — calls the auxiliary LLM with the profile roster + descriptions, parses the JSON task graph, rewrites unknown assignees to the configured kanban.default_assignee so a task never lands with assignee=None. Falls back to specify-style single-task promotion when the LLM returns fanout: false.
  • hermes kanban decompose [task_id | --all] CLI verb.

Auto-trigger

  • gateway/run.py: kanban dispatcher watcher now runs auto-decompose before each _tick_once, capped by kanban.auto_decompose_per_tick (default 3) so a bulk-load of triage tasks doesn't burst-spend the aux LLM.

Config

  • New DEFAULT_CONFIG keys:
    • kanban.orchestrator_profile (default "" → falls back to active default profile)
    • kanban.default_assignee (same fallback)
    • kanban.auto_decompose (default True)
    • kanban.auto_decompose_per_tick (default 3)
    • auxiliary.kanban_decomposer
    • auxiliary.profile_describer
  • No _config_version bump — deep-merge handles new nested keys.

Dashboard backend (plugins/kanban/dashboard/plugin_api.py)

  • GET /profiles — roster with descriptions
  • PATCH /profiles/<name> — set description (user-authored)
  • POST /profiles/<name>/describe-auto — LLM-generate
  • POST /tasks/<id>/decompose — run decomposer
  • GET/PUT /orchestration — orchestrator/default-assignee/auto-decompose pickers, with resolved fallbacks echoed back

Dashboard UI (plugins/kanban/dashboard/dist/index.js)

  • New collapsible Orchestration settings panel at the top of the kanban page: dropdowns for orchestrator profile + default assignee (with "(default: active)" placeholder), auto-decompose toggle, per-profile description editor with Save and ⚗ Auto buttons.
  • New ⚗ Decompose button on triage-column task drawers, next to ✨ Specify.

Behavior

Knob Default Effect
kanban.orchestrator_profile "" (active default) Profile that runs decomposition
kanban.default_assignee "" (active default) Where the orchestrator routes child tasks when no profile fits
kanban.auto_decompose True Dispatcher decomposes triage tasks automatically
kanban.auto_decompose_per_tick 3 Cap on decomposes per tick
  • Root stays alive after fan-out, gated by every child. When the whole graph finishes, root promotes to ready and the orchestrator wakes back up to judge completion or add more tasks — the "judges if finished, adds more tasks until done" part of the original vision.
  • Tasks never end up with assignee=None. Unknown assignees rewrite to the default fallback at decompose time.

Tests

Targeted: 23 new tests, all passing.

  • tests/hermes_cli/test_kanban_decompose_db.py — 7 tests for the atomic DB helper.
  • tests/hermes_cli/test_kanban_decompose.py — 6 tests for the decomposer module (LLM mocked).
  • tests/hermes_cli/test_profile_describer.py — 10 tests for profile.yaml r/w + the LLM auto-describer.

Pre-existing test suite (tests/hermes_cli/): 4560/4569 passing, 9 unrelated failures (test_api_key_providers, test_gateway_service systemd, test_setup_openclaw_migration, test_web_server PTY, test_startup_plugin_gating's send subcommand). None touch kanban/profiles.

Before After
Targeted kanban + profile tests 372 395 (+23 new)
Triage workflow manual fan-out via custom orchestrator skills drop a one-liner, the orchestrator + dispatcher handle the rest

E2E validation

CLI end-to-end (isolated HERMES_HOME, mocked aux LLM, real DB):

  • Created 3 profiles with descriptions on disk
  • Dropped a triage task ("ship the rate-limiter feature")
  • Mocked the aux LLM with a 3-task graph: researcher → engineer → writer
  • Verified: 3 children created with correct assignees, dependency edges match the graph, root flipped to todo gated by every child, audit comment + decomposed event recorded, first child auto-promoted to ready

CLI subprocess (hermes profile describe):

  • describe <name> (empty) → shows "(no description set)"
  • describe <name> --text "..." → persists to profile.yaml
  • describe <name> (read back) → shows the text
  • describe ghost → exits 1 with clear error
  • kanban decompose <id> (no aux configured) → reports inline failure cleanly

Dashboard end-to-end (browser tool):

  • Started dashboard against isolated HERMES_HOME
  • Verified all four new endpoints via curl: GET /profiles, PATCH /profiles, GET/PUT /orchestration, POST /tasks//decompose
  • Confirmed config.yaml and profile.yaml writes persist correctly
  • Opened UI in browser, expanded Orchestration settings panel, verified all pickers + per-profile editor render
  • Set orchestrator → engineer, default → researcher via dropdowns, confirmed config.yaml updated
  • Typed description for "default" profile, clicked Save, verified ~/.hermes/profile.yaml was written
  • Clicked ⚗ Decompose on the triage card, confirmed inline error surfaced ("no auxiliary client configured") instead of breaking the page

Files

17 changed (3 new modules + 3 new test files + 8 modified + 1 dashboard JS + 1 dashboard API + 1 dispatcher):

hermes_cli/profiles.py            (+ description fields, profile.yaml r/w)
hermes_cli/profile_describer.py   NEW
hermes_cli/kanban_decompose.py    NEW
hermes_cli/kanban_db.py           (+ decompose_triage_task helper)
hermes_cli/kanban.py              (+ decompose verb)
hermes_cli/config.py              (+ 4 kanban keys, 2 aux task slots)
hermes_cli/main.py                (+ profile create --description, profile describe verb)
gateway/run.py                    (+ auto-decompose phase in dispatcher loop)
plugins/kanban/dashboard/plugin_api.py  (+ 6 endpoints)
plugins/kanban/dashboard/dist/index.js  (+ OrchestrationPanel + Decompose button)
tests/hermes_cli/test_kanban_decompose_db.py    NEW
tests/hermes_cli/test_kanban_decompose.py       NEW
tests/hermes_cli/test_profile_describer.py      NEW

Closes the core gap in the kanban system: dropping a one-liner into Triage
now decomposes it into a graph of child tasks routed to specialist
profiles by description, matching teknium's original vision ("main
orchestrator splits/creates actual tasks, doles them out to each agent").

The build
---------
- hermes_cli/profiles.py: new `description` + `description_auto` fields
  on ProfileInfo, persisted in <profile_dir>/profile.yaml. Helpers
  read_profile_meta / write_profile_meta. `create_profile` accepts
  optional description.
- hermes_cli/profile_describer.py: new module — auto-generate a 1-2
  sentence description from a profile's skills + model + name via the
  auxiliary LLM (`auxiliary.profile_describer`).
- hermes_cli/main.py: new `hermes profile create --description ...`
  flag; new `hermes profile describe [name] [--text ... | --auto |
  --all --auto]` subcommand.
- hermes_cli/kanban_db.py: new `decompose_triage_task` atomic helper —
  creates N child tasks, links the root as a child of every leaf
  (root waits for the whole graph), flips root `triage -> todo` with
  orchestrator assignee, records an audit comment + `decomposed` event
  in a single write_txn.
- hermes_cli/kanban_decompose.py: new module — calls the auxiliary LLM
  (`auxiliary.kanban_decomposer`) with the profile roster + descriptions
  to produce a JSON task graph, then invokes the DB helper. Rewrites
  unknown assignees to the configured `kanban.default_assignee` (or
  the active default profile) so a task NEVER lands with assignee=None.
  Falls back to specify-style single-task promotion when the LLM
  returns `fanout: false`.
- hermes_cli/kanban.py: new `hermes kanban decompose [task_id | --all]`
  CLI verb.
- hermes_cli/config.py: new DEFAULT_CONFIG keys —
  kanban.orchestrator_profile, kanban.default_assignee,
  kanban.auto_decompose (default True), kanban.auto_decompose_per_tick
  (default 3), auxiliary.kanban_decomposer, auxiliary.profile_describer.
- gateway/run.py: kanban dispatcher watcher now runs auto-decompose
  before each `_tick_once`, capped by `auto_decompose_per_tick` so a
  bulk-load of triage tasks doesn't burst-spend the aux LLM.
- plugins/kanban/dashboard/plugin_api.py: new endpoints —
  GET /profiles (list roster + descriptions),
  PATCH /profiles/<name> (set description, user-authored),
  POST /profiles/<name>/describe-auto (LLM-generate),
  POST /tasks/<id>/decompose (run decomposer),
  GET/PUT /orchestration (orchestrator/default-assignee/auto-decompose
  pickers, with resolved fallbacks echoed back).
- plugins/kanban/dashboard/dist/index.js: new OrchestrationPanel
  collapsible — dropdowns for orchestrator profile and default
  assignee, auto-decompose toggle, per-profile description editor with
  Save and Auto-generate buttons. New ⚗ Decompose button next to
  ✨ Specify on triage-column task drawers.

Behavior
--------
- A task in Triage gets fanned out into a small DAG of child tasks.
  Children with no internal parents flip to `ready` immediately
  (parallel dispatch). Children with sibling parents wait. The root
  stays alive as a parent of every child — when the whole graph
  finishes, it promotes to `ready` and the orchestrator profile wakes
  back up to judge completion (the "adds more tasks until done" part
  of the original vision).
- `kanban.orchestrator_profile` unset -> falls back to the default
  profile (whichever `hermes` launches with no -p flag).
- `kanban.default_assignee` unset -> same fallback. Tasks NEVER end
  up unassigned.
- `kanban.auto_decompose=true` (default) runs the decomposer
  automatically on dispatcher ticks; manual `hermes kanban decompose`
  is always available.

Tests
-----
- tests/hermes_cli/test_kanban_decompose_db.py — 7 tests for the
  atomic DB helper (status transitions, dep graph, audit trail,
  validation errors).
- tests/hermes_cli/test_kanban_decompose.py — 6 tests for the
  decomposer module (fanout, no-fanout fallback, unknown-assignee
  rewrite, malformed-JSON resilience, no-aux-client path).
- tests/hermes_cli/test_profile_describer.py — 10 tests for
  profile.yaml r/w + the LLM auto-describer (yaml corrupt tolerance,
  user-vs-auto description protection, --overwrite, fallback parsing).

E2E
---
- CLI end-to-end: created profiles with descriptions, dropped a triage
  task, mocked the aux LLM with a 3-task graph -> verified all three
  children were created with the right assignees, the dependency
  edges matched the LLM's graph, root flipped to todo gated by every
  child, audit comment + `decomposed` event recorded.
- Dashboard end-to-end: started the dashboard against an isolated
  HERMES_HOME, verified all four new endpoints via curl (profile
  listing, PATCH for description, PUT for orchestration settings,
  POST for decompose). Opened the UI in the browser, confirmed the
  OrchestrationPanel renders with all three pickers + the per-profile
  description editor, typed a description, clicked Save, verified
  ~/.hermes/profile.yaml was written. Clicked Decompose on the triage
  card and confirmed the inline error message surfaced as designed
  ("no auxiliary client configured").
@github-actions

github-actions Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-58d845e6 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 8732 on HEAD, 8707 on base (🆕 +25)

🆕 New issues (14):

Rule Count
unresolved-attribute 5
unresolved-import 3
unused-type-ignore-comment 3
invalid-argument-type 3
First entries
tests/hermes_cli/test_kanban_decompose.py:15: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_kanban_decompose_db.py:67: [unresolved-attribute] unresolved-attribute: Attribute `status` is not defined on `None` in union `Task | None`
tests/hermes_cli/test_kanban_decompose.py:187: [unresolved-attribute] unresolved-attribute: Attribute `assignee` is not defined on `None` in union `Task | None`
tests/hermes_cli/test_kanban_decompose_db.py:68: [unresolved-attribute] unresolved-attribute: Attribute `assignee` is not defined on `None` in union `Task | None`
tests/hermes_cli/test_kanban_decompose.py:144: [unresolved-attribute] unresolved-attribute: Attribute `status` is not defined on `None` in union `Task | None`
hermes_cli/profile_describer.py:214: [unused-type-ignore-comment] unused-type-ignore-comment: Unused blanket `type: ignore` directive
tests/hermes_cli/test_kanban_decompose_db.py:9: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
hermes_cli/main.py:9203: [invalid-argument-type] invalid-argument-type: Argument to function `normalize_profile_name` is incorrect: Expected `str`, found `Any | None`
plugins/kanban/dashboard/plugin_api.py:1593: [unused-type-ignore-comment] unused-type-ignore-comment: Unused blanket `type: ignore` directive
hermes_cli/main.py:9233: [invalid-argument-type] invalid-argument-type: Argument to function `describe_profile` is incorrect: Expected `str`, found `str | Any | None`
hermes_cli/kanban_decompose.py:277: [unused-type-ignore-comment] unused-type-ignore-comment: Unused blanket `type: ignore` directive
tests/hermes_cli/test_kanban_decompose.py:145: [unresolved-attribute] unresolved-attribute: Attribute `title` is not defined on `None` in union `Task | None`
hermes_cli/main.py:9207: [invalid-argument-type] invalid-argument-type: Argument to function `get_profile_dir` is incorrect: Expected `str`, found `Any | None`
tests/hermes_cli/test_profile_describer.py:11: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`

✅ Fixed issues: none

Unchanged: 4589 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

that live directly under ``skills/`` show as bare ``skill_name``.
"""
skills_dir = profile_dir / "skills"
if not skills_dir.is_dir():
if not skills_dir.is_dir():
return []
names: list[str] = []
for md in skills_dir.rglob("SKILL.md"):
skill_names = _collect_skills(profile_dir)
skill_list = "\n".join(f" - {n}" for n in skill_names) or " (no skills installed)"
skill_count = sum(
1 for _ in (profile_dir / "skills").rglob("SKILL.md")
Comment thread hermes_cli/profile_describer.py Dismissed
Comment thread hermes_cli/profiles.py Dismissed
Comment thread hermes_cli/profiles.py Dismissed
Comment thread hermes_cli/profiles.py Dismissed
Comment thread hermes_cli/profiles.py Dismissed
Comment thread hermes_cli/profiles.py Dismissed
Comment thread plugins/kanban/dashboard/plugin_api.py Dismissed
@alt-glitch alt-glitch added type/feature New feature or request P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins labels May 17, 2026
teknium1 added 3 commits May 17, 2026 12:44
The auto/manual toggle already existed as kanban.auto_decompose (default
true), but it was buried inside the collapsed Orchestration settings
panel — users couldn't tell at a glance which mode they were in. This
hoists it to a pill at the top of the kanban page so the state is always
visible and one click flips it.

UX
- New "⚗ Decompose: AUTO|MANUAL" pill in the kanban header. Emerald
  styling when Auto is on (the default), muted/gray when Manual.
- Pill is visible both in the collapsed AND expanded Orchestration
  settings views so context is preserved when the user opens the panel.
- Tooltip explains both states + what clicking does.
- Renamed the in-panel "Auto-decompose on triage / Enabled" checkbox
  to "Decompose mode / Auto (default) | Manual" for language parity
  with the pill.

Behavior preserved
- Default remains Auto (kanban.auto_decompose=true).
- Manual mode restores pre-PR behavior: triage tasks stay in triage
  until the user clicks ⚗ Decompose on each card (or runs
  `hermes kanban decompose <id>`).

Implementation
- plugins/kanban/dashboard/dist/index.js: load /orchestration on mount
  (not just on expand) so the collapsed pill reflects real state.
  Render mode pill in both collapsed and expanded headers. Reuses the
  existing PUT /api/plugins/kanban/orchestration endpoint — no new
  backend, no new tests required.

E2E verified
- Pill renders as "⚗ Decompose: AUTO" on page load (default).
- One click flips to "⚗ Decompose: MANUAL" with muted styling.
- config.yaml on disk shows auto_decompose: false after the flip.
- Second click round-trips back to Auto; config.yaml flips to true.
Per Teknium feedback — "Decompose" was too implementation-specific.
"Orchestration" is the user-facing concept (the whole pitch is the
orchestrator profile routing work), and the pill is the front door to it.

- Pill text: "Orchestration: Auto" / "Orchestration: Manual" (title case,
  no ⚗ prefix, no SHOUTY-CAPS for the mode value)
- In-panel checkbox label: "Orchestration mode" (was "Decompose mode")
- Tooltips updated to match
- No behavior change
… mode

Brings the docs site up to parity with the PR. English build verified
locally (npx docusaurus build --locale en) — clean, no new broken links
or anchors. Pre-existing broken-link warnings (rl-training, llms.txt,
step-by-step-checklist, fallback-model) untouched.

- website/docs/reference/cli-commands.md
    + `hermes kanban decompose` action row in the action table, with
      pointer to the Auto vs Manual orchestration section.

- website/docs/reference/profile-commands.md
    + `--description "<text>"` flag on `hermes profile create`.
    + Full `hermes profile describe` section: read, --text, --auto,
      --overwrite, --all flags with examples.

- website/docs/user-guide/features/kanban.md (the big one)
    + Triage column intro rewritten around the Auto-decompose default
      behavior, with pointer to the new Auto vs Manual section.
    + Status action row updated to mention both ⚗ Decompose and
      ✨ Specify on triage cards.
    + New "Auto vs Manual orchestration" section explaining the two
      modes, how to flip them (pill, config), how routing-by-description
      works, the no-None-assignee guarantee, plus a config knob table
      (auto_decompose, auto_decompose_per_tick, orchestrator_profile,
      default_assignee) and the two new auxiliary slots
      (kanban_decomposer, profile_describer).
    + REST surface table gains 6 new endpoint rows: /tasks/:id/decompose,
      /profiles (GET), /profiles/:name (PATCH), /profiles/:name/describe-auto,
      /orchestration (GET + PUT).

- website/docs/user-guide/features/kanban-tutorial.md
    + Triage column blurb updated for Auto by default + Manual via the
      pill, with cross-link to the Auto vs Manual orchestration section.

- website/docs/user-guide/profiles.md
    + Blank-profile flow now mentions --description and points to the
      kanban routing model for context.

- website/docs/user-guide/configuration.md
    + `kanban_decomposer` and `profile_describer` added to the
      `hermes model -> Configure auxiliary models` menu listing.
@teknium1 teknium1 merged commit 1345dda into main May 17, 2026
18 checks passed
@teknium1 teknium1 deleted the hermes/hermes-58d845e6 branch May 17, 2026 20:54
qWaitCrypto added a commit to qWaitCrypto/hermes-agent that referenced this pull request May 18, 2026
After NousResearch#27572, triage automation defaults to the kanban decomposer path instead of only the specify path. Update the diagnostic to check the active triage automation backend: kanban_decomposer when auto-decompose is enabled, triage_specifier in manual mode.
teknium1 added a commit that referenced this pull request May 18, 2026
…aware) (#27871)

Adds a 'triage_aux_unavailable' diagnostic for tasks stuck in triage when
neither the active aux helper slot nor the main-model auto fallback is usable.

Auto-decompose aware:
- kanban.auto_decompose=True (default): primary is auxiliary.kanban_decomposer,
  triage_specifier is the fanout=false fallback.
- kanban.auto_decompose=False: primary is auxiliary.triage_specifier (manual
  'hermes kanban specify' path).

Default aux slots use 'provider: auto' which falls back to the main model, so
this rule only fires when both the explicit slot config AND the main-model
auto fallback are absent. Quiet by default; informative when there is a real
config gap.

Also adds kd.config_from_runtime_config() that carries kanban + auxiliary +
model keys through to diagnostics, and updates CLI/dashboard call sites to
use it. config_from_kanban_config() is preserved for back-compat.

Reworks the original PR #25640 idea (@qWaitCrypto) to align with the new
auto-decompose dispatcher path landed in #27572. The original PR pointed only
at auxiliary.triage_specifier, which is now the fallback rather than the
primary helper.

Co-authored-by: qWaitCrypto <axmaiqiu@gmail.com>
teknium1 added a commit that referenced this pull request May 18, 2026
The dashboard SDK's <Select> is a shadcn-style popup that fires
onValueChange(value), not native onChange({target:{value}}). The file
even has a selectChangeHandler() helper at L213 documenting this:
"Older plugin code calls onChange({target:{value}}) which silently
never fires."

#24547 already fixed the bulk-reassign, workspace-kind, and new-task
parent selects. This patch covers the two OrchestrationPanel selects
introduced later in #27572 that regressed onto the same broken pattern:

  - OrchestrationPanel orchestrator_profile picker
  - OrchestrationPanel default_assignee picker

Users opened the popup, picked an option, and the popup closed without
firing a PUT to /orchestration — so the orchestrator profile and
default assignee dropdowns appeared totally inert.

Uses the same selectChangeHandler helper as the other working Selects
in the file for consistency.

Reported by Exaario.
teknium1 added a commit that referenced this pull request May 18, 2026
…ts (#27893)

The dashboard SDK's <Select> is a shadcn-style popup that fires
onValueChange(value), not native onChange({target:{value}}). The file
even has a selectChangeHandler() helper at L213 documenting this:
"Older plugin code calls onChange({target:{value}}) which silently
never fires."

#24547 already fixed the bulk-reassign, workspace-kind, and new-task
parent selects. This patch covers the two OrchestrationPanel selects
introduced later in #27572 that regressed onto the same broken pattern:

  - OrchestrationPanel orchestrator_profile picker
  - OrchestrationPanel default_assignee picker

Users opened the popup, picked an option, and the popup closed without
firing a PUT to /orchestration — so the orchestrator profile and
default assignee dropdowns appeared totally inert.

Uses the same selectChangeHandler helper as the other working Selects
in the file for consistency.

Reported by Exaario.
Lillard01 pushed a commit to Lillard01/hermes-agent that referenced this pull request May 21, 2026
…aware) (NousResearch#27871)

Adds a 'triage_aux_unavailable' diagnostic for tasks stuck in triage when
neither the active aux helper slot nor the main-model auto fallback is usable.

Auto-decompose aware:
- kanban.auto_decompose=True (default): primary is auxiliary.kanban_decomposer,
  triage_specifier is the fanout=false fallback.
- kanban.auto_decompose=False: primary is auxiliary.triage_specifier (manual
  'hermes kanban specify' path).

Default aux slots use 'provider: auto' which falls back to the main model, so
this rule only fires when both the explicit slot config AND the main-model
auto fallback are absent. Quiet by default; informative when there is a real
config gap.

Also adds kd.config_from_runtime_config() that carries kanban + auxiliary +
model keys through to diagnostics, and updates CLI/dashboard call sites to
use it. config_from_kanban_config() is preserved for back-compat.

Reworks the original PR NousResearch#25640 idea (@qWaitCrypto) to align with the new
auto-decompose dispatcher path landed in NousResearch#27572. The original PR pointed only
at auxiliary.triage_specifier, which is now the fallback rather than the
primary helper.

Co-authored-by: qWaitCrypto <axmaiqiu@gmail.com>
Lillard01 pushed a commit to Lillard01/hermes-agent that referenced this pull request May 21, 2026
…ts (NousResearch#27893)

The dashboard SDK's <Select> is a shadcn-style popup that fires
onValueChange(value), not native onChange({target:{value}}). The file
even has a selectChangeHandler() helper at L213 documenting this:
"Older plugin code calls onChange({target:{value}}) which silently
never fires."

NousResearch#24547 already fixed the bulk-reassign, workspace-kind, and new-task
parent selects. This patch covers the two OrchestrationPanel selects
introduced later in NousResearch#27572 that regressed onto the same broken pattern:

  - OrchestrationPanel orchestrator_profile picker
  - OrchestrationPanel default_assignee picker

Users opened the popup, picked an option, and the popup closed without
firing a PUT to /orchestration — so the orchestrator profile and
default assignee dropdowns appeared totally inert.

Uses the same selectChangeHandler helper as the other working Selects
in the file for consistency.

Reported by Exaario.
Mucky010 pushed a commit to Mucky010/hermes-agent that referenced this pull request May 24, 2026
…ts (NousResearch#27893)

The dashboard SDK's <Select> is a shadcn-style popup that fires
onValueChange(value), not native onChange({target:{value}}). The file
even has a selectChangeHandler() helper at L213 documenting this:
"Older plugin code calls onChange({target:{value}}) which silently
never fires."

NousResearch#24547 already fixed the bulk-reassign, workspace-kind, and new-task
parent selects. This patch covers the two OrchestrationPanel selects
introduced later in NousResearch#27572 that regressed onto the same broken pattern:

  - OrchestrationPanel orchestrator_profile picker
  - OrchestrationPanel default_assignee picker

Users opened the popup, picked an option, and the popup closed without
firing a PUT to /orchestration — so the orchestrator profile and
default assignee dropdowns appeared totally inert.

Uses the same selectChangeHandler helper as the other working Selects
in the file for consistency.

Reported by Exaario.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…esearch#27572)

* feat(kanban): orchestrator-driven auto-decomposition on triage

Closes the core gap in the kanban system: dropping a one-liner into Triage
now decomposes it into a graph of child tasks routed to specialist
profiles by description, matching teknium's original vision ("main
orchestrator splits/creates actual tasks, doles them out to each agent").

The build
---------
- hermes_cli/profiles.py: new `description` + `description_auto` fields
  on ProfileInfo, persisted in <profile_dir>/profile.yaml. Helpers
  read_profile_meta / write_profile_meta. `create_profile` accepts
  optional description.
- hermes_cli/profile_describer.py: new module — auto-generate a 1-2
  sentence description from a profile's skills + model + name via the
  auxiliary LLM (`auxiliary.profile_describer`).
- hermes_cli/main.py: new `hermes profile create --description ...`
  flag; new `hermes profile describe [name] [--text ... | --auto |
  --all --auto]` subcommand.
- hermes_cli/kanban_db.py: new `decompose_triage_task` atomic helper —
  creates N child tasks, links the root as a child of every leaf
  (root waits for the whole graph), flips root `triage -> todo` with
  orchestrator assignee, records an audit comment + `decomposed` event
  in a single write_txn.
- hermes_cli/kanban_decompose.py: new module — calls the auxiliary LLM
  (`auxiliary.kanban_decomposer`) with the profile roster + descriptions
  to produce a JSON task graph, then invokes the DB helper. Rewrites
  unknown assignees to the configured `kanban.default_assignee` (or
  the active default profile) so a task NEVER lands with assignee=None.
  Falls back to specify-style single-task promotion when the LLM
  returns `fanout: false`.
- hermes_cli/kanban.py: new `hermes kanban decompose [task_id | --all]`
  CLI verb.
- hermes_cli/config.py: new DEFAULT_CONFIG keys —
  kanban.orchestrator_profile, kanban.default_assignee,
  kanban.auto_decompose (default True), kanban.auto_decompose_per_tick
  (default 3), auxiliary.kanban_decomposer, auxiliary.profile_describer.
- gateway/run.py: kanban dispatcher watcher now runs auto-decompose
  before each `_tick_once`, capped by `auto_decompose_per_tick` so a
  bulk-load of triage tasks doesn't burst-spend the aux LLM.
- plugins/kanban/dashboard/plugin_api.py: new endpoints —
  GET /profiles (list roster + descriptions),
  PATCH /profiles/<name> (set description, user-authored),
  POST /profiles/<name>/describe-auto (LLM-generate),
  POST /tasks/<id>/decompose (run decomposer),
  GET/PUT /orchestration (orchestrator/default-assignee/auto-decompose
  pickers, with resolved fallbacks echoed back).
- plugins/kanban/dashboard/dist/index.js: new OrchestrationPanel
  collapsible — dropdowns for orchestrator profile and default
  assignee, auto-decompose toggle, per-profile description editor with
  Save and Auto-generate buttons. New ⚗ Decompose button next to
  ✨ Specify on triage-column task drawers.

Behavior
--------
- A task in Triage gets fanned out into a small DAG of child tasks.
  Children with no internal parents flip to `ready` immediately
  (parallel dispatch). Children with sibling parents wait. The root
  stays alive as a parent of every child — when the whole graph
  finishes, it promotes to `ready` and the orchestrator profile wakes
  back up to judge completion (the "adds more tasks until done" part
  of the original vision).
- `kanban.orchestrator_profile` unset -> falls back to the default
  profile (whichever `hermes` launches with no -p flag).
- `kanban.default_assignee` unset -> same fallback. Tasks NEVER end
  up unassigned.
- `kanban.auto_decompose=true` (default) runs the decomposer
  automatically on dispatcher ticks; manual `hermes kanban decompose`
  is always available.

Tests
-----
- tests/hermes_cli/test_kanban_decompose_db.py — 7 tests for the
  atomic DB helper (status transitions, dep graph, audit trail,
  validation errors).
- tests/hermes_cli/test_kanban_decompose.py — 6 tests for the
  decomposer module (fanout, no-fanout fallback, unknown-assignee
  rewrite, malformed-JSON resilience, no-aux-client path).
- tests/hermes_cli/test_profile_describer.py — 10 tests for
  profile.yaml r/w + the LLM auto-describer (yaml corrupt tolerance,
  user-vs-auto description protection, --overwrite, fallback parsing).

E2E
---
- CLI end-to-end: created profiles with descriptions, dropped a triage
  task, mocked the aux LLM with a 3-task graph -> verified all three
  children were created with the right assignees, the dependency
  edges matched the LLM's graph, root flipped to todo gated by every
  child, audit comment + `decomposed` event recorded.
- Dashboard end-to-end: started the dashboard against an isolated
  HERMES_HOME, verified all four new endpoints via curl (profile
  listing, PATCH for description, PUT for orchestration settings,
  POST for decompose). Opened the UI in the browser, confirmed the
  OrchestrationPanel renders with all three pickers + the per-profile
  description editor, typed a description, clicked Save, verified
  ~/.hermes/profile.yaml was written. Clicked Decompose on the triage
  card and confirmed the inline error message surfaced as designed
  ("no auxiliary client configured").

* feat(kanban): surface decompose mode (Auto/Manual) as a one-click pill

The auto/manual toggle already existed as kanban.auto_decompose (default
true), but it was buried inside the collapsed Orchestration settings
panel — users couldn't tell at a glance which mode they were in. This
hoists it to a pill at the top of the kanban page so the state is always
visible and one click flips it.

UX
- New "⚗ Decompose: AUTO|MANUAL" pill in the kanban header. Emerald
  styling when Auto is on (the default), muted/gray when Manual.
- Pill is visible both in the collapsed AND expanded Orchestration
  settings views so context is preserved when the user opens the panel.
- Tooltip explains both states + what clicking does.
- Renamed the in-panel "Auto-decompose on triage / Enabled" checkbox
  to "Decompose mode / Auto (default) | Manual" for language parity
  with the pill.

Behavior preserved
- Default remains Auto (kanban.auto_decompose=true).
- Manual mode restores pre-PR behavior: triage tasks stay in triage
  until the user clicks ⚗ Decompose on each card (or runs
  `hermes kanban decompose <id>`).

Implementation
- plugins/kanban/dashboard/dist/index.js: load /orchestration on mount
  (not just on expand) so the collapsed pill reflects real state.
  Render mode pill in both collapsed and expanded headers. Reuses the
  existing PUT /api/plugins/kanban/orchestration endpoint — no new
  backend, no new tests required.

E2E verified
- Pill renders as "⚗ Decompose: AUTO" on page load (default).
- One click flips to "⚗ Decompose: MANUAL" with muted styling.
- config.yaml on disk shows auto_decompose: false after the flip.
- Second click round-trips back to Auto; config.yaml flips to true.

* feat(kanban): rename mode pill to "Orchestration: Auto/Manual"

Per Teknium feedback — "Decompose" was too implementation-specific.
"Orchestration" is the user-facing concept (the whole pitch is the
orchestrator profile routing work), and the pill is the front door to it.

- Pill text: "Orchestration: Auto" / "Orchestration: Manual" (title case,
  no ⚗ prefix, no SHOUTY-CAPS for the mode value)
- In-panel checkbox label: "Orchestration mode" (was "Decompose mode")
- Tooltips updated to match
- No behavior change

* docs(kanban): document decompose, profile descriptions, orchestration mode

Brings the docs site up to parity with the PR. English build verified
locally (npx docusaurus build --locale en) — clean, no new broken links
or anchors. Pre-existing broken-link warnings (rl-training, llms.txt,
step-by-step-checklist, fallback-model) untouched.

- website/docs/reference/cli-commands.md
    + `hermes kanban decompose` action row in the action table, with
      pointer to the Auto vs Manual orchestration section.

- website/docs/reference/profile-commands.md
    + `--description "<text>"` flag on `hermes profile create`.
    + Full `hermes profile describe` section: read, --text, --auto,
      --overwrite, --all flags with examples.

- website/docs/user-guide/features/kanban.md (the big one)
    + Triage column intro rewritten around the Auto-decompose default
      behavior, with pointer to the new Auto vs Manual section.
    + Status action row updated to mention both ⚗ Decompose and
      ✨ Specify on triage cards.
    + New "Auto vs Manual orchestration" section explaining the two
      modes, how to flip them (pill, config), how routing-by-description
      works, the no-None-assignee guarantee, plus a config knob table
      (auto_decompose, auto_decompose_per_tick, orchestrator_profile,
      default_assignee) and the two new auxiliary slots
      (kanban_decomposer, profile_describer).
    + REST surface table gains 6 new endpoint rows: /tasks/:id/decompose,
      /profiles (GET), /profiles/:name (PATCH), /profiles/:name/describe-auto,
      /orchestration (GET + PUT).

- website/docs/user-guide/features/kanban-tutorial.md
    + Triage column blurb updated for Auto by default + Manual via the
      pill, with cross-link to the Auto vs Manual orchestration section.

- website/docs/user-guide/profiles.md
    + Blank-profile flow now mentions --description and points to the
      kanban routing model for context.

- website/docs/user-guide/configuration.md
    + `kanban_decomposer` and `profile_describer` added to the
      `hermes model -> Configure auxiliary models` menu listing.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…aware) (NousResearch#27871)

Adds a 'triage_aux_unavailable' diagnostic for tasks stuck in triage when
neither the active aux helper slot nor the main-model auto fallback is usable.

Auto-decompose aware:
- kanban.auto_decompose=True (default): primary is auxiliary.kanban_decomposer,
  triage_specifier is the fanout=false fallback.
- kanban.auto_decompose=False: primary is auxiliary.triage_specifier (manual
  'hermes kanban specify' path).

Default aux slots use 'provider: auto' which falls back to the main model, so
this rule only fires when both the explicit slot config AND the main-model
auto fallback are absent. Quiet by default; informative when there is a real
config gap.

Also adds kd.config_from_runtime_config() that carries kanban + auxiliary +
model keys through to diagnostics, and updates CLI/dashboard call sites to
use it. config_from_kanban_config() is preserved for back-compat.

Reworks the original PR NousResearch#25640 idea (@qWaitCrypto) to align with the new
auto-decompose dispatcher path landed in NousResearch#27572. The original PR pointed only
at auxiliary.triage_specifier, which is now the fallback rather than the
primary helper.

Co-authored-by: qWaitCrypto <axmaiqiu@gmail.com>
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…ts (NousResearch#27893)

The dashboard SDK's <Select> is a shadcn-style popup that fires
onValueChange(value), not native onChange({target:{value}}). The file
even has a selectChangeHandler() helper at L213 documenting this:
"Older plugin code calls onChange({target:{value}}) which silently
never fires."

NousResearch#24547 already fixed the bulk-reassign, workspace-kind, and new-task
parent selects. This patch covers the two OrchestrationPanel selects
introduced later in NousResearch#27572 that regressed onto the same broken pattern:

  - OrchestrationPanel orchestrator_profile picker
  - OrchestrationPanel default_assignee picker

Users opened the popup, picked an option, and the popup closed without
firing a PUT to /orchestration — so the orchestrator profile and
default assignee dropdowns appeared totally inert.

Uses the same selectChangeHandler helper as the other working Selects
in the file for consistency.

Reported by Exaario.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P2 Medium — degraded but workaround exists type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants