fix(agent): bound per-session file state
This commit is contained in:
@@ -76,6 +76,7 @@ from nanobot.session.goal_state import (
|
||||
from nanobot.session.history_visibility import HIDDEN_HISTORY_META
|
||||
from nanobot.session.keys import UNIFIED_SESSION_KEY, remember_last_channel
|
||||
from nanobot.session.manager import (
|
||||
SESSION_CACHE_MAX_SIZE,
|
||||
Session,
|
||||
SessionManager,
|
||||
replay_max_messages_for_context,
|
||||
@@ -380,7 +381,7 @@ class AgentLoop:
|
||||
self.tools = tool_registry if tool_registry is not None else ToolRegistry()
|
||||
# One file-read/write tracker per logical session. The tool registry is
|
||||
# shared by this loop, so tools resolve the active state via contextvars.
|
||||
self._file_state_store = FileStateStore()
|
||||
self._file_state_store = FileStateStore(max_sessions=SESSION_CACHE_MAX_SIZE)
|
||||
self._exec_session_manager = ExecSessionManager()
|
||||
self.runner = AgentRunner()
|
||||
self.subagents = SubagentManager(
|
||||
@@ -818,8 +819,13 @@ class AgentLoop:
|
||||
self.sessions.invalidate(key)
|
||||
await self._cancel_active_tasks(key)
|
||||
finally:
|
||||
self.discard_session_file_state(key)
|
||||
self._discarding_sessions.discard(key)
|
||||
|
||||
def discard_session_file_state(self, key: str) -> None:
|
||||
"""Forget ephemeral file-read state for a reset or removed session."""
|
||||
self._file_state_store.discard(key)
|
||||
|
||||
def _effective_session_key(self, msg: InboundMessage) -> str:
|
||||
"""Return the session key used for task routing and mid-turn injections."""
|
||||
if self._unified_session and not msg.session_key_override:
|
||||
|
||||
@@ -4,6 +4,7 @@ from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
from contextvars import ContextVar, Token
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
@@ -135,21 +136,30 @@ class FileStates:
|
||||
|
||||
|
||||
class FileStateStore:
|
||||
"""Lookup table for per-session file read/write state."""
|
||||
"""Bounded lookup table for per-session file read/write state."""
|
||||
|
||||
__slots__ = ("_states_by_key",)
|
||||
__slots__ = ("_max_sessions", "_states_by_key")
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._states_by_key: dict[str, FileStates] = {}
|
||||
def __init__(self, *, max_sessions: int = 128) -> None:
|
||||
if max_sessions <= 0:
|
||||
raise ValueError("max_sessions must be positive")
|
||||
self._max_sessions = max_sessions
|
||||
self._states_by_key: OrderedDict[str, FileStates] = OrderedDict()
|
||||
|
||||
def for_session(self, session_key: str | None) -> FileStates:
|
||||
key = session_key or "__default__"
|
||||
states = self._states_by_key.get(key)
|
||||
states = self._states_by_key.pop(key, None)
|
||||
if states is None:
|
||||
states = FileStates()
|
||||
self._states_by_key[key] = states
|
||||
self._states_by_key[key] = states
|
||||
while len(self._states_by_key) > self._max_sessions:
|
||||
self._states_by_key.popitem(last=False)
|
||||
return states
|
||||
|
||||
def discard(self, session_key: str | None) -> None:
|
||||
"""Forget file state when a session is reset or removed."""
|
||||
self._states_by_key.pop(session_key or "__default__", None)
|
||||
|
||||
def clear(self) -> None:
|
||||
self._states_by_key.clear()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user