How Kimi CLI Skills Register and Load Custom Prompts: From SKILL.md to System Prompt
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 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 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. 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-matterdescription— from front-matterdescription:or the first non-empty line of the bodytype—standardorflow, detected by the presence of a Mermaid/D2 code blockpath— the absolute location of the markdown file
# 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:
# 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:
# 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:
# 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 to lazily load the prompt content:
# 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:
- 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 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
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
kimi --yolo --prompt /skill:my-example
What happens under the hood:
discover_skills_from_rootsfinds~/.kimi/skills/my-example/SKILL.mdat startup and builds aSkillobject.KimiSoul._make_skill_runnerregisters the commandskill:my-example.- When
/skill:my-exampleis parsed,_run_skillawaitsread_skill_text, retrieves the markdown above, and appends it to the system prompt. - The LLM call now includes the commit-message guidelines, and the model responds accordingly.
Run a flow skill
If flow-review/SKILL.md contains a Mermaid diagram, you can execute it:
kimi --prompt /flow:flow-review
To load the same file as a static prompt instead, use /skill:flow-review.
Summary
- Discovery:
discover_skills_from_rootsinsrc/kimi_cli/skill/__init__.pycrawls user, project, and built-in roots to indexSKILL.mdfiles intoSkillmodels. - Registration:
KimiSoul._make_skill_runnerinsrc/kimi_cli/soul/kimisoul.pydynamically creates/skill:<name>and/flow:<name>slash commands using theSKILL_COMMAND_PREFIXconstant. - Loading:
read_skill_textlazily readsSKILL.md, strips front-matter, and feeds the markdown body into the system prompt viaappend_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. 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 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>.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →