Convert agent and plugin metadata/configs to YAML
Replace JSON-based agent and plugin metadata/config files with YAML equivalents and update code paths to read/write YAML. Added python/helpers/yaml for YAML load/dump and files.read_file_yaml helper; subagents, plugins, and files helpers now prefer agent.yaml/plugin.yaml and default_config.yaml. Migration now includes convert_agents_json_yaml and startup_migration is invoked from initialize to perform conversions. Plugin toggle/config handling was refactored (META_FILE_NAME/CONFIG_DEFAULT_FILE_NAME changes, new determined_toggle_from_paths helper) and memory plugin defaults and extensions were updated to use per-agent plugin config lookups with guards for missing configs.
frdel committed
Feb 23, 2026 at 19:36 UTC
82aef08ae270a208e08c060b7c37016a6c1ed875
32 files changed
+150
-95
agents/agent0/_context.md
deleted
-4
@@ -1,4 +0,0 @@
1
-# Agent 0
2
-- main agent of the system
3
-- communicates to user and delegates to subordinates
4
-- general purpose assistant, communication skills, formatted output
\ No newline at end of file
agents/agent0/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Agent 0",
3
- "description": "Main agent of the system communicating directly with the user.",
4
- "context": ""
5
-}
agents/agent0/agent.yaml
new
+3
@@ -0,0 +1,3 @@
1
+title: Agent 0
2
+description: Main agent of the system communicating directly with the user.
3
+context: ''
agents/default/_context.md
deleted
-3
@@ -1,3 +0,0 @@
1
-# Default prompts
2
-- default prompt file templates
3
-- should be inherited and overriden by specialized prompt profiles
\ No newline at end of file
agents/default/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Default",
3
- "description": "Default prompt file templates. Should be inherited and overriden by specialized prompt profiles.",
4
- "context": ""
5
-}
agents/default/agent.yaml
new
+4
@@ -0,0 +1,4 @@
1
+title: Default
2
+description: Default prompt file templates. Should be inherited and overriden by specialized
3
+ prompt profiles.
4
+context: ''
agents/developer/_context.md
deleted
-2
@@ -1,2 +0,0 @@
1
-# Developer
2
-- agent specialized in complex software development
\ No newline at end of file
agents/developer/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Developer",
3
- "description": "Agent specialized in complex software development.",
4
- "context": "Use this agent for software development tasks, including writing code, debugging, refactoring, and architectural design."
5
-}
agents/developer/agent.yaml
new
+4
@@ -0,0 +1,4 @@
1
+title: Developer
2
+description: Agent specialized in complex software development.
3
+context: Use this agent for software development tasks, including writing code, debugging,
4
+ refactoring, and architectural design.
agents/hacker/_context.md
deleted
-2
@@ -1,2 +0,0 @@
1
-# Hacker
2
-- agent specialized in cyber security and penetration testing
\ No newline at end of file
agents/hacker/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Hacker",
3
- "description": "Agent specialized in cyber security and penetration testing.",
4
- "context": "Use this agent for cybersecurity tasks such as penetration testing, vulnerability analysis, and security auditing."
5
-}
agents/hacker/agent.yaml
new
+4
@@ -0,0 +1,4 @@
1
+title: Hacker
2
+description: Agent specialized in cyber security and penetration testing.
3
+context: Use this agent for cybersecurity tasks such as penetration testing, vulnerability
4
+ analysis, and security auditing.
agents/researcher/_context.md
deleted
-2
@@ -1,2 +0,0 @@
1
-# Researcher
2
-- agent specialized in research, data analysis and reporting
\ No newline at end of file
agents/researcher/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Researcher",
3
- "description": "Agent specialized in research, data analysis and reporting.",
4
- "context": "Use this agent for information gathering, data analysis, topic research, and generating comprehensive reports."
5
-}
agents/researcher/agent.yaml
new
+4
@@ -0,0 +1,4 @@
1
+title: Researcher
2
+description: Agent specialized in research, data analysis and reporting.
3
+context: Use this agent for information gathering, data analysis, topic research,
4
+ and generating comprehensive reports.
initialize.py
+1
-1
@@ -142,7 +142,7 @@ def initialize_preload():
142
def initialize_migration():
143
from python.helpers import migration, dotenv
144
# run migration
145
- migration.migrate_user_data()
145
+ migration.startup_migration()
146
# reload .env as it might have been moved
147
dotenv.load_dotenv()
148
# reload settings to ensure new paths are picked up
plugins/example_agent/agents/plugin_example/agent.json
deleted
-5
@@ -1,5 +0,0 @@
1
-{
2
- "title": "Plugin Example Agent",
3
- "description": "This is an example agent profile loaded from the 'example_agent' plugin.",
4
- "context": "You are an example agent provided by the 'example_agent' plugin. Your purpose is to demonstrate how plugins can distribute agent profiles."
5
-}
plugins/example_agent/agents/plugin_example/agent.yaml
new
+4
@@ -0,0 +1,4 @@
1
+title: Plugin Example Agent
2
+description: This is an example agent profile loaded from the 'example_agent' plugin.
3
+context: You are an example agent provided by the 'example_agent' plugin. Your purpose
4
+ is to demonstrate how plugins can distribute agent profiles.
plugins/example_agent/plugin.json
deleted
-9
@@ -1,9 +0,0 @@
1
-{
2
- "title": "Example Agent",
3
- "description": "Example agent plugin demonstrating the Agent Zero plugin system.",
4
- "version": "1.0.0",
5
- "settings_sections": [],
6
- "per_project_config": true,
7
- "per_agent_config": false,
8
- "always_enabled": true
9
-}
plugins/example_agent/plugin.yaml
new
+6
@@ -0,0 +1,6 @@
1
+name: Example Agent
2
+description: Example agent plugin demonstrating the Agent Zero plugin system.
3
+version: 1.0.0
4
+settings_sections: []
5
+per_project_config: true
6
+per_agent_config: false
\ No newline at end of file
plugins/memory/config.default.json
deleted
-18
@@ -1,18 +0,0 @@
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/default_config.yaml
new
+16
@@ -0,0 +1,16 @@
1
+project_memory_isolation: true
2
+memory_recall_enabled: true
3
+memory_recall_delayed: false
4
+memory_recall_interval: 3
5
+memory_recall_history_len: 10000
6
+memory_recall_memories_max_search: 12
7
+memory_recall_solutions_max_search: 8
8
+memory_recall_memories_max_result: 5
9
+memory_recall_solutions_max_result: 3
10
+memory_recall_similarity_threshold: 0.7
11
+memory_recall_query_prep: false
12
+memory_recall_post_filter: false
13
+memory_memorize_enabled: true
14
+memory_memorize_consolidation: true
15
+memory_memorize_replace_threshold: 0.9
16
+agent_memory_subdir: default
\ No newline at end of file
plugins/memory/extensions/python/message_loop_prompts_after/_50_recall_memories.py
+7
-3
@@ -25,11 +25,13 @@ class RecallMemories(Extension):
25
26
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
27
28
- set = plugins.get_plugin_config("memory")
28
+ set = plugins.get_plugin_config("memory", self.agent)
29
+ if not set:
30
+ return None
31
32
# turned off in settings?
33
if not set["memory_recall_enabled"]:
32
- return
34
+ return None
35
36
# every X iterations (or the first one) recall memories
37
if loop_data.iteration % set["memory_recall_interval"] == 0:
@@ -63,7 +65,9 @@ class RecallMemories(Extension):
65
del extras["solutions"]
66
67
66
- set = plugins.get_plugin_config("memory")
68
+ set = plugins.get_plugin_config("memory", self.agent)
69
+ if not set:
70
+ return None
71
# try:
72
73
# get system message and chat history for util llm
plugins/memory/extensions/python/message_loop_prompts_after/_91_recall_wait.py
+3
-1
@@ -6,7 +6,9 @@ from python.helpers import plugins
6
class RecallWait(Extension):
7
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
8
9
- set = plugins.get_plugin_config("memory")
9
+ set = plugins.get_plugin_config("memory", self.agent)
10
+ if not set:
11
+ return None
12
13
task = self.agent.get_data(DATA_NAME_TASK_MEMORIES)
14
iter = self.agent.get_data(DATA_NAME_ITER_MEMORIES) or 0
plugins/memory/extensions/python/monologue_end/_50_memorize_fragments.py
+6
-2
@@ -16,7 +16,9 @@ class MemorizeMemories(Extension):
16
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
17
# try:
18
19
- set = plugins.get_plugin_config("memory")
19
+ set = plugins.get_plugin_config("memory", self.agent)
20
+ if not set:
21
+ return None
22
23
if not set["memory_memorize_enabled"]:
24
return
@@ -36,7 +38,9 @@ class MemorizeMemories(Extension):
38
async def memorize(self, loop_data: LoopData, log_item: LogItem, **kwargs):
39
40
try:
39
- set = plugins.get_plugin_config("memory")
41
+ set = plugins.get_plugin_config("memory", self.agent)
42
+ if not set:
43
+ return None
44
45
db = await Memory.get(self.agent)
46
plugins/memory/extensions/python/monologue_end/_51_memorize_solutions.py
+7
-2
@@ -15,7 +15,9 @@ class MemorizeSolutions(Extension):
15
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
16
# try:
17
18
- set = plugins.get_plugin_config("memory")
18
+ set = plugins.get_plugin_config("memory", self.agent)
19
+ if not set:
20
+ return None
21
22
if not set["memory_memorize_enabled"]:
23
return
@@ -34,7 +36,10 @@ class MemorizeSolutions(Extension):
36
37
async def memorize(self, loop_data: LoopData, log_item: LogItem, **kwargs):
38
try:
37
- set = plugins.get_plugin_config("memory")
39
+ set = plugins.get_plugin_config("memory", self.agent)
40
+ if not set:
41
+ return None
42
+
43
44
db = await Memory.get(self.agent)
45
plugins/memory/plugin.json
deleted
-8
@@ -1,8 +0,0 @@
1
-{
2
- "title": "Memory",
3
- "description": "Provides persistent memory capabilities to Agent Zero agents.",
4
- "version": "1.0.0",
5
- "settings_sections": ["agent"],
6
- "per_project_config": true,
7
- "per_agent_config": false
8
-}
plugins/memory/plugin.yaml
new
+7
@@ -0,0 +1,7 @@
1
+name: Memory
2
+description: Provides persistent memory capabilities to Agent Zero agents.
3
+version: 1.0.0
4
+settings_sections:
5
+ - agent
6
+per_project_config: true
7
+per_agent_config: false
\ No newline at end of file
python/helpers/files.py
+7
-4
@@ -3,19 +3,16 @@ from fnmatch import fnmatch
3
import json
4
from ntpath import isabs
5
import os
6
-import sys
6
import re
7
import base64
8
import shutil
9
import tempfile
10
from typing import Any, Literal
11
import zipfile
13
-import importlib
14
-import importlib.util
15
-import inspect
12
import glob
13
import mimetypes
14
from simpleeval import simple_eval
15
+from python.helpers import yaml
16
17
AGENTS_DIR = "agents"
18
PLUGINS_DIR = "plugins"
@@ -224,6 +221,12 @@ def read_file_json(relative_path: str, encoding="utf-8"):
221
with open(absolute_path, "r", encoding=encoding) as f:
222
return json.load(f)
223
224
+def read_file_yaml(relative_path: str, encoding="utf-8"):
225
+ absolute_path = get_abs_path(relative_path)
226
+
227
+ with open(absolute_path, "r", encoding=encoding) as f:
228
+ return yaml.loads(f.read())
229
+
230
def read_file_bin(relative_path: str):
231
# Try to get the absolute path for the file from the original directory or backup directories
232
absolute_path = get_abs_path(relative_path)
python/helpers/migration.py
+28
@@ -1,7 +1,15 @@
1
import os
2
+import json
3
from python.helpers import files
4
+from python.helpers import subagents
5
+from python.helpers import yaml as yaml_helper
6
from python.helpers.print_style import PrintStyle
7
8
+
9
+def startup_migration() -> None:
10
+ migrate_user_data()
11
+ convert_agents_json_yaml()
12
+
13
def migrate_user_data() -> None:
14
"""
15
Migrate user data from /tmp and other locations to /usr.
@@ -44,6 +52,26 @@ def migrate_user_data() -> None:
52
53
PrintStyle().print("Migration check complete.")
54
55
+
56
+def convert_agents_json_yaml() -> None:
57
+ for root in subagents.get_agents_roots():
58
+ rel_root = files.deabsolute_path(root)
59
+ for subdir in files.get_subdirectories(rel_root):
60
+ agent_yaml = os.path.join(rel_root, subdir, "agent.yaml")
61
+ if files.exists(agent_yaml):
62
+ continue
63
+
64
+ agent_json = os.path.join(rel_root, subdir, "agent.json")
65
+ if not files.exists(agent_json):
66
+ continue
67
+
68
+ try:
69
+ agent_obj = json.loads(files.read_file(agent_json))
70
+ files.write_file(agent_yaml, yaml_helper.dumps(agent_obj))
71
+ except Exception as e:
72
+ PrintStyle.error(f"Failed to convert {agent_json} to YAML", e)
73
+ continue
74
+
75
# --- Helper Functions ----------------------------------------------------------
76
77
def _move_dir(src: str, dst: str, overwrite: bool = False) -> None:
python/helpers/subagents.py
+15
-4
@@ -1,4 +1,5 @@
1
from python.helpers import files
2
+from python.helpers import yaml as yaml_helper
3
from typing import TypedDict, TYPE_CHECKING
4
from pydantic import BaseModel, model_validator
5
import json
@@ -88,8 +89,13 @@ def _get_agents_list_from_dir(dir: str, origin: Origin) -> dict[str, SubAgentLis
89
90
for subdir in subdirs:
91
try:
91
- agent_json = files.read_file(files.get_abs_path(dir, subdir, "agent.json"))
92
- agent_data = SubAgentListItem.model_validate_json(agent_json)
92
+ agent_yaml_path = files.get_abs_path(dir, subdir, "agent.yaml")
93
+ if files.exists(agent_yaml_path):
94
+ agent_yaml = files.read_file(agent_yaml_path)
95
+ agent_data = SubAgentListItem.model_validate(yaml_helper.loads(agent_yaml) or {})
96
+ else:
97
+ agent_json = files.read_file(files.get_abs_path(dir, subdir, "agent.json"))
98
+ agent_data = SubAgentListItem.model_validate_json(agent_json)
99
name = agent_data.name or subdir
100
agent_data.name = name
101
agent_data.path = files.get_abs_path(dir, subdir)
@@ -176,8 +182,13 @@ def delete_agent_data(name: str) -> None:
182
183
def _load_agent_data_from_dir(dir: str, name: str, origin: Origin) -> SubAgent | None:
184
try:
179
- subagent_json = files.read_file(files.get_abs_path(dir, name, "agent.json"))
180
- subagent = SubAgent.model_validate_json(subagent_json)
185
+ agent_yaml_path = files.get_abs_path(dir, name, "agent.yaml")
186
+ if files.exists(agent_yaml_path):
187
+ agent_yaml = files.read_file(agent_yaml_path)
188
+ subagent = SubAgent.model_validate(yaml_helper.loads(agent_yaml) or {})
189
+ else:
190
+ subagent_json = files.read_file(files.get_abs_path(dir, name, "agent.json"))
191
+ subagent = SubAgent.model_validate_json(subagent_json)
192
except Exception:
193
# backward compatibility (before agent.json existed)
194
try:
python/helpers/yaml.py
new
+24
@@ -0,0 +1,24 @@
1
+import json
2
+import yaml
3
+
4
+
5
+def loads(text: str):
6
+ return yaml.safe_load(text)
7
+
8
+
9
+def dumps(obj, **kwargs) -> str:
10
+ dump_kwargs = {
11
+ "allow_unicode": True,
12
+ "sort_keys": False,
13
+ **kwargs,
14
+ }
15
+ return yaml.safe_dump(obj, **dump_kwargs)
16
+
17
+
18
+def from_json(text: str, **yaml_dump_kwargs) -> str:
19
+ return dumps(json.loads(text), **yaml_dump_kwargs)
20
+
21
+
22
+def to_json(text: str, **json_dump_kwargs) -> str:
23
+ obj = loads(text)
24
+ return json.dumps(obj, ensure_ascii=False, **json_dump_kwargs)