fix: patches to align skills with open spec (#334)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: Add New Model
|
||||
name: adding-models
|
||||
description: Guide for adding new LLM models to Letta Code. Use when the user wants to add support for a new model, needs to know valid model handles, or wants to update the model configuration. Covers models.json configuration, CI test matrix, and handle validation.
|
||||
---
|
||||
|
||||
# Add New Model
|
||||
# Adding Models
|
||||
|
||||
This skill guides you through adding a new LLM model to Letta Code.
|
||||
|
||||
@@ -9,16 +9,16 @@ You are a Letta Code agent with:
|
||||
|
||||
Your goal is to guide the user through a **focused, collaborative workflow** to create or update a Skill that will be reused in the future.
|
||||
|
||||
## 1. Load the skill-creator Skill (if available)
|
||||
## 1. Load the creating-skills Skill (if available)
|
||||
|
||||
1. Inspect your memory blocks:
|
||||
- `skills` – list of available skills and their descriptions
|
||||
- `loaded_skills` – SKILL.md contents for currently loaded skills
|
||||
2. If a `skill-creator` skill is **not already loaded** in `loaded_skills`, you should **attempt to load it** using the `Skill` tool:
|
||||
2. If a `creating-skills` skill is **not already loaded** in `loaded_skills`, you should **attempt to load it** using the `Skill` tool:
|
||||
- Call the `Skill` tool with:
|
||||
- `command: "load", skills: ["skill-creator"]`
|
||||
- The environment may resolve this from either the project’s `.skills` directory or a bundled `skills/skills/skill-creator/SKILL.md` location.
|
||||
3. If loading `skill-creator` fails (for example, the tool errors or the file is missing), or if the environment does not provide it, continue using your own judgment based on these instructions.
|
||||
- `command: "load", skills: ["creating-skills"]`
|
||||
- The environment may resolve this from either the project’s `.skills` directory or a bundled `skills/skills/creating-skills/SKILL.md` location.
|
||||
3. If loading `creating-skills` fails (for example, the tool errors or the file is missing), or if the environment does not provide it, continue using your own judgment based on these instructions.
|
||||
|
||||
Do **not** load unrelated skills unless clearly relevant to the user’s request.
|
||||
|
||||
|
||||
@@ -2848,7 +2848,7 @@ export default function App({
|
||||
|
||||
const initialOutput = description
|
||||
? `Starting skill creation for: ${description}`
|
||||
: "Starting skill creation. I’ll load the skill-creator skill and ask a few questions about the skill you want to build...";
|
||||
: "Starting skill creation. I’ll load the creating-skills skill and ask a few questions about the skill you want to build...";
|
||||
|
||||
buffersRef.current.byId.set(cmdId, {
|
||||
kind: "command",
|
||||
@@ -3073,14 +3073,14 @@ ${recentCommits}
|
||||
});
|
||||
refreshDerived();
|
||||
|
||||
// Send trigger message instructing agent to load the memory-init skill
|
||||
// Send trigger message instructing agent to load the initializing-memory skill
|
||||
const initMessage = `<system-reminder>
|
||||
The user has requested memory initialization via /init.
|
||||
|
||||
## 1. Load the memory-init skill
|
||||
## 1. Load the initializing-memory skill
|
||||
|
||||
First, check your \`loaded_skills\` memory block. If the \`memory-init\` skill is not already loaded:
|
||||
1. Use the \`Skill\` tool with \`command: "load", skills: ["memory-init"]\`
|
||||
First, check your \`loaded_skills\` memory block. If the \`initializing-memory\` skill is not already loaded:
|
||||
1. Use the \`Skill\` tool with \`command: "load", skills: ["initializing-memory"]\`
|
||||
2. The skill contains comprehensive instructions for memory initialization
|
||||
|
||||
If the skill fails to load, proceed with your best judgment based on these guidelines:
|
||||
@@ -3091,7 +3091,7 @@ If the skill fails to load, proceed with your best judgment based on these guide
|
||||
|
||||
## 2. Follow the loaded skill instructions
|
||||
|
||||
Once loaded, follow the instructions in the \`memory-init\` skill to complete the initialization.
|
||||
Once loaded, follow the instructions in the \`initializing-memory\` skill to complete the initialization.
|
||||
${gitContext}
|
||||
</system-reminder>`;
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: skill-creator
|
||||
name: creating-skills
|
||||
description: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Letta Code's capabilities with specialized knowledge, workflows, or tool integrations.
|
||||
---
|
||||
|
||||
# Skill Creator
|
||||
# Creating Skills
|
||||
|
||||
This skill provides guidance for creating effective skills in Letta Code.
|
||||
This skill provides guidance for creating effective skills in Letta Code. For the complete official specification, see [agentskills.io](https://agentskills.io/specification).
|
||||
|
||||
## About Skills
|
||||
|
||||
@@ -48,10 +48,10 @@ Think of the Letta Code agent as exploring a path: a narrow bridge with cliffs n
|
||||
Every skill consists of a required SKILL.md file and optional bundled resources:
|
||||
|
||||
```
|
||||
skill-name/
|
||||
processing-pdfs/
|
||||
├── SKILL.md (required)
|
||||
│ ├── YAML frontmatter metadata (required)
|
||||
│ │ ├── name: (required)
|
||||
│ │ ├── name: (required, must match directory name)
|
||||
│ │ └── description: (required)
|
||||
│ └── Markdown instructions (required)
|
||||
└── Bundled Resources (optional)
|
||||
@@ -148,9 +148,9 @@ The Letta Code agent loads FORMS.md, REFERENCE.md, or EXAMPLES.md only when need
|
||||
For Skills with multiple domains, organize content by domain to avoid loading irrelevant context:
|
||||
|
||||
```
|
||||
bigquery-skill/
|
||||
querying-bigquery/
|
||||
├── SKILL.md (overview and navigation)
|
||||
└── reference/
|
||||
└── references/
|
||||
├── finance.md (revenue, billing metrics)
|
||||
├── sales.md (opportunities, pipeline)
|
||||
├── product.md (API usage, features)
|
||||
@@ -162,7 +162,7 @@ When a user asks about sales metrics, the Letta Code agent only reads sales.md.
|
||||
Similarly, for skills supporting multiple frameworks or variants, organize by variant:
|
||||
|
||||
```
|
||||
cloud-deploy/
|
||||
deploying-to-cloud/
|
||||
├── SKILL.md (workflow + provider selection)
|
||||
└── references/
|
||||
├── aws.md (AWS deployment patterns)
|
||||
@@ -217,9 +217,9 @@ Skip this step only when the skill's usage patterns are already clearly understo
|
||||
|
||||
To create an effective skill, clearly understand concrete examples of how the skill will be used. This understanding can come from either direct user examples or generated examples that are validated with user feedback.
|
||||
|
||||
For example, when building an image-editor skill, relevant questions include:
|
||||
For example, when building an `editing-images` skill, relevant questions include:
|
||||
|
||||
- "What functionality should the image-editor skill support? Editing, rotating, anything else?"
|
||||
- "What functionality should the editing-images skill support? Editing, rotating, anything else?"
|
||||
- "Can you give some examples of how this skill would be used?"
|
||||
- "I can imagine users asking for things like 'Remove the red-eye from this image' or 'Rotate this image'. Are there other ways you imagine this skill being used?"
|
||||
- "What would a user say that should trigger this skill?"
|
||||
@@ -235,17 +235,17 @@ To turn concrete examples into an effective skill, analyze each example by:
|
||||
1. Considering how to execute on the example from scratch
|
||||
2. Identifying what scripts, references, and assets would be helpful when executing these workflows repeatedly
|
||||
|
||||
Example: When building a `pdf-editor` skill to handle queries like "Help me rotate this PDF," the analysis shows:
|
||||
Example: When building an `editing-pdfs` skill to handle queries like "Help me rotate this PDF," the analysis shows:
|
||||
|
||||
1. Rotating a PDF requires re-writing the same code each time
|
||||
2. A `scripts/rotate-pdf.ts` script would be helpful to store in the skill
|
||||
|
||||
Example: When designing a `frontend-webapp-builder` skill for queries like "Build me a todo app" or "Build me a dashboard to track my steps," the analysis shows:
|
||||
Example: When designing a `building-frontend-apps` skill for queries like "Build me a todo app" or "Build me a dashboard to track my steps," the analysis shows:
|
||||
|
||||
1. Writing a frontend webapp requires the same boilerplate HTML/React each time
|
||||
2. An `assets/hello-world/` template containing the boilerplate HTML/React project files would be helpful to store in the skill
|
||||
|
||||
Example: When building a `big-query` skill to handle queries like "How many users have logged in today?" the analysis shows:
|
||||
Example: When building a `querying-bigquery` skill to handle queries like "How many users have logged in today?" the analysis shows:
|
||||
|
||||
1. Querying BigQuery requires re-discovering the table schemas and relationships each time
|
||||
2. A `references/schema.md` file documenting the table schemas would be helpful to store in the skill
|
||||
@@ -304,13 +304,27 @@ Any example files and directories not needed for the skill should be deleted. Th
|
||||
|
||||
Write the YAML frontmatter with `name` and `description`:
|
||||
|
||||
- `name`: The skill name
|
||||
- `description`: This is the primary triggering mechanism for your skill, and helps the Letta Code agent understand when to use the skill.
|
||||
- Include both what the Skill does and specific triggers/contexts for when to use it.
|
||||
- Include all "when to use" information here - Not in the body. The body is only loaded after triggering, so "When to Use This Skill" sections in the body are not helpful to the Letta Code agent.
|
||||
- Example description for a `docx` skill: "Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. Use when the Letta Code agent needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks"
|
||||
**`name`** (required):
|
||||
- Use gerund form: `processing-pdfs`, `analyzing-data`, `creating-reports` (not `pdf-processor`)
|
||||
- Lowercase letters, numbers, and hyphens only
|
||||
- Must match the directory name exactly
|
||||
- Max 64 characters
|
||||
|
||||
Do not include any other fields in YAML frontmatter.
|
||||
**`description`** (required):
|
||||
- Write in third person: "Processes PDF files..." (not "I help process..." or "You can use this to...")
|
||||
- Include both what the skill does AND when to use it
|
||||
- Include trigger keywords that help the agent identify relevant tasks
|
||||
- Max 1024 characters
|
||||
|
||||
Example:
|
||||
```yaml
|
||||
---
|
||||
name: processing-pdfs
|
||||
description: Extracts text and tables from PDF files, fills forms, and merges documents. Use when working with PDF files or when the user mentions PDFs, forms, or document extraction.
|
||||
---
|
||||
```
|
||||
|
||||
**Note:** The spec allows optional fields (`license`, `compatibility`, `metadata`, `allowed-tools`) but most skills don't need them. See [agentskills.io/specification](https://agentskills.io/specification) for details.
|
||||
|
||||
##### Body
|
||||
|
||||
@@ -10,20 +10,22 @@
|
||||
*/
|
||||
|
||||
import { existsSync, readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { basename, join } from "node:path";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
|
||||
interface ValidationResult {
|
||||
valid: boolean;
|
||||
message: string;
|
||||
warnings?: string[];
|
||||
}
|
||||
|
||||
const ALLOWED_PROPERTIES = new Set([
|
||||
"name",
|
||||
"description",
|
||||
"license",
|
||||
"allowed-tools",
|
||||
"compatibility",
|
||||
"metadata",
|
||||
"allowed-tools",
|
||||
]);
|
||||
|
||||
export function validateSkill(skillPath: string): ValidationResult {
|
||||
@@ -63,15 +65,15 @@ export function validateSkill(skillPath: string): ValidationResult {
|
||||
};
|
||||
}
|
||||
|
||||
// Check for unexpected properties
|
||||
// Check for unexpected properties (warn but don't fail for forward-compatibility)
|
||||
const warnings: string[] = [];
|
||||
const unexpectedKeys = Object.keys(frontmatter).filter(
|
||||
(key) => !ALLOWED_PROPERTIES.has(key),
|
||||
);
|
||||
if (unexpectedKeys.length > 0) {
|
||||
return {
|
||||
valid: false,
|
||||
message: `Unexpected key(s) in SKILL.md frontmatter: ${unexpectedKeys.sort().join(", ")}. Allowed properties are: ${[...ALLOWED_PROPERTIES].sort().join(", ")}`,
|
||||
};
|
||||
warnings.push(
|
||||
`Unknown frontmatter key(s): ${unexpectedKeys.sort().join(", ")}. Known properties are: ${[...ALLOWED_PROPERTIES].sort().join(", ")}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Check required fields
|
||||
@@ -116,6 +118,14 @@ export function validateSkill(skillPath: string): ValidationResult {
|
||||
message: `Name is too long (${trimmedName.length} characters). Maximum is 64 characters.`,
|
||||
};
|
||||
}
|
||||
|
||||
// Check name matches directory name (warn if not)
|
||||
const dirName = basename(skillPath);
|
||||
if (trimmedName !== dirName) {
|
||||
warnings.push(
|
||||
`Name '${trimmedName}' doesn't match directory name '${dirName}'. For portability, these should match.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate description
|
||||
@@ -144,7 +154,11 @@ export function validateSkill(skillPath: string): ValidationResult {
|
||||
}
|
||||
}
|
||||
|
||||
return { valid: true, message: "Skill is valid!" };
|
||||
return {
|
||||
valid: true,
|
||||
message: "Skill is valid!",
|
||||
warnings: warnings.length > 0 ? warnings : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
@@ -155,7 +169,12 @@ if (require.main === module) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { valid, message } = validateSkill(args[0] as string);
|
||||
const { valid, message, warnings } = validateSkill(args[0] as string);
|
||||
console.log(message);
|
||||
if (warnings && warnings.length > 0) {
|
||||
for (const warning of warnings) {
|
||||
console.warn(`Warning: ${warning}`);
|
||||
}
|
||||
}
|
||||
process.exit(valid ? 0 : 1);
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: Memory Initialization
|
||||
name: initializing-memory
|
||||
description: Comprehensive guide for initializing or reorganizing agent memory. Load this skill when running /init, when the user asks you to set up your memory, or when you need guidance on creating effective memory blocks.
|
||||
---
|
||||
|
||||
# Memory Initialization Guide
|
||||
# Initializing Memory
|
||||
|
||||
The user has requested that you initialize or reorganize your memory state. You have access to the `memory` tool which allows you to create, edit, and manage memory blocks.
|
||||
|
||||
@@ -8,9 +8,9 @@ import { join } from "node:path";
|
||||
import {
|
||||
initSkill,
|
||||
titleCaseSkillName,
|
||||
} from "../../skills/builtin/skill-creator/scripts/init-skill";
|
||||
import { packageSkill } from "../../skills/builtin/skill-creator/scripts/package-skill";
|
||||
import { validateSkill } from "../../skills/builtin/skill-creator/scripts/validate-skill";
|
||||
} from "../../skills/builtin/creating-skills/scripts/init-skill";
|
||||
import { packageSkill } from "../../skills/builtin/creating-skills/scripts/package-skill";
|
||||
import { validateSkill } from "../../skills/builtin/creating-skills/scripts/validate-skill";
|
||||
|
||||
const TEST_DIR = join(import.meta.dir, ".test-skill-creator");
|
||||
|
||||
@@ -167,25 +167,72 @@ description: A skill with <invalid> description
|
||||
expect(result.message).toContain("cannot contain angle brackets");
|
||||
});
|
||||
|
||||
test("fails when unexpected frontmatter keys are present", () => {
|
||||
const skillDir = join(TEST_DIR, "unexpected-keys");
|
||||
test("warns but passes when unknown frontmatter keys are present", () => {
|
||||
const skillDir = join(TEST_DIR, "unknown-keys");
|
||||
mkdirSync(skillDir);
|
||||
writeFileSync(
|
||||
join(skillDir, "SKILL.md"),
|
||||
`---
|
||||
name: unexpected-keys
|
||||
description: A skill with unexpected keys
|
||||
name: unknown-keys
|
||||
description: A skill with unknown keys
|
||||
author: Someone
|
||||
version: 1.0.0
|
||||
---
|
||||
|
||||
# Unexpected Keys
|
||||
# Unknown Keys
|
||||
`,
|
||||
);
|
||||
|
||||
const result = validateSkill(skillDir);
|
||||
expect(result.valid).toBe(false);
|
||||
expect(result.message).toContain("Unexpected key(s)");
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.warnings).toBeDefined();
|
||||
expect(result.warnings?.length).toBeGreaterThan(0);
|
||||
expect(result.warnings?.[0]).toContain("Unknown frontmatter key(s)");
|
||||
});
|
||||
|
||||
test("accepts all official spec frontmatter fields without warnings", () => {
|
||||
const skillDir = join(TEST_DIR, "full-spec");
|
||||
mkdirSync(skillDir);
|
||||
writeFileSync(
|
||||
join(skillDir, "SKILL.md"),
|
||||
`---
|
||||
name: full-spec
|
||||
description: A skill with all official spec fields
|
||||
license: MIT
|
||||
compatibility: Requires Node.js 18+
|
||||
metadata:
|
||||
author: test
|
||||
version: "1.0"
|
||||
allowed-tools: Bash Read Write
|
||||
---
|
||||
|
||||
# Full Spec Skill
|
||||
`,
|
||||
);
|
||||
|
||||
const result = validateSkill(skillDir);
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.warnings).toBeUndefined();
|
||||
});
|
||||
|
||||
test("warns when name doesn't match directory name", () => {
|
||||
const skillDir = join(TEST_DIR, "my-directory");
|
||||
mkdirSync(skillDir);
|
||||
writeFileSync(
|
||||
join(skillDir, "SKILL.md"),
|
||||
`---
|
||||
name: different-name
|
||||
description: Name doesn't match directory
|
||||
---
|
||||
|
||||
# Mismatched Name
|
||||
`,
|
||||
);
|
||||
|
||||
const result = validateSkill(skillDir);
|
||||
expect(result.valid).toBe(true);
|
||||
expect(result.warnings).toBeDefined();
|
||||
expect(result.warnings?.[0]).toContain("doesn't match directory name");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user