Writing agentic skills (SKILL.md)
How to write a SKILL.md prompt — what to put in the body, what the runtime adds for you, and what to leave out.
An agentic skill has entrypoint: SKILL.md in its skill.yaml. The
SKILL.md file is the system prompt the LLM runs against. You describe the
domain knowledge — workflow, criteria, edge cases, tone — and the runtime
handles everything else: tools, input attachments, output validation,
billing, retries, telemetry.
This guide is about the body of SKILL.md. For the manifest fields (input/output schemas, model, examples), see skill-yaml-reference.
The shape of a SKILL.md
---
name: my-skill # optional — derived from folder name otherwise
description: One-line summary # optional — duplicated from skill.yaml
---
You are <role>. Given <inputs>, you produce <outputs>.
## Workflow
1. ...
2. ...
## Criteria
- ...
## Edge cases
- ...
The frontmatter is optional in Puras — Anthropic's Agent Skills spec asks
for it. Most authors leave it empty and let skill.yaml carry the metadata.
What to put in the body
Domain knowledge the agent needs to do the job well. Workflow steps, quality criteria, examples of good vs. bad output, tone, edge cases. Things that aren't obvious from the schema and aren't built into the LLM.
Good body content:
- Workflow — ordered steps, decision points, what to do when.
- Criteria — what makes the output good. Specific, not vague.
- Tone & style — register, formality, length expectations.
- Edge cases — input you should reject, ambiguous cases, failure modes.
- Worked examples — short illustrations of good and bad output.
What to leave out
The runtime adds these for you. Repeating them in the body wastes context and creates drift when the runtime changes.
Don't describe how to deliver the output. Don't write "call the
set_output tool", "return JSON in a code fence", "respond with this exact
structure." Your output_schema is auto-attached to a set_output tool the
agent calls when it's done — the runtime instructs the agent on that.
Don't redocument the input fields. Their names, types, descriptions, and
constraints (from input_schema) are auto-appended to the system prompt as
an ## Inputs section. If you also write your own ## Inputs block, you'll
duplicate the runtime's. Reference fields by name (e.g. "use the headline
input if provided") — the runtime gives the agent the full schema for free.
Don't reference upstream providers. Never name fal endpoint ids, OpenAI
model ids, OpenRouter routing, or specific provider keys. Use the Puras
abstractions: media({ model: "openai/gpt-image-2-edit", ... }) not "call
the fal endpoint at …". Same for LLMs — talk about capability, not provider.
Don't reference billing internals. Don't mention margins, micros, wallet math. Authors and end-users see final prices through Puras UI; the agent doesn't need that context.
Don't redescribe built-in tools. bash, media, web_search,
web_fetch, file_read, drive_url — these are auto-listed and the
agent's tool-use UI tells it how to call them. Mention a tool only when its
use is non-obvious for your workflow ("after each render, call file_read
on the returned drive_path and verify the layout").
How structured output works
You declare the output shape in skill.yaml:
output_schema:
type: object
required: [score, summary]
properties:
score: { type: number, minimum: 1, maximum: 10 }
summary: { type: text, maxLength: 800 }
The runtime:
- Injects a
set_outputtool whoseinput_schemais youroutput_schema. - Appends a short instruction telling the agent: "When done, call
set_outputonce with your final result." - Validates the call against the schema before returning success.
In your SKILL.md body, just describe what each field should contain semantically — the schema carries the types and constraints. Example:
Bad (redundant + brittle):
When you finish, call
set_outputwith{"score": …, "summary": "…"}.Good:
Give a score from 1–10 based on the criteria above. The summary should be 1–3 sentences explaining the score.
Inputs the agent sees
When the job runs, the agent receives:
- A system prompt = your SKILL.md body + an auto-appended
## Inputssection (derived frominput_schema) + theset_outputinstruction. - A first user message containing the input field values. File-typed
fields (
type: image | video | audio | file) are attached as inline images/documents the agent can see directly. - A tools list = built-in tools (bash, media, web_search, …) plus any
user tools you declared in
skill.yamlplus the auto-generatedset_output.
The agent runs in a loop: read → think → call tools → read tool results →
think → … → call set_output (or emit plain text if no output_schema).
Examples
examples in skill.yaml (see reference) seed
the playground form so users can hit Run immediately. They're not test
fixtures — they're product UX. Pick 2–5 inputs that show the range of what
the skill is good at.
A tight checklist
Before shipping a SKILL.md, scan it for:
- Does the body describe what to do, not how to deliver?
- Have I removed references to
set_output, JSON formats, code fences? - Have I removed my own
## Inputsredescription? (Runtime auto-injects it.) - No upstream provider names (fal endpoints, OpenRouter, etc.)?
- No billing internals (margins, micros)?
- Workflow + criteria + edge cases are concrete, not vague?
- Built-in tools mentioned only where their use is non-obvious?
Keep it under ~400 lines. Beyond that, break detail into reference files
under references/ and let the agent read them on demand
(cat "$PURAS_DEPLOYMENT_ROOT"/skills/<name>/references/<file>.md).