From b898677b1c9d24d3a949e3f5351321cd9b6355a9 Mon Sep 17 00:00:00 2001 From: Bokai Yang Date: Mon, 23 Mar 2026 13:12:30 -0400 Subject: [PATCH] feat: add ignored_memory_patterns config option for regex-based memory exclusion (#1145) Co-authored-by: Bokai Yang <22804145+YouJianFengXue@users.noreply.github.com> Co-authored-by: Michael Panchenko <35432522+MischaPanch@users.noreply.github.com> --- CHANGELOG.md | 7 ++++ docs/02-usage/045_memories.md | 16 ++++++++ src/serena/config/serena_config.py | 2 + src/serena/project.py | 39 ++++++++++++++++++- src/serena/resources/project.template.yml | 8 ++++ .../resources/serena_config.template.yml | 9 +++++ 6 files changed, 79 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41925853..701cfce7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ Status of the `main` branch. Changes prior to the next official version change will appear here. +* Memories: + * Add `ignored_memory_patterns` configuration option (regex-based) to completely exclude + matching memories from listing, reading, and writing. Configurable at both global + (`serena_config.yml`) and project (`project.yml`) level, merged additively. + Useful for projects with large numbers of archived memory files that inflate + `activate_project` output. + * New language support: * Add Solidity language server support (`Language.SOLIDITY`) using the Nomic Foundation `@nomicfoundation/solidity-language-server`. Automatically diff --git a/docs/02-usage/045_memories.md b/docs/02-usage/045_memories.md index 2a896f08..7b364aa9 100644 --- a/docs/02-usage/045_memories.md +++ b/docs/02-usage/045_memories.md @@ -39,6 +39,22 @@ Since global memories are not versioned alongside your project files, it can be helpful to track global memories with git (i.e. to make `~/.serena/memories/` a git repository) in order to have a history of changes and the possibility to revert them if needed. +### Ignoring Memories + +Projects that accumulate large numbers of archived memory files can use `ignored_memory_patterns` +to exclude them from `list_memories` and `activate_project` output. Add regex patterns to the +global or project-level [configuration](050_configuration): + +```yaml +ignored_memory_patterns: ["_archive/.*", "_episodes/.*"] +``` + +Ignored memories are completely excluded — they cannot be accessed via `read_memory`, `write_memory`, +or any other memory tool. To read an ignored memory file, use the `read_file` tool on the raw file path +(e.g., `.serena/memories/_archive/2026-03/some-topic.md`). + +Like `read_only_memory_patterns`, patterns from the global and project-level configurations are merged additively. + ### Manually Editing Memories You may edit memories directly in the file system, using your preferred text editor or IDE. diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index be026c8a..dcfe6f90 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -222,6 +222,7 @@ class SharedConfig(ModeSelectionDefinition, ToolInclusionDefinition, ToStringMix language_backend: LanguageBackend | None = None line_ending: LineEnding | None = None read_only_memory_patterns: list[str] = field(default_factory=list) + ignored_memory_patterns: list[str] = field(default_factory=list) ls_specific_settings: dict = field(default_factory=dict) """Advanced configuration option allowing to configure language server implementation specific options, see SolidLSPSettings for more info.""" @@ -453,6 +454,7 @@ class ProjectConfig(SharedConfig): included_optional_tools=data["included_optional_tools"], read_only=data["read_only"], read_only_memory_patterns=data.get("read_only_memory_patterns", []), + ignored_memory_patterns=data.get("ignored_memory_patterns", []), ignore_all_files_in_gitignore=data["ignore_all_files_in_gitignore"], initial_prompt=data["initial_prompt"], encoding=data["encoding"], diff --git a/src/serena/project.py b/src/serena/project.py index d33e157f..0f37fe26 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -35,10 +35,18 @@ class MemoriesManager: GLOBAL_TOPIC = "global" _global_memory_dir = SerenaPaths().global_memories_path - def __init__(self, serena_data_folder: str | Path | None, read_only_memory_patterns: Sequence[str] = ()): + def __init__( + self, + serena_data_folder: str | Path | None, + read_only_memory_patterns: Sequence[str] = (), + ignored_memory_patterns: Sequence[str] = (), + ): """ :param serena_data_folder: the absolute path to the project's .serena data folder :param read_only_memory_patterns: whether to allow writing global memories in tool execution contexts + :param ignored_memory_patterns: regex patterns for memories to completely exclude from listing, reading, and writing. + Matching memories will not appear in list_memories or activate_project output and cannot be accessed + via read_memory or write_memory. Use read_file on the raw path to access ignored memory files. """ self._project_memory_dir: Path | None = None if serena_data_folder is not None: @@ -46,6 +54,7 @@ class MemoriesManager: self._project_memory_dir.mkdir(parents=True, exist_ok=True) self._encoding = SERENA_FILE_ENCODING self._read_only_memory_patterns = [re.compile(pattern) for pattern in set(read_only_memory_patterns)] + self._ignored_memory_patterns = [re.compile(pattern) for pattern in set(ignored_memory_patterns)] def _is_read_only_memory(self, name: str) -> bool: for pattern in self._read_only_memory_patterns: @@ -53,6 +62,19 @@ class MemoriesManager: return True return False + def _is_ignored_memory(self, name: str) -> bool: + for pattern in self._ignored_memory_patterns: + if pattern.fullmatch(name): + return True + return False + + def _check_not_ignored(self, name: str) -> None: + if self._is_ignored_memory(name): + raise ValueError( + f"Memory '{name}' matches an ignored_memory_patterns pattern and cannot be accessed. " + f"Use the read_file tool on the raw file path instead." + ) + def _is_global(self, name: str) -> bool: return name == self.GLOBAL_TOPIC or name.startswith(self.GLOBAL_TOPIC + "/") @@ -95,6 +117,7 @@ class MemoriesManager: raise PermissionError(f"Attempted to write to read_only memory: '{name}')") def load_memory(self, name: str) -> str: + self._check_not_ignored(name) memory_file_path = self.get_memory_file_path(name) if not memory_file_path.exists(): return f"Memory file {name} not found, consider creating it with the `write_memory` tool if you need it." @@ -102,6 +125,7 @@ class MemoriesManager: return f.read() def save_memory(self, name: str, content: str, is_tool_context: bool) -> str: + self._check_not_ignored(name) self._check_write_access(name, is_tool_context) memory_file_path = self.get_memory_file_path(name) with open(memory_file_path, "w", encoding=self._encoding) as f: @@ -144,6 +168,8 @@ class MemoriesManager: for md_file in search_dir.rglob("*.md"): rel = str(md_file.relative_to(base_dir).with_suffix("")).replace(os.sep, "/") memory_name = prefix + rel + if self._is_ignored_memory(memory_name): + continue result.add(memory_name, is_read_only=self._is_read_only_memory(memory_name)) return result @@ -181,6 +207,7 @@ class MemoriesManager: return memories def delete_memory(self, name: str, is_tool_context: bool) -> str: + self._check_not_ignored(name) self._check_write_access(name, is_tool_context) memory_file_path = self.get_memory_file_path(name) if not memory_file_path.exists(): @@ -193,6 +220,8 @@ class MemoriesManager: Rename or move a memory file. Moving between global and project scope (e.g. "global/foo" -> "bar") is supported. """ + self._check_not_ignored(old_name) + self._check_not_ignored(new_name) self._check_write_access(new_name, is_tool_context) old_path = self.get_memory_file_path(old_name) @@ -220,6 +249,7 @@ class MemoriesManager: :param mode: "literal" or "regex" :param allow_multiple_occurrences: """ + self._check_not_ignored(name) self._check_write_access(name, is_tool_context) memory_file_path = self.get_memory_file_path(name) if not memory_file_path.exists(): @@ -250,7 +280,12 @@ class Project(ToStringMixin): log.info("Serena project data folder: %s", self._serena_data_folder) read_only_memory_patterns = serena_config.read_only_memory_patterns + project_config.read_only_memory_patterns - self.memories_manager = MemoriesManager(self._serena_data_folder, read_only_memory_patterns=read_only_memory_patterns) + ignored_memory_patterns = serena_config.ignored_memory_patterns + project_config.ignored_memory_patterns + self.memories_manager = MemoriesManager( + self._serena_data_folder, + read_only_memory_patterns=read_only_memory_patterns, + ignored_memory_patterns=ignored_memory_patterns, + ) # resolve line ending (project -> global) self.line_ending = project_config.line_ending or serena_config.line_ending diff --git a/src/serena/resources/project.template.yml b/src/serena/resources/project.template.yml index c6f74300..f6ba8cb1 100644 --- a/src/serena/resources/project.template.yml +++ b/src/serena/resources/project.template.yml @@ -141,3 +141,11 @@ symbol_info_budget: # list of regex patterns which, when matched, mark a memory entry as read‑only. # Extends the list from the global configuration, merging the two lists. read_only_memory_patterns: [] + +# list of regex patterns for memories to completely ignore. +# Matching memories will not appear in list_memories or activate_project output +# and cannot be accessed via read_memory or write_memory. +# To access ignored memory files, use the read_file tool on the raw file path. +# Extends the list from the global configuration, merging the two lists. +# Example: ["_archive/.*", "_episodes/.*"] +ignored_memory_patterns: [] diff --git a/src/serena/resources/serena_config.template.yml b/src/serena/resources/serena_config.template.yml index 0c4a6d38..7ec556db 100644 --- a/src/serena/resources/serena_config.template.yml +++ b/src/serena/resources/serena_config.template.yml @@ -79,6 +79,15 @@ ignored_paths: [] # You can extend the list on a per-project basis in the project.yml configuration file. read_only_memory_patterns: [] +# list of regex patterns for memories to completely ignore. +# Matching memories will not appear in list_memories or activate_project output +# and cannot be accessed via read_memory or write_memory. +# To access ignored memory files, use the read_file tool on the raw file path. +# This is useful for projects with large numbers of archived memory files. +# You can extend the list on a per-project basis in the project.yml configuration file. +# Example: ["_archive/.*", "_episodes/.*"] +ignored_memory_patterns: [] + # timeout, in seconds, after which tool executions are terminated tool_timeout: 240