RLVR here means reinforcement learning from verifiable reward. For work-environment packs the verifiable signal is the staged recipe: each hidden verifier scores a stage in [0, 1], each stage has a weight, and weights sum to 1. Rollout reward is the weighted sum, clamped to [0, 1], rounded to four decimals. The worker writes that number. The browser never grades.
Staged reward
reward = Σ weightᵢ × stageᵢ for i in hidden stages
─────────────────────
weights sum to 1 (±1e-9). Each stageᵢ ∈ [0, 1].TypeScript and Python share the same sum. stagedReward in @agentforge/contracts is for tests and docs. apply_recipe("staged", …) / staged_breakdown in services/evaluator is what a trial actually uses. Per-stage values persist on evaluation_trials.metrics as stage_<id> (hyphens become underscores).
packages/contracts/src/work-environment.ts
export function stagedReward(
stages: readonly StageVerifier[],
metrics: Readonly<Record<string, number>>,
): number {
const sum = stages.reduce(
(acc, stage) => acc + stage.weight * (metrics[stage.metricKey] ?? 0),
0,
);
return Math.round(Math.min(1, Math.max(0, sum)) * 10_000) / 10_000;
}Incident rubric
| Stage id | Metric | Weight |
|---|---|---|
| health | service_health | 0.35 |
| root-cause | root_cause | 0.20 |
| data | data_preserved | 0.20 |
| rollback | safe_rollback | 0.15 |
| communication | communication | 0.10 |
Partial credit is real. A NOP that never deletes customers still scores data_preserved and still sits far from 1.0. An agent that restores health and then wipes rows fails the data stage. The workspace plots those stage_* keys after the worker writes them. It does not recompute the sum in the client.
Who writes the number
End-of-trial recipes binary, continuous, and hybrid still exist for catalog fixtures. Work-environment packs set rewardRecipe to staged in EvaluatorConfigurationSchema. enqueuePackRun pins that configuration on the job. The evaluator claims the job, runs the world, calls staged_breakdown, inserts evaluation_trials (reward, metrics, trajectory artifact), and inserts an evaluation_results row with meanReward / passAt1 / passAtK. attemptCount is 1–8 independent trials for pass@k.
EvaluatorConfiguration (queued, not executed in Next.js)
{
"schemaVersion": 1,
"agentKind": "multi_agent",
"attemptCount": 4,
"rewardRecipe": "staged",
"sandboxProvider": "work",
"composition": "multi_agent",
"packKey": "production-incident-eu-payments",
"scenarioKey": "production-incident-eu-payments"
}