> ## 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.

# 扩展点

> 不改平台代码就能加进 StarForge 的所有东西，以及怎么选对那一种。

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

平台有九个扩展点。先回答一个问题，它决定了其余的一切：**平台会执行你的代码，还是只读你的声明？**

## 全图

| 你想加的东西        | 用                                                 | 平台执行你的代码吗      | 怎么发布                     |
| ------------- | ------------------------------------------------- | -------------- | ------------------------ |
| 一种训练方法        | [recipe pack](/zh-Hans/extend/recipe-packs)       | 否              | `sf plugin publish`      |
| 一个评测集         | [benchmark pack](/zh-Hans/extend/benchmark-packs) | 否              | `sf plugin publish`      |
| 给裁判用的判分标准     | [rubric](/zh-Hans/extend/rubrics)                 | 否              | 控制台，或 `PUT /api/rubrics` |
| 诊断阈值          | `playbook` 包（管理员）                                 | 否              | `sf plugin publish`      |
| 诊断 / 裁判的提示词   | `prompt` 包（管理员）                                   | 否              | `sf plugin publish`      |
| 训练循环的运行期补丁    | [algorithm 插件](/zh-Hans/extend/algorithm-plugins) | 在**你自己的**训练容器里 | `sf plugin publish`      |
| Agent RL 任务集  | [环境](/zh-Hans/extend/environments)                | —              | `sf env push`            |
| 驱动模型走完环境的那段代码 | `environment` 插件（harness）                         | 在**你自己的**训练容器里 | `sf plugin publish`      |
| 本地数据预处理步骤     | `data-prep` 插件                                    | 在**你自己的**机器上   | `sf plugin publish`      |

## 决定这一切形状的那条规则

<Warning>
  控制平面永远不装载第三方代码。现在不会，将来加个开关也不会。
</Warning>

控制台进程持有数据库连接、JWT 签名密钥和对象存储凭据。在那里跑插件，等于把整个平台交给发布者。

所以代码类插件只会在**已经属于你**的地方执行：你的训练容器，或者你自己的笔记本。
要改控制平面的行为，请跨进程边界：通知通道用 webhook，agent 工具用外部 MCP server。

这也是为什么没有 `runs_in: console` 这一档插件，以及为什么将来也不会有。

## 声明包里不能有代码

平台只读不执行的那类包（`recipe`、`benchmark`、`playbook`、`prompt`），
**根层出现可执行文件就会在发布期被拒**——`.py`、`.sh`、`.so`、`.js` 以及另外约二十种后缀。

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
my-grpo-variant/
├── plugin.yaml         # kind: recipe
├── recipe.yaml         # 平台要解析的那份声明
├── README.md
└── template/           # 唯一允许放可执行文件的地方
    ├── config.yaml
    └── run.sh
```

`template/` 是例外，理由值得知道：那是 `sf new` 拷进**你的**仓库、由**你**在**你的**容器里跑的脚手架，
平台一行都不碰。

这把「平台永不执行声明包」从一句承诺变成了脚本能检查的事实。踩到时的报错：

```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
recipe is a declarative pack -- the platform parses it and never runs it, so it must
contain no executable files: helper.py. Scaffolding meant to be handed to users belongs
under template/; if the platform really has to execute code, use one of the
algorithm/environment/data-prep kinds instead.
```

## 代码插件要过三道闸

只要代码会被执行，就要过同样的三道内容摘要校验。它们保证「集群上跑的」和「你发布的」是同一份东西。

<Steps>
  <Step title="发布时">
    服务端解包重算内容摘要，与客户端声明的比对，不一致就拒绝。
  </Step>

  <Step title="提交时">
    校验 JobSpec 里的 `(id, version, digest)` 三元组与库内记录一致，且插件未被管理员禁用。
  </Step>

  <Step title="启动时">
    launcher 对真正注入到作业里的目录再算一次摘要。与 JobSpec 锁定值不同，作业直接拒绝启动——
    宁可不跑，也不产出一个没人能复现的结果。
  </Step>
</Steps>

摘要是对**目录内容**算的（排序后的相对路径 + 文件字节），不是对 tar 包算的。
tar 的 hash 会把 mtime 和 uid 吃进去，同一份代码打两次包会得到两个 digest，锁定也就没意义了。

<Note>
  `__pycache__`、`.git`、`.venv`、`*.pyc` 和 `.DS_Store` 不参与摘要。它们是环境副产物，不是插件内容。
</Note>

## 代码包还要多过两道检查

发布期的结构校验，让错误在你的笔记本上就暴露，而不是在集群跑了四十分钟之后：

<AccordionGroup>
  <Accordion title="entrypoint 模块必须真实存在" icon="terminal">
    `entrypoint: patch:install` 要求包里有 `patch.py` 或 `patch/__init__.py`。拼错在发布期就被拒：

    ```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    entrypoint module 'pathc' does not exist in the package (expected pathc.py or
    pathc/__init__.py). A misspelled entry point would otherwise fail only when training
    starts, so it is refused here.
    ```
  </Accordion>

  <Accordion title="顶层名不得遮蔽真实依赖" icon="shield">
    插件根目录会加入 `sys.path`，所以根层一个叫 `torch.py` 的文件会让训练进程之后所有的
    `import torch` 都拿到**你的**文件。症状离病因极远，因此这些名字是保留字，直接拒绝：

    ```text theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
    starforge  nemo_rl  common  ray  torch  transformers  vllm
    trl  verl  numpy  yaml  json  os  sys
    ```
  </Accordion>
</AccordionGroup>

## 版本不可变

同一个 `1.2.0` 发两次会得到 `409`。修 bug 请发 `1.2.1`。

这是 digest 锁定成立的前提：版本能被覆盖，锁文件指向的东西就可能已经不存在了。
`README.md` 也一样在摘要覆盖范围内，所以改长描述同样要换版本号。
「文档说的和代码做的不一致」是插件市场最常见的坑，这是防住它最便宜的办法。

## 命名与命名空间

| 对象     | 引用形式                         | 说明                                |
| ------ | ---------------------------- | --------------------------------- |
| 插件     | `<owner>/<name>`             | owner 由平台在发布时盖章，不是 manifest 里自己写的 |
| 已发布的方法 | `<framework>/<owner>.<name>` | 多出来的这一段让「和内置方法撞名」在结构上不可能发生        |
| 环境     | `<owner>/<name>@<version>`   |                                   |
| Rubric | `<owner>/<name>`             |                                   |

## 治理

```bash theme={"theme":{"light":"github-light","dark":"github-dark-dimmed"}}
sf plugin disable alice/opsd-patch   # 管理员：新作业不得再引用它
sf plugin enable alice/opsd-patch
```

三个动词，三个不同的作用域，别混为一谈：

| 动词      | 谁   | 作用域                           |
| ------- | --- | ----------------------------- |
| 启用 / 禁用 | 管理员 | 整套部署。下架一个东西，保留历史              |
| 引用      | 你   | 单个实验，写在 `plugins.lock.json` 里 |
| 同步      | 自动  | 你的本地方法库。无状态——删了再同步一次即可        |

禁用只挡新提交。已经在跑和已经排队的作业不受影响。

## 下一步

<Columns cols={2}>
  <Card title="插件包" icon="package" href="/zh-Hans/extend/plugins" arrow="true">
    manifest、目录结构、发布与安装。
  </Card>

  <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>

  <Card title="Benchmark 包" icon="gauge" href="/zh-Hans/extend/benchmark-packs" arrow="true">
    声明跑什么、读哪些数。
  </Card>

  <Card title="环境" icon="joystick" href="/zh-Hans/extend/environments" arrow="true">
    Agent RL 的任务集，以及四种协议。
  </Card>

  <Card title="Verifier" icon="scale" href="/zh-Hans/extend/verifiers" arrow="true">
    判定任务是否完成的三种方式。
  </Card>
</Columns>
