# How Kimi CLI Skills Register and Load Custom Prompts: From SKILL.md to System Prompt

> Discover how Kimi CLI skills automatically register and load custom prompts from SKILL.md directly into the LLM system prompt for enhanced functionality. Learn the process now.

- Repository: [Moonshot AI/kimi-cli](https://github.com/MoonshotAI/kimi-cli)
- Tags: internals
- Published: 2026-07-21

---

**Skills in Kimi CLI are auto-discovered from filesystem roots, registered as `/skill:<name>` slash commands by `KimiSoul`, and loaded on demand by injecting the contents of [`SKILL.md`](https://github.com/MoonshotAI/kimi-cli/blob/main/SKILL.md) directly into the LLM system prompt.**

If you are building custom behaviors for the MoonshotAI/kimi-cli agent, understanding how skills register and load custom prompts is critical to extending the system. The Kimi CLI runtime performs a three-stage pipeline at startup: it crawls configurable skill roots, indexes every valid [`SKILL.md`](https://github.com/MoonshotAI/kimi-cli/blob/main/SKILL.md) into a typed `Skill` model, and defers markdown loading until the user actually triggers a slash command. The entire lifecycle is implemented in the `src/kimi_cli/skill/` and `src/kimi_cli/soul/` packages.

## How Kimi CLI Discovers Skills From the Filesystem

At startup, `KimiCLI.create` walks a set of **skill roots** that include the user directory `~/.kimi/skills`, a project-local `skills` folder, any directories passed via `--skills-dir`, and built-in skill packages.

The entry point is `discover_skills_from_roots` in [`src/kimi_cli/skill/__init__.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/skill/__init__.py). This async function iterates each `ScopedSkillsRoot` and inspects subdirectories expecting a `<name>/SKILL.md` layout, as well as flat markdown files named `<name>.md`. For every valid skill manifest found, it builds a **Skill** Pydantic model capturing:

- `name` — derived from the directory or filename, or overridden by front-matter
- `description` — from front-matter `description:` or the first non-empty line of the body
- `type` — `standard` or `flow`, detected by the presence of a Mermaid/D2 code block
- `path` — the absolute location of the markdown file

```python

# src/kimi_cli/skill/__init__.py

async def discover_skills_from_roots(
    roots: Sequence[ScopedSkillsRoot],
) -> list[Skill]:
    ...
    skill_md = entry / "SKILL.md"
    ...
    # build Skill(name, description, type, path, scope, …)

```

A lower-level helper, `discover_skills`, handles a single root and returns its list of `Skill` objects:

```python

# src/kimi_cli/skill/__init__.py

async def discover_skills(root_path: KaosPath, scope: str) -> list[Skill]:
    ...

```

Because discovery runs during the bootstrap phase, new skills placed in any configured root become available immediately on the next CLI launch without modifying source code.

## Registering Skill Slash Commands in KimiSoul

Once the catalogue is built, the runtime core `KimiSoul` receives the full list of `Skill` objects and registers a slash command for each one. The registration logic lives in `_make_skill_runner` inside [`src/kimi_cli/soul/kimisoul.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/kimisoul.py):

```python

# src/kimi_cli/soul/kimisoul.py

def _make_skill_runner(self, skill: Skill) -> Callable[[KimiSoul, str], ...]:
    async def _run_skill(soul: KimiSoul, args: str, *, _skill: Skill = skill):
        # Load the markdown and inject it as a system prompt

        skill_md = await read_skill_text(_skill)
        if skill_md:
            await soul.append_system_prompt(skill_md)
        ...

```

The bound command name uses the prefix defined centrally in [`src/kimi_cli/ui/shell/slash.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/ui/shell/slash.py):

```python

# src/kimi_cli/ui/shell/slash.py

SKILL_COMMAND_PREFIX = "skill:"

```

This means a skill named `my-example` is exposed to the user as `/skill:my-example`, and a flow skill is exposed as `/flow:<name>`. The design keeps prompt material out of memory until the exact moment the user invokes the command.

## Loading Custom Prompts from SKILL.md

When a slash command fires, the runner calls `read_skill_text` in [`src/kimi_cli/skill/__init__.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/skill/__init__.py) to lazily load the prompt content:

```python

# src/kimi_cli/skill/__init__.py

async def read_skill_text(skill: Skill) -> str | None:
    """
    Read the SKILL.md contents for a skill.
    """
    ...

```

This helper opens the file, strips any YAML front-matter, and returns the raw markdown body. Kimi CLI then appends that text to the agent’s **system prompt** via `soul.append_system_prompt`, ensuring the LLM receives the custom instructions in the very next request.

Because the markdown is read on demand rather than at startup, the system remains lightweight; large prompts do not consume memory unless they are actively used.

## Standard Skills vs Flow Skills

Kimi CLI distinguishes two skill types based on the contents of [`SKILL.md`](https://github.com/MoonshotAI/kimi-cli/blob/main/SKILL.md):

- **Standard skills** inject the prompt text directly. They are invoked with `/skill:<name>`.
- **Flow skills** contain a Mermaid or D2 diagram inside a code block. During discovery, the parser extracts this diagram into `skill.flow`.

When a user runs `/flow:<name>`, the engine interprets and executes the diagram nodes in sequence. When the same skill is invoked via `/skill:<name>`, the diagram is ignored and the raw [`SKILL.md`](https://github.com/MoonshotAI/kimi-cli/blob/main/SKILL.md) body is treated as a plain system prompt. This dual-mode behavior lets a single markdown file serve both as executable workflow and as static context.

## End-to-End Example: Creating and Invoking a Custom Skill

The following walkthrough shows how skills register and load custom prompts in practice.

### Create a skill directory and manifest

```bash
mkdir -p ~/.kimi/skills/my-example
cat > ~/.kimi/skills/my-example/SKILL.md <<'EOF'
---
name: my-example
description: "Guidelines for generating concise commit messages"
---

# Commit-Message Skill

Write a short, conventional commit message summarising the change.
EOF

```

### Invoke the skill from the shell

```bash
kimi --yolo --prompt /skill:my-example

```

**What happens under the hood:**

1. `discover_skills_from_roots` finds `~/.kimi/skills/my-example/SKILL.md` at startup and builds a `Skill` object.
2. `KimiSoul._make_skill_runner` registers the command `skill:my-example`.
3. When `/skill:my-example` is parsed, `_run_skill` awaits `read_skill_text`, retrieves the markdown above, and appends it to the system prompt.
4. The LLM call now includes the commit-message guidelines, and the model responds accordingly.

### Run a flow skill

If [`flow-review/SKILL.md`](https://github.com/MoonshotAI/kimi-cli/blob/main/flow-review/SKILL.md) contains a Mermaid diagram, you can execute it:

```bash
kimi --prompt /flow:flow-review

```

To load the same file as a static prompt instead, use `/skill:flow-review`.

## Summary

- **Discovery:** `discover_skills_from_roots` in [`src/kimi_cli/skill/__init__.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/skill/__init__.py) crawls user, project, and built-in roots to index [`SKILL.md`](https://github.com/MoonshotAI/kimi-cli/blob/main/SKILL.md) files into `Skill` models.
- **Registration:** `KimiSoul._make_skill_runner` in [`src/kimi_cli/soul/kimisoul.py`](https://github.com/MoonshotAI/kimi-cli/blob/main/src/kimi_cli/soul/kimisoul.py) dynamically creates `/skill:<name>` and `/flow:<name>` slash commands using the `SKILL_COMMAND_PREFIX` constant.
- **Loading:** `read_skill_text` lazily reads [`SKILL.md`](https://github.com/MoonshotAI/kimi-cli/blob/main/SKILL.md), strips front-matter, and feeds the markdown body into the system prompt via `append_system_prompt`.
- **Extensibility:** Because skills are file-based and loaded on demand, dropping a new folder into any skill root instantly exposes a new command without restarting the underlying runtime.

## Frequently Asked Questions

### How does Kimi CLI know where to look for skills?

Kimi CLI evaluates multiple scoped roots: the user directory at `~/.kimi/skills`, a local `skills` folder in the current project, any paths passed with `--skills-dir`, and built-in packages. The `discover_skills_from_roots` function aggregates results from every root into a single catalogue.

### What file format is required for a custom skill?

Each skill must provide a markdown file named [`SKILL.md`](https://github.com/MoonshotAI/kimi-cli/blob/main/SKILL.md). The file may include optional YAML front-matter for `name` and `description`, followed by the body text that becomes the prompt. Flat files named `<skill>.md` are also supported depending on the root scanner.

### Why does the skill prompt load at invocation time instead of startup?

The `_make_skill_runner` closure defers I/O by calling `read_skill_text` only when the user types the slash command. This lazy-loading strategy keeps the CLI memory footprint low and makes skill startup near-instant even when skill files are large.

### Can a single skill be used as both a prompt and an executable flow?

Yes. If [`SKILL.md`](https://github.com/MoonshotAI/kimi-cli/blob/main/SKILL.md) contains a Mermaid or D2 diagram, the discovery logic classifies it as a flow skill and stores the diagram in `skill.flow`. You can run the diagram with `/flow:<name>` or load the raw markdown as a system prompt with `/skill:<name>`.