feat(agent): make idle compaction scan interval configurable

Before this change, idle compaction is triggered every 1 second
if the incoming message stream is idle.
When triggered, it enumerates all session files, loads and parses them,
and then checks their expiration.

This becomes too CPU-intensive, especially on low-power devices like Raspberry Pi.
It's unlikely that you actually need to compact every second over the long time.

This change adds a configurable throttling for idle-compaction.

Default behavior is unchanged.
This commit is contained in:
Andrew Khmylov
2026-07-27 02:15:48 +08:00
committed by Xubin Ren
parent 4e2640f2d2
commit 7aab7e8830
4 changed files with 78 additions and 8 deletions
+17 -5
View File
@@ -297,6 +297,7 @@ class AgentLoop:
runtime_model_publisher: Callable[[str, str | None], None] | None = None,
restart_mode: str = "auto",
local_trigger_store: Any | None = None,
idle_compact_check_interval_seconds: int = 0,
):
from nanobot.config.schema import ToolsConfig
@@ -445,6 +446,8 @@ class AgentLoop:
consolidator=self.consolidator,
session_ttl_minutes=session_ttl_minutes,
)
self._idle_compact_check_interval_s = idle_compact_check_interval_seconds
self._next_idle_compact_check_at = time.monotonic()
if model_preset:
self.set_model_preset(model_preset, publish_update=False)
self._register_default_tools(provider_snapshot_loader=provider_snapshot_loader)
@@ -500,6 +503,7 @@ class AgentLoop:
unified_session=defaults.unified_session,
disabled_skills=defaults.disabled_skills,
session_ttl_minutes=defaults.session_ttl_minutes,
idle_compact_check_interval_seconds=defaults.idle_compact_check_interval_seconds,
consolidation_ratio=defaults.consolidation_ratio,
tools_config=config.tools,
model_presets=preset_helpers.configured_model_presets(config),
@@ -1081,6 +1085,18 @@ class AgentLoop:
logger.error("LLM returned error: {}", (result.final_content or "")[:200])
return result.final_content, result.tools_used, result.messages, result.stop_reason, result.had_injections
def _check_expired_sessions_if_due(self) -> None:
"""Scan idle sessions no more often than the configured interval."""
now = time.monotonic()
if now < self._next_idle_compact_check_at:
return
self._next_idle_compact_check_at = now + self._idle_compact_check_interval_s
self.auto_compact.check_expired(
self._schedule_background,
self.runtime_for_session,
active_session_keys=self._pending_queues.keys(),
)
async def run(self) -> None:
"""Run the agent loop, dispatching messages as tasks to stay responsive to /stop."""
self._running = True
@@ -1092,11 +1108,7 @@ class AgentLoop:
try:
msg = await asyncio.wait_for(self.bus.consume_inbound(), timeout=1.0)
except asyncio.TimeoutError:
self.auto_compact.check_expired(
self._schedule_background,
self.runtime_for_session,
active_session_keys=self._pending_queues.keys(),
)
self._check_expired_sessions_if_due()
continue
except asyncio.CancelledError:
# Preserve real task cancellation so shutdown can complete cleanly.