Conversation
Adds agent.stay_awake config option (default: false). When enabled, prevents system/idle sleep during run_conversation() so long-running LLM calls and tool execution aren't interrupted mid-session. OS support: - macOS: caffeinate -i (idle sleep only, display free to dim) - Linux: systemd-inhibit --what=sleep:idle (graceful no-op if absent) - Windows: SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) Design decisions: - Prevents system/idle sleep only, NOT display sleep (user doesn't need the screen lit — they need API calls to complete) - Applies to BOTH CLI and gateway runs (a message at 3am that causes a broken session because the machine slept is worse than battery drain — and the setting is opt-in) - Context-manager pattern with try/finally ensures cleanup even on errors and interrupts - Degrades gracefully to no-op when the inhibitor isn't available (containers, headless servers, WSL1)
Harden the salvaged inhibitor (#19484) onto the Sep-2026 layout: - move hermes_stay_awake.py -> agent/stay_awake.py (topical sibling, not a new root module) - add turn_scope(): one process-wide refcounted inhibitor shared by concurrent turns; first turn in starts it, last turn out stops it. Refcounting matters because Windows SetThreadExecutionState is not nestable - a second scope exiting would clear the first one's flags. - wire at the single choke point every surface passes through (TurnFacadeMixin.run_conversation), replacing the original PR's three per-surface constructor threads (cli.py kwarg, gateway raw-YAML loader, AIAgent __init__ param) - CLI, gateway, TUI, Desktop, cron and subagents all inherit it with zero per-surface wiring - config default agent.stay_awake: False in config_defaults.py (the original patched the pre-split hermes_cli/config.py) - replace platform.system()-mocked OS tests with 3 invariant tests (disabled = zero side effects; refcount contract; config default off) - repo policy forbids faking the host OS - document under user-guide/configuration.md Live E2E on this host: real systemd-inhibit registers during a scoped turn, stays single when nested, releases on scope exit; disabled config spawns nothing. Port inspired by Kilo-Org/kilocode#12974 + #13927 (keep-awake / /caffeinate); implementation salvaged from hermes-agent PR #19484 (@aommi) with authorship preserved on the base commit.
Contributor
૮ >ﻌ< ა ci reviewran on dd61acd — refactor: wire stay-awake into the turn facade as a refcount ❌ Job failuresCheck contributors / check-attribution · View jobJob Check contributors / check-attribution failed.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stay-awake inhibitor:
agent.stay_awakekeeps the OS from sleeping mid-turnA laptop that sleeps mid-turn drops the in-flight API call and halts tool execution; with
agent.stay_awake: true(default false) Hermes holds an OS sleep inhibitor for exactly the duration of each agent turn, on every surface (CLI, gateway, TUI, Desktop, cron, subagents).Salvage with credit: the inhibitor module is PR #19484 by @aommi (May 2026), cherry-picked so authorship survives, then rebased onto the Sep-2026 decomposed layout and rewired. Port motivated by the same feature landing in Kilo Code this week (Kilo-Org/kilocode#12974 keep-awake toggle, #13927
/caffeinateCLI).Changes
agent/stay_awake.py—StayAwakecontext manager (macOScaffeinate -i, Linuxsystemd-inhibit --what=sleep:idle, WindowsSetThreadExecutionState; display sleep untouched; graceful no-op where unavailable) + newturn_scope(): one process-wide refcounted inhibitor shared by concurrent turns. Refcounting matters because WindowsSetThreadExecutionStateis not nestable — a second scope exiting would clear the first one's flags.agent/turn_facade.py—turn_scope()joins the existingwithat the single choke point every surface passes through. This replaces the original PR's three per-surface threads (cli.py kwarg, gateway raw-YAML loader,AIAgent.__init__param): zero new constructor parameters, and surfaces added later inherit it for free.hermes_cli/config_defaults.py—agent.stay_awake: False.tests/agent/test_stay_awake.py— 3 invariant tests (disabled ⇒ zero side effects; refcount contract: nested scopes share one inhibitor, released exactly once; config default off). The original PR's 14 tests faked the host OS by patchingplatform.system()— repo policy forbids that, so they were replaced, not trimmed.website/docs/user-guide/configuration.md— user-facing docs.Validation
HERMES_HOME, real imports)systemd-inhibit --listshows the hermes inhibitor during aturn_scope(); nested scope keeps exactly one; released on exit; disabled config spawns nothingscripts/run_tests.sh tests/agent/test_stay_awake.pyscripts/run_tests.sh tests/agent/Live repro: before — no inhibitor exists,
systemd-inhibit --listshows nothing hermes-owned during a turn; after — inhibitor registers for the turn's duration and releases on exit (probe transcript in commit message).Architectural difference vs Kilo Code
Kilo ties keep-awake to a client-side service (VS Code extension /
/caffeinateTUI command) with session-busy tracking. Hermes runs turns synchronously in-process, so the turn lease choke point already IS the busy signal — the inhibitor scope maps 1:1 to real work with no polling or client wiring.Infographic