Skip to main content
你在评估一个接入了记忆服务的 Coding Agent。记忆服务跑在内网,每次评估前要先开一条隧道,拿到一个临时地址,再把地址告诉 Agent。隧道开一次就够,几十个 Attempt 共用;全部跑完后要记得关掉。 这类“一个实验一份、所有 Attempt 共用”的资源,写进 Experiment 的 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 回调读取:
换了 URL 再跑,已完成的照常沿用,只跑还缺的:
判断一个值该放哪里,问一句:它是不是你想比较的实验条件?
  • 是,例如记忆服务的版本号 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。