Skip to content

Commit a897e1f

Browse files
jingshenghangjingshenghang
andauthored
feat(coding_agent_rl): select claude_code/codex harness+adapter pair via SWE_AGENT (#2125)
Co-authored-by: jingshenghang <shenghang.jing@aminer.cn>
1 parent 34a533b commit a897e1f

2 files changed

Lines changed: 27 additions & 14 deletions

File tree

examples/coding_agent_rl/generate.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@
22
33
--custom-generate-function-path examples.coding_agent_rl.generate.generate
44
5-
generate() is a four-stage orchestrator: swe.prepare_workspace + ClaudeCodeHarness.run
6-
-> swe.git_diff -> swe.evaluate -> adapter.finish_session. Sandbox-side work is
7-
split across three layers: the provider-agnostic sandbox contract
8-
(slime.agent.sandbox), the swappable harness lifecycle (slime.agent.harness), and
9-
the SWE task layer (examples.coding_agent_rl.swe -- dataset parsing, workspace
10-
prep, diff, eval). LLM plumbing (Anthropic <-> SGLang /generate, token capture,
11-
segment split) is slime.agent.adapters.AnthropicAdapter. swe.get_metadata documents
12-
the dataset row schema and produces the md dict consumed below.
5+
generate() is a four-stage orchestrator: swe.prepare_workspace + harness.run
6+
-> swe.git_diff -> swe.evaluate -> adapter.finish_session. The (harness, adapter)
7+
pair is chosen by the SWE_AGENT env var (claude_code | codex); see _AGENTS below.
8+
Sandbox-side work is split across three layers: the provider-agnostic sandbox
9+
contract (slime.agent.sandbox), the swappable harness lifecycle
10+
(slime.agent.harness), and the SWE task layer (examples.coding_agent_rl.swe --
11+
dataset parsing, workspace prep, diff, eval). LLM plumbing (Anthropic / OpenAI
12+
<-> SGLang /generate, token capture, segment split) is the matching
13+
slime.agent.adapters adapter. swe.get_metadata documents the dataset row schema
14+
and produces the md dict consumed below.
1315
"""
1416

1517
from __future__ import annotations
@@ -25,9 +27,9 @@
2527
from dataclasses import dataclass
2628
from typing import Any
2729

28-
from slime.agent.adapters import AnthropicAdapter
30+
from slime.agent.adapters import AnthropicAdapter, OpenAIAdapter
2931
from slime.agent.aiohttp_threaded import FilteredAccessLogger, run_app_in_thread
30-
from slime.agent.harness import ClaudeCodeHarness
32+
from slime.agent.harness import ClaudeCodeHarness, CodexHarness
3133
from slime.agent.sandbox import E2BSandbox
3234
from slime.utils.misc import SingletonMeta
3335
from slime.utils.processing_utils import load_tokenizer
@@ -38,6 +40,15 @@
3840
logger = logging.getLogger(__name__)
3941
logging.getLogger("e2b").setLevel(logging.WARNING)
4042

43+
_AGENTS = {
44+
"claude_code": (ClaudeCodeHarness, AnthropicAdapter),
45+
"codex": (CodexHarness, OpenAIAdapter),
46+
}
47+
AGENT_NAME = os.environ.get("SWE_AGENT", "claude_code")
48+
if AGENT_NAME not in _AGENTS:
49+
raise ValueError(f"SWE_AGENT={AGENT_NAME!r} not in {sorted(_AGENTS)}")
50+
HARNESS_CLS, ADAPTER_CLS = _AGENTS[AGENT_NAME]
51+
4152

4253
@dataclass(frozen=True)
4354
class SweConfig:
@@ -77,7 +88,7 @@ def from_env(cls) -> SweConfig:
7788

7889
@asynccontextmanager
7990
async def boot_agent_sandbox(image: str, instance_id: str) -> AsyncIterator[E2BSandbox]:
80-
"""Boot a fresh E2B sandbox and install the Claude Code toolchain.
91+
"""Boot a fresh E2B sandbox and install the selected harness toolchain.
8192
8293
Create the sandbox from the dataset image, install Node 22 + the harness CLI
8394
from host tarballs, retry transient boot/install failures, and close the
@@ -91,7 +102,7 @@ async def boot_agent_sandbox(image: str, instance_id: str) -> AsyncIterator[E2BS
91102
async with _BOOT_SEM:
92103
await cand.__aenter__()
93104
try:
94-
await ClaudeCodeHarness().install_cli(cand)
105+
await HARNESS_CLS().install_cli(cand)
95106
except BaseException:
96107
await cand.__aexit__(None, None, None)
97108
raise
@@ -130,7 +141,7 @@ def __init__(self, args) -> None:
130141
"sandboxes can reach for reverse-connection to the adapter; "
131142
"without it the sandbox cannot dial back and the rollout aborts."
132143
)
133-
self.adapter = AnthropicAdapter(
144+
self.adapter = ADAPTER_CLS(
134145
tokenizer=self.tokenizer,
135146
sglang_url=sglang_url,
136147
tool_parser=self.tool_parser,
@@ -181,7 +192,7 @@ async def generate(args, base_sample: Sample, sampling_params: dict[str, Any]):
181192
async with asyncio.timeout(CONFIG.rollout_guard_sec):
182193
async with boot_agent_sandbox(md["image"], instance_id) as sb:
183194
await swe.prepare_workspace(sb, md["workdir"], md)
184-
agent_exit_code = await ClaudeCodeHarness().run(
195+
agent_exit_code = await HARNESS_CLS().run(
185196
sb,
186197
workdir=md["workdir"],
187198
session_id=session_id,

examples/coding_agent_rl/run_qwen36_35b_a3b_swe_8nodes.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ export NCCL_SOCKET_IFNAME="${NCCL_SOCKET_IFNAME:-${MLP_SOCKET_IFNAME:-eth0}}"
206206

207207
# ============ SWE / claude-code rollout knobs ============
208208

209+
export SWE_AGENT="${SWE_AGENT:-claude_code}"
209210
export E2B_API_KEY="${E2B_API_KEY:-e2b_0000000000000000000000000000000000000000}"
210211
# Metadata key your gateway routes images by; `image` is the neutral default.
211212
export SLIME_AGENT_SANDBOX_IMAGE_METADATA_KEY="${SLIME_AGENT_SANDBOX_IMAGE_METADATA_KEY:-image}"
@@ -268,6 +269,7 @@ RUNTIME_ENV_JSON=$(python3 - <<PY
268269
import json, os
269270
keys = (
270271
"no_proxy", "NO_PROXY",
272+
"SWE_AGENT",
271273
"E2B_API_KEY", "ADAPTER_PUBLIC_HOST",
272274
"SLIME_AGENT_NODE_TARBALL", "SLIME_AGENT_CC_TARBALL",
273275
"SWE_AGENT_TIME_BUDGET_SEC", "SWE_EVAL_TIMEOUT_SEC", "SWE_BOOT_CONCURRENCY",

0 commit comments

Comments
 (0)