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
This commit is contained in:
CrazyBoyM
2026-02-28 00:09:57 +08:00
parent 4f39ee4512
commit dbffe7c8d0
10 changed files with 75 additions and 61 deletions

View File

@@ -163,11 +163,7 @@ TOOLS = [
def agent_loop(messages: list):
rounds_since_todo = 0
while True:
# Nag reminder: if 3+ rounds without a todo update, inject reminder
if rounds_since_todo >= 3 and messages:
last = messages[-1]
if last["role"] == "user" and isinstance(last.get("content"), list):
last["content"].insert(0, {"type": "text", "text": "<reminder>Update your todos.</reminder>"})
# Nag reminder is injected below, alongside tool results
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
@@ -189,6 +185,8 @@ def agent_loop(messages: list):
if block.name == "todo":
used_todo = True
rounds_since_todo = 0 if used_todo else rounds_since_todo + 1
if rounds_since_todo >= 3:
results.insert(0, {"type": "text", "text": "<reminder>Update your todos.</reminder>"})
messages.append({"role": "user", "content": results})

View File

@@ -7,19 +7,25 @@ Two-layer skill injection that avoids bloating the system prompt:
Layer 1 (cheap): skill names in system prompt (~100 tokens/skill)
Layer 2 (on demand): full skill body in tool_result
skills/
pdf/
SKILL.md <-- frontmatter (name, description) + body
code-review/
SKILL.md
System prompt:
+--------------------------------------+
| You are a coding agent. |
| Skills available: |
| - git: Git workflow helpers | <-- Layer 1: metadata only
| - test: Testing best practices |
| - pdf: Process PDF files... | <-- Layer 1: metadata only
| - code-review: Review code... |
+--------------------------------------+
When model calls load_skill("git"):
When model calls load_skill("pdf"):
+--------------------------------------+
| tool_result: |
| <skill> |
| Full git workflow instructions... | <-- Layer 2: full body
| Full PDF processing instructions | <-- Layer 2: full body
| Step 1: ... |
| Step 2: ... |
| </skill> |
@@ -44,10 +50,10 @@ if os.getenv("ANTHROPIC_BASE_URL"):
WORKDIR = Path.cwd()
client = Anthropic(base_url=os.getenv("ANTHROPIC_BASE_URL"))
MODEL = os.environ["MODEL_ID"]
SKILLS_DIR = WORKDIR / ".skills"
SKILLS_DIR = WORKDIR / "skills"
# -- SkillLoader: parse .skills/*.md files with YAML frontmatter --
# -- SkillLoader: scan skills/<name>/SKILL.md with YAML frontmatter --
class SkillLoader:
def __init__(self, skills_dir: Path):
self.skills_dir = skills_dir
@@ -57,10 +63,10 @@ class SkillLoader:
def _load_all(self):
if not self.skills_dir.exists():
return
for f in sorted(self.skills_dir.glob("*.md")):
name = f.stem
for f in sorted(self.skills_dir.rglob("SKILL.md")):
text = f.read_text()
meta, body = self._parse_frontmatter(text)
name = meta.get("name", f.parent.name)
self.skills[name] = {"meta": meta, "body": body, "path": str(f)}
def _parse_frontmatter(self, text: str) -> tuple:

View File

@@ -199,7 +199,7 @@ class SkillLoader:
def __init__(self, skills_dir: Path):
self.skills = {}
if skills_dir.exists():
for f in sorted(skills_dir.glob("*.md")):
for f in sorted(skills_dir.rglob("SKILL.md")):
text = f.read_text()
match = re.match(r"^---\n(.*?)\n---\n(.*)", text, re.DOTALL)
meta, body = {}, text
@@ -209,7 +209,8 @@ class SkillLoader:
k, v = line.split(":", 1)
meta[k.strip()] = v.strip()
body = match.group(2).strip()
self.skills[f.stem] = {"meta": meta, "body": body}
name = meta.get("name", f.parent.name)
self.skills[name] = {"meta": meta, "body": body}
def descriptions(self) -> str:
if not self.skills: return "(no skills)"