Write a Skill
Skills are reusable, instance-scoped capability bundles — Markdown instructions plus structured metadata that the supervisor injects into the prompt when the skill is enabled. This guide walks through authoring a skill in the admin panel, then doing the same thing via the Management API. By the end you will have a skill in the global library, attached to an instance, with its environment variables populated.
Prerequisites
- An authenticated session on the admin panel (or a valid
Authorization: Bearertoken for API calls — see Get a JWT for API calls) - An instance you can attach the skill to (the slug, e.g.
my-bot) - Optional: credentials for any external service the skill will call
Step 1: Create the skill in the admin panel
- Navigate to Skills in the sidebar → New Skill.
- Fill the structured form:
- Name — lowercase, kebab-case slug (e.g.
email-triage). Lower-case + dashes are enforced by the input. - Description — one-line summary shown in the catalog.
- Required env — click Add to declare each env var the skill needs. Each row has
Name(any string is accepted; convention is UPPER_SNAKE_CASE, e.g.OPENAI_API_KEY), an optionalDescription, and aSensitiveswitch (masked in the admin UI when on). - Content — the Markdown body of the skill. This is what the LLM sees when the skill is active. Write it as instructions to the agent.
- Name — lowercase, kebab-case slug (e.g.
- Click Save. The skill lands in the library at version
0.1.0. Every subsequent Save on this skill creates a new version automatically; you do not pick the version number.
The Skills form is a structured editor — you do not paste Markdown with frontmatter. The frontmatter is reconstructed server-side from the structured fields.
Step 2: Or create it via the API
The same operation as a single REST call. Versions are auto-incremented server-side, so the payload never contains a version field.
curl -s -X POST http://localhost:4000/api/skills \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "email-triage",
"description": "Classify inbound email by urgency and draft a reply.",
"content": "# Email triage\n\nWhen the user shares an email, classify it as URGENT, NORMAL, or LATER. Then draft a reply in the same language as the original. Never include private data in the subject line.",
"requiredEnv": [
{ "name": "OPENAI_API_KEY", "description": "Used for the draft", "sensitive": true }
],
"requiredTools": ["httpRequest"]
}'Supported fields on create/update:
| Field | Type | Notes |
|---|---|---|
name | string | Required on create; immutable after that |
description | string | Required |
content | string | Required — the Markdown body |
requiredEnv | { name, description?, sensitive }[] | Per-instance env vars |
requiredTools | string[] | Tool names that must be enabled for this skill to work |
Note:
scriptsis not accepted byPOST /api/skillsorPUT /api/skills/:name. It is a catalog-import-only field — skills imported viaPOST /api/skills/catalog/importcan carry ascriptspayload, but the standard create/update API does not.
To update an existing skill (creates a new version):
curl -s -X PUT http://localhost:4000/api/skills/email-triage \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{ "description": "...", "content": "...", "requiredEnv": [...], "changelog": "tightened tone of voice" }'To browse the version history:
curl -s http://localhost:4000/api/skills/email-triage/versions \
-H "Authorization: Bearer $TOKEN"The response is wrapped in an object:
{
"versions": [
{ "version": "0.2.0", "changelog": "tightened tone of voice", "createdAt": "..." },
{ "version": "0.1.0", "changelog": null, "createdAt": "..." }
]
}Step 3: Attach the skill to an instance
A skill in the library does nothing until you attach it to an instance. From the admin panel, open the instance → Skills tab → toggle email-triage on. The API equivalent is:
curl -s -X POST http://localhost:4000/api/instances/my-bot/skills/email-triage \
-H "Authorization: Bearer $TOKEN"Step 4: Populate the per-instance env vars
If the skill declares requiredEnv entries, populate them per-instance. In the admin UI this is the form that appears next to the skill toggle. Via API:
curl -s -X PUT http://localhost:4000/api/instances/my-bot/skills/email-triage/env \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"env": [
{ "key": "OPENAI_API_KEY", "value": "sk-...", "sensitive": true }
]
}'Values are AES-256-GCM encrypted at rest. To remove a single var: DELETE /api/instances/my-bot/skills/email-triage/env/OPENAI_API_KEY.
Verification
- Open the instance Skills tab —
email-triageshould be toggled on and show no red “missing env” badge. - Go to Playground, send a message that matches the skill’s trigger (“can you triage this email…”). The skill content is now part of the supervisor prompt.
- In the conversation trace, the active skills list is logged on every turn — check the engine logs for
discoverSkillsentries namingemail-triage.
Caveats
- The fields
requiredSecrets,dependsOnTools,language, andtagsare not supported. Stick to the list in Step 2. - A skill version is created on every save — there is no draft mode. Use the version history (
GET /api/skills/:name/versions) to roll back if needed. - Skill
contentis plain Markdown but it ends up in the LLM prompt — keep it short and instruction-shaped, not narrative.