Pre-4:启动前准备

本章离线存档。正文、组成树和总览图可在本目录直接查看;其他章节链接使用本地 Hugo 预览,源码链接保留文件与行号。

读完本章,你应能沿着 generate() 说明:进入 Harness.run() 前准备了什么,每个组件负责什么,以及这些准备怎样交给下一步使用。

本章读到调用 HARNESS_CLS().run(...) 为止。目录把 Pre-4 放在 3.5 与 4 之间;运行时则先准备,再启动 CLI,随后才发生模型请求。本章沿 Claude Code / Anthropic 和 scaleswe 分支阅读。

阅读伪代码:简略定位当前准备与下一次交接。

generate:
    # 当前 Pre-4:把模型入口、任务与运行条件准备好
    state = _AdapterService(...)
    md = swe.get_metadata(...)
    state.adapter.open_session(...)
    boot_agent_sandbox:  # 使用 Action 5 的创建、文件和命令能力
        await swe.prepare_workspace(...)
        HARNESS_CLS().run:  # 接入 Action 4 的启动组织
            ...  # Action 6:CLI 的执行与等待
            ...  # 运行期间:7 回连;2/3/3.5 处理模型请求
        ...  # 返回后收集 diff,再退出工作环境
    ...  # 评估、结果导出与会话收尾

启动准备总览图

Pre-4 职责地图:五项准备与交给 Harness.run 的成果

先顺着五步看清“做什么 → 调用谁 → 留下什么”,再进入下面的代码骨架。state 的三个同级组成并排展示;其他步骤只保留关键调用和产物,成员的具体含义在对应源码段中展开。点击图片可放大查看。

需要逐项对照时,再展开完整代码归属图

这张详图保留对象类型、完整成员路径和每项用途,适合配合下方源码查阅。图中区分对象属性、字典值、局部变量和远端资源;它与上面的总览图描述同一条准备主线。

Pre-4 完整代码归属图:对象、字段和调用来源

主线代码骨架

把图与下面的调用逐项对上。这里省略详细参数、校验、预算和异常分支,用来定位阅读;后文保留真实源码及文件、行号。

generate()
  state = _AdapterService(args)
  md = swe.get_metadata(base_sample, protocol)
  session_id = _session_id(base_sample, md["instance_id"])
  state.adapter.open_session(session_id, ...)
  async with boot_agent_sandbox(md["image"], md["instance_id"]) as sb:
    await swe.prepare_workspace(sb, md["workdir"], md)
    await HARNESS_CLS().run(sb, ...)

按顺序进入 ① 模型入口 → ② 任务信息 → ③ 会话 → ④ 执行环境 → ⑤ 工作区,最后对上 run 的参数。

下面每一步都按“从哪里进入、需要什么 → 组成与职责 → 源码怎样实现 → 交给下一步什么”阅读。贯穿示例使用任务 demo、镜像 demo-image 和工作目录 /workspace/demo;这些是示意值,不是运行记录。

1.Adapter Service:让模型请求入口就位

为后续 CLI 建立可以回连的模型请求入口;输入来自 args 与本例 CONFIG。

从主线进入: generate(examples/coding_agent_rl/generate.py:182) 的第 184 行调用 _AdapterService.__init__(examples/coding_agent_rl/generate.py:138)。这段组装代码在 examples/,具体 Adapter 与轨迹组件在 slime/agent/。

调用结束后,外层变量 state 引用这份服务对象。因此构造方法里的 self.tokenizer、self.adapter,对应外层访问的 state.tokenizer、state.adapter;这是同一个对象在不同位置的写法。

组成与职责

Python 对象state : _AdapterService

直接成员共 7 个;点击三角展开内部对象。树线表示成员包含关系,共享引用会单独说明。

源码:examples/coding_agent_rl/generate.py:138 · slime/agent/adapters/common.py:141 · slime/agent/aiohttp_threaded.py:26

1.1 先选一对能接起来的 Harness 和 Adapter

模块加载时,SWE_AGENT 选择组件配对:Claude Code 对应 Anthropic Adapter,Codex 对应 OpenAI Adapter。前者的 CLI 发送 Anthropic 形状请求,后者发送本例配置的 OpenAI 形状请求。不能只替换启动 CLI 而忘记相应的协议入口。

_AGENTS = {
    "claude_code": (ClaudeCodeHarness, AnthropicAdapter),
    "codex": (CodexHarness, OpenAIAdapter),
}
AGENT_NAME = os.environ.get("SWE_AGENT", "claude_code")
if AGENT_NAME not in _AGENTS:
    raise ValueError(f"SWE_AGENT={AGENT_NAME!r} not in {sorted(_AGENTS)}")
HARNESS_CLS, ADAPTER_CLS = _AGENTS[AGENT_NAME]

本节先选 Claude Code / Anthropic,后续继续使用同一条具体实现。

1.2 读取 tokenizer、上下文和解析配置

        self.tokenizer = load_tokenizer(args.hf_checkpoint, trust_remote_code=True)
        self.max_context_len = int(getattr(args, "rollout_max_context_len", 0) or 0)
        self.tool_parser = getattr(args, "sglang_tool_call_parser", None) or None
        self.reasoning_parser = getattr(args, "sglang_reasoning_parser", None) or None
        sglang_url = f"http://{args.sglang_router_ip}:{args.sglang_router_port}"

当前成果: 请求处理需要的 tokenizer、上下文设置与解析配置已有来源。

1.3 区分两个网络地址

sglang_url 来自 args.sglang_router_ip/port,指向上游已经提供的 SGLang 服务。adapter_url 是之后交给 CLI 的回连地址,它们服务于不同一段请求链。

CLI -> adapter_url -> Adapter -> sglang_url -> SGLang

用示意地址理解:CLI 请求 http://192.0.2.10:18001/v1/messages,Adapter 再向 http://192.0.2.20:30000/generate 请求生成。

HTTP 服务的 adapter_bind_host 决定监听哪块本机接口;adapter_public_host 决定告诉沙箱 CLI 用哪个地址回连。配置端口可由服务实际监听结果确定,因而最终 adapter_url 使用 app_handle.port。源码会检查 public host 已配置;实际连通性仍需运行验证。

1.4 构造 Adapter,它内部接住什么

        self.adapter = ADAPTER_CLS(
            tokenizer=self.tokenizer,
            sglang_url=sglang_url,
            tool_parser=self.tool_parser,
            reasoning_parser=self.reasoning_parser,
            fork_threshold_tokens=CONFIG.fork_merge_threshold,
        )

进入 BaseAdapter.__init__(slime/agent/adapters/common.py:141),对照上面的 adapter 分支看成员如何建立:配置交给请求处理,app 注册路由,store 等容器保存会话与请求状态,manager 管理轨迹。

fork_threshold_tokens 把本例的轨迹组织阈值交给 TrajectoryManager;Pre-4 先看配置来源和接收者,具体分段行为留到轨迹部分。

此时已经有接收请求和管理会话的组件,但具体任务的 Session 要到第 3 部分才注册。Session 保存默认设置;token/消息轨迹在 TrajectoryManager 中,两者职责分开。

1.5 把 Adapter App 运行起来

        self.app_handle = run_app_in_thread(
            self.adapter.app,
            host=CONFIG.adapter_bind_host,
            port=CONFIG.adapter_port,
            thread_name="anthropic-adapter",
            runner_kwargs={
                "handler_cancellation": True,
                "access_log_class": FilteredAccessLogger,
            },
        )
        self.adapter_url = f"http://{CONFIG.adapter_public_host}:{self.app_handle.port}"

进入 run_app_in_thread(slime/agent/aiohttp_threaded.py:46),只走“创建后台线程 → 独立事件循环 → AppRunner/TCPSite 监听 → 返回 AppHandle”。这解释了后面外层等待 CLI 时,Adapter 仍可以处理它发来的模型请求。

AppHandle 保存线程、事件循环、runner、监听 host/port;state.adapter_url 根据可回连 host 和实际端口形成。

补读:请求取消与访问日志配置

handler_cancellation=True 是请求取消处理的一项配置,具体 abort 行为留到模型请求或失败链路展开。access_log_class 指定 FilteredAccessLogger,控制 HTTP 访问日志:略过 HEAD 请求与不超过 120 秒的成功请求,错误或慢请求继续记录。

1.6 回到 generate:复用服务,继续准备任务

SingletonMeta(slime/utils/misc.py:62) 让同一进程后续使用 _AdapterService(args) 时复用已有实例。不同进程不能因此自动共享一个 Python 对象,也不能把新任务的 md、Session 或 Sandbox 当成这份公共入口的一部分。

返回主线: generate:185。现在模型请求入口就位,下一步读这次任务本身。

2.任务信息:把样本整理成任务合同

模型入口就位后,读取本次样本;输入是 base_sample 和本次选用的任务协议。

从主线进入: get_metadata(examples/coding_agent_rl/swe.py:77)。evaluation 先选择 CONFIG.eval_protocol 或 train_protocol;具体协议再选择对应的元数据解析。

组成与职责

Python 字典 · scaleswe 分支md : dict

每次 generate 整理本次任务的数据;它是独立的局部变量。以下列出 scaleswe 返回的键。

源码:examples/coding_agent_rl/swe.py:83

沿源码走一遍

md 是 Python 字典,"instance_id" 是字典的一个键,实际取值写成 md["instance_id"]。例如示意任务的值是 "demo",表示当前处理哪一道任务;源码中的 instance_id = md["instance_id"] 将它取到同名局部变量。session_id 则标识本次 Agent 会话,后面的第 3 步需要新建会话标识时,会用任务标识等信息构造它。这里应区分字典键、局部变量和对象属性:base_sample.session_id 才是对样本对象属性的访问。

第一遍选 scaleswe 分支,进入 _metadata_scaleswe(examples/coding_agent_rl/swe.py:83)。它从 base_sample.metadata、部分 remote_env_info 回退字段、label/prompt 中整理一份 md;字段的作用与下游用途见上面的组成树。

示意任务的关键值是 instance_id=demo、image=demo-image、workdir=/workspace/demo,题目为“修复一个函数并通过测试”,评分材料可以含 eval_cmd=python -m pytest -q。这里是规范化任务信息示意,不要求读者在第一遍记住所有数据集字段与回退优先级。

    protocol = CONFIG.eval_protocol if evaluation else CONFIG.train_protocol
    md = swe.get_metadata(base_sample, protocol)
    instance_id = md["instance_id"]
    if not md["image"] or not md["workdir"]:
        return _abort_result(base_sample, "missing_image_or_workdir", instance_id)
    reason = swe.evaluability_check(md)
    if reason:
        return _abort_result(base_sample, f"unevaluatable:{reason}", instance_id)

源码先检查镜像与目录,再由 evaluability_check(examples/coding_agent_rl/swe.py:151) 做协议相关检查。条件不满足就返回 ABORTED 结果,尚未到 Session/沙箱准备。md 描述任务,但读取 md 不会创建环境,也不会执行题目。

返回主线: generate:194。任务身份、执行环境要求与题目材料已有来源,下一步给它注册模型会话。

3.会话和默认采样:让请求属于这次任务

任务检查通过后,为它关联模型请求;输入是任务身份、采样参数和上下文预算。

从主线进入: _session_id(examples/coding_agent_rl/generate.py:308) 与 BaseAdapter.open_session(slime/agent/adapters/common.py:210)。

组成与职责

Python 对象 · open_session 之后state.adapter.store[sid] : Session

sid 是 store 的字典键。Session 只有下面两个字段;消息与 token 轨迹由 adapter.manager 管理。

源码:slime/agent/adapters/common.py:34 · slime/agent/adapters/common.py:210

沿源码走一遍

    session_id = base_sample.session_id = _session_id(base_sample, instance_id)
    state.adapter.open_session(
        session_id,
        sampling_defaults=sampling_params,
        max_context_tokens=state.max_context_len,
    )

sid 的来源依次是:复用 base_sample.session_id;样本有 index/group_index 时按任务身份构造;否则生成带随机后缀的 ID。示意样本 index=0/group_index=0/instance_id=demo 得到 cagent-demo-0-0,并写回样本。

进入 open_session:

        """Register a fresh per-sid Session; sids must be unique."""
        if sid in self.store:
            raise ValueError(f"session_id {sid!r} already exists; sids must be unique per agent run")
        self.store[sid] = Session(
            sampling_defaults=dict(sampling_defaults or {}),
            max_context_tokens=int(max_context_tokens or 0),
        )

它要求 sid 未占用,在 adapter.store 注册 Session(slime/agent/adapters/common.py:34);保存默认采样字典的浅拷贝和上下文预算。此时没有发模型请求,也没有训练轨迹。

例如 sampling_params 含 temperature=0.7/max_new_tokens=1024,这些值保存在 Session。后面的 CLI 请求携带 sid,Adapter 才能从 Session 取出基础设置。_sampling_params(slime/agent/adapters/common.py:416) 随后还会结合请求 body:温度等可由 body 覆盖,生成长度则受到默认长度与请求长度的共同限制。本节记住“先存默认设置,后续处理请求时消费”即可。

sid 最后会由 Harness 写进 CLI 请求凭据;采样字典并未直接作为 Harness.run 的参数。Session 是任务级设置,Adapter HTTP 服务是进程内可复用入口,两者生命周期不同。

返回主线: generate:202。会话已注册,接下来进入任务预算与环境上下文。

4.执行环境与 CLI:交出能运行 Agent 的 sb

会话已注册,现在需要承载 CLI 的远端环境;输入是任务镜像与环境配置。

从主线进入: boot_agent_sandbox(examples/coding_agent_rl/generate.py:96)。

组成与职责

Python 对象 · 进入环境上下文之后sb : E2BSandbox

构造时 _sb 为 None、sandbox_id 为空;__aenter__ 创建远端环境后填入。CLI 安装在远端环境中。

源码:slime/agent/sandbox.py:174 · slime/agent/sandbox.py:281

沿源码走一遍

task image
  -> E2BSandbox wrapper
  -> __aenter__: create remote environment
  -> selected Harness.install_cli
  -> yield sb
  -> caller uses sb
  -> __aexit__: cleanup attempt

构造 E2BSandbox(image) 先得到包装对象,进入 E2BSandbox.aenter(slime/agent/sandbox.py:281) 才通过 E2B SDK 创建远端环境。镜像通过网关使用的 metadata key 传递;这一步的具体集群/网关实现是外部边界。

然后执行 ClaudeCodeHarness.install_cli(slime/agent/harness/claude_code.py:36) → install_npm_cli(slime/agent/harness/common.py:125):从宿主配置的安装包准备 Node 运行时、把 CLI 包写到环境中、安装并执行 CLI 自检。第一遍走通每层的输入和成果,安装重试与底层 SDK 细节按故障再深入。

创建/安装由 _BOOT_SEM 限制启动并发;失败时有重试,安装出错会尝试退出候选环境。成功后 yield sb 交给 generate;调用方离开上下文时执行 E2BSandbox.aexit(slime/agent/sandbox.py:302) 的回收尝试。

当前成果: 有可操作的执行环境,所选 CLI 已安装;还没有启动这次 Agent 任务。返回主线: generate:204,准备仓库目录与题目。这里使用 E2B,Kubernetes 后端的操作语义与真实运行还需另行适配和验证。

5.工作区与题目:把任务放到 CLI 能读取的位置

环境与 CLI 已就位,现在落实用户权限和具体题目;输入是 sb、工作目录与 md。

从主线进入: prepare_workspace(examples/coding_agent_rl/swe.py:177),输入是 sb/workdir/md。

组成与职责

远端文件目录 · 示意路径workdir → /workspace/demo/

这里展示文件位置。md["workdir"] 保存路径字符串,sb 提供访问远端目录的操作。

源码:examples/coding_agent_rl/swe.py:177

沿源码走一遍

它先调用 ensure_agent_user(slime/agent/sandbox.py:390),准备 agent 用户和工作目录权限;scaleswe 分支再依次处理 grading.swepro.before_repo_set_cmd 与 grading.pre_commands 中的可选准备命令,最后写题目文件:

    await sb.write_file(
        f"{workdir}/PROBLEM_STATEMENT.md",
        md.get("problem_statement") or "",
        user="agent",
    )

用前面的示意值,此时 /workspace/demo/PROBLEM_STATEMENT.md 内已有具体题目。Claude Code 的通用任务 prompt 会指示它读取问题文件并完成任务;具体题目不是通过额外的 run 参数传递。

工作区来自镜像与任务目录,这个函数不意味着对每个任务重新 clone 一份仓库。它负责落实本例需要的用户、准备命令和问题文件。BaseHarness.run() 随后也会确保用户,操作设计为可以重复调用,不能把两次调用解释成创建了两份工作区。

返回主线: generate:205。模型入口、任务、会话、环境与题目五项条件已经连到一起,现在可以进入 Harness。

6.接入 run:显式参数和隐含条件逐项对上

                agent_exit_code = await HARNESS_CLS().run(
                    sb,
                    workdir=md["workdir"],
                    session_id=session_id,
                    adapter_url=state.adapter_url,
                    time_budget_sec=CONFIG.agent_time_budget_sec,
                    prompt=swe.SWE_PROMPT,
                )
run 接收到的参数 Pre-4 的来源 run 内怎样接着使用
sb boot_agent_sandbox 创建并安装 CLI 后交出 通过环境接口落实用户、配置与命令操作
workdir md 中的目录,prepare_workspace 已处理 填入 HarnessContext,作为 CLI 工作目录
session_id _session_id + open_session 填入 HarnessContext,供 CLI 请求关联本次会话
adapter_url state 中已运行的 HTTP 入口地址 填入 HarnessContext,再形成 CLI 模型请求地址
time_budget_sec CONFIG.agent_time_budget_sec 交给启动/等待逻辑的 Agent 运行预算
prompt swe.SWE_PROMPT 交给 CLI 的通用任务指令,具体题目从文件读取

还有三项隐含条件:所选 CLI 已安装,工作区题目文件已存在,Adapter 中已有对应 sid 的默认采样与上下文设置。这些不需要变成额外的 run 参数。

预算也要区分:外层 rollout_guard_sec 包住任务处理范围,agent_time_budget_sec 是 Harness 的运行等待预算,eval_timeout_sec 之后交给评分。Pre-4 中配置已就位,它们由不同阶段消费。

下一步:Launch Agent 继续准备什么

进入 BaseHarness.run(slime/agent/harness/common.py:81) 后,继续确保用户,建立 HarnessContext,调用 write_config() 和 launch_and_wait()。其中 HarnessContext 只接住 workdir、sid、adapter_url 和模型标签;sb、prompt 与时间预算继续以独立参数参与调用。

所以 Pre-4 将启动条件准备好,动作 4 再把这些条件变成 CLI 的具体配置、命令、环境变量和启动等待行为。模型消息转换、token 生成、响应与轨迹记录仍发生在 CLI 启动后的请求循环中。

回到总览图:试着自己串起来

回到 开头的五步图,暂不翻看源码和成员细节,试着回答:

  1. 从模型入口到题目文件,五步各留下了什么?哪一步创建环境、哪一步安装 CLI、哪一步才开始运行任务?
  2. state 与 Session 为什么分开?默认采样和轨迹各保存在什么地方?
  3. run 的六个参数分别来自哪个对象、配置或常量?除了参数,还需要哪三个已经成立的条件?

回答后再对照 主线代码骨架 和 run 参数来源 核对。说不清哪次交接,就只回到那一步补读;能连起来之后,再进入 Launch Agent。

以上为静态源码讲解,示意参数不是运行证据。本轮没有执行真实 CLI、E2B、SGLang 或源码仓库测试;网络连通、环境回收和性能效果仍需实际验证。代码片段来自 THUDM/slime 源码,许可见 Apache 2.0。

下一步:4. Launch Agent。