Building a skillpack
Build the smallest complete skillpack — hello-world — from scratch. A deterministic skill, an agentic skill, a custom tool, and two subagents, end to end.
This guide builds a skillpack from scratch — the smallest one that still uses every core piece. By the end you'll know what a skillpack is, the two kinds of skill, how a skill declares its contract, and how skills call tools and each other. Read skill-yaml-reference for the field-by-field manifest spec; this guide is the shape and the why.
The example is hello-world: give it a name and it builds a little greeting card. No external APIs and no media, so it's cheap to run and easy to read.
The finished pack is public at
github.com/PurasAI/hello-world —
clone it, or scaffold it straight into a fresh directory with
puras init --template hello-world. Starting your own pack instead? Default
puras init (or
github.com/PurasAI/skillpack-template)
gives you the blank version of the same layout.
What a skillpack is
A skillpack is a directory of skills. Each top-level folder that contains a
skill.yaml is a skill — discovered by scanning the bundle root. There's no
skills/ wrapper and no root manifest: a skill's name is its folder name.
hello-world bundles two skills, one of each kind:
| Skill | Kind | Entrypoint | What it shows |
|---|---|---|---|
formatter | deterministic | scripts/format.py:run | a plain Python skill — no LLM |
greeter | agentic | SKILL.md | a custom tool + two subagents, driven by a model |
The two compose: greeter calls formatter to lay out its card. A skillpack
is a pipeline of related skills, not a junk drawer.
hello-world/ # the bundle root
├── formatter/ # each top-level dir with a skill.yaml is a skill
│ ├── skill.yaml # manifest: schemas (deterministic skill)
│ └── scripts/format.py # run(name, shout, poem) -> {card}
└── greeter/
├── skill.yaml # manifest: schemas, model, the `emphasize` tool
├── SKILL.md # the agent's system prompt (the entrypoint)
├── tools/emphasize.py # custom tool — run(text) -> {loud}
└── references/poet.md # a subagent's prompt, read on demand
The entrypoint suffix decides the kind: a Python file.py:func entrypoint is
deterministic; a markdown SKILL.md entrypoint (plus a text_model) is agentic.
Everything else is the same skill.yaml contract.
Part 1 — A deterministic skill (formatter)
The simplest skill is a Python function with a typed contract. formatter takes
a name (plus an optional shout and couplet) and returns a text card. Its
skill.yaml is just the contract — no model, no SKILL.md:
title: Greeting Formatter
description: Lays out a small greeting card from a name.
entrypoint: scripts/format.py:run
input_schema:
type: object
required: [name]
properties:
name: { type: string, maxLength: 60 }
shout: { type: string, maxLength: 120 }
poem: { type: text, maxLength: 400 }
output_schema:
type: object
properties:
card: { type: text, description: The assembled greeting card. }
The entrypoint scripts/format.py:run points at a function. The worker calls it
as run(**inputs) and validates the returned dict against output_schema:
def run(name: str, shout: str = "", poem: str = "") -> dict:
headline = (shout or f"Hello, {name}!").strip()
# ...frame the headline + couplet in a text box...
return {"card": "\n".join(rows)}
That's a whole skill. A deterministic skill is code with a schema — use one
whenever the work is mechanical and needs no judgment. Pure stdlib here, so
there's no requirements.txt; add one next to the script if you need packages.
Part 2 — An agentic skill (greeter)
greeter does need judgment (writing a greeting), so it's agentic: its
entrypoint is SKILL.md and it declares a model. Its skill.yaml adds a
text_model, a richer input_schema, and a custom tool:
title: Greeter
entrypoint: SKILL.md
text_model: claude/haiku-4-5
input_schema:
type: object
required: [name]
properties:
name: { type: string, maxLength: 60 }
style: { type: string, enum: [friendly, formal, playful], default: friendly }
output_schema:
type: object
properties:
card: { type: text }
shout: { type: string }
poem: { type: text }
tools:
- name: emphasize
description: Make a short string LOUD.
entrypoint: tools/emphasize.py:run
input_schema:
type: object
required: [text]
properties:
text: { type: string }
output_schema:
type: object
properties:
loud: { type: string }
A few things to notice:
-
The model is set high-level, in
text_model:— not hard-coded in a prompt.greeteris a thin orchestrator, so it runsclaude/haiku-4-5; spend frontier models where real judgment lives. (See skill-yaml-reference for media model slots likeimage_model:too.) -
The schema dialect drives the UI.
style'senumbecomes a dropdown in the playground;type: textbecomes a multi-line field. Richer types (image,video,color) render upload widgets and pickers — a well-typed schema means the playground form "just works" with zero UI code. -
A custom tool is a deterministic Python function, declared under
tools:with its owninput_schema/output_schemaand afile.py:funcentrypoint — exactly like a deterministic skill, but callable by this agent:python# tools/emphasize.py def run(text: str) -> dict: return {"loud": text.upper().rstrip("!?. ") + "!!!"}
SKILL.md is the brain
skill.yaml is the contract; SKILL.md is the system prompt the agent runs.
greeter's walks through three small moves, then returns. It uses the tool and
two subagents rather than doing the work itself:
## Step 1 — Shout the name (custom tool)
emphasize({ "text": <name> }) → keep `loud` as your `shout`
## Step 2 — Ask the poet (a `.md` subagent)
run_subagent({ "target": "references/poet.md",
"inputs": { "name": <name>, "style": <style> } }) → keep `poem`
## Step 3 — Assemble the card (a sibling skill as a subagent)
run_subagent({ "target": "formatter",
"inputs": { "name": <name>, "shout": <shout>, "poem": <poem> } })
## Step 4 — Return
set_output({ "card": ..., "shout": ..., "poem": ... })
Subagents — two shapes
run_subagent hands a self-contained stage to a
fresh agent (its own context, linked to this run). greeter uses both forms a
skillpack offers, resolved inside this same bundle:
- A
.mdprompt —target: "references/poet.md"runs that bundle file as the system prompt of an isolated subagent. Keep a stage's prompt inreferences/and point at it. - A sibling skill by name —
target: "formatter"runs another skill in this skillpack. (Cross-skillpack refs useskillpack/skillorworkspace/skillpack/skill; an inlineprompt:runs a one-off with no file.)
inputs is passed to the child verbatim — it reads them as its own inputs. This
is how a skillpack becomes a pipeline: each skill stays small and one agent
stitches them together.
Finishing a run — set_output
Every skill ends by calling set_output once, with exactly the fields in its
output_schema. For agentic skills it's an auto-injected tool; for deterministic
ones it's the returned dict. The platform validates the result against the schema
and prunes anything extra — so output_schema is the real boundary of what a
skill exposes.
Seed the playground with examples
Each skill.yaml carries an examples block — real inputs the playground loads
as one-click starting points. greeter ships a couple:
examples:
- title: Greet Ada
inputs: { name: Ada, style: playful }
- title: Greet the team (formal)
inputs: { name: the Puras team, style: formal }
Good examples are how a new user understands a skill in five seconds — invest in them, and make sure they mirror the real input schema.
Deploy and run it
A skillpack is deployed as a whole bundle, versioned per skillpack. Scaffold a new one and push it with the CLI:
pip install puras
puras init # scaffold a blank starter + puras.yaml
# (--template hello-world scaffolds this guide's pack)
puras deploy # zip the dir + push a deployment
puras run greeter --input name=Ada --input style=playful
puras.yaml is the pack manifest: it binds the directory to its remote
skillpack (skillpack_id, slug) and carries the pack page's own title,
description, and optional marketing block — see skill-yaml-reference
for the full shape.
From here: cli-reference covers deploy / run / pull from your terminal,
mcp-tools covers the same from an agent (fork_skillpack, pull_skillpack)
plus running deployed skills, and sdk-client-reference covers calling them
from application code. To go deeper on any field, the manifest spec is
skill-yaml-reference; the agent's runtime tools are agent-tools-reference;
the in-skill Python runtime is sdk-runtime-reference.
Once hello-world makes sense, the same four pieces — deterministic skills, an agent in the middle, custom tools, and subagents — scale up to any real pipeline.