Preserve continuous rollout hard scores

Rollout hard scores can be continuous when smoothed rewards are used. Converting the field through int() turned values like 0.75 into 0, which loses signal before scoring and serialization.

Constraint: Keep the existing RolloutResult dictionary shape unchanged.

Rejected: Clamp hard scores to 0 or 1 | contradicts existing continuous-score support in compute_score and sleep replay types.

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Treat hard as numeric reward data, not only a binary label.

Tested: uv run --with pytest pytest tests/test_types.py tests/test_scoring.py -q

Tested: uv run --with ruff ruff check skillopt/types.py tests/test_types.py

Not-tested: Full benchmark rollouts.
This commit is contained in:
Ziiii
2026-07-06 15:42:07 +08:00
parent e4ea6a6771
commit 0c30b778fb
2 changed files with 20 additions and 7 deletions
+15 -2
View File
@@ -3,8 +3,7 @@ from __future__ import annotations
import pytest
from skillopt.types import Edit, Patch
from skillopt.types import Edit, Patch, RolloutResult
# ── Edit ────────────────────────────────────────────────────────────────────
@@ -247,3 +246,17 @@ class TestPatchEdgeCases:
assert isinstance(p.edits[0], Edit)
assert p.edits[0].op == "append"
assert p.edits[0].content == "hello"
# ── RolloutResult ────────────────────────────────────────────────────────────
class TestRolloutResultRoundTrip:
"""RolloutResult.to_dict() / RolloutResult.from_dict() round-trip."""
def test_preserves_fractional_hard_score(self) -> None:
"""Hard can be a continuous reward and must not be truncated."""
result = RolloutResult.from_dict({"id": "episode-1", "hard": 0.75, "soft": 0.5})
assert result.hard == pytest.approx(0.75)
assert result.to_dict()["hard"] == pytest.approx(0.75)