fix(agent): throttle idle scans by default
This commit is contained in:
@@ -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.
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user