Detach memory and update config

Removed the memory_subdir attribute from AgentConfig and related settings, transitioning to a project-based memory isolation approach. Updated the memory plugin configuration to default to an empty string for agent_memory_subdir. Enhanced the get_context_memory_subdir function to support project-specific memory directories. Removed the project-edit-memory component and adjusted the UI to reflect these changes. Added a new memory configuration file for better management of memory settings.

Alessandro committed Feb 21, 2026 at 10:41 UTC 505128c384c86f06809fb5fcde0e412f4cef1a94
14 files changed +54 -158
agent.py
-1
@@ -303,7 +303,6 @@ class AgentConfig:
303 browser_model: models.ModelConfig
304 mcp_servers: str
305 profile: str = ""
306 - memory_subdir: str = ""
306 knowledge_subdirs: list[str] = field(default_factory=lambda: ["default", "custom"])
307 browser_http_headers: dict[str, str] = field(
308 default_factory=dict
initialize.py
-1
@@ -78,7 +78,6 @@ def initialize_agent(override_settings: dict | None = None):
78 embeddings_model=embedding_llm,
79 browser_model=browser_llm,
80 profile=current_settings["agent_profile"],
81 - memory_subdir=current_settings["agent_memory_subdir"],
81 knowledge_subdirs=[current_settings["agent_knowledge_subdir"], "default"],
82 mcp_servers=current_settings["mcp_servers"],
83 browser_http_headers=current_settings["browser_http_headers"],
plugins/memory/config.default.json
+1 -1
@@ -13,5 +13,5 @@
13 "memory_memorize_enabled": true,
14 "memory_memorize_consolidation": true,
15 "memory_memorize_replace_threshold": 0.9,
16 - "agent_memory_subdir": "default"
16 + "agent_memory_subdir": ""
17 }
plugins/memory/config.json new
+18
@@ -0,0 +1,18 @@
1 +{
2 + "project_memory_isolation": true,
3 + "memory_recall_enabled": true,
4 + "memory_recall_delayed": false,
5 + "memory_recall_interval": 3,
6 + "memory_recall_history_len": 10000,
7 + "memory_recall_memories_max_search": 12,
8 + "memory_recall_solutions_max_search": 8,
9 + "memory_recall_memories_max_result": 5,
10 + "memory_recall_solutions_max_result": 3,
11 + "memory_recall_similarity_threshold": 0.7,
12 + "memory_recall_query_prep": false,
13 + "memory_recall_post_filter": false,
14 + "memory_memorize_enabled": true,
15 + "memory_memorize_consolidation": true,
16 + "memory_memorize_replace_threshold": 0.9,
17 + "agent_memory_subdir": "default"
18 +}
plugins/memory/helpers/memory.py
+11 -12
@@ -23,7 +23,7 @@ import os, json
23 import numpy as np
24
25 from python.helpers.print_style import PrintStyle
26 -from python.helpers import files, plugins
26 +from python.helpers import files, plugins, projects
27 from langchain_core.documents import Document
28 from . import knowledge_import
29 from python.helpers.log import Log, LogItem
@@ -520,17 +520,16 @@ def get_agent_memory_subdir(agent: Agent) -> str:
520
521
522 def get_context_memory_subdir(context: AgentContext) -> str:
523 - # if project is active, use project memory subdir
524 - from python.helpers.projects import (
525 - get_context_memory_subdir as get_project_memory_subdir,
526 - )
527 -
528 - memory_subdir = get_project_memory_subdir(context)
529 - if memory_subdir:
530 - return memory_subdir
531 -
532 - # no project, regular memory subdir
533 - return plugins.get_plugin_config("memory").get("agent_memory_subdir", "default")
523 + config = plugins.get_plugin_config("memory")
524 +
525 + # Check if project isolation is enabled and we are in a project
526 + if config.get("project_memory_isolation", True):
527 + project_name = projects.get_context_project_name(context)
528 + if project_name:
529 + return "projects/" + project_name
530 +
531 + # Fallback to configured subdir or default
532 + return config.get("agent_memory_subdir", "") or "default"
533
534
535 def get_existing_memory_subdirs() -> list[str]:
plugins/memory/webui/config.html
+17 -3
@@ -13,16 +13,30 @@
13 context awareness.
14 </div>
15
16 + <div class="field" x-show="$store.pluginSettings.projectName">
17 + <div class="field-label">
18 + <div class="field-title">Project Isolation</div>
19 + <div class="field-description">
20 + If enabled, this project uses its own memory folder (projects/NAME). If disabled, it shares the global memory (default or custom subdir).
21 + </div>
22 + </div>
23 + <div class="field-control">
24 + <label class="toggle">
25 + <input type="checkbox" x-model="$store.pluginSettings.settings.project_memory_isolation" />
26 + <span class="toggler"></span>
27 + </label>
28 + </div>
29 + </div>
30 +
31 <div class="field">
32 <div class="field-label">
33 <div class="field-title">Memory Subdirectory</div>
34 <div class="field-description">
20 - Subdirectory of /memory folder to use for agent memory storage. Used to separate memory
21 - storage between different instances.
35 + Subdirectory of /memory folder.
36 </div>
37 </div>
38 <div class="field-control">
25 - <input type="text" x-model="$store.pluginSettings.settings.agent_memory_subdir" />
39 + <input type="text" x-model="$store.pluginSettings.settings.agent_memory_subdir" placeholder="Auto (Project-isolated)" />
40 </div>
41 </div>
42
python/extensions/agent_init/_15_load_profile_settings.py
-1
@@ -38,7 +38,6 @@ class LoadProfileSettings(Extension):
38
39 for override_key, config_attr in (
40 ("agent_profile", "profile"),
41 - ("agent_memory_subdir", "memory_subdir"),
41 ("mcp_servers", "mcp_servers"),
42 ("browser_http_headers", "browser_http_headers"),
43 ):
python/helpers/projects.py
-15
@@ -34,9 +34,6 @@ class BasicProjectData(TypedDict):
34 instructions: str
35 color: str
36 git_url: str
37 - memory: Literal[
38 - "own", "global"
39 - ] # in the future we can add cutom and point to another existing folder
37 file_structure: FileStructureInjectionSettings
38
39 class GitStatusData(TypedDict, total=False):
@@ -158,7 +155,6 @@ def _normalizeBasicData(data: BasicProjectData) -> BasicProjectData:
155 "instructions": data.get("instructions", ""),
156 "color": data.get("color", ""),
157 "git_url": data.get("git_url", ""),
161 - "memory": data.get("memory", "own"),
158 "file_structure": data.get(
159 "file_structure",
160 _default_file_structure_settings(),
@@ -179,7 +175,6 @@ def _normalizeEditData(data: EditProjectData) -> EditProjectData:
175 "instruction_files_count": data.get("instruction_files_count", 0),
176 "knowledge_files_count": data.get("knowledge_files_count", 0),
177 "secrets": data.get("secrets", ""),
182 - "memory": data.get("memory", "own"),
178 "file_structure": data.get(
179 "file_structure",
180 _default_file_structure_settings(),
@@ -463,16 +458,6 @@ def save_project_secrets(name: str, secrets: str):
458 secrets_manager.save_secrets_with_merge(secrets)
459
460
466 -def get_context_memory_subdir(context: "AgentContext") -> str | None:
467 - # if a project is active and has memory isolation set, return the project memory subdir
468 - project_name = get_context_project_name(context)
469 - if project_name:
470 - project_data = load_basic_project_data(project_name)
471 - if project_data["memory"] == "own":
472 - return "projects/" + project_name
473 - return None # no memory override
474 -
475 -
461 def create_project_meta_folders(name: str):
462 # create instructions folder
463 files.create_dir(get_project_meta(name, PROJECT_INSTRUCTIONS_DIR))
python/helpers/settings.py
-31
@@ -92,7 +92,6 @@ class Settings(TypedDict):
92 browser_http_headers: dict[str, Any]
93
94 agent_profile: str
95 - agent_memory_subdir: str
95 agent_knowledge_subdir: str
96
97 workdir_path: str
@@ -103,21 +102,6 @@ class Settings(TypedDict):
102 workdir_max_lines: int
103 workdir_gitignore: str
104
106 - memory_recall_enabled: bool
107 - memory_recall_delayed: bool
108 - memory_recall_interval: int
109 - memory_recall_history_len: int
110 - memory_recall_memories_max_search: int
111 - memory_recall_solutions_max_search: int
112 - memory_recall_memories_max_result: int
113 - memory_recall_solutions_max_result: int
114 - memory_recall_similarity_threshold: float
115 - memory_recall_query_prep: bool
116 - memory_recall_post_filter: bool
117 - memory_memorize_enabled: bool
118 - memory_memorize_consolidation: bool
119 - memory_memorize_replace_threshold: float
120 -
105 api_keys: dict[str, str]
106
107 auth_login: str
@@ -547,26 +531,11 @@ def get_default_settings() -> Settings:
531 browser_model_rl_output=get_default_value("browser_model_rl_output", 0),
532 browser_model_kwargs=get_default_value("browser_model_kwargs", {}),
533 browser_http_headers=get_default_value("browser_http_headers", {}),
550 - memory_recall_enabled=get_default_value("memory_recall_enabled", True),
551 - memory_recall_delayed=get_default_value("memory_recall_delayed", False),
552 - memory_recall_interval=get_default_value("memory_recall_interval", 3),
553 - memory_recall_history_len=get_default_value("memory_recall_history_len", 10000),
554 - memory_recall_memories_max_search=get_default_value("memory_recall_memories_max_search", 12),
555 - memory_recall_solutions_max_search=get_default_value("memory_recall_solutions_max_search", 8),
556 - memory_recall_memories_max_result=get_default_value("memory_recall_memories_max_result", 5),
557 - memory_recall_solutions_max_result=get_default_value("memory_recall_solutions_max_result", 3),
558 - memory_recall_similarity_threshold=get_default_value("memory_recall_similarity_threshold", 0.7),
559 - memory_recall_query_prep=get_default_value("memory_recall_query_prep", False),
560 - memory_recall_post_filter=get_default_value("memory_recall_post_filter", False),
561 - memory_memorize_enabled=get_default_value("memory_memorize_enabled", True),
562 - memory_memorize_consolidation=get_default_value("memory_memorize_consolidation", True),
563 - memory_memorize_replace_threshold=get_default_value("memory_memorize_replace_threshold", 0.9),
534 api_keys={},
535 auth_login="",
536 auth_password="",
537 root_password="",
538 agent_profile=get_default_value("agent_profile", "agent0"),
569 - agent_memory_subdir=get_default_value("agent_memory_subdir", "default"),
539 agent_knowledge_subdir=get_default_value("agent_knowledge_subdir", "custom"),
540 workdir_path=get_default_value("workdir_path", files.get_abs_path_dockerized("usr/workdir")),
541 workdir_show=get_default_value("workdir_show", True),
webui/components/plugins/list/pluginListStore.js
+1
@@ -14,6 +14,7 @@ const model = {
14 activeTab: "custom",
15
16 async init() {
17 + this.loading = false;
18 await this.setTab('custom');
19 if (this.plugins.length === 0) {
20 await this.setTab('builtin');
webui/components/plugins/plugin-settings-store.js
+5
@@ -301,6 +301,11 @@ const model = {
301 this.loadedProjectName = "";
302 this.loadedAgentProfile = "";
303 this.error = null;
304 + this.isLoading = false;
305 + this.isSaving = false;
306 + this.isListingConfigs = false;
307 + this.configsError = null;
308 + this.configs = [];
309 },
310
311 // Reactive URL for the plugin's settings component (used with x-html injection)
webui/components/projects/project-edit-memory.html deleted
-81
@@ -1,81 +0,0 @@
1 -<html>
2 -
3 -<head>
4 - <title>Create a new project</title>
5 - <script type="module">
6 - import { store } from "/components/projects/projects-store.js";
7 - </script>
8 -</head>
9 -
10 -<body>
11 - <div x-data>
12 - <template x-if="$store.projects && $store.projects.selectedProject">
13 - <div>
14 -
15 - <div class="projects-setting-row" style="padding: 1rem 0;">
16 - <div class="projects-setting-text">
17 - <label class="projects-form-label">Project-specific memory</label>
18 - <span class="projects-form-description">When turned on, the agent's memory while working on this
19 - project will be isolated from the global memory. The memory will be stored in <strong><span
20 - x-text="$store.projects.getSelectedAbsPath('.a0proj','memory')"></span></strong>.
21 - <br>
22 - Project-specific knowledge files can be used only when project-specific memory is turned on.</span>
23 - </div>
24 - <div class="projects-setting-control">
25 - <label class="toggle">
26 - <input type="checkbox" x-model="$store.projects.selectedProject._ownMemory">
27 - <span class="toggler"></span>
28 - </label>
29 - </div>
30 - </div>
31 -
32 - <div class="projects-form-group" x-show="$store.projects.selectedProject._ownMemory">
33 - <label class="projects-form-label">Knowledge files</label>
34 - <div class="projects-input-with-button-wrapper">
35 - <span class="projects-form-description">Additional knowledge files in <strong><span
36 - x-text="$store.projects.getSelectedAbsPath('.a0proj','knowledge')"></span></strong>.
37 - Knowledge files are imported into memory and are used when relevant based on the context of the conversation.</span>
38 - <span class="projects-form-description">Currently there are <strong><span
39 - x-text="$store.projects.selectedProject.knowledge_files_count || 0"></span></strong>
40 - knowledge files.</span>
41 - <button class="button icon-button" x-on:click="$store.projects.browseKnowledgeFiles()">
42 - <span class="icon material-symbols-outlined">folder</span>
43 - <span>Browse</span>
44 - </button>
45 - </div>
46 - </div>
47 -
48 - <style>
49 - .projects-setting-row {
50 - display: flex;
51 - align-items: center;
52 - justify-content: space-between;
53 - gap: 2rem;
54 - }
55 -
56 - .projects-setting-text {
57 - flex-grow: 1;
58 - flex-shrink: 1;
59 - min-width: 0;
60 - }
61 -
62 - .projects-setting-control {
63 - flex-grow: 0;
64 - flex-shrink: 0;
65 - }
66 -
67 - .projects-form-label {
68 - display: block;
69 - }
70 - </style>
71 -
72 - </div>
73 -
74 -
75 - </template>
76 - </div>
77 -</body>
78 -<style>
79 -</style>
80 -
81 -</html>
\ No newline at end of file
webui/components/projects/project-edit.html
-8
@@ -42,14 +42,6 @@
42 </x-component>
43 </div>
44
45 - <div class="project-detail">
46 - <div class="project-detail-header">
47 - <span class="projects-project-card-title">Memory</span>
48 - </div>
49 - <x-component path="projects/project-edit-memory.html">
50 - </x-component>
51 - </div>
52 -
45 <div class="project-detail">
46 <div class="project-detail-header">
47 <span class="projects-project-card-title">File structure</span>
webui/components/projects/projects-store.js
+1 -4
@@ -297,7 +297,7 @@ const model = {
297 const response = await api.callJsonApi("projects", {
298 action: "delete",
299 name: name,
300 - });
300 + });
301 if (response.ok) {
302 notifications.toastFrontendSuccess(
303 "Project deleted successfully",
@@ -350,7 +350,6 @@ const model = {
350 // prepare data
351 const data = {
352 ...this.selectedProject,
353 - memory: this.selectedProject._ownMemory ? "own" : "global",
353 };
354 // remove internal fields
355 for (const kvp of Object.entries(data))
@@ -402,7 +401,6 @@ const model = {
401 _meta: {
402 creating: true,
403 },
405 - _ownMemory: true,
404 _cloning: false,
405 name: ``,
406 title: `Project #${this.projectList.length + 1}`,
@@ -425,7 +423,6 @@ const model = {
423 creating: false,
424 },
425 ...projectData,
428 - _ownMemory: projectData.memory == "own",
426 };
427 },
428