legato

legato.rl: graded desktop rollouts for computer-use RL

legato.rl runs the rollout side of computer-use RL. Your trainer keeps the weights, and your model server keeps serving them. The desktops run wherever you already run them. legato.rl sits between the three:

It owns no GPUs, and it never sees your weights.

trainer ──(weight sync, as today)──> model server (vLLM, SGLang, a provider)
   ^                                        ^
   | trajectories                           | screenshot + history -> action, with logprobs
   |                                        |
legato.rl collect ──────────────────────────┘
   └── desktops (one worker each): reset / act / wait until ready / screenshot / grade

What it measured (our OSWorld study: OSWorld tasks on OSWorld's own harness, Qwen3.5-9B, 1,560 rollouts)

Quickstart

pip install legatolabs
export LEGATO_POLICY_KEY=...            # the model server's key; never passed on the command line

# 1. Check the server: logprobs, token ids, cached-token reporting
python -m legato.rl probe --policy-url https://your-server/v1 --model Qwen/Qwen3.5-9B

# 2. Collect a batch on policy version step-41
python -m legato.rl collect --tasks tasks.json --policy-url https://your-server/v1 --model Qwen/Qwen3.5-9B \
    --version step-41 --out runs/step-41 --desktops 16 --group 8 --oversample 0.25 --max-steps 15 \
    --max-inflight 32 --osworld ~/osworld

# 3. One training sample per policy step, with group advantages
python -m legato.rl export runs/step-41 --out runs/step-41/train.jsonl

# 4. Where the batch spent its time
python -m legato.rl profile runs/step-41

From Python:

from legato.rl import collect, export_steps, PolicyConfig, DesktopConfig
summary = collect(tasks, PolicyConfig("https://your-server/v1", "Qwen/Qwen3.5-9B", version="step-41", api_key=key),
                  out="runs/step-41", desktops=16, group=8, oversample=0.25, max_inflight=32,
                  desktop=DesktopConfig(osworld_path="/home/me/osworld"))
export_steps("runs/step-41", "runs/step-41/train.jsonl")

tasks.json is a list of entries in either of two forms:

Backends

The harness is OSWorld's QwenAgent: its prompt, its history and its action parsing. legato.rl routes its model call through legato.rl.policy.

Knobs

Option Default What it does
--group 4 Attempts per task: one GRPO group
--oversample 0 Extra attempts per group. The group closes once group attempts finish, and the rest are cancelled at their next step (stragglers do not hold the batch)
--max-inflight none Cap on concurrent model requests across all desktops. Set it to about 32–48 per H100 for a 9B model (our OSWorld study)
--step-timeout / --retries 60 s / 1 Per-step bound on a model call. In our OSWorld study, one hung request held a rollout for 34 minutes
--rollout-timeout none Wall-clock cap per rollout. A rollout that hits it is graded and marked truncated
--env-retries 1 A desktop failure (VM or setup) re-queues the attempt on another try
--no-ready-signal --fixed-wait S off OSWorld-style fixed sleeps, for baselines
--no-fast-reset off Snapshot revert every rollout, for baselines
--image-max 20 Screenshots kept in the prompt

Output: legato.rollout/v0

DIR/rollouts.jsonl holds one record per attempt; DIR/messages/ and DIR/images/ hold the prompts. Its fields:

legato.rl export writes one line per step of every ok or truncated rollout. Each line carries:

DIR/summary.json (and profile) reports:

Notes

Edit this page on GitHub · Markdown