> ## Documentation Index
> Fetch the complete documentation index at: https://docs.labelbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Skills

> Write reusable procedures as SKILL.md, version them, group them, and attach them to agents.

A skill is a reusable procedure stored as `SKILL.md`, optionally with scripts and reference files. Attach skills to an agent instead of putting every procedure in its system prompt: the agent sees each skill's short description on every turn and loads the full instructions only when a skill fits the task.

<Note>
  Creating, changing, or deleting skills and skill groups needs the organization developer or admin role. The organization user role can view them. See [Organizations and roles](/recursion/organizations-and-roles). For the API, create a key on [API keys](/recursion/api-keys) and export it as `RECURSION_API_KEY`.
</Note>

## Write SKILL.md

`SKILL.md` starts with YAML frontmatter on the first line, followed by Markdown instructions. `name` and `description` are required. Write the description as a condition for using the skill, because it is what the model routes on.

```markdown theme={"theme":"css-variables"}
---
name: release-notes
description: Use when drafting customer-facing notes from merged pull requests.
---

# Draft release notes

1. Read every merged pull request in the requested range.
2. Group the changes by product area.
3. Write one plain sentence for each change.
4. Save the result with write_artifact as release-notes.md.
```

<Accordion title="Frontmatter keys and document rules">
  | Frontmatter key | Rule |
  | - | - |
  | `name` | Required. Lowercase letters, digits, and single hyphens, at most 64 characters. The agent activates the skill by this name. |
  | `description` | Required. At most 1,024 characters. Write it as a condition for using the skill, because it is what the model routes on. |
  | `license`, `compatibility`, `metadata`, `allowed-tools` | Optional. Stored with the version. `compatibility` is at most 500 characters, and the agent sees it when it activates the skill. `allowed-tools` isn't enforced. |

  Any other key has no effect. The whole `SKILL.md` can be at most 256 KiB, and it must have instructions below the frontmatter.

  When a session loads a skill, it removes HTML comments and escapes `<`, `>`, and `&` in the name, description, and instructions. The agent reads `&lt;`, `&gt;`, and `&amp;` in their place, so write procedures that don't depend on those characters or on hidden comments. Instructions longer than 64,000 bytes of UTF-8 after this escaping are cut off, and the agent is told they were cut off. The catalog keeps your text as you wrote it.
</Accordion>

## Create a skill

Creating a skill also creates version 1. Versions are immutable.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Skills**.
    2. Click **Create skill**.
    3. Enter the document in **SKILL.md**, and optionally a **Display title** and a **Group**.
    4. Click **Create skill**.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/skills' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{
        "display_title": "Release notes",
        "document": "---\nname: release-notes\ndescription: Use when drafting customer-facing notes from merged pull requests.\n---\n\n# Draft release notes\n\nRead the merged pull requests, group them by product area, and write one sentence for each change."
      }'
    ```
  </Tab>
</Tabs>

A `200` response is the catalog entry. Some fields are left out here.

```json theme={"theme":"css-variables"}
{
  "skill_id": "4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64",
  "latest_skill_version_id": "8b1f5d73-2c94-467a-a0e6-4d9c7b3f1e52",
  "latest_version_number": 1,
  "latest_instruction_chars": 104,
  "name": "release-notes",
  "display_title": "Release notes",
  "description": "Use when drafting customer-facing notes from merged pull requests.",
  "organization_id": "org_01a08a705220724f9a2fe1bcd8638c9d",
  "created_at": "2026-09-17T09:30:11Z",
  "updated_at": "2026-09-17T09:30:11Z"
}
```

Keep `skill_id` to attach the skill and `latest_skill_version_id` to publish the next version. Next, [attach it to an agent](#attach-skills-to-an-agent).

<Accordion title="Create fields">
  | Field | Description |
  | - | - |
  | `document` | Required. The complete `SKILL.md`, frontmatter included. |
  | `display_title` | Title shown in the catalog, at most 256 characters. Defaults to the frontmatter `name`. |
  | `skill_group_id` | The [group](#organize-skills-in-groups) to file the skill under. Omit it to leave the skill ungrouped. |
  | `metadata` | Your own JSON object. The API returns it unchanged. |

  Skill names are unique in your organization. A name that another live skill already uses returns `409 conflict` on `name`.
</Accordion>

### Upload a bundle

To include scripts and reference files, upload a zip archive of the skill's directory instead. Put `SKILL.md` at the root of the archive, or inside one wrapper directory. Put runnable helpers in `scripts/`, supporting documents in `references/`, and other files in `assets/`. Files under `scripts/` are made executable in the sandbox; all other files are not.

<Tabs>
  <Tab title="Console">
    1. In **Skills**, click **Create skill**.
    2. Drop the zip on **Upload a bundle**, or pick it. Optionally enter a **Display title**.
    3. Click **Review and upload**, check the file list, then click **Create skill** in the **Upload bundle** dialog.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/skills/upload' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -F 'bundle=@release-notes.zip' \
      -F 'display_title=Release notes'
    ```
  </Tab>
</Tabs>

The document, name, and description come from the `SKILL.md` inside the archive.

<Accordion title="Bundle rules and limits">
  If you use a wrapper directory, its name must match the skill's `name`, and every file must be inside it. Symbolic links and other non-regular files are refused.

  The multipart form also accepts `skill_group_id`, and `metadata` as a JSON string.

  | Bundle limit | Value |
  | - | - |
  | Compressed archive | 8 MiB |
  | Unpacked contents | 32 MiB |
  | One file | 4 MiB |
  | Files | 500 |
  | Directory depth below the skill root | 3 |
</Accordion>

## Attach skills to an agent

Skills are part of an agent version, in the `skills` field. The list is ordered: the API stores and returns it in the order you send it, and the model reads the skills in that order. Each entry is one of these:

| Entry | What the session gets |
| - | - |
| `{ "skill_id": "..." }` | The skill's latest version when the session starts or moves to another agent version. |
| `{ "skill_id": "...", "version": "<skill_version_id>" }` | That exact version, every time. `"latest"` also works and means the same as leaving `version` out. |
| `{ "type": "group", "skill_group_id": "..." }` | The group's current skills when the session starts or moves to another agent version. A skill added to the group later reaches the agent without an agent update. |

<Tabs>
  <Tab title="Console">
    1. Open the agent and stay on the **Configuration** tab.
    2. In **Skills**, pick a skill or group in **Attach a skill or group**. Reorder the cards so the most important skills come first.
    3. Click **Save new version**.

    The console attaches the latest version. To pin a version, use the API.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/agents/5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55/versions' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{
        "base_agent_version_id": "c1a94e07-2f6b-4d18-b3a5-0e7d8c6f4a21",
        "name": "Release notes writer",
        "model": "<model-id>",
        "system": "Follow the attached procedures.",
        "disabled_integration_mcp_providers": ["jira"],
        "skills": [
          {"skill_id": "4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64"},
          {"type": "group", "skill_group_id": "6c2e9a47-1f83-45d0-b7a6-3e8c5f2d9a14"}
        ]
      }'
    ```
  </Tab>
</Tabs>

A `201` response is the agent at its new version. A version is a full replacement, so resend every other setting you want to keep. See [Update an agent](/recursion/agents#update-an-agent).

```json theme={"theme":"css-variables"}
{
  "agent_id": "5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55",
  "latest_agent_version_id": "7a4e1c95-6d2f-4380-b915-3e7a0c6f2d84",
  "skills": [
    { "skill_id": "4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64" },
    { "type": "group", "skill_group_id": "6c2e9a47-1f83-45d0-b7a6-3e8c5f2d9a14" }
  ]
}
```

Entry order matters, because descriptions past the disclosure budget are dropped from the end. A group's skills take the group's place in the list, in the order they were created, oldest first. You can't change that order; to order skills one by one, attach each skill instead of the group. Next, [check the saved agent's skills](#check-the-skills-of-a-saved-agent) to see that the descriptions fit.

<Accordion title="Entry rules">
  An agent can have at most 100 entries. The API checks every id when you save the version; an unknown skill, version, or group returns `400 invalid_request` naming the entry, such as `skills[0].skill_id`.
</Accordion>

### Reorder skills

To change the order, save a new agent version with the same entries in the new order. A version is a full replacement, so the [update rules](/recursion/agents#update-rules) apply.

<Tabs>
  <Tab title="Console">
    1. Open the agent and stay on the **Configuration** tab.
    2. In **Skills**, drag a card to its new place, or open the card's menu and click **Move to top** or **Move to bottom**.
    3. Click **Save new version**.
  </Tab>

  <Tab title="cURL">
    Get the agent first, as in [Find an agent](/recursion/agents#find-an-agent). Then send its `skills` in the new order, with its `latest_agent_version_id` as `base_agent_version_id`. This request moves the group ahead of the skill.

    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/agents/5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55/versions' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{
        "base_agent_version_id": "7a4e1c95-6d2f-4380-b915-3e7a0c6f2d84",
        "name": "Release notes writer",
        "model": "<model-id>",
        "system": "Follow the attached procedures.",
        "disabled_integration_mcp_providers": ["jira"],
        "skills": [
          {"type": "group", "skill_group_id": "6c2e9a47-1f83-45d0-b7a6-3e8c5f2d9a14"},
          {"skill_id": "4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64"}
        ]
      }'
    ```
  </Tab>
</Tabs>

A `201` response returns `skills` in the new order.

## Check the skills of a saved agent

Every turn lists the session's skills within a character budget. The budget depends on the agent's model: a model with a larger context window allows a longer skill list. When the descriptions don't fit, the ones at the end of the list are dropped and the model sees only those skills' names, which makes it less likely to pick them. `getAgentSkillDisclosure` reports the list the agent's next session gets, in one read.

<Tabs>
  <Tab title="Console">
    1. Open the agent and stay on the **Configuration** tab.
    2. In **Skills**, read the cards from the top. That is the order the model reads the skills. Click **Show skills** on a group's card to see the skills it adds.
    3. Read the budget meter. Cards past the line marked "Past this line the model sees only skill names." are flagged **Name only**.

    The meter is a [preview](#preview-prompt-disclosure) of the cards on the page. It doesn't count the [skill for using apps](/recursion/integrations#what-a-session-receives) that sessions of an agent with built-in integrations list first, and for a model the model list doesn't include it measures against the smallest budget and says so. The API read is exact in both cases.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/agents/5f0c2a1e-8b7d-4c3a-9e21-6d4f0b9a7c55/skill-disclosure' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

A `200` response is the list. This agent's latest version is the one saved in [Reorder skills](#reorder-skills), so the group's skill comes first.

```json theme={"theme":"css-variables"}
{
  "budget_chars": 16000,
  "used_chars": 291,
  "entries": [
    {
      "name": "release-checklist",
      "description_included": true,
      "attachmentIndex": 0,
      "skillId": "b3d81e6a-4c07-4f52-9a1d-7e2c5f8a6b90"
    },
    {
      "name": "release-notes",
      "description_included": true,
      "attachmentIndex": 1,
      "skillId": "4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64"
    }
  ],
  "warnings": []
}
```

`entries` is in the order the model reads the skills. The first entry with `description_included: false` is where the line falls, and every entry after it is name only too. Those skills can still be activated, but the model sees only their names. `attachmentIndex` is the position, counted from 0, of the entry in the agent's `skills` that a skill came from, so it tells you which entry to move. [Move important skills earlier](#reorder-skills), or shorten descriptions, until they fit.

<Accordion title="What the read measures">
  The read answers for the agent's latest version, the way a session starting now would list its skills:

  * **The agent's own model.** The budget comes from the model the agent is saved with, including a model the model list no longer offers. If that model's context window can't be read when you ask, the response measures against the smallest budget, 8,000 characters, and says so in `warnings`.
  * **The skill for using apps.** Sessions of an agent with built-in integrations list [a skill for using apps](/recursion/integrations#what-a-session-receives) first. The response lists it first too and counts it in `used_chars`. Its entry has no `attachmentIndex` and no `skillId`.
  * **Groups and versions as they are now.** Each group expands to the skills it holds now, and each entry without a pinned version resolves to the skill's latest version.

  An agent that doesn't exist, is deleted, or belongs to another organization returns `404 not_found`. An agent whose model can no longer start sessions returns `400 invalid_request` on `model`. Save a new agent version with a model from the model list, then read again.
</Accordion>

<Accordion title="Response fields">
  | Field | Description |
  | - | - |
  | `budget_chars` | Characters the model allows the skill list on each turn. |
  | `used_chars` | Characters the list uses. |
  | `entries[].name` | The skill's name, as the model sees it. |
  | `entries[].description_included` | `false` when the skill is listed by name only. |
  | `entries[].attachmentIndex` | The position, counted from 0, of the entry in `skills` that the skill came from. Every skill of a group has the group's position. Absent for a skill the platform adds, such as the skill for using apps. |
  | `entries[].skillId` | The catalog skill's `skill_id`. Absent for a skill the platform adds, and for a skill written inline in its entry. |
  | `warnings` | Sentences that say what the list lost and why: an entry that could not be resolved, such as a deleted skill or group, a budget measured against the smallest because the model's context window could not be read, and each skill listed by name only. |
</Accordion>

## Preview prompt disclosure

To check a list before you save it, send it to the preview. The preview resolves groups exactly as a session start does and reports what fits, in the same shape as [the read for a saved agent](#check-the-skills-of-a-saved-agent). It saves nothing.

<Tabs>
  <Tab title="Console">
    1. Open the agent and stay on the **Configuration** tab.
    2. In **Skills**, attach the skills or groups you plan to use.
    3. Read the budget meter. It measures against the model selected on the agent. For a model the model list doesn't include, it measures against the smallest budget and says so. Cards past the line marked "Past this line the model sees only skill names." are flagged **Name only**. When the list resolves to a different number of skills than it has cards, as with a group that holds several skills, the meter names the first affected skills instead of drawing the line.
  </Tab>

  <Tab title="cURL">
    Send the model's context window as `context_window_tokens`. It is `contextWindow` on the model's entry from `listModels`, the request in [REST client setup](/recursion/api#rest-client-setup). Without it, the preview measures against the smallest budget any model gets.

    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/skills/disclosure-preview' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"context_window_tokens":200000,"skills":[{"skill_id":"4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64"},{"type":"group","skill_group_id":"6c2e9a47-1f83-45d0-b7a6-3e8c5f2d9a14"}]}'
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "budget_chars": 16000,
  "used_chars": 291,
  "entries": [
    {
      "name": "release-notes",
      "description_included": true,
      "attachmentIndex": 0,
      "skillId": "4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64"
    },
    {
      "name": "release-checklist",
      "description_included": true,
      "attachmentIndex": 1,
      "skillId": "b3d81e6a-4c07-4f52-9a1d-7e2c5f8a6b90"
    }
  ],
  "warnings": []
}
```

Read the response as you read [the one for a saved agent](#check-the-skills-of-a-saved-agent): `entries` is in the order the model reads the skills, and the first entry with `description_included: false` is where the line falls. Here `attachmentIndex` is the position, counted from 0, of the entry in the `skills` you sent. [Move important skills earlier](#reorder-skills), or shorten descriptions, until they fit.

<Accordion title="Preview details">
  Here the group holds one skill, `release-checklist`, so the response lists it after `release-notes`, with the group's `attachmentIndex`, 1. `skillId` is the catalog skill each entry resolved to. `warnings` also reports entries that could not be resolved, such as a deleted skill or group. `context_window_tokens` is optional. Without it, or when the model's entry has no `contextWindow` to send, the preview measures against the smallest budget, 8,000 characters.

  The preview measures only the list and the context window you send. The model list holds the models you can choose for an agent today, so for an agent on a model it no longer lists there is no `contextWindow` to send. Sessions of an agent with built-in integrations also list [a skill for using apps](/recursion/integrations#what-a-session-receives) first, which the preview does not count. For a saved agent, [`getAgentSkillDisclosure`](#check-the-skills-of-a-saved-agent) is exact in both cases.
</Accordion>

## Publish a new version

Get the skill first, then send its current `latest_skill_version_id` as `base_skill_version_id`. The request is a full replacement: a `display_title` you leave out resets to the frontmatter `name`, and `metadata` you leave out is cleared. You cannot change the group here; use [Move a skill to a group](#move-a-skill-to-a-group).

<Tabs>
  <Tab title="Console">
    1. In **Skills**, open the skill.
    2. Edit **SKILL.md** or **Display title**, or drop a new zip on **Upload a bundle**. A skill with bundled files keeps its instructions locked here; change them by uploading a new zip.
    3. Click **Save new version**. For a bundle, confirm in the **Upload bundle** dialog.
  </Tab>

  <Tab title="cURL — document">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/skills/4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64/versions' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{
        "base_skill_version_id": "8b1f5d73-2c94-467a-a0e6-4d9c7b3f1e52",
        "document": "---\nname: release-notes\ndescription: Use when drafting customer-facing notes from merged pull requests.\n---\n\n# Draft release notes\n\nCheck every pull request before writing the notes."
      }'
    ```
  </Tab>

  <Tab title="cURL — bundle">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/skills/4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64/versions/upload' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -F 'base_skill_version_id=8b1f5d73-2c94-467a-a0e6-4d9c7b3f1e52' \
      -F 'bundle=@release-notes.zip' \
      -F 'display_title=Release notes'
    ```
  </Tab>
</Tabs>

The JSON `createSkillVersion` operation returns the new version with `201`; the multipart `publishSkillVersionBundle` action returns it with `200`.

```json theme={"theme":"css-variables"}
{
  "skill_id": "4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64",
  "skill_version_id": "13d8a6f4-5b27-49e1-8c30-7a2f9d6b4e85",
  "version_number": 2,
  "name": "release-notes",
  "description": "Use when drafting customer-facing notes from merged pull requests.",
  "created_at": "2026-09-18T14:02:37Z"
}
```

Agents that follow the latest version pick up version 2 in their next session. Sessions already running keep the skill version they have: a session resolves its skills again only when it [moves to another agent version](/recursion/sessions#move-a-session-to-another-version). If another writer published first, the API returns `409 revision_conflict` and saves nothing. Get the skill again, apply your change, and retry.

## Find a skill

List the catalog, or get one skill by id. Neither includes the instructions; [get a version](#list-and-get-versions) for those.

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Skills**.
    2. Search by name, description, or id, or expand a group in the catalog.
    3. Click the skill to open its latest version.
  </Tab>

  <Tab title="cURL — list">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/skills?search=release&limit=100' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>

  <Tab title="cURL — get">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/skills/4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{
  "skills": [
    {
      "skill_id": "4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64",
      "latest_skill_version_id": "8b1f5d73-2c94-467a-a0e6-4d9c7b3f1e52",
      "name": "release-notes",
      "description": "Use when drafting customer-facing notes from merged pull requests."
    }
  ]
}
```

The list is newest first and leaves out deleted skills. That is a browse order, unrelated to the order of an agent's `skills`. Follow `next_page_token` to read the whole catalog; it is absent on the last page. A get returns one skill in the same shape as the create response.

<Accordion title="List parameters">
  | Query parameter | Description |
  | - | - |
  | `search` | 2 to 256 characters. Matches part of the name, title, or description, ignoring case, or a complete skill id. |
  | `skill_group_id` | Only skills in this group. An unknown group returns `404 not_found`. |
  | `ungrouped` | `true` for only skills in no group. Cannot be combined with `skill_group_id`. |
  | `limit` | Page size. Defaults to 100, at most 500. |
  | `page_token` | The `next_page_token` from the previous page. Send the same filters and `limit` with it. |
</Accordion>

## List and get versions

`listSkillVersions` returns every version, newest first, without pages and without instructions. `getSkillVersion` returns one version with its `instructions`.

<Tabs>
  <Tab title="Console">
    1. In **Skills**, open the skill.
    2. Use **Compare with** under **Changes** to see what changed between versions.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl 'https://api.recursion.labelbox.com/managed-agents/v1/skills/4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64/versions/13d8a6f4-5b27-49e1-8c30-7a2f9d6b4e85' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

Every version has a bundle, and `bundle.entries` always includes `SKILL.md`. A skill created from a document alone has a bundle that holds only `SKILL.md`. Some fields are left out of this sample.

```json theme={"theme":"css-variables"}
{
  "skill_id": "4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64",
  "skill_version_id": "13d8a6f4-5b27-49e1-8c30-7a2f9d6b4e85",
  "version_number": 2,
  "name": "release-notes",
  "description": "Use when drafting customer-facing notes from merged pull requests.",
  "instructions": "# Draft release notes\n\nCheck that every pull request is merged before writing the notes. Run scripts/collect.sh to list them.",
  "entrypoint_path": "SKILL.md",
  "file_manifest": ["scripts/collect.sh", "references/style.md"],
  "bundle": {
    "bytes": 1843,
    "entries": [
      { "path": "SKILL.md", "bytes": 214, "mode": 420 },
      { "path": "references/style.md", "bytes": 2210, "mode": 420 },
      { "path": "scripts/collect.sh", "bytes": 512, "mode": 493 }
    ]
  },
  "created_at": "2026-09-18T14:02:37Z"
}
```

A version with bundled files lists them in `bundle.entries`. `mode` is `493` (octal 0755) for files under `scripts/` and `420` (octal 0644) for everything else.

<Accordion title="Download a version's files">
  `getSkillVersionContent` returns the version's files as a gzipped tarball, exactly as a session receives them: `SKILL.md` plus any bundled files.

  ```bash theme={"theme":"css-variables"}
  curl 'https://api.recursion.labelbox.com/managed-agents/v1/skills/4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64/versions/13d8a6f4-5b27-49e1-8c30-7a2f9d6b4e85/content' \
    -H "Authorization: Bearer $RECURSION_API_KEY" \
    -o release-notes-v2.tar.gz
  ```
</Accordion>

## Organize skills in groups

A skill group is a folder in the catalog and a way to attach a whole set of skills to an agent in one entry. The model never sees the group. Filing a skill in a group never creates a skill version.

### Create a group

<Tabs>
  <Tab title="Console">
    1. In the sidebar, click **Skills**.
    2. Click **New group**.
    3. Fill in **Name**, and optionally **Display title** and **Description**.
    4. Click **Save group**.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X POST 'https://api.recursion.labelbox.com/managed-agents/v1/skill-groups' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{
        "name": "release-workflows",
        "display_title": "Release workflows",
        "description": "Procedures used while preparing a release."
      }'
    ```
  </Tab>
</Tabs>

A `201` response is the group. Keep `skill_group_id` to file skills under it and to attach it to an agent.

```json theme={"theme":"css-variables"}
{
  "skill_group_id": "6c2e9a47-1f83-45d0-b7a6-3e8c5f2d9a14",
  "organization_id": "org_01a08a705220724f9a2fe1bcd8638c9d",
  "name": "release-workflows",
  "display_title": "Release workflows",
  "description": "Procedures used while preparing a release.",
  "skill_count": 0,
  "created_at": "2026-09-17T09:41:52Z",
  "updated_at": "2026-09-17T09:41:52Z"
}
```

<Accordion title="Group fields">
  `name` is required, follows the same rule as a skill name, and must be unique among your organization's groups; a taken name returns `409 conflict` on `name`. `display_title` is at most 256 characters and defaults to `name`. `description` is for people browsing the catalog, at most 1,024 characters.
</Accordion>

### Move a skill to a group

`updateSkill` files a skill under a group. Send an empty `skill_group_id` to take the skill out of its group. The skill's versions don't change.

<Tabs>
  <Tab title="Console">
    1. In **Skills**, open the skill.
    2. In the header, pick a group in **Group**, or **Ungrouped**. The change saves at once.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X PATCH 'https://api.recursion.labelbox.com/managed-agents/v1/skills/4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64' \
      -H "Authorization: Bearer $RECURSION_API_KEY" \
      -H 'Content-Type: application/json' \
      -d '{"skill_group_id":"6c2e9a47-1f83-45d0-b7a6-3e8c5f2d9a14"}'
    ```
  </Tab>
</Tabs>

<Accordion title="List, get, and update groups">
  `listSkillGroups` returns `skill_groups` newest first, with `limit` (default 100, at most 500) and `page_token`. `getSkillGroup` returns one group. `updateSkillGroup` changes only the fields you send, so renaming a group is safe: agents attach it by id.

  In the console, open the group's menu in the catalog in **Skills**, click **Edit group**, change the fields, and click **Save group**.

  ```bash theme={"theme":"css-variables"}
  curl -X PATCH 'https://api.recursion.labelbox.com/managed-agents/v1/skill-groups/6c2e9a47-1f83-45d0-b7a6-3e8c5f2d9a14' \
    -H "Authorization: Bearer $RECURSION_API_KEY" \
    -H 'Content-Type: application/json' \
    -d '{"display_title":"Release procedures"}'
  ```
</Accordion>

<Accordion title="List the skills in a group">
  `listSkillsInGroup` returns the group's skills in the order a group attachment expands them, oldest first. That order decides whose description is dropped first when the disclosure budget runs out. It takes `limit` and `page_token` like `listSkills`, and the response has the same shape as the list in [Find a skill](#find-a-skill). In the console, open an agent that attaches the group and click **Show skills** on the group's card to see the same order. The catalog in **Skills** sorts a group's skills by name.

  ```bash theme={"theme":"css-variables"}
  curl 'https://api.recursion.labelbox.com/managed-agents/v1/skill-groups/6c2e9a47-1f83-45d0-b7a6-3e8c5f2d9a14/skills' \
    -H "Authorization: Bearer $RECURSION_API_KEY"
  ```
</Accordion>

<Accordion title="Delete a group">
  In the console, open the group's menu in the catalog in **Skills**, click **Archive group**, then confirm.

  ```bash theme={"theme":"css-variables"}
  curl -X DELETE 'https://api.recursion.labelbox.com/managed-agents/v1/skill-groups/6c2e9a47-1f83-45d0-b7a6-3e8c5f2d9a14' \
    -H "Authorization: Bearer $RECURSION_API_KEY"
  ```

  A delete returns `{ "deleted": true }`. The group's skills stay in the catalog. Agents that attach the group keep the entry, and their next session runs without the group's skills.
</Accordion>

## Delete a skill

A delete is a soft delete. The skill leaves the catalog and stops resolving into new sessions, and its versions can no longer be read through the API. A session that already started keeps the version it loaded until it moves to another agent version. Agents that reference the skill keep the entry, and their next session runs without it. A deleted skill cannot be restored.

<Tabs>
  <Tab title="Console">
    1. In **Skills**, open the skill.
    2. At the bottom of the page, click **Archive skill**, then confirm **Archive skill**.
  </Tab>

  <Tab title="cURL">
    ```bash theme={"theme":"css-variables"}
    curl -X DELETE 'https://api.recursion.labelbox.com/managed-agents/v1/skills/4e7a2c91-6d35-4f80-b8a1-9c3e5d7f2a64' \
      -H "Authorization: Bearer $RECURSION_API_KEY"
    ```
  </Tab>
</Tabs>

```json theme={"theme":"css-variables"}
{ "deleted": true }
```

## How an agent uses a skill

1. When a session starts, it resolves the agent's skills and skill groups and fixes the version of each skill. It does so again only when the session [moves to another agent version](/recursion/sessions#move-a-session-to-another-version).
2. Every turn lists each skill's name and, while the budget allows, its description. See [Check the skills of a saved agent](#check-the-skills-of-a-saved-agent).
3. When a skill fits, the agent calls `activate_skill` with the skill's `name`. The full instructions and the list of bundled files enter the conversation and stay in effect for the rest of the session. After a move to another agent version, the agent activates a skill whose content changed again to read the new instructions.
4. Bundled files are placed under `/workspace/.managed-agents/skills` in the sandbox. The agent reads or runs a file only when the instructions point it there.

`activate_skill` is offered only when the session has at least one usable skill.

## What can go wrong

The most common problems:

* **The agent never activates a skill.** Its description was dropped by the disclosure budget, or doesn't say when to use the skill. [Check the saved agent's skills](#check-the-skills-of-a-saved-agent), move the skill earlier, and write the description as a condition.
* **A new skill version doesn't reach a running session.** Sessions fix skill versions when they start, and again only when they move to another agent version. Start a new session, or save a new agent version and [move the session](/recursion/sessions#move-a-session-to-another-version) to it. Pinned entries never move; update `version` to change them.
* **`400 invalid_request` on `document` or `bundle`.** The frontmatter, instructions, or archive layout is wrong, or the document or bundle is over a limit. Read the full cause below.

<Accordion title="Every skills problem">
  | Symptom or code | Cause | Fix |
  | - | - | - |
  | The agent never activates a skill | Its description was dropped by the disclosure budget, or doesn't say when to use the skill. | [Check the saved agent's skills](#check-the-skills-of-a-saved-agent), move the skill earlier, and write the description as a condition. |
  | A session runs without a skill the agent attaches | The skill or group was deleted after the agent attached it. [`getAgentSkillDisclosure`](#check-the-skills-of-a-saved-agent) reports it in `warnings`. | Remove the entry in a new agent version, or attach a replacement. |
  | A new skill version doesn't reach a running session | Sessions fix skill versions when they start, and again only when they move to another agent version. | Start a new session, or save a new agent version and [move the session](/recursion/sessions#move-a-session-to-another-version) to it. Pinned entries never move; update `version` to change them. |
  | `400 invalid_request` on `document` or `bundle` | The frontmatter is missing, `name` or `description` is missing or malformed, there are no instructions below the frontmatter, the bundle has no `SKILL.md` at the root or one directory down, the wrapper directory's name doesn't match `name`, or the document or bundle is over a limit. | Fix the frontmatter or the archive layout, and check the [limits](#limits). |
  | `400 invalid_request` on a list filter | `skill_group_id` and `ungrouped` are combined, `search` is shorter than 2 characters, or `page_token` was sent with different filters. | Send one filter, and reuse the same filters with a page token. |
  | `400 invalid_request` on `skills` or `skills[n]` | An agent has more than 100 entries, or an entry names a skill, version, or group that doesn't exist, is deleted, or belongs to another organization. | Use groups to shorten the list, and check the ids in the named entry. |
  | `400 invalid_request` on `model` from `getAgentSkillDisclosure` | The agent's model can no longer start sessions. | Save a new agent version with a model from the model list. |
  | `404 not_found` | A skill, version, or group doesn't exist, is deleted, or belongs to another organization. | Check the id. |
  | `409 conflict` on `name` | Another skill or group already uses the name. | Pick another name, or publish a new version of the existing skill. |
  | `409 revision_conflict` | `base_skill_version_id` is no longer the latest version. | Get the skill again, apply your change, and retry. |
  | `413 payload_too_large` | The whole request is larger than the upload limit. | Shrink the bundle to fit the [limits](#limits). |
  | `429 rate_limit_exceeded` | Your organization is over its request rate. | Wait for the time in `Retry-After`. Retry only when the operation's retry strategy in **Endpoints** permits replay. |

  The full error catalog is on [Errors](/recursion/errors).
</Accordion>

## Limits

Skill description, bundle, entry, list page, and search limits are in [Limits](/recursion/limits), with request sizes and rates. These apply too:

| Limit | Value |
| - | - |
| Skill and group `name` | 64 characters |
| `compatibility` in frontmatter | 500 characters |
| `SKILL.md` | 256 KiB |
| Instructions a session loads | 64,000 bytes of UTF-8 after escaping, then cut off |
| Skill and group `display_title` | 256 characters |
| Group `description` | 1,024 characters |
| `listSkillGroups` and `listSkillsInGroup` page size | 500, default 100 |

## Next steps

<CardGroup cols={2}>
  <Card title="Agents" href="/recursion/agents">
    Publish an agent version with skills and groups attached.
  </Card>

  <Card title="Tools" href="/recursion/tools">
    See what else the agent can call once a skill is active.
  </Card>

  <Card title="Sessions" href="/recursion/sessions">
    Start a session and watch the agent activate a skill.
  </Card>

  <Card title="Artifacts" href="/recursion/artifacts">
    Collect the files a skill tells the agent to produce.
  </Card>
</CardGroup>
