Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/plugin-custom-agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": minor
---

Add support for plugin-contributed custom agents, discovered automatically and available for sub-agent delegation. Ship an `agents/` directory in the plugin (or declare `agents` paths in the plugin manifest) to provide them.
4 changes: 3 additions & 1 deletion docs/en/customization/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Beyond the three built-in sub-agents, you can define your own agents as Markdown

### Agent Locations

Kimi Code CLI discovers agent files by scope; more specific scopes take higher priority: **Explicit (`--agent-file`) > Project > Extra > User > Built-in**. When two files define the same `name`, the higher-priority scope wins. Each directory is scanned recursively for `.md` files.
Kimi Code CLI discovers agent files by scope; more specific scopes take higher priority: **Explicit (`--agent-file`) > Project > Extra > User > Plugin > Built-in**. When two files define the same `name`, the higher-priority scope wins. Each directory is scanned recursively for `.md` files.

**User level** (applies to all projects):
- `$KIMI_CODE_HOME/agents/` (default: `~/.kimi-code/agents/`)
Expand All @@ -63,6 +63,8 @@ The Kimi-specific user agent directory moves with `KIMI_CODE_HOME`, while the ge
extra_agent_dirs = ["~/team-agents", ".agents/team-agents"]
```

**Plugin level**: directories declared in an enabled plugin's manifest `agents` field (when omitted, the `agents/` directory under the plugin root is picked up automatically); see [Plugin Agents](./plugins.md#plugin-agents). Plugin agents outrank only the built-in agents.

**Built-in agents** are distributed with the CLI and have the lowest priority. A directory-discovered file does not override a same-name built-in Agent unless its frontmatter declares `override: true`. A file loaded through `--agent-file` is treated as explicit launch intent, may override a same-name built-in Agent, outranks every directory scope, and applies to the current launch only. Separately, `$KIMI_CODE_HOME/SYSTEM.md` permanently overrides the default main agent's system prompt (it is not part of agent-file discovery); its precedence interactions are covered in the SYSTEM.md section below.

::: warning Trust model
Expand Down
16 changes: 15 additions & 1 deletion docs/en/customization/plugins.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Plugins

Plugins package reusable Kimi Code CLI capabilities into installable units — they can add [Agent Skills](./skills.md), automatically load a specified Skill at session start, contribute system-prompt instructions, and declare MCP servers to provide real tool capabilities. They are ideal for sharing workflows with a team, connecting to external services, or installing extensions from the official marketplace.
Plugins package reusable Kimi Code CLI capabilities into installable units — they can add [Agent Skills](./skills.md), custom [agents](./agents.md), automatically load a specified Skill at session start, contribute system-prompt instructions, and declare MCP servers to provide real tool capabilities. They are ideal for sharing workflows with a team, connecting to external services, or installing extensions from the official marketplace.

## Installation and Management

Expand Down Expand Up @@ -162,6 +162,7 @@ Supported fields:
| `version`, `description`, `keywords`, `author`, `homepage`, `license` | Display metadata |
| `interface` | Fields shown in `/plugins`: `displayName`, `shortDescription`, `longDescription`, `developerName`, `websiteURL` |
| `skills` | One or more `./` paths; must be within the plugin root directory. When omitted, the `SKILL.md` in the root directory is treated as a single Skill root |
| `agents` | One or more `./` paths; must be within the plugin root directory and point to directories containing [agent files](./agents.md#custom-agents). When omitted, the `agents/` directory under the plugin root (if present) is picked up automatically |
| `sessionStart.skill` | Loads the specified plugin Skill into the main Agent when a new or resumed session starts |
| `skillInstructions` | Additional instructions appended whenever a Skill from this plugin is loaded |
| `systemPrompt` | Inline instructions contributed to the agent's system prompt while the plugin is enabled |
Expand Down Expand Up @@ -271,6 +272,19 @@ my-plugin/

Regardless of how a Skill is loaded (`sessionStart.skill`, `/skill:<name>`, or automatic model invocation), `skillInstructions` appears alongside that plugin's Skill.

## Plugin Agents

A plugin can ship custom agents: declare one or more `./` directories in the manifest's `agents` field (or simply place an `agents/` directory under the plugin root). The agent files inside use the same format as [custom agents](./agents.md#custom-agents) and, while the plugin is enabled, are discovered automatically and can be delegated to as sub-agents by the main Agent.

```text
my-plugin/
kimi.plugin.json
agents/
reviewer.md
```

Plugin agents rank below every other file source: on a name collision, user-level, extra, project-level, and `--agent-file` agents all win over the plugin-provided one, and replacing a built-in agent still requires an explicit `override: true` in the frontmatter. After installing, enabling, disabling, or removing a plugin, the agent list refreshes in a new session (or on `/reload`); on the v2 engine the live session also refreshes after `/plugins reload`.

## MCP Servers in Plugins

When a plugin needs real tool capabilities, it can declare `mcpServers` in its manifest, reusing the [MCP](./mcp.md) schema.
Expand Down
4 changes: 3 additions & 1 deletion docs/zh/customization/agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Kimi Code CLI 内置三种子 Agent,开箱即用,分别面向不同任务形

### Agent 目录

Kimi Code CLI 按作用域发现 Agent 文件,作用域越具体,优先级越高:**显式(`--agent-file`)> 项目 > 额外 > 用户 > 内置**。两个文件定义了相同的 `name` 时,高优先级作用域胜出。每个目录都会递归扫描 `.md` 文件。
Kimi Code CLI 按作用域发现 Agent 文件,作用域越具体,优先级越高:**显式(`--agent-file`)> 项目 > 额外 > 用户 > Plugin > 内置**。两个文件定义了相同的 `name` 时,高优先级作用域胜出。每个目录都会递归扫描 `.md` 文件。

**用户级**(对所有项目生效):
- `$KIMI_CODE_HOME/agents/`(默认:`~/.kimi-code/agents/`)
Expand All @@ -63,6 +63,8 @@ Kimi 专属的用户 Agent 目录随 `KIMI_CODE_HOME` 移动,通用的 `~/.age
extra_agent_dirs = ["~/team-agents", ".agents/team-agents"]
```

**Plugin 级**:已启用 plugin 在其 manifest 的 `agents` 字段中声明的目录(省略时自动采用 plugin 根下的 `agents/` 目录),见[插件 Agent](./plugins.md#插件-agent)。Plugin Agent 优先级仅高于内置 Agent。

**内置 Agent** 随 CLI 分发,优先级最低。目录中发现的文件不会仅凭同名覆盖内置 Agent;如确需替换,必须在 Frontmatter 中声明 `override: true`。通过 `--agent-file` 加载的文件视为显式启动意图,可以覆盖同名内置 Agent,优先级高于所有目录作用域,且仅对本次启动生效。另外,`$KIMI_CODE_HOME/SYSTEM.md` 可永久覆盖默认主 Agent 的系统提示词(它不参与 Agent 文件发现),其优先级交互见下文 SYSTEM.md 小节。

::: warning 信任模型
Expand Down
16 changes: 15 additions & 1 deletion docs/zh/customization/plugins.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Plugins

Plugins 把可复用的 Kimi Code CLI 能力打包成可安装单元——可以添加 [Agent Skills](./skills.md)、在会话启动时自动加载指定 Skill、提供系统提示词指令,也可以声明 MCP servers 来提供真实工具能力。适合把工作流共享给团队、连接外部服务,或从官方 marketplace 安装扩展。
Plugins 把可复用的 Kimi Code CLI 能力打包成可安装单元——可以添加 [Agent Skills](./skills.md)、自定义 [Agent](./agents.md)、在会话启动时自动加载指定 Skill、提供系统提示词指令,也可以声明 MCP servers 来提供真实工具能力。适合把工作流共享给团队、连接外部服务,或从官方 marketplace 安装扩展。

## 安装与管理

Expand Down Expand Up @@ -162,6 +162,7 @@ Plugin 是一个带 manifest 的目录或 zip 文件。Manifest 可以放在以
| `version`、`description`、`keywords`、`author`、`homepage`、`license` | 展示元数据 |
| `interface` | 在 `/plugins` 中展示的字段:`displayName`、`shortDescription`、`longDescription`、`developerName`、`websiteURL` |
| `skills` | 一个或多个 `./` 路径,必须位于 plugin 根目录内。省略时根目录的 `SKILL.md` 被当作单个 Skill root |
| `agents` | 一个或多个 `./` 路径,必须位于 plugin 根目录内,指向含有 [Agent 文件](./agents.md#自定义-agent)的目录。省略时根下的 `agents/` 目录(若存在)被自动采用 |
| `sessionStart.skill` | 在新会话或恢复会话开始时,把指定 plugin Skill 加载到主 Agent |
| `skillInstructions` | 每次加载此 plugin 的 Skill 时一并附带的额外说明 |
| `systemPrompt` | plugin 启用期间提供给 Agent 系统提示词的内联指令 |
Expand Down Expand Up @@ -271,6 +272,19 @@ my-plugin/

无论 Skill 通过哪种方式加载(`sessionStart.skill`、`/skill:<name>` 或模型自动调用),`skillInstructions` 都会随该 plugin 的 Skill 一起出现。

## 插件 Agent

Plugin 可以携带自定义 Agent:在 manifest 的 `agents` 字段里声明一个或多个 `./` 目录(或直接在 plugin 根下放置 `agents/` 目录),其中的 Agent 文件与[自定义 Agent](./agents.md#自定义-agent) 格式相同,会在 plugin 启用期间作为子 Agent 被主 Agent 自动发现和委派。

```text
my-plugin/
kimi.plugin.json
agents/
reviewer.md
```

Plugin Agent 的优先级低于其他文件来源:同名时用户级、额外目录、项目级和 `--agent-file` 的 Agent 都会覆盖 plugin 提供的版本;替换内置 Agent 同样需要在 frontmatter 里显式写 `override: true`。安装、启用、禁用或移除 plugin 后,Agent 列表在新会话(或 `/reload`)时刷新;v2 引擎的当前会话还会在 `/plugins reload` 后刷新。

## Plugin 中的 MCP servers

当 plugin 需要真实工具能力时,可以在 manifest 中声明 `mcpServers`,复用 [MCP](./mcp.md) 的 schema。
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core-v2/docs/state-manifest.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,7 @@ export interface AgentStateSnapshot {
'llmRequester.lastConfigLogSignature': string | undefined;
'llmRequester.mediaDegradedTurns': Set<number>;
'llmRequester.mediaStrippedTurns': Map<number, /* MediaStripSnapshot — packages/agent-core-v2/src/agent/contextProjector/contextProjector.ts */ {
readonly "__@mediaStripSnapshotBrand@2678": undefined;
readonly "__@mediaStripSnapshotBrand@2681": undefined;
}>;
'llmRequester.turnConfigs': Map<number, /* TurnRequestConfig — packages/agent-core-v2/src/agent/llmRequester/llmRequesterService.ts */ {
readonly resolved: /* ProfileModelContext — packages/agent-core-v2/src/agent/profile/profile.ts */ {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
* collisions). Mirrors `skillCatalog/skillSource`, with one deliberate
* deviation: `explicit` outranks every other source (in the skill system it
* aliases `user`) because `--agent-file` is a one-shot command-line intent that
* must always win. Concrete sources (user at App scope; project / extra /
* explicit at Session scope) each bind their own DI token extending this
* contract.
* must always win. Concrete sources (user at App scope; plugin / project /
* extra / explicit at Session scope) each bind their own DI token extending
* this contract.
*
* A source may mark `load()` failures as `fatal`: the Session catalog lets
* them propagate into `ready` so awaiters see the error (`explicit` does —
Expand Down Expand Up @@ -38,6 +38,7 @@ export interface AgentProfileContribution {
}

export const AGENT_PROFILE_SOURCE_PRIORITY = {
plugin: 5,
user: 10,
extra: 20,
project: 30,
Expand Down
2 changes: 1 addition & 1 deletion packages/agent-core-v2/src/app/agentFileCatalog/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import type { AgentModelPreference } from '#/app/agentProfileCatalog/agentProfileCatalog';

export type AgentFileSource = 'project' | 'user' | 'extra' | 'explicit';
export type AgentFileSource = 'plugin' | 'project' | 'user' | 'extra' | 'explicit';

export interface AgentFileRoot {
readonly path: string;
Expand Down
12 changes: 12 additions & 0 deletions packages/agent-core-v2/src/app/plugin/manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import path from 'node:path';
import { Error2, PluginErrors } from '#/errors';
import type { HookDef } from '#/agent/externalHooks/types';
import type { McpServerConfig } from '#/agent/mcp/config-schema';
import type { AgentFileRoot } from '#/app/agentFileCatalog/types';
import { discoverFileSkills } from '#/app/skillCatalog/fileSkillDiscovery';
import type { SkillDiscoveryResult } from '#/app/skillCatalog/skillDiscovery';
import type { SkillRoot } from '#/app/skillCatalog/types';
Expand Down Expand Up @@ -313,6 +314,17 @@ export class PluginManager {
return roots;
}

pluginAgentRoots(): readonly AgentFileRoot[] {
const roots: AgentFileRoot[] = [];
for (const record of this.records.values()) {
if (!record.enabled || record.state !== 'ok' || record.manifest === undefined) continue;
for (const dir of record.manifest.agents ?? []) {
roots.push({ path: dir, source: 'plugin' });
}
}
return roots;
}

enabledSessionStarts(): readonly EnabledPluginSessionStart[] {
const out: EnabledPluginSessionStart[] = [];
for (const record of this.records.values()) {
Expand Down
22 changes: 16 additions & 6 deletions packages/agent-core-v2/src/app/plugin/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,22 @@ export async function parseManifest(pluginRoot: string): Promise<ParsedManifestR
return { manifestKind, manifestPath, shadowedManifestPath, diagnostics };
}

let skills = await resolveSkillsField(pluginRoot, raw['skills'], diagnostics);
let skills = await resolveDirListField(pluginRoot, 'skills', raw['skills'], diagnostics);
if (raw['skills'] === undefined) {
const rootSkillMd = path.join(pluginRoot, 'SKILL.md');
if (await isFile(rootSkillMd)) {
skills = [pluginRoot];
}
}

let agents = await resolveDirListField(pluginRoot, 'agents', raw['agents'], diagnostics);
if (raw['agents'] === undefined) {
const agentsDir = path.join(pluginRoot, 'agents');
if (await isDir(agentsDir)) {
agents = [agentsDir];
}
}

const skillInstructions =
typeof raw['skillInstructions'] === 'string' ? raw['skillInstructions'] : undefined;

Expand All @@ -121,6 +129,7 @@ export async function parseManifest(pluginRoot: string): Promise<ParsedManifestR
license: stringField(raw, 'license'),
author: readAuthor(raw['author']),
skills,
agents,
sessionStart: readSessionStart(raw['sessionStart'], diagnostics),
mcpServers: await readMcpServers(pluginRoot, raw['mcpServers'], diagnostics),
hooks: readHooks(raw['hooks'], diagnostics),
Expand All @@ -146,8 +155,9 @@ function recordUnsupportedRuntimeFields(
}
}

async function resolveSkillsField(
async function resolveDirListField(
pluginRoot: string,
field: string,
raw: unknown,
diagnostics: PluginDiagnostic[],
): Promise<readonly string[]> {
Expand All @@ -158,7 +168,7 @@ async function resolveSkillsField(
} else if (Array.isArray(raw) && raw.every((entry) => typeof entry === 'string')) {
entries.push(...raw);
} else {
diagnostics.push({ severity: 'error', message: '"skills" must be a string or string[]' });
diagnostics.push({ severity: 'error', message: `"${field}" must be a string or string[]` });
return [];
}

Expand All @@ -167,7 +177,7 @@ async function resolveSkillsField(
if (!entry.startsWith('./')) {
diagnostics.push({
severity: 'error',
message: `"skills" path must start with "./" (got "${entry}")`,
message: `"${field}" path must start with "./" (got "${entry}")`,
});
continue;
}
Expand All @@ -182,14 +192,14 @@ async function resolveSkillsField(
if (!isWithin(real, rootReal)) {
diagnostics.push({
severity: 'error',
message: `"skills" path resolves outside the plugin (${entry})`,
message: `"${field}" path resolves outside the plugin (${entry})`,
});
continue;
}
if (!(await isDir(real))) {
diagnostics.push({
severity: 'warn',
message: `"skills" path is not a directory (${entry})`,
message: `"${field}" path is not a directory (${entry})`,
});
continue;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/agent-core-v2/src/app/plugin/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { createDecorator, type ServiceIdentifier } from '#/_base/di/instantiatio
import type { Event } from '#/_base/event';
import type { HookDef } from '#/agent/externalHooks/types';
import type { McpServerConfig } from '#/agent/mcp/config-schema';
import type { AgentFileRoot } from '#/app/agentFileCatalog/types';
import type { SkillRoot } from '#/app/skillCatalog/types';

import type {
Expand Down Expand Up @@ -59,6 +60,7 @@ export interface IPluginService {
listPluginCommands(): Promise<readonly PluginCommandDef[]>;
checkUpdates(): Promise<readonly PluginUpdateStatus[]>;
pluginSkillRoots(): Promise<readonly SkillRoot[]>;
pluginAgentRoots(): Promise<readonly AgentFileRoot[]>;
enabledSessionStarts(): Promise<readonly EnabledPluginSessionStart[]>;
enabledSystemPrompts(): Promise<readonly EnabledPluginSystemPrompt[]>;
enabledMcpServers(): Promise<Record<string, McpServerConfig>>;
Expand Down
5 changes: 5 additions & 0 deletions packages/agent-core-v2/src/app/plugin/pluginService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { IProviderService } from '#/kosong/provider/provider';
import { ISkillDiscovery } from '#/app/skillCatalog/skillDiscovery';
import type { HookDef } from '#/agent/externalHooks/types';
import type { McpServerConfig } from '#/agent/mcp/config-schema';
import type { AgentFileRoot } from '#/app/agentFileCatalog/types';
import type { SkillRoot } from '#/app/skillCatalog/types';

import { PluginManager } from './manager';
Expand Down Expand Up @@ -158,6 +159,10 @@ export class PluginService extends Disposable implements IPluginService {
return this.runConsumptionRead([], async () => this.manager.pluginSkillRoots());
}

pluginAgentRoots(): Promise<readonly AgentFileRoot[]> {
return this.runConsumptionRead([], async () => this.manager.pluginAgentRoots());
}

enabledSessionStarts(): Promise<readonly EnabledPluginSessionStart[]> {
return this.runConsumptionRead([], async () => this.manager.enabledSessionStarts());
}
Expand Down
1 change: 1 addition & 0 deletions packages/agent-core-v2/src/app/plugin/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface PluginManifest {
readonly homepage?: string;
readonly license?: string;
readonly skills?: readonly string[];
readonly agents?: readonly string[];
readonly sessionStart?: PluginSessionStart;
readonly mcpServers?: Readonly<Record<string, McpServerConfig>>;
readonly hooks?: readonly HookDefConfig[];
Expand Down
1 change: 1 addition & 0 deletions packages/agent-core-v2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ export * from '#/session/sessionSkillCatalog/workspaceFileSkillSource';
export * from '#/session/sessionSkillCatalog/pluginSkillSource';
export * from '#/session/sessionAgentProfileCatalog/sessionAgentProfileCatalog';
export * from '#/session/sessionAgentProfileCatalog/sessionAgentProfileCatalogService';
export * from '#/session/sessionAgentProfileCatalog/pluginAgentProfileSource';
export * from '#/session/sessionAgentProfileCatalog/projectFileAgentSource';
export * from '#/session/sessionAgentProfileCatalog/extraFileAgentSource';
export * from '#/session/sessionAgentProfileCatalog/explicitFileAgentSource';
Expand Down
Loading
Loading