skill.yaml reference
Every field in a skill's manifest — entrypoint, model, input/output schemas (Puras dialect), examples, tools.
Every skill is a directory under skills/ in your project bundle, and every
directory has one skill.yaml that declares how it runs.
skills/
└── my-skill/
├── skill.yaml # this file
├── SKILL.md # agentic only — the system prompt
├── scripts/main.py # deterministic only — the function
└── references/ # optional — reference docs an agent can read
Top-level fields
title: Human-friendly Name # optional — display label in cards / playground / SEO
description: One-line summary # required — what this skill does
entrypoint: SKILL.md # required — agentic loop (system prompt = file)
# OR "scripts/main.py:run" — deterministic function
model: claude/sonnet-4-6 # optional, agentic only — see docs/models
disable_bash: false # optional, agentic only — strip the bash tool
input_schema: { Puras dialect } # required — validated before run, drives playground form
output_schema: { Puras dialect } # required — validated after run, drives result rendering
examples: # optional — playground seed scenarios (0..N)
- title: short label # optional — falls back to "Example N"
description: 1-line note # optional — tooltip in the playground chip
inputs: { ... } # required — fully-formed input matching input_schema
tools: # optional, agentic only — user-defined Python tools
- name: my_tool
description: short
entrypoint: tools/foo.py:run
input_schema: { ... }
output_schema: { ... }
The skill name is the directory name. Use [a-z0-9_-]+ slug style.
Names duplicate across the bundle are rejected at parse time.
entrypoint
Two shapes, distinguished by file suffix:
| Suffix | Kind | What the worker does |
|---|---|---|
.md | agentic | Runs an LLM loop with the file's text as system prompt. |
.py:func | deterministic | Imports func from the file and calls it with the inputs dict. |
The path is relative to the skill's own directory. For deterministic skills,
group helper modules under scripts/ (convention, not enforced).
model
Public slug in family/variant shape — claude/sonnet-4-6, gpt/5,
gemini/2.5-pro. See models for the catalog. Routing
between Anthropic / OpenRouter / etc. is resolved internally; never put a
provider prefix in your skill.
examples
Each example is a complete inputs payload (must match input_schema),
plus an optional title and description. The playground:
- Seeds the form with
examples[0].inputson mount. - Renders the rest as clickable chips above the form.
Pick examples that cover the range of useful inputs — they double as discoverability ("Try it") and as a smoke baseline you can test against.
Puras schema dialect
input_schema and output_schema use a small dialect that adds end-user
types on top of standard JSON Schema. Standard types pass through unchanged
and are validated with jsonschema Draft 2020-12; Puras types are
translated server-side before validation.
Type table
Puras type | UI widget | Validates as |
|---|---|---|
string | Single-line input | JSON Schema string |
number, integer | Number input | JSON Schema number / integer |
boolean | Toggle (switch) | JSON Schema boolean |
array | JSON textarea | JSON Schema array |
array of file type | Multi-file uploader | JSON Schema array of file shape |
object | JSON textarea | JSON Schema object |
null | (rare) | JSON Schema null |
image | Drop-zone + URL field + preview | bare URL string or {drive_path / url / data} object |
video | Same as image, video preview | same |
audio | Same, audio player | same |
file | Generic file picker | same |
text | Multi-line textarea | JSON Schema string |
color | Color picker + hex input | string with hex pattern |
Standard JSON Schema fields you can still use
description, default, title, enum, minimum, maximum,
minLength, maxLength, minItems, maxItems, additionalProperties,
required, properties, items. Everything else from Draft 2020-12 is
honored after dialect translation.
Why a custom dialect
Puras targets end-user playgrounds and structured I/O over a billed
backend. JSON Schema doesn't have first-class concepts for "image upload",
"hex color", or "multi-line text" — the spec leaves those to vendor
extensions. Rather than scattering x-puras keys, contentMediaType
strings, and polymorphic oneOf blocks across every field, the dialect
encodes intent in the type. A single property:
image:
type: image
description: Kullanıcının fotoğrafı.
Replaces what used to be a 12-line type: object with oneOf, x-puras
hints, accept lists, and a default. The runtime knows what to do with it,
the playground picks the right widget, and authors stop carrying schema
complexity that adds no signal.
File inputs end-to-end
A field declared type: image (or video/audio/file) accepts:
# Bare strings (any of these):
image: "https://example.com/photo.jpg"
image: "uploads/photo.jpg" # drive path
image: "data:image/png;base64,iVBORw0KGgo…" # data URI
# Or an explicit object (see docs/inputs-and-drive):
image: { url: "https://…" }
image: { drive_path: "uploads/photo.jpg" }
image: { data: "data:image/png;base64,…" }
Inside a deterministic skill, puras.load_bytes(inputs["image"]) and
puras.load_path(inputs["image"]) normalize all of these to bytes or a
filesystem path — see inputs-and-drive.
In an agentic skill, the runtime auto-attaches file inputs as inline image or document blocks the LLM can see, alongside the JSON of all other non-file fields.
Output schema and structured returns
For agentic skills, output_schema powers a set_output tool the agent
calls when it's done — its input_schema is your translated output schema.
Authors should not instruct the agent to call set_output in SKILL.md;
the runtime adds that. See writing-skills.
For deterministic skills, the function's return value is validated against
output_schema and surfaced as the job result.
A whole-output media type works exactly like a file input — declare
type: image and return a bare URL string:
# skill.yaml
output_schema:
type: image
# scripts/main.py
def run(inputs):
return media.run("openai/gpt-image-2-edit", { ... })["output_url"]
The playground sees type: image on the output and renders the URL as an
inline <img> automatically.
Tools (agentic only)
User-defined Python helpers the agent can call from inside the loop:
tools:
- name: search_inventory
description: Look up SKU stock by warehouse.
entrypoint: tools/inventory.py:run
input_schema:
type: object
required: [sku]
properties:
sku: { type: string }
output_schema:
type: object
properties:
in_stock: { type: integer, minimum: 0 }
Same schema dialect as input_schema / output_schema. The agent sees the
tool with its translated schema; the worker validates both the call's input
and the function's output before feeding the result back. See
agent-tools-reference for the full surface.
What changed from the old format
If you have skills written before the dialect existed:
| Was | Now |
|---|---|
type: object + oneOf: [drive_path/url/base64] + x-puras: { widget: image, … } | type: image |
type: string + contentMediaType: image/* | type: image |
type: string + x-puras: { widget: long_text } | type: text |
type: string + x-puras: { widget: color } | type: color |
default: per property (used to seed playground) | examples: [{ inputs: { … } }] at top level |
x-puras: { help: "…" } | description: "…" (rendered the same way) |
x-puras: { placeholder: "…" } | drop it |
x-puras: { accept, max_size_mb, upload } | drop — runtime defaults handle it |
x-puras is no longer read by anything. Strip it.