mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-28 06:51:16 +08:00
feat(skills): add skill config interface + llm-wiki skill (#5635)
Skills can now declare config.yaml settings via metadata.hermes.config in their SKILL.md frontmatter. Values are stored under skills.config.* namespace, prompted during hermes config migrate, shown in hermes config show, and injected into the skill context at load time. Also adds the llm-wiki skill (Karpathy's LLM Wiki pattern) as the first skill to use the new config interface, declaring wiki.path. Skill config interface (new): - agent/skill_utils.py: extract_skill_config_vars(), discover_all_skill_config_vars(), resolve_skill_config_values(), SKILL_CONFIG_PREFIX - agent/skill_commands.py: _inject_skill_config() injects resolved values into skill messages as [Skill config: ...] block - hermes_cli/config.py: get_missing_skill_config_vars(), skill config prompting in migrate_config(), Skill Settings in show_config() LLM Wiki skill (skills/research/llm-wiki/SKILL.md): - Three-layer architecture (raw sources, wiki pages, schema) - Three operations (ingest, query, lint) - Session orientation, page thresholds, tag taxonomy, update policy, scaling guidance, log rotation, archiving workflow Docs: creating-skills.md, configuration.md, skills.md, skills-catalog.md Closes #5100
This commit is contained in:
@@ -1264,6 +1264,43 @@ def get_missing_config_fields() -> List[Dict[str, Any]]:
|
||||
return missing
|
||||
|
||||
|
||||
def get_missing_skill_config_vars() -> List[Dict[str, Any]]:
|
||||
"""Return skill-declared config vars that are missing or empty in config.yaml.
|
||||
|
||||
Scans all enabled skills for ``metadata.hermes.config`` entries, then checks
|
||||
which ones are absent or empty under ``skills.config.<key>`` in the user's
|
||||
config.yaml. Returns a list of dicts suitable for prompting.
|
||||
"""
|
||||
try:
|
||||
from agent.skill_utils import discover_all_skill_config_vars, SKILL_CONFIG_PREFIX
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
all_vars = discover_all_skill_config_vars()
|
||||
if not all_vars:
|
||||
return []
|
||||
|
||||
config = load_config()
|
||||
missing: List[Dict[str, Any]] = []
|
||||
for var in all_vars:
|
||||
# Skill config is stored under skills.config.<logical_key>
|
||||
storage_key = f"{SKILL_CONFIG_PREFIX}.{var['key']}"
|
||||
parts = storage_key.split(".")
|
||||
current = config
|
||||
value = None
|
||||
for part in parts:
|
||||
if isinstance(current, dict) and part in current:
|
||||
current = current[part]
|
||||
value = current
|
||||
else:
|
||||
value = None
|
||||
break
|
||||
# Missing = key doesn't exist or is empty string
|
||||
if value is None or (isinstance(value, str) and not value.strip()):
|
||||
missing.append(var)
|
||||
return missing
|
||||
|
||||
|
||||
def check_config_version() -> Tuple[int, int]:
|
||||
"""
|
||||
Check config version.
|
||||
@@ -1695,7 +1732,50 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A
|
||||
config = load_config()
|
||||
config["_config_version"] = latest_ver
|
||||
save_config(config)
|
||||
|
||||
|
||||
# ── Skill-declared config vars ──────────────────────────────────────
|
||||
# Skills can declare config.yaml settings they need via
|
||||
# metadata.hermes.config in their SKILL.md frontmatter.
|
||||
# Prompt for any that are missing/empty.
|
||||
missing_skill_config = get_missing_skill_config_vars()
|
||||
if missing_skill_config and interactive and not quiet:
|
||||
print(f"\n {len(missing_skill_config)} skill setting(s) not configured:")
|
||||
for var in missing_skill_config:
|
||||
skill_name = var.get("skill", "unknown")
|
||||
print(f" • {var['key']} — {var['description']} (from skill: {skill_name})")
|
||||
print()
|
||||
try:
|
||||
answer = input(" Configure skill settings? [y/N]: ").strip().lower()
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
answer = "n"
|
||||
|
||||
if answer in ("y", "yes"):
|
||||
print()
|
||||
config = load_config()
|
||||
try:
|
||||
from agent.skill_utils import SKILL_CONFIG_PREFIX
|
||||
except Exception:
|
||||
SKILL_CONFIG_PREFIX = "skills.config"
|
||||
for var in missing_skill_config:
|
||||
default = var.get("default", "")
|
||||
default_hint = f" (default: {default})" if default else ""
|
||||
value = input(f" {var['prompt']}{default_hint}: ").strip()
|
||||
if not value and default:
|
||||
value = str(default)
|
||||
if value:
|
||||
storage_key = f"{SKILL_CONFIG_PREFIX}.{var['key']}"
|
||||
_set_nested(config, storage_key, value)
|
||||
results["config_added"].append(var["key"])
|
||||
print(f" ✓ Saved {var['key']} = {value}")
|
||||
else:
|
||||
results["warnings"].append(
|
||||
f"Skipped {var['key']} — skill '{var.get('skill', '?')}' may ask for it later"
|
||||
)
|
||||
print()
|
||||
save_config(config)
|
||||
else:
|
||||
print(" Set later with: hermes config set <key> <value>")
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@@ -2349,6 +2429,23 @@ def show_config():
|
||||
print(f" Telegram: {'configured' if telegram_token else color('not configured', Colors.DIM)}")
|
||||
print(f" Discord: {'configured' if discord_token else color('not configured', Colors.DIM)}")
|
||||
|
||||
# Skill config
|
||||
try:
|
||||
from agent.skill_utils import discover_all_skill_config_vars, resolve_skill_config_values
|
||||
skill_vars = discover_all_skill_config_vars()
|
||||
if skill_vars:
|
||||
resolved = resolve_skill_config_values(skill_vars)
|
||||
print()
|
||||
print(color("◆ Skill Settings", Colors.CYAN, Colors.BOLD))
|
||||
for var in skill_vars:
|
||||
key = var["key"]
|
||||
value = resolved.get(key, "")
|
||||
skill_name = var.get("skill", "")
|
||||
display_val = str(value) if value else color("(not set)", Colors.DIM)
|
||||
print(f" {key:<20s} {display_val} {color(f'[{skill_name}]', Colors.DIM)}")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
print()
|
||||
print(color("─" * 60, Colors.DIM))
|
||||
print(color(" hermes config edit # Edit config file", Colors.DIM))
|
||||
|
||||
Reference in New Issue
Block a user