Skip to content

Skills-System

源码版本v0.73.1

skills.ts implementiert die Agent-Skills-Spezifikation (siehe https://agentskills.io/integrate-skills). Ein Skill ist eine Markdown-Datei mit Frontmatter, das den Zweck des Skills beschreibt. Der LLM sieht die Skill-Liste im Systemprompt und liest dann über das read-Werkzeug die entsprechende SKILL.md, um an die detaillierten Instruktionen zu kommen. Diese Datei ist zuständig für Entdeckung, Laden, Validierung, Deduplizierung und Formatierung als Prompt-Abschnitt.

Zuständigkeiten

  1. Entdeckungsregel:loadSkillsFromDirInternal scannt Verzeichnisse rekursiv — trifft es auf SKILL.md, gilt das als Skill-Wurzel und es wird nicht weiter rekursiert; andernfalls werden Unterverzeichnisse und die .md-Dateien im Wurzelverzeichnis gescannt. Siehe packages/coding-agent/src/core/skills.ts:173-280.
  2. Frontmatter-Validierung:validateName prüft den Namen (a-z0-9-, max. 64 Zeichen, kein führendes/abschließendes Minus, kein --, Übereinstimmung mit dem Namen des übergeordneten Verzeichnisses); validateDescription prüft, dass die Beschreibung pflicht ist und 1024 Zeichen nicht überschreitet. Siehe packages/coding-agent/src/core/skills.ts:93-117 und packages/coding-agent/src/core/skills.ts:122-132.
  3. Laden aus mehreren Quellen:loadSkills lädt aus drei Quellen: dem globalen Benutzerbereich (~/.pi/agent/skills), dem Projekt (./.pi/skills) und expliziten skillPaths. Bei Konflikten behält der zuerst registrierte Skill den Vortritt, und es wird eine collision-Diagnose erzeugt. Siehe packages/coding-agent/src/core/skills.ts:405-504.
  4. Deduplizierung: Über realpath werden symbolische Links aufgelöst; dieselbe Datei, die über verschiedene Pfade registriert wird, zählt nur einmal. Bei gleichnamigen Skills gilt First-Come-First-Served. Siehe packages/coding-agent/src/core/skills.ts:416-445.
  5. Prompt-Formatierung:formatSkillsForPrompt erzeugt den XML-Abschnitt <available_skills>; jeder Skill wird als Tripel <name>/<description>/<location> ausgegeben. Skills mit disableModelInvocation: true kommen nicht in den Prompt (nur über /skill:name explizit aufrufbar). Siehe packages/coding-agent/src/core/skills.ts:340-366.
  6. ignore-Regeln:.gitignore / .ignore / .fdignore werden respektiert; beim rekursiven Scan wird das Muster mit dem Verzeichnis-Präfix zusammengesetzt. Siehe packages/coding-agent/src/core/skills.ts:48-66 und packages/coding-agent/src/core/skills.ts:25-46.

Entwurfsmotivation

Warum SKILL.md statt einfach alle .md-Dateien zu scannen? Weil ein Skill mehrere Dateien umfassen kann (Skripte, Sub-Dokumente); SKILL.md ist das Eintritts-Marker. Sobald SKILL.md gefunden wird, stoppt die Rekursion, und das gesamte Verzeichnis wird als Skill-Paket betrachtet. So können Skill-Autoren mehrere Dateien organisieren, ohne dass sie als mehrere unabhängige Skills aufgefasst werden.

Warum Frontmatter statt Kommentare? Weil Frontmatter YAML ist — strukturiert, parsbar, validierbar. Felder wie Name, Beschreibung und disable-model-invocation müssen sowohl für den LLM als auch für das Programm lesbar sein. validateName erzwingt, dass der Name mit dem übergeordneten Verzeichnisnamen übereinstimmt (name "x" does not match parent directory "y"), damit der Name nicht verrutscht, wenn die Skill-Datei verschoben wird.

Warum ist disableModelInvocation nötig? Manche Skills sind Prompt-Vorlagen für den Eigengebrauch und sollen nicht automatisch vom LLM getriggert werden (z. B. „beim Code-Schreiben diesen Stil verwenden"). Sie werden nur geladen, wenn der Nutzer explizit /skill:name aufruft. formatSkillsForPrompt filtert diese Skills heraus, sodass im Prompt nur die Skills auftauchen, die der LLM automatisch aufrufen darf.

Warum Deduplizierung über realpath? Weil der Nutzer über --skills ./my-skills gleichzeitig ./my-skills nach ~/.pi/agent/skills/my-skills linken könnte; zwei Scans würden dann dieselbe Datei mit unterschiedlichen Pfaden liefern. canonicalizePath löst die symbolischen Links auf und vergleicht erst danach, damit derselbe Skill nicht doppelt geladen wird.

Wichtige Dateien

formatSkillsForPrompt wickelt die Skill-Liste in XML-Tags ein und maskiert XML-Sonderzeichen:

typescript
// packages/coding-agent/src/core/skills.ts:340-366
export function formatSkillsForPrompt(skills: Skill[]): string {
	const visibleSkills = skills.filter((s) => !s.disableModelInvocation);

	if (visibleSkills.length === 0) {
		return "";
	}

	const lines = [
		"\n\nThe following skills provide specialized instructions for specific tasks.",
		"Use the read tool to load a skill's file when the task matches its description.",
		"When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.",
		"",
		"<available_skills>",
	];

	for (const skill of visibleSkills) {
		lines.push("  <skill>");
		lines.push(`    <name>${escapeXml(skill.name)}</name>`);
		lines.push(`    <description>${escapeXml(skill.description)}</description>`);
		lines.push(`    <location>${escapeXml(skill.filePath)}</location>`);
		lines.push("  </skill>");
	}
	lines.push("</available_skills>");
	return lines.join("\n");
}

loadSkillsFromDirInternal kehrt sofort zurück, sobald SKILL.md gefunden wird, und rekursiert nicht weiter:

typescript
// packages/coding-agent/src/core/skills.ts:199-226
for (const entry of entries) {
	if (entry.name !== "SKILL.md") {
		continue;
	}
	const fullPath = join(dir, entry.name);
	let isFile = entry.isFile();
	if (entry.isSymbolicLink()) {
		try {
			isFile = statSync(fullPath).isFile();
		} catch {
			continue;
		}
	}
	const relPath = toPosixPath(relative(root, fullPath));
	if (!isFile || ig.ignores(relPath)) {
		continue;
	}
	const result = loadSkillFromFile(fullPath, source);
	if (result.skill) {
		skills.push(result.skill);
	}
	diagnostics.push(...result.diagnostics);
	return { skills, diagnostics };
}

Datenfluss

Vom Disk in den Prompt:

Grenzen und Fehler

Zusammenfassung

skills.ts implementiert die Agent-Skills-Spezifikation: Laden aus drei Quellen (globaler Benutzerbereich, Projekt, explizite Pfade), Frontmatter-Validierung, Deduplizierung über realpath, XML-Formatierung und Injektion in den Prompt. Skills werden über den Systemprompt referenziert und dann per read-Werkzeug gelesen — siehe Systemprompt-Erzeugung und Werkzeuge read/bash/edit/write/grep/find/ls. Wo die Skills-Pfade zusammengebaut werden, steht in CLI-Eintritt und Dispatch.