Publish a skill
This walks through publishing a real skill — a pricing-policy skill that gives an agent the company refund and pricing rules, declares one permission and one secret, and ships a couple of reference documents the model can pull on demand.
Roughly 15 minutes if you have the policy text ready.
1. Decide the skill's shape#
Before any API calls, pick:
- Slug. URL-safe, kebab-case, unique across the workspace. The slug is forever — versions move, the slug doesn't.
- Visibility.
private(only the owning workspace can see it) orpublic(every tenant on the deployment can see it). Defaultprivate. Public is for shared skills you intend the whole platform to consume. - Triggers. Short words or phrases the LLM can use to recognise the skill applies. Used at search time and shown in the resolved manifest.
- Permissions and secrets. What runtime grants does the skill require? Each declared item turns into a pending-grants gate at bind time.
2. Lay out the bundle#
A bundle is a .tar.gz with this shape:
1 2 3 4 | |
SKILL.md is the only required file. Its YAML frontmatter is the manifest; everything below the closing --- is the markdown body the LLM reads when it calls skills.view. The references/ directory holds additional files the LLM can pull on demand via skills.view(slug, path="references/<file>").
Lay out the directory:
1 2 3 4 5 | |
SKILL.md carries the YAML frontmatter (manifest) plus a markdown body:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | |
The references/ files are plain markdown — the policy text and pricing table the LLM will pull on demand. Then bundle:
1 | |
3. Register the slug#
1 2 3 4 5 6 7 8 | |
1 2 3 4 5 6 7 8 9 10 | |
1 2 3 4 5 6 7 8 9 10 11 12 | |
A second publish with a different version will not need to repeat this step.
4. Publish the first version#
1 2 3 | |
What happens on the server:
- The validator opens the tarball, reads
SKILL.md, parses the YAML frontmatter, checks the schema. - The slug in the manifest is compared to the URL slug. Mismatch fails validation.
- The
versionin the manifest is checked againstlist_published_semvers(skill_id)— it must be strictly greater than every previously published version.0.0.5after0.1.0is rejected. - The references directory is checked for path-traversal (
../, absolute paths) and binary content. - A SHA-256 hash of the bundle bytes is computed. If a bundle with the same hash exists for this skill, the put is skipped and the existing storage URI is reused.
- The version row is written with
status: "published"and the parsed manifest stored as JSON text. - Best-effort: the manifest is indexed into the ScaiMatrix
__scaiskillscollection for semantic search.
5. Publish a follow-up version#
Iteration: update the body, bump the version, re-tar, re-upload.
1 2 3 4 5 6 7 8 9 | |
Old bindings keep their pinned or resolved version. New bindings against latest or ^0.1 will pick up 0.2.0.
6. Yank if a release is wrong#
A yank doesn't break existing bindings — they keep working on the pinned version — but stops new bindings from picking the yanked semver, and latest/^x.y resolution skips it.
1 2 | |
After yanking, publish a fixed 0.2.1 rather than republishing under the yanked semver — the version monotonicity check enforces it.
7. Confirm via the skill detail endpoint#
1 2 | |
You'll see the slug, the owner workspace, visibility, the description, and the version list with statuses and content hashes. This is the same view the admin UI's catalog page renders.
Done#
You have a versioned skill, a published bundle, and a path to iterate without breaking existing consumers. Next: bind it with grants — the manifest declared one permission and one required secret, so the first binding will sit in pending_grants until an admin clears both.