fix(agent): bound per-session file state

This commit is contained in:
yu-xin-c
2026-08-15 23:49:20 +08:00
committed by Xubin Ren
parent ecef2b055d
commit 42afebb0cb
8 changed files with 73 additions and 7 deletions
+7 -1
View File
@@ -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:
+16 -6
View File
@@ -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()