Platform
ScaiWave ScaiGrid ScaiCore ScaiBot ScaiDrive ScaiKey Models Tools & Services
Solutions
Organisations Developers Internet Service Providers Managed Service Providers AI-in-a-Box
Resources
Support Documentation Blog Downloads
Company
About Research Careers Investment Opportunities Contact
Log in

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) or public (every tenant on the deployment can see it). Default private. 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:

text
1
2
3
4
SKILL.md
references/
  refund-policy.md
  pricing-tiers.md

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:

text
1
2
3
4
5
pricing-policy/
├── SKILL.md
└── references/
    ├── refund-policy.md
    └── pricing-tiers.md

SKILL.md carries the YAML frontmatter (manifest) plus a markdown body:

yaml
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
---
name: pricing-policy
version: 0.1.0
description: Acme's pricing tiers, refund windows, and discount policies.
triggers: [pricing, refund, discount, billing]
permissions:
  - scaidrive:read:/policies/
secrets:
  - name: stripe_read_only
    required: true
    description: Stripe API key for fetching invoice metadata.
---

When asked about pricing or refunds, follow these rules:

- Always cite a tier name from `references/pricing-tiers.md`.
- For refund questions, quote the exact policy from `references/refund-policy.md`.
- If the customer cites an invoice id, use the stripe_read_only secret to look it up.
- Never improvise a discount — discounts come strictly from pricing-tiers.md.

The references/ files are plain markdown — the policy text and pricing table the LLM will pull on demand. Then bundle:

bash
1
tar -czf pricing-policy-0.1.0.tar.gz -C pricing-policy SKILL.md references

3. Register the slug#

bash
1
2
3
4
5
6
7
8
curl -X POST "$SCAIGRID_HOST/v1/modules/scaiskills/skills" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "pricing-policy",
    "visibility": "private",
    "description": "Acme pricing and refund rules."
  }'
python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import httpx, os
res = httpx.post(
    f"{os.environ['SCAIGRID_HOST']}/v1/modules/scaiskills/skills",
    headers={"Authorization": f"Bearer {os.environ['SCAIGRID_API_KEY']}"},
    json={
        "slug": "pricing-policy",
        "visibility": "private",
        "description": "Acme pricing and refund rules.",
    },
).raise_for_status()
javascript
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
await fetch(`${process.env.SCAIGRID_HOST}/v1/modules/scaiskills/skills`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.SCAIGRID_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    slug: "pricing-policy",
    visibility: "private",
    description: "Acme pricing and refund rules.",
  }),
});

A second publish with a different version will not need to repeat this step.

4. Publish the first version#

bash
1
2
3
curl -X POST "$SCAIGRID_HOST/v1/modules/scaiskills/skills/pricing-policy/versions" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -F "bundle=@pricing-policy-0.1.0.tar.gz"

What happens on the server:

  1. The validator opens the tarball, reads SKILL.md, parses the YAML frontmatter, checks the schema.
  2. The slug in the manifest is compared to the URL slug. Mismatch fails validation.
  3. The version in the manifest is checked against list_published_semvers(skill_id) — it must be strictly greater than every previously published version. 0.0.5 after 0.1.0 is rejected.
  4. The references directory is checked for path-traversal (../, absolute paths) and binary content.
  5. 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.
  6. The version row is written with status: "published" and the parsed manifest stored as JSON text.
  7. Best-effort: the manifest is indexed into the ScaiMatrix __scaiskills collection for semantic search.

5. Publish a follow-up version#

Iteration: update the body, bump the version, re-tar, re-upload.

bash
1
2
3
4
5
6
7
8
9
sed -i 's/^version: 0.1.0$/version: 0.2.0/' pricing-policy/SKILL.md
echo "" >> pricing-policy/SKILL.md
echo "**Update 0.2.0:** added VAT handling notes to refund-policy.md." >> pricing-policy/SKILL.md

tar -czf pricing-policy-0.2.0.tar.gz -C pricing-policy SKILL.md references

curl -X POST "$SCAIGRID_HOST/v1/modules/scaiskills/skills/pricing-policy/versions" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY" \
  -F "bundle=@pricing-policy-0.2.0.tar.gz"

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.

bash
1
2
curl -X POST "$SCAIGRID_HOST/v1/modules/scaiskills/skills/pricing-policy/versions/0.2.0/yank" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY"

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#

bash
1
2
curl "$SCAIGRID_HOST/v1/modules/scaiskills/skills/pricing-policy" \
  -H "Authorization: Bearer $SCAIGRID_API_KEY"

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.

Updated 2026-05-18 15:01:32 View source (.md) rev 12