{"repo":"NousResearch/hermes-agent","public_id":"28016","display_label":"Kanban config, concurrency, and child task promotion","summary":"These items address kanban dispatcher concurrency controls (capping concurrent workers, per-profile limits), a new config toggle for auto-promoting child tasks to ready, and related documentation and bug fixes.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":28344,"merged_at":"2026-05-19T03:04:32+00:00"},"resolution":{"state":"resolved","by":"pr-28344"},"freshness":{"state":"verified","last_verified_at":"2026-05-19T03:04:32+00:00","is_stale":false},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":28016,"pr_number":28344,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-05-19T03:04:32+00:00"}],"members":[{"kind":"issue","number":28016,"title":"[Feature]: Config option to keep decomposed child tasks in todo instead of auto-promoting to ready","url":null,"state":"closed"},{"kind":"pr","number":28220,"title":"feat(kanban): add auto_promote_children config toggle","url":null,"state":"closed","close_reason":{"refs":[{"kind":"commit","value":"2e09d2567c851787fbc8f42ead23cf9ff9f06f6a","number":0},{"kind":"pr","value":"#28344","number":28344}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":28344,"title":"feat(kanban): add auto_promote_children config toggle (#28220)","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["hermes_cli/kanban_db.py","hermes_cli/kanban_decompose.py","plugins/kanban/dashboard/plugin_api.py"],"fix_hunks":[{"file":"hermes_cli/kanban_db.py","hunks":["@@ -2798,6 +2798,7 @@ def decompose_triage_task(\n     root_assignee: Optional[str],\n     children: list[dict],\n     author: Optional[str] = None,\n+    auto_promote: bool = True,\n ) -> Optional[list[str]]:\n     \"\"\"Fan a triage task out into child tasks and promote the root to ``todo``.\n ","@@ -2983,8 +2984,11 @@ def decompose_triage_task(\n \n     # Outside the write_txn: promote parent-free children to 'ready'\n     # so the dispatcher picks them up on its next tick. Same pattern\n-    # specify_triage_task uses.\n-    recompute_ready(conn)\n+    # specify_triage_task uses.  When auto_promote is False children\n+    # stay in 'todo' until the user manually promotes them — useful\n+    # for manual-review-first workflows.\n+    if auto_promote:\n+        recompute_ready(conn)\n     return child_ids\n \n "]},{"file":"hermes_cli/kanban_decompose.py","hunks":["@@ -271,6 +271,8 @@ def decompose_task(\n     cfg = _load_config()\n     orchestrator = _resolve_orchestrator_profile(cfg)\n     default_assignee = _resolve_default_assignee(cfg)\n+    kanban_cfg = cfg.get(\"kanban\", {}) if isinstance(cfg, dict) else {}\n+    auto_promote = bool(kanban_cfg.get(\"auto_promote_children\", True))\n     roster, valid_names = _build_roster()\n \n     try:","@@ -410,6 +412,7 @@ def decompose_task(\n                 root_assignee=orchestrator,\n                 children=children,\n                 author=audit_author,\n+                auto_promote=auto_promote,\n             )\n     except ValueError as exc:\n         return DecomposeOutcome(task_id, False, f\"DB rejected graph: {exc}\")"]},{"file":"plugins/kanban/dashboard/plugin_api.py","hunks":["@@ -1701,6 +1701,7 @@ class OrchestrationSettingsBody(BaseModel):\n     orchestrator_profile: Optional[str] = None\n     default_assignee: Optional[str] = None\n     auto_decompose: Optional[bool] = None\n+    auto_promote_children: Optional[bool] = None\n \n \n @router.get(\"/orchestration\")","@@ -1716,6 +1717,7 @@ def get_orchestration_settings():\n     explicit_orch = (kanban_cfg.get(\"orchestrator_profile\") or \"\").strip()\n     explicit_default = (kanban_cfg.get(\"default_assignee\") or \"\").strip()\n     auto_decompose = bool(kanban_cfg.get(\"auto_decompose\", True))\n+    auto_promote_children = bool(kanban_cfg.get(\"auto_promote_children\", True))\n \n     # Resolve fallbacks the same way the decomposer does.\n     resolved_orch = explicit_orch","@@ -1738,6 +1740,7 @@ def get_orchestration_settings():\n         \"orchestrator_profile\": explicit_orch,\n         \"default_assignee\": explicit_default,\n         \"auto_decompose\": auto_decompose,\n+        \"auto_promote_children\": auto_promote_children,\n         \"resolved_orchestrator_profile\": resolved_orch,\n         \"resolved_default_assignee\": resolved_default,\n         \"active_profile\": active_default,","@@ -1803,6 +1806,9 @@ def set_orchestration_settings(payload: OrchestrationSettingsBody):\n     if payload.auto_decompose is not None:\n         kanban_section[\"auto_decompose\"] = bool(payload.auto_decompose)\n \n+    if payload.auto_promote_children is not None:\n+        kanban_section[\"auto_promote_children\"] = bool(payload.auto_promote_children)\n+\n     try:\n         save_config(cfg)\n     except Exception as exc:\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}