gsj-harness-rollout-server
A rollout server for the gsj corpus: given a task (case, timestep, prompt), it runs a pinned coding agent (pi 0.83.0) in an isolated sandbox whose git checkout and retrieval are both truncated at timestep, captures every token and logprob the model produced, and emits one validated, training-ready trajectory — task → sandbox → agent → trace, nothing else (no storage, scheduling, rewards, weights, versioning, or training; those belong to the trainer that calls it).
Episode execution and trajectory reconstruction are NVIDIA's Polar, vendored by commit with three carried patches; our own code is the 2,016-line shell that points Polar at our corpus, retrieval service, agent, and checks. The evaluation behind it ended in ADOPT (provisional 2026-08-09, converted 2026-08-11 on production hardware) — docs/VERDICT.md is the standalone statement, with the conditions that would reverse it.
One task triple in, one validated trace out. Everything around the sandbox is Polar's, the operator's estate, or the trainer's; ours is the harness, the builder, the receiver, and the checks.
Install
# trainer: any Python >= 3.12, anywhere; deps pydantic, httpx, pyyaml; runs no episodes
pip install gsj-harness-rollout-server
# server: a checkout, Polar's venv (it must also host gsj_rollout — Polar loads our harness and builder by import path), and an estate no pip install provides
git clone https://github.com/MHGanainy/gsj-harness-rollout-server && cd gsj-harness-rollout-server
python3.12 -m venv .venv && . .venv/bin/activate && pip install -e ".[dev]"
cd vendor/polar && uv venv --python 3.12 .venv && uv pip install -p .venv/bin/python -e . && uv pip install -p .venv/bin/python -e ../.. && cd ../..
gsj-rollout serve --config rollout.yaml # renders topology.rendered.yaml, prints the two Polar commands, runs the receiver
Quick start
from gsj_rollout import RolloutClient, load_config
from gsj_rollout.config import render_task_request
cfg = load_config("rollout.yaml") # one YAML describes the whole deployment
request = render_task_request(cfg, task_id="demo", instruction="Fix the failing test.", case_id="case_0001", timestep=12)
traces = RolloutClient(cfg.polar.rollout.base_url).collect([request]) # Polar's rollout API, not the receiver; list[Trace], only checks-clean sessions come back
gsj-rollout submit --config rollout.yaml --case case_0001 --timestep 12 --prompt "Fix the failing test."
# exit codes: 0 all collected · 1 partial · 2 usage · 3 server unreachable
The two roles
| Server role | Trainer role | |
|---|---|---|
| You need | the four estate services — an inference engine (vLLM, pinned chat template), a Forgejo git host (one repository per case, one branch per timestep), the MCP retrieval service, the ingested corpus — plus this checkout with Polar's venv under vendor/polar/ |
Python ≥ 3.12, anywhere: pip install gsj-harness-rollout-server (0.1.6, wheel-only: gsj_rollout/, both pins sets, the G2 reference capture, ingest_corpus.py, estate.py). No vendor/, no Polar |
| You run | gsj-rollout serve --config <yaml>, then the two printed Polar commands yourself: serve_rollout (rollout API + scheduler — the trainer's base_url) and serve_gateway (gateway + capture proxy, one sandbox per episode, loading pi_harness.py and builder.py by import path) |
RolloutClient: submit · wait · collect — collect submits, polls GET /rollout/task/{id}, re-runs checks on every result, returns the Traces of clean sessions |
| You validate | every callback: clean → traces/, bad → quarantine/ with its findings — the same checks.py on both sides of the wire |
checks.validate_session_result(result) — the identical validators the receiver ran, because nothing upstream is trusted |
| Start here | Server guide; an estate from nothing: gsj-rollout-demo |
Trainer guide; a loop against an existing server: gsj-harness-rollout-server-examples + its RUNBOOK.md |
One trap worth naming at the door. checks validates traces against pinned approved sets (tool rosters, system prompts, skill cards, settings), and the wheel ships this estate's pins — on any other estate every hash gate fails *_not_approved, loudly, by design. Point GSJ_PINS_PATH at your own pins file before the first import of gsj_rollout.checks; resolution is GSJ_PINS_PATH → repo checkout → packaged copy (a UserWarning when the packaged copy is what resolved), and an unusable path raises PinsConfigurationError rather than falling through. The thinking-on reference set rides at gsj_rollout/pins/thinking-on/pins.gsj.json — gate G6 compares against per-mode pins data. Format and reasoning: docs/checks-spec.md.
The timestep cutoff
timestep is a boundary the agent cannot cross — one wall on the filesystem, one in front of retrieval — enforced twice and audited once.
- The filesystem wall. The sandbox clone is branch
timestep-T,--depth 1, remote removed, reflogs scrubbed — git history cannot reach a page past T even offline. - The retrieval wall. The harness mints an HS256 token host-side with claims
{case_id, timestep, episode_id, exp}; the signing secret never enters the sandbox; the service verifies the signature, then filters topage ≤ Tbefore ranking, with T from the verified claims only. The agent may read its own token but cannot widen its timestep — any mutation invalidates the signature (tampered claims, timestep 12→18 with the original signature: HTTP 401). - The audit.
checks.pygate G5, from the trace alone: every retrieved page ≤ T; the checkout shallow, zero remotes, branch== timestep-T; checkout pages contiguous1..T.
Documentation
- The guide, six flat pages under
docs/guide/: index · how it works · validation and pins · server guide · trainer guide · troubleshooting - Normative: Verdict (read first) · Charter (scope laws; assumptions §4; gap register §7; standing rules §8) · Checks spec · Corpus contract
What has been proven
| claim | measured — as of 2026-08-24 (the audit re-executed every suite and census that day; per-checkpoint figures carry their report's date) | evidence |
|---|---|---|
| traces match the predecessor's golden reference | loss_mask exact at zero tolerance; prompt_ids byte-identical (2965/2965) — on the Mac pair and again on the H200 pair |
CP-09, CP-09′; docs/golden/COMPARISON.md |
| the logprobs are real captures | H200 replay-vs-replay bit-deterministic (0.000000); capture-vs-replay floor mean ≈ 0.005–0.007 on both traces symmetrically (platform-classified, per the contract); Mac identical-context agreement mean |Δ| = 0.000114 | CP-09′, CP-09 |
| the cutoff holds under a forged claim | tampered token (timestep 12→18, original signature) rejected HTTP 401 from inside the sandbox; valid token 200; live episode retrieved pages [1, 5, 7, 9, 11], all ≤ 12 | CP-07 |
| two trainers, two loops, zero server changes | slime: 27 qualifying traces → one optimizer step → weight sync (logprobs moved at 5623/5782 positions) → 8/8 re-collect; verl: 110 qualifying → one step → sync (310/310 tensors, exactly one AdamW step) → 8/8; gsj_rollout/ untouched both times |
CP-17, CP-21 |
| two model families, no code change | Qwen3-0.6B (both golden pairs); Llama-3.1-8B — 8 completions merged into one full chain, quarantine empty, gates green | CP-04′/CP-09′, CP-38 |
| a stranger can run it from nothing | fresh machine, demo README the only input: clone → pip → estate up → first episode accepted, ≈ 5 minutes wall plus the model endpoint; two manual image pulls needed (amd64-only images on an ARM host — registered) | CP-36 |
| the shell stays thin | ours 1,999/2,000 lines vs Polar's ~14,200 driven; the predecessor spent ~1,800 lines on episode execution alone | audit 2026-08-24; VERDICT §1 |
| the fixture suites | root 161 + corpus 58 + mcp-service 89, all green by execution | audit 2026-08-24 |
The badge covers none of this — not the golden pairs, fidelity, the loops, or any episode (episodes need an estate and GPU time). Green means the fixtures still pass, not that the harness runs.
What it does not do
- It never trained anything, and says so. Each loop above is exactly one optimizer step bracketed by two collections; the post-sync 8/8 reward reads as the onset of mode collapse, not competence. Concurrent collection-and-training and weight sync at cadence have zero data points.
- The trainer's problems stay the trainer's: storage, retention, mixing, staleness, collation, reward — every callback carries
reward: null— and weight sync. Dropped deliberately, at the start (charter §7 rows 16–21). - Sampling and codec provenance are the estate's, not the trace's. pi sends no sampling parameters, so the engine's configuration is the sampling policy — an unpinned engine silently samples at neutral defaults (measured). Codec identity is verified at bring-up by the pins walk, not per-trace.
- Two open gaps, of a 32-row capability register (21 parity, 7 dropped by decision, 1 better, 1 TBD): row 12 — G4 codec evidence never rides the callback, receiver-side by decision; row 22 — per-episode binding of traces to engine identity (serve argv, generation config, codec), owned by the first production bring-up, along with the credentialed-clone/egress decision (the evaluation estates serve anonymous git read).
- Model-agnostic in mechanism, Qwen-fitted in defaults. One foreign family is one data point; a different reasoning geometry is the wall. Thinking-on requires the symmetric served template.
Repository layout
| Path | What it is |
|---|---|
gsj_rollout/ |
the server, eight modules (pi_harness, builder, receiver, checks, config, client, cli, the package surface: RolloutClient, Trace, checks, load_config, RunConfig) — the whole of what the wheel ships, plus the pins, ingest_corpus.py and estate.py copied in at build time |
vendor/polar/ |
Polar at the commit in POLAR_SHA, patched (vendor/patches/ P1–P3, vendor/apply_patches.sh --verify, re-vendor recipe vendor/REVENDOR.md); ships in no artifact |
estate/ |
this repository's test estate: estate.sh front door, estate.py (the estate's one tool — scaffold · validate · up · ingest · update · status · down), corpus/, mcp-service/, forgejo/, serving/ — outside the line budget |
docs/ · pins/ |
the guide, the four normative documents, and the seven Polar run bodies CI/tests/pins-walk read; the approved sets (reference + thinking-on/) with derive scripts — the single source for the wheel copies |
tests/ |
the root suite (169 tests, no estate needed) and the tracked golden fixtures tests/fixtures/golden-mac/ |
The CP-06 feasibility spike (stub backend, spike harness, the stub-side wire captures, p1_verdict.py, wire_diff.py) left the tree at CP-68 and is frozen at tag spike-cp06 — commit a769771, tree 2082a24f; restore with git checkout spike-cp06 -- spike.
Where the record lives
The development record is maintained privately. The per-checkpoint reports and prompts, the 32 ADRs, the 2026-08-24 adversarial audit (24 agents; no live gate, pin value, or code path wrong; zero of 91 findings refuted), and the raw docs/golden//docs/polar/ artifacts exist unchanged in the operator's working tree, not in this repository — so the evidence column above, the shelf map in docs/README.md, and two report paths in the shipped pins.gsj.json provenance resolve only there; for anyone else those claims reduce from "checkable at the cited path" to "asserted". What stays checkable from a clone: the code, the three suites, the pins walk, the wheel assertions, and the evidence bodies they execute against.
Licence
Apache-2.0 — LICENSE. vendor/polar/ is NVIDIA's, carries its own Apache-2.0 LICENSE, and ships in no released artifact: the wheel contains gsj_rollout/, the two pins sets, the G2 reference capture (pins/container/system_prompt.container.derived.txt), ingest_corpus.py and estate.py (as gsj_rollout.ingest_corpus / gsj_rollout.estate — gsj_rollout.bringup on wheels 0.1.3–0.1.5) — nothing else, asserted at build time (18 entries since 0.1.3). Predecessor: gsj-envloader @ v0.8.0, archived 2026-08-25 — still the goldens' collecting stack, readable at v0.8.0; no longer the fallback, a term that expired at the verdict's conversion on 2026-08-11.
Provenance
- The verdict provisional at CP-12, converted at CP-17 · the predecessor archived at CP-45 (2026-08-25, ADR-0026), the fallback term expired at CP-17 (2026-08-11) · the wheel's contents asserted at build time — CP-19,
ingest_corpus.pyin the wheel since CP-34. - The filesystem wall — CP-11 · gate G6 as per-mode pins data, the thinking-on set in the wheel — ADR-0024 · the two ARM image pulls registered — wishlist row 40 · an unpinned engine at neutral defaults — CP-09 finding F1 · codec at bring-up via the pins walk, G4 estate-side — ADR-0011.
- One optimizer step per loop, 8/8 read as mode-collapse onset — CP-21 · zero data points on concurrent collect-and-train and cadence sync — charter A-13 · the trainer's problems dropped — CP-00, charter §7 rows 16–21 · row 22 and credentialed-clone/egress owned by the first production bring-up — CP-40 · "Qwen-fitted in defaults" — CP-38's own words · the symmetric served template — charter A-22.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gsj_harness_rollout_server-0.1.6-py3-none-any.whl.
File metadata
- Download URL: gsj_harness_rollout_server-0.1.6-py3-none-any.whl
- Upload date:
- Size: 133.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
79825cf3a7c6fb3b4b2d53451ad9567e37b813da191459d177841104450b1caa
|
|
| MD5 |
a1efd80089ad12211225b78320d9fa01
|
|
| BLAKE2b-256 |
1f8806dffe64a23deeca8a7c7f6dba6a6618114e01a5c3a6427130ace02da448
|
Provenance
The following attestation bundles were made for gsj_harness_rollout_server-0.1.6-py3-none-any.whl:
Publisher:
release.yml on MHGanainy/gsj-harness-rollout-server
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gsj_harness_rollout_server-0.1.6-py3-none-any.whl -
Subject digest:
79825cf3a7c6fb3b4b2d53451ad9567e37b813da191459d177841104450b1caa - Sigstore transparency entry: 2684776252
- Sigstore integration time:
-
Permalink:
MHGanainy/gsj-harness-rollout-server@31d51e13b9e0410925d42379704d66cb1cf8bd08 -
Branch / Tag:
refs/tags/v0.1.6 - Owner: https://github.com/MHGanainy
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@31d51e13b9e0410925d42379704d66cb1cf8bd08 -
Trigger Event:
push
-
Statement type: