mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-21 03:39:54 +00:00
fix(kanban): don't crash dispatched workers when kanban-worker skill is absent
Salvages #27372 by @oemtalks. The dispatcher unconditionally injected `--skills kanban-worker` into every worker spawn, but worker profiles sometimes don't have that bundled skill in their skills dir, which is fatal at CLI startup (`ValueError: Unknown skill(s): kanban-worker`). Adds `_kanban_worker_skill_available(hermes_home)` and only injects the flag when the skill resolves. The MANDATORY lifecycle still ships via KANBAN_GUIDANCE in the system prompt, so omitting the flag is safe.
This commit is contained in:
+51
-9
@@ -4411,6 +4411,41 @@ def _resolve_hermes_argv() -> list[str]:
|
||||
return _module_hermes_argv()
|
||||
|
||||
|
||||
def _kanban_worker_skill_available(hermes_home: Optional[str]) -> bool:
|
||||
"""True if the bundled ``kanban-worker`` skill resolves for the home the
|
||||
spawned worker will run under.
|
||||
|
||||
The dispatcher injects ``--skills kanban-worker`` into every worker. When
|
||||
the worker activates a profile (``hermes -p <name>``), its ``SKILLS_DIR``
|
||||
becomes ``<profile_home>/skills`` — which on many profiles does NOT contain
|
||||
the bundled skill (it ships in the *default* root home, not every
|
||||
profile-scoped skills dir). Preloading a missing skill is fatal at CLI
|
||||
startup (``ValueError: Unknown skill(s): kanban-worker``), aborting the
|
||||
worker before the agent loop runs. Gate the flag on actual resolvability;
|
||||
the kanban lifecycle contract is still injected via ``KANBAN_GUIDANCE``, so
|
||||
omitting the flag only drops the supplementary pattern library.
|
||||
"""
|
||||
from pathlib import Path as _Path
|
||||
|
||||
# An unset HERMES_HOME means the worker falls back to the default root
|
||||
# home (``~/.hermes``), which ships the bundled skill.
|
||||
base = _Path(hermes_home) if hermes_home else (_Path.home() / ".hermes")
|
||||
skills_root = base / "skills"
|
||||
if not skills_root.is_dir():
|
||||
return False
|
||||
# Canonical bundled location first (cheap), then a bounded scan for
|
||||
# profiles that have it nested elsewhere.
|
||||
if (skills_root / "devops" / "kanban-worker" / "SKILL.md").is_file():
|
||||
return True
|
||||
try:
|
||||
for skill_md in skills_root.rglob("kanban-worker/SKILL.md"):
|
||||
if skill_md.is_file():
|
||||
return True
|
||||
except OSError:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def _worker_terminal_timeout_env(
|
||||
max_runtime_seconds: Optional[int],
|
||||
current_timeout: Optional[str],
|
||||
@@ -4535,16 +4570,23 @@ def _default_spawn(
|
||||
# dispatcher's root allowlist. Pass --accept-hooks explicitly so
|
||||
# profile-local worker sessions still register configured hooks.
|
||||
"--accept-hooks",
|
||||
# Auto-load the kanban-worker skill so every dispatched worker
|
||||
# has the pattern library (good summary/metadata shapes, retry
|
||||
# diagnostics, block-reason examples) in its context, even if
|
||||
# the profile hasn't wired it into skills config. The MANDATORY
|
||||
# lifecycle is already in the system prompt via KANBAN_GUIDANCE;
|
||||
# this skill is the deeper reference. Users can point a profile
|
||||
# at a different/additional skill via config if they want —
|
||||
# --skills is additive to the profile's default skill set.
|
||||
"--skills", "kanban-worker",
|
||||
]
|
||||
# Auto-load the kanban-worker skill so every dispatched worker
|
||||
# has the pattern library (good summary/metadata shapes, retry
|
||||
# diagnostics, block-reason examples) in its context, even if
|
||||
# the profile hasn't wired it into skills config. The MANDATORY
|
||||
# lifecycle is already in the system prompt via KANBAN_GUIDANCE;
|
||||
# this skill is the deeper reference. Users can point a profile
|
||||
# at a different/additional skill via config if they want —
|
||||
# --skills is additive to the profile's default skill set.
|
||||
#
|
||||
# Only add the flag when the skill actually resolves for the home
|
||||
# the worker runs under: the bundled skill is absent from many
|
||||
# profile-scoped skills dirs, and preloading a missing skill is
|
||||
# fatal at CLI startup. Omitting it is safe — the lifecycle
|
||||
# contract still ships via KANBAN_GUIDANCE.
|
||||
if _kanban_worker_skill_available(env.get("HERMES_HOME")):
|
||||
cmd.extend(["--skills", "kanban-worker"])
|
||||
# Per-task force-loaded skills. Each name goes in its own
|
||||
# `--skills X` pair rather than a single comma-joined arg: the CLI
|
||||
# accepts both forms (action='append' + comma-split), but
|
||||
|
||||
Reference in New Issue
Block a user