Skip to content

Two dispatch-contract mismatches: (a) goal_mode tasks reject kanban_block with kind unset / capability / transient, but the tool schema documents all four kinds and says "Omit only if none apply"; (b) card skills that a worker profile lacks are skipped with only a log-file warning ("Unknown skill(s)"), silently losing the context the card was authored with #59764

Description

@Mourey

Title

Two dispatch-contract mismatches: (a) goal_mode tasks reject kanban_block with kind unset / capability / transient, but the tool schema documents all four kinds and says "Omit only if none apply"; (b) card skills that a worker profile lacks are skipped with only a log-file warning ("Unknown skill(s)"), silently losing the context the card was authored with

Affected version

  • hermes-agent 0.18.0 (pyproject.toml, [project] version = "0.18.0")
  • Install: git checkout of main @ 3ba5ba89c2479b04466a3cb2f6b14b1f6e8f164c (2026-07-06)
  • Platform: macOS (darwin), Python 3.11–3.13 per requires-python

These are two facets of the same problem — the contract the dispatcher/worker actually enforces differs from the contract published to card authors and to the model — so they are filed together; happy to split if preferred.

Part (a): goal_mode block-kind restriction is undocumented and rejects the schema's own default

The logic, reproduced from the install

tools/kanban_tools.py:182:

_GOAL_MODE_BLOCK_ALLOWED_KINDS = frozenset({"dependency", "needs_input"})

tools/kanban_tools.py:701-714 (inside _handle_block; rationale comment at 691-700 cites Issue #38696):

task = kb.get_task(conn, tid)
if (
    task
    and task.goal_mode
    and kind not in _GOAL_MODE_BLOCK_ALLOWED_KINDS
):
    conn.close()
    return tool_error(
        f"goal_mode tasks can only block with kind in "
        f"{sorted(_GOAL_MODE_BLOCK_ALLOWED_KINDS)} (got {kind!r}). "
        f"If the task is actually finished or cannot proceed for "
        f"another reason, call kanban_complete instead — the "
        f"completion judge will evaluate it."
    )

Note kind defaults to None (kanban_tools.py:682), and None not in {"dependency","needs_input"} — so omitting kind is rejected on goal_mode tasks.

But the tool schema the model sees — KANBAN_BLOCK_SCHEMA, tools/kanban_tools.py:1290-1332 — tells it the opposite:

  • The description advertises all four kinds: 'dependency', 'needs_input', 'capability', 'transient' (lines 1292-1303).
  • The kind property enum lists all four (line 1321) and its description ends "Omit only if none apply." (lines 1322-1326).
  • "required": ["reason"] (line 1330) — kind is optional per the schema.

Neither the schema, nor the enum, nor any shipped doc mentions that goal_mode changes the rules. The full-kind vocabulary is also the board-level contract: hermes_cli/kanban_db.py:125 VALID_BLOCK_KINDS = {"dependency", "needs_input", "capability", "transient"}.

Minimal repro

  1. Create a goal_mode card and run a worker on it (or set HERMES_KANBAN_TASK=<id> and call the tool directly).
  2. Call exactly what the schema permits:
    kanban_block(reason="need repo credentials")            # kind omitted
    kanban_block(reason="no API access", kind="capability")
    

Expected vs actual

  • Expected (per the published schema): the task blocks; capability surfaces to a human, omitted kind is accepted.
  • Actual: tool_error("goal_mode tasks can only block with kind in ['dependency', 'needs_input'] (got None)..."). A worker whose lane instructions (written against the schema) block without a kind can never block: with goal_mode on, its only exits are a judge-gated kanban_complete or burning the turn budget. In our deployment this made goal_mode effectively unusable on one lane until we found the restriction by reading the source — and note the gate it defers to ("the completion judge will evaluate it") is itself currently a no-op (see our companion report on the kanban_tools.py:605 tuple-unpack bug), so the two gates combined block the honest path and leave the dishonest one open.

Suggested fix

Any of, in preference order:

  1. Document the restriction where the model can see it: extend KANBAN_BLOCK_SCHEMA's kind description with the goal_mode rule, and drop/condition the "Omit only if none apply" sentence.
  2. Treat an omitted kind on goal_mode tasks as needs_input (coerce) instead of erroring — the schema marks it optional, so the enforcement should meet the schema halfway.
  3. Reconsider excluding capability: a genuine hard wall (no credentials, missing access) is precisely a case the worker cannot resolve and the judge cannot fix; forcing it through kanban_complete invites the judge to reject an honest "cannot proceed".

Part (b): unknown card skills are silently dropped (or crash-loop) with nothing on the board

The logic, reproduced from the install

Cards carry a skills column that the dispatcher passes through to the worker command line — hermes_cli/kanban_db.py:878-881 (dataclass field), 7785-7788:

if task.skills:
    for sk in task.skills:
        if sk:
            cmd.extend(["--skills", sk])

The worker is spawned as hermes -p <assignee> --accept-hooks --skills <sk> ... chat -q <prompt> (kanban_db.py:7771-7797) with a profile-scoped HERMES_HOME (kanban_db.py:7752-7769), so skill names resolve against the assignee profile's skill registry, not the dispatcher's.

At worker startup, cli.py:15827-15848 resolves the names via build_preloaded_skills_prompt (agent/skill_commands.py:695-743, which returns (prompt, loaded, missing); a name is missing when _load_skill_payloadskill_commands.py:138 — finds nothing under the trusted skill roots):

if missing_skills:
    missing_display = ", ".join(missing_skills)
    if loaded_skills:
        logger.warning(
            "Unknown skill(s) requested, skipping: %s. "
            "Continuing with: %s. "
            "List available skills with `hermes skills list`.",
            ...
        )
    else:
        raise ValueError(f"Unknown skill(s): {missing_display}")

Two failure modes when a card names a skill the assignee profile lacks:

  1. All named skills missingValueError → the worker process dies at startup, on every dispatch retry, until the failure breaker gives up. The card's own metadata (its skills list) makes it undispatchable to that profile, and nothing validates this at kanban_create/dispatch time (kanban_db.py:2494-2537 normalizes and de-dupes skill names but never checks existence).
  2. Some skills load → a logger.warning that lands only in the per-task worker log file (kanban_db.py:7798-7809 redirects worker output to <board-root>/logs/<task-id>.log). The run proceeds without the context the card author considered necessary. No kanban_comment, no card annotation, no dispatch-time error — from the board, the run looks fully provisioned. This is silent context loss: the worker produces output that may pass a judge while missing the exact guidance the skill carried.

Minimal repro

hermes kanban create --title "probe" --body "say hi" \
  --skill no-such-skill --assignee <any-profile>          # mode 1: crash-loops
hermes kanban create --title "probe2" --body "say hi" \
  --skill humanizer --skill no-such-skill --assignee <p>  # mode 2: silent skip
hermes kanban dispatch

Observed live in our deployment: cards authored with skills=["ai-radar"] dispatched to a profile that didn't have that skill installed → "Unknown skill(s)" on every spawn; the intended skill context was simply absent from every run. Upstream has hit the crash-loop variant itself: kanban_swarm.py hardcoded a nonexistent avoid-ai-writing skill (#29415; regression test at tests/hermes_cli/test_kanban_cli_dispatch_passthrough.py:119-150).

Expected vs actual

  • Expected: skill names on a card are validated against the assignee profile's registry when the card is created or at dispatch time; if a worker must skip a skill at runtime, the skip is surfaced on the board (comment/annotation on the card and/or a dispatch failure), not only in a log file.
  • Actual: dispatch happily spawns; missing-skill information exists only inside the worker log; partial-miss runs complete as if fully provisioned.

Suggested fix

  1. Validate task.skills against the resolved assignee profile's skill registry in dispatch_once before spawning (the dispatcher already resolves the profile-scoped HERMES_HOME, kanban_db.py:7752-7758, so the right registry is knowable). On failure: block the card with a structured reason instead of spawning, mirroring the existing skills shape validation at kanban_db.py:2494-2537.
  2. When the CLI skips unknown skills at startup in a kanban-worker context (HERMES_KANBAN_TASK set), emit a kanban_comment on the card (the comment tool already defaults its author from HERMES_PROFILE, set at kanban_db.py:7769) so the skip is board-visible.
  3. Keep the current hard-fail when all skills are missing, but make the dispatcher recognize that specific startup error as a card-configuration failure (block, don't retry) rather than burning max_retries on a deterministic crash.

Workaround we deployed

  • (a) Updated our lane skill/instructions so goal_mode workers always block with an explicit allowed kind (kind="needs_input"); this fix had to precede enabling goal_mode on that lane at all.
  • (b) Added a fail-fast pre-spawn validator in our own dispatch wrapper that resolves every card-named skill against the target profile before the worker is launched (dispatch dies loudly with the unresolved names instead of spawning), and moved skill installation into the profile bootstrap (pre-install skill sync plus a drift guard) so profiles can't lack the skills cards reference.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low — cosmetic, nice to havearea/profilesMulti-profile isolation, HERMES_HOME scopingcomp/cronCron scheduler and job managementtool/skillsSkills system (list, view, manage)type/bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions