Ordinary token capture: Gym picks each call's parent itself by matching the request against earlier calls. A rewritten history has no match, and the rollout is masked.
This PR
With context compaction (CC), the agent still decides when to compact, but it sends no flag. Gym proposes a parent, and RL's vLLM worker checks whether the request really extends it: continue it, or start a new root. Everything else on this page already existed.
Status
Proposed — open PR, not merged. The values below come from a real run of the PR's code, with a stand-in model.
Nine pages on this PR, one question each. This one: what moves where during one model call, and after the rollout ends.
Background. Five places take part.
The agent (the harness in Gym) owns the conversation and its compaction policy.
Gym's model server sits in front of the model and keeps the ledger.
RL's vLLM worker runs the model.
TransferQueue stores tokens.
The RL driver collects results, rebuilds training rows and trains.
Dotted words have a short explanation: hover or tap them.
The figure follows call 2 (c2) of one rollout, the same run as page 3. The agent sends only messages and never tokens (it no longer sends a parent hint either). Gym proposes the earlier call whose history the new request extends (_admit_candidate).
RL's worker checks that claim and decides (decide_capture_input). The worker then writes the call's new tokens straight to TransferQueue and returns only small numbers to Gym's ledger.
After the rollout, the RL driver pulls the ledger and rebuilds rows from TransferQueue. The two orange steps are new in this PR.
One call, then the end of the rollout
What changed for each kind of request
Ordinary capture (CC off)
Context compaction (this PR)
Who picks the parent
Gym, by matching the request's earlier assistant messages against recorded calls (sink.py L246-319)
Gym proposes, RL's worker decides (steps ② and ④)
Plain next turn
continues the last call
continues the last call
History rewritten
no parent found → unresolved_parent → whole rollout masked
new root, stored with its whole prompt → one more training row
Agent rejects a response and asks again
the retry is a sibling of the rejected call
the same: Gym proposes the last accepted call, so the retry is a sibling, not a new root
What the ledger holds
Never tokens: ids, parent, three lengths, hashes, response id. Tokens live only in TransferQueue, keyed <rollout>/<call>.
So what. Your picture of "Gym's ledger goes back to RL, and the reassembler builds the row" is right, with one correction: the ledger never carries tokens. The worker writes them straight to TransferQueue, and RL joins the two by key.
What CC adds is steps ② and ④. Gym proposes a parent, and RL accepts it only when the request truly extends that call. Every rejection becomes a new root. Page 3 shows how those roots turn into rows.