From 84e840659aabc5682d3699c9466a050d82f644df Mon Sep 17 00:00:00 2001 From: Xubin Ren Date: Sat, 11 Apr 2026 07:32:56 +0000 Subject: [PATCH] refactor(config): rename auto compact config key Prefer the more user-friendly idleCompactAfterMinutes name for auto compact while keeping sessionTtlMinutes as a backward-compatible alias. Update tests and README to document the retained recent-context behavior and the new preferred key. --- README.md | 13 ++++++++----- nanobot/config/schema.py | 7 ++++++- tests/agent/test_auto_compact.py | 17 +++++++++++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 88ff35f29..856986754 100644 --- a/README.md +++ b/README.md @@ -1505,13 +1505,13 @@ MCP tools are automatically discovered and registered on startup. The LLM can us ### Auto Compact -When a user is idle for longer than a configured TTL, nanobot **proactively** compresses the older part of the session context into a summary while keeping a recent legal suffix of live messages. This reduces token cost and first-token latency when the user returns — instead of re-processing a long stale context with an expired KV cache, the model receives a compact summary, the most recent live context, and fresh input. +When a user is idle for longer than a configured threshold, nanobot **proactively** compresses the older part of the session context into a summary while keeping a recent legal suffix of live messages. This reduces token cost and first-token latency when the user returns — instead of re-processing a long stale context with an expired KV cache, the model receives a compact summary, the most recent live context, and fresh input. ```json { "agents": { "defaults": { - "sessionTtlMinutes": 15 + "idleCompactAfterMinutes": 15 } } } @@ -1519,15 +1519,18 @@ When a user is idle for longer than a configured TTL, nanobot **proactively** co | Option | Default | Description | |--------|---------|-------------| -| `agents.defaults.sessionTtlMinutes` | `0` (disabled) | Minutes of idle time before auto-compaction. Set to `0` to disable. Recommended: `15` — matches typical LLM KV cache expiration, so compacted sessions won't waste cache on cold entries. | +| `agents.defaults.idleCompactAfterMinutes` | `0` (disabled) | Minutes of idle time before auto-compaction starts. Set to `0` to disable. Recommended: `15` — close to a typical LLM KV cache expiry window, so stale sessions get compacted before the user returns. | + +`sessionTtlMinutes` remains accepted as a legacy alias for backward compatibility, but `idleCompactAfterMinutes` is the preferred config key going forward. How it works: 1. **Idle detection**: On each idle tick (~1 s), checks all sessions for expiration. -2. **Background compaction**: Expired sessions summarize the older live prefix via LLM and keep the most recent legal suffix (currently 8 messages). +2. **Background compaction**: Idle sessions summarize the older live prefix via LLM and keep the most recent legal suffix (currently 8 messages). 3. **Summary injection**: When the user returns, the summary is injected as runtime context (one-shot, not persisted) alongside the retained recent suffix. +4. **Restart-safe resume**: The summary is also mirrored into session metadata so it can still be recovered after a process restart. > [!TIP] -> The summary survives bot restarts — it's stored in session metadata and recovered on the next message. +> Think of auto compact as "summarize older context, keep the freshest live turns." It is not a hard session reset. ### Timezone diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py index 8ab68d7b5..67cce4470 100644 --- a/nanobot/config/schema.py +++ b/nanobot/config/schema.py @@ -77,7 +77,12 @@ class AgentDefaults(Base): reasoning_effort: str | None = None # low / medium / high / adaptive - enables LLM thinking mode timezone: str = "UTC" # IANA timezone, e.g. "Asia/Shanghai", "America/New_York" unified_session: bool = False # Share one session across all channels (single-user multi-device) - session_ttl_minutes: int = Field(default=0, ge=0) # Auto /new after idle (0 = disabled) + session_ttl_minutes: int = Field( + default=0, + ge=0, + validation_alias=AliasChoices("idleCompactAfterMinutes", "sessionTtlMinutes"), + serialization_alias="idleCompactAfterMinutes", + ) # Auto-compact idle threshold in minutes (0 = disabled) dream: DreamConfig = Field(default_factory=DreamConfig) diff --git a/tests/agent/test_auto_compact.py b/tests/agent/test_auto_compact.py index 8f1be03a2..b3462820b 100644 --- a/tests/agent/test_auto_compact.py +++ b/tests/agent/test_auto_compact.py @@ -55,6 +55,23 @@ class TestSessionTTLConfig: defaults = AgentDefaults(session_ttl_minutes=30) assert defaults.session_ttl_minutes == 30 + def test_user_friendly_alias_is_supported(self): + """Config should accept idleCompactAfterMinutes as the preferred JSON key.""" + defaults = AgentDefaults.model_validate({"idleCompactAfterMinutes": 30}) + assert defaults.session_ttl_minutes == 30 + + def test_legacy_alias_is_still_supported(self): + """Config should still accept the old sessionTtlMinutes key for compatibility.""" + defaults = AgentDefaults.model_validate({"sessionTtlMinutes": 30}) + assert defaults.session_ttl_minutes == 30 + + def test_serializes_with_user_friendly_alias(self): + """Config dumps should use idleCompactAfterMinutes for JSON output.""" + defaults = AgentDefaults(session_ttl_minutes=30) + data = defaults.model_dump(mode="json", by_alias=True) + assert data["idleCompactAfterMinutes"] == 30 + assert "sessionTtlMinutes" not in data + class TestAgentLoopTTLParam: """Test that AutoCompact receives and stores session_ttl_minutes."""