Write a Skill
On this page
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 - the organization’s catalogue - then New Skill.
- Fill the structured form:
- Name - the display name (e.g.
email-triage, orEmail triage). The slug is derived from it server-side: lowercased, with every character outsidea-z0-9-turned into a dash. It is immutable afterwards and unique within the organization, so pick a name you can live with. - 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 - the display name (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. Each field is stored on its own: name, description and category on the skill row, requiredEnv and requiredTools on the version’s metadata. A --- block typed into the body is not parsed; it just becomes part of the prompt.
Step 2: Or create it via the API
The same operation as a single REST call. The catalogue is per organization, so every route carries the organization slug - set ORG to yours. Versions are auto-incremented server-side, so the payload never contains a version field.
curl -s -X POST http://localhost:4000/api/organizations/$ORG/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. A plugin tool is named with its namespace (hubspot:contact); a name the organization cannot use, including a tool’s old name from before it moved into a plugin, is refused with 400 |
Note:
scriptsis not accepted byPOST .../skillsorPUT .../skills/:name. It is a catalog-import-only field - skills imported viaPOST /api/organizations/:orgSlug/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/organizations/$ORG/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/organizations/$ORG/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: Assign the skill to the workspace
A skill in the catalogue does nothing until the workspace the agent lives in is allowed to draw on it. A workspace created after the allow-list shipped starts empty, so this step is not optional for a new workspace - see What a workspace may draw on.
From the admin panel: Settings → Skills, in the workspace tier, and switch email-triage on. It needs workspace.assignment:manage, which Admin and above hold. The API equivalent:
curl -s -X PUT http://localhost:4000/api/organizations/$ORG/workspaces/$WS/skills/email-triage \
-H "Authorization: Bearer $TOKEN"Step 4: Enable the skill on the agent
From the admin panel, open the agent → Skills section → toggle email-triage on. The API equivalent takes the complete set of enabled skills, not a delta - anything you leave out is turned off:
curl -s -X PATCH http://localhost:4000/api/instances/my-bot/skills \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{ "enabled": ["email-triage"] }'Step 5: 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 agent’s Skills section -
email-triageshould be toggled on, show no red “missing env” badge, and not be counted in the “not assigned to this workspace” banner. - Click Try and send a message that matches the skill’s trigger (“can you triage this email…”). The skill content is now part of the supervisor prompt.
- If it does not fire, check the agent’s Status section: a skill missing its env, or one the workspace does not carry, is reported there by name or by count.
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/organizations/:orgSlug/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.