feat(reward): MIT reference reward worker with self-signed reward receipts - #452
Merged
Conversation
…eipts Add openadapt_flow/reward: a loopback HTTP worker that reads the system of record after a training episode through an independent oracle, judges required and forbidden effects with the shared three-valued judge, scores with openadapt_types.reward.score, and signs a RewardEvidenceReceiptV1 with the same local Ed25519 key mechanism as the reference Execute server. INDETERMINATE and an unreachable store map to unscored outcomes, never 0. Tier 0 and 1 reads are development_only and never certified. The seeded MockMed certificate carries synthetic scope and an epsilon computed as the exact one-sided 95% Clopper-Pearson bound from 300 ExtraDup trials the seed runs through the bundle's own judge. The POST /v1/rewards wire shape matches openadapt_evals.reward.receipts. The OpenAI grader route mirrors the python grader's grade(sample, item) contract and answers 422 for an unscored episode. TRL and verl adapters return None and NaN respectively for unscored samples, with a helper that drops them from a group. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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.
What this adds
openadapt-flow serve-reward --contract <bundle> --port 8788scores a training episode by reading the system of record after the episode ends, through the effect-verifier kit's REST, SQL, FHIR, and file readers, and signs aRewardEvidenceReceiptV1(openadapt-types 0.17) with the same local Ed25519 key mechanism asserve-execute, in a sibling data directory (~/.openadapt/reward-ref/).A reward receipt is not an Execute Seal. A model rollout is not a qualified program, so it never gets an Execute receipt, and the reward receipt never claims Flow governed the policy. The envelope says so in three fields (
execute_seal: false,production_seal: false,flow_governed_policy: false) and the receipt has none of the Execute binding fields.Outcome mapping
Required and forbidden effects go through the shared three-valued judge in
runtime/effects/_common.py. The table is indocs/REWARD_WORKER.md. The rules a reviewer should check:reconciliation_requiredorfailed_platform,scalar_reward: null). Never 0.wrong_effect. The seeded MockMed contract declareswrong_effect_reward: 0.0; the contract default is -1.0.wrong_effect. Scoring the sameepisode_idtwice returns 409.development_onlyand never certified. A tier-2 verified receipt with an expired certificate is scored and not certified.Wire shapes
POST /v1/rewardstakes the descriptoropenadapt_evals.reward.receipts.EpisodeDescriptorsends (episode_id,policy_checkpoint_id,policy_update,reward_contract_digest, optionaltask_id,environment_id,metadata) and answers 200 with{"receipt": ...}and no top-levelschema_version, which is what that client's parser keys on.test_http_matches_the_evals_client_wire_shaperound-trips that exact JSON. The oracle identity comes frommetadata.oracle_identity, anoracle_identityfield, or abegin_episoderegistration; a wrong contract digest or a missing identity is 422.POST /v1/graders/openaimirrors the only custom-grader contract OpenAI documents, the python grader'sgrade(sample, item) -> float(graders guide and RFT guide at developers.openai.com, read 2026-09-01). OpenAI documents no grader that calls a user-hosted URL and its python grader has no network access, so a hosted RFT job cannot reach this route; it serves a self-hosted loop that speaks that shape. An unscored episode answers 422error: unscoredrather than 0, and a wrapper must drop the sample.openadapt_flow.reward.callables:trl_reward_functionreturnsNonefor an unscored sample (TRL's documented sentinel);verl_compute_scorereturns{"score": nan, "openadapt_unscored": true}anddrop_unscored/scored_groupsremove those samples. Doc URLs are in the module docstring.Calibration
--seed-mockmedruns 300 ExtraDup trials through the bundle's own judge before it signs the synthetic certificate (extra record, duplicate, missing record, wrong type, forbidden discharge; seeded generator).epsilonis the exact one-sided 95% Clopper-Pearson bound from the observed counts (0 of 300 gives 0.0099; the evals proof's 0 of 15 gives 0.181036 by the same function, asserted in a test).calibration.jsonbeside the certificate holds the counts. The certificate carriescalibration_scope: syntheticandissuer: self_signed. No production-scope certificate exists.Dependencies
openadapt-types is pinned
>=0.17.0,<0.18.0in core and every extra that carried it;uv.lockis regenerated. The types-boundary tests pass locally against 0.17.0. New extra:reward(fastapi, uvicorn, openadapt-types).Boundary
openadapt_flow/reward/imports nothing from Cloud;test_source_boundary_has_no_cloud_moduleschecks it. Oracle recipes carry no credential (headers_env,token_envname environment variables). The seeded recipes are synthetic; a per-system-of-record recipe stays private.Opened by an agent session, not the founder.
🤖 Generated with Claude Code