From 0d7fad7b88bbbaab735565622709a575837adc78 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sun, 19 Jul 2026 23:33:53 -0700 Subject: [PATCH] fix(config): shipped template no longer enables session auto-reset (#67772) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #60194 flipped SessionResetPolicy's default to mode: none, but cli-config.yaml.example still shipped session_reset.mode: both. Every install path (install.sh, install.ps1, docker stage2-hook, hermes doctor) copies the template verbatim to ~/.hermes/config.yaml, so fresh installs got an EXPLICIT mode: both that overrides the code default — users hit 24h-idle resets with 'nothing' in their config enabling it. - cli-config.yaml.example: session_reset.mode both -> none, comments rewritten to describe auto-reset as opt-in - docs/session-lifecycle.md: appendix example updated to match - tests/gateway/test_config.py: invariant tests — template seed, absent config, and mode-less session_reset block all resolve to mode none; explicit opt-in still honored --- cli-config.yaml.example | 33 +++++++++--------- docs/session-lifecycle.md | 2 +- tests/gateway/test_config.py | 67 ++++++++++++++++++++++++++++++++++++ 3 files changed, 84 insertions(+), 18 deletions(-) diff --git a/cli-config.yaml.example b/cli-config.yaml.example index 2049b4426a54..d74b5f3c6ba0 100644 --- a/cli-config.yaml.example +++ b/cli-config.yaml.example @@ -583,28 +583,27 @@ memory: # Session Reset Policy (Messaging Platforms) # ============================================================================= # Controls when messaging sessions (Telegram, Discord, WhatsApp, Slack) are -# automatically cleared. Without resets, conversation context grows indefinitely -# which increases API costs with every message. +# automatically cleared. Default is "none": sessions never auto-reset — +# conversation context lives until you /reset or /new manually, or context +# compression kicks in. Opt in to automatic resets if you prefer sessions to +# clear on a schedule (long-lived context increases API cost per message, +# though prompt caching and compression keep this manageable). # -# When a reset triggers, the agent first saves important information to its -# persistent memory — but the conversation context is wiped. The agent starts -# fresh but retains learned facts via its memory system. -# -# Users can always manually reset with /reset or /new in chat. +# When an automatic reset triggers, the agent first saves important +# information to its persistent memory — but the conversation context is +# wiped. The agent starts fresh but retains learned facts via its memory +# system. # # Modes: -# "both" - Reset on EITHER inactivity timeout or daily boundary (recommended) -# "idle" - Reset only after N minutes of inactivity -# "daily" - Reset only at a fixed hour each day -# "none" - Never auto-reset; context lives until /reset or compression kicks in -# -# When a reset triggers, the agent gets one turn to save important memories and -# skills before the context is wiped. Persistent memory carries across sessions. +# "none" - Never auto-reset (default); context lives until /reset or compression +# "idle" - Reset after N minutes of inactivity +# "daily" - Reset at a fixed hour each day +# "both" - Reset on EITHER inactivity timeout or daily boundary # session_reset: - mode: both # "both", "idle", "daily", or "none" - idle_minutes: 1440 # Inactivity timeout in minutes (default: 1440 = 24 hours) - at_hour: 4 # Daily reset hour, 0-23 local time (default: 4 AM) + mode: none # "none", "idle", "daily", or "both" + idle_minutes: 1440 # Inactivity timeout in minutes (used by "idle"/"both") + at_hour: 4 # Daily reset hour, 0-23 local time (used by "daily"/"both") # Maximum number of simultaneously active chat sessions across CLI, TUI, # dashboard chat, and messaging gateway. Set to null, 0, or omit to allow diff --git a/docs/session-lifecycle.md b/docs/session-lifecycle.md index fbca305c54b6..3c9bb4ee2824 100644 --- a/docs/session-lifecycle.md +++ b/docs/session-lifecycle.md @@ -628,7 +628,7 @@ When a session expires: ```yaml session_reset: - mode: both # none | idle | daily | both + mode: none # none (default) | idle | daily | both at_hour: 4 # daily reset hour (local time) idle_minutes: 1440 # idle timeout (24h) notify: true # notify user on auto-reset diff --git a/tests/gateway/test_config.py b/tests/gateway/test_config.py index 3422ebcf08e5..4ef4def58714 100644 --- a/tests/gateway/test_config.py +++ b/tests/gateway/test_config.py @@ -2,6 +2,7 @@ import logging import os +from pathlib import Path from unittest.mock import patch import pytest @@ -486,6 +487,72 @@ class TestGatewayConfigRoundtrip: class TestLoadGatewayConfig: + def test_shipped_template_does_not_enable_auto_reset(self, tmp_path, monkeypatch): + """A fresh install seeded from cli-config.yaml.example must not + auto-reset sessions. + + Installers (scripts/install.sh, scripts/install.ps1, + docker/stage2-hook.sh, hermes doctor) copy the template verbatim to + ~/.hermes/config.yaml, so whatever ``session_reset.mode`` the template + ships becomes an EXPLICIT user setting that overrides the code + default. After #60194 flipped the default to "none", the template + still said "both" — every new install kept 24h-idle resets on + (Luciano's report, July 2026). This pins the invariant: template + seed == no auto-reset. + """ + template = ( + Path(__file__).resolve().parents[2] / "cli-config.yaml.example" + ) + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + (hermes_home / "config.yaml").write_text( + template.read_text(encoding="utf-8"), encoding="utf-8" + ) + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + config = load_gateway_config() + + assert config.default_reset_policy.mode == "none" + + def test_no_config_yaml_means_no_auto_reset(self, tmp_path, monkeypatch): + """With no config.yaml at all, sessions must never auto-reset.""" + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + config = load_gateway_config() + + assert config.default_reset_policy.mode == "none" + + def test_session_reset_without_mode_means_no_auto_reset(self, tmp_path, monkeypatch): + """A session_reset block that tunes knobs but omits mode stays off.""" + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + (hermes_home / "config.yaml").write_text( + "session_reset:\n idle_minutes: 60\n", encoding="utf-8" + ) + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + config = load_gateway_config() + + assert config.default_reset_policy.mode == "none" + assert config.default_reset_policy.idle_minutes == 60 + + def test_explicit_session_reset_opt_in_is_honored(self, tmp_path, monkeypatch): + """Users who explicitly opt in to auto-reset keep their policy.""" + hermes_home = tmp_path / ".hermes" + hermes_home.mkdir() + (hermes_home / "config.yaml").write_text( + "session_reset:\n mode: idle\n idle_minutes: 30\n", + encoding="utf-8", + ) + monkeypatch.setenv("HERMES_HOME", str(hermes_home)) + + config = load_gateway_config() + + assert config.default_reset_policy.mode == "idle" + assert config.default_reset_policy.idle_minutes == 30 + def test_bridges_quick_commands_from_config_yaml(self, tmp_path, monkeypatch): hermes_home = tmp_path / ".hermes" hermes_home.mkdir()