mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-02 08:47:26 +08:00
fix(curator): seed defaults on update, create logs/curator dir, defer fire import (#17927)
Three fixes bundled for curator reliability on existing installs and broken/partial installs: 1. run_agent.py: defer `import fire` into the __main__ block. `fire` is only used by `fire.Fire(main)` when running run_agent.py directly as a CLI — it is NOT needed for library usage. Importing it at module top made `from run_agent import AIAgent` from a daemon thread (e.g. the curator's forked review agent) crash with ModuleNotFoundError on broken/partial installs where `fire` isn't present. 2. hermes_cli/config.py: add version 22 → 23 migration that writes the `curator` + `auxiliary.curator` sections to config.yaml with their defaults, only filling keys the user hasn't overridden. Existing configs from before PR #16049 / the April 2026 `auxiliary.curator` unification had neither section on disk, so users couldn't see or edit the settings in their config.yaml (runtime deep-merge papered over it at read time, but the file never reflected reality). 3. hermes_cli/config.py: `ensure_hermes_home()` now pre-creates `~/.hermes/logs/curator/` alongside cron/sessions/logs/memories on every CLI launch. Managed-mode (NixOS) variant mkdir's it defensively after the activation-script existence checks, since the activation script may not know about this subpath. 4. agent/curator.py: `_reports_root()` mkdir's the dir at call time as belt-and-suspenders for entry paths that bypass both ensure_hermes_home() and the v23 migration (gateway-only installs, bare library use). E2E validated in isolated HERMES_HOME: fresh install gets full defaults seeded; partial-override config keeps user's `enabled: false` and custom `interval_hours` while filling the missing keys; re-running the migration is a no-op.
This commit is contained in:
@@ -350,7 +350,7 @@ def ensure_hermes_home():
|
||||
else:
|
||||
home.mkdir(parents=True, exist_ok=True)
|
||||
_secure_dir(home)
|
||||
for subdir in ("cron", "sessions", "logs", "memories"):
|
||||
for subdir in ("cron", "sessions", "logs", "logs/curator", "memories"):
|
||||
d = home / subdir
|
||||
d.mkdir(parents=True, exist_ok=True)
|
||||
_secure_dir(d)
|
||||
@@ -371,6 +371,10 @@ def _ensure_hermes_home_managed(home: Path):
|
||||
f"{d} does not exist. "
|
||||
"Run 'sudo nixos-rebuild switch' first."
|
||||
)
|
||||
# Curator reports dir is a sub-path of logs/; create it if missing.
|
||||
# In managed mode the activation script may not know about this subdir,
|
||||
# so we mkdir it ourselves (it's inside an already-secured logs/ dir).
|
||||
(home / "logs" / "curator").mkdir(parents=True, exist_ok=True)
|
||||
# Inside umask(0o007) scope — SOUL.md will be created as 0660
|
||||
_ensure_default_soul_md(home)
|
||||
|
||||
@@ -1201,7 +1205,7 @@ DEFAULT_CONFIG = {
|
||||
},
|
||||
|
||||
# Config schema version - bump this when adding new required fields
|
||||
"_config_version": 22,
|
||||
"_config_version": 23,
|
||||
}
|
||||
|
||||
# =============================================================================
|
||||
@@ -3274,6 +3278,90 @@ def migrate_config(interactive: bool = True, quiet: bool = False) -> Dict[str, A
|
||||
"Use `hermes plugins enable <name>` to activate."
|
||||
)
|
||||
|
||||
# ── Version 22 → 23: seed curator defaults + create logs/curator/ ──
|
||||
# The curator (background skill maintenance) was added in PR #16049, but
|
||||
# existing configs from before that PR (or before the April 2026
|
||||
# unification under `auxiliary.curator`) never wrote the curator section
|
||||
# to disk. The runtime deep-merge in `load_config()` fills defaults at
|
||||
# read time, so the curator *functions*; but users can't see/edit the
|
||||
# settings in their `config.yaml`, and `hermes curator status` has no
|
||||
# stable logs dir to point at until the first run mkdir's it.
|
||||
#
|
||||
# This migration:
|
||||
# 1. Writes the `curator` top-level section to config.yaml (enabled,
|
||||
# interval_hours, min_idle_hours, stale_after_days, archive_after_days)
|
||||
# — only keys the user hasn't already overridden.
|
||||
# 2. Writes the `auxiliary.curator` aux-task slot (provider, model,
|
||||
# base_url, api_key, timeout, extra_body) — canonical slot for
|
||||
# routing the curator fork to a cheaper aux model.
|
||||
# 3. Creates `~/.hermes/logs/curator/` if missing (belt-and-suspenders
|
||||
# on top of ensure_hermes_home() — old profiles that predate this
|
||||
# migration still benefit).
|
||||
if current_ver < 23:
|
||||
try:
|
||||
curator_dir = get_hermes_home() / "logs" / "curator"
|
||||
curator_dir.mkdir(parents=True, exist_ok=True)
|
||||
except Exception as e:
|
||||
results["warnings"].append(f"Could not create {curator_dir}: {e}")
|
||||
|
||||
config = read_raw_config()
|
||||
touched = False
|
||||
|
||||
# (1) Top-level curator section — only add missing keys
|
||||
_curator_defaults = DEFAULT_CONFIG.get("curator", {})
|
||||
raw_curator = config.get("curator")
|
||||
if not isinstance(raw_curator, dict):
|
||||
raw_curator = {}
|
||||
added_curator: List[str] = []
|
||||
for k, v in _curator_defaults.items():
|
||||
if k not in raw_curator:
|
||||
raw_curator[k] = copy.deepcopy(v)
|
||||
added_curator.append(k)
|
||||
if added_curator:
|
||||
config["curator"] = raw_curator
|
||||
touched = True
|
||||
|
||||
# (2) auxiliary.curator task slot
|
||||
_aux_curator_defaults = (
|
||||
DEFAULT_CONFIG.get("auxiliary", {}).get("curator", {})
|
||||
)
|
||||
raw_aux = config.get("auxiliary")
|
||||
if not isinstance(raw_aux, dict):
|
||||
raw_aux = {}
|
||||
raw_aux_curator = raw_aux.get("curator")
|
||||
if not isinstance(raw_aux_curator, dict):
|
||||
raw_aux_curator = {}
|
||||
added_aux: List[str] = []
|
||||
for k, v in _aux_curator_defaults.items():
|
||||
if k not in raw_aux_curator:
|
||||
raw_aux_curator[k] = copy.deepcopy(v)
|
||||
added_aux.append(k)
|
||||
if added_aux:
|
||||
raw_aux["curator"] = raw_aux_curator
|
||||
config["auxiliary"] = raw_aux
|
||||
touched = True
|
||||
|
||||
if touched:
|
||||
save_config(config)
|
||||
if added_curator:
|
||||
results["config_added"].append(
|
||||
f"curator ({len(added_curator)} default key(s))"
|
||||
)
|
||||
if not quiet:
|
||||
print(
|
||||
" ✓ Seeded curator defaults in config.yaml: "
|
||||
f"{', '.join(added_curator)}"
|
||||
)
|
||||
if added_aux:
|
||||
results["config_added"].append(
|
||||
f"auxiliary.curator ({len(added_aux)} default key(s))"
|
||||
)
|
||||
if not quiet:
|
||||
print(
|
||||
" ✓ Seeded auxiliary.curator defaults in config.yaml: "
|
||||
f"{', '.join(added_aux)}"
|
||||
)
|
||||
|
||||
if current_ver < latest_ver and not quiet:
|
||||
print(f"Config version: {current_ver} → {latest_ver}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user