From 70ce49932f239ef445a22aa1bcd23c1809685763 Mon Sep 17 00:00:00 2001 From: Jiajun Li Date: Tue, 7 Apr 2026 20:00:47 +0000 Subject: [PATCH 1/2] chore: remove obsolete standalone swe-agent server.py This FastAPI server wrapping Harbor has been superseded by the Harbor CLI integration. The functionality is no longer needed. Made-with: Cursor --- examples/experimental/swe-agent-v2/server.py | 298 ------------------- 1 file changed, 298 deletions(-) delete mode 100644 examples/experimental/swe-agent-v2/server.py diff --git a/examples/experimental/swe-agent-v2/server.py b/examples/experimental/swe-agent-v2/server.py deleted file mode 100644 index 137b9989752..00000000000 --- a/examples/experimental/swe-agent-v2/server.py +++ /dev/null @@ -1,298 +0,0 @@ -""" -FastAPI server wrapping Harbor for generalized agent-environment orchestration. - -Provides a single ``/run`` endpoint that handles any task type (SWE-bench, -Terminal-Bench, custom datasets, etc.) through Harbor's unified Trial API. -Harbor handles Docker orchestration, agent execution, and grading — the -server is task-type agnostic. - -Requires: - - Harbor installed: pip install harbor-framework - - Prepared task dirs under HARBOR_TASKS_DIR (via adapters or prepare_harbor_tasks.py) - -Usage: - python server.py --port 11000 --max-concurrent 8 -""" - -import argparse -import asyncio -import logging -import os -import re -import traceback -from collections.abc import AsyncIterator -from contextlib import asynccontextmanager -from pathlib import Path -from typing import Any - -import uvicorn -from fastapi import FastAPI -from pydantic import BaseModel - -logger = logging.getLogger(__name__) - - -_semaphore: asyncio.Semaphore | None = None - - -@asynccontextmanager -async def _lifespan(app: FastAPI) -> AsyncIterator[None]: - global _semaphore - max_concurrent = int(os.getenv("AGENT_MAX_CONCURRENT", os.getenv("SWE_AGENT_MAX_CONCURRENT", "8"))) - _semaphore = asyncio.Semaphore(max_concurrent) - logger.info(f"Initialized semaphore with max_concurrent={max_concurrent}") - yield - - -app = FastAPI(title="Agent Environment Server (Harbor)", lifespan=_lifespan) - - -class RunRequest(BaseModel): - base_url: str - model: str - sampling_params: dict[str, Any] = {} - api_key: str = "dummy" - - instance_id: str = "" - agent_name: str = "mini-swe-agent" - max_seq_len: int | None = None - - model_config = {"extra": "allow"} - - -class RunResponse(BaseModel): - reward: float = 0.0 - exit_status: str = "" - agent_metrics: dict[str, Any] = {} - eval_report: dict[str, Any] = {} - - -def get_semaphore() -> asyncio.Semaphore: - assert _semaphore is not None, "Semaphore not initialized — server not started?" - return _semaphore - - -_TIMEOUT_EXCEPTIONS = {"AgentTimeoutError", "VerifierTimeoutError", "EnvironmentStartTimeoutError"} -_OUTPUT_LIMIT_EXCEPTIONS = {"MaxSeqLenExceededError"} - -_HOST_PROCESS_AGENTS = {"terminus-2", "terminus-1", "terminus"} - -_SAFE_INSTANCE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$") - - -def _extract_exit_status(result) -> str: - """Derive exit status from Harbor TrialResult.""" - exc = getattr(result, "exception_info", None) - if exc is not None: - exc_type = getattr(exc, "exception_type", "") - if exc_type in _TIMEOUT_EXCEPTIONS: - return "TimeLimitExceeded" - if exc_type in _OUTPUT_LIMIT_EXCEPTIONS: - return "SequenceLengthLimitExceeded" - return "AgentError" - if getattr(result, "verifier_result", None) is not None: - return "Submitted" - return "Unknown" - - -def _timing_duration_sec(timing) -> float | None: - started = getattr(timing, "started_at", None) - finished = getattr(timing, "finished_at", None) - if started and finished: - return (finished - started).total_seconds() - return None - - -def _extract_reward(result) -> tuple[float, dict[str, Any]]: - """Extract scalar reward and full eval report from Harbor TrialResult. - - Looks for the ``"reward"`` key first, then falls back to the first value - in the rewards dict. Works with both ``reward.txt`` and ``reward.json``. - """ - vr = getattr(result, "verifier_result", None) - if vr is None: - return 0.0, {} - rewards = getattr(vr, "rewards", None) or {} - reward = float(rewards.get("reward", next(iter(rewards.values()), 0.0))) - return reward, dict(rewards) - - -def _extract_metrics(result) -> dict[str, Any]: - """Extract agent metrics from Harbor TrialResult.""" - metrics: dict[str, Any] = {} - try: - ar = getattr(result, "agent_result", None) - if ar is not None: - for field in ("n_input_tokens", "n_output_tokens", "cost_usd"): - val = getattr(ar, field, None) - if val is not None: - metrics[field] = val - agent_meta = getattr(ar, "metadata", None) - if isinstance(agent_meta, dict): - metrics.update(agent_meta) - - agent_timing = getattr(result, "agent_execution", None) - if agent_timing is not None: - dur = _timing_duration_sec(agent_timing) - if dur is not None: - metrics["agent_run_time"] = dur - - verifier_timing = getattr(result, "verifier", None) - if verifier_timing is not None: - dur = _timing_duration_sec(verifier_timing) - if dur is not None: - metrics["eval_time"] = dur - except Exception as e: - logger.warning(f"Failed to extract metrics: {e}", exc_info=True) - return metrics - - -def _error_response(exit_status: str) -> dict[str, Any]: - return {"reward": 0.0, "exit_status": exit_status, "agent_metrics": {}, "eval_report": {}} - - -async def _run_trial(request: RunRequest) -> dict[str, Any]: - """Run a Harbor trial for a single task instance. - - Task-type agnostic — all differentiation (environment, grading harness) - is encoded in the Harbor task directory's 4 files. - """ - try: - from harbor.models.trial.config import AgentConfig, EnvironmentConfig, TaskConfig, TrialConfig - from harbor.trial.trial import Trial - except ImportError: - logger.error("Harbor not installed. Install with: pip install harbor-framework") - return _error_response("ImportError") - - try: - tasks_dir = Path( - os.getenv("HARBOR_TASKS_DIR", "/root/harbor_tasks"), - ).resolve() - - if not request.instance_id: - logger.error("Empty instance_id") - return _error_response("InvalidInstanceId") - - raw_id = request.instance_id - if not _SAFE_INSTANCE_ID.match(raw_id): - logger.error(f"Invalid instance_id rejected: {raw_id!r}") - return _error_response("InvalidInstanceId") - - # Normalize and verify the path stays within tasks_dir. - # Uses the pattern recommended by CodeQL (py/path-injection): - # normpath(join(base, user_input)) + startswith(base) - tasks_dir_str = str(tasks_dir) - task_path = os.path.normpath(os.path.join(tasks_dir_str, raw_id)) - if not task_path.startswith(tasks_dir_str): - logger.error(f"Path traversal blocked: {raw_id!r}") - return _error_response("InvalidInstanceId") - - if not os.path.exists(task_path): - logger.error(f"Task directory not found: {task_path}") - return _error_response("TaskNotFound") - - task_path = Path(task_path) - agent_kwargs: dict[str, Any] = {} - agent_env: dict[str, str] = {} - - is_host_agent = request.agent_name in _HOST_PROCESS_AGENTS - - if "hosted_vllm" in request.model or "openai" in request.model: - agent_kwargs["model_info"] = { - "max_input_tokens": int(os.getenv("AGENT_MAX_INPUT_TOKENS", "32768")), - "max_output_tokens": int(os.getenv("AGENT_MAX_OUTPUT_TOKENS", "8192")), - "input_cost_per_token": 0.0, - "output_cost_per_token": 0.0, - } - - if request.max_seq_len is not None: - agent_kwargs["max_seq_len"] = request.max_seq_len - - if is_host_agent: - agent_kwargs["api_base"] = request.base_url - agent_kwargs["api_key"] = request.api_key or "dummy" - agent_kwargs["enable_summarize"] = False - agent_env = { - "OPENAI_API_KEY": request.api_key or "dummy", - "OPENAI_API_BASE": request.base_url, - } - else: - agent_env = { - "OPENAI_API_BASE": request.base_url, - "OPENAI_API_KEY": request.api_key, - "HOSTED_VLLM_API_BASE": request.base_url, - "HOSTED_VLLM_API_KEY": request.api_key, - "MSWEA_COST_TRACKING": "ignore_errors", - } - - config = TrialConfig( - task=TaskConfig(path=task_path), - agent=AgentConfig( - name=request.agent_name, - model_name=request.model, - env=agent_env, - kwargs=agent_kwargs, - ), - environment=EnvironmentConfig( - type="docker", - delete=os.getenv("HARBOR_DELETE_CONTAINERS", "false").lower() in ("true", "1", "t"), - ), - ) - - trial = Trial(config=config) - result = await trial.run() - - reward, eval_report = _extract_reward(result) - exit_status = _extract_exit_status(result) - agent_metrics = _extract_metrics(result) - - return { - "reward": reward, - "exit_status": exit_status, - "agent_metrics": agent_metrics, - "eval_report": eval_report, - } - - except Exception as e: - logger.error(f"Harbor trial failed: {e}\n{traceback.format_exc()}") - return _error_response(f"Error: {type(e).__name__}") - - -@app.post("/run") -async def run_instance(request: RunRequest) -> RunResponse: - """Run an agent on a single task instance via Harbor.""" - logger.info(f"Running instance: {request.instance_id}") - async with get_semaphore(): - result = await _run_trial(request) - logger.info( - f"Instance {request.instance_id} finished: exit_status={result['exit_status']}, reward={result['reward']}" - ) - return RunResponse(**result) - - -@app.get("/health") -async def health(): - return {"status": "ok"} - - -def main(): - parser = argparse.ArgumentParser(description="Agent Environment Server (Harbor)") - parser.add_argument("--host", type=str, default="0.0.0.0") - parser.add_argument("--port", type=int, default=11000) - parser.add_argument("--max-concurrent", type=int, default=8) - args = parser.parse_args() - - os.environ["AGENT_MAX_CONCURRENT"] = str(args.max_concurrent) - - os.environ.setdefault("MSWEA_API_KEY", "dummy") - os.environ.setdefault("HOSTED_VLLM_API_KEY", "dummy") - - logging.basicConfig( - level=logging.INFO, - format="%(asctime)s %(name)s %(levelname)s %(message)s", - ) - uvicorn.run(app, host=args.host, port=args.port) - - -if __name__ == "__main__": - main() From 66d39e7363dccd127aa03bd36515d86d9ea5fc6d Mon Sep 17 00:00:00 2001 From: Jiajun Li Date: Tue, 7 Apr 2026 21:48:19 +0000 Subject: [PATCH 2/2] chore: also remove obsolete run-qwen3.sh launcher script Co-Authored-By: Claude Opus 4.6 (1M context) --- .../experimental/swe-agent-v2/run-qwen3.sh | 166 ------------------ 1 file changed, 166 deletions(-) delete mode 100755 examples/experimental/swe-agent-v2/run-qwen3.sh diff --git a/examples/experimental/swe-agent-v2/run-qwen3.sh b/examples/experimental/swe-agent-v2/run-qwen3.sh deleted file mode 100755 index ac0d8bf8634..00000000000 --- a/examples/experimental/swe-agent-v2/run-qwen3.sh +++ /dev/null @@ -1,166 +0,0 @@ -#!/bin/bash -# Agent V2 launcher (Qwen3-4B): Miles <-> Harbor agent orchestration. -# -# Supports any task type (SWE-bench, Terminal-Bench, custom) via Harbor. - -pkill -9 sglang 2>/dev/null || true -sleep 3 -ray stop --force 2>/dev/null || true -pkill -9 ray 2>/dev/null || true -pkill -9 python 2>/dev/null || true -sleep 3 -pkill -9 ray 2>/dev/null || true -pkill -9 python 2>/dev/null || true -sleep 3 - -set -euo pipefail - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -MILES_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" - -source "$MILES_ROOT/scripts/models/qwen3-4B.sh" - -BASE_DIR=/root/shared -AGENT_SERVER_URL="${AGENT_SERVER_URL:-${SWE_AGENT_URL:-http://agent_env:11000}}" -HARBOR_TASKS_DIR="${HARBOR_TASKS_DIR:-/root/harbor_tasks}" -ROUTER_EXTERNAL_HOST="${MILES_ROUTER_EXTERNAL_HOST:-$(hostname)}" - -CKPT_ARGS=( - --hf-checkpoint $BASE_DIR/Qwen3-4B - --ref-load $BASE_DIR/Qwen3-4B_torch_dist - --save $BASE_DIR/Qwen3-4B_agent_V2/ - --save-interval 100 -) - -ROLLOUT_ARGS=( - --prompt-data /root/swe_train.jsonl - --input-key prompt - --metadata-key metadata - --rollout-shuffle - - --num-rollout 3000 - --rollout-batch-size 1 - --n-samples-per-prompt 1 - --rollout-temperature 0.8 - --rollout-max-response-len 8192 - --global-batch-size 1 - --balance-data -) - -PERF_ARGS=( - --tensor-model-parallel-size 1 - --pipeline-model-parallel-size 1 - --context-parallel-size 1 - --expert-model-parallel-size 1 - --expert-tensor-parallel-size 1 - - --recompute-granularity full - --recompute-method uniform - --recompute-num-layers 1 - - --use-dynamic-batch-size - --max-tokens-per-gpu 2048 -) - -GRPO_ARGS=( - --advantage-estimator grpo - --use-kl-loss - --kl-loss-coef 0.01 - --kl-loss-type low_var_kl - --entropy-coef 0.0 - --eps-clip 0.2 - --eps-clip-high 0.28 -) - -OPTIMIZER_ARGS=( - --optimizer adam - --lr 1e-6 - --lr-decay-style constant - --weight-decay 0.1 - --adam-beta1 0.9 - --adam-beta2 0.98 -) - -SGLANG_ARGS=( - --rollout-num-gpus-per-engine 1 - --sglang-mem-fraction-static 0.8 - --sglang-tool-call-parser qwen25 - --sglang-reasoning-parser qwen3 - - --use-miles-router - --sglang-router-port 30000 -) - -AGENT_ARGS=( - --custom-generate-function-path miles.rollout.generate_hub.agentic_tool_call.generate - --custom-agent-function-path swe_agent_function.run - --custom-rm-path generate.reward_func - --rollout-function-path generate.RolloutFn - --dynamic-sampling-filter-path miles.rollout.filter_hub.dynamic_sampling_filters.check_no_aborted - --tito-model qwen3 - --chat-template-path autofix - --use-session-server -) - -WANDB_ARGS=( - # --use-wandb - # --wandb-project miles-agent-v2 - # --wandb-group agent-v2 -) - -MISC_ARGS=( - --attention-dropout 0.0 - --hidden-dropout 0.0 - --accumulate-allreduce-grads-in-fp32 - --attention-softmax-in-fp32 - --attention-backend flash -) - -DEBUG_ARGS=( - --debug-rollout-only -) - -# ── Start Ray ──────────────────────────────────────────────────────── -export MASTER_ADDR=${MASTER_ADDR:-"127.0.0.1"} -ray start --head \ - --node-ip-address "$MASTER_ADDR" \ - --num-gpus 1 \ - --disable-usage-stats \ - --dashboard-host=0.0.0.0 \ - --dashboard-port=8265 \ - --port=8899 - -RUNTIME_ENV=$(python3 -c " -import json, sys -print(json.dumps({'env_vars': { - 'PYTHONPATH': '/root/Megatron-LM/:${SCRIPT_DIR}:${MILES_ROOT}', - 'CUDA_DEVICE_MAX_CONNECTIONS': '1', - 'MILES_EXPERIMENTAL_ROLLOUT_REFACTOR': '1', - 'AGENT_SERVER_URL': '${AGENT_SERVER_URL}', - 'AGENT_MODEL_NAME': '${AGENT_MODEL_NAME:-model}', - 'MILES_ROUTER_EXTERNAL_HOST': '${ROUTER_EXTERNAL_HOST}', - 'HARBOR_TASKS_DIR': '${HARBOR_TASKS_DIR}', - 'MILES_HOST_IP': '${MILES_HOST_IP:-$(hostname)}', - 'NCCL_NVLS_ENABLE': '0', -}})) -") - -ray job submit \ - --address="http://127.0.0.1:8265" \ - --runtime-env-json="$RUNTIME_ENV" \ - -- python3 "$MILES_ROOT/train.py" \ - --colocate \ - --actor-num-nodes 1 \ - --actor-num-gpus-per-node 1 \ - --rollout-num-gpus 1 \ - "${MODEL_ARGS[@]}" \ - "${CKPT_ARGS[@]}" \ - "${ROLLOUT_ARGS[@]}" \ - "${OPTIMIZER_ARGS[@]}" \ - "${GRPO_ARGS[@]}" \ - "${PERF_ARGS[@]}" \ - "${SGLANG_ARGS[@]}" \ - "${AGENT_ARGS[@]}" \ - "${WANDB_ARGS[@]}" \ - "${MISC_ARGS[@]}" \ - "${DEBUG_ARGS[@]}"