fix(config): shipped template no longer enables session auto-reset (#67772)

#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
This commit is contained in:
Teknium 2026-07-19 23:33:53 -07:00 committed by GitHub
parent 1157c636c5
commit 0d7fad7b88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 84 additions and 18 deletions

View file

@ -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

View file

@ -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

View file

@ -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()