fix(agent): throttle idle scans by default

This commit is contained in:
Xubin Ren
2026-07-27 02:15:48 +08:00
parent 7aab7e8830
commit 68717937e8
3 changed files with 9 additions and 9 deletions
+3 -3
View File
@@ -2159,7 +2159,7 @@ When a user is idle for longer than a configured threshold, nanobot **proactivel
"agents": { "agents": {
"defaults": { "defaults": {
"idleCompactAfterMinutes": 15, "idleCompactAfterMinutes": 15,
"idleCompactCheckIntervalSeconds": 0 "idleCompactCheckIntervalSeconds": 60
} }
} }
} }
@@ -2168,12 +2168,12 @@ When a user is idle for longer than a configured threshold, nanobot **proactivel
| Option | Default | Description | | Option | Default | Description |
|--------|---------|-------------| |--------|---------|-------------|
| `agents.defaults.idleCompactAfterMinutes` | `15` | Minutes of idle time before auto-compaction starts. Set to `0` to disable. The default is close to a typical LLM KV cache expiry window, so stale sessions get compacted before the user returns. | | `agents.defaults.idleCompactAfterMinutes` | `15` | Minutes of idle time before auto-compaction starts. Set to `0` to disable. The default is close to a typical LLM KV cache expiry window, so stale sessions get compacted before the user returns. |
| `agents.defaults.idleCompactCheckIntervalSeconds` | `0` | Minimum number of seconds between scans for idle sessions. | | `agents.defaults.idleCompactCheckIntervalSeconds` | `60` | Minimum number of seconds between scans for idle sessions. Set to `0` to scan on every idle tick (~1 s). |
`sessionTtlMinutes` remains accepted as a legacy alias for backward compatibility, but `idleCompactAfterMinutes` is the preferred config key going forward. `sessionTtlMinutes` remains accepted as a legacy alias for backward compatibility, but `idleCompactAfterMinutes` is the preferred config key going forward.
How it works: How it works:
1. **Idle detection**: On each idle tick (~1 s), checks all sessions for expiration, subject to the minimum interval set by `idleCompactCheckIntervalSeconds`. 1. **Idle detection**: On each idle tick (~1 s), checks whether an idle-session scan is due. By default, the full scan runs at most once per minute.
2. **Background compaction**: Idle 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. 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. 4. **Restart-safe resume**: The summary is also mirrored into session metadata so it can still be recovered after a process restart.
+1 -1
View File
@@ -155,7 +155,7 @@ class AgentDefaults(Base):
serialization_alias="idleCompactAfterMinutes", serialization_alias="idleCompactAfterMinutes",
) # Auto-compact idle threshold in minutes (0 = disabled) ) # Auto-compact idle threshold in minutes (0 = disabled)
idle_compact_check_interval_seconds: int = Field( idle_compact_check_interval_seconds: int = Field(
default=0, default=60,
ge=0, ge=0,
) # Minimum interval in seconds between scans for idle sessions ) # Minimum interval in seconds between scans for idle sessions
consolidation_ratio: float = Field( consolidation_ratio: float = Field(
+5 -5
View File
@@ -180,10 +180,10 @@ class TestSessionTTLConfig:
assert data["idleCompactAfterMinutes"] == 30 assert data["idleCompactAfterMinutes"] == 30
assert "sessionTtlMinutes" not in data assert "sessionTtlMinutes" not in data
def test_idle_scan_interval_defaults_to_zero(self): def test_idle_scan_interval_defaults_to_sixty_seconds(self):
"""The default should preserve a scan on every idle tick.""" """The config default should avoid scanning all sessions every idle tick."""
defaults = AgentDefaults() defaults = AgentDefaults()
assert defaults.idle_compact_check_interval_seconds == 0 assert defaults.idle_compact_check_interval_seconds == 60
def test_idle_scan_interval_uses_camel_case_config_key(self): def test_idle_scan_interval_uses_camel_case_config_key(self):
"""The JSON config should use the standard camelCase alias.""" """The JSON config should use the standard camelCase alias."""
@@ -226,8 +226,8 @@ class TestIdleScanThrottling:
assert loop.auto_compact.check_expired.call_count == 2 assert loop.auto_compact.check_expired.call_count == 2
def test_default_idle_scan_interval_checks_every_tick(self, tmp_path, monkeypatch): def test_zero_idle_scan_interval_checks_every_tick(self, tmp_path, monkeypatch):
"""The zero default should leave each idle tick eligible to scan.""" """An explicit zero should leave each idle tick eligible to scan."""
monkeypatch.setattr("nanobot.agent.loop.time.monotonic", lambda: 1_000.0) monkeypatch.setattr("nanobot.agent.loop.time.monotonic", lambda: 1_000.0)
loop = _make_loop(tmp_path) loop = _make_loop(tmp_path)
loop.auto_compact.check_expired = MagicMock() loop.auto_compact.check_expired = MagicMock()