Skills

Give an agent domain knowledge it loads only when it needs it: a skill is a versioned SKILL.md bundle your organization owns, bound to an agent at a label, and expanded into the run through the Skill tool.

How It Works

A skill is a folder in the format Claude Code uses: a SKILL.md with YAML frontmatter (name, description, anything else you keep) and a body of instructions, plus optional references/, assets/ and scripts/ files. 2kw.ai stores it as a skill with immutable versions and movable labels — the same model prompts, schemas and agents use.

An agent version binds skills by name and label. At run start each binding resolves to one version; the resolved set is pinned on the run and recorded on its trace. The model sees two tools:

  • Skill — its description lists every bound skill's name and description (progressive disclosure, a few dozen tokens per skill). Calling it with a skill name returns the SKILL.md body and the skill's base directory, /skills/<name>.
  • Read — reads a file the skill bundles, such as /skills/<name>/references/guide.md. Only files of the run's bound skills are readable; there is no host filesystem behind it.

Both calls are ordinary tool items in the Responses stream, so skill activation is visible to your application and in the trace. Where the sandbox Bash tool is enabled for your deployment (not yet generally available) and the agent has it, its bound skills are placed at /skills/<name> in the conversation's sandbox before each command, so a SKILL.md that says "run scripts/extract.py" works: the model runs python3 /skills/<name>/scripts/extract.py and writes results under outputs/. The placed files are read-only by file mode; commands in the sandbox can still change them until the next version or sandbox. Without Bash, bundled scripts/ are stored and exported only.

The console shows a skill's History: every version with when it was made, which labels point at it, whether it came from a plugin, and which earlier version has the same content. The version latest points at is marked Current.

Importing a Skill

POST /v1/skills/import

Upload a SKILL.md or a zip archive of the whole folder. The skill's name comes from the frontmatter and is slugified; re-importing identical content creates no new version.

The CLI also takes the folder itself: bb skills import ./invoice-workflow zips SKILL.md plus references/, assets/ and scripts/ locally and uploads that. Anything else in the folder — a .git, a node_modules, scratch notes — stays behind and is listed on stderr, because the importer would only report it as an unsupported path.

Request

curl -X POST https://api.2kw.ai/v1/skills/import \
  -H "Authorization: Bearer sk_..." \
  -F "file=@invoice-workflow.zip"

Response

{
  "skillId": "skl_...",
  "name": "invoice-workflow",
  "versionNumber": 1,
  "outcome": "CREATED",
  "renamedFrom": null,
  "skipped": []
}

A version can also be created from a markdown body with POST /v1/skills/{id}/versions; GET /v1/skills/{id}/versions/{n}/export returns the bundle as a zip with the resources byte for byte and the frontmatter re-rendered. bb skills export <id> <n> writes that zip, or unpacks it into a folder you can edit with --extract-to ./invoice-workflow (--force to write into a folder that is not empty). Labels live under /v1/skills/{id}/labels; latest is managed for you and always points at the newest version.

Resolving a reference

GET /v1/skills/resolve?ref=<name>[@<label|number>] returns the version a reference resolves to right now — the same rule an agent run applies at start.

Binding Skills to an Agent

Add skills to an agent or agent version. Each entry names a skill of your organization and, optionally, the label or version number to follow:

Agent version

{
  "models": ["openai/gpt-4o"],
  "instructions": "You process supplier invoices.",
  "skills": [
    { "name": "invoice-workflow", "ref": "production" },
    { "name": "pdf-forms" }
  ]
}
FieldTypeRequiredDescription
namestringYesThe skill's name, a lowercase slug
refstringNoA label name or a version number as string; latest when omitted

Rules, enforced when the agent or version is written (422 otherwise):

  • at most 20 skills per agent version, each name once;
  • the skill must exist in your organization and be ACTIVE;
  • the reference must resolve to a version at the time of writing.

At run start the references resolve again; a label that no longer exists fails the run rather than running without the skill. The resolved pins (name@number) and a hash over the set are recorded on the response's run record and as backbone.skills.* attributes on its trace.

The console offers the same binding on the agent form under Skills, with the version each label currently resolves to shown inline.

Plugins

A plugin is a git repository in Claude Code plugin layout (.claude-plugin/plugin.json plus a skills/ directory). Installing one imports every skill it carries as ordinary skills of your organization and keeps them in sync with the repository. Repositories on github.com and gitlab.com are accepted over https; other hosts are refused.

POST /v1/plugins

Installs the plugin and runs its first sync. Nothing is stored when that sync fails, so a malformed skill comes back as a 422 naming the directory.

Request

curl -X POST https://api.2kw.ai/v1/plugins \
  -H "Authorization: Bearer sk_..." \
  -H "Content-Type: application/json" \
  -d '{"gitUrl": "https://github.com/2kw-ai/claude-plugin.git", "refPolicy": "track:main"}'

The refPolicy decides how the plugin follows its repository:

PolicyBehaviour
track:<branch or tag>Re-synced every hour and on demand. When the ref points at a new commit, the changed skills get a new version and the plugin label moves to it.
pin:<sha>Imported once. Never advances on its own; change the pin to move it.

Each imported skill carries a managed plugin label that always points at the newest imported version, next to latest. Bind an agent with {"name": "invoice-workflow", "ref": "plugin"} to follow the plugin, or with a version number to freeze it. If you edit an imported skill yourself, later syncs still import each new upstream version and move the plugin label to it, but latest stays on your edit and the skill is reported as forked, so your edit stays in charge.

POST /v1/plugins/{id}/sync

Pulls the repository now, for "I just pushed to my plugin repo". A sha that has not moved is a no-op. The response, also stored on the plugin as lastSyncReport, says what happened per skill:

Response

{
  "sha": "0123456...",
  "syncedAt": "2026-09-14T20:00:00Z",
  "outcome": "SYNCED",
  "skills": [
    { "name": "invoice-workflow", "directory": "invoice-workflow", "versionNumber": 3, "outcome": "IMPORTED", "skipped": [] }
  ],
  "unsupported": [
    { "path": "hooks/", "reason": "unsupported-component" }
  ]
}

commands/, hooks/ and agents/ are host-harness features and are listed under unsupported; mcpServers from the manifest is stored but not activated in this release.

PATCH /v1/plugins/{id} changes refPolicy or pauses the plugin with "status": "DISABLED"; a paused plugin is skipped by the scheduled refresh and refuses a manual sync. DELETE /v1/plugins/{id} detaches it: the imported skills remain as ordinary skills of your organization and stop receiving updates.

The console lists installed plugins under Skills > Plugins, with install, sync, ref policy, pause and detach.

Editing Skills in Chat

A member can have an agent change its own skills during a conversation: fix a procedure, add a reference file, attach a template, or create a new skill and bind it to the agent.

Who can. The caller's organization role must be Member or higher, whether it comes from a member session or an API key. Callers with the User or Viewer role and panel users never see the authoring tool or its guidance skill; they keep Skill and Read for the agent's bound skills.

How it works. The agent reads the current files, then proposes one SkillsApply call that carries the whole change. One call is one approval, and each changed skill gets one new version — or, when the result is byte for byte the skill's newest version, that version, as with import. Text files are sent inline; a small change to a long text file is sent as exact-match edits against the version the agent read; a binary file is referenced by a Files API file_id and copied when the change is approved, so a denied change uploads nothing.

What the approval shows. The run pauses with a backbone:approval_request whose preview lists, per skill, the version the change starts from, the labels it moves, and each file's unified diff (binary files by path and size). It also shows:

  • Followers — the agents whose current version binds the skill through one of the moved labels. Every one of them gets the new version on its next run. The list is a snapshot taken when the preview was drawn ("as of"), not a guarantee.
  • Plugin fork — editing a skill a plugin installed forks it: later plugin syncs still update the plugin label, but no longer move latest.
  • Agent binding — a created skill is bound through a new agent version.

Nothing goes live until a member approves. An edit moves the label the agent's binding follows, plus latest. Skills bound by version number (invoice-workflow@3) or by the plugin label are frozen and cannot be edited from chat: change the binding in the console.

Every change lands on an immutable version, and the earlier versions stay. To undo a change, restore the earlier version: open the skill's History in the console and click Restore, call POST /v1/skills/{id}/versions/{n}/restore, or run bb skills versions restore <id> <n>. A restore creates a new version with the earlier content and points latest at it — nothing is rewritten, the versions in between stay, and every agent following latest uses the restored content from its next run. When the newest version already has that content, no version is created and latest moves to it. latest cannot be moved by hand; a label of your own can be moved back to an earlier version with PUT /v1/skills/{id}/labels/{name}, and plugin and custom labels are never touched by a restore.

Who Sees a Skill

By default every caller of an agent gets all of its bound skills. An admin can give a skill a minimum role: an agent run whose caller's organization role is below it gets the agent as if the skill were not bound. The skill is not listed to the model, Read cannot open its files, it is not placed in the sandbox, invoking it by name is refused as "not bound", and the chat composer's / menu leaves it out. Nothing tells the caller a skill was left out.

  • Roles count from low to high: User, Viewer, Member, Admin, Owner. A minimum of Member means Members, Admins and Owners see the skill.
  • The role is the caller's own: the member signed in, or the owner of the API key. A continuation or an approval by another member uses that member's role.
  • Anonymous chat widgets are below every role and see only skills without a minimum.
  • Evaluations see every skill: an experiment measures the agent as configured.

Set it on the skill's page in the console under Minimum role, with PUT /v1/skills/{id}/min-role and a body of {"minRole": "ADMIN"} (null removes it), or with bb skills min-role <id> admin (none removes it). Only admins can change it. It is a setting of the skill, not of a version: new versions, imports, plugin syncs and restores keep it, and an exported bundle does not carry it.

Retiring a Skill

Set status to ARCHIVED with PUT /v1/skills/{id}: new bindings are refused, existing agents keep resolving their pinned versions, and nothing is deleted.

DELETE /v1/skills/{id} removes the skill and its versions, and is refused with 409 while any agent version binds it — the message names the agents. Unbind or archive first.

Limits

LimitValue
Skills per agent version20
SKILL.md body256 KB
Resource file5 MB
Bundle20 MB, 500 entries
Resource pathsbelow references/, assets/ or scripts/

Was this page helpful?