setup 和 teardown。
写一对 setup / teardown
setup在这个实验第一个要真正执行的 Attempt 之前运行。teardown在全部 Attempt 结束后运行,运行被中断时也会执行。- 只要
setup开始过,teardown就一定会执行,setup抛错也一样。所以teardown里要防御变量还没赋值的情况,上面用的是tunnel?.stop()。 - 上次的结果全部能沿用、这次一个 Attempt 都不用真正执行时,两者都不会运行。
setup 运行期间,终端 ACTIVE 区会显示一行 experiment setup · <实验 id>,ctx.progress(...) 的消息更新在这一行末尾。这时该实验的 Attempt 计入排队数,不是卡住了。
setup 失败时会发生什么
setup 抛错时,这个实验的每个 Attempt 都记为 errored,错误码是 experiment-setup-failed。同一批里的其它实验照常运行。环境没起来不会被算成通过,也不会拖累别的实验。
teardown 里先保证释放
释放资源是必须做到的事,健康检查、上报指标这类观测只是尽力而为。用try/finally 包住,观测出错也不能挡住释放;运行被中断时直接跳过观测:
运行时地址不要写进 flags
隧道每次重启都会换一个 URL。flags 是实验条件,值一变,已经跑完的结果就不能沿用了。把临时地址写进 flags,等于每次重启隧道都要整批重跑。
把地址留在模块变量里,由 Agent 工厂或 Sandbox 回调读取:
- 是,例如记忆服务的版本号
0.10.39,换版本行为可能真的不同,写进flags,变了就该重跑。 - 不是,只是这一次的连接坐标,留在
setup和模块变量里。 - 只用来给报告分组的标注,写
labels。
在 Sandbox 里准备环境
setup / teardown 管的是你机器上的服务。Agent 运行前要在 Sandbox 里做的准备,例如写入配置、装二进制、载入上次保存的状态,挂在 sandbox 的 .before() 上。它对每个真正执行的 Attempt 跑一次,一定晚于 setup,所以能读到 setup 赋好的变量:
setup / teardown,每个 Sandbox 的准备和收尾在 .before() 和 ctx.onCleanup。
Agent CLI、系统包这类固定不变的安装,最好先做进镜像或 template。NiceEval 会复用没变的准备步骤,不会因为评估用例改了就重装一遍。做法见 预制 Sandbox 环境 · 从官方基线继续构建。
跨 Attempt 保存状态
上面的例子里,每个 Attempt 开始时载入记忆、结束时存回去,让记忆一题一题累积下来。这要求 Attempt 按顺序一个接一个跑:maxConcurrency: 1让这次运行里的 Attempt 串行。sharedState.key防止你在另一个终端同时跑同一个实验,两边交错读写同一份状态。
sharedState 只保证同一时刻只有一个运行持有这个 key,存储本身、存回时的原子性和异常中断后的恢复仍由你负责。key 里不要放密钥。
多个实验共用同一套启停代码
对比实验里,几个实验往往对着同一类基础设施,例如 Claude Code 和 Codex 各接同一个记忆产品。把启停写成一个工厂函数,每个实验文件各调用一次,代码只写一份,实例和坐标各自独立:- 工厂函数被调用时只创建闭包,不做 I/O,也不读配置。
niceeval exp在发现阶段就会 import 实验文件,这时抛错会连累同一批里无关的实验。所有可能失败的操作都放进setup。 - 运行时坐标放在工厂闭包里,不放在模块级单例里。并行的两个实验各持一份,互不覆写。
setup 开始过就一定会配对执行 teardown,计数总能回到 0,不会泄漏。
跨多次运行存在的服务
setup 启动的服务活不过这一次 niceeval exp。需要先起好、连续跑好几次的服务,用 docker compose 这类外部工具启停,再通过环境变量把地址传给 Adapter。
setup 和 teardown 的完整参数见 Experiment。Sandbox 准备步骤的顺序、缓存和收尾见 Sandbox · 按顺序准备 Sandbox。