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)