feat(mini_swe_env): add SWE-Gym async GRPO environment with Pi interception and HF Space deployment - #695
feat(mini_swe_env): add SWE-Gym async GRPO environment with Pi interception and HF Space deployment#695rycerzes wants to merge 83 commits into
Conversation
- related tests
- fix agent handling
- tests
…roxy the transparent proxy was a passive forwarder that captured logprobs by injecting logprobs=true into upstream requests. It is replaced by the interception_gate mode where the trainer owns the forward pass entirely — no proxy needed inside the sandbox.
…eneration
InterceptionServer (aiohttp) runs on the trainer host. Each rollout
registers a queue. The agent's OPENAI_BASE_URL points at
`{base_url}/rollout/{id}/v1`. When the agent makes an LLM call it
blocks at the server. The training loop dequeues the request, calls
vLLM with logprobs=True and return_token_ids=True, and delivers the
response back via deliver_response().
- update deps and envs for named-tunnels
- use trl prefix-preserving training template for reliable suffix computation - replace prev_prompt_ids with prev_base_ids (add_generation_prompt=false) - add fallback interstitial extraction on prefix mismatch - parse tool_call arguments from json strings to dicts for qwen3.5 - strip trailing eos tokens from vllm output
|
Thanks @AmineDiro for the detailed review. It helped me catch a few core issues. Addressing each point: 1. The weight-transfer
I verified this, weight sync does happen. No code change needed, but I added a clarifying comment. The trainer's 2. The loss is REINFORCE, not GRPO
Thank you for catching this, I already had this fixed but havent pushed yet. I implemented proper group-relative normalization: accumulate N rollouts per task, compute 3. Import helpers from TRL
Fixed for suffix/template. I now import TRL's Staleness: staleness handling matches Polar's approach for now but this results in wasted sandbox compute. I will visit this at a later revision. Duplicated Defensive scaffolding: Fair point. The getattr and try/except is higher than it should be — a lot of it accrued while debugging Qwen3.5-specific crashes. I will do a pass to tighten these up. Reinvent turn-termination heuristics: The |
Co-authored-by: Ben Burtenshaw <ben.burtenshaw@gmail.com>
f9aa2eb to
a0ab2e8
Compare
rycerzes
left a comment
There was a problem hiding this comment.
quick summary of what changed after the GRPO normalization work:
- moved to proper group-normalized GRPO with configurable num_generations (default 16)
- fixed Qwen3.5/vLLM compatibility issues (weight sync + chat-template/tool-call tokenization edge cases)
- hardened long-run stability with better context-window handling (trimming + overflow guards) so rollouts degrade gracefully instead of looping/crashing
- integrated your key harness stabilizations: reward mode toggle, stronger prompt/instructions, anti-test-edit protections, and configurable git checkout timeout
- improved control-plane reliability with explicit agent exit signaling so workers don’t sit on long idle timeouts
- stabilized training stack for Spaces with multi-GPU/LoRA path, FSDP2-safe optimizer settings, and kernel/dependency fixes
fix: max-turns arg
Darktex
left a comment
There was a problem hiding this comment.
Automated Alignment Review — PR #695
Reviewer: Claude Code (alignment-reviewer)
Scope: 77 changed files across 4 review areas: core harness infrastructure, mini_swe_env, training examples/deployment, and coding_agent_env refactor.
Verdict: REQUEST_CHANGES — 4 critical issues, 6 high-severity issues, and 5 alignment flags requiring human decision.
Critical Issues (must fix before merge)
-
api_keyexposed in MCP tool schema —envs/mini_swe_env/server/swe_environment.py:120-140
FastMCP generates JSON Schema from therun_swe_rolloutfunction signature, soapi_keyappears intools/listresponses visible to any MCP client (including agents). Any MCP-level logging captures the key in plaintext. Fix: removeapi_key/base_urlfrom the tool signature; read from env vars server-side, matching the pattern used everywhere else in the codebase. [Violates: No credential exposure — INVARIANTS.md §3] -
HF_TOKEN shell injection in deploy script —
examples/mini_swe_env/async_grpo/space_app/deploy_hf_space.sh:114,125
HF_TOKENis interpolated directly into a Python-cstring literal (api = HfApi(token='$HF_TOKEN')). A token containing a single quote causes a syntax error; the literal token also appears inps auxoutput. The same script correctly usesos.environ['HF_TOKEN']elsewhere (lines 157, 212, 339). Fix: use a heredoc or env var read for the--pause/--resumeblocks. -
_put_queue_threadsafebroken on Python 3.10+ —src/openenv/core/harness/agents/interception_server.py:550-566
The fallback path callsq.put_nowait(item)on anasyncio.Queuefrom a non-event-loop thread whengetattr(q, "_loop", None)returnsNone(which it does on Python 3.10+). This silently corrupts internal queue state. Fix: store the event loop reference at creation time and useloop.call_soon_threadsafe(q.put_nowait, item). -
TOCTOU race in InterceptionServer —
src/openenv/core/harness/agents/interception_server.py:456-462
_handle_chat_completionschecks for rollout context inside_state_lock, exits the lock, then re-enters to write the intercept. Between the two acquisitions,unregister_rolloutcould remove the entry, leaving a stranded intercept. Fix: do the entire context-fetch, intercept-write, and queue-put under a single lock acquisition.
High-Severity Issues
-
API_KEY string interpolation —
examples/mini_swe_env/deploy_collect_space.sh:175
$API_KEYis expanded by shell before Python sees it in an unquoted heredoc. Tokens with\,", or$corrupt the Python string. Fix: pass as env var, read withos.environ. -
CPU Dockerfile runs as root —
examples/mini_swe_env/deploy_collect_space.sh(inline Dockerfile ~line 281)
NoUSERdirective. The GPU Dockerfile (space_app/Dockerfile:56) correctly switches to uid 1000. HF Spaces strongly recommends non-root. Fix: addUSER 1000. -
DockerSandboxBackend
start_bglacks shell quoting —src/openenv/core/harness/sandbox/docker_backend.py:131-148
cmdis embedded directly in abash -cwrapper withoutshell_quote. Commands containing single quotes or special characters break the wrapper. Fix: applyshell_quotetocmdin the wrapper string. -
stateproperty returnsAny—envs/mini_swe_env/server/swe_environment.py:226
Should be-> SWEStateto maintain generic type safety per INVARIANTS.md API Invariants §2. -
Docstring/implementation mismatch —
envs/mini_swe_env/task_loader_swegym.py
Docstring says__is replaced with"_1776_"but implementation uses"_s_". Fix: update docstring. -
Dataclasses inside method bodies —
envs/mini_swe_env/server/swe_environment.py:707-732
_AgentConfigand_AgentTaskare created inside method bodies, accumulating stale class objects on every call. Fix: move to module level.
Medium-Severity Issues
- README/script variable mismatch — README says
SWE_ASYNC_MODELbuttrain_swe_async_grpo.py:233readsSWE_MODEL. Every user following the README will fail immediately. - Missing
openenv.yaml—envs/mini_swe_env/lacks this file, required for deployment tooling and CI discovery. - Missing
server/Dockerfile—envs/mini_swe_env/server/has no Dockerfile for standalone environment deployment. - JSON-RPC notification response —
envs/mini_swe_env/server/sandbox_mcp_server.py:263-266sends a response tonotifications/initializedin HTTP mode, violating JSON-RPC 2.0 spec. - Unused parameters —
envs/coding_agent_env/server/coding_environment.py:490_build_session_factoryacceptsdisable_thinking,top_logprobs,max_tokens_capbut never uses them.max_tokens_cap/top_logprobssilently dropped for Pi agent without warning. - Private attribute coupling —
envs/coding_agent_env/harness.py:138,143,161accessesCLIAgentDriver._interception_serverand._interception_base_urldirectly. Fix: promote to public properties or delegate through driver's public API. - Duplicated grading logic —
_apply_test_patch,_run_swegym_case_tests, etc. appear in bothswe_environment.pyandharness.pywith near-identical implementations. A fix in one won't reach the other. - Missing None guard on logprobs —
examples/mini_swe_env/async_grpo/rollout_worker.py:651—choice["logprobs"]["token_logprobs"]accessed without a None guard.
Alignment Flags (human decision needed)
FLAG 1: Two parallel harness frameworks (openenv.core.harness vs openenv.core.harnesses) with nearly identical names and incompatible abstractions. The PR should either merge these or clearly mark one as superseding the other.
Principle: One canonical way to build environments
FLAG 2: Server-wide bearer secret written into sandbox environment variables as OPENAI_API_KEY/ANTHROPIC_API_KEY and into Pi models.json on the sandbox filesystem (cli_driver.py:508-509,528). Any process in the sandbox can read these. Consider per-sandbox, short-lived tokens rotated after each rollout.
Principle: No credential exposure — INVARIANTS.md §3
FLAG 3: Reward computation in harness.py runs on the host process (outside the environment container), triggered by the InterceptionServer's _answer_handler. The grading path is also duplicated between swe_environment.py (in-container) and harness.py (host-side), creating two independently-evolvable reward definitions.
Principle: Rewards inside environment — RFC 002
FLAG 4: _noop_reward in train_swe_async_grpo.py bypasses TRL's standard reward_funcs mechanism entirely, carrying the actual reward via RolloutSample.advantage. Need confirmation that TRL's AsyncGRPOTrainer reads advantage as gradient signal and ignores the zero-valued reward_funcs output.
Principle: Rewards inside environment — RFC 002
FLAG 5: RESERVED_TOOL_NAMES defined in two places (openenv.core.harness/__init__.py:27 and openenv.core.env_server/mcp_types.py:321). No single source of truth — if someone adds a reserved name in one location but not the other, the invariant silently breaks.
Principle: Agents cannot reset — INVARIANTS.md §1
What's Good
- The
opencode_env→coding_agent_envrename is clean — no stale references remain RESERVED_TOOL_NAMESenforcement blocking reset/step/state/close from MCP tools is well-implemented- InterceptionServer architecture (host-owned generation for trainer-controlled logprob capture) is sound
- Test coverage is solid across the new harness modules (5 new test files with substantive tests)
- The PR description and mermaid diagrams are excellent documentation
Suggested reviewer for alignment flags: @Darktex
chore: local run
- remove duplicate response content
Darktex
left a comment
There was a problem hiding this comment.
Note: This is an automated review by Claude Code, not a human review.
Tier 1: Bugs & Issues
-
Private driver method access:
swe_environment.pylines 338-339 calldriver._bootstrap_sandbox(...)anddriver._start_agent(...)directly, bypassing the publiccreate()path. Same issue incoding_agent_env/harness.pylines 138, 143, 161 accessingself._driver._interception_server. These will break silently on driver refactors. -
Inline
@dataclassdefinitions per-call:swe_environment.pylines 711-725 and 728-745 define@dataclassclasses inside methods, creating fresh anonymous types on every invocation. These defeat type-checking and should be module-level definitions (or reuseSWEAgentConfigfromharness.py). -
Dangling doc reference:
harness.pyline 42-44 says "Requires core changes — see CORE_CHANGES.md" but that file doesn't exist. -
Config type divergence:
coding_environment.pylines 67-78 introduce_GenericAgentConfigfor Pi withthinking: str | None = "off", whileCodingAgentConfigusesdisable_thinking: bool = False. Two separate config shapes for what should be one abstraction. -
Merge conflict risk: This PR overlaps significantly with #694 (same rename, same core harness files). Must resolve ordering before merge.
Tier 2: Alignment
ALIGNMENT FLAG: Host-side grading runs outside environment server boundary
- Invariant at risk: "Rewards inside environment" (RFC 002, INVARIANTS.md).
SWESessionFactory._register_answer_tooland_grade_answer_submissioninharness.pyrun grading in the trainer's process, not the environment server. Theswe_environment.pypath (black_box) correctly keeps grading server-side, but the harness path does not. This needs an explicit RFC-level decision about whether the harness is considered "inside" the environment boundary. - Suggested reviewer: @Darktex
ALIGNMENT FLAG: grading.py exported at package level enables trainer-side reward computation
- Invariant at risk: "Rewards inside environment" (RFC 002).
__init__.pyexportsgrade_from_case_resultspublicly, inviting trainers to compute rewards entirely outside the environment. - Suggested reviewer: @Darktex
ALIGNMENT FLAG: No RFC for new SWE environment category with interception_gate training path
- Invariant at risk: RFC process. This PR adds a new environment category (real-repo SWE tasks), a new agent mode (
interception_gateas full RL training path with host-side grading), and a new async GRPO integration pattern. RFC 005 covers agentic harnesses but not this specific grading boundary architecture. - Suggested reviewer: @Darktex
Summary
Several mechanical issues (private attribute access, inline dataclass definitions, dangling doc references) need fixing. The key alignment concern is that the harness-path grading runs in the trainer process rather than the environment server, which may violate the "rewards inside environment" invariant. The new SWE + interception_gate architecture warrants an RFC before merge.
Automated review by Claude Code | Learn more
Darktex
left a comment
There was a problem hiding this comment.
Note: This is an automated review by Claude Code, not a human review.
Alignment Review Report
Automated Checks
- Lint: SKIP — Manual structural review performed.
- Debug code: CLEAN — No stray
print,breakpoint, orTODOartifacts in new files.
Tier 1: Fixes Required
-
envs/mini_swe_env/harness.py:496,519—_build_agent_task(swe_task)is called twice inSWESessionFactory.create. The first call's result is used only for_bootstrap_sandbox, then discarded and rebuilt on line 519. Call once and reuse. -
envs/mini_swe_env/server/swe_environment.py:110-113—CLIAgentSessionFactoryis imported and stored asself._CLIAgentSessionFactorybut never referenced again. Dead code — remove the import and instance attribute. -
envs/coding_agent_env/harness.py:138,143,161— Directly accessesself._driver._interception_serverandself._driver._interception_base_url(private attributes ofCLIAgentDriver). This bypasses encapsulation and will break silently on any internal refactor. Use the driver's publiccreate_sessionpath, or promote these to properties.
Tier 2: Alignment Discussion
ALIGNMENT FLAG: In-sandbox reward computation returned to agent in sandbox_mcp_server.py
- Principle at stake: Rewards inside environment (INVARIANTS.md §Architectural Invariants 3)
- The concern:
sandbox_mcp_server.pyruns inside the agent's sandbox, executes verify commands, computesreward = passed / totalwhen the agent callsterminal(final_answer=...), and returns that reward value to the agent in the tool response. While the host-sideSWEEnvironment._grade_submissionalso computes reward independently, the sandbox-side path exposes reward to the agent during the episode. The agent can condition its behavior on the reward signal it sees in tool output. - Suggested reviewer: @Darktex
ALIGNMENT FLAG: interception_gate mode adds trainer-owned LLM generation without an RFC
- Principle at stake: RFC-backed architectural decisions
- The concern: The
interception_gatemode is a substantial architecture where the trainer process owns the forward pass of the agent's LLM, intercepting everychat/completionscall. This goes beyond RFC 005's scope and changes the dual-API boundary. TheInterceptionServersits between the agent's MCP boundary and the LLM as a third party. The PR states "RFC not required" but the rationale isn't documented. - Suggested reviewer: @Darktex
ALIGNMENT FLAG: answer tool returns resolved status to agent
- Principle at stake: Agents cannot access reward/grading outcome
- The concern: The
answertool handler returns"✅ Resolved: true"or"false"to the agent. Whileansweris terminal (sets_done = True), nothing inCLIAgentSessionhard-prevents the agent from reading the response. The session relies on the agent obeying the "you cannot continue" instruction — a soft guarantee. A hard session kill before the response is delivered would be more aligned. - Suggested reviewer: @Darktex
Summary
- 3 mechanical issues to fix (double task build, dead import, private attribute access)
- 3 alignment points for human review (in-sandbox reward exposure, interception mode without RFC, answer tool leaking grading result)
- The core architecture is well-structured.
InterceptionServerhas HMAC auth andRESERVED_TOOL_NAMESenforcement. Grading is cleanly separated host-side by default.
Automated review by Claude Code | Learn more
Darktex
left a comment
There was a problem hiding this comment.
Alignment Review: feat(mini_swe_env) — Two-Tier Report
Reviewed against
.claude/docs/PRINCIPLES.md,.claude/docs/INVARIANTS.md, and RFC 005 (agentic harnesses).
Verdict: Request Changes — two mechanical fixes required, four alignment flags need human sign-off.
Tier 1 — Fixes Required
T1-1 · sandbox_mcp_server.py · subprocess.run(shell=True) with agent-controlled command string
envs/mini_swe_env/server/sandbox_mcp_server.py execute_command():
proc = subprocess.run(
command, # ← fully agent-controlled string
shell=True, # ← unsafe
...
)command is passed verbatim from the agent's terminal tool call. shell=True enables shell metacharacter injection. Since this runs inside an already-isolated sandbox the blast radius is bounded, but the pattern is contrary to defense-in-depth.
Fix: Use ["bash", "-c", command] with shell=False.
T1-2 · sandbox_mcp_server.py · Agent-writeable reward.txt overrides computed reward
run_verify() reads /home/user/logs/verifier/reward.txt and, if present, substitutes its float as the reward. The agent has full shell access via the terminal tool. Before calling final_answer, the agent can write any float to reward.txt and receive that value as its reward. This is a direct agent→reward channel.
The PR body correctly says "The agent cannot influence the training reward" — but that claim only holds for the Pi/interception path. The sandbox_mcp_server.py (legacy terminal-tool path) breaks this guarantee.
Fix: Either remove the reward.txt override entirely, or chmod 000 the file before the agent session starts, or gate the read behind a host-side verify step outside the agent's writable filesystem.
Tier 2 — Alignment Flags
ALIGNMENT FLAG 1: sandbox_mcp_server.py reward.txt — violates "rewards inside environment"
- Principle at stake: RFC 002 + INVARIANTS.md "Reward computation must stay inside environment boundary"
- The concern: Agent can write arbitrary floats to
/home/user/logs/verifier/reward.txt, which is read and used as the training reward in the legacy terminal-tool path. Even if this path is secondary, the invariant is unconditional. - Suggested reviewer: @Darktex
ALIGNMENT FLAG 2: InterceptionServer multiplexes concurrent rollouts — needs RFC coverage
- Principle at stake: PRINCIPLES.md "One env = one trajectory" (RFC 004)
- The concern:
InterceptionServeris a single server that routes concurrent rollouts byrollout_idat the trainer level. RFC 004 says environments don't support multiplexed trajectories and batching is via environment stacking. The PR argues correctly that the InterceptionServer is trainer-side infrastructure, but this architectural extension is not covered by any existing RFC. Should be documented (even as a short addendum to RFC 004/005). - Suggested reviewer: @Darktex
ALIGNMENT FLAG 3: answer tool returns live reward signal to agent during episode
- Principle at stake: RFC 005 §"Harness Security Boundary": "domain-specific validation … should return a factual result, not a reward signal"
- The concern: The
answerhost-side tool calls SWE-Gym grading and returns"Resolved: true/false"directly to the Pi agent mid-episode. The agent can use this signal to decide whether to retry. This is a deliberate SWE-Gym design choice but was not discussed in any RFC and sits in tension with RFC 005's guidance. - Suggested reviewer: @Darktex
ALIGNMENT FLAG 4: HF Space deployment — InterceptionServer secret accessible inside sandbox
- Principle at stake: INVARIANTS.md "No credential exposure" and "Container isolation"
- The concern: The InterceptionServer is exposed on
0.0.0.0:7860(public HF Space port) protected by a bearer token. That token is injected into the sandbox asSWE_VLLM_API_KEY/SWE_LLM_API_KEY. The agent can read its own environment variables via theterminaltool (printenv), exfiltrate the token, and call the host-side tool endpoints directly. Please confirm this is acceptable in the HF Space demo context, or scope the secret so it only grants access to/rollout/{id}/v1/chat/completions(LLM interception) and not/rollout/{id}/v1/tools/answer(grading). - Suggested reviewer: @Darktex
What's Well Done
- The host-side grading path for the Pi/interception mode is correctly architected.
- The
RESERVED_TOOL_NAMESguard inInterceptionServer._validate_tool_registrationis correct. - The anti-test-tampering logic in
_list_changed_test_paths+_revert_test_filesis good hardening.
Note: This is an automated review by Claude Code, not a human review.
Automated review by Claude Code | Learn more
Darktex
left a comment
There was a problem hiding this comment.
Alignment Review — PR #695
Tier 1: Fixes Required
HF_TOKENexposed in process args (deploy_hf_space.sh:114,125):python -c "...HfApi(token='$HF_TOKEN')..."expands the token into the command line, visible viaps aux. The heredoc blocks later in the same script correctly useos.environ['HF_TOKEN']— convert the two-cblocks to match.- Unguarded
logprobsdict access (rollout_worker.py:651):choice["logprobs"]["token_logprobs"]crashes withTypeErroriflogprobsisNone(some vLLM builds). Use.get()with a descriptiveRuntimeError. SWEGymTask/SWETaskare plain@dataclass, not Pydantic (models.py): These types cross the MCP wire astask_json. INVARIANTS.md requires all wire types to be Pydantic models. Migrate toBaseModel.- Inline
_AgentConfigdataclass recreated per-call (swe_environment.py:709-721): Defeats type checking and produces confusing tracebacks. Move to module level or reuseSWEAgentConfigfrom harness.py.
Tier 2: Alignment Flags (for human review)
- Reward visible inside sandbox:
sandbox_mcp_server.pywrites reward to/home/user/logs/verifier/reward.txtinside the sandbox, potentially readable by the agent before callingfinal_answer. The harness path correctly routes reward computation host-side — but standalone Docker mode retains file-based reward with weaker isolation. interception_gatemode lacks an RFC: New architectural boundary (trainer-owned interception between MCP and model provider) landed without RFC documentation. RFC 007 was referenced as future work for this.SUPPORTS_CONCURRENT_SESSIONS = Truebut shared mutable_state: Concurrentrun_swe_rolloutcalls race onself._state. Either set toFalseor use per-session state with locking.- Harness boundary ambiguity:
harness.pyimports from core and straddles client/server boundary. Needs__all__restriction and docstring clarifying it's trainer-side only.
Verdict: request_changes — Credential exposure in deploy script, wire-type invariant violation, and crash risk in training worker must be fixed. Alignment flags need human decision.
Darktex
left a comment
There was a problem hiding this comment.
Note: This is an automated review by Claude Code, not a human review.
Alignment Review — PR #695: mini_swe_env
Automated Checks
- Lint: SKIP (network unavailable in sandbox)
- Debug code: CLEAN
CORE_CHANGES.md: NOT FOUND — referenced inharness.pydocstring but absent from repo
Tier 1: Fixes Required
T1-1 Shell injection — base_commit not shell-quoted
envs/mini_swe_env/server/swe_environment.py:652 and envs/mini_swe_env/harness.py:558:
# CURRENT (unsafe):
f"git checkout --quiet {task.base_commit}"
# FIXED:
import shlex
f"git checkout --quiet {shlex.quote(task.base_commit)}"A malicious base_commit value would execute arbitrary commands in the sandbox.
T1-2 Shell injection — task.repo not validated or quoted
envs/mini_swe_env/server/swe_environment.py:641-643 and envs/mini_swe_env/harness.py:548-550:
Validate task.repo against a pattern like r'^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$' before interpolating into shell commands.
T1-3 Agent-writeable reward.txt override in sandbox_mcp_server.py
envs/mini_swe_env/server/sandbox_mcp_server.py:126-132: An agent can call terminal(command="echo 0.99 > /home/user/logs/verifier/reward.txt") before calling final_answer, overriding the host-side grading. This breaks reward integrity. The reward file override should either be removed or written atomically by the host after agent exit.
T1-4 Module-level _done global is not thread-safe
envs/mini_swe_env/server/sandbox_mcp_server.py:53: Two concurrent final_answer calls would both read _done=False, both set it, and both run verify. Use a threading lock or make the state per-connection.
T1-5 Missing CORE_CHANGES.md document
envs/mini_swe_env/harness.py references CORE_CHANGES.md for required core prerequisites, but the file doesn't exist in the repo. Either ship the doc or add runtime assertions.
T1-6 Server bypasses SWESessionFactory, calls private driver methods
envs/mini_swe_env/server/swe_environment.py:333-349 directly calls driver._bootstrap_sandbox(...) and driver._start_agent(...) (private methods). SWESessionFactory.create() already encapsulates this logic — the server should use it.
Tier 2: Alignment Discussion
ALIGNMENT FLAG 1: Reward computed inside the sandbox, observable by agent
- Principle at stake: "Rewards inside environment" (RFC 002, PRINCIPLES.md)
- The concern:
sandbox_mcp_server.pyrunsrun_verify()inside the sandbox and returns{"reward": ..., "done": true}directly to the agent. The agent sees its own score mid-episode. Combined with T1-3, the agent can also manipulate the score. Host-side_grade_submission()re-grades, but the in-sandbox reward path blurs the boundary. - Suggested reviewer: @Darktex
ALIGNMENT FLAG 2: No RFC for interception_gate architecture
- Principle at stake: Dual API boundary (RFC 001, INVARIANTS.md)
- The concern:
interception_gateplaces the host in-band on the agent's LLM forward pass — a new execution model not covered by existing RFCs. This warrants a formal RFC. - Suggested reviewer: @Darktex
ALIGNMENT FLAG 3: New HTTP in-sandbox server contradicts WebSocket-first direction
- Principle at stake: Communication patterns (INVARIANTS.md)
- The concern:
sandbox_mcp_server.pyusesBaseHTTPRequestHandleron port 8765. MCP stdio transport is already supported by the same file. The HTTP server creates a new HTTP dependency against the stated deprecation direction. - Suggested reviewer: @Darktex
Other Notes
- The grading anti-gaming logic (
_revert_test_files,_list_changed_test_paths) is well-designed and correctly reverts agent-modified test files. Good defensive design. - The
coding_agent_envrename fromopencode_envis clean and consistent.
Automated review by Claude Code | Learn more
Darktex
left a comment
There was a problem hiding this comment.
Note: This is an automated review by Claude Code, not a human review.
Alignment Review Report
Automated Checks
- Lint: FAIL — 4 unused import/variable errors in
examples/mini_swe_env/trajectory_store.py - Format: PASS
- Debug code: CLEAN
Tier 1: Fixes Required (blocking)
- Lint failures —
trajectory_store.py: unused imports (os,time,typing.Iterator) and unused variable (local_path). Fix withruff --fix. - Security: Unauthenticated
_handle_exit— Same issue as PR #694.interception_server.py_handle_exithas no auth check while other handlers do. - Silently dropped tunables —
coding_environment.py_build_session_factory:disable_thinking,top_logprobs,max_tokens_capare silently dropped for the Pi branch. Either document/raise or thread through. - Broken doc reference —
harness.pyreferencesCORE_CHANGES.mdwhich doesn't exist in the PR or repo.
Tier 2: Alignment Discussion
FLAG 1: In-sandbox reward computation — sandbox_mcp_server.py computes reward = passed / total and writes reward.txt inside the sandbox. The agent could write to reward.txt to override its own reward. Reward authority should flow host→sandbox, not sandbox→host.
- Principle: "Rewards inside environment" (INVARIANTS.md §3)
FLAG 2: _handle_exit unauthenticated — beyond the security fix, should agents be able to signal their own exit, or only the host process?
- Principle: Agent isolation (INVARIANTS.md §1)
FLAG 3: interception_gate bypasses the Gymnasium step loop — introduces a third communication channel (HTTP endpoints) alongside WebSocket and MCP.
- Principle: "WebSocket for all environment communication" (INVARIANTS.md §4)
FLAG 4: No RFC for InterceptionServer as a new 674-line core primitive.
- Principle: Core changes require RFC (PRINCIPLES.md)
FLAG 5: opencode_env → coding_agent_env rename without backward-compatibility shim. Any downstream users will break silently.
- Principle: Minimize lifecycle deltas (PRINCIPLES.md)
Summary
- 4 blocking mechanical issues
- 5 alignment points for human review
- Structural foundation is solid; main blockers are security fix + architectural sign-off.
Automated review by Claude Code
rycerzes
left a comment
There was a problem hiding this comment.
Closing alongside #694 — same reason: re-landing as a reviewable stack, not dropping the work.
Context:
- This PR is stacked on #694 and patches
cli_driver.py, a file that only exists on the #694 branch — so it can't rebase onto main independently. Closing #694 alone would strand it, so both close together. - The work splits cleanly:
mini_swe_envin black-box mode is reviewable standalone and doesn't depend on the interception rebuild or the RFC. Re-basing its task model on the SWE-rebench V2 schema, with the SWE-Gym loader kept as a compat adapter. - The
async_grpo/control plane is the piece gated on the RFC — the cross-framework research (Polar, verifiers, AReaL, TRL-native) reshaped several decisions there (token-level trace contract, TRL owning IS correction, session-ID-as-API-key auth, prefix-break forking).
The env package and grading logic (anti-gaming test-file revert, binary all-pass reward) carry forward largely intact and land as the mini_swe_env split PR.
Summary
This PR adds a new
mini_swe_envand a complete async RL training path for SWE tasks: Pi agent in sandbox, host-side interception, vLLM generation, and Async GRPO updates. It is self-contained for review againstfeat/multi-harnessand supports both direct black-box rollouts and interception-driven async training. The goal is to make SWE-Gym-style training runnable end-to-end on local Docker and HF Space + HF Sandbox.Type of Change
Alignment Checklist
Before submitting, verify:
.claude/docs/PRINCIPLES.mdand this PR aligns with our principles.claude/docs/INVARIANTS.mdand no invariants are violated/pre-submit-pr(orbash .claude/hooks/lint.shand tests) and addressed all issuesWhat this PR does (self-contained)
1) Adds a new SWE environment package
New package:
envs/mini_swe_env/models.py,task_loader_swegym.py)grading.py) with binary resolved/not-resolved semanticsharness.py) for Pi/OpenCode sessionsserver/swe_environment.py,server/app.py)client.py) and env package metadata (pyproject.toml,uv.lock)2) Supports both rollout modes needed by SWE workflows
black_boxmode: agent calls upstream LLM directlyinterception_gatemode: trainer owns generation + logprob capture3) Adds async GRPO control plane + rollout worker
New modules in
envs/mini_swe_env/async_grpo/:control_plane.py— starts/owns interception server lifecyclerollout_worker.py— custom worker implementing TRL rollout protocolapply_chat_template/v1/completionswithreturn_token_ids=True,logprobs=0input_ids,completion_mask,old_log_probsfor trainer4) Adds runnable training/examples and HF Space deploy path
examples/mini_swe_env/train_swe_async_grpo.pyexamples/mini_swe_env/run_swe_sample.pyenvs/mini_swe_env/async_grpo/space_app/Dockerfileenvs/mini_swe_env/async_grpo/space_app/start.shenvs/mini_swe_env/async_grpo/space_app/deploy_hf_space.shenvs/mini_swe_env/async_grpo/space_app/README.md5) Core harness compatibility updates used by this env
src/openenv/core/harness/agents/cli_driver.pyupdated for interception + queue handling needed by async rollouts.Execution modes
black_boxmodeinterception_gatemodeanswerhost-side)/rollout/{id}/v1/chat/completions,/rollout/{id}/v1/tools/answer, internal vLLM/v1/completionsNetwork diagram (async training path)
flowchart LR subgraph SPACE[HF Space / Trainer Host] IS["InterceptionServer<br/>0.0.0.0:7860<br/>/rollout/:rollout_id/v1/chat/completions<br/>/rollout/:rollout_id/v1/tools/answer"] RW["SWERolloutWorker<br/>threads + queues"] TR["AsyncGRPOTrainer"] VLLM["vLLM<br/>127.0.0.1:8000<br/>/v1/completions"] IS <--> RW RW --> TR RW <--> VLLM TR -. weight sync .-> VLLM end SB["Sandbox (HF or Docker)<br/>Pi agent + /testbed"] -->|chat completion request| IS SB -->|answer tool call| IS RW -->|create/exec/kill sandboxes| HFAPI["HF Sandbox API"]black_boxmode at a glanceReward and grading integrity
answer()is routed to host tool handler (not sandbox-owned grader).FAIL_TO_PASS,PASS_TO_PASS) with binary reward1.0/0.0.Note
This PR depends and should be merged after #694
CC: @burtenshaw