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

# 用 Plugin 组合生命周期

> 用 definePlugin 为实验、评估组、Sandbox 和评估组合多个生命周期。

Plugin 是带稳定身份的生命周期组合方式。它适合把一项条件在不同运行范围内需要的 setup 和 teardown 放在一起。

Plugin 不配置 Agent，不提供另一套 Sandbox 命令或资源系统。Agent 的 Skill、MCP server、原生 Plugin 和配置文件仍直接传给 Agent factory；Sandbox image 和命令仍写在 `sandbox` 声明中。

## 定义多个生命周期

所有 Plugin API 都从 `niceeval/plugin` 导入：

```ts theme={null}
// experiments/shared/memory.ts
import { definePlugin } from "niceeval/plugin";

type MemoryOptions = {
  readonly model: string;
};

export const memory = definePlugin<MemoryOptions>({
  name: "acme.memory",
  behaviorRevision: "1",
  instanceKey: ({ model }) => model,
  experiment: ({ model }) => ({
    identity: { model },
    setup: (ctx) => ctx.progress({ message: `启动 ${model} memory service` }),
    teardown: (ctx) => ctx.diagnostic({
      code: "memory-service-stopped",
      level: "warning",
      message: `${model} memory service stopped`,
    }),
  }),
  sandbox: ({ model }) => ({
    identity: { model },
    setup: (_sandbox, ctx) => ctx.progress({ message: "连接当前 Sandbox" }),
    teardown: (_sandbox, ctx) => ctx.progress({ message: "断开当前 Sandbox" }),
  }),
});
```

`name` 命名 Plugin family。`instanceKey(options)` 区分同一 family 的不同配置。实现语义改变时提高 `behaviorRevision`，让旧结果不再被精确沿用。

无参数 family 可以省略 `instanceKey`，其固定实例键是 `"default"`。每个 fragment 可以声明 `identity`，并且必须至少提供 `setup` 或 `teardown`。

## 在一个位置组合多个 Plugin

`plugins` 始终接收数组：

```ts theme={null}
export default defineExperiment({
  agent: codexAgent({
    skills: [{ kind: "local", path: ".agents/skills/memory" }],
  }),
  model: "gpt-5.6-luna",
  plugins: [
    memory({ model: "gpt-5.6-luna" }),
    telemetry(),
  ],
  sandbox: dockerSandbox({
    source: { type: "image", image: "acme/codex-memory:1" },
  }),
});
```

Setup 按数组顺序运行，teardown 按逆序运行。一个 Plugin 的 setup 失败时，NiceEval 仍会调用该 occurrence 的 teardown，并继续执行其它终结 callback。

## 选择生命周期范围

一个 family 可以声明四种 fragment：

| fragment              | 运行次数                         |
| --------------------- | ---------------------------- |
| `experiment(options)` | 每个有实际工作要运行的实验一次              |
| `group(options)`      | 每个实验与评估组组合一次，替换 Sandbox 时不重复 |
| `sandbox(options)`    | 每台实际物理 Sandbox 一次，替换后重新运行    |
| `eval(options)`       | 每条实际运行的 Attempt 一次           |

调用方只在 `defineExperiment`、`defineEvalGroup` 或 `defineEval` 的 `plugins` 数组挂载 occurrence。若 occurrence 同时声明 `sandbox` fragment，NiceEval 会自动把它用于该 owner 对应的物理 Sandbox；`SandboxLayer` 没有额外的 Plugin 挂载语法。

```ts theme={null}
export default defineEval({
  plugins: [fixtureLifecycle(), diagnosticsLifecycle()],
  async test(t) {
    const turn = await t.send("完成任务");
    await turn.succeeded().orStop();
  },
});
```

完整沿用的 Attempt 不运行 Plugin lifecycle。部分沿用时，只有实际运行的范围会激活对应 lifecycle。

## 先检查计划

先确认 Plugin identity 和实际需要运行的 Attempt：

```bash theme={null}
npx niceeval exp codex-memory --dry
npx niceeval exp codex-memory memory/smoke
```

修改 setup 或 teardown 的行为时，同时提高 `behaviorRevision`。只改 callback 函数体但不改身份，NiceEval 无法知道旧结果对应的行为已经变化。

## 接着看

* [实验与生命周期](/docs/zh/tutorials/write-experiment)——配置 Agent、Provider 与实验级生命周期。
* [评估组](/docs/zh/tutorials/eval-groups)——让同组 Attempt 串行共享一台 Sandbox。
* [重跑与沿用](/docs/zh/tutorials/rerun-and-cache)——身份变化怎样影响已有结果。
