From 8744060cf94df5e54fe0aa09ec5c88689df17e78 Mon Sep 17 00:00:00 2001 From: Shi Dong Date: Tue, 21 Jul 2026 03:16:55 -0700 Subject: [PATCH 1/6] Prepare swe-agent-v2 sync validation --- examples/experimental/swe-agent-v2/run.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/examples/experimental/swe-agent-v2/run.py b/examples/experimental/swe-agent-v2/run.py index 053547f192..f250734d8a 100644 --- a/examples/experimental/swe-agent-v2/run.py +++ b/examples/experimental/swe-agent-v2/run.py @@ -41,10 +41,13 @@ class ScriptArgs(U.ExecuteTrainConfig): prompt_data: str = "/root/swe_train.jsonl" # Training settings - max_seq_len: int = 16384 + max_seq_len: int = 65536 + num_rollout: int = 3000 rollout_batch_size: int = 2 n_samples_per_prompt: int = 4 global_batch_size: int = 8 + save_interval: int = 100 + save_traces_dir: str = "" # Agent settings agent_server_url: str = os.environ.get( @@ -100,7 +103,7 @@ def execute(args: ScriptArgs): f"--hf-checkpoint {args.hf_checkpoint} " f"--ref-load {args.ref_load} " f"--save {args.save_dir} " - "--save-interval 100 " + f"--save-interval {args.save_interval} " ) rollout_args = ( @@ -108,7 +111,7 @@ def execute(args: ScriptArgs): "--input-key prompt " "--metadata-key metadata " "--rollout-shuffle " - "--num-rollout 3000 " + f"--num-rollout {args.num_rollout} " f"--rollout-batch-size {args.rollout_batch_size} " f"--n-samples-per-prompt {args.n_samples_per_prompt} " "--rollout-temperature 0.8 " @@ -159,7 +162,6 @@ def execute(args: ScriptArgs): "--sglang-mem-fraction-static 0.7 " "--sglang-tool-call-parser glm47 " "--sglang-reasoning-parser glm45 " - "--use-miles-router " "--sglang-router-port 31000 " # TODO: speculative decoding has issue, need to fix later ) @@ -191,6 +193,10 @@ def execute(args: ScriptArgs): debug_args = "--debug-rollout-only " if args.mode == "debug_rollout_only" else "" + trace_args = "" + if args.save_traces_dir: + trace_args = f"--save-traces-dir {args.save_traces_dir} " + wandb_args = "" if args.wandb_key: wandb_args = ( @@ -217,6 +223,7 @@ def execute(args: ScriptArgs): f"{grpo_args}" f"{wandb_args}" f"{prometheus_args}" + f"{trace_args}" f"{perf_args}" f"{sglang_args}" f"{agent_args}" From e8976b44b1fccd2df321993d6e269e950d35a455 Mon Sep 17 00:00:00 2001 From: Shi Dong Date: Tue, 21 Jul 2026 03:45:39 -0700 Subject: [PATCH 2/6] Use current Miles trace dump flag --- examples/experimental/swe-agent-v2/run.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/experimental/swe-agent-v2/run.py b/examples/experimental/swe-agent-v2/run.py index f250734d8a..e89f92a0eb 100644 --- a/examples/experimental/swe-agent-v2/run.py +++ b/examples/experimental/swe-agent-v2/run.py @@ -195,7 +195,7 @@ def execute(args: ScriptArgs): trace_args = "" if args.save_traces_dir: - trace_args = f"--save-traces-dir {args.save_traces_dir} " + trace_args = f"--dump-details {args.save_traces_dir} " wandb_args = "" if args.wandb_key: From 24c1342361d862d461bbcf9873d4a5093ff37ac0 Mon Sep 17 00:00:00 2001 From: Shi Dong Date: Tue, 21 Jul 2026 03:51:07 -0700 Subject: [PATCH 3/6] Let Miles resolve the rollout host IP --- examples/experimental/swe-agent-v2/run.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/experimental/swe-agent-v2/run.py b/examples/experimental/swe-agent-v2/run.py index e89f92a0eb..0fbd442096 100644 --- a/examples/experimental/swe-agent-v2/run.py +++ b/examples/experimental/swe-agent-v2/run.py @@ -56,7 +56,7 @@ class ScriptArgs(U.ExecuteTrainConfig): agent_model_name: str = os.environ.get("AGENT_MODEL_NAME", "model") harbor_tasks_dir: str = os.environ.get("HARBOR_TASKS_DIR", "/root/harbor_tasks") router_external_host: str = os.environ.get("MILES_ROUTER_EXTERNAL_HOST", socket.gethostname()) # public IP - miles_host_ip: str = os.environ.get("MILES_HOST_IP", socket.gethostname()) # cluster/pod IP + miles_host_ip: str = os.environ.get("MILES_HOST_IP", "") # optional cluster/pod IP override # W&B settings wandb_key: str = os.environ.get("WANDB_KEY", os.environ.get("WANDB_API_KEY", "")) @@ -240,8 +240,9 @@ def execute(args: ScriptArgs): "AGENT_MODEL_NAME": args.agent_model_name, "MILES_ROUTER_EXTERNAL_HOST": args.router_external_host, "HARBOR_TASKS_DIR": args.harbor_tasks_dir, - "MILES_HOST_IP": args.miles_host_ip, } + if args.miles_host_ip: + extra_env_vars["MILES_HOST_IP"] = args.miles_host_ip U.execute_train( train_args=train_args, From 1e5a196b74689aea80d2d541a6835a216843ef84 Mon Sep 17 00:00:00 2001 From: Shi Dong Date: Tue, 21 Jul 2026 04:53:15 -0700 Subject: [PATCH 4/6] Promote SWE-Agent V2 example --- docs/user-guide/agentic-chat-template.md | 2 +- docs/user-guide/rollout-endpoints.md | 6 +- examples/README.md | 1 + .../experimental/glm47-reasoning/README.md | 8 + .../run-glm47-reasoning-async.py | 2 + .../run-glm47-reasoning.py | 2 + .../experimental/swe-agent-v2-amd/README.md | 4 +- examples/experimental/swe-agent-v2/README.md | 321 ------------------ .../README.md | 149 -------- .../launch.sh | 71 ---- examples/swe-agent-v2/README.md | 108 ++++++ .../swe-agent-v2/download_and_process_data.py | 4 +- .../swe-agent-v2/generate.py | 2 +- .../run-glm47-flash-agentic-async.py | 2 +- .../{experimental => }/swe-agent-v2/run.py | 4 +- .../swe-agent-v2/swe_agent_function.py | 2 +- 16 files changed, 134 insertions(+), 554 deletions(-) create mode 100644 examples/experimental/glm47-reasoning/README.md rename examples/experimental/{swe-agent-v2 => glm47-reasoning}/run-glm47-reasoning-async.py (99%) rename examples/experimental/{swe-agent-v2 => glm47-reasoning}/run-glm47-reasoning.py (99%) delete mode 100644 examples/experimental/swe-agent-v2/README.md delete mode 100644 examples/experimental/swe-agent-v2/train-tb2-via-harbor-private-server/README.md delete mode 100755 examples/experimental/swe-agent-v2/train-tb2-via-harbor-private-server/launch.sh create mode 100644 examples/swe-agent-v2/README.md rename examples/{experimental => }/swe-agent-v2/download_and_process_data.py (97%) rename examples/{experimental => }/swe-agent-v2/generate.py (98%) rename examples/{experimental => }/swe-agent-v2/run-glm47-flash-agentic-async.py (99%) rename examples/{experimental => }/swe-agent-v2/run.py (98%) rename examples/{experimental => }/swe-agent-v2/swe_agent_function.py (98%) diff --git a/docs/user-guide/agentic-chat-template.md b/docs/user-guide/agentic-chat-template.md index 23b53beb01..e2ad461145 100644 --- a/docs/user-guide/agentic-chat-template.md +++ b/docs/user-guide/agentic-chat-template.md @@ -45,7 +45,7 @@ ROLLOUT_ARGS+=( ## Example -A full multi-turn agentic setup on the session-server TITO path lives in [`examples/experimental/swe-agent-v2`](https://github.com/radixark/miles/tree/main/examples/experimental/swe-agent-v2): its launchers wire `--use-session-server` + `--tito-model glm47` + `--tito-allowed-append-roles user tool` against a real SWE agent. +A full multi-turn agentic setup on the session-server TITO path lives in [`examples/swe-agent-v2`](https://github.com/radixark/miles/tree/main/examples/swe-agent-v2): its launchers wire `--use-session-server` + `--tito-model glm47` + `--tito-allowed-append-roles user tool` against a real SWE agent. ## Add a new model diff --git a/docs/user-guide/rollout-endpoints.md b/docs/user-guide/rollout-endpoints.md index 4cef87074d..1e18b9fc68 100644 --- a/docs/user-guide/rollout-endpoints.md +++ b/docs/user-guide/rollout-endpoints.md @@ -33,7 +33,7 @@ Key modules: |---|---| | `miles/rollout/base_types.py` | `GenerateFnInput` / `GenerateFnOutput` | | `miles/rollout/inference_rollout/inference_rollout_common.py` | Builds a `GenerateState` and calls the generate function | -| `MILES_EXPERIMENTAL_ROLLOUT_REFACTOR=1` | Enables the new path (see `examples/experimental/swe-agent-v2`) | +| `MILES_EXPERIMENTAL_ROLLOUT_REFACTOR=1` | Enables the new path (see `examples/swe-agent-v2`) | ### Generate function basics @@ -162,7 +162,7 @@ Generator entry point: Example: -- [`examples/experimental/swe-agent-v2`](https://github.com/radixark/miles/tree/main/examples/experimental/swe-agent-v2): +- [`examples/swe-agent-v2`](https://github.com/radixark/miles/tree/main/examples/swe-agent-v2): multi-turn agentic SWE agent on the session-server TITO path, with ready-to-run launchers. Wire-up (as used by swe-agent-v2): @@ -206,7 +206,7 @@ The hook is **entirely optional and safe to omit**: - It only fires when `--custom-agent-function-path` is set, so non-agentic runs never invoke it. -See [`swe_agent_function.abort`](https://github.com/radixark/miles/blob/main/examples/experimental/swe-agent-v2/swe_agent_function.py) +See [`swe_agent_function.abort`](https://github.com/radixark/miles/blob/main/examples/swe-agent-v2/swe_agent_function.py) for a reference implementation that flushes the Harbor agent server. ### Customizing the wrapper diff --git a/examples/README.md b/examples/README.md index 88f1352418..1233ea1c15 100644 --- a/examples/README.md +++ b/examples/README.md @@ -18,6 +18,7 @@ These examples provide concrete examples to leverage miles in your own RL workfl - **[retool](./retool)**: Demonstrates the retool functionality for tool-enabled language model generation. - **[search-r1](./search-r1)**: A minimal reproduction of Search-R1, featuring multi-turn conversation and tool-calling. - **[strands-agents](./strands-agents)**: Integration example with the Strands-Agents scaffolding framework. +- **[swe-agent-v2](./swe-agent-v2)**: Trains coding and terminal agents with Harbor-managed sandboxes and verifier rewards. - **[tau-bench](./tau-bench)**: Training in an agentic multi-turn tool use environment (Tau-bench). - **[train_infer_mismatch_helper](./train_infer_mismatch_helper)**: Algorithmic methods for rollout correction (e.g., TIS, MIS). - **[true_on_policy](./true_on_policy)**: Ensures strictly equal log probabilities between inference (SGLang) and training engines. diff --git a/examples/experimental/glm47-reasoning/README.md b/examples/experimental/glm47-reasoning/README.md new file mode 100644 index 0000000000..5ccd08ce97 --- /dev/null +++ b/examples/experimental/glm47-reasoning/README.md @@ -0,0 +1,8 @@ +# GLM-4.7 reasoning launchers + +Experimental synchronous and fully asynchronous launchers for training the full +GLM-4.7 model on GSM8K. These scripts use the math reward path and do not depend +on SWE-Agent, Harbor, or an agent server. + +- `run-glm47-reasoning.py`: colocated synchronous training. +- `run-glm47-reasoning-async.py`: disaggregated fully asynchronous training. diff --git a/examples/experimental/swe-agent-v2/run-glm47-reasoning-async.py b/examples/experimental/glm47-reasoning/run-glm47-reasoning-async.py similarity index 99% rename from examples/experimental/swe-agent-v2/run-glm47-reasoning-async.py rename to examples/experimental/glm47-reasoning/run-glm47-reasoning-async.py index 0205d466df..d52fdd3477 100644 --- a/examples/experimental/swe-agent-v2/run-glm47-reasoning-async.py +++ b/examples/experimental/glm47-reasoning/run-glm47-reasoning-async.py @@ -1,5 +1,7 @@ """GLM-4.7 Full (355B-A32B) fully-async reasoning training with GSM8K data. +This launcher is independent of the SWE-agent environment example. + Disaggregated fully-async variant of run-glm47-reasoning.py: training and rollout run on separate nodes concurrently. Uses train_async.py and the fully_async_rollout module so that weight updates do not block generation. diff --git a/examples/experimental/swe-agent-v2/run-glm47-reasoning.py b/examples/experimental/glm47-reasoning/run-glm47-reasoning.py similarity index 99% rename from examples/experimental/swe-agent-v2/run-glm47-reasoning.py rename to examples/experimental/glm47-reasoning/run-glm47-reasoning.py index 347edf1752..497433f6fa 100644 --- a/examples/experimental/swe-agent-v2/run-glm47-reasoning.py +++ b/examples/experimental/glm47-reasoning/run-glm47-reasoning.py @@ -1,5 +1,7 @@ """GLM-4.7 Full (355B-A32B) reasoning training with GSM8K data. +This launcher is independent of the SWE-agent environment example. + Debug script: uses math (GSM8K) data instead of agentic tool use to verify that the training pipeline produces nonzero rewards and learns successfully. diff --git a/examples/experimental/swe-agent-v2-amd/README.md b/examples/experimental/swe-agent-v2-amd/README.md index 104f10d084..587e41b242 100644 --- a/examples/experimental/swe-agent-v2-amd/README.md +++ b/examples/experimental/swe-agent-v2-amd/README.md @@ -1,8 +1,8 @@ # Agent V2 — Qwen3 / Qwen3-Coder on ROCm/AMD -ROCm/AMD-oriented variant of [`../swe-agent-v2`](../swe-agent-v2). It is **self-contained** — +ROCm/AMD-oriented variant of [`../../swe-agent-v2`](../../swe-agent-v2). It is **self-contained** — it carries its own copies of `run.py`, `generate.py`, and `swe_agent_function.py` so it runs -without modifying the shared example. See [`../swe-agent-v2/README.md`](../swe-agent-v2/README.md) +without modifying the shared example. See [`../../swe-agent-v2/README.md`](../../swe-agent-v2/README.md) for the full pipeline/architecture; this doc covers only the Qwen3 launcher and the ROCm notes. ## Qwen3 / Qwen3-Coder diff --git a/examples/experimental/swe-agent-v2/README.md b/examples/experimental/swe-agent-v2/README.md deleted file mode 100644 index 1c8aebc54b..0000000000 --- a/examples/experimental/swe-agent-v2/README.md +++ /dev/null @@ -1,321 +0,0 @@ -# Agent V2: Generalized Agent-Environment RL Training with Harbor - -A unified pipeline for training agents on **mixed datasets** — SWE-bench, Terminal-Bench, custom tasks, etc. — through a single endpoint. Uses **TITO (Token In Token Out)** through SGLang's `/v1/chat/completions` for exact token-level training signals. - -**Agent orchestration and grading** are handled by [Harbor](https://github.com/harbor-framework/harbor). Harbor provides unified rollout + grading in a single `Trial.run()` call. The server is **task-type agnostic** — all differentiation (environment, grading harness) is encoded in each task's 4 files (instruction.md, Dockerfile, test.sh, task.toml). - -## Architecture - -``` -Docker Network (swe-net) -┌───────────────────────────────────┐ ┌───────────────────────────────────┐ -│ Miles container (GPU) │ │ Harbor container (agent-env) │ -│ │ │ │ -│ Ray job → train.py │ │ server.py (port 11000) │ -│ ├─ MegatronTrainRayActor (×N) │ │ Wraps Harbor Trial API │ -│ ├─ SGLangEngine (×N, 1/GPU) │ │ Task-type agnostic │ -│ └─ RolloutManager │ │ │ -│ │ │ │ -│ agentic_tool_call.generate │ │ │ -│ (miles/rollout/generate_hub/) │ │ │ -│ 1. Create session on Router │ │ │ -│ 2. Call agent_function.run ─────────────►│ 3. Harbor Trial.run(): │ -│ 5. Collect records │ │ a. Start Docker (task image) │ -│ 6. Build training samples │ │ b. Install agent │ -│ 7. Merge agent metadata │ │ c. Run agent loop ────────┐ │ -│ │ │ │ │ -│ Miles Router (port 30000) │ │ │ │ -│ /sessions/{id}/v1/chat/ │ │ │ │ -│ completions │ │ │ │ -│ - Proxies to SGLang engines │◄──────────── LLM via OPENAI_API_BASE │ │ -│ - Records request + response │ │ d. Run verifier (test.sh) │ -│ with TITO data │ │ e. Return TrialResult │ -│ │ │ │ -│ SGLang engines (1 per GPU) │ │ 4. Returns: │ -│ /v1/chat/completions │ │ reward, exit_status, │ -│ - Applies chat template │ │ agent_metrics, eval_report │ -│ - Returns input_token_ids │ │ │ -│ - Returns token_id per logprob │ │ Harbor spawns Docker containers │ -│ │ │ from task images (on swe-net) │ -│ Megatron (training, GRPO) │ │ │ -└───────────────────────────────────┘ └───────────────────────────────────┘ -``` - -## Files - -| File | Description | -| --- | --- | -| `run.sh` | Training launcher — handles Ray lifecycle, model loading, and job submission | -| `server.py` | FastAPI server wrapping Harbor Trial API — deploy in the agent-env container | -| `swe_agent_function.py` | Custom agent function — dispatches to Harbor server, returns env metadata | -| `generate.py` | Reward function, agent metrics aggregation, `RolloutFn` | -| `download_and_process_data.py` | Download from HuggingFace or local JSONL, convert to Miles format | - -## Step-by-Step Setup - -### Prerequisites - -- Docker with GPU support (nvidia-container-toolkit) -- Model weights downloaded (e.g. `zai-org/GLM-4.7-Flash`) -- `transformers>=5` (`pip install "transformers>=5"` — GLM-4.7-Flash's `glm4_moe_lite` model type is not in transformers 4.x) -- Harbor task directories prepared under a shared path - -### Step 1: Create Docker network - -All containers must be on the same network so Harbor-spawned agent containers can reach the Miles Router. - -```bash -docker network create swe-net -``` - -### Step 2: Start the Miles container (GPU, training + inference) - -```bash -docker run -itd \ - --shm-size 32g \ - --gpus all \ - -v /data/cache/huggingface:/root/.cache/huggingface \ - -v /data:/data \ - --ipc=host \ - --ulimit nofile=65536:65536 \ - --ulimit memlock=-1 \ - --ulimit stack=67108864 \ - --privileged \ - --network swe-net \ - --name miles \ - radixark/miles:latest \ - /bin/bash -``` - -### Step 3: Start the agent-env container (Harbor server + Docker-in-Docker) - -This container runs the Harbor server and spawns agent Docker containers via the host's Docker socket. - -```bash -docker run -itd \ - --name agent_env \ - --shm-size 16g \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v /data:/data \ - --ipc=host \ - --ulimit nofile=65536:65536 \ - --ulimit memlock=-1 \ - --ulimit stack=67108864 \ - --network swe-net \ - python:3.12-slim \ - /bin/bash -``` - -Inside agent_env, install Harbor and set up the server: - -```bash -pip install harbor -# Copy server.py into the container, or mount the miles repo -``` - -### Step 4: Prepare data and Harbor task directories - -Harbor task directories are prepared on the agent server side using **harbor adapters**. Each adapter converts a specific dataset into Harbor's 4-file task format. For example, to prepare SWE-bench tasks: - -```bash -# On the agent server (CPU machine), inside the harbor repo: -cd $CWD/harbor/adapters/swebench && uv sync - -# Generate Harbor task directories for all SWE-bench Verified instances -uv run run_adapter.py --task-dir $HARBOR_TASKS_DIR --all -``` - -This uses the `swebench` Python package to produce correct Docker image names and Dockerfiles for each instance. Other adapters (e.g. `adapters/swe-gym`) follow the same pattern. - -To prepare training data on the Miles side: - -```bash -# Inside miles container: - -# Download and convert to Miles format -python download_and_process_data.py --input SWE-Gym/SWE-Gym --output swe.jsonl -python download_and_process_data.py --input /data/tb.jsonl --output tb.jsonl \ - --agent-name terminus-2 --prompt-key instruction - -# Merge into one mixed JSONL -cat swe.jsonl tb.jsonl > mixed.jsonl -``` - -Each Harbor task directory contains 4 files: -- `instruction.md` — agent prompt -- `task.toml` — config (timeout, resources) -- `environment/Dockerfile` (or `docker-compose.yaml`) — container image -- `tests/test.sh` — grading logic - -### Step 5: Start the Harbor server (in agent_env) - -```bash -docker exec -d agent_env bash -c \ - 'OPENAI_API_KEY=dummy python server.py --port 11000 --max-concurrent 8 \ - > /tmp/server.log 2>&1' -``` - -Verify it's running: - -```bash -docker exec agent_env curl -s http://localhost:11000/health -# {"status":"ok"} -``` - -### Step 6: Convert model weights to Megatron format - -The reference checkpoint must be in Megatron distributed format for training: - -```bash -# Inside miles container — one-time conversion -cd /root/miles -source scripts/models/glm4.7-flash.sh -PYTHONPATH=/root/Megatron-LM python tools/convert_hf_to_torch_dist.py \ - ${MODEL_ARGS[@]} \ - --hf-checkpoint /root/GLM-4.7-Flash \ - --save /root/GLM-4.7-Flash_torch_dist -``` - -### Step 7: Launch training - -```bash -# Inside miles container: -cd /root/miles/examples/experimental/swe-agent-v2 - -# Debug mode (small batch, quick pipeline verification) -bash run.sh --mode debug --num-gpus 8 --ep 8 \ - --prompt-data /root/mixed.jsonl \ - --sglang-tool-call-parser glm47 \ - --sglang-reasoning-parser glm45 \ - --no-wait - -# Full training -bash run.sh --num-gpus 8 --ep 8 \ - --prompt-data /root/mixed.jsonl \ - --sglang-tool-call-parser glm47 \ - --sglang-reasoning-parser glm45 \ - --no-wait -``` - -What `run.sh` does: -1. Kills stale sglang/ray processes -2. Starts a Ray head node with `--num-gpus` GPUs -3. Sources the model architecture script (e.g. `glm4.7-flash.sh`) -4. Submits a Ray job running `train.py` with all configured arguments - -Monitor the job: - -```bash -ray job logs --follow -``` - -### Step 8 (Optional): Start the trace-viewer - -[radixark/trace-viewer](https://github.com/radixark/trace-viewer) visualizes agent trajectories. Run it inside agent_env where Harbor saves trial artifacts: - -```bash -docker exec -d agent_env bash -c \ - 'python3 /root/trace-viewer/server.py --dir /root/trials \ - --host 0.0.0.0 --port 8081 > /tmp/trace_viewer.log 2>&1' -``` - -If the container isn't directly reachable, set up a port forwarder on the host: - -```bash -# On the Docker host — forward host:8081 to agent_env:8081 -# (replace 172.18.0.4 with agent_env's IP from: docker inspect agent_env -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}') -socat TCP-LISTEN:8081,fork,reuseaddr TCP:172.18.0.4:8081 -``` - -Then open `http://:8081` in a browser. - -## Key Configuration - -### run.sh defaults vs overrides - -| Parameter | Default | Debug mode override | -| --- | --- | --- | -| `--num-rollout` | 3000 | 50 | -| `--rollout-batch-size` | 8 | 4 | -| `--n-samples-per-prompt` | 8 | 4 | -| `--rollout-max-response-len` | 8192 | 4096 | -| `--global-batch-size` | 64 | 16 | -| `--max-tokens-per-gpu` | 2048 | 1024 | - -### Environment variables - -| Variable | Default | Description | -| --- | --- | --- | -| `AGENT_SERVER_URL` | `http://agent_env:11000` | Harbor server URL | -| `AGENT_MODEL_NAME` | `model` | Model name passed to agents | -| `AGENT_MAX_CONCURRENT` | `8` | Max concurrent Harbor trials | -| `HARBOR_TASKS_DIR` | `/root/harbor_tasks` | Root directory containing task subdirectories | -| `MILES_ROUTER_EXTERNAL_HOST` | `$(hostname)` | Hostname for agent containers to reach Miles Router | -| `MILES_HOST_IP` | `$(hostname)` | IP/hostname for inter-container communication | - -### Model-specific arguments - -These are passed as CLI args to `run.sh` (not defaults, since they vary per model): - -| Model | Tool call parser | Reasoning parser | -| --- | --- | --- | -| GLM-4.7-Flash | `--sglang-tool-call-parser glm47` | `--sglang-reasoning-parser glm45` | -| Qwen3 | `--sglang-tool-call-parser qwen25` | (none) | - -## How It Works - -1. **Session creation**: `agentic_tool_call.generate` creates a session on Miles Router -2. **Dispatch to agent**: calls `swe_agent_function.run()` which POSTs to the Harbor server -3. **Harbor Trial**: `server.py` creates a `TrialConfig` and runs `Trial.run()`: - - Starts a Docker container from the task's Dockerfile - - Installs and runs the agent (determined by `agent_name` in metadata) - - Agent calls back to Miles Router at `OPENAI_API_BASE` for model inference - - Runs the verifier (`test.sh`) and returns `TrialResult` with reward -4. **TITO recording**: Miles Router proxies each `/v1/chat/completions` to SGLang and records exact token IDs and logprobs -5. **Sample building**: Records are converted to training `Sample`s with token IDs, logprobs, loss masks -6. **Training**: GRPO policy update using Megatron, then weights synced back to SGLang engines - -### Multi-turn merge (TITO mode) - -When TITO is working, `merge_samples()` concatenates multi-turn conversations: - -``` -Turn 1: [prompt_tokens] [response_tokens_1] -> Sample 0 -Turn 2: [prompt_tokens] [response_tokens_1] [observation_tokens] [resp_tokens_2] -> Sample 1 - -merge_samples() -> - tokens: [prompt] [resp1] [obs] [resp2] - loss_mask: -------- [1]*r1 [0]*o [1]*r2 - logprobs: -------- [real] [0.0] [real] -``` - -Without TITO, use `--generate-multi-samples` to skip merge and train on per-turn samples instead (current default in `run.sh`). - -## Troubleshooting - -### Harbor containers can't reach Miles Router - -Agent containers need to resolve the Miles container's hostname. Ensure: -- All containers are on the same Docker network (`swe-net`) -- `MILES_ROUTER_EXTERNAL_HOST` is set to the Miles container name (e.g. `miles-maocheng`) -- Task directories were created with `--docker-network swe-net` - -### `TaskNotFound` error - -The task directory for the given `instance_id` doesn't exist under `HARBOR_TASKS_DIR`. Run the appropriate harbor adapter first (e.g. `adapters/swebench/run_adapter.py` for SWE-bench tasks). - -### SGLang engines OOM (`Not enough memory`) - -- Ensure all GPUs are clean before starting (`nvidia-smi` shows 0 MiB) -- Kill stale processes: `pkill -9 sglang; ray stop --force; pkill -9 ray` -- `run.sh` does this automatically, but leftover processes from crashed jobs may persist - -### `b.tokens must start with a.tokens` assertion error - -Multi-turn merge fails due to BPE re-tokenization inconsistency. Use `--generate-multi-samples` (already default in `run.sh`) to skip merge and train on per-turn samples. - -### Trace-viewer shows no trajectories - -- Trial artifacts are saved inside the **agent_env** container (not miles), at the path Harbor uses (default: `./trials/` relative to where `server.py` runs) -- Point the trace-viewer at the correct directory inside agent_env -- Restart the trace-viewer after clearing old data (it caches in memory) diff --git a/examples/experimental/swe-agent-v2/train-tb2-via-harbor-private-server/README.md b/examples/experimental/swe-agent-v2/train-tb2-via-harbor-private-server/README.md deleted file mode 100644 index 6993836bab..0000000000 --- a/examples/experimental/swe-agent-v2/train-tb2-via-harbor-private-server/README.md +++ /dev/null @@ -1,149 +0,0 @@ -# Train GLM-4.7-Flash on TB2 against a `harbor-private` agent server (2-node example) - -End-to-end recipe for a small (2-node) GLM-4.7-Flash agentic-async training -run that uses the `miles_agent_server.py` shipped on the [`harbor-private` -branch `shi/rebase-on-upstream-v0.7.0`][harbor-private-branch] as its -rollout backend. - -This is the **training-side** companion to -[`examples/eval/terminal_bench_via_agent_server/`](../../../eval/terminal_bench_via_agent_server/), -which is the eval-side client for the same agent server. Both target the -same `POST /run` endpoint; this one drives it from -`run-glm47-flash-agentic-async.py` instead of a one-shot Python client. - -## What gets exercised - -``` -trainer-side (rcli pod, this node) agent-server (external host) -+--------------------+ +-----------------------------+ -| Megatron actor | weights -> sglang ---->| | -| RolloutManager | | miles_agent_server.py | -| ^ | POST /run ---->| (harbor-private branch) | -| | GRPO updates | | spawns Docker -> agent | -+-----+--------------+ | -> verifier -> reward | - +-----------------------------+ -``` - -The training loop dispatches one `/run` per (prompt, sample) and receives a -verifier-scored reward back. The agent-server side is what we're really -validating: it must keep up with the trainer's batch cadence without -crashing under sustained load. - -## Prerequisites - -### 1. A running `miles_agent_server` from harbor-private - -On a Tailscale-reachable host (we use `aws-agent-server` internally), check -out the [`harbor-private` branch][harbor-private-branch] and run the agent -server as documented in -[its README](https://github.com/radixark/harbor-private/blob/shi/rebase-on-upstream-v0.7.0/README.md). -The default port is 8080; the dashboard at 8081. - -For training-style workloads `OPENAI_API_KEY=dummy` is correct (LLM -credentials flow per request, not from server env). - -### 2. The 23-task TB2 variance jsonl on shared GPFS - -Each line is `{"prompt": , "metadata": {"instance_id": -}}`. The 23 task names are the TB2 instances where vanilla -GLM-4.7-Flash passes 1-3 of 4 trials (the "improvable" middle band). Place -the file on the cluster's shared volume so it survives pod recreate: - -```bash -# inside the head pod -/cluster_personal/job_workspaces//tb2_train.jsonl -``` - -### 3. A current miles checkout - -Run from a current `miles` checkout — `origin/main` is fine. Older topic -branches may carry stale launcher flags or out-of-date torch / sglang -glue code; this example was validated against main. - -```bash -cd /workspace/miles -git checkout origin/main -``` - -## Launch - -The launcher hard-codes `--prompt-data /root/swe_train.jsonl`; symlink it -to the TB2 jsonl so the same launcher works without editing. - -```bash -# inside the head pod -ln -sf /cluster_personal/job_workspaces//tb2_train.jsonl /root/swe_train.jsonl - -cd /workspace/miles -bash examples/experimental/swe-agent-v2/train-tb2-via-harbor-private-server/launch.sh -``` - -Pass any short tag (`pr-smoke`, `260527-2n-v1`, ...) — it threads through -`--save-dir`, `--save-traces-dir`, and `--wandb-run-name` so multiple -attempts don't collide. - -The full launcher invocation (from `launch.sh`, with optional env -overrides expanded): - -```bash -python examples/experimental/swe-agent-v2/run-glm47-flash-agentic-async.py \ - --num-nodes 2 --train-num-nodes 1 --skip-prepare \ - --max-seq-len 65536 \ - --save-dir "${OUTPUT_ROOT}/GLM-4.7-Flash_2node_tb2_/" \ - --save-traces-dir "${OUTPUT_ROOT}/flash-2node-traces-/traces" \ - --rollout-batch-size 4 --n-samples-per-prompt 8 --global-batch-size 32 \ - --save-interval 5 \ - --agent-server-url "$AGENT_SERVER_URL" \ - --wandb-project glm47-flash-agentic-async \ - --wandb-run-name - # optional, only added when the corresponding env var is set: - # --router-external-host "$ROUTER_EXTERNAL_HOST" - # --wandb-team "$WANDB_TEAM" -``` - -`AGENT_SERVER_URL` defaults to `http://agent-server:8080`; set it to wherever -your `miles_agent_server` is reachable from the trainer pod. -`OUTPUT_ROOT` defaults to `/workspace` (writable storage on the trainer -pod that the rollout / save / traces dirs are created under). - -If the agent server cannot reach the trainer at its default service -name (e.g. you're running the agent server on a different host/network), -set `ROUTER_EXTERNAL_HOST` to a hostname the agent server can dial back -to the trainer's session-server through. Otherwise leave it unset. - -## Sanity checks (in order) - -1. `Job 'raysubmit_' submitted successfully` in the launch log within - ~30s. -2. `ray job status --address http://localhost:8265` reports - `RUNNING` within ~2 min. -3. `wandb: 🚀 View run at https://wandb.ai/.../runs/` appears in - the log within ~3 min. **Record the ``**; the S3 ckpt prefix - is `-` once `--save-interval` kicks in. -4. The agent-server dashboard at `:8081` starts seeing `mini-swe-agent` - trials with the 23 task names from the TB2 variance band. If you see - different task names, the dataset symlink didn't take. -5. First `rollout 0:` and `step 0:` log lines typically appear 30-60 min - after launch (long because of sglang weight broadcast on cold pods); - subsequent iters at 10-20 min/step. - -If `/tmp/` stops growing but `ray job status` says `RUNNING`, -the shell's `tee` pipe died (e.g. ssh session ended) but training is -fine — use `ray job logs ` to read live progress instead. - -## Tuning knobs - -| Knob | Default here | Rationale | -|---|---|---| -| `--num-nodes 2 --train-num-nodes 1` | 1 trainer + 1 rollout | Minimum useful split; 1-node would co-locate and serialise the loop. | -| `--rollout-batch-size 4 --n-samples-per-prompt 8` | 32 trials/iter | Same shape as the 5-node baseline so iter cadence is comparable. | -| `--max-seq-len 65536` | 65536 | The agent server's `poll_steps` wrapper enforces this; raising it costs more truncation, lowering trips it more often. | -| `--sglang-mem-fraction-static 0.72` | 0.72 | Empirically avoids sglang OOM after weight-transfer broadcast. | -| `--save-interval 5` | every 5 iters | Checkpoints land under `--save-dir`; sync to S3 separately per your team's playbook. | - -## Related - -- [`examples/eval/terminal_bench_via_agent_server/`](../../../eval/terminal_bench_via_agent_server/) — same agent server, used from the **eval** side. -- [`harbor-private` branch `shi/rebase-on-upstream-v0.7.0`][harbor-private-branch] — the agent server code this example talks to. - -[harbor-private-branch]: https://github.com/radixark/harbor-private/tree/shi/rebase-on-upstream-v0.7.0 diff --git a/examples/experimental/swe-agent-v2/train-tb2-via-harbor-private-server/launch.sh b/examples/experimental/swe-agent-v2/train-tb2-via-harbor-private-server/launch.sh deleted file mode 100755 index 93aa344e7d..0000000000 --- a/examples/experimental/swe-agent-v2/train-tb2-via-harbor-private-server/launch.sh +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env bash -# Launch a 2-node GLM-4.7-Flash agentic-async training run that uses the -# miles_agent_server from the harbor-private branch -# shi/rebase-on-upstream-v0.7.0 as the rollout backend. -# -# See README.md for prerequisites (running agent server, populated -# /root/swe_train.jsonl, a current miles checkout). -# -# Usage: -# bash launch.sh # e.g. bash launch.sh pr-smoke -# -# is threaded through --save-dir, --save-traces-dir, and -# --wandb-run-name so multiple attempts don't collide. -# -# Optional env overrides (sensible defaults shown): -# AGENT_SERVER_URL=http://agent-server:8080 -# Base URL of the running miles_agent_server. -# ROUTER_EXTERNAL_HOST= -# Hostname/FQDN that the trainer's session-server advertises back -# to the agent server, so the agent server can call into the -# trainer for tokenization. Required when the agent server cannot -# reach the trainer at the trainer's default service name (e.g. an -# off-cluster agent server with an explicit ingress/proxy hostname). -# Leave unset to omit --router-external-host. -# WANDB_TEAM= -# Wandb team to log to. Leave unset to omit --wandb-team. -# OUTPUT_ROOT=/workspace -# Filesystem root under which save-dir and save-traces-dir are -# created. Must point at writable storage shared between the -# training nodes; the cluster's persistent volume is usually right. - -set -euo pipefail - -if [ "$#" -lt 1 ]; then - echo "usage: $0 " >&2 - exit 64 -fi -RUN_TAG="$1" - -LAUNCHER="examples/experimental/swe-agent-v2/run-glm47-flash-agentic-async.py" -if [ ! -f "$LAUNCHER" ]; then - echo "error: $LAUNCHER not found. Run this script from the miles repo root." >&2 - exit 66 -fi - -if [ ! -e /root/swe_train.jsonl ]; then - echo "error: /root/swe_train.jsonl missing. See README.md step 'Launch'." >&2 - exit 65 -fi - -AGENT_SERVER_URL="${AGENT_SERVER_URL:-http://agent-server:8080}" -ROUTER_EXTERNAL_HOST="${ROUTER_EXTERNAL_HOST:-}" -WANDB_TEAM="${WANDB_TEAM:-}" -OUTPUT_ROOT="${OUTPUT_ROOT:-/workspace}" - -ARGS=( - --num-nodes 2 --train-num-nodes 1 --skip-prepare - --max-seq-len 65536 - --save-dir "${OUTPUT_ROOT}/GLM-4.7-Flash_2node_tb2_${RUN_TAG}/" - --save-traces-dir "${OUTPUT_ROOT}/flash-2node-traces-${RUN_TAG}/traces" - --rollout-batch-size 4 --n-samples-per-prompt 8 --global-batch-size 32 - --save-interval 5 - --agent-server-url "$AGENT_SERVER_URL" - --wandb-project glm47-flash-agentic-async - --wandb-run-name "$RUN_TAG" -) -[ -n "$ROUTER_EXTERNAL_HOST" ] && ARGS+=(--router-external-host "$ROUTER_EXTERNAL_HOST") -[ -n "$WANDB_TEAM" ] && ARGS+=(--wandb-team "$WANDB_TEAM") - -python "$LAUNCHER" "${ARGS[@]}" \ - 2>&1 | tee "/tmp/flash-2node-launch-${RUN_TAG}.log" diff --git a/examples/swe-agent-v2/README.md b/examples/swe-agent-v2/README.md new file mode 100644 index 0000000000..f35b67f921 --- /dev/null +++ b/examples/swe-agent-v2/README.md @@ -0,0 +1,108 @@ +# SWE-Agent V2 training with Harbor + +This example trains GLM-4.7-Flash on agentic coding and terminal tasks. Miles +runs synchronous GRPO and serves the policy through its session server; a +separate [Harbor](https://github.com/harbor-framework/harbor) agent server +creates the task sandboxes, runs the agents, and returns verifier rewards. + +The same pipeline supports Terminal-Bench, SWE-bench, and custom Harbor tasks. +Training records must contain a `prompt` and `metadata.instance_id` identifying +the Harbor task. + +## Files + +| File | Purpose | +| --- | --- | +| `run.py` | Validated synchronous GLM-4.7-Flash launcher. | +| `run-glm47-flash-agentic-async.py` | Disaggregated fully asynchronous launcher. | +| `swe_agent_function.py` | Sends each rollout to the Harbor agent server. | +| `generate.py` | Builds rewards, metrics, and training samples. | +| `download_and_process_data.py` | Converts supported datasets to Miles JSONL. | + +## 1. Start the Harbor agent server + +Use the public Harbor repository and its Miles integration branch, not +`harbor-private`: + +```bash +git clone https://github.com/harbor-framework/harbor.git +cd harbor +git checkout harbor-miles-v0.13.1 +uv sync + +uv run python miles_agent_server.py \ + --host 0.0.0.0 \ + --port 30000 \ + --dashboard-port 0 \ + --max-concurrent 8 \ + --agent-timeout 5400 \ + --trials-dir /path/to/trials +``` + +The agent-server machine must have Docker and enough capacity for the requested +number of concurrent sandboxes. Verify `http://:30000/health` +before launching Miles. + +## 2. Prepare Terminal-Bench data + +Convert a local JSONL whose rows include a task instruction and instance name: + +```bash +python examples/swe-agent-v2/download_and_process_data.py \ + --input /path/to/terminal-bench.jsonl \ + --output /path/to/tb2_train.jsonl \ + --agent-name mini-swe-agent \ + --prompt-key instruction +``` + +The resulting `metadata.instance_id` values must match task directories known to +the Harbor agent server. + +## 3. Launch synchronous GLM-4.7-Flash training + +The following one-node shape was validated on 8 H200 GPUs with eight Terminal- +Bench trajectories (two prompts times four samples), followed by a Megatron +training step: + +```bash +python examples/swe-agent-v2/run.py \ + --num-nodes 1 \ + --num-gpus-per-node 8 \ + --skip-prepare \ + --megatron-path /root/Megatron-LM \ + --hf-checkpoint /path/to/GLM-4.7-Flash \ + --ref-load /path/to/GLM-4.7-Flash_torch_dist \ + --save-dir /path/to/checkpoints \ + --prompt-data /path/to/tb2_train.jsonl \ + --max-seq-len 65536 \ + --rollout-batch-size 2 \ + --n-samples-per-prompt 4 \ + --global-batch-size 8 \ + --agent-server-url http://:30000 \ + --router-external-host \ + --miles-host-ip 0.0.0.0 \ + --save-traces-dir /path/to/traces +``` + +For a smoke test, add `--num-rollout 1`. For a long run, leave the default or +set the desired rollout count explicitly. + +`--router-external-host` is the address Harbor sandboxes use to call the Miles +session server and SGLang router. It must resolve and route from the agent-server +machine. `--miles-host-ip 0.0.0.0` is useful when those services must accept +connections forwarded from another host. Ensure ports 30000 and 31000 are +reachable end to end; Tailscale is one option when the machines are on different +networks. + +## 4. Verify progress + +Check all three layers: + +1. Harbor trial logs show increasing `mini-swe-agent (step N)` values. +2. Miles logs emit rollout metrics and write `rollout_data/*.pt` under the trace + directory. +3. Megatron logs emit `train/step` and the Ray job exits successfully. + +The synchronous launcher uses GLM-4.7 tool-call and reasoning parsers, TITO, +the Miles session server, and the Megatron backend. The asynchronous launcher is +available for multi-node disaggregated runs but is not the one-node recipe above. diff --git a/examples/experimental/swe-agent-v2/download_and_process_data.py b/examples/swe-agent-v2/download_and_process_data.py similarity index 97% rename from examples/experimental/swe-agent-v2/download_and_process_data.py rename to examples/swe-agent-v2/download_and_process_data.py index 4a5103f0de..a807e72da4 100644 --- a/examples/experimental/swe-agent-v2/download_and_process_data.py +++ b/examples/swe-agent-v2/download_and_process_data.py @@ -1,9 +1,9 @@ #!/usr/bin/env python3 -"""Download and convert datasets to Miles format for agent training. +"""Download and convert datasets to Miles format for SWE-agent training. Supports any task type — SWE-bench, Terminal-Bench, custom datasets, etc. Each record's metadata is enriched with ``agent_name`` so that -downstream components (server.py) can route to the correct Harbor agent. +the Harbor agent server can route to the correct agent. All original fields are preserved in metadata so that prepare_harbor_tasks.py can infer the right task directory layout. diff --git a/examples/experimental/swe-agent-v2/generate.py b/examples/swe-agent-v2/generate.py similarity index 98% rename from examples/experimental/swe-agent-v2/generate.py rename to examples/swe-agent-v2/generate.py index 878fba3282..3f48c65032 100644 --- a/examples/experimental/swe-agent-v2/generate.py +++ b/examples/swe-agent-v2/generate.py @@ -1,5 +1,5 @@ """ -Agent V2: reward, metrics, and rollout class. +SWE-Agent V2: reward, metrics, and rollout class. The generate function is provided by: miles.rollout.generate_hub.agentic_tool_call.generate diff --git a/examples/experimental/swe-agent-v2/run-glm47-flash-agentic-async.py b/examples/swe-agent-v2/run-glm47-flash-agentic-async.py similarity index 99% rename from examples/experimental/swe-agent-v2/run-glm47-flash-agentic-async.py rename to examples/swe-agent-v2/run-glm47-flash-agentic-async.py index 44bb7cbbbc..a43e53269d 100644 --- a/examples/experimental/swe-agent-v2/run-glm47-flash-agentic-async.py +++ b/examples/swe-agent-v2/run-glm47-flash-agentic-async.py @@ -35,7 +35,7 @@ import miles.utils.external_utils.command_utils as U SCRIPT_DIR = Path(__file__).resolve().parent -FULLY_ASYNC_DIR = (Path(__file__).resolve().parent.parent.parent / "fully_async").resolve() +FULLY_ASYNC_DIR = (Path(__file__).resolve().parent.parent / "fully_async").resolve() # Cluster-wide GPU-node ceiling for the ckpt-conversion job. Kept below the # raw node count so ckpt conversion doesn't starve the rest of the cluster. diff --git a/examples/experimental/swe-agent-v2/run.py b/examples/swe-agent-v2/run.py similarity index 98% rename from examples/experimental/swe-agent-v2/run.py rename to examples/swe-agent-v2/run.py index 0fbd442096..0af0a6f7ad 100644 --- a/examples/experimental/swe-agent-v2/run.py +++ b/examples/swe-agent-v2/run.py @@ -1,8 +1,8 @@ -"""Agent V2 launcher (GLM-4.7-Flash): Miles <-> Harbor agent orchestration. +"""SWE-Agent V2 launcher (GLM-4.7-Flash): Miles <-> Harbor orchestration. Supports any task type (SWE-bench, Terminal-Bench, custom) via Harbor. -Python equivalent of run.sh. Usage: +Usage: python run.py python run.py --mode normal python run.py --base-dir /my/models --prompt-data /my/data.jsonl diff --git a/examples/experimental/swe-agent-v2/swe_agent_function.py b/examples/swe-agent-v2/swe_agent_function.py similarity index 98% rename from examples/experimental/swe-agent-v2/swe_agent_function.py rename to examples/swe-agent-v2/swe_agent_function.py index b347240b08..252bd80035 100644 --- a/examples/experimental/swe-agent-v2/swe_agent_function.py +++ b/examples/swe-agent-v2/swe_agent_function.py @@ -1,5 +1,5 @@ """ -Custom agent function for agentic_tool_call.generate. +Custom agent function for ``agentic_tool_call.generate``. Dispatches to a Harbor-based agent server and returns env metadata as a plain dict. The generate layer merges this into sample.metadata so From d0d1d17e1af18ebb16cfa4d70c55856e6fc80588 Mon Sep 17 00:00:00 2001 From: Shi Dong Date: Tue, 21 Jul 2026 05:35:11 -0700 Subject: [PATCH 5/6] Document Harbor task directory --- examples/swe-agent-v2/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/swe-agent-v2/README.md b/examples/swe-agent-v2/README.md index f35b67f921..59895957ef 100644 --- a/examples/swe-agent-v2/README.md +++ b/examples/swe-agent-v2/README.md @@ -30,7 +30,7 @@ cd harbor git checkout harbor-miles-v0.13.1 uv sync -uv run python miles_agent_server.py \ +HARBOR_TASKS_DIR=/path/to/harbor_tasks uv run python miles_agent_server.py \ --host 0.0.0.0 \ --port 30000 \ --dashboard-port 0 \ @@ -39,9 +39,10 @@ uv run python miles_agent_server.py \ --trials-dir /path/to/trials ``` -The agent-server machine must have Docker and enough capacity for the requested -number of concurrent sandboxes. Verify `http://:30000/health` -before launching Miles. +`HARBOR_TASKS_DIR` must contain one Harbor task directory for every +`metadata.instance_id` in the training data. The agent-server machine must have +Docker and enough capacity for the requested number of concurrent sandboxes. +Verify `http://:30000/health` before launching Miles. ## 2. Prepare Terminal-Bench data From e58fa33fe9d8398477ad494363ab90d240c2a740 Mon Sep 17 00:00:00 2001 From: Shi Dong Date: Tue, 21 Jul 2026 20:10:01 -0700 Subject: [PATCH 6/6] Keep long SWE agent requests alive --- examples/swe-agent-v2/README.md | 6 ++++ examples/swe-agent-v2/swe_agent_function.py | 33 ++++++++++++++++++++- 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/examples/swe-agent-v2/README.md b/examples/swe-agent-v2/README.md index 59895957ef..79a88d6ab8 100644 --- a/examples/swe-agent-v2/README.md +++ b/examples/swe-agent-v2/README.md @@ -44,6 +44,12 @@ HARBOR_TASKS_DIR=/path/to/harbor_tasks uv run python miles_agent_server.py \ Docker and enough capacity for the requested number of concurrent sandboxes. Verify `http://:30000/health` before launching Miles. +When the trainer reaches the machine through the Kubernetes Tailscale egress +service, use its stable service name, for example +`http://egress-agent-server.tailscale.svc.cluster.local:8080`. The rollout +client enables TCP keepalive probes so long-running trials do not lose an idle +connection while Harbor is working. + ## 2. Prepare Terminal-Bench data Convert a local JSONL whose rows include a task instruction and instance name: diff --git a/examples/swe-agent-v2/swe_agent_function.py b/examples/swe-agent-v2/swe_agent_function.py index 252bd80035..6ec2252367 100644 --- a/examples/swe-agent-v2/swe_agent_function.py +++ b/examples/swe-agent-v2/swe_agent_function.py @@ -13,13 +13,44 @@ import asyncio import logging import os +import socket from typing import Any from urllib.parse import urlparse, urlsplit, urlunparse +import httpx + from miles.utils.http_utils import post logger = logging.getLogger(__name__) +_agent_server_client: httpx.AsyncClient | None = None + + +def _get_agent_server_client() -> httpx.AsyncClient: + """Return a client whose long-running requests survive idle network paths.""" + global _agent_server_client + if _agent_server_client is None: + socket_options = [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + (socket.IPPROTO_TCP, getattr(socket, "TCP_KEEPIDLE", 4), 60), + (socket.IPPROTO_TCP, getattr(socket, "TCP_KEEPINTVL", 5), 30), + (socket.IPPROTO_TCP, getattr(socket, "TCP_KEEPCNT", 6), 5), + ] + transport = httpx.AsyncHTTPTransport(socket_options=socket_options) + _agent_server_client = httpx.AsyncClient( + transport=transport, + limits=httpx.Limits(max_connections=64, max_keepalive_connections=32), + timeout=None, + ) + return _agent_server_client + + +async def _post_agent_server(url: str, payload: dict[str, Any]) -> dict[str, Any]: + client = _get_agent_server_client() + response = await client.post(url, json=payload) + response.raise_for_status() + return response.json() + async def run( base_url: str, @@ -73,7 +104,7 @@ async def run( try: response = await asyncio.wait_for( - post(f"{agent_server_url}/run", request), + _post_agent_server(f"{agent_server_url}/run", request), timeout=3600, # 1 hour max per trial ) except asyncio.TimeoutError: