mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-08 03:01:47 +00:00
fix(curator): make manual runs synchronous
This commit is contained in:
parent
bda7b240b4
commit
6b9f7140bb
3 changed files with 134 additions and 8 deletions
|
|
@ -12,6 +12,7 @@ from __future__ import annotations
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -57,7 +58,8 @@ def _cmd_status(args) -> int:
|
||||||
print(f" last summary: {summary}")
|
print(f" last summary: {summary}")
|
||||||
_report = state.get("last_report_path")
|
_report = state.get("last_report_path")
|
||||||
if _report:
|
if _report:
|
||||||
print(f" last report: {_report}")
|
suffix = "" if Path(_report).exists() else " (missing)"
|
||||||
|
print(f" last report: {_report}{suffix}")
|
||||||
_ih = curator.get_interval_hours()
|
_ih = curator.get_interval_hours()
|
||||||
_interval_label = (
|
_interval_label = (
|
||||||
f"{_ih // 24}d" if _ih % 24 == 0 and _ih >= 24
|
f"{_ih // 24}d" if _ih % 24 == 0 and _ih >= 24
|
||||||
|
|
@ -161,6 +163,8 @@ def _cmd_run(args) -> int:
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
dry = bool(getattr(args, "dry_run", False))
|
dry = bool(getattr(args, "dry_run", False))
|
||||||
|
background = bool(getattr(args, "background", False))
|
||||||
|
synchronous = bool(getattr(args, "synchronous", False)) or not background
|
||||||
if dry:
|
if dry:
|
||||||
print("curator: running DRY-RUN (report only, no mutations)...")
|
print("curator: running DRY-RUN (report only, no mutations)...")
|
||||||
else:
|
else:
|
||||||
|
|
@ -171,7 +175,7 @@ def _cmd_run(args) -> int:
|
||||||
|
|
||||||
result = curator.run_curator_review(
|
result = curator.run_curator_review(
|
||||||
on_summary=_on_summary,
|
on_summary=_on_summary,
|
||||||
synchronous=bool(args.synchronous),
|
synchronous=synchronous,
|
||||||
dry_run=dry,
|
dry_run=dry,
|
||||||
)
|
)
|
||||||
auto = result.get("auto_transitions", {})
|
auto = result.get("auto_transitions", {})
|
||||||
|
|
@ -188,13 +192,19 @@ def _cmd_run(args) -> int:
|
||||||
f"archived={auto.get('archived', 0)} "
|
f"archived={auto.get('archived', 0)} "
|
||||||
f"reactivated={auto.get('reactivated', 0)}"
|
f"reactivated={auto.get('reactivated', 0)}"
|
||||||
)
|
)
|
||||||
if not args.synchronous:
|
if not synchronous:
|
||||||
print("llm pass running in background — check `hermes curator status` later")
|
print("llm pass running in background — check `hermes curator status` later")
|
||||||
if dry:
|
if dry:
|
||||||
print(
|
if synchronous:
|
||||||
"dry-run: no changes applied. When the report lands, read it with "
|
print(
|
||||||
"`hermes curator status` and run `hermes curator run` (no flag) to apply."
|
"dry-run: no changes applied. Read the report with "
|
||||||
)
|
"`hermes curator status` and run `hermes curator run` (no flag) to apply."
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
"dry-run: no changes applied. When the report lands, read it with "
|
||||||
|
"`hermes curator status` and run `hermes curator run` (no flag) to apply."
|
||||||
|
)
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -461,7 +471,11 @@ def register_cli(parent: argparse.ArgumentParser) -> None:
|
||||||
p_run = subs.add_parser("run", help="Trigger a curator review now")
|
p_run = subs.add_parser("run", help="Trigger a curator review now")
|
||||||
p_run.add_argument(
|
p_run.add_argument(
|
||||||
"--sync", "--synchronous", dest="synchronous", action="store_true",
|
"--sync", "--synchronous", dest="synchronous", action="store_true",
|
||||||
help="Wait for the LLM review pass to finish (default: background thread)",
|
help="Wait for the LLM review pass to finish (default for manual runs)",
|
||||||
|
)
|
||||||
|
p_run.add_argument(
|
||||||
|
"--background", dest="background", action="store_true",
|
||||||
|
help="Start the LLM review pass in a background thread and return immediately",
|
||||||
)
|
)
|
||||||
p_run.add_argument(
|
p_run.add_argument(
|
||||||
"--dry-run", dest="dry_run", action="store_true",
|
"--dry-run", dest="dry_run", action="store_true",
|
||||||
|
|
|
||||||
87
tests/hermes_cli/test_curator_run.py
Normal file
87
tests/hermes_cli/test_curator_run.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
"""Tests for `hermes curator run` CLI behavior."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from types import SimpleNamespace
|
||||||
|
|
||||||
|
|
||||||
|
def _args(**kwargs):
|
||||||
|
values = {
|
||||||
|
"dry_run": False,
|
||||||
|
"synchronous": False,
|
||||||
|
"background": False,
|
||||||
|
}
|
||||||
|
values.update(kwargs)
|
||||||
|
return SimpleNamespace(**values)
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_defaults_to_synchronous(monkeypatch, capsys):
|
||||||
|
import agent.curator as curator_state
|
||||||
|
import hermes_cli.curator as curator_cli
|
||||||
|
|
||||||
|
calls = []
|
||||||
|
monkeypatch.setattr(curator_state, "is_enabled", lambda: True)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
curator_state,
|
||||||
|
"run_curator_review",
|
||||||
|
lambda **kwargs: calls.append(kwargs) or {"auto_transitions": {}},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert curator_cli._cmd_run(_args()) == 0
|
||||||
|
|
||||||
|
assert calls[0]["synchronous"] is True
|
||||||
|
assert calls[0]["dry_run"] is False
|
||||||
|
assert "background" not in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_background_opts_into_async(monkeypatch, capsys):
|
||||||
|
import agent.curator as curator_state
|
||||||
|
import hermes_cli.curator as curator_cli
|
||||||
|
|
||||||
|
calls = []
|
||||||
|
monkeypatch.setattr(curator_state, "is_enabled", lambda: True)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
curator_state,
|
||||||
|
"run_curator_review",
|
||||||
|
lambda **kwargs: calls.append(kwargs) or {"auto_transitions": {}},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert curator_cli._cmd_run(_args(background=True)) == 0
|
||||||
|
|
||||||
|
assert calls[0]["synchronous"] is False
|
||||||
|
assert "llm pass running in background" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_sync_wins_over_background(monkeypatch):
|
||||||
|
import agent.curator as curator_state
|
||||||
|
import hermes_cli.curator as curator_cli
|
||||||
|
|
||||||
|
calls = []
|
||||||
|
monkeypatch.setattr(curator_state, "is_enabled", lambda: True)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
curator_state,
|
||||||
|
"run_curator_review",
|
||||||
|
lambda **kwargs: calls.append(kwargs) or {"auto_transitions": {}},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert curator_cli._cmd_run(_args(synchronous=True, background=True)) == 0
|
||||||
|
|
||||||
|
assert calls[0]["synchronous"] is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_dry_run_default_reports_synchronous_wording(monkeypatch, capsys):
|
||||||
|
import agent.curator as curator_state
|
||||||
|
import hermes_cli.curator as curator_cli
|
||||||
|
|
||||||
|
monkeypatch.setattr(curator_state, "is_enabled", lambda: True)
|
||||||
|
monkeypatch.setattr(
|
||||||
|
curator_state,
|
||||||
|
"run_curator_review",
|
||||||
|
lambda **kwargs: {"auto_transitions": {}},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert curator_cli._cmd_run(_args(dry_run=True)) == 0
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert "When the report lands" not in out
|
||||||
|
assert "Read the report with `hermes curator status`" in out
|
||||||
|
|
@ -175,3 +175,28 @@ def test_status_no_skills_produces_clean_empty_output(curator_status_env):
|
||||||
# None of the ranking sections render
|
# None of the ranking sections render
|
||||||
assert "most active" not in out
|
assert "most active" not in out
|
||||||
assert "least active" not in out
|
assert "least active" not in out
|
||||||
|
|
||||||
|
|
||||||
|
def test_status_marks_missing_last_report_path(monkeypatch, capsys, tmp_path):
|
||||||
|
import agent.curator as curator_state
|
||||||
|
import hermes_cli.curator as curator_cli
|
||||||
|
import tools.skill_usage as skill_usage
|
||||||
|
|
||||||
|
missing_report = tmp_path / "stale-report"
|
||||||
|
monkeypatch.setattr(curator_state, "load_state", lambda: {
|
||||||
|
"paused": False,
|
||||||
|
"last_run_at": None,
|
||||||
|
"last_run_summary": "auto: no changes",
|
||||||
|
"run_count": 1,
|
||||||
|
"last_report_path": str(missing_report),
|
||||||
|
})
|
||||||
|
monkeypatch.setattr(curator_state, "is_enabled", lambda: True)
|
||||||
|
monkeypatch.setattr(curator_state, "get_interval_hours", lambda: 168)
|
||||||
|
monkeypatch.setattr(curator_state, "get_stale_after_days", lambda: 30)
|
||||||
|
monkeypatch.setattr(curator_state, "get_archive_after_days", lambda: 90)
|
||||||
|
monkeypatch.setattr(skill_usage, "agent_created_report", lambda: [])
|
||||||
|
|
||||||
|
assert curator_cli._cmd_status(SimpleNamespace()) == 0
|
||||||
|
|
||||||
|
out = capsys.readouterr().out
|
||||||
|
assert f"last report: {missing_report} (missing)" in out
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue