Skip to content

Kanban auto-decompose silently reassigns and launches tasks whose assignee is deliberately non-spawnable (containment bypass) #62985

Description

@northernhartdesigns

Version: hermes-agent 0.18.2, Windows 11, gateway-embedded dispatcher.

Summary

The dispatcher deliberately respects a "non-spawnable assignee" convention: dispatch_once skips ready/review tasks whose assignee doesn't resolve to a real profile (skipped_nonspawnable; the profile_exists filter in has_spawnable_ready). The auto-decomposer does not: it selects triage tasks by status alone, and _normalize_assignee_choice rewrites any non-profile assignee to kanban.default_assignee on every child, while the root task is reassigned to kanban.orchestrator_profile. With auto_promote_children (default true) the children go straight to ready and live workers spawn.

A task that was intentionally parked by assigning it to a non-profile name is therefore silently un-parked, reassigned, and executed.

Steps to reproduce

  1. config.yaml: kanban.auto_decompose: true, with orchestrator_profile and default_assignee set to a real profile.
  2. hermes kanban create "parked task - do not run" --assignee not-a-profile --triage
  3. Wait one dispatcher tick (~60 s).
  4. The card fans out into children assigned to real profiles, the root is reassigned to the orchestrator profile, children promote to ready, and workers spawn. The explicit non-profile assignee survives nowhere.

Observed impact

We run shadow/dry-run pipelines contained by non-spawnable assignees. On 2026-07-11 the decomposer fanned a contained triage card into 6 children assigned to a live profile; they auto-promoted, spawned, and ran to completion - live agent tokens spent on work that was parked for human review.

Expected

The decomposer should honor the same convention the dispatcher already does - a task whose assignee is set but doesn't resolve to a real profile stays parked. Any of these would resolve it:

  • (a) decompose_task returns ok=False, reason="assignee is not a spawnable profile - parked" when task.assignee is non-empty and profile_exists() is false (mirrors dispatch semantics);
  • (b) the same filter in list_triage_ids;
  • (c) per-board auto_decompose in board.json (currently global-only);
  • (d) a per-card no_decompose/hold flag.

Relevant code (v0.18.2)

  • hermes_cli/kanban_decompose.py - triage-only guard ~L288; _normalize_assignee_choice ~L252; list_triage_ids ~L468
  • gateway/kanban_watchers.py - _auto_decompose_tick ~L1134
  • hermes_cli/kanban_db.py - decompose_triage_task ~L4984; children promoted via recompute_ready ~L5202

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existscomp/cronCron scheduler and job managementtype/bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions