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

# Skills on Disk

> Install registry skills as plain folders your framework loads natively, locked to exact bytes for CI and Docker: decimalai skills add, install --frozen, update, verify.

`decimalai skills` installs registry skills into your project as ordinary folders, `.agents/skills/<install-name>/`, the layout every native skill loader reads. It works like a package manager. `.decimal/skills.toml` lists the skills the project wants, `.decimal/skills.lock` records the exact bytes that were written, and `decimalai skills install --frozen` reproduces those bytes in CI or Docker, or refuses with an exit code that says why.

Added in SDK 0.15.0. A project set up by 0.14 or older has a version 1 lock; run [`decimalai skills migrate`](#upgrading-from-an-older-sdk) once.

> [!NOTE]
>
> This is the **disk channel**: your framework reads the files itself. The [Skill Router](/guides/skills) is the other channel: it puts skills into the prompt at run time and needs no files. Pick one per agent. A skill that is both routed and on disk reaches the prompt twice, and the adapters log a warning when they see the router's loader inside a runtime that loads `SKILL.md` from disk.

## Quick start

**1. Install the SDK**

```bash theme={null}
pip install "decimalai>=0.15"
```

The core package is enough: no framework extra is needed for disk installs.

**2. Add a skill**

```bash theme={null}
decimalai skills add acme/refund-policy
```

The reference can be a registry name, a url\_slug, an install name or a skill id. Without `DECIMAL_API_KEY`, only public skills are available, fetched anonymously.

**3. Point your framework at the folder**

`decimalai.skills_dir()` returns the absolute path of `.agents/skills`. Each framework has a one-line entry point; see [the table below](#point-your-framework-at-it) and [Native skill loaders](/guides/native-skill-loaders). Claude Code loads the whole root as a plugin:

```bash theme={null}
claude --plugin-dir "$(git rev-parse --show-toplevel)/.agents"
```

**4. Commit, and install frozen in CI**

Commit `.decimal/skills.toml`, `.decimal/skills.lock` and `.agents/`. CI and Docker run `decimalai skills install --frozen`.

```text theme={null}
$ decimalai skills add acme/refund-policy
  ✓ added acme/refund-policy v3 as .agents/skills/acme-refund-policy

  Commit .decimal/skills.toml, .decimal/skills.lock and .agents/; CI and Docker run `decimalai skills install --frozen`.
  Point your framework's skill loader at .agents/skills (decimalai.skills_dir()); Claude Code loads it with:
    claude --plugin-dir "$(git rev-parse --show-toplevel)/.agents"
```

## What `skills add` writes

```text theme={null}
<project>/
  .agents/
    .claude-plugin/plugin.json      names the root as a Claude Code plugin    (commit)
    skills/
      acme-refund-policy/           a real folder, never a symlink            (commit)
        SKILL.md
        references/fees.md
        scripts/compute_refund.py   mode 0755
      my-own-skill/                 yours: not in the lock, never touched
  .decimal/
    skills.toml                     what the project wants                    (commit)
    skills.lock                     exactly what is installed                 (commit)
    .gitignore                      keeps the engine's scratch files out of git
```

**The project** is found from the working directory upwards. The search stops at the first folder with a `.git` entry and never goes above your home directory; within that range the project is the nearest folder holding `.decimal/skills.toml` or `.decimal/skills.lock`, else the nearest holding any `.decimal/`, else the repository root. Every command takes `--directory DIR` to name it instead.

**The root is always `.agents/skills`.** The manifest and the lock record it, and any other value is refused.

### `.agents/skills/<install-name>/`

The registry's own files for one version of the skill, written byte for byte: `SKILL.md` plus its bundled files under `scripts/`, `references/`, `templates/` and `assets/`. Bundled scripts that the skill marks executable are written with mode `0755`, everything else `0644`. A version's `eval.yaml` is not part of the folder; [`skills pull`](/sdk/cli#decimalai-skills-pull) writes it for a loose copy.

`SKILL.md` carries the skill's identity under `metadata`, where every loader keeps it:

```markdown theme={null}
---
name: acme-refund-policy
description: "Apply Acme refund policy rules when a customer asks for a refund, a return or store credit."
license: "Apache-2.0"
allowed-tools: "Read"
metadata:
  decimalai-skill-id: "6f1c…"
  decimalai-version-id: "9b2e…"
  decimalai-version: "3"
  decimalai-body-sha256: "<64 hex>"
  decimalai-name: "acme/refund-policy"
  decimalai-format: "1"
  decimalai-invocation: "model"
  decimalai-category: "support"
  decimalai-source: "https://app.decimal.ai/s/acme-refund-policy@3/SKILL.md"
---
# Acme refund policy
…
```

* `name` is the install name, which is also the folder name.
* `metadata.decimalai-name` is the registry name. `decimalai-body-sha256` is the sha256 of the body with surrounding whitespace stripped, so you can tell an edited copy from the original.
* The top-level keys are only ever `name`, `description`, `license`, `compatibility`, `allowed-tools`, `disable-model-invocation` and `metadata`. `allowed-tools` and `compatibility` are single strings, never lists. `decimalai-source` is written for public skills only.
* `disable-model-invocation: true` is written for a skill whose owner made it user-only. Only Claude Code honours it; [the other seven loaders offer it to the model](/guides/native-skill-loaders#known-upstream-gaps).

**`allowed-tools` depends on who wrote the skill.** Claude Code pre-approves every tool a skill lists, so a skill your organization did not author (a public skill, a skill you use, a fork, a GitHub import) keeps only `Read`, `Grep` and `Glob`; its scripts still run, with your permission. A skill your organization authored keeps its list, except that a bare `Bash` becomes one scoped entry per bundled script, such as `Bash(python3 ${CLAUDE_SKILL_DIR}/scripts/compute_refund.py *)`. Every dropped or rewritten tool is printed when the skill is installed.

**Binary attachments are not installed.** The registry stores text attachments only. A file it cannot serve is named in a warning and recorded in the lock as `unavailable`, so `verify` does not report it missing. If you put such a file there yourself, updates carry it over.

### `.decimal/skills.toml`

```toml theme={null}
# Managed by `decimalai skills`. Commit this file. Hand edits are fine;
# run `decimalai skills install` afterwards.
root = ".agents/skills"

[skills]
"acme/refund-policy" = "latest"
"pdf-tools" = "1"
```

Keys are canonical registry names: `skills add` given a url\_slug, an install name or a skill id records the name the registry answers with. A value is `"latest"` or a version number written as a string; there are no ranges. A skill added by its id is written as `"name" = { version = "latest", id = "…" }`. The engine rewrites only the lines whose entry changed, so your comments and other lines stay as you wrote them.

### `.decimal/skills.lock`

```json theme={null}
{
  "lock_hash": "<64 hex>",
  "root": ".agents/skills",
  "skills": {
    "acme-refund-policy": {
      "name": "acme/refund-policy",
      "skill_id": "6f1c…", "version_id": "9b2e…", "version": 3,
      "render": {"format": 1, "hash": "<64 hex>", "variant": "foreign"},
      "body_sha256": "…", "description_sha256": "…",
      "path": ".agents/skills/acme-refund-policy",
      "files": {"SKILL.md": "…", "references/fees.md": "…", "scripts/compute_refund.py": "…"},
      "executable": ["scripts/compute_refund.py"],
      "unavailable": [],
      "source": {"base_url": "https://api.decimal.ai", "org": "<your org id, or null>"},
      "pin": "latest",
      "invocation": "model",
      "installed_at": "2026-10-15T12:04:44Z"
    }
  },
  "version": 2
}
```

One entry per installed skill, keyed by install name. `files` holds the sha256 of every file exactly as written, and `render` names the exact rendering, so the same bytes can be fetched again later. `lock_hash` covers every entry except `installed_at`, so two machines that install the same lock agree. Never edit the lock by hand: a frozen install refuses a lock whose `lock_hash` does not recompute (exit 6), and `skills install` recomputes it with a warning.

`source.base_url` records where the skill came from and is informational only. The API key is only ever sent to the registry you configure (`--base-url` or `DECIMAL_BASE_URL`), never to a URL read from a committed lock.

### `.agents/.claude-plugin/plugin.json`

```json theme={null}
{"name":"decimalai-skills","version":"lock-<first 12 hex of lock_hash>","description":"Skills installed by `decimalai skills`. Managed: edit through the CLI.","author":{"name":"decimalai"}}
```

It makes `.agents` a Claude Code plugin named `decimalai-skills`, so Claude Code lists the skills as `decimalai-skills:<install-name>`. It is not named `decimalai`, which is the [DecimalAI marketplace plugin](/guides/claude-code); two plugins with one name replace each other. The engine refuses to overwrite a `plugin.json` it did not write, unless you pass `--force`, which backs it up first.

### `.decimal/.gitignore`

Lists the engine's working files so they are never committed: `install.json`, `staging/`, `backup/`, `migrated-v1/`, `skills.lock.journal` and `.skills.lck`. Missing lines are appended; your own lines stay.

## Install names

The install name is the folder name and the `name:` in `SKILL.md`. The registry chooses it for your project, from the names the project already uses:

| Registry name | Install name |
| - | - |
| `acme/refund-policy` | `acme-refund-policy` |
| `pdf-v2` | `pdf-v2` |
| `Ünïcödé Helper` | `unicode-helper` |
| a second skill whose name also gives `acme-refund-policy` | `acme-refund-policy-3f9a1c` |
| `日本語スキル` | `skill-<6 hex>` |

* The base form lowercases the name, drops accents, and turns every other character run into one `-`.
* The hashed form, up to 57 characters of the base plus `-` and 6 hex of the skill's id, is used when the base is taken in this project, or when the base would be unusable: empty, over 64 characters, reserved (`install`, `search`, `check`, `decimalai-skills`), a name that lost characters other than Latin letters, only the `owner` part of `owner/name`, or a word YAML reads as something other than text (`404`, `yes`, a date).
* Names compare case-insensitively, because `Acme/` and `acme/` are one folder on macOS and Windows.
* **An installed skill keeps its folder name for good**, through upstream renames. Only `remove` followed by `add` picks up a new one.

## Versions: latest or exact

```bash theme={null}
decimalai skills add acme/refund-policy      # "latest": `skills update` moves it forward
decimalai skills add acme/refund-policy@3    # exact: stays at version 3
decimalai skills add acme/refund-policy@latest
```

Adding a skill that is already installed with another version changes the version, as `cargo add` does. **`skills install` never moves a version**: it installs what the manifest and lock say. Moving forward is `skills update`:

| Command | What moves |
| - | - |
| `decimalai skills update` | Every `latest` entry, to the registry's current version. Exact pins stay. |
| `decimalai skills update acme-refund-policy@4` | That skill, to version 4, and the manifest now says `"4"`. |
| `decimalai skills update acme-refund-policy@latest` | That skill, to the current version, and the manifest now says `"latest"`. |
| `decimalai skills update acme-refund-policy` | On an exact pin: its files are refreshed at the same version. On `latest`: the current version. |

Each changed folder is replaced whole, so a file the new version dropped disappears. If one skill cannot be updated, none is. `--dry-run` says what would change and changes nothing; `--dry-run --json` gives the plan to a script.

The owner can also edit a version's description, license, compatibility, allowed tools or invocation setting without publishing a new version. `skills status --remote` reports that as *description changed upstream (same version)* or *rendered differently upstream (same version)*, and `skills update` delivers it. A frozen install keeps reproducing the bytes the lock names until someone runs `update`.

For another organization's skill, `latest` skips a version whose safety scan failed and installs the newest one that passed, and says so. Asking for that failed version exactly is refused (exit 7).

## Commands

| Command | Network | What it does |
| - | - | - |
| `skills add <ref>[@N]` | yes | Records the skill in the manifest, installs it, locks it. |
| `skills install` | only for new entries and missing folders | Makes the folders and the lock match the manifest. Never moves a version. |
| `skills install --frozen` | only for missing folders | CI and Docker: installs exactly what the lock records, writes neither the manifest nor the lock. |
| `skills update [<name>[@N]]` | yes | Moves `latest` entries (or one named skill) forward. |
| `skills remove <name>` | no | Deletes the folder and its manifest and lock entries. Nothing changes on the registry. |
| `skills status [--remote] [--check]` | only with `--remote` | Each folder against the lock; with `--remote`, outdated, re-described and revoked skills. |
| `skills verify [PATH]` | no | Checks folders the way the native loaders read them. |
| `skills migrate` | yes | Moves a 0.14-or-older project to the manifest and a version 2 lock. |
| `skills pull <ref>` | yes | A loose copy of a public skill, outside any project: nothing locks or updates it. |

Every flag and output is in the [CLI reference](/sdk/cli#skills-on-disk). The same engine is a Python API:

```python theme={null}
import decimalai
from decimalai import skillpkg

result = skillpkg.add("acme/refund-policy")
skillpkg.install(frozen=True)
skillpkg.update(dry_run=True)
skillpkg.status(remote=True)
skillpkg.verify()
skillpkg.remove("acme-refund-policy")
skillpkg.migrate()

print(result.exit_code, result.changes, result.problems)
decimalai.skills_dir()          # the absolute path of .agents/skills
```

Each call returns a `Result` with the same `changes`, `problems` and `exit_code` the command prints; a refusal is returned, not raised.

## The trust gate

Every skill passes the registry's safety review before it is written:

| Safety | `skills add`, `install`, `update` |
| - | - |
| Passed | Installed. |
| Caution, or not yet reviewed | Asked about on a terminal. Without one (CI, a script), refused with exit 7 unless you pass `--yes`. |
| Withheld (pending review) | Refused (exit 7), unless your organization owns the skill or uses it. A skill you use is then asked about like Caution. |
| Blocked | Refused (exit 7) for everyone, owners included. |

Skills your organization owns are never asked about. The question comes once per version: after you accept a version, `install` of the same version does not ask again, and **`install --frozen` never asks**, because the lock records that the version was accepted.

If the registry later blocks or withholds a locked version, a frozen install that has to fetch it refuses:

```text theme={null}
  ✗ acme/refund-policy: version 3 is no longer available (blocked); run `decimalai skills update acme/refund-policy` or `decimalai skills remove acme/refund-policy`
```

A frozen install over a committed root fetches nothing, so it cannot notice; `skills status --remote` reports the locked version as *revoked*.

What the bands mean, and what scanning cannot catch: [How skills are vetted](/guides/trust-safety/how-skills-are-vetted).

## Commit the root, or gitignore it

**Commit it (the default).** Commit `.decimal/skills.toml`, `.decimal/skills.lock` and `.agents/`. A fresh clone works with no install step, and code review shows exactly which skill text changed. In CI, `install --frozen` re-checks every committed byte against the lock and fetches nothing.

**Or gitignore the root.** Add `.agents/skills/` and `.agents/.claude-plugin/` to your `.gitignore` and commit only the two `.decimal/` files. Then every checkout runs `install --frozen`, which fetches each missing folder by its exact lock entry and checks every byte against it. Skills you wrote yourself in `.agents/skills/` would be ignored too, so keep those elsewhere if you choose this.

> [!WARNING]
>
> A committed lock names the skills you install and records your organization's id (`source.org`). A committed root also contains each skill's full text. In a public repository, that includes private skills of your own.

`skills status` warns when git ignores `.decimal/skills.lock` or `.decimal/skills.toml`.

### CI

```yaml theme={null}
# .github/workflows/ci.yml (excerpt)
- run: pip install "decimalai>=0.15"
- run: decimalai skills install --frozen
  env:
    DECIMAL_API_KEY: ${{ secrets.DECIMAL_API_KEY }}   # only needed for non-public skills
- run: decimalai skills verify
```

`install --frozen` exits 4 when someone edited `.decimal/skills.toml` without running `skills install`, and 5 when a committed folder no longer matches the lock. With a committed root, add `--offline` to make the job fail (exit 8) rather than fetch anything. A scheduled job can run `decimalai skills status --remote --check`, which exits 1 when a skill is outdated, re-described upstream or revoked.

### Docker

With the root committed, the image needs no network and no key:

```dockerfile theme={null}
FROM python:3.12-slim
WORKDIR /app
RUN pip install "decimalai>=0.15"
COPY . .
RUN decimalai skills install --frozen --offline
```

With the root gitignored, fetch it at build time. Public skills need no key. For your own skills, pass the key as a build secret, so it never lands in an image layer:

```dockerfile theme={null}
# syntax=docker/dockerfile:1
FROM python:3.12-slim
WORKDIR /app
RUN pip install "decimalai>=0.15"
COPY . .
RUN --mount=type=secret,id=decimal_api_key \
    DECIMAL_API_KEY="$(cat /run/secrets/decimal_api_key)" \
    decimalai skills install --frozen
```

```bash theme={null}
docker build --secret id=decimal_api_key,env=DECIMAL_API_KEY .
```

A lock written with your key records your organization's rendering of skills it authored. A key from another organization cannot fetch that rendering (exit 7), so build with a key of the organization that wrote the lock.

## Local edits, `--force` and backups

A managed folder is the registry's bytes. When a file in it differs from the lock, `status` shows the folder as `modified`, and every command that would replace it stops with exit 5 instead:

```text theme={null}
$ decimalai skills install --frozen
  ✗ acme/refund-policy: `.agents/skills/acme-refund-policy` has local changes (SKILL.md); keep your edits by copying the folder to a new name (a skill of your own), or re-run with `--force` to replace them
```

To keep your edits, copy the folder to a new name: a folder the lock does not list is yours, and the engine never touches it. To discard them, re-run with `--force`. **`--force` first copies the folder as it was to `.decimal/backup/<txid>/<install-name>/`**, where `<txid>` is the time of the run plus a random suffix, and prints that path:

```text theme={null}
$ decimalai skills install --frozen --force
  ✓ restored acme/refund-policy v3 in .agents/skills/acme-refund-policy; a copy of the folder as it was is saved in .decimal/backup/20261015T120446Z-20a85ae9/acme-refund-policy
```

| Command | What `--force` lets it do |
| - | - |
| `skills add` | Replace a folder the lock does not list that already uses the skill's name. |
| `skills install`, `install --frozen` | Restore folders with local edits to their locked files. |
| `skills update` | Replace folders with local edits by the new version. |
| `skills remove` | Remove a folder with local edits. |

On every one of them, `--force` also replaces a `.agents/.claude-plugin/plugin.json` the engine did not write, after backing it up. It never overrides a symlink, or a file where a skill folder belongs (exit 5): the engine never writes through a symlink inside the project. Backups are gitignored and never deleted by the engine; remove them when you no longer need them.

Without `--force`, `skills install` leaves a modified folder alone and says so; only the commands above that must replace it stop.

**A crash is recoverable.** Every command fetches and checks everything before it changes a folder, then swaps whole folders one at a time and writes the lock after each one. A run that stops halfway leaves each folder either as it was or as it should be, never half written. The next `add`, `install`, `update` or `remove` finishes or rolls back the interrupted change; until then `status` shows it as `interrupted`, and `install --frozen`, which never writes the lock, refuses with exit 1. Two `decimalai skills` commands in one project never run at once: the second waits up to 30 seconds for `.decimal/.skills.lck`, then exits 1.

## `status` and `verify`

`skills status` reads the project and changes nothing. Each folder is `clean`, `modified` (with the files), `missing` (locked, not on disk), `unmanaged` (a folder the lock does not list) or `illegal` (a symlink or a file), and it reports a manifest that disagrees with the lock, an interrupted run and a version 1 lock:

```text theme={null}
$ decimalai skills status --remote
  Installed skills (.agents/skills in /home/me/support-bot):
    ✓ acme-refund-policy               acme/refund-policy v3 (latest)           clean  [outdated (v3 -> v4)]
    ✓ pdf-tools                        pdf-tools v1 (pinned)                    clean
  ⚠ acme/refund-policy: outdated (v3 -> v4); run `decimalai skills update`
```

When `./skills` holds skills you wrote and an API key is set, a second section compares them with the platform, as `decimalai skills sync` sees them. `--check` makes `status` exit 1 when the installed skills have drifted, for CI.

`skills verify` checks folders the way the eight [native loaders](/guides/native-skill-loaders) read them, and catches what a loader would drop silently. Seven of the eight skip or rename a folder that breaks the format without raising an error, so a bad folder in production usually shows up as a skill that is simply never used.

* **Managed folders** (in the lock) must hold exactly their locked files, hashes and exec bits; every rule is an error.
* **Your own folders** in the root, and any tree you name with `skills verify PATH`, are checked by the format rules every loader needs (errors) and the format's own rules (warnings, or errors with `--strict`).

```text theme={null}
$ decimalai skills verify
  ⚠ warning F2 .agents/skills/my-own/SKILL.md: unknown top-level key 'category'; format 1 keeps it as metadata.decimalai-category
  ✗ error F3 .agents/skills/my-own/SKILL.md: name 'acme/my-own' differs from its folder 'my-own'
  ✗ error F3 .agents/skills/my-own/SKILL.md: name 'acme/my-own' must be lowercase letters, digits and single hyphens, at most 64 characters
  2 error(s), 1 warning(s)
```

`decimalai skills verify ./skills --strict` is a CI check for a repository of skills you author. The rule ids are listed in the [CLI reference](/sdk/cli#decimalai-skills-verify).

## Exit codes

Stable across releases, so CI and Dockerfiles can branch on them. Every refusal names the skill, the reason and the command that fixes it.

| Code | Meaning |
| - | - |
| 0 | OK |
| 1 | `status --check` or `verify` found problems; an interrupted run left a folder that matches neither its old nor its new state; another `decimalai skills` run holds the project; the filesystem refused a write |
| 2 | Usage error |
| 3 | The manifest or lock is missing or unreadable, or the lock is a version 1 or npx lock (`skills migrate`) |
| 4 | `install --frozen`: the lock is out of date with `.decimal/skills.toml` |
| 5 | Local edits, a folder the lock does not list, or a symlink block the operation (`--force` for the first two) |
| 6 | Integrity: served bytes differ from the declared or locked hashes, or `lock_hash` does not recompute |
| 7 | Unavailable: not found, ambiguous, withheld, blocked, version unsafe, no access, name taken, already installed under another name, not in this project (`remove`, `update`), not confirmed (`--yes`), or a registry too old for this client |
| 8 | Network failure (a spent quota included), or `--offline` needed a fetch |

## Point your framework at it

`decimalai.skills_dir()` finds the project from the working directory and returns `.agents/skills` as an absolute path. It raises `SkillsNotInstalledError` when there is no lock or no root yet (run `decimalai skills install`), and `LegacyLockError` for a version 1 lock (run `decimalai skills migrate`).

| Framework | One line |
| - | - |
| Claude Agent SDK | `ClaudeAgentOptions(plugins=[{"type": "local", "path": str(decimalai.skills_dir().parent)}], skills="all")` |
| Claude Code | `claude --plugin-dir "$(git rev-parse --show-toplevel)/.agents"` |
| Google ADK | `SkillToolset(skills=load_skills_from_dir(decimalai.skills_dir()), code_executor=UnsafeLocalCodeExecutor())` |
| CrewAI | `Agent(..., skills=[decimalai.skills_dir()])` |
| OpenAI Agents (`SandboxAgent`) | `Skills(lazy_from=LocalDirLazySkillSource(source=LocalDir(src=decimalai.skills_dir(relative_to=Path.cwd()))))` |
| Deep Agents | `create_deep_agent(model=model, skills=[str(decimalai.skills_dir())], backend=LocalShellBackend(root_dir=project, virtual_mode=False))` |
| Microsoft Agent Framework | `SkillsProvider.from_paths(str(decimalai.skills_dir()), script_runner=run_script)` |
| Pydantic AI harness | `Skills(str(decimalai.skills_dir(relative_to=project)))` inside `LocalWorkspace(project)` |
| Strands Agents | `AgentSkills(skills=str(decimalai.skills_dir()), strict=True)` |

`project` is the project directory (`decimalai.skills_dir().parents[1]`), and `run_script` is a script runner you provide. The full snippet for each, what each one reads and the gaps each has upstream: [Native skill loaders](/guides/native-skill-loaders).

**After `skills update`, restart the agent process.** A loader that has already read a skill can keep serving the old version: Microsoft Agent Framework caches skills by default, and a running Claude Code session can keep the skills it already loaded. Each loader is checked in a fresh process, so a reload is the safe assumption.

## How usage is counted

* **Fetching a skill for disk is an install, not a delivery.** Each fetch is counted on its own, by organization, day and command (frozen CI installs included), and never as the skill being served to a model.
* **Traces name installed skills by registry name.** A loader reports a skill by its folder name, by `decimalai-skills:<install-name>` (Claude Code) or by its path. Before the SDK sends a trace, it looks those names up in the lock of the project the process runs in and adds the registry name, skill id, version id and body hash, so the activation counts for that exact version. A folder with local edits is reported with the hash of its edited body and no version id, so it is never credited to the registry version.
* For a framework with no DecimalAI adapter, report a load yourself inside a trace with `decimalai.log_skill_activation(name="acme-refund-policy")`; the same lookup adds the ids. See [Skills in the SDK](/sdk/python/skills).

## Upgrading from an older SDK

SDK 0.14 and older wrote a version 1 `.decimal/skills.lock` (keyed by registry name, with no record of the bytes) and per-agent folders such as `.claude/skills/`. 0.15 refuses that lock and asks you to migrate:

```text theme={null}
$ decimalai skills install
  ✗ `.decimal/skills.lock` is a version 1 lock (or has no version), written by an older decimalai; run `decimalai skills migrate`
```

```bash theme={null}
decimalai skills migrate
git add .decimal .agents && git commit -m "Move skills to decimalai 0.15"
```

`migrate` resolves every entry again, installs each skill fresh from the registry, and writes the manifest, a version 2 lock and `plugin.json`. Old folders inside `.agents/skills` move to `.decimal/migrated-v1/`; old folders anywhere else stay where they are; **nothing is deleted**. What moves where, step by step: [Migrations](/migrations#upgrading-the-sdk-skills-on-disk).

> [!WARNING]
>
> Upgrade everyone who works on the project together. `decimalai skills export` from 0.14 or older, run in a 0.15 project, merges version 1 entries into the version 2 lock. 0.15 then refuses `install --frozen` (exit 3), and `skills install` drops those entries and leaves their folders as unmanaged.

### What changed in the files

| | 0.14 and older | 0.15 |
| - | - | - |
| Where | One folder per agent you named (`.claude/skills/`, `.agents/skills/`, …), project or home | One root, `.agents/skills/`, in the project |
| Folder and `name:` | A sanitized folder (`acme-refund-policy/`) whose `name:` is still the registry name (`acme/refund-policy`), which most loaders reject or rename | The install name in both, valid in every loader |
| Identity | None in the file from the SDK; top-level `source:` and `source_sha256:` from the raw `/s/` route | `metadata.decimalai-*`: skill id, version id, version, body hash, registry name |
| `allowed-tools`, `compatibility` | YAML lists, which several loaders reject | Always one string |
| Taxonomy | Top-level `category:`, `skill-scope:`, `invocation:` | Under `metadata` |
| Scripts | Written without the exec bit | `0755` when the skill marks them executable |
| Lock | Version 1: names and versions | Version 2: every file's sha256, the exact rendering, a `lock_hash` |
| Stale files | Left behind by an update | Removed: each update replaces the whole folder |
| Background writes | The adapters could mirror your organization's skills to disk while your agent ran | None: disk changes only when you run a `decimalai skills` command |

## Limits

* **Windows** is not tested in this release: folder renames, exec bits and long paths.
* **One root per project**, inside it. For a copy in your home directory (personal Claude Code skills), use `decimalai skills pull <ref> --out ~/.claude/skills`, a loose copy that nothing updates.
* **A 0.15 client needs the current registry.** Against a registry without the install routes, every command that reaches the registry exits 7 naming the missing route; it never falls back to the routes 0.14 used.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.