Skip to content

fix(curator): make manual 'hermes curator run' synchronous by default (#20555)#21216

Merged
teknium1 merged 2 commits into
mainfrom
hermes/hermes-8615d3a6
May 7, 2026
Merged

fix(curator): make manual 'hermes curator run' synchronous by default (#20555)#21216
teknium1 merged 2 commits into
mainfrom
hermes/hermes-8615d3a6

Conversation

@teknium1

@teknium1 teknium1 commented May 7, 2026

Copy link
Copy Markdown
Contributor

Summary

hermes curator run now blocks until the LLM pass finishes, so its report is actually written before the CLI exits. Daemon-thread execution is still available behind an explicit --background flag. Scheduled curator runs (gateway tick, CLI-startup check) are untouched.

Root cause: the manual CLI path spawned the LLM review in a daemon thread and returned immediately; in a short-lived hermes curator run invocation, the daemon thread was killed on process exit before it could write REPORT.md / run.json or update last_report_path. Users saw hermes curator status reporting stale or nonexistent report paths.

Changes

  • hermes_cli/curator.py: manual run defaults to synchronous=True; --background opts into the legacy daemon-thread behavior; --sync still accepted and wins over --background. curator status annotates missing last_report_path as (missing).
  • tests/hermes_cli/test_curator_run.py + test_curator_status.py: regression tests for all 4 flag combinations + missing-path annotation.
  • website/docs/user-guide/features/curator.md + reference/cli-commands.md: update CLI docs — default is now sync; --background is the opt-in async flag.

Scope check

Only the manual hermes curator run CLI path is affected. Two other callers of run_curator_review() are unchanged:

  • cli.py:10258 — interactive CLI startup auto-run (long-lived session, daemon thread has time to finish)
  • gateway/run.py:14886 — gateway weekly cron tick (long-lived daemon process, daemon thread fine)

Validation

Before After
hermes curator run default async daemon thread, killed on CLI exit, report often lost synchronous, report written before return
hermes curator run --background (did not exist) legacy async behavior available behind explicit flag
Targeted tests (8 files) 144 pass 144 pass
E2E (real imports, all 4 flag paths) 5/5: default sync, --background async, --sync sync, --sync --background sync wins, --dry-run sync

Authorship

Closes #20555. Supersedes #20556.

Thanks @steezkelly for the clear bug report + local fix sketch that @LeonSGP43's PR implemented.

LeonSGP43 and others added 2 commits May 7, 2026 05:24
Follow-up to the previous commit which flipped 'hermes curator run'
default from async to sync. Updates the curator.md feature page and
cli-commands.md reference to show --background as the opt-in async
flag and note that the default now blocks until the LLM pass finishes.
@github-actions

github-actions Bot commented May 7, 2026

Copy link
Copy Markdown
Contributor

🔎 Lint report: hermes/hermes-8615d3a6 vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 7498 on HEAD, 7547 on base (✅ -49)

🆕 New issues: none

✅ Fixed issues (31):

Rule Count
unresolved-attribute 25
invalid-assignment 6
First entries
tests/run_agent/test_compressor_fallback_update.py:71: [unresolved-attribute] unresolved-attribute: Attribute `context_length` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:11879: [invalid-assignment] invalid-assignment: Object of type `Literal[False]` is not assignable to attribute `_context_probed` on type `None | Unknown | ContextCompressor`
run_agent.py:13573: [unresolved-attribute] unresolved-attribute: Attribute `should_compress` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:12687: [unresolved-attribute] unresolved-attribute: Attribute `context_length` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:11880: [invalid-assignment] invalid-assignment: Object of type `Literal[False]` is not assignable to attribute `_context_probe_persistable` on type `None | Unknown | ContextCompressor`
cli.py:7967: [unresolved-attribute] unresolved-attribute: Attribute `context_length` is not defined on `None` in union `None | Unknown | ContextCompressor`
tests/run_agent/test_compressor_fallback_update.py:72: [unresolved-attribute] unresolved-attribute: Attribute `threshold_percent` is not defined on `None` in union `None | Unknown | ContextCompressor`
tests/run_agent/test_compressor_fallback_update.py:68: [unresolved-attribute] unresolved-attribute: Attribute `base_url` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:9374: [invalid-assignment] invalid-assignment: Object of type `int` is not assignable to attribute `last_prompt_tokens` on type `None | Unknown | ContextCompressor`
run_agent.py:9252: [unresolved-attribute] unresolved-attribute: Attribute `compress` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:12764: [unresolved-attribute] unresolved-attribute: Attribute `update_model` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:2739: [invalid-assignment] invalid-assignment: Object of type `int` is not assignable to attribute `threshold_tokens` on type `None | Unknown | ContextCompressor`
tests/run_agent/test_compressor_fallback_update.py:72: [unresolved-attribute] unresolved-attribute: Attribute `threshold_tokens` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:10812: [unresolved-attribute] unresolved-attribute: Attribute `protect_first_n` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:2745: [invalid-assignment] invalid-assignment: Object of type `int | float` is not assignable to attribute `threshold_percent` on type `None | Unknown | ContextCompressor`
run_agent.py:10868: [unresolved-attribute] unresolved-attribute: Attribute `threshold_tokens` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:9355: [unresolved-attribute] unresolved-attribute: Attribute `compression_count` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:11869: [unresolved-attribute] unresolved-attribute: Attribute `update_from_response` is not defined on `None` in union `None | Unknown | ContextCompressor`
tests/run_agent/test_switch_model_context.py:60: [unresolved-attribute] unresolved-attribute: Attribute `model` is not defined on `None` in union `None | Unknown | ContextCompressor`
tests/run_agent/test_compression_feasibility.py:346: [unresolved-attribute] unresolved-attribute: Attribute `threshold_tokens` is not defined on `None` in union `None | Unknown | ContextCompressor`
cli.py:7969: [unresolved-attribute] unresolved-attribute: Attribute `compression_count` is not defined on `None` in union `None | Unknown | ContextCompressor`
run_agent.py:10813: [unresolved-attribute] unresolved-attribute: Attribute `protect_last_n` is not defined on `None` in union `None | Unknown | ContextCompressor`
tests/run_agent/test_compressor_fallback_update.py:70: [unresolved-attribute] unresolved-attribute: Attribute `provider` is not defined on `None` in union `None | Unknown | ContextCompressor`
tests/run_agent/test_switch_model_context.py:49: [unresolved-attribute] unresolved-attribute: Attribute `context_length` is not defined on `None` in union `None | Unknown | ContextCompressor`
tests/run_agent/test_compressor_fallback_update.py:69: [unresolved-attribute] unresolved-attribute: Attribute `api_key` is not defined on `None` in union `None | Unknown | ContextCompressor`
... and 6 more

Unchanged: 3938 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

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

Labels

comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: hermes curator run can lose LLM reports because CLI exits while background daemon thread is still running

3 participants