puras

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

markdown
---
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:

yaml
output_schema:
  type: object
  required: [score, summary]
  properties:
    score: { type: number, minimum: 1, maximum: 10 }
    summary: { type: text, maxLength: 800 }

The runtime:

  1. Injects a set_output tool whose input_schema is your output_schema.
  2. Appends a short instruction telling the agent: "When done, call set_output once with your final result."
  3. 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_output with {"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 ## Inputs section (derived from input_schema) + the set_output instruction.
  • 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.yaml plus the auto-generated set_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 ## Inputs redescription? (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).