sergiopaniego/opencode-rollout-trace
One opencode rollout, as the trainer sees it The artefact behind the talk Training a coding agent through a harness you did not write (Lisbon AI, September 2026). An off-the-shelf coding agent, opencode, runs untouched in a remote sandbox. A proxy sits between it and vLLM, speaks the agent's own dialect, and records every model call the agent makes together with the exact token ids and logprobs. That recording is what gets trained on. This repo is one of those recordings, plus… See the full description on the dataset page: https://huggingface.co/datasets/sergiopaniego/opencode-rollout-trace.
One opencode rollout, as the trainer sees it
The artefact behind the talk Training a coding agent through a harness you did not write (Lisbon AI, September 2026).
An off-the-shelf coding agent, opencode, runs untouched in a remote sandbox. A proxy sits between it and vLLM, speaks the agent's own dialect, and records every model call the agent makes together with the exact token ids and logprobs. That recording is what gets trained on. This repo is one of those recordings, plus the code that produced it.
Nothing here is a transcript. A transcript would be text, and retokenising text gives you different ids than the ones the model actually sampled. That difference is the point.
Files
Reading turns.json
Each turn carries what the training path actually uses:
prompt_ids: the conversation sent to the model that turn, re-tokenized with its tools, so the prompt matches what the upstream renderedoutput_ids: the ids the model generated, taken from the capture and not re-encodedper_token_logps: the generator's logprobs for those idstrained: whether the training policy keeps this turn. Here it ishas_tool_call, so turns where the model took an action are kept and pure-text turns are notmask: derived, not recorded.0for the context,1for the tokens the model generated. The proxy never stores a mask, the distinction is structural: onlyoutput_idsget a gradient. It is materialised here because that distinction is the whole reason the artefact is interesting.
Turn count in turns.json is smaller than in trace.json on purpose. opencode fires extra model calls for its own bookkeeping, a title generator and a context summariser, and those are not the task. Training them with the rollout's reward would reinforce the wrong thing, so they are filtered by anchoring on the system prompt of the first tool-enabled turn.
Reproducing it
TASK_INDEX=0 PUSH_TO_DATASET=sergiopaniego/opencode-rollout-trace \
hf jobs uv run --flavor a100-large --secrets HF_TOKEN --timeout 3600s \
"https://huggingface.co/datasets/sergiopaniego/opencode-rollout-trace/resolve/main/capture_launcher.py"One GPU is enough: this captures, it does not train, so there is no weight sync. The vLLM flags matter, --return-tokens-as-token-ids is what lets the proxy recover exact ids instead of re-encoding decoded text.
build_dataset(n_prompts, seed) in the training example is deterministic, so TASK_INDEX selects a reproducible task. Keep N_PROMPTS equal to what the training run used and the indices line up.
Where this comes from
- Training script, maintained upstream in TRL: `examples/async_grpo_opencode/opencode_hf_sandbox.py`
- Write-up: Training a coding agent using the OpenCode harness
- Task data: DeepCoder-Preview-Dataset
- Sandbox image:
ghcr.io/huggingface/openenv-opencode-sandbox:latest - The policy trained this way: Qwen3-8B-opencode-deepcoder-grpo
