Skip to content

Commit dbffe7c

Browse files
committed
fix: address community issues #37 #30 #36
- s03: inject reminder into tool_result instead of mutating history (#37) - s05: SkillLoader uses rglob("SKILL.md") + frontmatter name priority, matching Agent Skills standard (#30, PR #34) - CI: upgrade actions/checkout and actions/setup-node to v6 (#36) - docs: update s05 skill directory structure in all 3 languages
1 parent 4f39ee4 commit dbffe7c

10 files changed

Lines changed: 75 additions & 61 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ jobs:
1414
working-directory: web
1515

1616
steps:
17-
- uses: actions/checkout@v4
17+
- uses: actions/checkout@v6
1818

19-
- uses: actions/setup-node@v4
19+
- uses: actions/setup-node@v6
2020
with:
2121
node-version: 20
2222
cache: npm

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ jobs:
5555
- uses: actions/checkout@v6
5656

5757
- name: Set up Node.js
58-
uses: actions/setup-node@v4
58+
uses: actions/setup-node@v6
5959
with:
6060
node-version: "20"
6161
cache: "npm"

agents/s03_todo_write.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,11 +163,7 @@ def run_edit(path: str, old_text: str, new_text: str) -> str:
163163
def agent_loop(messages: list):
164164
rounds_since_todo = 0
165165
while True:
166-
# Nag reminder: if 3+ rounds without a todo update, inject reminder
167-
if rounds_since_todo >= 3 and messages:
168-
last = messages[-1]
169-
if last["role"] == "user" and isinstance(last.get("content"), list):
170-
last["content"].insert(0, {"type": "text", "text": "<reminder>Update your todos.</reminder>"})
166+
# Nag reminder is injected below, alongside tool results
171167
response = client.messages.create(
172168
model=MODEL, system=SYSTEM, messages=messages,
173169
tools=TOOLS, max_tokens=8000,
@@ -189,6 +185,8 @@ def agent_loop(messages: list):
189185
if block.name == "todo":
190186
used_todo = True
191187
rounds_since_todo = 0 if used_todo else rounds_since_todo + 1
188+
if rounds_since_todo >= 3:
189+
results.insert(0, {"type": "text", "text": "<reminder>Update your todos.</reminder>"})
192190
messages.append({"role": "user", "content": results})
193191

194192

agents/s05_skill_loading.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,25 @@
77
Layer 1 (cheap): skill names in system prompt (~100 tokens/skill)
88
Layer 2 (on demand): full skill body in tool_result
99
10+
skills/
11+
pdf/
12+
SKILL.md <-- frontmatter (name, description) + body
13+
code-review/
14+
SKILL.md
15+
1016
System prompt:
1117
+--------------------------------------+
1218
| You are a coding agent. |
1319
| Skills available: |
14-
| - git: Git workflow helpers | <-- Layer 1: metadata only
15-
| - test: Testing best practices |
20+
| - pdf: Process PDF files... | <-- Layer 1: metadata only
21+
| - code-review: Review code... |
1622
+--------------------------------------+
1723
18-
When model calls load_skill("git"):
24+
When model calls load_skill("pdf"):
1925
+--------------------------------------+
2026
| tool_result: |
2127
| <skill> |
22-
| Full git workflow instructions... | <-- Layer 2: full body
28+
| Full PDF processing instructions | <-- Layer 2: full body
2329
| Step 1: ... |
2430
| Step 2: ... |
2531
| </skill> |
@@ -44,10 +50,10 @@
4450
WORKDIR = Path.cwd()
4551
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
4652
MODEL = os.environ["MODEL_ID"]
47-
SKILLS_DIR = WORKDIR / ".skills"
53+
SKILLS_DIR = WORKDIR / "skills"
4854

4955

50-
# -- SkillLoader: parse .skills/*.md files with YAML frontmatter --
56+
# -- SkillLoader: scan skills/<name>/SKILL.md with YAML frontmatter --
5157
class SkillLoader:
5258
def __init__(self, skills_dir: Path):
5359
self.skills_dir = skills_dir
@@ -57,10 +63,10 @@ def __init__(self, skills_dir: Path):
5763
def _load_all(self):
5864
if not self.skills_dir.exists():
5965
return
60-
for f in sorted(self.skills_dir.glob("*.md")):
61-
name = f.stem
66+
for f in sorted(self.skills_dir.rglob("SKILL.md")):
6267
text = f.read_text()
6368
meta, body = self._parse_frontmatter(text)
69+
name = meta.get("name", f.parent.name)
6470
self.skills[name] = {"meta": meta, "body": body, "path": str(f)}
6571

6672
def _parse_frontmatter(self, text: str) -> tuple:

agents/s_full.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ class SkillLoader:
199199
def __init__(self, skills_dir: Path):
200200
self.skills = {}
201201
if skills_dir.exists():
202-
for f in sorted(skills_dir.glob("*.md")):
202+
for f in sorted(skills_dir.rglob("SKILL.md")):
203203
text = f.read_text()
204204
match = re.match(r"^---\n(.*?)\n---\n(.*)", text, re.DOTALL)
205205
meta, body = {}, text
@@ -209,7 +209,8 @@ def __init__(self, skills_dir: Path):
209209
k, v = line.split(":", 1)
210210
meta[k.strip()] = v.strip()
211211
body = match.group(2).strip()
212-
self.skills[f.stem] = {"meta": meta, "body": body}
212+
name = meta.get("name", f.parent.name)
213+
self.skills[name] = {"meta": meta, "body": body}
213214

214215
def descriptions(self) -> str:
215216
if not self.skills: return "(no skills)"

docs/en/s05-skill-loading.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,27 @@ Layer 1: skill *names* in system prompt (cheap). Layer 2: full *body* via tool_r
3333

3434
## How It Works
3535

36-
1. Skill files live in `.skills/` as Markdown with YAML frontmatter.
36+
1. Each skill is a directory containing a `SKILL.md` with YAML frontmatter.
3737

3838
```
39-
.skills/
40-
git.md # ---\n description: Git workflow\n ---\n ...
41-
test.md # ---\n description: Testing patterns\n ---\n ...
39+
skills/
40+
pdf/
41+
SKILL.md # ---\n name: pdf\n description: Process PDF files\n ---\n ...
42+
code-review/
43+
SKILL.md # ---\n name: code-review\n description: Review code\n ---\n ...
4244
```
4345

44-
2. SkillLoader parses frontmatter, separates metadata from body.
46+
2. SkillLoader scans for `SKILL.md` files, uses the directory name as the skill identifier.
4547

4648
```python
4749
class SkillLoader:
4850
def __init__(self, skills_dir: Path):
4951
self.skills = {}
50-
for f in sorted(skills_dir.glob("*.md")):
52+
for f in sorted(skills_dir.rglob("SKILL.md")):
5153
text = f.read_text()
5254
meta, body = self._parse_frontmatter(text)
53-
self.skills[f.stem] = {"meta": meta, "body": body}
55+
name = meta.get("name", f.parent.name)
56+
self.skills[name] = {"meta": meta, "body": body}
5457

5558
def get_descriptions(self) -> str:
5659
lines = []
@@ -87,7 +90,7 @@ The model learns what skills exist (cheap) and loads them when relevant (expensi
8790
|----------------|------------------|----------------------------|
8891
| Tools | 5 (base + task) | 5 (base + load_skill) |
8992
| System prompt | Static string | + skill descriptions |
90-
| Knowledge | None | .skills/*.md files |
93+
| Knowledge | None | skills/\*/SKILL.md files |
9194
| Injection | None | Two-layer (system + result)|
9295

9396
## Try It

docs/ja/s05-skill-loading.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,27 @@ When model calls load_skill("git"):
3333

3434
## 仕組み
3535

36-
1. スキルファイルは`.skills/`にYAMLフロントマター付きMarkdownとして配置される
36+
1. 各スキルは `SKILL.md` ファイルを含むディレクトリとして配置される
3737

3838
```
39-
.skills/
40-
git.md # ---\n description: Git workflow\n ---\n ...
41-
test.md # ---\n description: Testing patterns\n ---\n ...
39+
skills/
40+
pdf/
41+
SKILL.md # ---\n name: pdf\n description: Process PDF files\n ---\n ...
42+
code-review/
43+
SKILL.md # ---\n name: code-review\n description: Review code\n ---\n ...
4244
```
4345

44-
2. SkillLoaderがフロントマターを解析し、メタデータと本体を分離する
46+
2. SkillLoaderが `SKILL.md` を再帰的に探索し、ディレクトリ名をスキル識別子として使用する
4547

4648
```python
4749
class SkillLoader:
4850
def __init__(self, skills_dir: Path):
4951
self.skills = {}
50-
for f in sorted(skills_dir.glob("*.md")):
52+
for f in sorted(skills_dir.rglob("SKILL.md")):
5153
text = f.read_text()
5254
meta, body = self._parse_frontmatter(text)
53-
self.skills[f.stem] = {"meta": meta, "body": body}
55+
name = meta.get("name", f.parent.name)
56+
self.skills[name] = {"meta": meta, "body": body}
5457

5558
def get_descriptions(self) -> str:
5659
lines = []
@@ -87,7 +90,7 @@ TOOL_HANDLERS = {
8790
|----------------|------------------|----------------------------|
8891
| Tools | 5 (base + task) | 5 (base + load_skill) |
8992
| System prompt | Static string | + skill descriptions |
90-
| Knowledge | None | .skills/*.md files |
93+
| Knowledge | None | skills/\*/SKILL.md files |
9194
| Injection | None | Two-layer (system + result)|
9295

9396
## 試してみる

docs/zh/s05-skill-loading.md

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,24 +33,27 @@ When model calls load_skill("git"):
3333

3434
## 工作原理
3535

36-
1. 技能文件以 Markdown 格式存放在 `.skills/`, 带 YAML frontmatter。
36+
1. 每个技能是一个目录, 包含 `SKILL.md` 文件和 YAML frontmatter。
3737

3838
```
39-
.skills/
40-
git.md # ---\n description: Git workflow\n ---\n ...
41-
test.md # ---\n description: Testing patterns\n ---\n ...
39+
skills/
40+
pdf/
41+
SKILL.md # ---\n name: pdf\n description: Process PDF files\n ---\n ...
42+
code-review/
43+
SKILL.md # ---\n name: code-review\n description: Review code\n ---\n ...
4244
```
4345

44-
2. SkillLoader 解析 frontmatter, 分离元数据和正文
46+
2. SkillLoader 递归扫描 `SKILL.md` 文件, 用目录名作为技能标识
4547

4648
```python
4749
class SkillLoader:
4850
def __init__(self, skills_dir: Path):
4951
self.skills = {}
50-
for f in sorted(skills_dir.glob("*.md")):
52+
for f in sorted(skills_dir.rglob("SKILL.md")):
5153
text = f.read_text()
5254
meta, body = self._parse_frontmatter(text)
53-
self.skills[f.stem] = {"meta": meta, "body": body}
55+
name = meta.get("name", f.parent.name)
56+
self.skills[name] = {"meta": meta, "body": body}
5457

5558
def get_descriptions(self) -> str:
5659
lines = []
@@ -87,7 +90,7 @@ TOOL_HANDLERS = {
8790
|----------------|------------------|--------------------------------|
8891
| Tools | 5 (基础 + task) | 5 (基础 + load_skill) |
8992
| 系统提示 | 静态字符串 | + 技能描述列表 |
90-
| 知识库 || .skills/*.md 文件 |
93+
| 知识库 || skills/\*/SKILL.md 文件 |
9194
| 注入方式 || 两层 (系统提示 + result) |
9295

9396
## 试一试

0 commit comments

Comments
 (0)