Add per-domain RewardAdapter seam (Refs #199)#360
Open
larstalian wants to merge 1 commit into
Open
Conversation
Packs emit a structured EpisodeResult and never a scalar; today the result->reward mapping is hardcoded harness-side. Introduce a RewardAdapter ABC on the pack SDK so each pack owns its natural reward shape and training loops stay domain-agnostic. - SDK: RewardAdapter ABC + generic SubgoalFractionRewardAdapter (1.0 on success, else subgoal fraction, else 0.0), exposed via a concrete Pack.default_reward_adapter() hook so every existing pack keeps working. Widen EpisodeReportLike with episode_result so adapters can read subgoals for partial credit; concrete EpisodeReport already satisfies it. - Cyber: CyberPentestRewardAdapter gives a monotone exploit-chain ladder (reached_endpoint 0.2 < extracted_anything 0.6 < matched_flag 1.0), wired through WebappPack.default_reward_adapter(). - Trainer: episode_reward/episode_trajectory take an optional adapter that sources the scalar; adapter=None preserves current behavior exactly. - Example: examples/reward_adapter_demo.py contrasts the generic default against the cyber ladder and feeds episode_trajectory (no LLM/network). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Collaborator
Author
Code review — RewardAdapter seam (Refs #199)Reviewed against the repo's own bar: What's good
Must address before merge
Should address
Notes / follow-up (non-blocking)
Overall: solid, in-scope, well-tested implementation of #199. Reconcile the design docs (must) and de-dup the fraction logic (should), and this is ready. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Refs #199. Adds a
RewardAdapterseam so each pack owns itsEpisodeResult -> rewardmapping and training loops stay domain-agnostic — replacing the hardcodedsuccess -> 1.0 / else 0.0shaping. The point of this increment is the seam, not a perfect reward function.openrange-pack-sdk,_protocols.py): newRewardAdapterABC —reward(report: EpisodeReportLike) -> float | Sequence[float]— consuming the SDK'sEpisodeReportLike, neveropenrange. Adds a genericSubgoalFractionRewardAdapterthat reproduces today's dense mapping exactly (1.0on success, else fraction of subgoals passed, else0.0), returned by a concretePack.default_reward_adapter()hook so every existing pack keeps working (trading/swe inherit it unchanged).EpisodeReportLikeis widened with anepisode_result: EpisodeResultproperty so adapters can read subgoals for partial credit; concreteEpisodeReportalready satisfies it. Both new symbols exported from the SDK__init__.packs/cyber_webapp/.../reward.py):CyberPentestRewardAdaptergives a monotone exploit-chain ladder from the subgoals the pentest grader already emits —reached_endpoint (0.2) < extracted_anything (0.6) < matched_flag (1.0),1.0on success — wired viaWebappPack.default_reward_adapter().src/openrange/training.py):episode_reward/episode_trajectorytake an optionaladapter. Afloatoutput is the scalar (keeping per-subgoalcomponents); aSequence[float]becomes an indexedcomponentsmap whose mean is the scalar.adapter=Nonepreserves current behavior byte-for-byte.examples/reward_adapter_demo.py): builds miss / reached / extracted / solved reports by hand and prints the generic default vs the cyber ladder, then feedsepisode_trajectory(report, adapter=...). No LLM, no network.The protocol widening required adding
episode_resultto a handful of existingEpisodeReportLiketest shims (curriculum / mutation / dashboard tests); no core behavior changed.Test plan
uv run ruff check .— cleanuv run ruff format --check .— cleanbash scripts/check_boundary.sh— clean (cyberreward.pyimports only the SDK)uv run mypy src tests examples main.py packages/openrange-trl/src packages/openrange-rllm/src— Success, no issues (89 files)uv run mypy packages/openrange-pack-sdk/src packages/openrange-pack-sdk/tests/test_reward.py packs/cyber_webapp/cyber_webapp/reward.py— cleanuv run pytest packages/openrange-pack-sdk/tests/test_reward.py tests/test_cyber_reward.py tests/test_training.py tests/test_curriculum.py tests/test_cyber_auto_curriculum.py tests/test_v1_llm_generation.py tests/test_dashboard_runs.py tests/test_import_boundaries.py packages/openrange-pack-sdk/tests/test_sdk.py(220 passed)training.py+ cyberreward.pyreport complete; new_protocols.pylines all covered). Tests use real concrete subclasses + realEpisodeReport/EpisodeResult— no mocks/patches/fakes.uv run python -m examples.reward_adapter_demoruns (no LLM/network)🤖 Generated with Claude Code