[![INFORMS Journal on Computing Logo](https://INFORMSJoC.github.io/logos/INFORMS_Journal_on_Computing_Header.jpg)](https://pubsonline.informs.org/journal/ijoc)


# Learning Virtual Machine Scheduling in Cloud Computing through Language Agents

This repository is organized as a research artifact for two-stage heuristic search on the VM scheduling task. It is intended to support the computational results described in the associated paper submission to [INFORMS Journal on Computing](https://pubsonline.informs.org/journal/ijoc).

## Cite

To cite the contents of this repository, please cite both the paper and this repo, using their respective DOIs.

https://doi.org/10.1287/ijoc.2025.1368

https://doi.org/10.1287/ijoc.2025.1368.cd

Below is the BibTex for citing this snapshot of the repository.

```bibtex
@misc{wu2026learning,
  author = {Wu, Jiehao and Wang, Ziwei and Sheng, Junjie and Li, Wenhao and Wang, Xiangfeng and Luo, Jun},
  publisher = {INFORMS Journal on Computing},
  title = {{Learning Virtual Machine Scheduling in Cloud Computing through Language Agents}},
  year = {2026},
  doi = {10.1287/ijoc.2025.1368.cd},
  url = {https://github.com/INFORMSJoC/2025.1368},
  note = {Available for download at https://github.com/INFORMSJoC/2025.1368},
}
```

## License Scope

Unless stated otherwise, the source code in this repository is released under the MIT License in [`LICENSE`](LICENSE).

This repository also references or redistributes third-party datasets and embedded third-party components that are **not** relicensed under MIT:

- `data/azure/*`: Azure Public Dataset material with attribution requirements; see [`data/README.md`](data/README.md) and `data/azure/LICENSE`
- `data/huawei/dataset_clean.csv`: upstream Huawei / VMAgent dataset material with non-commercial and attribution restrictions; see [`data/README.md`](data/README.md) and `data/huawei/LICENSE`
- `src/optionMiner/vmagent/data/dataset_clean.csv` and `src/optionComposer/vmagent/data/dataset_clean.csv`: copies of the same Huawei / VMAgent dataset material, therefore subject to the same upstream restrictions
- `src/**/implementation/vm/VMAgent_plus/*`: embedded third-party VM environment code with its own MIT license notice

If you reuse or redistribute this repository, you must comply with the license terms attached to each non-code or third-party component in addition to the repository's MIT-licensed code.

## Contribution

This repository contains the implementation of a two-stage heuristic search pipeline for VM scheduling:

- `optionMiner` searches for high-quality priority heuristics.
- `optionComposer` imports the functions generated by `optionMiner`, cross-evaluates them on real validation requests, prunes them, and then searches for a heuristic selector that switches among the retained functions.
- The repository also includes the VM scheduling environment, evaluation scripts, dataset layout, and reproduction scripts needed to rerun the main experiments.

## Recommended Entrypoints

Start from `scripts/` rather than running files under `src/` directly.

- Main experiment entrypoint: `bash scripts/run_ijoc.sh`
- Deterministic summary entrypoint: `bash scripts/run_ijoc_det.sh`
- Minimal Stage 1 -> Stage 2 smoke test: `bash scripts/task1_e2e_minimal.sh`

Recommended usage:

```bash
bash scripts/run_ijoc.sh
```

If you only want the deterministic tables / lightweight reproduction path, run:

```bash
bash scripts/run_ijoc_det.sh
```

Additional script-level details are documented in `scripts/README.md`.

## Large External Data

The Azure trace file `data/azure/vmtable.csv` is intentionally treated as a local download, not a repository asset. It is ignored by Git and should be fetched from the upstream Azure Public Dataset when needed. See [`data/README.md`](data/README.md) for provenance and download guidance.

## Repository Structure

- `src/`
  - `src/optionMiner/`: stage-1 heuristic generation
  - `src/optionComposer/`: stage-2 heuristic selector search using functions imported from stage 1
  - `src/**/implementation/vm/VMAgent_plus/`: VM scheduling evaluator + runtime environment code used by `test_baselines.py`
- `data/`
  - shared datasets and score tables extracted from the source tree (see `data/README.md` for data provenance/attribution)
  - note: Azure traces can be large and are typically downloaded locally (not committed); see `data/README.md`
- `results/`
  - `results/figures/`: figures shipped with this artifact snapshot
  - `results/runtime/`: shipped runtime artifacts needed to reproduce the paper's main conclusions
- `scripts/`
  - experiment entry points and postprocess/figure scripts (see `scripts/README.md`)
  - `scripts/run_ijoc.sh`: recommended main experiment entrypoint
  - `scripts/run_ijoc_det.sh`: recommended deterministic summary entrypoint
  - `scripts/ijoc_main.py` / `scripts/ijoc_deterministic.py`: Python entrypoints behind the shell wrappers
  - `scripts/figure_scripts/*`: lightweight figure drawing

## Quickstart (minimal end-to-end run)

This section is the shortest path to run `optionMiner -> optionComposer` once.

### 0) Activate conda environments

This repository currently uses **two** conda environments:

- `mico_env`: the main driver environment for running `optionMiner` / `optionComposer` (Python 3.10+ required).
- `sf`: the evaluator environment used to run the real VM-scheduling validator `test_baselines.py`.

Note: older notes may refer to the driver environment as `funsearch`. The current codebase assumes the conda env
name `mico_env` in a few subprocess calls (for example, a legacy helper path in `optionMiner`); if your
environment is named differently, either rename it or update the `conda run -n ...` calls in the code.

To help with reproducibility, this repository includes conda environment exports under `envs/`:

- `envs/mico_env.yml` (driver env)
- `envs/sf.yml` (evaluator env)

To create the environments from these exports:

```bash
conda env create -f envs/mico_env.yml
conda env create -f envs/sf.yml
```

Why split?
The evaluator (`test_baselines.py`) has heavier / more specialized dependencies than the search drivers, and the
current codebase calls it via `conda run -n sf ...` from inside both stages.

Activate the driver env for running Stage 1/2:

```bash
conda activate mico_env
```

Note: the evaluator is invoked via `conda run -n sf ...` automatically, so you typically do *not* need to
activate `sf` manually, but you must ensure it exists and can run:

```bash
conda run -n sf python --version
```

### 1) Set LLM environment variables

The search drivers use an OpenAI-compatible endpoint via environment variables:

```bash
export LLM_API_KEY="your_key"
export LLM_BASE_URL="http://host:port/v1"
# Optional (only used by optionMiner's sample_factor path)
export LLM_FACTOR_MODEL="model"
```

If you do not have an endpoint yet, you can run a smoke test using the built-in stub client:

```bash
export LLM_USE_STUB=1
export LLM_API_KEY=dummy
export LLM_BASE_URL=http://dummy
```

### 2) Run Stage 1 (optionMiner) with a tiny sample budget

```bash
cd src/optionMiner
python main.py \
  --seed bf_cpu \
  --model_set model \
  --is_condition 1 \
  --sample_num 1 \
  --server_num 50 \
  --process_index_list '[0]' \
  --auto_export_function_list 1 \
  --top_option_miner_funcs 1
```

After this, optionMiner writes samples under:

- `${MICO_LOG_ROOT:-./log}/optionMiner/**/samples/*.json`

If `--auto_export_function_list 1` is enabled, optionMiner also exports a function list under:

- `${MICO_LOG_ROOT:-./log}/optionMiner/**/function_list/`
  - `ranked_functions.json`: top-\(k\) ranked functions (before pruning)
  - `pruned_functions.json`: functions retained after pruning

### 3) Run Stage 2 (optionComposer) using Stage 1 outputs

Stage 2 supports **two** ways to import Stage 1 outputs:

- Recommended: pass `--option_miner_function_list_dir` pointing at the exported `function_list/` directory (this is what `scripts/task1_e2e_minimal.sh` uses).
- Alternative: pass `--option_miner_samples_dir` pointing at the Stage 1 `samples/` directory (Stage 2 can derive the function list from samples internally).

For a quick local run, you can locate the newest `samples/` directory and derive `function_list/` like this:

```bash
SAMPLES_DIR="$(
  find "${MICO_LOG_ROOT:-$(pwd -P | sed 's|/src/optionMiner$||')/log}/optionMiner" -type d -name samples -path '*server_50*' -print0 \
    | xargs -0 ls -td \
    | head -n 1
)"
echo "${SAMPLES_DIR}"

FUNCTION_LIST_DIR="$(cd "$(dirname "${SAMPLES_DIR}")" && pwd)/function_list"
echo "${FUNCTION_LIST_DIR}"
```

Then run optionComposer:

```bash
cd ../optionComposer
python main.py \
  --seed bf_cpu \
  --model_set model \
  --is_condition 1 \
  --sample_num 1 \
  --id 1 \
  --is_all_range 0 \
  --process_index_list '[0]' \
  --option_miner_function_list_dir "${FUNCTION_LIST_DIR}" \
  --top_option_miner_funcs 1 \
  --auto_func_set 0 \
  --func_set 3,4,5
```

Stage 2 performs a pruning/cross-evaluation step that runs the real evaluator:

```bash
conda run -n sf python src/optionComposer/implementation/vm/VMAgent_plus/vmagent/test_baselines.py ...
```

### One-command helper

If you prefer a single command that runs the same minimal chain and auto-finds the latest samples directory, use:

```bash
bash scripts/task1_e2e_minimal.sh
```

## How To Use (manual)

Main programs:

- Stage 1: `src/optionMiner/main.py`
- Stage 2: `src/optionComposer/main.py`

Required LLM environment variables:

```bash
export LLM_API_KEY="your_key"
export LLM_BASE_URL="http://host:port/v1"
```
Run stage 1:


```bash
cd src/optionMiner
python main.py \
  --seed bf_cpu \
  --model_set model \
  --is_condition 1 \
  --sample_num 5
```

Run stage 2:

```bash
cd src/optionComposer
python main.py \
  --seed bf_cpu \
  --model_set model \
  --option_miner_function_list_dir "/path/to/optionMiner_function_list" \
  --top_option_miner_funcs 3 \
  --auto_func_set 1
```

Notes:

- Recommended Stage 1 \(\rightarrow\) Stage 2 handoff is via `--option_miner_function_list_dir`, which points to a `function_list/` directory containing:
  - `ranked_functions.json`
  - `pruned_functions.json`
- To export `function_list/` from Stage 1 automatically, run Stage 1 with:
  - `--auto_export_function_list 1`
  - `--top_option_miner_funcs <k>`
  - optional: `--function_list_output_dir <path>` (defaults to `<log_dir>/function_list`)
- Stage 2 also supports `--option_miner_samples_dir` as an alternative input if you want to point directly at a Stage 1 `samples/` directory.

### process_index_list

`process_index_list` controls which request segments are evaluated by the VM scheduling validator.

- It must be passed as a Python-style integer list, for example:

```bash
--process_index_list "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
```

- In `optionMiner`, it determines which request segments are used when evaluating candidate `priority` functions.
- In `optionComposer`, it affects both the validation-based pruning of imported functions from `optionMiner` and the later selector evaluation.
- `--num_process` inside `test_baselines.py` is derived from the length of this list.
- The default setting in the main programs is a contiguous index range:
  - `optionMiner`: `[0, 1, ..., 29]`
  - `optionComposer`: `[0, 1, ..., 29]`

Example:

```bash
cd src/optionMiner
python main.py \
  --seed bf_cpu \
  --model_set deepseek-chat \
  --is_condition 1 \
  --sample_num 1 \
  --process_index_list "[0, 1, 2, 3, 4]"
```

## Artifact Locations

- New runtime outputs (logs/traces from your own runs): `${MICO_LOG_ROOT:-./log}/` (defaults to `./log/`)
- Shipped runtime artifacts (committed for reproducibility): `results/runtime/`
- Shipped figures: `results/figures/`
- Regenerated figures (default output): `scripts/figures/` (or `${MICO_FIGURES_DIR}` if set)

## Results, tables, and figures

- Runtime outputs from **new** runs go under `${MICO_LOG_ROOT:-./log}/` (Stage 1/2 logs, query-response traces, etc.).
- The repository also ships the key runtime artifacts needed by the main paper entry points under `results/runtime/`, including:
  - `results/runtime/optionComposer/composer.py`
  - `results/runtime/optionMiner/heuristics/heuristic_1.py` to `heuristic_4.py`
  - `results/runtime/schedrl/*.csv`
  - `results/runtime/upper_bound/scenario_results.csv`
- Figures shipped with this artifact snapshot live under `results/figures/`.
- Scripts that regenerate figures run in the `sf` conda environment and write to `scripts/figures/` by default (or `${MICO_FIGURES_DIR}` if set).
- For regenerated *tables*, prefer an external directory such as `${MICO_TABLES_DIR:-/tmp/clean_mico_results/tables}`
  instead of creating a repo-owned `results/tables/` tree.

### Notes about this snapshot (reproducibility)

- This snapshot intentionally stores the **main** runtime artifacts needed for reproducibility under `results/runtime/`.
- Some historical intermediate outputs referenced by older logs are missing due to a machine reset/disk format.
- For the main experiment entry points vs postprocess scripts, see `scripts/README.md`.
