Revert loaded skill history persistence
Reverts db01d7c1c873f59b656618bb23890bb518d4c0a3 and 3c83b2eca290f824867cb51cbed3fec60de2a8cb. Restores the prior loaded-skills prompt-extras behavior and removes the compaction reattachment metadata path.
Alessandro committed
Jun 18, 2026 at 16:45 UTC
bf2741990a239ce68877ca8565f5fc842e9ad40b
13 files changed
+32
-389
extensions/python/message_loop_prompts_after/AGENTS.md
+2
-4
@@ -2,19 +2,17 @@
2
3
## Purpose
4
5
-- Own prompt extras and history-output adjustments appended after primary message-loop prompt construction.
5
+- Own prompt extras appended after primary message-loop prompt construction.
6
7
## Ownership
8
9
-- Ordered Python files own current datetime, skill recall/load context, loaded-skill reattachment, agent info, parallel job status, and workdir extras injection.
9
+- Ordered Python files own current datetime, skill recall/load context, agent info, parallel job status, and workdir extras injection.
10
11
## Local Contracts
12
13
- Keep injected content bounded and clearly attributed.
14
- Preserve ordering where later prompt extras depend on earlier recall or load results.
15
- Do not expose secrets or private files from workdir extras.
16
-- Explicitly loaded skill instructions belong in normal tool-result history; this hook may recall candidate skills, but must not reinject loaded skill bodies through prompt extras every turn.
17
-- If compression hides an explicitly loaded skill body, this hook may reattach the missing visible revision as a bounded normal tool-result history message.
16
17
## Work Guidance
18
extensions/python/message_loop_prompts_after/_65_include_loaded_skills.py
+17
-74
@@ -1,95 +1,38 @@
1
from helpers.extension import Extension
2
-from helpers import skills, tokens
2
+from helpers import skills
3
from tools.skills_tool import DATA_NAME_LOADED_SKILLS
4
from agent import LoopData
5
6
7
-SKILL_REATTACHMENT_TOKEN_BUDGET = 12_000
8
-SKILL_REATTACHMENT_HEADER = (
9
- "Reattached loaded skill instructions after history compaction."
10
-)
11
-
12
-
7
class IncludeLoadedSkills(Extension):
8
async def execute(self, loop_data: LoopData = LoopData(), **kwargs):
9
if not self.agent:
10
return
11
18
- loop_data.extras_persistent.pop("loaded_skills", None)
12
+ extras = loop_data.extras_persistent
13
+
14
+ # Get loaded skills names
15
skill_names = self.agent.data.get(DATA_NAME_LOADED_SKILLS)
16
if not skill_names:
17
return
18
23
- # `skills_tool load` now appends full skill instructions as a normal
24
- # tool-result history message. Keep this legacy ledger pruned, but do
25
- # not reinject loaded skills through prompt extras every turn.
19
+ # load skill text here
20
+ content = ""
21
visible_skill_names = []
27
- loaded_skills = []
22
for skill_name in skill_names:
29
- skill = skills.find_skill(skill_name, agent=self.agent)
30
- if not skill:
23
+ if not skills.find_skill(skill_name, agent=self.agent):
24
continue
32
- visible_skill_names.append(skill.name)
33
- loaded_skills.append(skill)
25
+ visible_skill_names.append(skill_name)
26
+ skill_data = skills.load_skill_for_agent(skill_name=skill_name, agent=self.agent)
27
+ content += "\n\n" + skill_data
28
self.agent.data[DATA_NAME_LOADED_SKILLS] = visible_skill_names
35
-
36
- self._reattach_missing_skill_bodies(loop_data, loaded_skills)
37
-
38
- def _reattach_missing_skill_bodies(self, loop_data: LoopData, loaded_skills):
39
- if not self.agent or not loaded_skills:
29
+ content = content.strip()
30
+ if not content:
31
return
32
42
- visible_revisions = _visible_skill_revisions(loop_data.history_output)
43
- selected = []
44
- used_tokens = 0
45
-
46
- for skill in reversed(loaded_skills):
47
- skill_data = skills.load_skill_for_agent(
48
- skill_name=skill.name,
49
- agent=self.agent,
50
- )
51
- revision = skills.skill_revision(skill_data)
52
- if (skill.name, revision) in visible_revisions:
53
- continue
54
-
55
- message = f"{SKILL_REATTACHMENT_HEADER}\n\n{skill_data}"
56
- message_tokens = tokens.approximate_tokens(message)
57
- if used_tokens + message_tokens > SKILL_REATTACHMENT_TOKEN_BUDGET:
58
- continue
59
-
60
- selected.append((skill, revision, message))
61
- used_tokens += message_tokens
62
-
63
- for skill, revision, message in reversed(selected):
64
- history_message = self.agent.hist_add_tool_result(
65
- "skills_tool",
66
- message,
67
- skill_instructions={
68
- "name": skill.name,
69
- "path": str(skill.path),
70
- "revision": revision,
71
- "source": "skills_tool:reattach",
72
- "content_included": True,
73
- },
74
- )
75
- loop_data.history_output.extend(history_message.output())
76
-
33
78
-def _visible_skill_revisions(history_output) -> set[tuple[str, str]]:
79
- visible = set()
80
- for message in history_output or []:
81
- if not isinstance(message, dict):
82
- continue
83
- content = message.get("content")
84
- if not isinstance(content, dict):
85
- continue
86
- meta = content.get("skill_instructions")
87
- if not isinstance(meta, dict):
88
- continue
89
- if not meta.get("content_included"):
90
- continue
91
- name = str(meta.get("name") or "").strip()
92
- revision = str(meta.get("revision") or "").strip()
93
- if name and revision:
94
- visible.add((name, revision))
95
- return visible
34
+ # Inject into extras
35
+ extras["loaded_skills"] = self.agent.read_prompt(
36
+ "agent.system.skills.loaded.md",
37
+ skills=content,
38
+ )
helpers/skills.py
-5
@@ -1,6 +1,5 @@
1
from __future__ import annotations
2
3
-import hashlib
3
import os
4
import re
5
from dataclasses import dataclass, field
@@ -452,10 +451,6 @@ def load_skill_for_agent(
451
return "\n".join(lines)
452
453
455
-def skill_revision(skill_data: str) -> str:
456
- return hashlib.sha256(skill_data.encode("utf-8")).hexdigest()[:16]
457
-
458
-
454
def _get_skill_files(skill_dir: Path) -> str:
455
"""Get file tree for skill directory."""
456
if not skill_dir.exists():
helpers/skills.py.dox.md
+2
-3
@@ -30,7 +30,6 @@
30
- `delete_skill(skill_path: str) -> None`: Delete a skill directory.
31
- `find_skill(skill_name: str, agent: Agent | None=..., include_content: bool=..., include_hidden: bool=...) -> Optional[Skill]`
32
- `load_skill_for_agent(skill_name: str, agent: Agent | None=...) -> str`: Load skill and format it as a complete string for agent context.
33
-- `skill_revision(skill_data: str) -> str`
33
- `_get_skill_files(skill_dir: Path) -> str`: Get file tree for skill directory.
34
- `search_skills(query: str, limit: int=..., agent: Agent | None=..., include_hidden: bool=...) -> List[Skill]`
35
- `validate_skill(skill: Skill) -> List[str]`
@@ -53,11 +52,11 @@
52
- Helper modules own reusable framework APIs and must preserve public callers unless all callers, tests, and docs are updated together.
53
- Update this file whenever public functions, classes, persistence behavior, path/security assumptions, side effects, or cross-module contracts change.
54
- Observed side-effect areas: filesystem reads, filesystem deletion, plugin state, settings/state persistence, secret handling.
56
-- Imported dependency areas include: `__future__`, `dataclasses`, `hashlib`, `helpers`, `os`, `pathlib`, `re`, `typing`.
55
+- Imported dependency areas include: `__future__`, `dataclasses`, `helpers`, `os`, `pathlib`, `re`, `typing`.
56
57
## Key Concepts
58
60
-- Important called helpers/classes observed in the source: `dataclass`, `re.compile`, `field`, `Path`, `root.rglob`, `results.sort`, `re.sub`, `path.read_text`, `text.splitlines`, `join.strip`, `parse_frontmatter`, `frontmatter_text.splitlines`, `_parse_frontmatter_fallback`, `split_frontmatter`, `str.strip`, `_coerce_list`, `Skill`, `get_skill_roots`, `_filter_hidden_skills`, `files.get_abs_path`, `hashlib.sha256`.
59
+- Important called helpers/classes observed in the source: `dataclass`, `re.compile`, `field`, `Path`, `root.rglob`, `results.sort`, `re.sub`, `path.read_text`, `text.splitlines`, `join.strip`, `parse_frontmatter`, `frontmatter_text.splitlines`, `_parse_frontmatter_fallback`, `split_frontmatter`, `str.strip`, `_coerce_list`, `Skill`, `get_skill_roots`, `_filter_hidden_skills`, `files.get_abs_path`.
60
- Keep request/response, tool, or helper semantics documented here at the same time as source changes.
61
62
## Work Guidance
plugins/_chat_compaction/AGENTS.md
-1
@@ -18,7 +18,6 @@
18
- Preserve chat history integrity and persistence after compaction.
19
- Keep generated summaries bounded by configured model and token limits.
20
- Do not discard original context data unless the compaction flow explicitly owns that behavior.
21
-- Preserve loaded skill name/revision metadata in summaries without copying full skill bodies.
21
22
## Work Guidance
23
plugins/_chat_compaction/prompts/compact.sys.md
-1
@@ -6,7 +6,6 @@ Rules:
6
- Use terse bullet points, not prose
7
- Collapse related items into single lines
8
- Keep exact values: file paths, config values, code identifiers, credentials, URLs
9
-- Preserve loaded skill names and revisions from skill_instructions metadata, but do not copy full skill bodies
9
- Omit anything that can be re-derived from context
10
- Group by topic, not chronology
11
- No meta-commentary about the summarization
prompts/AGENTS.md
-1
@@ -18,7 +18,6 @@
18
- Keep placeholder names, include aliases, and template assumptions synchronized with prompt-loading code and extensions.
19
- Prompt changes can alter agent behavior; keep edits narrow and intentional.
20
- Maintain clear separation between core behavior prompts and profile/plugin-specific customization.
21
-- Framework summary prompts must preserve loaded skill name/revision metadata without copying full skill bodies.
21
22
## Work Guidance
23
prompts/agent.system.tool.skills.md
+1
-1
@@ -5,7 +5,7 @@ common args: action skill_name query file_path
5
workflow:
6
- action `search`: find candidate skills by keywords or trigger phrases from the current task
7
- action `list`: discover available skills
8
-- action `load`: append one skill's full instructions to chat history by `skill_name`
8
+- action `load`: load one skill by `skill_name`
9
- action `read_file`: open one file inside a loaded skill directory
10
if the user says "find/search a skill", call `search` before `load` even when the likely skill name seems obvious
11
`read_file` requires both `skill_name` and `file_path`; load the skill first, then read `SKILL.md` or the named relative file
prompts/fw.bulk_summary.sys.md
+1
-2
@@ -7,8 +7,7 @@ You must return a single summary of all records
7
# Expected output
8
Your output will be a text of the summary
9
Length of the text should be one paragraph, approximately 100 words
10
-If a tool result includes skill_instructions metadata, preserve the loaded skill name and revision in the summary, but do not copy the full skill body
10
No intro
11
No conclusion
12
No formatting
14
-Only the summary text is returned
13
+Only the summary text is returned
\ No newline at end of file
prompts/fw.topic_summary.sys.md
+1
-2
@@ -8,8 +8,7 @@ You must return a single summary of all records
8
Your output will be a text of the summary
9
Summary must be shorter than original messages
10
Length of the text should be maximum one paragraph, approximately 100 words, shorter if original is shorter
11
-If a tool result includes skill_instructions metadata, preserve the loaded skill name and revision in the summary, but do not copy the full skill body
11
No intro
12
No conclusion
13
No formatting
15
-Only the summary text is returned
14
+Only the summary text is returned
\ No newline at end of file
tests/test_tool_action_contracts.py
-220
@@ -38,7 +38,6 @@ class _FakeAgent:
38
def __init__(self) -> None:
39
self.data = {}
40
self.context = types.SimpleNamespace(id="ctx")
41
- self.history = types.SimpleNamespace(output=lambda: [])
41
42
def read_prompt(self, _name: str, **kwargs) -> str:
43
return f"deleted {kwargs.get('memory_count', 0)}"
@@ -75,10 +74,6 @@ def _load_skills_tool(monkeypatch, skill_root: Path):
74
skills_stub.list_skills = lambda *args, **kwargs: [fake_skill]
75
skills_stub.search_skills = lambda *args, **kwargs: [fake_skill]
76
skills_stub.find_skill = lambda *args, **kwargs: fake_skill
78
- skills_stub.load_skill_for_agent = (
79
- lambda *args, **kwargs: "Skill: browser-form-workflows\n\nInstructions:\nUse labels before typing."
80
- )
81
- skills_stub.skill_revision = lambda skill_data: "rev1"
77
monkeypatch.setitem(sys.modules, "helpers.skills", skills_stub)
78
79
print_style_stub = types.ModuleType("helpers.print_style")
@@ -91,65 +86,6 @@ def _load_skills_tool(monkeypatch, skill_root: Path):
86
return importlib.import_module("tools.skills_tool")
87
88
94
-class _FakeExtension:
95
- def __init__(self, agent=None):
96
- self.agent = agent
97
-
98
-
99
-class _FakeLoadedSkillAgent:
100
- def __init__(self) -> None:
101
- self.data = {"loaded_skills": ["browser-form-workflows"]}
102
- self.added_tool_results = []
103
-
104
- def hist_add_tool_result(self, tool_name: str, tool_result: str, **kwargs):
105
- content = {"tool_name": tool_name, "tool_result": tool_result, **kwargs}
106
- self.added_tool_results.append(content)
107
- return types.SimpleNamespace(
108
- output=lambda: [{"ai": False, "content": content}]
109
- )
110
-
111
-
112
-def _load_loaded_skills_extension(monkeypatch, skill_root: Path):
113
- extension_stub = types.ModuleType("helpers.extension")
114
- extension_stub.Extension = _FakeExtension
115
- monkeypatch.setitem(sys.modules, "helpers.extension", extension_stub)
116
-
117
- agent_stub = types.ModuleType("agent")
118
- agent_stub.LoopData = lambda **kwargs: types.SimpleNamespace(**kwargs)
119
- monkeypatch.setitem(sys.modules, "agent", agent_stub)
120
-
121
- skills_stub = types.ModuleType("helpers.skills")
122
- fake_skill = _FakeSkill(
123
- name="browser-form-workflows",
124
- description="Use for complex browser forms.",
125
- path=skill_root,
126
- tags=[],
127
- )
128
- skills_stub.find_skill = lambda *args, **kwargs: fake_skill
129
- skills_stub.load_skill_for_agent = (
130
- lambda *args, **kwargs: "Skill: browser-form-workflows\n\nInstructions:\nUse labels before typing."
131
- )
132
- skills_stub.skill_revision = lambda skill_data: "rev1"
133
- monkeypatch.setitem(sys.modules, "helpers.skills", skills_stub)
134
-
135
- tokens_stub = types.ModuleType("helpers.tokens")
136
- tokens_stub.approximate_tokens = lambda text: len(str(text).split())
137
- monkeypatch.setitem(sys.modules, "helpers.tokens", tokens_stub)
138
-
139
- import helpers
140
-
141
- monkeypatch.setattr(helpers, "skills", skills_stub, raising=False)
142
- monkeypatch.setattr(helpers, "tokens", tokens_stub, raising=False)
143
-
144
- skills_tool_stub = types.ModuleType("tools.skills_tool")
145
- skills_tool_stub.DATA_NAME_LOADED_SKILLS = "loaded_skills"
146
- monkeypatch.setitem(sys.modules, "tools.skills_tool", skills_tool_stub)
147
-
148
- module_name = "extensions.python.message_loop_prompts_after._65_include_loaded_skills"
149
- sys.modules.pop(module_name, None)
150
- return importlib.import_module(module_name)
151
-
152
-
89
def _load_computer_use_remote_tool(monkeypatch):
90
_install_tool_stub(monkeypatch)
91
@@ -210,162 +146,6 @@ def test_skills_tool_accepts_action_alias_for_search(monkeypatch, tmp_path: Path
146
assert "browser-form-workflows" in response.message
147
148
213
-def test_skills_tool_load_appends_skill_instructions_as_tool_result(
214
- monkeypatch, tmp_path: Path
215
-):
216
- module = _load_skills_tool(monkeypatch, tmp_path)
217
- agent = _FakeAgent()
218
- tool = module.SkillsTool(
219
- agent,
220
- "skills_tool",
221
- None,
222
- {"action": "load", "skill_name": "browser-form-workflows"},
223
- "",
224
- None,
225
- )
226
-
227
- response = asyncio.run(tool.execute(**tool.args))
228
-
229
- assert "Skill: browser-form-workflows" in response.message
230
- assert response.additional["skill_instructions"]["name"] == "browser-form-workflows"
231
- assert response.additional["skill_instructions"]["content_included"] is True
232
- assert agent.data["loaded_skills"] == ["browser-form-workflows"]
233
-
234
-
235
-def test_skills_tool_load_omits_duplicate_visible_skill_revision(
236
- monkeypatch, tmp_path: Path
237
-):
238
- module = _load_skills_tool(monkeypatch, tmp_path)
239
- agent = _FakeAgent()
240
- tool = module.SkillsTool(
241
- agent,
242
- "skills_tool",
243
- None,
244
- {"action": "load", "skill_name": "browser-form-workflows"},
245
- "",
246
- None,
247
- )
248
- first = asyncio.run(tool.execute(**tool.args))
249
- loaded_message = {
250
- "ai": False,
251
- "content": {"skill_instructions": first.additional["skill_instructions"]},
252
- }
253
- agent.history = types.SimpleNamespace(output=lambda: [loaded_message])
254
-
255
- second = asyncio.run(tool.execute(**tool.args))
256
-
257
- assert "already loaded in visible chat history" in second.message
258
- assert "Instructions:\nUse labels before typing." not in second.message
259
- assert second.additional["skill_instructions"]["content_included"] is False
260
- assert second.additional["skill_instructions"]["already_loaded"] is True
261
- assert agent.data["loaded_skills"] == ["browser-form-workflows"]
262
-
263
-
264
-def test_skills_tool_load_reloads_when_prior_skill_is_not_model_visible(
265
- monkeypatch, tmp_path: Path
266
-):
267
- module = _load_skills_tool(monkeypatch, tmp_path)
268
- agent = _FakeAgent()
269
- tool = module.SkillsTool(
270
- agent,
271
- "skills_tool",
272
- None,
273
- {"action": "load", "skill_name": "browser-form-workflows"},
274
- "",
275
- None,
276
- )
277
- first = asyncio.run(tool.execute(**tool.args))
278
- hidden_message = types.SimpleNamespace(
279
- summary="",
280
- content={"skill_instructions": first.additional["skill_instructions"]},
281
- )
282
- agent.history = types.SimpleNamespace(
283
- all_messages=lambda: [hidden_message],
284
- output=lambda: [
285
- {
286
- "ai": False,
287
- "content": "Earlier history was summarized and no skill body is visible.",
288
- }
289
- ],
290
- )
291
-
292
- second = asyncio.run(tool.execute(**tool.args))
293
-
294
- assert "Skill: browser-form-workflows" in second.message
295
- assert second.additional["skill_instructions"]["content_included"] is True
296
-
297
-
298
-def test_loaded_skills_extension_reattaches_missing_body_after_compaction(
299
- monkeypatch, tmp_path: Path
300
-):
301
- module = _load_loaded_skills_extension(monkeypatch, tmp_path)
302
- agent = _FakeLoadedSkillAgent()
303
- loop_data = types.SimpleNamespace(
304
- extras_persistent={"loaded_skills": "legacy"},
305
- history_output=[
306
- {
307
- "ai": False,
308
- "content": "Earlier history was summarized and no skill body is visible.",
309
- }
310
- ],
311
- )
312
-
313
- asyncio.run(module.IncludeLoadedSkills(agent).execute(loop_data))
314
-
315
- assert "loaded_skills" not in loop_data.extras_persistent
316
- assert len(agent.added_tool_results) == 1
317
- added = agent.added_tool_results[0]
318
- assert added["tool_name"] == "skills_tool"
319
- assert "Skill: browser-form-workflows" in added["tool_result"]
320
- assert added["skill_instructions"] == {
321
- "name": "browser-form-workflows",
322
- "path": str(tmp_path),
323
- "revision": "rev1",
324
- "source": "skills_tool:reattach",
325
- "content_included": True,
326
- }
327
- assert loop_data.history_output[-1]["content"] == added
328
-
329
-
330
-def test_loaded_skills_extension_does_not_reattach_visible_revision(
331
- monkeypatch, tmp_path: Path
332
-):
333
- module = _load_loaded_skills_extension(monkeypatch, tmp_path)
334
- agent = _FakeLoadedSkillAgent()
335
- loop_data = types.SimpleNamespace(
336
- extras_persistent={},
337
- history_output=[
338
- {
339
- "ai": False,
340
- "content": {
341
- "skill_instructions": {
342
- "name": "browser-form-workflows",
343
- "revision": "rev1",
344
- "content_included": True,
345
- }
346
- },
347
- }
348
- ],
349
- )
350
-
351
- asyncio.run(module.IncludeLoadedSkills(agent).execute(loop_data))
352
-
353
- assert agent.added_tool_results == []
354
-
355
-
356
-def test_loaded_skills_extension_keeps_reattachments_under_budget(
357
- monkeypatch, tmp_path: Path
358
-):
359
- module = _load_loaded_skills_extension(monkeypatch, tmp_path)
360
- monkeypatch.setattr(module, "SKILL_REATTACHMENT_TOKEN_BUDGET", 1)
361
- agent = _FakeLoadedSkillAgent()
362
- loop_data = types.SimpleNamespace(extras_persistent={}, history_output=[])
363
-
364
- asyncio.run(module.IncludeLoadedSkills(agent).execute(loop_data))
365
-
366
- assert agent.added_tool_results == []
367
-
368
-
149
def test_skills_tool_read_file_action_reads_inside_skill_dir(
150
monkeypatch, tmp_path: Path
151
):
tools/skills_tool.py
+6
-70
@@ -111,7 +111,7 @@ class SkillsTool(Tool):
111
skill_name = self._normalize_skill_name(
112
str(kwargs.get("skill_name") or self.args.get("skill_name") or "")
113
)
114
- return self._load(skill_name)
114
+ return Response(message=self._load(skill_name), break_loop=False)
115
if action == "read_file":
116
skill_name = self._normalize_skill_name(
117
str(kwargs.get("skill_name") or self.args.get("skill_name") or "")
@@ -185,14 +185,11 @@ class SkillsTool(Tool):
185
)
186
return "\n".join(lines)
187
188
- def _load(self, skill_name: str) -> Response:
188
+ def _load(self, skill_name: str) -> str:
189
skill_name = self._normalize_skill_name(skill_name)
190
191
if not skill_name:
192
- return Response(
193
- message="Error: 'skill_name' is required for action=load.",
194
- break_loop=False,
195
- )
192
+ return "Error: 'skill_name' is required for action=load."
193
194
# Verify skill exists
195
skill = skills_helper.find_skill(
@@ -201,29 +198,9 @@ class SkillsTool(Tool):
198
agent=self.agent,
199
)
200
if not skill:
204
- return Response(
205
- message=(
206
- f"Error: skill not found: {skill_name!r}. "
207
- "Try skills_tool action=list or action=search."
208
- ),
209
- break_loop=False,
210
- )
201
+ return f"Error: skill not found: {skill_name!r}. Try skills_tool action=list or action=search."
202
212
- skill_data = skills_helper.load_skill_for_agent(
213
- skill_name=skill.name,
214
- agent=self.agent,
215
- )
216
- revision = skills_helper.skill_revision(skill_data)
217
- metadata = {
218
- "name": skill.name,
219
- "path": str(skill.path),
220
- "revision": revision,
221
- "source": "skills_tool:load",
222
- "content_included": True,
223
- }
224
-
225
- # Keep the old ledger for UI/backwards compatibility. The skill body now
226
- # lives in normal tool-result history, not in per-turn prompt extras.
203
+ # Store skill name for fresh loading each turn
204
if not self.agent.data.get(DATA_NAME_LOADED_SKILLS):
205
self.agent.data[DATA_NAME_LOADED_SKILLS] = []
206
loaded = self.agent.data[DATA_NAME_LOADED_SKILLS]
@@ -232,48 +209,7 @@ class SkillsTool(Tool):
209
loaded.append(skill.name)
210
self.agent.data[DATA_NAME_LOADED_SKILLS] = loaded[-max_loaded_skills():]
211
235
- if self._visible_skill_revision_loaded(skill.name, revision):
236
- return Response(
237
- message=(
238
- f"Skill '{skill.name}' is already loaded in visible "
239
- "chat history for this revision."
240
- ),
241
- break_loop=False,
242
- additional={
243
- "skill_instructions": {
244
- **metadata,
245
- "content_included": False,
246
- "already_loaded": True,
247
- }
248
- },
249
- )
250
-
251
- return Response(
252
- message=skill_data,
253
- break_loop=False,
254
- additional={"skill_instructions": metadata},
255
- )
256
-
257
- def _visible_skill_revision_loaded(self, skill_name: str, revision: str) -> bool:
258
- history_obj = getattr(self.agent, "history", None)
259
- output = getattr(history_obj, "output", None)
260
- if not callable(output):
261
- return False
262
-
263
- for message in output():
264
- if not isinstance(message, dict):
265
- continue
266
- content = message.get("content")
267
- if not isinstance(content, dict):
268
- continue
269
- meta = content.get("skill_instructions")
270
- if not isinstance(meta, dict):
271
- continue
272
- if not meta.get("content_included"):
273
- continue
274
- if meta.get("name") == skill_name and meta.get("revision") == revision:
275
- return True
276
- return False
212
+ return f"Loaded skill '{skill.name}' into EXTRAS."
213
214
def _read_file(self, skill_name: str, file_path: str) -> str:
215
if not skill_name:
tools/skills_tool.py.dox.md
+2
-5
@@ -25,15 +25,12 @@
25
- Update this file whenever tool arguments, output shape, `break_loop` behavior, intervention handling, prompt instructions, or side effects change.
26
- `SkillsTool` is a `Tool`.
27
- `SkillsTool` defines `execute(...)`.
28
-- `load` returns the full formatted skill instructions as the tool result so the instructions are appended once through normal message history instead of being reinjected through prompt extras.
29
-- `load` includes a `skill_instructions` metadata sidecar in the tool-result content with skill name, path, revision, source, and whether full content was included.
30
-- Duplicate `load` calls may omit the full body only when the same skill revision is still present in model-visible `history.output()` content.
31
-- Observed side-effect areas: filesystem reads, filesystem deletion, settings/state persistence, chat history content.
28
+- Observed side-effect areas: filesystem reads, filesystem deletion, settings/state persistence.
29
- Imported dependency areas include: `__future__`, `helpers`, `helpers.print_style`, `helpers.tool`, `pathlib`, `typing`.
30
31
## Key Concepts
32
36
-- Important called helpers/classes observed in the source: `str.strip.lower.replace`, `skill_name.strip`, `super.get_log_object`, `self._normalize_skill_name`, `self.get_log_object`, `skills_helper.list_skills`, `join`, `skills_helper.search_skills`, `skills_helper.find_skill`, `skills_helper.load_skill_for_agent`, `skills_helper.skill_revision`, `skill.path.resolve`, `Path`, `resolved.read_text`, `skill_name.startswith`, `skill_name.endswith`, `self._current_action`, `self.agent.context.log.log`, `Response`, `strip`, `loaded.remove`, `target.is_absolute`.
33
+- Important called helpers/classes observed in the source: `str.strip.lower.replace`, `skill_name.strip`, `super.get_log_object`, `self._normalize_skill_name`, `self.get_log_object`, `skills_helper.list_skills`, `join`, `skills_helper.search_skills`, `skills_helper.find_skill`, `skill.path.resolve`, `Path`, `resolved.read_text`, `skill_name.startswith`, `skill_name.endswith`, `self._current_action`, `self.agent.context.log.log`, `Response`, `strip`, `loaded.remove`, `target.is_absolute`.
34
- Keep request/response, tool, or helper semantics documented here at the same time as source changes.
35
36
## Work Guidance