agentevolve-optimizer 0.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (414) hide show
  1. agent_evolve/__init__.py +722 -0
  2. agent_evolve/agentic.py +2800 -0
  3. agent_evolve/api.py +767 -0
  4. agent_evolve/application/__init__.py +1580 -0
  5. agent_evolve/application/action_allocation.py +744 -0
  6. agent_evolve/application/action_allocation_frame.py +347 -0
  7. agent_evolve/application/action_allocation_frame_commit.py +185 -0
  8. agent_evolve/application/action_allocation_frame_commit_v3.py +184 -0
  9. agent_evolve/application/action_allocation_frame_v3.py +338 -0
  10. agent_evolve/application/action_archive_value.py +497 -0
  11. agent_evolve/application/action_evidence_consistency.py +455 -0
  12. agent_evolve/application/action_forecast_partitioning.py +1471 -0
  13. agent_evolve/application/action_metric_projection.py +211 -0
  14. agent_evolve/application/action_role_value.py +680 -0
  15. agent_evolve/application/action_score_authorities.py +363 -0
  16. agent_evolve/application/action_structural_signature.py +116 -0
  17. agent_evolve/application/action_target_realization.py +402 -0
  18. agent_evolve/application/agentic_evolution.py +7734 -0
  19. agent_evolve/application/agentic_portfolio_residual_expert.py +835 -0
  20. agent_evolve/application/anchor_residual_identification.py +463 -0
  21. agent_evolve/application/archive_conditioned_action_target.py +208 -0
  22. agent_evolve/application/artifact_journal.py +246 -0
  23. agent_evolve/application/artifact_replay.py +347 -0
  24. agent_evolve/application/budgeted_optimizer.py +1828 -0
  25. agent_evolve/application/calibrated_campaign.py +485 -0
  26. agent_evolve/application/calibrated_current_prefix_forecast_opportunity.py +322 -0
  27. agent_evolve/application/calibrated_positive_gain_opportunity.py +1581 -0
  28. agent_evolve/application/campaign_capacity_recourse.py +254 -0
  29. agent_evolve/application/campaign_contextual_outcomes.py +119 -0
  30. agent_evolve/application/campaign_diagnostic_blocks.py +930 -0
  31. agent_evolve/application/campaign_evidence_registry.py +262 -0
  32. agent_evolve/application/campaign_execution.py +2537 -0
  33. agent_evolve/application/campaign_generation_audit.py +942 -0
  34. agent_evolve/application/campaign_learning.py +1812 -0
  35. agent_evolve/application/campaign_learning_runtime.py +1977 -0
  36. agent_evolve/application/campaign_search_phase.py +227 -0
  37. agent_evolve/application/campaign_selector_context_extension.py +220 -0
  38. agent_evolve/application/campaign_variation_envelope.py +649 -0
  39. agent_evolve/application/campaign_variation_trace.py +451 -0
  40. agent_evolve/application/candidate_archive_consequence.py +128 -0
  41. agent_evolve/application/causal_opportunity_portfolio_gate.py +385 -0
  42. agent_evolve/application/composite_outcome_updater.py +145 -0
  43. agent_evolve/application/composition_portfolio_selection.py +363 -0
  44. agent_evolve/application/concurrent_stage.py +144 -0
  45. agent_evolve/application/contextual_action_allocation.py +181 -0
  46. agent_evolve/application/contextual_campaign_outcomes.py +267 -0
  47. agent_evolve/application/contextual_campaign_planning.py +1366 -0
  48. agent_evolve/application/contextual_delayed_credit.py +651 -0
  49. agent_evolve/application/contextual_search_controller.py +2374 -0
  50. agent_evolve/application/current_prefix_forecast_opportunity.py +714 -0
  51. agent_evolve/application/decision_metric_projection.py +112 -0
  52. agent_evolve/application/derived_action_semantics.py +129 -0
  53. agent_evolve/application/detailed_evaluation.py +449 -0
  54. agent_evolve/application/earned_lineage.py +1011 -0
  55. agent_evolve/application/effective_choice_audit.py +484 -0
  56. agent_evolve/application/empirical_consequence_calibration.py +908 -0
  57. agent_evolve/application/evaluation_accounting.py +325 -0
  58. agent_evolve/application/evaluation_cache.py +199 -0
  59. agent_evolve/application/evaluation_escrow.py +547 -0
  60. agent_evolve/application/evaluation_recourse.py +253 -0
  61. agent_evolve/application/event_recorder.py +151 -0
  62. agent_evolve/application/evolution_campaign.py +1840 -0
  63. agent_evolve/application/executable_hypothesis.py +323 -0
  64. agent_evolve/application/factorial_branch_pilot.py +772 -0
  65. agent_evolve/application/finite_acquisition_capacity_recourse.py +672 -0
  66. agent_evolve/application/finite_acquisition_residual_expert.py +373 -0
  67. agent_evolve/application/finite_acquisition_variation_envelope.py +802 -0
  68. agent_evolve/application/finite_action_hypothesis_semantics.py +446 -0
  69. agent_evolve/application/finite_action_selection.py +188 -0
  70. agent_evolve/application/finite_action_set.py +306 -0
  71. agent_evolve/application/finite_action_transition.py +537 -0
  72. agent_evolve/application/finite_variation_eligibility.py +296 -0
  73. agent_evolve/application/forecast_geometry_portfolio.py +799 -0
  74. agent_evolve/application/forecast_opportunity_shadow_calibration.py +316 -0
  75. agent_evolve/application/front_proximity_admission.py +311 -0
  76. agent_evolve/application/front_proximity_parent_basis.py +458 -0
  77. agent_evolve/application/frozen_hurdle_score.py +659 -0
  78. agent_evolve/application/g3_causal_screen.py +2257 -0
  79. agent_evolve/application/g3_causal_validation.py +1046 -0
  80. agent_evolve/application/g3_postseal_curation.py +818 -0
  81. agent_evolve/application/gated_agentic_generator.py +205 -0
  82. agent_evolve/application/generation_feedback.py +293 -0
  83. agent_evolve/application/generative_proposal_journal.py +185 -0
  84. agent_evolve/application/geometry_conditional_elasticity.py +453 -0
  85. agent_evolve/application/global_wave_action_allocation.py +1151 -0
  86. agent_evolve/application/head_mass_conditional_seat.py +268 -0
  87. agent_evolve/application/identifiable_reflection_evidence.py +1147 -0
  88. agent_evolve/application/identifiable_reflection_learning.py +395 -0
  89. agent_evolve/application/identifiable_reflection_request.py +364 -0
  90. agent_evolve/application/in_memory_residual_archive.py +341 -0
  91. agent_evolve/application/insight_memory.py +1804 -0
  92. agent_evolve/application/live_runtime_manifest.py +758 -0
  93. agent_evolve/application/llm_task_queue.py +769 -0
  94. agent_evolve/application/matched_finite_action_block.py +409 -0
  95. agent_evolve/application/materialized_action_broker.py +2328 -0
  96. agent_evolve/application/materialized_action_constraints.py +83 -0
  97. agent_evolve/application/materialized_variation.py +211 -0
  98. agent_evolve/application/multi_option_evolution.py +1536 -0
  99. agent_evolve/application/outcome_adaptive_action_racing.py +2827 -0
  100. agent_evolve/application/outcome_adaptive_residual_campaign_runtime.py +580 -0
  101. agent_evolve/application/outcome_adaptive_residual_portfolio_evolution.py +3671 -0
  102. agent_evolve/application/outcome_conditioned_portfolio_selection.py +1374 -0
  103. agent_evolve/application/outcome_relation.py +193 -0
  104. agent_evolve/application/paired_allocation_comparison.py +241 -0
  105. agent_evolve/application/paired_block_schedule.py +127 -0
  106. agent_evolve/application/parent_measurement.py +226 -0
  107. agent_evolve/application/pareto_archive.py +811 -0
  108. agent_evolve/application/portfolio_campaign_runtime.py +4739 -0
  109. agent_evolve/application/portfolio_evolution.py +2950 -0
  110. agent_evolve/application/portfolio_hypothesis_observations.py +814 -0
  111. agent_evolve/application/portfolio_memory_attribution.py +581 -0
  112. agent_evolve/application/portfolio_memory_dose.py +788 -0
  113. agent_evolve/application/portfolio_memory_matched_control.py +938 -0
  114. agent_evolve/application/portfolio_memory_transfer.py +297 -0
  115. agent_evolve/application/portfolio_optimization_memory.py +363 -0
  116. agent_evolve/application/portfolio_outcome_feedback.py +1613 -0
  117. agent_evolve/application/portfolio_projection.py +335 -0
  118. agent_evolve/application/portfolio_recombination.py +2032 -0
  119. agent_evolve/application/post_evolution_reflection.py +834 -0
  120. agent_evolve/application/postcommit_rank_authority.py +245 -0
  121. agent_evolve/application/precommitted_portfolio_racing.py +2762 -0
  122. agent_evolve/application/prequential_archive_opportunity_calibration.py +1154 -0
  123. agent_evolve/application/prequential_residual_exploration.py +343 -0
  124. agent_evolve/application/prequential_score_portfolio.py +954 -0
  125. agent_evolve/application/projections.py +292 -0
  126. agent_evolve/application/protected_action_committee.py +1027 -0
  127. agent_evolve/application/protected_branch_pilot.py +376 -0
  128. agent_evolve/application/protected_current_prefix_forecast_opportunity.py +552 -0
  129. agent_evolve/application/provider_replay.py +910 -0
  130. agent_evolve/application/rank_balanced_causal_pilot.py +1372 -0
  131. agent_evolve/application/recombination_residual_expert.py +403 -0
  132. agent_evolve/application/reflection_workflow.py +571 -0
  133. agent_evolve/application/region_conditional_credit.py +911 -0
  134. agent_evolve/application/residual_campaign_runtime.py +531 -0
  135. agent_evolve/application/residual_headroom_campaign_runtime.py +459 -0
  136. agent_evolve/application/residual_headroom_ledger.py +1544 -0
  137. agent_evolve/application/residual_learning_transaction.py +396 -0
  138. agent_evolve/application/residual_portfolio_evolution.py +1228 -0
  139. agent_evolve/application/residual_reachability.py +749 -0
  140. agent_evolve/application/residual_stage_credit.py +499 -0
  141. agent_evolve/application/same_prefix_paired_audit.py +1580 -0
  142. agent_evolve/application/semantic_coverage_score_portfolio.py +838 -0
  143. agent_evolve/application/sequential_lineage_allocation.py +1017 -0
  144. agent_evolve/application/sequential_market_replay.py +1395 -0
  145. agent_evolve/application/sequential_residual_campaign_runtime.py +305 -0
  146. agent_evolve/application/sequential_residual_portfolio_evolution.py +940 -0
  147. agent_evolve/application/single_score_action_allocation.py +299 -0
  148. agent_evolve/application/source_exposure_allocation.py +906 -0
  149. agent_evolve/application/staged_memory.py +210 -0
  150. agent_evolve/application/stratified_cold_start_allocation.py +732 -0
  151. agent_evolve/application/support_guarded_hurdle_score.py +549 -0
  152. agent_evolve/application/target_conditioned_action_forecast.py +595 -0
  153. agent_evolve/application/target_conditioned_campaign.py +566 -0
  154. agent_evolve/application/treatment_assignment.py +201 -0
  155. agent_evolve/application/trusted_objective_evidence.py +217 -0
  156. agent_evolve/application/two_stage_action_evolution.py +1131 -0
  157. agent_evolve/application/v8lite_allocation_policy.py +1083 -0
  158. agent_evolve/application/v9_candidate_policy.py +1303 -0
  159. agent_evolve/bootstrap.py +108 -0
  160. agent_evolve/campaign_presets.py +517 -0
  161. agent_evolve/campaign_profiles.py +452 -0
  162. agent_evolve/campaign_variation_topology.py +288 -0
  163. agent_evolve/campaign_workload.py +950 -0
  164. agent_evolve/cli.py +797 -0
  165. agent_evolve/contract.py +241 -0
  166. agent_evolve/core/__init__.py +91 -0
  167. agent_evolve/core/action_semantics.py +411 -0
  168. agent_evolve/core/authored.py +105 -0
  169. agent_evolve/core/formatting.py +286 -0
  170. agent_evolve/core/optimization_semantics.py +324 -0
  171. agent_evolve/core/problem.py +167 -0
  172. agent_evolve/core/results.py +323 -0
  173. agent_evolve/core/stats.py +70 -0
  174. agent_evolve/core/telemetry.py +100 -0
  175. agent_evolve/domain/__init__.py +89 -0
  176. agent_evolve/domain/artifact.py +162 -0
  177. agent_evolve/domain/durable_text.py +68 -0
  178. agent_evolve/domain/event.py +1454 -0
  179. agent_evolve/domain/finite_action_set.py +426 -0
  180. agent_evolve/domain/finite_variation.py +526 -0
  181. agent_evolve/domain/generative_emission.py +559 -0
  182. agent_evolve/domain/ids.py +163 -0
  183. agent_evolve/domain/inline_text.py +106 -0
  184. agent_evolve/domain/insight.py +27 -0
  185. agent_evolve/domain/lineage.py +737 -0
  186. agent_evolve/domain/llm_task_queue.py +960 -0
  187. agent_evolve/domain/outcome.py +96 -0
  188. agent_evolve/domain/patch.py +854 -0
  189. agent_evolve/domain/typed_json.py +542 -0
  190. agent_evolve/domain/variation_space.py +158 -0
  191. agent_evolve/driver.py +1014 -0
  192. agent_evolve/harness/__init__.py +29 -0
  193. agent_evolve/harness/base.py +242 -0
  194. agent_evolve/harness/directives.py +163 -0
  195. agent_evolve/harness/generative_seal.py +479 -0
  196. agent_evolve/harness/registry.py +41 -0
  197. agent_evolve/infrastructure/__init__.py +39 -0
  198. agent_evolve/infrastructure/artifacts/__init__.py +6 -0
  199. agent_evolve/infrastructure/artifacts/_verification.py +67 -0
  200. agent_evolve/infrastructure/artifacts/filesystem.py +343 -0
  201. agent_evolve/infrastructure/artifacts/in_memory.py +73 -0
  202. agent_evolve/infrastructure/asyncio_runtime.py +109 -0
  203. agent_evolve/infrastructure/authored_runtime.py +188 -0
  204. agent_evolve/infrastructure/authored_worker.py +171 -0
  205. agent_evolve/infrastructure/clock.py +53 -0
  206. agent_evolve/infrastructure/events/__init__.py +6 -0
  207. agent_evolve/infrastructure/events/_validation.py +89 -0
  208. agent_evolve/infrastructure/events/in_memory.py +56 -0
  209. agent_evolve/infrastructure/events/jsonl.py +193 -0
  210. agent_evolve/infrastructure/exception_provenance.py +215 -0
  211. agent_evolve/infrastructure/ids.py +118 -0
  212. agent_evolve/infrastructure/lineage_codec.py +1836 -0
  213. agent_evolve/infrastructure/outcome_adaptive_phase_journal.py +170 -0
  214. agent_evolve/infrastructure/residual_headroom_journal.py +221 -0
  215. agent_evolve/infrastructure/resource_lease.py +370 -0
  216. agent_evolve/infrastructure/sanitization/__init__.py +8 -0
  217. agent_evolve/infrastructure/sanitization/strict_json.py +484 -0
  218. agent_evolve/infrastructure/sequential_phase_journal.py +170 -0
  219. agent_evolve/infrastructure/stream_liveness.py +383 -0
  220. agent_evolve/infrastructure/subprocess_boundary.py +136 -0
  221. agent_evolve/integrations/__init__.py +1 -0
  222. agent_evolve/integrations/botorch/__init__.py +28 -0
  223. agent_evolve/integrations/botorch/finite_qlognehvi.py +190 -0
  224. agent_evolve/integrations/botorch/finite_qlognehvi_batch.py +155 -0
  225. agent_evolve/integrations/botorch/finite_qlognehvi_batch_identity.py +20 -0
  226. agent_evolve/integrations/botorch/finite_qlognehvi_batch_worker.py +55 -0
  227. agent_evolve/integrations/botorch/finite_qlognehvi_identity.py +22 -0
  228. agent_evolve/integrations/botorch/finite_qlognehvi_worker.py +55 -0
  229. agent_evolve/integrations/botorch/subprocess_qlognehvi.py +261 -0
  230. agent_evolve/integrations/botorch/subprocess_qlognehvi_batch.py +273 -0
  231. agent_evolve/integrations/completion.py +242 -0
  232. agent_evolve/integrations/pydantic_ai/__init__.py +441 -0
  233. agent_evolve/integrations/pydantic_ai/action_forecast.py +1068 -0
  234. agent_evolve/integrations/pydantic_ai/agentic_generator.py +2308 -0
  235. agent_evolve/integrations/pydantic_ai/async_generator.py +1604 -0
  236. agent_evolve/integrations/pydantic_ai/boundary_codec.py +1526 -0
  237. agent_evolve/integrations/pydantic_ai/calibrated_portfolio_campaign.py +756 -0
  238. agent_evolve/integrations/pydantic_ai/calibrated_portfolio_selection.py +7537 -0
  239. agent_evolve/integrations/pydantic_ai/campaign_acquisition.py +609 -0
  240. agent_evolve/integrations/pydantic_ai/execution_binding.py +138 -0
  241. agent_evolve/integrations/pydantic_ai/forecast_geometry_action_committee.py +217 -0
  242. agent_evolve/integrations/pydantic_ai/harness.py +159 -0
  243. agent_evolve/integrations/pydantic_ai/heterogeneous_model_execution.py +306 -0
  244. agent_evolve/integrations/pydantic_ai/hierarchical_residual_adaptive_semantic_view.py +179 -0
  245. agent_evolve/integrations/pydantic_ai/json_schema_dialect.py +108 -0
  246. agent_evolve/integrations/pydantic_ai/materialized_hierarchical_residual_expert.py +952 -0
  247. agent_evolve/integrations/pydantic_ai/materialized_portfolio_judge.py +520 -0
  248. agent_evolve/integrations/pydantic_ai/model_execution_profile.py +659 -0
  249. agent_evolve/integrations/pydantic_ai/outbound_request_manifest.py +1170 -0
  250. agent_evolve/integrations/pydantic_ai/portable_residual_consequence_features.py +575 -0
  251. agent_evolve/integrations/pydantic_ai/portfolio_selection.py +422 -0
  252. agent_evolve/integrations/pydantic_ai/progress_aware_openrouter.py +416 -0
  253. agent_evolve/integrations/pydantic_ai/provider_attempt_join.py +1523 -0
  254. agent_evolve/integrations/pydantic_ai/provider_free_calibrated_runner.py +607 -0
  255. agent_evolve/integrations/pydantic_ai/queued_runner.py +2634 -0
  256. agent_evolve/integrations/pydantic_ai/reconciled_residual_reachability.py +1417 -0
  257. agent_evolve/integrations/pydantic_ai/residual_forecast_geometry.py +445 -0
  258. agent_evolve/integrations/pydantic_ai/residual_reachability.py +674 -0
  259. agent_evolve/integrations/pydantic_ai/residual_semantic_cells.py +239 -0
  260. agent_evolve/integrations/pydantic_ai/sealed_output_replay.py +1068 -0
  261. agent_evolve/integrations/pydantic_ai/semantic_coverage_residual_portfolio.py +770 -0
  262. agent_evolve/integrations/pydantic_ai/semantic_decision_replay.py +383 -0
  263. agent_evolve/integrations/pydantic_ai/support_adaptive_residual_portfolio.py +135 -0
  264. agent_evolve/integrations/pydantic_ai/trusted_residual_prompt_context.py +143 -0
  265. agent_evolve/integrations/pydantic_ai/validated_openrouter_model.py +107 -0
  266. agent_evolve/integrations/pymoo_adapter.py +242 -0
  267. agent_evolve/policies/__init__.py +17 -0
  268. agent_evolve/policies/check.py +469 -0
  269. agent_evolve/policies/emit_scaffold.py +451 -0
  270. agent_evolve/policies/feedback/__init__.py +37 -0
  271. agent_evolve/policies/feedback/held_out_asn.py +1325 -0
  272. agent_evolve/policies/genetic.py +607 -0
  273. agent_evolve/policies/llm_backoff.py +183 -0
  274. agent_evolve/policies/llm_chooser.py +226 -0
  275. agent_evolve/policies/llm_generator.py +1760 -0
  276. agent_evolve/policies/llm_init.py +267 -0
  277. agent_evolve/policies/llm_operator.py +109 -0
  278. agent_evolve/policies/llm_prior.py +194 -0
  279. agent_evolve/policies/llm_surrogate.py +334 -0
  280. agent_evolve/policies/measurement_evidence.py +704 -0
  281. agent_evolve/policies/memory/__init__.py +223 -0
  282. agent_evolve/policies/memory/balanced_subset_blocks.py +707 -0
  283. agent_evolve/policies/memory/compatibility_matching.py +593 -0
  284. agent_evolve/policies/memory/global_falsification.py +1841 -0
  285. agent_evolve/policies/memory/prompt_shape.py +503 -0
  286. agent_evolve/policies/memory/randomized_subset.py +714 -0
  287. agent_evolve/policies/memory/staged_causal.py +1270 -0
  288. agent_evolve/policies/memory/treatment_compliance.py +759 -0
  289. agent_evolve/policies/objective_resolution/__init__.py +17 -0
  290. agent_evolve/policies/objective_resolution/fixed_grid.py +364 -0
  291. agent_evolve/policies/operator_portfolio.py +407 -0
  292. agent_evolve/policies/reguidance.py +1133 -0
  293. agent_evolve/policies/reward/__init__.py +83 -0
  294. agent_evolve/policies/reward/affine_candidate_consequence.py +156 -0
  295. agent_evolve/policies/reward/affine_candidate_consequence_3d.py +159 -0
  296. agent_evolve/policies/reward/affine_hypervolume.py +490 -0
  297. agent_evolve/policies/reward/affine_hypervolume_3d.py +567 -0
  298. agent_evolve/policies/reward/contextual_marginal_utility.py +318 -0
  299. agent_evolve/policies/reward/frozen_archive.py +360 -0
  300. agent_evolve/policies/reward/frozen_wave_archive.py +368 -0
  301. agent_evolve/policies/search_state.py +208 -0
  302. agent_evolve/policies/selection/__init__.py +345 -0
  303. agent_evolve/policies/selection/acquisition_certified_slate.py +684 -0
  304. agent_evolve/policies/selection/affine_frontier_context.py +330 -0
  305. agent_evolve/policies/selection/affine_frontier_target.py +473 -0
  306. agent_evolve/policies/selection/archive_elite.py +1346 -0
  307. agent_evolve/policies/selection/calibrated_portfolio_binding.py +640 -0
  308. agent_evolve/policies/selection/calibrated_slate.py +1394 -0
  309. agent_evolve/policies/selection/calibrated_slate_codec.py +579 -0
  310. agent_evolve/policies/selection/common_candidate_pool.py +685 -0
  311. agent_evolve/policies/selection/diagnostic_sampling.py +319 -0
  312. agent_evolve/policies/selection/disjoint_pairs.py +479 -0
  313. agent_evolve/policies/selection/elite_explorer.py +719 -0
  314. agent_evolve/policies/selection/finite_action.py +187 -0
  315. agent_evolve/policies/selection/finite_option_prompt_projection.py +377 -0
  316. agent_evolve/policies/selection/finite_palette_evidence.py +247 -0
  317. agent_evolve/policies/selection/forecast_calibration.py +922 -0
  318. agent_evolve/policies/selection/frontier_probe_slate.py +814 -0
  319. agent_evolve/policies/selection/frozen_archive_pairs.py +762 -0
  320. agent_evolve/policies/selection/full_support_slate.py +91 -0
  321. agent_evolve/policies/selection/meaningful_direction.py +240 -0
  322. agent_evolve/policies/selection/memory_dose_feasibility.py +259 -0
  323. agent_evolve/policies/selection/model_anchored_slate.py +826 -0
  324. agent_evolve/policies/selection/phenotype_recourse.py +979 -0
  325. agent_evolve/policies/selection/proposal_support.py +368 -0
  326. agent_evolve/policies/selection/random_portfolio.py +254 -0
  327. agent_evolve/policies/selection/regret_bounded_slate.py +1084 -0
  328. agent_evolve/policies/selection/residual_frontier.py +463 -0
  329. agent_evolve/policies/selection/residual_frontier_target.py +605 -0
  330. agent_evolve/policies/selection/structural_posterior_slate.py +1571 -0
  331. agent_evolve/policies/selection/target_conditioned_allocator.py +648 -0
  332. agent_evolve/policies/selection/target_conditioned_features.py +812 -0
  333. agent_evolve/policies/selection/target_conditioned_prequential.py +1527 -0
  334. agent_evolve/policies/selection/task_keyed_palette.py +906 -0
  335. agent_evolve/policies/semantics.py +147 -0
  336. agent_evolve/policies/structure.py +362 -0
  337. agent_evolve/policies/structured_output_budget.py +62 -0
  338. agent_evolve/policies/surrogate.py +696 -0
  339. agent_evolve/policies/variation/__init__.py +1 -0
  340. agent_evolve/policies/variation/compositional_finite_catalog.py +426 -0
  341. agent_evolve/policies/variation/crossover_inheritance.py +575 -0
  342. agent_evolve/policies/variation/disjoint_recombination.py +611 -0
  343. agent_evolve/policies/variation/exact_composition_capacity.py +214 -0
  344. agent_evolve/policies/variation/exact_parent_crossover.py +950 -0
  345. agent_evolve/policies/variation/multiscale_restart_catalog.py +372 -0
  346. agent_evolve/policies/variation/source_union_finite_catalog.py +403 -0
  347. agent_evolve/policies/variation/typed_patch.py +1981 -0
  348. agent_evolve/policies/weighted_prior.py +394 -0
  349. agent_evolve/ports/__init__.py +383 -0
  350. agent_evolve/ports/action_allocation.py +733 -0
  351. agent_evolve/ports/action_allocation_frame.py +1153 -0
  352. agent_evolve/ports/action_allocation_frame_commit.py +294 -0
  353. agent_evolve/ports/action_allocation_frame_commit_v3.py +432 -0
  354. agent_evolve/ports/action_allocation_frame_v3.py +995 -0
  355. agent_evolve/ports/action_forecast.py +1568 -0
  356. agent_evolve/ports/action_metric_projection.py +165 -0
  357. agent_evolve/ports/agentic_generator.py +1561 -0
  358. agent_evolve/ports/archive_context.py +136 -0
  359. agent_evolve/ports/artifact_sanitizer.py +44 -0
  360. agent_evolve/ports/artifact_store.py +225 -0
  361. agent_evolve/ports/clock.py +13 -0
  362. agent_evolve/ports/contextual_search_allocation.py +827 -0
  363. agent_evolve/ports/decision_metric_projection.py +258 -0
  364. agent_evolve/ports/event_store.py +55 -0
  365. agent_evolve/ports/executable_hypothesis.py +557 -0
  366. agent_evolve/ports/finite_acquisition.py +377 -0
  367. agent_evolve/ports/finite_acquisition_batch.py +296 -0
  368. agent_evolve/ports/finite_acquisition_batch_json.py +164 -0
  369. agent_evolve/ports/finite_acquisition_json.py +247 -0
  370. agent_evolve/ports/finite_acquisition_space.py +168 -0
  371. agent_evolve/ports/finite_action_selection.py +348 -0
  372. agent_evolve/ports/finite_action_set.py +256 -0
  373. agent_evolve/ports/frontier_target.py +396 -0
  374. agent_evolve/ports/generation_failure.py +43 -0
  375. agent_evolve/ports/hard_feasibility.py +233 -0
  376. agent_evolve/ports/id_factory.py +34 -0
  377. agent_evolve/ports/llm_task_queue.py +93 -0
  378. agent_evolve/ports/objective_resolution.py +419 -0
  379. agent_evolve/ports/paired_allocation_comparison.py +401 -0
  380. agent_evolve/ports/paired_block_schedule.py +475 -0
  381. agent_evolve/ports/parent_measurement.py +336 -0
  382. agent_evolve/ports/portfolio_memory_dose.py +643 -0
  383. agent_evolve/ports/portfolio_selection.py +3169 -0
  384. agent_evolve/ports/postcommit_rank_authority.py +467 -0
  385. agent_evolve/ports/presented_action_evidence.py +794 -0
  386. agent_evolve/ports/resource_lease.py +162 -0
  387. agent_evolve/ports/structured_generator.py +734 -0
  388. agent_evolve/ports/structured_output_budget.py +120 -0
  389. agent_evolve/ports/subprocess_boundary.py +138 -0
  390. agent_evolve/ports/treatment_assignment.py +466 -0
  391. agent_evolve/ports/variation_catalog.py +76 -0
  392. agent_evolve/ports/variation_source.py +226 -0
  393. agent_evolve/proposal_mode.py +157 -0
  394. agent_evolve/proposers/__init__.py +10 -0
  395. agent_evolve/proposers/random_proposer.py +188 -0
  396. agent_evolve/provider_accounting.py +163 -0
  397. agent_evolve/py.typed +0 -0
  398. agent_evolve/reference_method.py +1570 -0
  399. agent_evolve/session/__init__.py +11 -0
  400. agent_evolve/session/authorship.py +864 -0
  401. agent_evolve/session/evaluate.py +236 -0
  402. agent_evolve/session/fidelity.py +237 -0
  403. agent_evolve/session/genetic_loop.py +742 -0
  404. agent_evolve/session/loop.py +803 -0
  405. agent_evolve/session/screening.py +671 -0
  406. agent_evolve/settings.py +376 -0
  407. agent_evolve/workload_kit.py +368 -0
  408. agent_evolve/workload_prompt.py +398 -0
  409. agentevolve_optimizer-0.5.0.dist-info/METADATA +599 -0
  410. agentevolve_optimizer-0.5.0.dist-info/RECORD +414 -0
  411. agentevolve_optimizer-0.5.0.dist-info/WHEEL +5 -0
  412. agentevolve_optimizer-0.5.0.dist-info/entry_points.txt +2 -0
  413. agentevolve_optimizer-0.5.0.dist-info/licenses/LICENSE +21 -0
  414. agentevolve_optimizer-0.5.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1581 @@
1
+ """Calibrated positive-gain opportunity without adverse-scenario gating.
2
+
3
+ The forecast-opportunity challenger this module replaces required positive
4
+ gain in the ADVERSE scenario before it would recommend anything, so every
5
+ uncertain candidate abstained and uncertainty was mapped to zero acquisition
6
+ authority. This policy removes hard abstention: every eligible candidate
7
+ receives a defined score, which may be non-positive, and the horizon policy
8
+ decides exploit-versus-explore.
9
+
10
+ Evidence combination:
11
+
12
+ * forecast geometry — scenario quantile points are valued strictly against
13
+ the CURRENT ARCHIVE through an injected gain port, so parent-relative
14
+ improvement contributes nothing;
15
+ * prequential conversion evidence — per (engine, rank-band) conversion rates
16
+ from outcomes observed BEFORE the current decision, with hierarchical
17
+ Beta shrinkage cell -> engine -> global; and
18
+ * a configurable mixture of the two (log-odds for probabilities, arithmetic
19
+ for magnitudes).
20
+
21
+ ``score = p_positive_archive_gain * expected_positive_gain
22
+ - lambda_ * tail_risk
23
+ + beta * (future_seats_remaining / horizon_total)
24
+ * value_of_information``
25
+
26
+ Value of information is exactly zero when no future seat remains, preserving
27
+ the V7 terminal semantics that a terminal seat never purchases information.
28
+ The policy knows no workload, objective name, model, provider, or prompt.
29
+ """
30
+
31
+ from __future__ import annotations
32
+
33
+ import hashlib
34
+ import json
35
+ import math
36
+ import re
37
+ from dataclasses import dataclass, field
38
+ from typing import Protocol, runtime_checkable
39
+
40
+ from agent_evolve.application.rank_balanced_causal_pilot import (
41
+ rank_band_index,
42
+ )
43
+ from agent_evolve.domain.patch import require_sha256
44
+
45
+
46
+ CALIBRATED_POSITIVE_GAIN_OPPORTUNITY_POLICY_ID = (
47
+ "calibrated_positive_gain_opportunity"
48
+ )
49
+ CALIBRATED_POSITIVE_GAIN_OPPORTUNITY_POLICY_VERSION = 1
50
+ LOWER_QUANTILE_SCENARIO_ID = "p10"
51
+ CENTRAL_QUANTILE_SCENARIO_ID = "p50"
52
+ UPPER_QUANTILE_SCENARIO_ID = "p90"
53
+ _QUANTILE_SCENARIO_IDS = (
54
+ LOWER_QUANTILE_SCENARIO_ID,
55
+ CENTRAL_QUANTILE_SCENARIO_ID,
56
+ UPPER_QUANTILE_SCENARIO_ID,
57
+ )
58
+ _TOKEN = re.compile(r"^[a-z][a-z0-9_.:-]{0,255}$")
59
+ _DEFINITION_DOMAIN = (
60
+ b"agent-evolve:calibrated-positive-gain-policy:v1\x00"
61
+ )
62
+ _ARCHIVE_DOMAIN = (
63
+ b"agent-evolve:calibrated-positive-gain-archive:v1\x00"
64
+ )
65
+ _SCORE_DOMAIN = b"agent-evolve:calibrated-positive-gain-score:v1\x00"
66
+ _RANKING_DOMAIN = (
67
+ b"agent-evolve:calibrated-positive-gain-ranking:v1\x00"
68
+ )
69
+
70
+ ObjectivePoint = tuple[tuple[str, float], ...]
71
+
72
+
73
+ def _canonical_json(value: object) -> bytes:
74
+ return json.dumps(
75
+ value,
76
+ allow_nan=False,
77
+ ensure_ascii=True,
78
+ separators=(",", ":"),
79
+ sort_keys=True,
80
+ ).encode("ascii", errors="strict")
81
+
82
+
83
+ def _hash(domain: bytes, value: object) -> str:
84
+ return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
85
+
86
+
87
+ def _require_token(value: str, *, name: str) -> None:
88
+ if type(value) is not str or _TOKEN.fullmatch(value) is None:
89
+ raise ValueError(f"{name} must use the closed token grammar")
90
+
91
+
92
+ def _require_objective_point(value: ObjectivePoint, *, name: str) -> None:
93
+ if (
94
+ type(value) is not tuple
95
+ or not value
96
+ or value != tuple(sorted(value))
97
+ ):
98
+ raise ValueError(f"{name} must be non-empty and canonical")
99
+ metric_ids: list[str] = []
100
+ for item in value:
101
+ if type(item) is not tuple or len(item) != 2:
102
+ raise TypeError(f"{name} must contain metric pairs")
103
+ metric_id, metric_value = item
104
+ _require_token(metric_id, name=f"{name} metric_id")
105
+ if type(metric_value) is not float or not math.isfinite(metric_value):
106
+ raise TypeError(f"{name} values must be finite exact floats")
107
+ metric_ids.append(metric_id)
108
+ if len(metric_ids) != len(set(metric_ids)):
109
+ raise ValueError(f"{name} repeats a metric")
110
+
111
+
112
+ def _objective_point_record(value: ObjectivePoint) -> list[dict[str, object]]:
113
+ return [
114
+ {"metric_id": metric_id, "value_hex": metric_value.hex()}
115
+ for metric_id, metric_value in value
116
+ ]
117
+
118
+
119
+ def _point_values(
120
+ point: ObjectivePoint,
121
+ metric_ids: tuple[str, ...],
122
+ ) -> tuple[float, ...]:
123
+ mapping = dict(point)
124
+ if tuple(sorted(mapping)) != metric_ids:
125
+ raise ValueError("objective point uses a foreign metric frame")
126
+ return tuple(mapping[metric_id] for metric_id in metric_ids)
127
+
128
+
129
+ def non_dominated(
130
+ points: tuple[ObjectivePoint, ...],
131
+ metric_ids: tuple[str, ...],
132
+ ) -> tuple[tuple[float, ...], ...]:
133
+ """Exact minimization Pareto filter over an archive."""
134
+
135
+ raw = sorted({_point_values(point, metric_ids) for point in points})
136
+ return tuple(
137
+ point
138
+ for point in raw
139
+ if not any(
140
+ other != point
141
+ and all(
142
+ o <= p for o, p in zip(other, point, strict=True)
143
+ )
144
+ for other in raw
145
+ )
146
+ )
147
+
148
+
149
+ def chebyshev_excess(
150
+ anchor: ObjectivePoint,
151
+ archive_points: tuple[ObjectivePoint, ...],
152
+ metric_ids: tuple[str, ...],
153
+ ) -> float:
154
+ """Worst-axis improvement the anchor already holds over the front.
155
+
156
+ Minimization convention. For each non-dominated archive point the
157
+ anchor's worst-axis excess is ``max_j(anchor_j - front_j)``; the returned
158
+ value is the smallest such excess over the front. It is <= 0 exactly
159
+ when the anchor weakly dominates some front point (the anchor is at
160
+ least as good on every axis), and it grows with how much the anchor
161
+ would have to improve on its worst axis to reach the nearest front
162
+ point. The archive is Pareto-filtered first: dominated archive points
163
+ would otherwise lower the excess of anchors that no front point can
164
+ reach.
165
+
166
+ Scale-free in the sense that matters here: it lives in whatever frame
167
+ the archive lives in, and the policy consumes only its ORDER.
168
+ """
169
+
170
+ if not archive_points:
171
+ raise ValueError("archive_points must be non-empty")
172
+ values = _point_values(anchor, metric_ids)
173
+ best: float | None = None
174
+ for other in non_dominated(archive_points, metric_ids):
175
+ worst = max(a - b for a, b in zip(values, other, strict=True))
176
+ if best is None or worst < best:
177
+ best = worst
178
+ return float(best if best is not None else 0.0)
179
+
180
+
181
+ @runtime_checkable
182
+ class ArchiveConditionedGainPort(Protocol):
183
+ """Value one hypothetical objective point against an archive.
184
+
185
+ Implementations own objective senses, normalization, and reference
186
+ points. The returned value must be the exact non-negative marginal
187
+ archive utility of adding the point; a point dominated by the archive
188
+ must return exactly zero. Parents are never visible to this port, so
189
+ parent-relative improvement cannot leak into opportunity.
190
+ """
191
+
192
+ utility_id: str
193
+ utility_version: int
194
+ definition_sha256: str
195
+
196
+ def marginal_archive_gain(
197
+ self,
198
+ archive_points: tuple[ObjectivePoint, ...],
199
+ objective_point: ObjectivePoint,
200
+ ) -> float: ...
201
+
202
+
203
+ def validate_archive_conditioned_gain_port(
204
+ value: ArchiveConditionedGainPort,
205
+ ) -> tuple[str, int, str]:
206
+ if not isinstance(value, ArchiveConditionedGainPort):
207
+ raise TypeError(
208
+ "gain port must implement ArchiveConditionedGainPort"
209
+ )
210
+ identity = (
211
+ value.utility_id,
212
+ value.utility_version,
213
+ value.definition_sha256,
214
+ )
215
+ _require_token(identity[0], name="gain utility_id")
216
+ if type(identity[1]) is not int or identity[1] <= 0:
217
+ raise ValueError("gain utility_version must be positive")
218
+ require_sha256(identity[2], "gain utility definition_sha256")
219
+ return identity
220
+
221
+
222
+ @dataclass(frozen=True, slots=True)
223
+ class PositiveGainForecast:
224
+ """Sealed scenario-quantile forecast for one candidate."""
225
+
226
+ quantile_points: tuple[tuple[str, ObjectivePoint], ...]
227
+ reliability: float = 1.0
228
+
229
+ def __post_init__(self) -> None:
230
+ if (
231
+ type(self.quantile_points) is not tuple
232
+ or tuple(value[0] for value in self.quantile_points)
233
+ != tuple(sorted(_QUANTILE_SCENARIO_IDS))
234
+ ):
235
+ raise ValueError(
236
+ "quantile_points must cover exactly the canonical "
237
+ "p10/p50/p90 scenarios"
238
+ )
239
+ frames = set()
240
+ for scenario_id, point in self.quantile_points:
241
+ _require_objective_point(
242
+ point,
243
+ name=f"{scenario_id} objective point",
244
+ )
245
+ frames.add(tuple(metric_id for metric_id, _value in point))
246
+ if len(frames) != 1:
247
+ raise ValueError(
248
+ "all quantile points must share one objective frame"
249
+ )
250
+ if (
251
+ type(self.reliability) is not float
252
+ or not math.isfinite(self.reliability)
253
+ or not 0.0 <= self.reliability <= 1.0
254
+ ):
255
+ raise ValueError("reliability must lie in [0, 1]")
256
+
257
+ @classmethod
258
+ def from_parent_and_deltas(
259
+ cls,
260
+ *,
261
+ parent_point: ObjectivePoint,
262
+ quantile_deltas: tuple[tuple[str, tuple[float, ...]], ...],
263
+ reliability: float = 1.0,
264
+ ) -> PositiveGainForecast:
265
+ """Absolute scenario points from a parent point plus per-metric deltas.
266
+
267
+ A proposal-time self-report is a per-metric scenario DELTA against
268
+ the candidate's parent, so the absolute scenario point is the parent
269
+ point plus that delta. ``quantile_deltas`` pairs each metric with
270
+ one delta per canonical scenario, ordered exactly as
271
+ ``LOWER/CENTRAL/UPPER``; the deltas must live in the same frame as
272
+ ``parent_point``, and the arithmetic is a pure translation, so any
273
+ affine renormalization of the frame commutes with this constructor.
274
+
275
+ The only reason a self-report cannot become a forecast is missing
276
+ evidence: a candidate with no parent, or a report that does not cover
277
+ every metric, has no absolute point and must stay forecast-free. The
278
+ caller decides that by not calling this.
279
+ """
280
+
281
+ _require_objective_point(parent_point, name="parent_point")
282
+ if type(quantile_deltas) is not tuple:
283
+ raise TypeError("quantile_deltas must be an exact tuple")
284
+ deltas: dict[str, tuple[float, ...]] = {}
285
+ for item in quantile_deltas:
286
+ if type(item) is not tuple or len(item) != 2:
287
+ raise TypeError(
288
+ "quantile_deltas must pair a metric with its deltas"
289
+ )
290
+ metric_id, scenario_deltas = item
291
+ _require_token(metric_id, name="quantile delta metric_id")
292
+ if (
293
+ type(scenario_deltas) is not tuple
294
+ or len(scenario_deltas) != len(_QUANTILE_SCENARIO_IDS)
295
+ or any(
296
+ type(value) is not float or not math.isfinite(value)
297
+ for value in scenario_deltas
298
+ )
299
+ ):
300
+ raise TypeError(
301
+ "each metric needs one finite delta per scenario"
302
+ )
303
+ if metric_id in deltas:
304
+ raise ValueError("quantile_deltas repeat a metric")
305
+ deltas[metric_id] = scenario_deltas
306
+ parent = dict(parent_point)
307
+ if tuple(sorted(deltas)) != tuple(sorted(parent)):
308
+ raise ValueError(
309
+ "quantile_deltas must cover the parent's exact frame"
310
+ )
311
+ return cls(
312
+ quantile_points=tuple(
313
+ (
314
+ scenario_id,
315
+ tuple(
316
+ sorted(
317
+ (
318
+ metric_id,
319
+ parent[metric_id]
320
+ + deltas[metric_id][index],
321
+ )
322
+ for metric_id in parent
323
+ )
324
+ ),
325
+ )
326
+ for index, scenario_id in enumerate(
327
+ _QUANTILE_SCENARIO_IDS
328
+ )
329
+ ),
330
+ reliability=float(reliability),
331
+ )
332
+
333
+ def point(self, scenario_id: str) -> ObjectivePoint:
334
+ for value_id, point in self.quantile_points:
335
+ if value_id == scenario_id:
336
+ return point
337
+ raise ValueError("forecast omits the requested scenario")
338
+
339
+ def to_record(self) -> dict[str, object]:
340
+ self.__post_init__()
341
+ return {
342
+ "quantile_points": [
343
+ {
344
+ "scenario_id": scenario_id,
345
+ "objective_point": _objective_point_record(point),
346
+ }
347
+ for scenario_id, point in self.quantile_points
348
+ ],
349
+ "reliability_hex": self.reliability.hex(),
350
+ }
351
+
352
+
353
+ @dataclass(frozen=True, slots=True)
354
+ class PositiveGainCandidate:
355
+ """Outcome-blind view of one unevaluated eligible candidate."""
356
+
357
+ action_sha256: str
358
+ engine_id: str
359
+ native_rank: int
360
+ lane_size: int
361
+ forecast: PositiveGainForecast | None = None
362
+ frozen_score: float | None = None
363
+ #: The objective point this action was derived from, known at proposal
364
+ #: time and never an outcome of this action. Absent when the action has
365
+ #: no parent geometry (a global acquisition lane, say).
366
+ anchor_point: ObjectivePoint | None = None
367
+
368
+ def __post_init__(self) -> None:
369
+ require_sha256(self.action_sha256, "action_sha256")
370
+ _require_token(self.engine_id, name="engine_id")
371
+ if (
372
+ type(self.native_rank) is not int
373
+ or type(self.lane_size) is not int
374
+ or self.native_rank <= 0
375
+ or self.lane_size <= 0
376
+ or self.native_rank > self.lane_size
377
+ ):
378
+ raise ValueError("native rank must fit the positive lane size")
379
+ if self.forecast is not None:
380
+ if type(self.forecast) is not PositiveGainForecast:
381
+ raise TypeError("forecast must be exact or None")
382
+ self.forecast.__post_init__()
383
+ if self.frozen_score is not None and (
384
+ type(self.frozen_score) is not float
385
+ or not math.isfinite(self.frozen_score)
386
+ or not 0.0 <= self.frozen_score <= 1.0
387
+ ):
388
+ raise ValueError("frozen_score must lie in [0, 1] or be None")
389
+ if self.anchor_point is not None:
390
+ _require_objective_point(self.anchor_point, name="anchor_point")
391
+
392
+ def to_record(self) -> dict[str, object]:
393
+ self.__post_init__()
394
+ return {
395
+ "action_sha256": self.action_sha256,
396
+ "engine_id": self.engine_id,
397
+ "native_rank": self.native_rank,
398
+ "lane_size": self.lane_size,
399
+ "forecast": (
400
+ None
401
+ if self.forecast is None
402
+ else self.forecast.to_record()
403
+ ),
404
+ "frozen_score_hex": (
405
+ None
406
+ if self.frozen_score is None
407
+ else self.frozen_score.hex()
408
+ ),
409
+ "anchor_point": (
410
+ None
411
+ if self.anchor_point is None
412
+ else _objective_point_record(self.anchor_point)
413
+ ),
414
+ }
415
+
416
+
417
+ @dataclass(frozen=True, slots=True)
418
+ class ObservedConversionOutcome:
419
+ """One real outcome observed strictly before the current decision."""
420
+
421
+ observation_ordinal: int
422
+ engine_id: str
423
+ native_rank: int
424
+ lane_size: int
425
+ feasible: bool
426
+ marginal_archive_gain: float
427
+
428
+ def __post_init__(self) -> None:
429
+ if (
430
+ type(self.observation_ordinal) is not int
431
+ or self.observation_ordinal <= 0
432
+ ):
433
+ raise ValueError("observation_ordinal must be positive")
434
+ _require_token(self.engine_id, name="engine_id")
435
+ if (
436
+ type(self.native_rank) is not int
437
+ or type(self.lane_size) is not int
438
+ or self.native_rank <= 0
439
+ or self.lane_size <= 0
440
+ or self.native_rank > self.lane_size
441
+ ):
442
+ raise ValueError("native rank must fit the positive lane size")
443
+ if type(self.feasible) is not bool:
444
+ raise TypeError("feasible must be exact")
445
+ if (
446
+ type(self.marginal_archive_gain) is not float
447
+ or not math.isfinite(self.marginal_archive_gain)
448
+ or self.marginal_archive_gain < 0.0
449
+ ):
450
+ raise ValueError(
451
+ "marginal_archive_gain must be finite and non-negative"
452
+ )
453
+ if not self.feasible and self.marginal_archive_gain != 0.0:
454
+ raise ValueError("an infeasible outcome cannot contribute gain")
455
+
456
+ @property
457
+ def positive(self) -> bool:
458
+ return self.marginal_archive_gain > 0.0
459
+
460
+ def to_record(self) -> dict[str, object]:
461
+ self.__post_init__()
462
+ return {
463
+ "observation_ordinal": self.observation_ordinal,
464
+ "engine_id": self.engine_id,
465
+ "native_rank": self.native_rank,
466
+ "lane_size": self.lane_size,
467
+ "feasible": self.feasible,
468
+ "positive": self.positive,
469
+ "marginal_archive_gain_hex": (
470
+ self.marginal_archive_gain.hex()
471
+ ),
472
+ }
473
+
474
+
475
+ @dataclass(frozen=True, slots=True)
476
+ class CalibratedPositiveGainScore:
477
+ """Complete score card for one eligible candidate; never abstains."""
478
+
479
+ action_sha256: str
480
+ p_feasible: float
481
+ p_positive_archive_gain: float
482
+ expected_positive_gain: float
483
+ tail_risk: float
484
+ value_of_information: float
485
+ uncertainty: float
486
+ effective_sample_size: float
487
+ score: float
488
+ forecast_probability: float | None
489
+ forecast_magnitude: float | None
490
+ forecast_nondominated_fraction: float | None
491
+ conversion_probability: float
492
+ conversion_magnitude: float
493
+ frozen_score_probability: float | None
494
+ frozen_evidence_weight: float
495
+ #: Chebyshev excess of the candidate's anchor over the current
496
+ #: archive front; smaller means the parent sits nearer the front.
497
+ anchor_excess: float | None = None
498
+ #: Smallest max-norm distance from the candidate's anchor to any anchor
499
+ #: this market already bought; ``inf`` when nothing is bought yet.
500
+ anchor_dispersion: float | None = None
501
+ score_sha256: str = field(init=False)
502
+
503
+ def __post_init__(self) -> None:
504
+ require_sha256(self.action_sha256, "action_sha256")
505
+ for name in ("p_feasible", "p_positive_archive_gain"):
506
+ value = getattr(self, name)
507
+ if (
508
+ type(value) is not float
509
+ or not math.isfinite(value)
510
+ or not 0.0 <= value <= 1.0
511
+ ):
512
+ raise ValueError(f"{name} must lie in [0, 1]")
513
+ for name in (
514
+ "expected_positive_gain",
515
+ "tail_risk",
516
+ "value_of_information",
517
+ "uncertainty",
518
+ "conversion_magnitude",
519
+ ):
520
+ value = getattr(self, name)
521
+ if type(value) is not float or not math.isfinite(value) or value < 0.0:
522
+ raise ValueError(f"{name} must be finite and non-negative")
523
+ if (
524
+ type(self.effective_sample_size) is not float
525
+ or not math.isfinite(self.effective_sample_size)
526
+ or self.effective_sample_size <= 0.0
527
+ ):
528
+ raise ValueError("effective_sample_size must be positive")
529
+ if type(self.score) is not float or not math.isfinite(self.score):
530
+ raise ValueError("score must be a finite float")
531
+ forecast_fields = (
532
+ self.forecast_probability,
533
+ self.forecast_magnitude,
534
+ self.forecast_nondominated_fraction,
535
+ )
536
+ if any(value is None for value in forecast_fields) != all(
537
+ value is None for value in forecast_fields
538
+ ):
539
+ raise ValueError(
540
+ "forecast components must be jointly present or absent"
541
+ )
542
+ for value in forecast_fields:
543
+ if value is not None and (
544
+ type(value) is not float
545
+ or not math.isfinite(value)
546
+ or value < 0.0
547
+ ):
548
+ raise ValueError(
549
+ "forecast components must be finite and non-negative"
550
+ )
551
+ if (
552
+ type(self.conversion_probability) is not float
553
+ or not math.isfinite(self.conversion_probability)
554
+ or not 0.0 <= self.conversion_probability <= 1.0
555
+ ):
556
+ raise ValueError("conversion_probability must lie in [0, 1]")
557
+ if self.frozen_score_probability is not None and (
558
+ type(self.frozen_score_probability) is not float
559
+ or not math.isfinite(self.frozen_score_probability)
560
+ or not 0.0 <= self.frozen_score_probability <= 1.0
561
+ ):
562
+ raise ValueError(
563
+ "frozen_score_probability must lie in [0, 1] or be None"
564
+ )
565
+ if (
566
+ type(self.frozen_evidence_weight) is not float
567
+ or not math.isfinite(self.frozen_evidence_weight)
568
+ or not 0.0 <= self.frozen_evidence_weight <= 1.0
569
+ ):
570
+ raise ValueError("frozen_evidence_weight must lie in [0, 1]")
571
+ if (
572
+ self.frozen_score_probability is None
573
+ and self.frozen_evidence_weight != 0.0
574
+ ):
575
+ raise ValueError(
576
+ "frozen evidence weight requires a frozen score"
577
+ )
578
+ if self.anchor_excess is not None and (
579
+ type(self.anchor_excess) is not float
580
+ or not math.isfinite(self.anchor_excess)
581
+ ):
582
+ raise ValueError("anchor_excess must be finite or None")
583
+ if self.anchor_dispersion is not None and (
584
+ type(self.anchor_dispersion) is not float
585
+ or math.isnan(self.anchor_dispersion)
586
+ or self.anchor_dispersion < 0.0
587
+ ):
588
+ raise ValueError(
589
+ "anchor_dispersion must be non-negative or None"
590
+ )
591
+ if (self.anchor_excess is None) != (
592
+ self.anchor_dispersion is None
593
+ ):
594
+ raise ValueError(
595
+ "anchor geometry must be jointly present or absent"
596
+ )
597
+ object.__setattr__(
598
+ self,
599
+ "score_sha256",
600
+ _hash(_SCORE_DOMAIN, self._unsigned_record()),
601
+ )
602
+
603
+ def _unsigned_record(self) -> dict[str, object]:
604
+ # The anchor keys are emitted only when the archive-geometry
605
+ # channel is active, so a score card produced with the channel off
606
+ # keeps its pre-existing bytes and hash exactly.
607
+ anchor: dict[str, object] = (
608
+ {}
609
+ if self.anchor_excess is None
610
+ else {
611
+ "anchor_excess_hex": self.anchor_excess.hex(),
612
+ "anchor_dispersion_hex": (
613
+ "inf"
614
+ if math.isinf(self.anchor_dispersion)
615
+ else self.anchor_dispersion.hex()
616
+ ),
617
+ }
618
+ )
619
+ return {
620
+ **anchor,
621
+ "schema_version": 1,
622
+ "action_sha256": self.action_sha256,
623
+ "p_feasible_hex": self.p_feasible.hex(),
624
+ "p_positive_archive_gain_hex": (
625
+ self.p_positive_archive_gain.hex()
626
+ ),
627
+ "expected_positive_gain_hex": (
628
+ self.expected_positive_gain.hex()
629
+ ),
630
+ "tail_risk_hex": self.tail_risk.hex(),
631
+ "value_of_information_hex": (
632
+ self.value_of_information.hex()
633
+ ),
634
+ "uncertainty_hex": self.uncertainty.hex(),
635
+ "effective_sample_size_hex": (
636
+ self.effective_sample_size.hex()
637
+ ),
638
+ "score_hex": self.score.hex(),
639
+ "forecast_probability_hex": (
640
+ None
641
+ if self.forecast_probability is None
642
+ else self.forecast_probability.hex()
643
+ ),
644
+ "forecast_magnitude_hex": (
645
+ None
646
+ if self.forecast_magnitude is None
647
+ else self.forecast_magnitude.hex()
648
+ ),
649
+ "forecast_nondominated_fraction_hex": (
650
+ None
651
+ if self.forecast_nondominated_fraction is None
652
+ else self.forecast_nondominated_fraction.hex()
653
+ ),
654
+ "conversion_probability_hex": (
655
+ self.conversion_probability.hex()
656
+ ),
657
+ "conversion_magnitude_hex": (
658
+ self.conversion_magnitude.hex()
659
+ ),
660
+ "frozen_score_probability_hex": (
661
+ None
662
+ if self.frozen_score_probability is None
663
+ else self.frozen_score_probability.hex()
664
+ ),
665
+ "frozen_evidence_weight_hex": (
666
+ self.frozen_evidence_weight.hex()
667
+ ),
668
+ "abstained": False,
669
+ }
670
+
671
+ def to_record(self) -> dict[str, object]:
672
+ self.__post_init__()
673
+ return {
674
+ **self._unsigned_record(),
675
+ "score_sha256": self.score_sha256,
676
+ }
677
+
678
+
679
+ @dataclass(frozen=True, slots=True)
680
+ class CalibratedPositiveGainRanking:
681
+ """One cutoff-bound, always-defined ranking of eligible candidates."""
682
+
683
+ policy_id: str
684
+ policy_version: int
685
+ policy_definition_sha256: str
686
+ archive_sha256: str
687
+ future_seats_remaining: int
688
+ horizon_total: int
689
+ scores: tuple[CalibratedPositiveGainScore, ...]
690
+ ranked_action_sha256s: tuple[str, ...]
691
+ ranking_sha256: str = field(init=False)
692
+
693
+ def __post_init__(self) -> None:
694
+ _require_token(self.policy_id, name="policy_id")
695
+ if type(self.policy_version) is not int or self.policy_version <= 0:
696
+ raise ValueError("policy_version must be positive")
697
+ require_sha256(
698
+ self.policy_definition_sha256,
699
+ "policy_definition_sha256",
700
+ )
701
+ require_sha256(self.archive_sha256, "archive_sha256")
702
+ if (
703
+ type(self.future_seats_remaining) is not int
704
+ or type(self.horizon_total) is not int
705
+ or self.horizon_total <= 0
706
+ or not 0 <= self.future_seats_remaining <= self.horizon_total
707
+ ):
708
+ raise ValueError(
709
+ "future seats must fit the positive horizon total"
710
+ )
711
+ if (
712
+ type(self.scores) is not tuple
713
+ or not self.scores
714
+ or any(
715
+ type(value) is not CalibratedPositiveGainScore
716
+ for value in self.scores
717
+ )
718
+ ):
719
+ raise TypeError("scores must contain exact score cards")
720
+ for value in self.scores:
721
+ value.__post_init__()
722
+ action_ids = tuple(value.action_sha256 for value in self.scores)
723
+ if action_ids != tuple(sorted(set(action_ids))):
724
+ raise ValueError("scores must be action-canonical")
725
+ if (
726
+ type(self.ranked_action_sha256s) is not tuple
727
+ or tuple(sorted(self.ranked_action_sha256s)) != action_ids
728
+ ):
729
+ raise ValueError(
730
+ "ranking must order the exact eligible market"
731
+ )
732
+ object.__setattr__(
733
+ self,
734
+ "ranking_sha256",
735
+ _hash(_RANKING_DOMAIN, self._unsigned_record()),
736
+ )
737
+
738
+ def score_for(self, action_sha256: str) -> CalibratedPositiveGainScore:
739
+ for value in self.scores:
740
+ if value.action_sha256 == action_sha256:
741
+ return value
742
+ raise ValueError("action is outside the ranked market")
743
+
744
+ def _unsigned_record(self) -> dict[str, object]:
745
+ return {
746
+ "schema_version": 1,
747
+ "policy": {
748
+ "policy_id": self.policy_id,
749
+ "policy_version": self.policy_version,
750
+ "definition_sha256": self.policy_definition_sha256,
751
+ },
752
+ "archive_sha256": self.archive_sha256,
753
+ "future_seats_remaining": self.future_seats_remaining,
754
+ "horizon_total": self.horizon_total,
755
+ "score_sha256s": [
756
+ value.score_sha256 for value in self.scores
757
+ ],
758
+ "ranked_action_sha256s": list(self.ranked_action_sha256s),
759
+ "hard_abstention": False,
760
+ "eligible_candidate_outcomes_observed": False,
761
+ }
762
+
763
+ def to_record(self, *, include_scores: bool = False) -> dict[str, object]:
764
+ self.__post_init__()
765
+ result = {
766
+ **self._unsigned_record(),
767
+ "ranking_sha256": self.ranking_sha256,
768
+ }
769
+ if include_scores:
770
+ result["scores"] = [
771
+ value.to_record() for value in self.scores
772
+ ]
773
+ return result
774
+
775
+
776
+ def _beta_posterior_mean(
777
+ *,
778
+ prior_mean: float,
779
+ prior_strength: float,
780
+ successes: float,
781
+ failures: float,
782
+ ) -> float:
783
+ return (prior_strength * prior_mean + successes) / (
784
+ prior_strength + successes + failures
785
+ )
786
+
787
+
788
+ def _shrunk_magnitude(
789
+ *,
790
+ prior_mean: float,
791
+ prior_strength: float,
792
+ positive_gain_sum: float,
793
+ positive_count: float,
794
+ ) -> float:
795
+ return (prior_strength * prior_mean + positive_gain_sum) / (
796
+ prior_strength + positive_count
797
+ )
798
+
799
+
800
+ @dataclass(frozen=True, slots=True)
801
+ class _ConversionCellEvidence:
802
+ observation_count: float
803
+ positive_count: float
804
+ feasible_count: float
805
+ positive_gain_sum: float
806
+
807
+
808
+ @dataclass(frozen=True, slots=True)
809
+ class CalibratedPositiveGainOpportunityPolicy:
810
+ """Rank every eligible candidate by calibrated positive archive gain."""
811
+
812
+ archive_gain_utility: ArchiveConditionedGainPort = field(
813
+ repr=False,
814
+ compare=False,
815
+ )
816
+ lambda_: float = 1.0
817
+ beta: float = 1.0
818
+ mixture_weight: float = 0.5
819
+ prior_strength: float = 2.0
820
+ band_count: int = 3
821
+ reference_gain_scale: float = 1.0e-4
822
+ root_prior_probability: float = 0.5
823
+ probability_floor: float = 1.0e-6
824
+ scenario_weights: tuple[float, float, float] = (0.25, 0.5, 0.25)
825
+ # The V70 census measured the frozen cross-campaign score as
826
+ # uninformative (pooled Spearman with positivity about -0.01) while
827
+ # native rank was informative, so frozen evidence is a WEAK log-odds
828
+ # feature: a small default weight, and no effect at all unless the
829
+ # frozen fit's training history covers enough runs.
830
+ frozen_score_weight: float = 0.125
831
+ frozen_score_minimum_training_runs: int = 10
832
+ # When cell posteriors tie exactly (no forecasts, shared evidence
833
+ # cell), break ties by the engine's own native-rank order instead
834
+ # of the arbitrary action hash. Off by default so the r1 ranking
835
+ # and definition hash are preserved bit-for-bit.
836
+ within_cell_rank_tie_break: bool = False
837
+ # ARCHIVE-GEOMETRY TIE-BREAK. The jul28 diagnosis measured that with
838
+ # forecasts absent, every probability input to this score is a per
839
+ # (engine, rank-band) CELL constant: the score took 1.4 to 2.8 distinct
840
+ # values over markets of 37 to 64 members, 46-83% of every market tied
841
+ # with the argmax, and 23 of 28 live seats were therefore chosen by the
842
+ # TIE-BREAK and not by the score. The tie-break in use was native-rank
843
+ # quality, a measured non-feature (AUC 0.552), and the seats it bought
844
+ # were geometrically redundant: the panel converted only 30% of the
845
+ # individual gain it purchased into union gain, against a uniform
846
+ # draw's 68%.
847
+ #
848
+ # This flag replaces that tie-break with archive geometry, in the exact
849
+ # place the decision is actually taken. Among candidates the score
850
+ # cannot separate, prefer, in order:
851
+ #
852
+ # 1. the anchor FARTHEST from the anchors this market already bought
853
+ # (max-min Chebyshev dispersion). Provably submodular: a second
854
+ # seat on an identical anchor has dispersion exactly zero and is
855
+ # taken last, which is the redundancy the census measured; and
856
+ # 2. failing that -- nothing bought yet, or an exact dispersion tie --
857
+ # the anchor NEAREST the current archive front (smallest Chebyshev
858
+ # excess), which is the strongest outcome-blind predictor measured
859
+ # (pooled AUC 0.732 held-out, 0.700 on the replay corpus).
860
+ #
861
+ # Both quantities are pure archive geometry consumed as an ORDER only:
862
+ # no metric name, no unit, no absolute threshold, no workload constant,
863
+ # and invariant to any monotone rescaling of the objective frame. A
864
+ # candidate with no anchor takes the market's median position on both,
865
+ # so an anchorless lane is never systematically evicted or protected.
866
+ # Off by default so the r1 and r2 rankings and definition hashes are
867
+ # preserved bit-for-bit.
868
+ anchor_geometry_tie_break: bool = False
869
+ # DOWNSIDE TAIL RISK. The forecast branch already prices risk as an
870
+ # expected SHORTFALL -- ``sum_s w_s * max(0, central - gain_s)`` -- which
871
+ # is zero when the scenarios agree and never scales with the magnitude on
872
+ # its own. The conversion branch instead priced it as ``(1 - p) * M``,
873
+ # which is not a shortfall: substituting it into
874
+ # ``score = p*M - lambda*tail_risk`` collapses the score to
875
+ # ``M * (2p - 1)`` at ``lambda = 1``, so the magnitude channel is
876
+ # INVERTED for every candidate with ``p < 0.5`` and a cell that has
877
+ # demonstrated larger gains scores lower. The jul28 census measured that
878
+ # exposure at 18.1% of multiplier scored candidates.
879
+ #
880
+ # This flag applies the forecast branch's own definition to the
881
+ # conversion branch's own two-point scenario set -- gain ``M`` with
882
+ # probability ``p``, gain ``0`` with probability ``1 - p``, central
883
+ # outcome the expected gain ``p*M`` -- which evaluates in closed form to
884
+ # ``p * (1 - p) * M``. The score becomes ``p*M*(1 - lambda*(1 - p))``,
885
+ # non-decreasing in both ``p`` and ``M`` at ``lambda <= 1``: risk still
886
+ # penalises an uncertain candidate, but it can no longer reverse the
887
+ # sign of the magnitude channel. No new constant, no threshold, and the
888
+ # forecast branch's shortfall is untouched. Off by default so the r1,
889
+ # r2 and r3 rankings and definition hashes are preserved bit-for-bit.
890
+ downside_shortfall_tail_risk: bool = False
891
+ policy_id: str = CALIBRATED_POSITIVE_GAIN_OPPORTUNITY_POLICY_ID
892
+ policy_version: int = (
893
+ CALIBRATED_POSITIVE_GAIN_OPPORTUNITY_POLICY_VERSION
894
+ )
895
+ definition_sha256: str = field(init=False)
896
+
897
+ def __post_init__(self) -> None:
898
+ utility_identity = validate_archive_conditioned_gain_port(
899
+ self.archive_gain_utility
900
+ )
901
+ for name in ("lambda_", "beta"):
902
+ value = getattr(self, name)
903
+ if type(value) is not float or not math.isfinite(value) or value < 0.0:
904
+ raise ValueError(f"{name} must be finite and non-negative")
905
+ for name in (
906
+ "mixture_weight",
907
+ "root_prior_probability",
908
+ "frozen_score_weight",
909
+ ):
910
+ value = getattr(self, name)
911
+ if (
912
+ type(value) is not float
913
+ or not math.isfinite(value)
914
+ or not 0.0 <= value <= 1.0
915
+ ):
916
+ raise ValueError(f"{name} must lie in [0, 1]")
917
+ if (
918
+ type(self.frozen_score_minimum_training_runs) is not int
919
+ or self.frozen_score_minimum_training_runs < 0
920
+ ):
921
+ raise ValueError(
922
+ "frozen_score_minimum_training_runs must be non-negative"
923
+ )
924
+ for name in (
925
+ "within_cell_rank_tie_break",
926
+ "anchor_geometry_tie_break",
927
+ "downside_shortfall_tail_risk",
928
+ ):
929
+ if type(getattr(self, name)) is not bool:
930
+ raise TypeError(f"{name} must be exact")
931
+ for name in ("prior_strength", "reference_gain_scale"):
932
+ value = getattr(self, name)
933
+ if type(value) is not float or not math.isfinite(value) or value <= 0.0:
934
+ raise ValueError(f"{name} must be positive")
935
+ if type(self.band_count) is not int or self.band_count <= 0:
936
+ raise ValueError("band_count must be positive")
937
+ if (
938
+ type(self.probability_floor) is not float
939
+ or not math.isfinite(self.probability_floor)
940
+ or not 0.0 < self.probability_floor < 0.5
941
+ ):
942
+ raise ValueError("probability_floor must lie in (0, 0.5)")
943
+ if (
944
+ type(self.scenario_weights) is not tuple
945
+ or len(self.scenario_weights) != 3
946
+ or any(
947
+ type(value) is not float
948
+ or not math.isfinite(value)
949
+ or value <= 0.0
950
+ for value in self.scenario_weights
951
+ )
952
+ or math.fsum(self.scenario_weights) != 1.0
953
+ ):
954
+ raise ValueError(
955
+ "scenario_weights must be three positive floats "
956
+ "summing to exactly one"
957
+ )
958
+ _require_token(self.policy_id, name="policy_id")
959
+ if (
960
+ self.policy_id
961
+ != CALIBRATED_POSITIVE_GAIN_OPPORTUNITY_POLICY_ID
962
+ or self.policy_version
963
+ != CALIBRATED_POSITIVE_GAIN_OPPORTUNITY_POLICY_VERSION
964
+ ):
965
+ raise ValueError("policy identity is immutable")
966
+ definition = {
967
+ "schema_version": 1,
968
+ "policy_id": self.policy_id,
969
+ "policy_version": self.policy_version,
970
+ "archive_gain_utility": {
971
+ "utility_id": utility_identity[0],
972
+ "utility_version": utility_identity[1],
973
+ "definition_sha256": utility_identity[2],
974
+ },
975
+ "lambda_hex": self.lambda_.hex(),
976
+ "beta_hex": self.beta.hex(),
977
+ "mixture_weight_hex": self.mixture_weight.hex(),
978
+ "prior_strength_hex": self.prior_strength.hex(),
979
+ "band_count": self.band_count,
980
+ "reference_gain_scale_hex": (
981
+ self.reference_gain_scale.hex()
982
+ ),
983
+ "root_prior_probability_hex": (
984
+ self.root_prior_probability.hex()
985
+ ),
986
+ "probability_floor_hex": (
987
+ self.probability_floor.hex()
988
+ ),
989
+ "scenario_weights_hex": [
990
+ value.hex() for value in self.scenario_weights
991
+ ],
992
+ "scenario_ids": list(_QUANTILE_SCENARIO_IDS),
993
+ "frozen_score_weight_hex": (
994
+ self.frozen_score_weight.hex()
995
+ ),
996
+ "frozen_score_minimum_training_runs": (
997
+ self.frozen_score_minimum_training_runs
998
+ ),
999
+ "frozen_score_evidence": (
1000
+ "weak_log_odds_feature_gated_by_training_"
1001
+ "run_floor_probability_only"
1002
+ ),
1003
+ "forecast_geometry": (
1004
+ "archive_conditioned_scenario_quantile_"
1005
+ "nondomination_and_positive_gain"
1006
+ ),
1007
+ "conversion_evidence": (
1008
+ "prequential_engine_rank_band_hierarchical_"
1009
+ "beta_shrinkage"
1010
+ ),
1011
+ "probability_mixture": "log_odds_blend",
1012
+ "magnitude_mixture": "arithmetic_blend",
1013
+ "hard_abstention": False,
1014
+ "terminal_value_of_information": 0.0,
1015
+ "parent_relative_improvement_counts": False,
1016
+ "eligible_candidate_outcomes_observed": False,
1017
+ "workload_objective_model_provider_prompt_branches": (
1018
+ False
1019
+ ),
1020
+ }
1021
+ # Emitted only when active so the r1 definition hash is
1022
+ # preserved bit-for-bit.
1023
+ if self.within_cell_rank_tie_break:
1024
+ definition["within_cell_tie_break"] = (
1025
+ "native_rank_quality_then_action_sha256"
1026
+ )
1027
+ if self.anchor_geometry_tie_break:
1028
+ definition["anchor_geometry_tie_break"] = (
1029
+ "max_min_chebyshev_dispersion_from_bought_anchors_then_"
1030
+ "chebyshev_excess_over_current_archive_front"
1031
+ )
1032
+ if self.downside_shortfall_tail_risk:
1033
+ definition["conversion_tail_risk"] = (
1034
+ "expected_shortfall_below_the_conversion_branch_own_"
1035
+ "expected_gain"
1036
+ )
1037
+ object.__setattr__(
1038
+ self,
1039
+ "definition_sha256",
1040
+ _hash(_DEFINITION_DOMAIN, definition),
1041
+ )
1042
+
1043
+ def _clamped(self, probability: float) -> float:
1044
+ return min(
1045
+ max(probability, self.probability_floor),
1046
+ 1.0 - self.probability_floor,
1047
+ )
1048
+
1049
+ def _conversion_evidence(
1050
+ self,
1051
+ observed_outcomes: tuple[ObservedConversionOutcome, ...],
1052
+ ) -> dict[object, _ConversionCellEvidence]:
1053
+ if type(observed_outcomes) is not tuple or any(
1054
+ type(value) is not ObservedConversionOutcome
1055
+ for value in observed_outcomes
1056
+ ):
1057
+ raise TypeError(
1058
+ "observed_outcomes must contain exact conversion outcomes"
1059
+ )
1060
+ ordinals = tuple(
1061
+ value.observation_ordinal for value in observed_outcomes
1062
+ )
1063
+ if ordinals != tuple(sorted(set(ordinals))):
1064
+ raise ValueError(
1065
+ "observed outcomes must use unique ascending ordinals"
1066
+ )
1067
+ counts: dict[object, list[float]] = {}
1068
+ for value in observed_outcomes:
1069
+ value.__post_init__()
1070
+ band = rank_band_index(
1071
+ value.native_rank,
1072
+ value.lane_size,
1073
+ self.band_count,
1074
+ )
1075
+ for key in (
1076
+ None,
1077
+ value.engine_id,
1078
+ (value.engine_id, band),
1079
+ ):
1080
+ row = counts.setdefault(key, [0.0, 0.0, 0.0, 0.0])
1081
+ row[0] += 1.0
1082
+ row[1] += float(value.positive)
1083
+ row[2] += float(value.feasible)
1084
+ row[3] += (
1085
+ value.marginal_archive_gain
1086
+ if value.positive
1087
+ else 0.0
1088
+ )
1089
+ return {
1090
+ key: _ConversionCellEvidence(
1091
+ observation_count=row[0],
1092
+ positive_count=row[1],
1093
+ feasible_count=row[2],
1094
+ positive_gain_sum=row[3],
1095
+ )
1096
+ for key, row in counts.items()
1097
+ }
1098
+
1099
+ def _hierarchical_estimates(
1100
+ self,
1101
+ *,
1102
+ evidence: dict[object, _ConversionCellEvidence],
1103
+ engine_id: str,
1104
+ band: int,
1105
+ ) -> dict[str, float]:
1106
+ empty = _ConversionCellEvidence(0.0, 0.0, 0.0, 0.0)
1107
+ chain = (
1108
+ evidence.get(None, empty),
1109
+ evidence.get(engine_id, empty),
1110
+ evidence.get((engine_id, band), empty),
1111
+ )
1112
+ p_positive = self.root_prior_probability
1113
+ p_feasible = self.root_prior_probability
1114
+ magnitude = self.reference_gain_scale
1115
+ for level in chain:
1116
+ p_positive = _beta_posterior_mean(
1117
+ prior_mean=p_positive,
1118
+ prior_strength=self.prior_strength,
1119
+ successes=level.positive_count,
1120
+ failures=level.observation_count - level.positive_count,
1121
+ )
1122
+ p_feasible = _beta_posterior_mean(
1123
+ prior_mean=p_feasible,
1124
+ prior_strength=self.prior_strength,
1125
+ successes=level.feasible_count,
1126
+ failures=level.observation_count - level.feasible_count,
1127
+ )
1128
+ magnitude = _shrunk_magnitude(
1129
+ prior_mean=magnitude,
1130
+ prior_strength=self.prior_strength,
1131
+ positive_gain_sum=level.positive_gain_sum,
1132
+ positive_count=level.positive_count,
1133
+ )
1134
+ cell = chain[2]
1135
+ effective_sample_size = (
1136
+ self.prior_strength + cell.observation_count
1137
+ )
1138
+ return {
1139
+ "p_positive": p_positive,
1140
+ "p_feasible": p_feasible,
1141
+ "magnitude": magnitude,
1142
+ "effective_sample_size": effective_sample_size,
1143
+ }
1144
+
1145
+ def _forecast_geometry(
1146
+ self,
1147
+ *,
1148
+ archive_points: tuple[ObjectivePoint, ...],
1149
+ forecast: PositiveGainForecast,
1150
+ ) -> dict[str, float]:
1151
+ gains: dict[str, float] = {}
1152
+ for scenario_id in _QUANTILE_SCENARIO_IDS:
1153
+ gain = self.archive_gain_utility.marginal_archive_gain(
1154
+ archive_points,
1155
+ forecast.point(scenario_id),
1156
+ )
1157
+ if type(gain) is not float or not math.isfinite(gain) or gain < 0.0:
1158
+ raise ValueError(
1159
+ "gain port returned an invalid archive gain"
1160
+ )
1161
+ gains[scenario_id] = gain
1162
+ weights = dict(
1163
+ zip(_QUANTILE_SCENARIO_IDS, self.scenario_weights, strict=True)
1164
+ )
1165
+ nondominated = math.fsum(
1166
+ weights[scenario_id]
1167
+ for scenario_id in _QUANTILE_SCENARIO_IDS
1168
+ if gains[scenario_id] > 0.0
1169
+ )
1170
+ positive_weight = nondominated
1171
+ magnitude = (
1172
+ math.fsum(
1173
+ weights[scenario_id] * gains[scenario_id]
1174
+ for scenario_id in _QUANTILE_SCENARIO_IDS
1175
+ if gains[scenario_id] > 0.0
1176
+ )
1177
+ / positive_weight
1178
+ if positive_weight > 0.0
1179
+ else 0.0
1180
+ )
1181
+ central = gains[CENTRAL_QUANTILE_SCENARIO_ID]
1182
+ shortfall = math.fsum(
1183
+ weights[scenario_id]
1184
+ * max(0.0, central - gains[scenario_id])
1185
+ for scenario_id in _QUANTILE_SCENARIO_IDS
1186
+ )
1187
+ return {
1188
+ "nondominated_fraction": nondominated,
1189
+ "probability": nondominated,
1190
+ "magnitude": magnitude,
1191
+ "shortfall": shortfall,
1192
+ }
1193
+
1194
+ def _logit(self, probability: float) -> float:
1195
+ clamped = self._clamped(probability)
1196
+ return math.log(clamped / (1.0 - clamped))
1197
+
1198
+ def _anchor_geometry(
1199
+ self,
1200
+ *,
1201
+ candidates: tuple[PositiveGainCandidate, ...],
1202
+ archive_points: tuple[ObjectivePoint, ...],
1203
+ covered_anchors: tuple[ObjectivePoint, ...],
1204
+ ) -> dict[str, tuple[float, float]]:
1205
+ """Per-candidate ``(excess, dispersion)`` in pure archive geometry.
1206
+
1207
+ ``excess`` is the anchor's Chebyshev excess over the CURRENT archive
1208
+ front: smaller means the parent sits nearer the front, and it is the
1209
+ strongest outcome-blind predictor of realised positivity measured on
1210
+ this corpus.
1211
+
1212
+ ``dispersion`` is the smallest max-norm distance from the anchor to
1213
+ any anchor this market has already bought, and ``inf`` when nothing
1214
+ has been bought. A second seat on an identical anchor therefore has
1215
+ dispersion exactly zero -- the redundancy the census measured -- and
1216
+ buying an anchor can only lower the dispersion of the candidates
1217
+ near it, never of the ones far from it, so preferring larger
1218
+ dispersion is submodular in the bought set by construction.
1219
+
1220
+ A candidate with no anchor is absent from the result and takes the
1221
+ market's median position, so an anchorless lane is never
1222
+ systematically evicted or systematically protected.
1223
+ """
1224
+
1225
+ metric_ids = tuple(
1226
+ metric_id for metric_id, _value in archive_points[0]
1227
+ )
1228
+ covered = tuple(
1229
+ _point_values(value, metric_ids) for value in covered_anchors
1230
+ )
1231
+ result: dict[str, tuple[float, float]] = {}
1232
+ for candidate in candidates:
1233
+ if candidate.anchor_point is None:
1234
+ continue
1235
+ values = _point_values(candidate.anchor_point, metric_ids)
1236
+ dispersion = (
1237
+ min(
1238
+ max(abs(a - b) for a, b in zip(values, other, strict=True))
1239
+ for other in covered
1240
+ )
1241
+ if covered
1242
+ else math.inf
1243
+ )
1244
+ result[candidate.action_sha256] = (
1245
+ chebyshev_excess(
1246
+ candidate.anchor_point,
1247
+ archive_points,
1248
+ metric_ids,
1249
+ ),
1250
+ float(dispersion),
1251
+ )
1252
+ return result
1253
+
1254
+ @staticmethod
1255
+ def _median(values: list[float]) -> float:
1256
+ ordered = sorted(values)
1257
+ return ordered[len(ordered) // 2]
1258
+
1259
+ def score_market(
1260
+ self,
1261
+ *,
1262
+ candidates: tuple[PositiveGainCandidate, ...],
1263
+ archive_points: tuple[ObjectivePoint, ...],
1264
+ observed_outcomes: tuple[ObservedConversionOutcome, ...],
1265
+ future_seats_remaining: int,
1266
+ horizon_total: int,
1267
+ frozen_fit_training_run_count: int = 0,
1268
+ covered_anchors: tuple[ObjectivePoint, ...] = (),
1269
+ ) -> CalibratedPositiveGainRanking:
1270
+ """Score and rank the whole market; never abstain."""
1271
+
1272
+ self.__post_init__()
1273
+ if type(candidates) is not tuple or not candidates:
1274
+ raise ValueError("candidates must be a non-empty exact tuple")
1275
+ for value in candidates:
1276
+ if type(value) is not PositiveGainCandidate:
1277
+ raise TypeError(
1278
+ "candidates must contain exact eligible candidates"
1279
+ )
1280
+ value.__post_init__()
1281
+ action_ids = tuple(value.action_sha256 for value in candidates)
1282
+ if len(action_ids) != len(set(action_ids)):
1283
+ raise ValueError("candidate identities repeat")
1284
+ if type(archive_points) is not tuple or not archive_points:
1285
+ raise ValueError(
1286
+ "archive_points must be a non-empty exact tuple"
1287
+ )
1288
+ for value in archive_points:
1289
+ _require_objective_point(value, name="archive point")
1290
+ if (
1291
+ type(future_seats_remaining) is not int
1292
+ or type(horizon_total) is not int
1293
+ or horizon_total <= 0
1294
+ or not 0 <= future_seats_remaining <= horizon_total
1295
+ ):
1296
+ raise ValueError(
1297
+ "future seats must fit the positive horizon total"
1298
+ )
1299
+ if (
1300
+ type(frozen_fit_training_run_count) is not int
1301
+ or frozen_fit_training_run_count < 0
1302
+ ):
1303
+ raise ValueError(
1304
+ "frozen_fit_training_run_count must be non-negative"
1305
+ )
1306
+ frozen_history_sufficient = (
1307
+ frozen_fit_training_run_count
1308
+ >= self.frozen_score_minimum_training_runs
1309
+ )
1310
+ evidence = self._conversion_evidence(observed_outcomes)
1311
+ if type(covered_anchors) is not tuple:
1312
+ raise TypeError("covered_anchors must be an exact tuple")
1313
+ for value in covered_anchors:
1314
+ _require_objective_point(value, name="covered anchor")
1315
+ anchor_geometry = (
1316
+ self._anchor_geometry(
1317
+ candidates=candidates,
1318
+ archive_points=archive_points,
1319
+ covered_anchors=covered_anchors,
1320
+ )
1321
+ if self.anchor_geometry_tie_break
1322
+ else {}
1323
+ )
1324
+ horizon_fraction = future_seats_remaining / horizon_total
1325
+ effective_beta = self.beta * horizon_fraction
1326
+ scores: list[CalibratedPositiveGainScore] = []
1327
+ for candidate in sorted(
1328
+ candidates,
1329
+ key=lambda value: value.action_sha256,
1330
+ ):
1331
+ band = rank_band_index(
1332
+ candidate.native_rank,
1333
+ candidate.lane_size,
1334
+ self.band_count,
1335
+ )
1336
+ conversion = self._hierarchical_estimates(
1337
+ evidence=evidence,
1338
+ engine_id=candidate.engine_id,
1339
+ band=band,
1340
+ )
1341
+ p_conversion = conversion["p_positive"]
1342
+ conversion_magnitude = conversion["magnitude"]
1343
+ if candidate.forecast is None:
1344
+ geometry: dict[str, float] | None = None
1345
+ raw_forecast_weight = 0.0
1346
+ else:
1347
+ geometry = self._forecast_geometry(
1348
+ archive_points=archive_points,
1349
+ forecast=candidate.forecast,
1350
+ )
1351
+ raw_forecast_weight = (
1352
+ self.mixture_weight
1353
+ * candidate.forecast.reliability
1354
+ )
1355
+ raw_conversion_weight = 1.0 - self.mixture_weight
1356
+ frozen_active = (
1357
+ candidate.frozen_score is not None
1358
+ and frozen_history_sufficient
1359
+ and self.frozen_score_weight > 0.0
1360
+ )
1361
+ raw_frozen_weight = (
1362
+ self.frozen_score_weight if frozen_active else 0.0
1363
+ )
1364
+ anchor = anchor_geometry.get(candidate.action_sha256)
1365
+ probability_total = (
1366
+ raw_forecast_weight
1367
+ + raw_conversion_weight
1368
+ + raw_frozen_weight
1369
+ )
1370
+ if (
1371
+ geometry is None and not frozen_active
1372
+ ) or probability_total <= 0.0:
1373
+ # Only conversion evidence is active: keep the exact
1374
+ # hierarchical posterior instead of a clamped round trip.
1375
+ p_positive = p_conversion
1376
+ else:
1377
+ blended_logit = (
1378
+ raw_conversion_weight
1379
+ * self._logit(p_conversion)
1380
+ + (
1381
+ raw_forecast_weight
1382
+ * self._logit(geometry["probability"])
1383
+ if geometry is not None
1384
+ else 0.0
1385
+ )
1386
+ + (
1387
+ raw_frozen_weight
1388
+ * self._logit(candidate.frozen_score)
1389
+ if frozen_active
1390
+ else 0.0
1391
+ )
1392
+ ) / probability_total
1393
+ p_positive = 1.0 / (1.0 + math.exp(-blended_logit))
1394
+ frozen_evidence_weight = (
1395
+ raw_frozen_weight / probability_total
1396
+ if frozen_active
1397
+ else 0.0
1398
+ )
1399
+ # Magnitudes and tail risk come only from forecast geometry
1400
+ # and conversion evidence; a frozen rank prior carries no
1401
+ # gain scale.
1402
+ magnitude_total = raw_forecast_weight + raw_conversion_weight
1403
+ forecast_weight = (
1404
+ raw_forecast_weight / magnitude_total
1405
+ if magnitude_total > 0.0
1406
+ else 0.0
1407
+ )
1408
+ conversion_weight = 1.0 - forecast_weight
1409
+ # The conversion branch's own downside. Both forms are the
1410
+ # weighted mass of scenarios that fall short of a central
1411
+ # outcome; they differ only in which central outcome, and the
1412
+ # shortfall form is the one that cannot invert the magnitude.
1413
+ conversion_shortfall = (
1414
+ p_conversion
1415
+ * (1.0 - p_conversion)
1416
+ * conversion_magnitude
1417
+ if self.downside_shortfall_tail_risk
1418
+ else (1.0 - p_conversion) * conversion_magnitude
1419
+ )
1420
+ if geometry is None:
1421
+ expected_positive_gain = conversion_magnitude
1422
+ tail_risk = conversion_weight * conversion_shortfall
1423
+ else:
1424
+ expected_positive_gain = (
1425
+ forecast_weight * geometry["magnitude"]
1426
+ + conversion_weight * conversion_magnitude
1427
+ )
1428
+ tail_risk = (
1429
+ forecast_weight * geometry["shortfall"]
1430
+ + conversion_weight * conversion_shortfall
1431
+ )
1432
+ uncertainty = math.sqrt(
1433
+ p_conversion
1434
+ * (1.0 - p_conversion)
1435
+ / (conversion["effective_sample_size"] + 1.0)
1436
+ )
1437
+ value_of_information = (
1438
+ uncertainty
1439
+ * max(
1440
+ expected_positive_gain,
1441
+ self.reference_gain_scale,
1442
+ )
1443
+ if future_seats_remaining > 0
1444
+ else 0.0
1445
+ )
1446
+ score = (
1447
+ p_positive * expected_positive_gain
1448
+ - self.lambda_ * tail_risk
1449
+ + effective_beta * value_of_information
1450
+ )
1451
+ scores.append(
1452
+ CalibratedPositiveGainScore(
1453
+ action_sha256=candidate.action_sha256,
1454
+ p_feasible=conversion["p_feasible"],
1455
+ p_positive_archive_gain=float(p_positive),
1456
+ expected_positive_gain=float(
1457
+ expected_positive_gain
1458
+ ),
1459
+ tail_risk=float(tail_risk),
1460
+ value_of_information=float(value_of_information),
1461
+ uncertainty=float(uncertainty),
1462
+ effective_sample_size=float(
1463
+ conversion["effective_sample_size"]
1464
+ ),
1465
+ score=float(score),
1466
+ forecast_probability=(
1467
+ None
1468
+ if geometry is None
1469
+ else float(geometry["probability"])
1470
+ ),
1471
+ forecast_magnitude=(
1472
+ None
1473
+ if geometry is None
1474
+ else float(geometry["magnitude"])
1475
+ ),
1476
+ forecast_nondominated_fraction=(
1477
+ None
1478
+ if geometry is None
1479
+ else float(geometry["nondominated_fraction"])
1480
+ ),
1481
+ conversion_probability=float(p_conversion),
1482
+ conversion_magnitude=float(conversion_magnitude),
1483
+ frozen_score_probability=candidate.frozen_score,
1484
+ frozen_evidence_weight=float(
1485
+ frozen_evidence_weight
1486
+ ),
1487
+ anchor_excess=(
1488
+ None if anchor is None else float(anchor[0])
1489
+ ),
1490
+ anchor_dispersion=(
1491
+ None if anchor is None else float(anchor[1])
1492
+ ),
1493
+ )
1494
+ )
1495
+ rank_quality_by_action = {
1496
+ value.action_sha256: (
1497
+ 1.0
1498
+ if value.lane_size == 1
1499
+ else 1.0
1500
+ - (value.native_rank - 1)
1501
+ / float(value.lane_size - 1)
1502
+ )
1503
+ for value in candidates
1504
+ }
1505
+ # Candidates the score cannot separate are separated here. An
1506
+ # anchorless candidate takes the market's median position on both
1507
+ # geometric keys, so no lane is systematically evicted.
1508
+ if self.anchor_geometry_tie_break and anchor_geometry:
1509
+ known = list(anchor_geometry.values())
1510
+ median_excess = self._median([value[0] for value in known])
1511
+ median_dispersion = self._median(
1512
+ [value[1] for value in known]
1513
+ )
1514
+ else:
1515
+ median_excess = 0.0
1516
+ median_dispersion = math.inf
1517
+
1518
+ def geometry_keys(action_sha256: str) -> tuple[float, float]:
1519
+ if not self.anchor_geometry_tie_break:
1520
+ return (0.0, 0.0)
1521
+ excess, dispersion = anchor_geometry.get(
1522
+ action_sha256,
1523
+ (median_excess, median_dispersion),
1524
+ )
1525
+ # Larger dispersion first (novel region), then smaller excess
1526
+ # (parent nearer the front).
1527
+ return (-dispersion, excess)
1528
+
1529
+ ranked = tuple(
1530
+ value.action_sha256
1531
+ for value in sorted(
1532
+ scores,
1533
+ key=lambda value: (
1534
+ -value.score,
1535
+ -value.p_positive_archive_gain,
1536
+ *geometry_keys(value.action_sha256),
1537
+ (
1538
+ -rank_quality_by_action[value.action_sha256]
1539
+ if self.within_cell_rank_tie_break
1540
+ else 0.0
1541
+ ),
1542
+ value.action_sha256,
1543
+ ),
1544
+ )
1545
+ )
1546
+ return CalibratedPositiveGainRanking(
1547
+ policy_id=self.policy_id,
1548
+ policy_version=self.policy_version,
1549
+ policy_definition_sha256=self.definition_sha256,
1550
+ archive_sha256=_hash(
1551
+ _ARCHIVE_DOMAIN,
1552
+ [
1553
+ _objective_point_record(value)
1554
+ for value in archive_points
1555
+ ],
1556
+ ),
1557
+ future_seats_remaining=future_seats_remaining,
1558
+ horizon_total=horizon_total,
1559
+ scores=tuple(scores),
1560
+ ranked_action_sha256s=ranked,
1561
+ )
1562
+
1563
+
1564
+ __all__ = [
1565
+ "CALIBRATED_POSITIVE_GAIN_OPPORTUNITY_POLICY_ID",
1566
+ "CALIBRATED_POSITIVE_GAIN_OPPORTUNITY_POLICY_VERSION",
1567
+ "CENTRAL_QUANTILE_SCENARIO_ID",
1568
+ "LOWER_QUANTILE_SCENARIO_ID",
1569
+ "UPPER_QUANTILE_SCENARIO_ID",
1570
+ "ArchiveConditionedGainPort",
1571
+ "CalibratedPositiveGainOpportunityPolicy",
1572
+ "CalibratedPositiveGainRanking",
1573
+ "CalibratedPositiveGainScore",
1574
+ "ObjectivePoint",
1575
+ "ObservedConversionOutcome",
1576
+ "PositiveGainCandidate",
1577
+ "PositiveGainForecast",
1578
+ "chebyshev_excess",
1579
+ "non_dominated",
1580
+ "validate_archive_conditioned_gain_port",
1581
+ ]