Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
8646a8d
refactor(agent): replace segment-based trajectory with turn-node Traj…
Jun 1, 2026
f10552f
refactor(agent): drop dead code left by trajectory-manager refactor
Jun 2, 2026
ba557ae
refactor(agent): port anthropic + trajectory_manager from trajectory-…
Jun 4, 2026
b8a5cd4
refactor(agent): rewrite openai.py for Codex CLI + TrajectoryManager
Jun 4, 2026
44e7999
refactor(agent): centralize snapshot-threshold default + filter acces…
Jun 5, 2026
5e59c25
feat(agent): add fork-merge rescue for short assistant rewrites
Jun 5, 2026
1d459bd
fix(agent): replace sib.messages on fork-merge rescue
Jun 5, 2026
8d6fdc9
refactor(agent): drop billing-header scrub now cc emits no header
Jun 5, 2026
2b4efc4
refactor(agent): migrate TrajectoryManager and adapters (v4)
Jun 8, 2026
6f95f18
refactor(agent): drop drift fork/merge params, strict exact-prefix li…
Jun 8, 2026
4fcbb24
feat(agent): assistant-rewrite merge to de-dilute reward
Jun 8, 2026
0f1c5aa
feat(agent): TrajectoryManager re-accepts fork_merge_max_response_tokens
Jun 8, 2026
4732702
docs(test): spec for TrajectoryManager e2e test script
Jun 8, 2026
4ea5d5a
test(agent): end-to-end TrajectoryManager test matrix (append_turn/ge…
Jun 8, 2026
cbee0de
test(agent): dump raw append_turn inputs in e2e readable output
Jun 8, 2026
a11c9b4
test(agent): 1.7 now shows token drift's effect on the linearized sample
Jun 8, 2026
f1b1792
test(agent): every case prints [samples] + mask info
Jun 8, 2026
0be5966
test(agent): make reward-split explicit in dump + conservation assert
Jun 8, 2026
fe7692a
test(agent): set every case's input reward to 1.0
Jun 8, 2026
2ee5ee8
test(agent): render whitespace in token labels as visible ␣
Jun 8, 2026
8eed4dd
test(agent): add Group 4 (boundary/defensive/feature) -> 98% coverage
Jun 8, 2026
cfad29d
test(agent): assert full output via golden token+loss strings
Jun 8, 2026
624b927
refactor(agent): drift-tolerant trajectory linearization
Jun 8, 2026
054f89a
chore(agent): untrack e2e test design doc and trajectory_manager tests
Jun 8, 2026
bc3d304
chore(agent): drop comments/docstrings in generate.py and TurnRecord
Jun 8, 2026
ece9007
docs(agent): tighten trajectory_manager comments to why-not-what
Jun 8, 2026
6565fe1
refactor(agent): slim adapters and trajectory_manager, add e2e test
Jun 9, 2026
ad122f4
refactor(agent): assert base_sample in get_trajectory instead of defa…
Jun 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions examples/coding_agent_rl/aiohttp_threaded.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@
from typing import Any

from aiohttp import web
from aiohttp.web_log import AccessLogger


class FilteredAccessLogger(AccessLogger):
SLOW_THRESHOLD_SEC = 120.0

def log(self, request, response, time):
if request.method == "HEAD":
return
if response.status == 200 and time <= self.SLOW_THRESHOLD_SEC:
return
super().log(request, response, time)


@dataclass
Expand Down
101 changes: 28 additions & 73 deletions examples/coding_agent_rl/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
1. ``sandbox.run_claude_code`` prepares the agent sandbox and runs claude-code.
2. ``sandbox.git_diff`` captures the model-produced patch.
3. ``sandbox.evaluate`` scores that patch in a second clean sandbox.
4. ``_merge_samples`` combines reward + adapter ``TokenSegment``s,
delegating segment-to-``Sample`` fan-out to ``slime.agent.trajectory``.
4. ``adapter.finish_session`` drains the session tree into reward-weighted
``Sample`` objects with ``.response`` already decoded; ``generate`` logs.

All sandbox-side details live in ``sandbox.py``; the LLM plumbing
(Anthropic <-> SGLang /generate, token capture, 3-kind segment split) uses
Expand Down Expand Up @@ -49,17 +49,15 @@
import secrets
import time
import traceback
from dataclasses import dataclass
from typing import Any

from slime.agent.adapters import AnthropicAdapter
from slime.agent.trajectory import TokenSegment, fan_out_sample_segments
from slime.utils.misc import SingletonMeta
from slime.utils.processing_utils import load_tokenizer
from slime.utils.types import Sample

from . import sandbox
from .aiohttp_threaded import run_app_in_thread
from .aiohttp_threaded import FilteredAccessLogger, run_app_in_thread

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -97,11 +95,13 @@ def __init__(self, args) -> None:
"Without it the sandbox cannot dial back and the rollout will "
"silently abort."
)
fork_merge_threshold = int(v) if (v := os.environ.get("SLIME_FORK_MERGE_MAX_RESPONSE_TOKENS")) else None
self.adapter = AnthropicAdapter(
tokenizer=self.tokenizer,
sglang_url=sglang_url,
tool_parser=self.tool_parser,
reasoning_parser=self.reasoning_parser,
fork_threshold_tokens=fork_merge_threshold,
)
# handler_cancellation=True so a client disconnect cancels the handler
# coroutine, arming the fire-and-forget /abort_request inside the
Expand All @@ -113,7 +113,10 @@ def __init__(self, args) -> None:
host=SHIM_BIND_HOST,
port=SHIM_PORT,
thread_name="anthropic-adapter",
runner_kwargs={"handler_cancellation": True},
runner_kwargs={
"handler_cancellation": True,
"access_log_class": FilteredAccessLogger,
},
)
self.adapter_url = f"http://{public_host}:{self.app_handle.port}"
logger.info(
Expand All @@ -127,18 +130,8 @@ def __init__(self, args) -> None:


# ---------------------------------------------------------------------------
# Trajectory -> Sample conversion
# adapter.finish_session() returns TokenSegments. One trajectory yields >=1
# segments because the agent may compact + reset mid-run; trajectory.py handles
# the mechanical segment -> Sample fan-out.
# Session setup
# ---------------------------------------------------------------------------
@dataclass(frozen=True)
class RewardResult:
reward: float
is_solved: bool
applied_cleanly: bool


def _start_session(
state: _State,
sample: Sample,
Expand All @@ -164,55 +157,11 @@ def _start_session(
return session_id


def _merge_samples(
*,
sample: Sample,
state: _State,
segments: list[TokenSegment],
reward_result: RewardResult,
elapsed_sec: float,
instance_id: str,
):
if not segments:
return _abort_result(sample, "adapter_session_empty")

trajectory_metadata = {
**(sample.metadata or {}),
"instance_id": instance_id,
"is_solved": reward_result.is_solved,
"applied_cleanly": reward_result.applied_cleanly,
"elapsed_sec": elapsed_sec,
}

# All K samples share rollout_id so the loss reducer counts this
# trajectory once.
fanned = fan_out_sample_segments(
sample,
segments,
reward_result.reward,
state.tokenizer,
metadata=trajectory_metadata,
)
if not fanned:
raise ValueError("fan-out produced no samples")

logger.info(
"[coding_agent_rl] %s: reward=%.2f solved=%s applied=%s elapsed=%.1fs segments=%d",
instance_id,
reward_result.reward,
reward_result.is_solved,
reward_result.applied_cleanly,
elapsed_sec,
len(fanned),
)
return fanned


# ---------------------------------------------------------------------------
# Main per-sample agent function
#
# The four calls inside the timeout are the high-level rollout recipe:
# run_claude_code -> git_diff -> sandbox.evaluate -> merge_samples.
# run_claude_code -> git_diff -> sandbox.evaluate -> finish_session.
# ---------------------------------------------------------------------------
async def generate(args, sample: Sample, sampling_params: dict[str, Any]):
"""Per-sample agent function with wall-clock guard. See
Expand Down Expand Up @@ -249,20 +198,26 @@ async def generate(args, sample: Sample, sampling_params: dict[str, Any]):
pre_commands=md["pre_commands"],
timeout_sec=SWE_EVAL_TIMEOUT_SEC,
)
reward_result = RewardResult(
samples = await state.adapter.finish_session(
session_id,
base_sample=sample,
reward=float(reward),
is_solved=bool(is_solved),
applied_cleanly=bool(applied_cleanly),
)
segments = await state.adapter.finish_session(session_id)
return _merge_samples(
sample=sample,
state=state,
segments=segments,
reward_result=reward_result,
elapsed_sec=time.time() - t0,
instance_id=instance_id,
if not samples:
return _abort_result(sample, "adapter_session_empty")

# finish_session already linearized, reward-weighted and decoded
# each segment's .response; here we only log a summary.
logger.info(
"[coding_agent_rl] %s: reward=%.2f solved=%s applied=%s elapsed=%.1fs segments=%d",
instance_id,
float(reward),
bool(is_solved),
bool(applied_cleanly),
time.time() - t0,
len(samples),
)
return samples

except asyncio.TimeoutError:
_log_timeout_diagnostic(t0)
Expand Down
3 changes: 1 addition & 2 deletions slime/agent/adapters/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@

from slime.agent.adapters.anthropic import AnthropicAdapter
from slime.agent.adapters.common import BaseAdapter
from slime.agent.adapters.openai import OpenAIAdapter

__all__ = ["AnthropicAdapter", "BaseAdapter", "OpenAIAdapter"]
__all__ = ["AnthropicAdapter", "BaseAdapter"]
Loading
Loading