diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 28b6131b5..96f7af1ee 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -157,6 +157,7 @@ class AgentLoop: channels_config: ChannelsConfig | None = None, timezone: str | None = None, session_ttl_minutes: int = 0, + consolidation_ratio: float = 0.5, hooks: list[AgentHook] | None = None, unified_session: bool = False, disabled_skills: list[str] | None = None, @@ -236,6 +237,7 @@ class AgentLoop: build_messages=self.context.build_messages, get_tool_definitions=self.tools.get_definitions, max_completion_tokens=provider.generation.max_tokens, + consolidation_ratio=consolidation_ratio, ) self.auto_compact = AutoCompact( sessions=self.sessions, diff --git a/nanobot/agent/memory.py b/nanobot/agent/memory.py index 5f235e3aa..138542700 100644 --- a/nanobot/agent/memory.py +++ b/nanobot/agent/memory.py @@ -361,6 +361,7 @@ class Consolidator: build_messages: Callable[..., list[dict[str, Any]]], get_tool_definitions: Callable[[], list[dict[str, Any]]], max_completion_tokens: int = 4096, + consolidation_ratio: float = 0.5, ): self.store = store self.provider = provider @@ -368,6 +369,7 @@ class Consolidator: self.sessions = sessions self.context_window_tokens = context_window_tokens self.max_completion_tokens = max_completion_tokens + self.consolidation_ratio = consolidation_ratio self._build_messages = build_messages self._get_tool_definitions = get_tool_definitions self._locks: weakref.WeakValueDictionary[str, asyncio.Lock] = ( @@ -479,7 +481,7 @@ class Consolidator: lock = self.get_lock(session.key) async with lock: budget = self.context_window_tokens - self.max_completion_tokens - self._SAFETY_BUFFER - target = budget // 2 + target = int(budget * self.consolidation_ratio) try: estimated, source = self.estimate_session_prompt_tokens(session) except Exception: diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index 5f043050c..cbf9da434 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -593,6 +593,7 @@ def serve( unified_session=runtime_config.agents.defaults.unified_session, disabled_skills=runtime_config.agents.defaults.disabled_skills, session_ttl_minutes=runtime_config.agents.defaults.session_ttl_minutes, + consolidation_ratio=runtime_config.agents.defaults.consolidation_ratio, tools_config=runtime_config.tools, ) @@ -688,6 +689,7 @@ def gateway( unified_session=config.agents.defaults.unified_session, disabled_skills=config.agents.defaults.disabled_skills, session_ttl_minutes=config.agents.defaults.session_ttl_minutes, + consolidation_ratio=config.agents.defaults.consolidation_ratio, tools_config=config.tools, ) @@ -967,6 +969,7 @@ def agent( unified_session=config.agents.defaults.unified_session, disabled_skills=config.agents.defaults.disabled_skills, session_ttl_minutes=config.agents.defaults.session_ttl_minutes, + consolidation_ratio=config.agents.defaults.consolidation_ratio, tools_config=config.tools, ) restart_notice = consume_restart_notice_from_env() diff --git a/nanobot/config/schema.py b/nanobot/config/schema.py index 66759cb31..10282a9c4 100644 --- a/nanobot/config/schema.py +++ b/nanobot/config/schema.py @@ -89,6 +89,13 @@ class AgentDefaults(Base): validation_alias=AliasChoices("idleCompactAfterMinutes", "sessionTtlMinutes"), serialization_alias="idleCompactAfterMinutes", ) # Auto-compact idle threshold in minutes (0 = disabled) + consolidation_ratio: float = Field( + default=0.5, + ge=0.1, + le=0.95, + validation_alias=AliasChoices("consolidationRatio"), + serialization_alias="consolidationRatio", + ) # Consolidation target ratio (0.5 = 50% of budget retained after compression) dream: DreamConfig = Field(default_factory=DreamConfig) diff --git a/nanobot/nanobot.py b/nanobot/nanobot.py index 96102e3d2..f9aeae84e 100644 --- a/nanobot/nanobot.py +++ b/nanobot/nanobot.py @@ -84,6 +84,7 @@ class Nanobot: unified_session=defaults.unified_session, disabled_skills=defaults.disabled_skills, session_ttl_minutes=defaults.session_ttl_minutes, + consolidation_ratio=defaults.consolidation_ratio, tools_config=config.tools, ) return cls(loop)