skills extras preparation

frdel committed Feb 5, 2026 at 16:59 UTC 6fafba67d7d5150259a74ce00da009937151ed9b
5 files changed +51 -134
prompts/agent.system.skills.loaded.md
+2 -3
@@ -1,5 +1,4 @@
1 -# Loaded skill
2 -- The following skill was explicitly loaded via skills_tool
3 -- Full content persists here each turn to survive history compression
1 +# loaded skills
2 +- the following skills were explicitly loaded via skills_tool
3
4 {{skills}}
python/extensions/message_loop_prompts_after/_65_include_loaded_skill.py deleted
-91
@@ -1,91 +0,0 @@
1 -from pathlib import Path
2 -from python.helpers.extension import Extension
3 -from python.helpers import skills, files, file_tree, runtime
4 -from agent import LoopData
5 -
6 -
7 -DATA_NAME_LOADED_SKILL = "loaded_skill"
8 -
9 -
10 -class IncludeLoadedSkill(Extension):
11 - async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
12 - skill_name = self.agent.data.get(DATA_NAME_LOADED_SKILL)
13 - if not skill_name:
14 - return
15 -
16 - # Load skill fresh each turn
17 - skill = skills.find_skill(skill_name, include_content=True, agent=self.agent)
18 - if not skill:
19 - return
20 -
21 - # Build skill block
22 - skill_block = self._build_skill_block(skill)
23 -
24 - # Add to persistent extras
25 - loop_data.extras_persistent["loaded_skill"] = self.agent.read_prompt(
26 - "agent.system.skill.loaded.md",
27 - skill=skill_block,
28 - )
29 -
30 - def _build_skill_block(self, skill: skills.Skill) -> str:
31 - """Build complete skill content with metadata, description, body, and files."""
32 - runtime_path = (
33 - files.normalize_a0_path(str(skill.path))
34 - if self.agent.config.code_exec_ssh_enabled
35 - else str(skill.path)
36 - )
37 -
38 - lines = [f"Skill: {skill.name}", f"Path: {runtime_path}"]
39 -
40 - # Metadata
41 - metadata = [
42 - ("Version", skill.version),
43 - ("Author", skill.author),
44 - ("License", skill.license),
45 - ("Compatibility", skill.compatibility),
46 - ("Tags", ", ".join(skill.tags) if skill.tags else None),
47 - ("Allowed tools", ", ".join(skill.allowed_tools) if skill.allowed_tools else None),
48 - ("Triggers", ", ".join(skill.triggers) if skill.triggers else None),
49 - ]
50 - lines.extend(f"{label}: {value}" for label, value in metadata if value)
51 -
52 - # Description and content
53 - if skill.description:
54 - lines.extend(["", "Description:", skill.description.strip()])
55 -
56 - lines.extend(["", "Content (SKILL.md body):", skill.content.strip() or "(empty)"])
57 -
58 - # File tree
59 - files_tree = self._get_skill_files(skill.path)
60 - lines.append("")
61 - if files_tree:
62 - lines.append("Files (use skills_tool method=read_file to open):")
63 - lines.append(files_tree)
64 - else:
65 - lines.append("No additional files found.")
66 -
67 - return "\n".join(lines)
68 -
69 - def _get_skill_files(self, skill_dir: Path) -> str:
70 - """Get file tree for skill directory."""
71 - if not skill_dir.exists():
72 - return ""
73 -
74 - tree = str(
75 - file_tree.file_tree(
76 - str(skill_dir),
77 - max_depth=10,
78 - folders_first=True,
79 - max_files=100,
80 - max_folders=100,
81 - output_mode="string",
82 - max_lines=300,
83 - ignore=files.read_file("conf/skill.default.gitignore"),
84 - )
85 - )
86 -
87 - if tree and runtime.is_development():
88 - runtime_path = files.normalize_a0_path(str(skill_dir))
89 - tree = tree.replace(str(skill_dir), runtime_path)
90 -
91 - return str(tree)
python/extensions/message_loop_prompts_after/_65_include_loaded_skills.py
+10 -11
@@ -1,24 +1,23 @@
1 from python.helpers.extension import Extension
2 +from python.helpers import skills
3 +from python.tools.skills_tool import DATA_NAME_LOADED_SKILLS
4 from agent import LoopData
5
6
5 -DATA_NAME_LOADED_SKILL = "loaded_skill"
6 -
7 -
7 class IncludeLoadedSkills(Extension):
8 async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
9 extras = loop_data.extras_persistent
10
12 - # Clear previous
13 - if "loaded_skills" in extras:
14 - del extras["loaded_skills"]
15 -
16 - # Get single loaded skill
17 - skill_data = self.agent.data.get(DATA_NAME_LOADED_SKILL)
18 - if not skill_data or not isinstance(skill_data, dict):
11 + # Get loaded skills names
12 + skill_names = self.agent.data.get(DATA_NAME_LOADED_SKILLS)
13 + if not skill_names:
14 return
15
21 - content = str(skill_data.get("content") or "").strip()
16 + # load skill text here
17 + content = ""
18 + for skill_name in skill_names:
19 + skill_data = skills.load_skill_for_agent(skill_name=skill_name, agent=self.agent)
20 + content += "\n\n" + skill_data
21 if not content:
22 return
23
python/helpers/skills.py
+8
@@ -464,6 +464,14 @@ def find_skill(
464 return s
465 return None
466
467 +def load_skill_for_agent(
468 + skill_name: str,
469 + agent:Agent|None=None,
470 +) -> str:
471 + skill = find_skill(skill_name, agent=agent, include_content=True)
472 + if not skill:
473 + return "TODO"
474 + return "TODO"
475
476 def search_skills(
477 query: str,
python/tools/skills_tool.py
+31 -29
@@ -8,7 +8,7 @@ from python.helpers import projects, files, file_tree
8 from python.helpers import skills as skills_helper, runtime
9
10
11 -DATA_NAME_LOADED_SKILL = "loaded_skill"
11 +DATA_NAME_LOADED_SKILLS = "loaded_skills"
12
13
14 class SkillsTool(Tool):
@@ -118,34 +118,36 @@ class SkillsTool(Tool):
118 if not skill_name:
119 return "Error: 'skill_name' is required for method=load."
120
121 - skill = skills_helper.find_skill(
122 - skill_name,
123 - include_content=True,
124 - agent=self.agent,
125 - )
126 - if not skill:
127 - return f"Error: skill not found: {skill_name!r}. Try skills_tool method=list or method=search."
128 -
129 - # Build skill content block
130 - files_tree = self._list_skill_files(skill.path, max_files=80)
131 - if self.agent.config.code_exec_ssh_enabled:
132 - runtime_path = files.normalize_a0_path(str(skill.path))
133 - else:
134 - runtime_path = str(skill.path)
135 -
136 - content_block = self._build_loaded_skill_block(
137 - skill=skill,
138 - runtime_path=runtime_path,
139 - files_tree=files_tree,
140 - )
141 -
142 - # Store single skill in agent.data (replaces previous)
143 - self.agent.data[DATA_NAME_LOADED_SKILL] = {
144 - "name": skill.name,
145 - "content": content_block,
146 - }
147 -
148 - return f"Loaded skill '{skill.name}' into persistent extras."
121 + self.agent.data[DATA_NAME_LOADED_SKILL] = [skill_name]
122 +
123 + # skill = skills_helper.find_skill(
124 + # skill_name,
125 + # include_content=True,
126 + # agent=self.agent,
127 + # )
128 + # if not skill:
129 + # return f"Error: skill not found: {skill_name!r}. Try skills_tool method=list or method=search."
130 +
131 + # # Build skill content block
132 + # files_tree = self._list_skill_files(skill.path, max_files=80)
133 + # if self.agent.config.code_exec_ssh_enabled:
134 + # runtime_path = files.normalize_a0_path(str(skill.path))
135 + # else:
136 + # runtime_path = str(skill.path)
137 +
138 + # content_block = self._build_loaded_skill_block(
139 + # skill=skill,
140 + # runtime_path=runtime_path,
141 + # files_tree=files_tree,
142 + # )
143 +
144 + # # Store single skill in agent.data (replaces previous)
145 + # self.agent.data[DATA_NAME_LOADED_SKILL] = {
146 + # "name": skill.name,
147 + # "content": content_block,
148 + # }
149 +
150 + return f"Loaded skill '{skill_name}' into EXTRAS."
151
152 def _build_loaded_skill_block(
153 self, *, skill: skills_helper.Skill, runtime_path: str, files_tree: str