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

# 插件包

> manifest、目录结构，以及发布—安装—引用这条链路。

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf plugin publish ./my-plugin
```

一个插件就是一个含 `plugin.yaml` 的目录。打包格式仅此而已——没有构建步骤，没有 wheel，也不需要注册账号。

## 目录结构

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
my-plugin/
├── plugin.yaml       # manifest，必需
├── README.md         # 长描述，可选，展示在详情页
└── <你的文件>         # 代码类插件放代码，声明类插件放声明
```

## plugin.yaml

```yaml plugin.yaml theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
schema: forge/plugin/v1
name: opsd-patch
version: 1.2.0
kind: algorithm
entrypoint: patch:install
load: eager
summary: OPSD teacher-sampling correction

requires:
  core: ">=0.3,<1"

# 货架元数据 —— 只影响展示与检索，不影响装载与安全
icon: 🧩
author: Alice
license: Apache-2.0
homepage: https://github.com/alice/opsd-patch
keywords: [opsd, distillation]
```

### 必填字段

<ParamField path="schema" type="string" required>
  永远是 `forge/plugin/v1`。写别的会被拒，并告诉你这个版本支持哪个 schema。
</ParamField>

<ParamField path="name" type="string" required>
  叶子名。平台在发布时把你的账号盖上去，所以对外 id 是 `<owner>/<name>`——owner 不用你自己写。
</ParamField>

<ParamField path="version" type="string" required>
  首字符是字母或数字，之后允许字母、数字、`.`、`_`、`-`。一旦发布就不可变。
</ParamField>

<ParamField path="kind" type="string" required>
  `algorithm`、`environment`、`data-prep`、`recipe`、`benchmark`、`playbook`、`prompt` 之一。
  它决定这个包被怎么对待的一切。
</ParamField>

### 代码类插件必填

<ParamField path="entrypoint" type="module.path:callable">
  `algorithm` 和 `environment` 必填。模块路径相对于包根目录，且发布时必须真实存在。

  其他 kind 一律不接受。`data-prep` 插件按 `prepare_*.py` 约定发现；
  声明包什么都不执行，因此根本没有入口。
</ParamField>

<ParamField path="load" type="eager | deferred" default="eager">
  launcher 什么时候调用你的入口。见 [algorithm 插件](/zh-Hans/extend/algorithm-plugins)。
</ParamField>

### 可选字段

<ParamField path="requires.core" type="PEP 440 specifier">
  你的插件适用于哪些 `starforge-core` 版本，例如 `">=0.3,<1"`。启动时校验；
  不满足会以 `plugin x@1.2.0 requires SDK >=0.3,<1, this one is 0.2.9` 停下，
  而不是在更靠后、更古怪的地方失败。
</ParamField>

<ParamField path="summary" type="string">
  一句话，列表里展示。
</ParamField>

<ParamField path="icon" type="emoji">
  货架卡片上的一个 emoji，最多 8 字节。留空则按 kind 取默认图标。
</ParamField>

<ParamField path="author" type="string">
  谁写的。与 `owner` 不同——后者是发布它的平台账号。
</ParamField>

<ParamField path="license, homepage, keywords" type="string, url, string[]">
  只用于展示与检索。`homepage` 必须是 `http(s)`。keywords 最多 12 个，会小写并去重。
</ParamField>

### README.md

包根的 `README.md` 会成为详情页的长描述。上限 64 KB，超出部分截断——详情页要的是介绍，不是手册。

它有意不做成 manifest 字段：长文本塞进 YAML 会逼作者转义，也失去 Markdown 工具链。
它在包摘要的覆盖范围内，所以改它同样要换版本号。

## 七种 kind

| Kind          | 执行 | 在哪执行   | entrypoint             | 谁能发布     |
| ------------- | -- | ------ | ---------------------- | -------- |
| `algorithm`   | 是  | 你的训练容器 | 必填                     | 任何人      |
| `environment` | 是  | 你的训练容器 | 必填                     | 任何人      |
| `data-prep`   | 是  | 你的机器   | 无——按 `prepare_*.py` 约定 | 任何人      |
| `recipe`      | 否  | —      | 禁止                     | 任何人      |
| `benchmark`   | 否  | —      | 禁止                     | 任何人      |
| `playbook`    | 否  | —      | 禁止                     | **仅管理员** |
| `prompt`      | 否  | —      | 禁止                     | **仅管理员** |

<Info>
  `playbook` 和 `prompt` 是部署级单例：只有一份生效，而且会改变所有人在诊断页上看到的东西。
  它们只允许管理员发布，因为后果落在别人头上。也没有按用户区分的版本——
  「这是谁的诊断结论」是一个不该存在的问题。
</Info>

## 发布

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf plugin publish ./my-plugin
sf plugin publish ./my-plugin --owner platform-team   # 仅管理员
```

客户端先在本地校验 manifest 和目录结构、算出目录摘要，然后上传。服务端重算摘要，不一致就拒绝。

| 失败    | 含义                             |
| ----- | ------------------------------ |
| `409` | 这个版本已存在。版本不可变——请升号             |
| `400` | manifest 或结构有问题；`detail` 会点名字段 |
| `403` | 往不属于你的命名空间发布                   |

## 安装与引用

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf plugin ls                                      # 这套部署有哪些插件
sf plugin info alice/opsd-patch                   # manifest、版本历史、digest
sf plugin install alice/opsd-patch                # 下载到本地 ./forge_plugins/
sf plugin install alice/opsd-patch --exp my-grpo  # 并锁进某个实验
```

`--exp` 会往实验里写 `plugins.lock.json`：引用加它的 digest。提交时平台读这个锁文件，
把**正好那个版本**注入作业包的 `forge_plugins/<name>/`，并把三元组记进 JobSpec。

```json plugins.lock.json theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
{
  "plugins": [
    { "id": "alice/opsd-patch", "version": "1.2.0", "digest": "sha256:9f2c…" }
  ]
}
```

<Warning>
  两个会解包到同名叶子目录的插件不能在同一个作业里组合使用。改名其中一个。
</Warning>

## 确认成功

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf plugin info alice/opsd-patch
```

会显示你刚发布的版本和它的 digest。提交引用了它的作业之后，作业日志里每个装载的插件有一行：

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
plugin  : alice/opsd-patch@1.2.0 loaded
```

如果注入之后内容被改动过，作业会拒绝启动：

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
plugin alice/opsd-patch@1.2.0 content digest mismatch: spec=sha256:9f2c…, actual=sha256:41ab…
```

## 下一步

<Columns cols={2}>
  <Card title="Algorithm 插件" icon="code" href="/zh-Hans/extend/algorithm-plugins" arrow="true">
    你的入口函数收到什么，什么时候被调用。
  </Card>

  <Card title="Recipe 包" icon="book-marked" href="/zh-Hans/extend/recipe-packs" arrow="true">
    把一种训练方法写成声明。
  </Card>
</Columns>
