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,2328 @@
1
+ """Workload-blind brokerage over already-materialized evolutionary actions.
2
+
3
+ The broker is deliberately downstream of proposal generation. Workload
4
+ adapters and proposal experts own legality and materialization; this module
5
+ sees only authenticated configurations, generic state cells, lineage, and
6
+ strictly prior outcomes. Mutation, restart, acquisition, and recombination
7
+ therefore compete for the same expensive evaluation slots without exposing a
8
+ workload or model identifier to the policy.
9
+
10
+ Outcome channels remain orthogonal diagnostics. Selection itself uses one
11
+ currency: normalized archive return resolved over an authenticated lineage
12
+ horizon. Feasibility and realization gate that return, forecast error governs
13
+ the authority of the consequence model, and uncertainty is used only for
14
+ nonterminal exploration. Sparse state cells shrink toward an arm-level
15
+ posterior before a joint-slate optimizer adds only residual complementarity.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import hashlib
21
+ import json
22
+ import math
23
+ import re
24
+ from dataclasses import dataclass, field
25
+ from enum import Enum
26
+ from itertools import combinations
27
+ from typing import Protocol, runtime_checkable
28
+
29
+ from agent_evolve.application.contextual_search_controller import SearchPhase
30
+ from agent_evolve.application.outcome_adaptive_action_racing import (
31
+ AdaptiveActionAllocationDirective,
32
+ )
33
+ from agent_evolve.domain.ids import CandidateId
34
+ from agent_evolve.domain.patch import require_sha256
35
+ from agent_evolve.domain.typed_json import (
36
+ FrozenJsonObject,
37
+ freeze_json,
38
+ thaw_json,
39
+ typed_json_sha256,
40
+ )
41
+
42
+
43
+ MATERIALIZED_ACTION_BROKER_ID = "regret_brokered_expert_evolution"
44
+ MATERIALIZED_ACTION_BROKER_VERSION = 8
45
+ EMPIRICAL_RETURN_ESTIMATOR_DEFINITION_SHA256 = hashlib.sha256(
46
+ b"agent-evolve:materialized-action-empirical-return:v1"
47
+ ).hexdigest()
48
+ MATERIALIZED_ACTION_BROKER_DEFINITION_SHA256 = hashlib.sha256(
49
+ b"agent-evolve:regret-brokered-expert-evolution:v8;"
50
+ b"decision-unit=authenticated-materialized-action;"
51
+ b"axes=expert,native-rank,parent-arity,operator,target,role;"
52
+ b"state=residual-cell,parent-cell,archive-cell,structural-signature,"
53
+ b"patch-compatibility,phase,horizon,calibration,source-distance,memory-dose;"
54
+ b"forbidden-inputs=workload-id,model-id,provider-id,objective-name;"
55
+ b"selection-currency=normalized-resolved-lineage-return;"
56
+ b"diagnostic-channels=gain,positive,stage-survival,terminal-persistence,"
57
+ b"descendant,feasibility,forecast-error,realization;"
58
+ b"return-estimator=injected-port-or-zero-baseline-bounded-empirical-mean-with-arm-hierarchical-shrinkage;"
59
+ b"cold-start-tie=within-expert-native-rank-then-expert-diversity;"
60
+ b"uncertainty=distribution-free-maximum-standard-error;"
61
+ b"selection=additive-return-plus-residual-complementarity;"
62
+ b"allocation=optional-authenticated-static-requirement-or-outcome-"
63
+ b"adaptive-directive;"
64
+ b"adaptive-allocation=prior-decision-and-observed-outcome-bound;"
65
+ b"exploration=authenticated-prequential-required-set-plus-at-most-one-"
66
+ b"posterior-nonterminal-arm;"
67
+ b"terminal-information-bonus=zero;"
68
+ b"reference=bounded-multislot-conservative-escrow;"
69
+ b"duplicate-constraint=one-evaluation-per-phenotype;"
70
+ b"bounded-beam-prefix-gate=canonical-suffix-unique-phenotype-"
71
+ b"completion-witness;deterministic=true"
72
+ ).hexdigest()
73
+
74
+ _TOKEN = re.compile(r"^[a-z][a-z0-9_.:-]{0,255}$")
75
+ _CONTEXT_DOMAIN = b"agent-evolve:materialized-action-context:v1\x00"
76
+ _ACTION_DOMAIN = b"agent-evolve:materialized-action-descriptor:v1\x00"
77
+ _OUTCOME_DOMAIN = b"agent-evolve:materialized-action-outcome:v1\x00"
78
+ _CREDIT_DOMAIN = b"agent-evolve:materialized-action-delayed-credit:v1\x00"
79
+ _RESOLVED_RETURN_DOMAIN = b"agent-evolve:materialized-action-resolved-return:v1\x00"
80
+ _RETURN_PRIOR_PREDICTION_DOMAIN = (
81
+ b"agent-evolve:materialized-action-return-prior-prediction:v1\x00"
82
+ )
83
+ _EMPIRICAL_BAYES_RETURN_VALUE_DOMAIN = (
84
+ b"agent-evolve:empirical-bayes-materialized-action-return-value:v1\x00"
85
+ )
86
+ _ACTION_OPPORTUNITY_EVIDENCE_DOMAIN = (
87
+ b"agent-evolve:materialized-action-opportunity-evidence:v1\x00"
88
+ )
89
+ _OPPORTUNITY_CONDITIONED_RETURN_VALUE_DOMAIN = (
90
+ b"agent-evolve:opportunity-conditioned-materialized-action-return-value:v1\x00"
91
+ )
92
+ _EXPLORATION_REQUIREMENT_DOMAIN = (
93
+ b"agent-evolve:materialized-action-exploration-requirement:v1\x00"
94
+ )
95
+ _ALLOCATION_REQUIREMENT_DOMAIN = (
96
+ b"agent-evolve:materialized-action-allocation-requirement:v1\x00"
97
+ )
98
+ _DECISION_DOMAIN = b"agent-evolve:materialized-action-broker-decision:v1\x00"
99
+
100
+
101
+ def _canonical_json(value: object) -> bytes:
102
+ return json.dumps(
103
+ value,
104
+ allow_nan=False,
105
+ ensure_ascii=True,
106
+ separators=(",", ":"),
107
+ sort_keys=True,
108
+ ).encode("ascii", errors="strict")
109
+
110
+
111
+ def _hash(domain: bytes, value: object) -> str:
112
+ return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
113
+
114
+
115
+ def _require_token(value: str, *, name: str) -> None:
116
+ if type(value) is not str or _TOKEN.fullmatch(value) is None:
117
+ raise ValueError(f"{name} must use the closed token grammar")
118
+
119
+
120
+ def _require_probability(value: float, *, name: str) -> None:
121
+ if type(value) is not float or not math.isfinite(value):
122
+ raise TypeError(f"{name} must be a finite exact float")
123
+ if not 0.0 <= value <= 1.0:
124
+ raise ValueError(f"{name} must lie in [0, 1]")
125
+
126
+
127
+ def _require_nonnegative_finite(value: float, *, name: str) -> None:
128
+ if type(value) is not float or not math.isfinite(value):
129
+ raise TypeError(f"{name} must be a finite exact float")
130
+ if value < 0.0:
131
+ raise ValueError(f"{name} must be non-negative")
132
+
133
+
134
+ def _candidate_record(value: CandidateId) -> str:
135
+ if type(value) is not CandidateId:
136
+ raise TypeError("parent and target IDs must be exact CandidateId values")
137
+ CandidateId.__post_init__(value)
138
+ return value.value
139
+
140
+
141
+ @dataclass(frozen=True, slots=True)
142
+ class MaterializedActionContext:
143
+ """Bounded generic state visible to the action broker."""
144
+
145
+ campaign_scope_sha256: str
146
+ decision_index: int
147
+ phase: SearchPhase
148
+ remaining_decisions: int
149
+ remaining_evaluations: int
150
+ residual_frontier_cell: str
151
+ parent_position_cell: str
152
+ archive_relation_cell: str
153
+ structural_signature_sha256: str
154
+ patch_compatibility_cell: str
155
+ forecast_calibration_cell: str
156
+ source_distance_bin: int
157
+ memory_dose_bin: int
158
+ state_signature_sha256: str = field(init=False)
159
+ context_sha256: str = field(init=False)
160
+
161
+ def __post_init__(self) -> None:
162
+ require_sha256(self.campaign_scope_sha256, "campaign_scope_sha256")
163
+ require_sha256(
164
+ self.structural_signature_sha256,
165
+ "structural_signature_sha256",
166
+ )
167
+ if type(self.decision_index) is not int or self.decision_index <= 0:
168
+ raise ValueError("decision_index must be a positive exact integer")
169
+ if type(self.phase) is not SearchPhase:
170
+ raise TypeError("phase must be an exact SearchPhase")
171
+ for name in ("remaining_decisions", "remaining_evaluations"):
172
+ value = getattr(self, name)
173
+ if type(value) is not int or value <= 0:
174
+ raise ValueError(f"{name} must be a positive exact integer")
175
+ for name in (
176
+ "residual_frontier_cell",
177
+ "parent_position_cell",
178
+ "archive_relation_cell",
179
+ "patch_compatibility_cell",
180
+ "forecast_calibration_cell",
181
+ ):
182
+ _require_token(getattr(self, name), name=name)
183
+ for name in ("source_distance_bin", "memory_dose_bin"):
184
+ value = getattr(self, name)
185
+ if type(value) is not int or not 0 <= value <= 15:
186
+ raise ValueError(f"{name} must lie in [0, 15]")
187
+ state = self._state_record()
188
+ object.__setattr__(
189
+ self,
190
+ "state_signature_sha256",
191
+ _hash(_CONTEXT_DOMAIN, state),
192
+ )
193
+ object.__setattr__(
194
+ self,
195
+ "context_sha256",
196
+ _hash(
197
+ _CONTEXT_DOMAIN,
198
+ {
199
+ **state,
200
+ "campaign_scope_sha256": self.campaign_scope_sha256,
201
+ "decision_index": self.decision_index,
202
+ },
203
+ ),
204
+ )
205
+
206
+ def _state_record(self) -> dict[str, object]:
207
+ return {
208
+ "schema_version": 1,
209
+ "phase": self.phase.value,
210
+ "remaining_decisions": self.remaining_decisions,
211
+ "remaining_evaluations": self.remaining_evaluations,
212
+ "residual_frontier_cell": self.residual_frontier_cell,
213
+ "parent_position_cell": self.parent_position_cell,
214
+ "archive_relation_cell": self.archive_relation_cell,
215
+ "structural_signature_sha256": self.structural_signature_sha256,
216
+ "patch_compatibility_cell": self.patch_compatibility_cell,
217
+ "forecast_calibration_cell": self.forecast_calibration_cell,
218
+ "source_distance_bin": self.source_distance_bin,
219
+ "memory_dose_bin": self.memory_dose_bin,
220
+ }
221
+
222
+ def to_record(self) -> dict[str, object]:
223
+ self.__post_init__()
224
+ return {
225
+ **self._state_record(),
226
+ "campaign_scope_sha256": self.campaign_scope_sha256,
227
+ "decision_index": self.decision_index,
228
+ "state_signature_sha256": self.state_signature_sha256,
229
+ "context_sha256": self.context_sha256,
230
+ }
231
+
232
+
233
+ @dataclass(frozen=True, slots=True)
234
+ class MaterializedActionDescriptor:
235
+ """One legal route to one fully materialized candidate configuration."""
236
+
237
+ context: MaterializedActionContext
238
+ configuration: FrozenJsonObject
239
+ phenotype_identity_sha256: str
240
+ expert_id: str
241
+ native_rank: int
242
+ parent_ids: tuple[CandidateId, ...]
243
+ operator_id: str
244
+ target_candidate_id: CandidateId
245
+ role_id: str
246
+ normalized_evaluation_cost: float
247
+ reference_action: bool = False
248
+ action_sha256: str = field(init=False)
249
+
250
+ def __post_init__(self) -> None:
251
+ if type(self.context) is not MaterializedActionContext:
252
+ raise TypeError("context must be an exact MaterializedActionContext")
253
+ MaterializedActionContext.__post_init__(self.context)
254
+ if type(self.configuration) is not FrozenJsonObject:
255
+ raise TypeError("configuration must be an exact FrozenJsonObject")
256
+ require_sha256(self.phenotype_identity_sha256, "phenotype_identity_sha256")
257
+ _require_token(self.expert_id, name="expert_id")
258
+ _require_token(self.operator_id, name="operator_id")
259
+ _require_token(self.role_id, name="role_id")
260
+ if type(self.native_rank) is not int or self.native_rank <= 0:
261
+ raise ValueError("native_rank must be a positive exact integer")
262
+ if type(self.parent_ids) is not tuple or len(self.parent_ids) > 8:
263
+ raise ValueError(
264
+ "parent_ids must be an exact tuple with arity at most eight"
265
+ )
266
+ parent_values = tuple(_candidate_record(value) for value in self.parent_ids)
267
+ if len(parent_values) != len(set(parent_values)):
268
+ raise ValueError("parent_ids must be unique")
269
+ _candidate_record(self.target_candidate_id)
270
+ if self.target_candidate_id in self.parent_ids:
271
+ raise ValueError("target_candidate_id cannot be one of its parents")
272
+ _require_probability(
273
+ self.normalized_evaluation_cost,
274
+ name="normalized_evaluation_cost",
275
+ )
276
+ if type(self.reference_action) is not bool:
277
+ raise TypeError("reference_action must be an exact bool")
278
+ object.__setattr__(
279
+ self,
280
+ "action_sha256",
281
+ _hash(_ACTION_DOMAIN, self._unsigned_record()),
282
+ )
283
+
284
+ @property
285
+ def parent_arity(self) -> int:
286
+ return len(self.parent_ids)
287
+
288
+ @property
289
+ def configuration_sha256(self) -> str:
290
+ return typed_json_sha256(self.configuration)
291
+
292
+ @property
293
+ def arm_key(self) -> tuple[str, str, int]:
294
+ return (self.expert_id, self.operator_id, self.parent_arity)
295
+
296
+ def _unsigned_record(self) -> dict[str, object]:
297
+ return {
298
+ "schema_version": 1,
299
+ "context_sha256": self.context.context_sha256,
300
+ "state_signature_sha256": self.context.state_signature_sha256,
301
+ "configuration_sha256": self.configuration_sha256,
302
+ "phenotype_identity_sha256": self.phenotype_identity_sha256,
303
+ "expert_id": self.expert_id,
304
+ "native_rank": self.native_rank,
305
+ "parent_ids": [_candidate_record(value) for value in self.parent_ids],
306
+ "parent_arity": self.parent_arity,
307
+ "operator_id": self.operator_id,
308
+ "target_candidate_id": _candidate_record(self.target_candidate_id),
309
+ "role_id": self.role_id,
310
+ "normalized_evaluation_cost_hex": (self.normalized_evaluation_cost.hex()),
311
+ "reference_action": self.reference_action,
312
+ }
313
+
314
+ def to_record(self, *, include_configuration: bool = False) -> dict[str, object]:
315
+ self.__post_init__()
316
+ record = {**self._unsigned_record(), "action_sha256": self.action_sha256}
317
+ if include_configuration:
318
+ record["configuration"] = self.configuration
319
+ return record
320
+
321
+
322
+ @dataclass(frozen=True, slots=True)
323
+ class MaterializedActionOutcome:
324
+ """Immediate, append-only evidence for one requested broker action."""
325
+
326
+ action: MaterializedActionDescriptor
327
+ realized: bool
328
+ feasible: bool | None
329
+ normalized_archive_gain: float | None
330
+ positive_marginal_utility: bool | None
331
+ normalized_forecast_error: float | None = None
332
+ outcome_sha256: str = field(init=False)
333
+
334
+ def __post_init__(self) -> None:
335
+ if type(self.action) is not MaterializedActionDescriptor:
336
+ raise TypeError("action must be an exact MaterializedActionDescriptor")
337
+ MaterializedActionDescriptor.__post_init__(self.action)
338
+ if type(self.realized) is not bool:
339
+ raise TypeError("realized must be an exact bool")
340
+ if not self.realized:
341
+ if (
342
+ self.feasible is not None
343
+ or self.normalized_archive_gain is not None
344
+ or self.positive_marginal_utility is not None
345
+ or self.normalized_forecast_error is not None
346
+ ):
347
+ raise ValueError("unrealized actions cannot carry evaluator evidence")
348
+ else:
349
+ if type(self.feasible) is not bool:
350
+ raise TypeError("realized actions require an exact feasibility verdict")
351
+ if self.normalized_archive_gain is None:
352
+ raise ValueError("realized actions require normalized archive gain")
353
+ _require_probability(
354
+ self.normalized_archive_gain,
355
+ name="normalized_archive_gain",
356
+ )
357
+ if type(self.positive_marginal_utility) is not bool:
358
+ raise TypeError("realized actions require an exact positive verdict")
359
+ if self.positive_marginal_utility != (self.normalized_archive_gain > 0.0):
360
+ raise ValueError("positive verdict differs from normalized gain")
361
+ if not self.feasible and (
362
+ self.normalized_archive_gain != 0.0 or self.positive_marginal_utility
363
+ ):
364
+ raise ValueError("infeasible actions cannot carry positive gain")
365
+ if self.normalized_forecast_error is not None:
366
+ _require_probability(
367
+ self.normalized_forecast_error,
368
+ name="normalized_forecast_error",
369
+ )
370
+ object.__setattr__(
371
+ self,
372
+ "outcome_sha256",
373
+ _hash(_OUTCOME_DOMAIN, self._unsigned_record()),
374
+ )
375
+
376
+ def _unsigned_record(self) -> dict[str, object]:
377
+ return {
378
+ "schema_version": 1,
379
+ "action_sha256": self.action.action_sha256,
380
+ "realized": self.realized,
381
+ "feasible": self.feasible,
382
+ "normalized_archive_gain_hex": (
383
+ None
384
+ if self.normalized_archive_gain is None
385
+ else self.normalized_archive_gain.hex()
386
+ ),
387
+ "positive_marginal_utility": self.positive_marginal_utility,
388
+ "normalized_forecast_error_hex": (
389
+ None
390
+ if self.normalized_forecast_error is None
391
+ else self.normalized_forecast_error.hex()
392
+ ),
393
+ }
394
+
395
+ def to_record(self) -> dict[str, object]:
396
+ self.__post_init__()
397
+ return {**self._unsigned_record(), "outcome_sha256": self.outcome_sha256}
398
+
399
+
400
+ @dataclass(frozen=True, slots=True)
401
+ class MaterializedActionDelayedCredit:
402
+ """Later survival or descendant evidence joined to an immediate outcome."""
403
+
404
+ outcome: MaterializedActionOutcome
405
+ available_at_decision_index: int
406
+ stage_front_survived: bool | None = None
407
+ terminal_front_persisted: bool | None = None
408
+ useful_descendant_observed: bool | None = None
409
+ credit_sha256: str = field(init=False)
410
+
411
+ def __post_init__(self) -> None:
412
+ if type(self.outcome) is not MaterializedActionOutcome:
413
+ raise TypeError("outcome must be an exact MaterializedActionOutcome")
414
+ MaterializedActionOutcome.__post_init__(self.outcome)
415
+ if not self.outcome.realized or self.outcome.feasible is not True:
416
+ raise ValueError("delayed credit requires a realized feasible action")
417
+ if (
418
+ type(self.available_at_decision_index) is not int
419
+ or self.available_at_decision_index
420
+ < self.outcome.action.context.decision_index
421
+ ):
422
+ raise ValueError("delayed credit cannot precede its source action")
423
+ values = (
424
+ self.stage_front_survived,
425
+ self.terminal_front_persisted,
426
+ self.useful_descendant_observed,
427
+ )
428
+ if all(value is None for value in values):
429
+ raise ValueError("delayed credit must adjudicate at least one channel")
430
+ if any(value is not None and type(value) is not bool for value in values):
431
+ raise TypeError("delayed credit channels must be exact bools or None")
432
+ object.__setattr__(
433
+ self,
434
+ "credit_sha256",
435
+ _hash(_CREDIT_DOMAIN, self._unsigned_record()),
436
+ )
437
+
438
+ def _unsigned_record(self) -> dict[str, object]:
439
+ return {
440
+ "schema_version": 1,
441
+ "outcome_sha256": self.outcome.outcome_sha256,
442
+ "available_at_decision_index": self.available_at_decision_index,
443
+ "stage_front_survived": self.stage_front_survived,
444
+ "terminal_front_persisted": self.terminal_front_persisted,
445
+ "useful_descendant_observed": self.useful_descendant_observed,
446
+ }
447
+
448
+ def to_record(self) -> dict[str, object]:
449
+ self.__post_init__()
450
+ return {**self._unsigned_record(), "credit_sha256": self.credit_sha256}
451
+
452
+
453
+ @dataclass(frozen=True, slots=True)
454
+ class MaterializedActionResolvedReturn:
455
+ """One append-only resolution of an action's finite-horizon return.
456
+
457
+ A resolution may be refined as descendants become observable. The ledger
458
+ uses only the newest resolution available strictly before the decision it
459
+ is scoring. Components are expressed in the same normalized archive-
460
+ utility currency and must close exactly to ``normalized_horizon_return``.
461
+ The resolver, not the broker, owns lineage attribution and discounting.
462
+ """
463
+
464
+ outcome: MaterializedActionOutcome
465
+ available_at_decision_index: int
466
+ horizon_end_decision_index: int
467
+ normalized_immediate_return: float
468
+ normalized_descendant_return: float
469
+ normalized_horizon_return: float
470
+ fully_resolved: bool
471
+ attribution_definition_sha256: str
472
+ return_sha256: str = field(init=False)
473
+
474
+ def __post_init__(self) -> None:
475
+ if type(self.outcome) is not MaterializedActionOutcome:
476
+ raise TypeError("outcome must be an exact MaterializedActionOutcome")
477
+ MaterializedActionOutcome.__post_init__(self.outcome)
478
+ source_index = self.outcome.action.context.decision_index
479
+ if (
480
+ type(self.available_at_decision_index) is not int
481
+ or self.available_at_decision_index <= source_index
482
+ ):
483
+ raise ValueError("resolved return must become available after its action")
484
+ if (
485
+ type(self.horizon_end_decision_index) is not int
486
+ or self.horizon_end_decision_index < source_index
487
+ or self.horizon_end_decision_index >= self.available_at_decision_index
488
+ ):
489
+ raise ValueError(
490
+ "resolved return horizon is inconsistent with availability"
491
+ )
492
+ for name in (
493
+ "normalized_immediate_return",
494
+ "normalized_descendant_return",
495
+ "normalized_horizon_return",
496
+ ):
497
+ _require_probability(getattr(self, name), name=name)
498
+ if not math.isclose(
499
+ self.normalized_immediate_return + self.normalized_descendant_return,
500
+ self.normalized_horizon_return,
501
+ rel_tol=0.0,
502
+ abs_tol=1e-12,
503
+ ):
504
+ raise ValueError("resolved return components do not close")
505
+ expected_immediate = (
506
+ 0.0 if not self.outcome.realized else self.outcome.normalized_archive_gain
507
+ )
508
+ assert expected_immediate is not None
509
+ if not math.isclose(
510
+ self.normalized_immediate_return,
511
+ expected_immediate,
512
+ rel_tol=0.0,
513
+ abs_tol=1e-12,
514
+ ):
515
+ raise ValueError("resolved immediate return differs from its outcome")
516
+ if type(self.fully_resolved) is not bool:
517
+ raise TypeError("fully_resolved must be an exact bool")
518
+ require_sha256(
519
+ self.attribution_definition_sha256,
520
+ "attribution_definition_sha256",
521
+ )
522
+ object.__setattr__(
523
+ self,
524
+ "return_sha256",
525
+ _hash(_RESOLVED_RETURN_DOMAIN, self._unsigned_record()),
526
+ )
527
+
528
+ def _unsigned_record(self) -> dict[str, object]:
529
+ return {
530
+ "schema_version": 1,
531
+ "outcome_sha256": self.outcome.outcome_sha256,
532
+ "available_at_decision_index": self.available_at_decision_index,
533
+ "horizon_end_decision_index": self.horizon_end_decision_index,
534
+ "normalized_immediate_return_hex": (self.normalized_immediate_return.hex()),
535
+ "normalized_descendant_return_hex": (
536
+ self.normalized_descendant_return.hex()
537
+ ),
538
+ "normalized_horizon_return_hex": self.normalized_horizon_return.hex(),
539
+ "fully_resolved": self.fully_resolved,
540
+ "attribution_definition_sha256": self.attribution_definition_sha256,
541
+ }
542
+
543
+ def to_record(self) -> dict[str, object]:
544
+ self.__post_init__()
545
+ return {**self._unsigned_record(), "return_sha256": self.return_sha256}
546
+
547
+
548
+ class BrokerEvidenceChannel(str, Enum):
549
+ GAIN = "gain"
550
+ POSITIVE = "positive"
551
+ STAGE_SURVIVAL = "stage_survival"
552
+ TERMINAL_PERSISTENCE = "terminal_persistence"
553
+ DESCENDANT = "descendant"
554
+ FEASIBILITY = "feasibility"
555
+ FORECAST_ERROR = "forecast_error"
556
+ REALIZATION = "realization"
557
+
558
+
559
+ @dataclass(frozen=True, slots=True)
560
+ class BrokerChannelEstimate:
561
+ channel: BrokerEvidenceChannel
562
+ mean: float
563
+ standard_deviation: float
564
+ local_count: int
565
+ global_count: int
566
+ local_mean: float
567
+ global_mean: float
568
+ shrinkage_weight: float
569
+
570
+ def __post_init__(self) -> None:
571
+ if type(self.channel) is not BrokerEvidenceChannel:
572
+ raise TypeError("channel must be an exact BrokerEvidenceChannel")
573
+ for name in (
574
+ "mean",
575
+ "standard_deviation",
576
+ "local_mean",
577
+ "global_mean",
578
+ "shrinkage_weight",
579
+ ):
580
+ _require_probability(getattr(self, name), name=name)
581
+ for name in ("local_count", "global_count"):
582
+ value = getattr(self, name)
583
+ if type(value) is not int or value < 0:
584
+ raise ValueError(f"{name} must be a non-negative exact integer")
585
+ if self.local_count > self.global_count:
586
+ raise ValueError("local evidence cannot exceed its arm-global evidence")
587
+
588
+ def to_record(self) -> dict[str, object]:
589
+ self.__post_init__()
590
+ return {
591
+ "channel": self.channel.value,
592
+ "mean_hex": self.mean.hex(),
593
+ "standard_deviation_hex": self.standard_deviation.hex(),
594
+ "local_count": self.local_count,
595
+ "global_count": self.global_count,
596
+ "local_mean_hex": self.local_mean.hex(),
597
+ "global_mean_hex": self.global_mean.hex(),
598
+ "shrinkage_weight_hex": self.shrinkage_weight.hex(),
599
+ }
600
+
601
+
602
+ def _bounded_empirical(values: tuple[float, ...]) -> tuple[float, float, float]:
603
+ """Preserve return scale while retaining a distribution-free error bound."""
604
+
605
+ if not values:
606
+ # Archive return is a non-negative *gain*, not a Bernoulli success
607
+ # probability. A Beta(1, 1)-style mean of 1/2 overwhelms the small
608
+ # gains seen in real campaigns and causes every multi-action slate to
609
+ # saturate. Zero is the only scale-free lower-bound mean; uncertainty
610
+ # remains maximal and may buy one explicit nonterminal probe below.
611
+ return 0.0, 0.5, 0.0
612
+ mean = math.fsum(values) / len(values)
613
+ # Popoviciu's bound gives sigma <= 1/2 for observations in [0, 1].
614
+ standard_error = 0.5 / math.sqrt(len(values))
615
+ return float(mean), float(standard_error), float(len(values))
616
+
617
+
618
+ @dataclass(frozen=True, slots=True)
619
+ class BrokerReturnEstimate:
620
+ """Hierarchically shrunk posterior in the sole selection currency."""
621
+
622
+ mean: float
623
+ standard_deviation: float
624
+ local_count: int
625
+ global_count: int
626
+ resolved_count: int
627
+ provisional_count: int
628
+ local_mean: float
629
+ global_mean: float
630
+ shrinkage_weight: float
631
+
632
+ def __post_init__(self) -> None:
633
+ for name in (
634
+ "mean",
635
+ "standard_deviation",
636
+ "local_mean",
637
+ "global_mean",
638
+ "shrinkage_weight",
639
+ ):
640
+ _require_probability(getattr(self, name), name=name)
641
+ for name in (
642
+ "local_count",
643
+ "global_count",
644
+ "resolved_count",
645
+ "provisional_count",
646
+ ):
647
+ value = getattr(self, name)
648
+ if type(value) is not int or value < 0:
649
+ raise ValueError(f"{name} must be a non-negative exact integer")
650
+ if self.local_count > self.global_count:
651
+ raise ValueError("local return evidence exceeds arm-global evidence")
652
+ if self.resolved_count + self.provisional_count != self.global_count:
653
+ raise ValueError("return resolution counts do not close")
654
+
655
+ def to_record(self) -> dict[str, object]:
656
+ self.__post_init__()
657
+ return {
658
+ "mean_hex": self.mean.hex(),
659
+ "standard_deviation_hex": self.standard_deviation.hex(),
660
+ "local_count": self.local_count,
661
+ "global_count": self.global_count,
662
+ "resolved_count": self.resolved_count,
663
+ "provisional_count": self.provisional_count,
664
+ "local_mean_hex": self.local_mean.hex(),
665
+ "global_mean_hex": self.global_mean.hex(),
666
+ "shrinkage_weight_hex": self.shrinkage_weight.hex(),
667
+ }
668
+
669
+
670
+ @runtime_checkable
671
+ class MaterializedActionReturnValuePort(Protocol):
672
+ """Predict the common evaluator-grounded return of one unseen action.
673
+
674
+ Implementations may wrap a frozen cross-run meta-prior plus branch-local
675
+ prequential updates. The descriptor deliberately contains no workload,
676
+ model, provider, prompt, or objective-name feature, so the orchestration
677
+ core cannot branch on those identities. Returning the same typed estimate
678
+ as the empirical fallback keeps one selection currency and one uncertainty
679
+ contract.
680
+ """
681
+
682
+ definition_sha256: str
683
+
684
+ def estimate(
685
+ self, action: MaterializedActionDescriptor
686
+ ) -> BrokerReturnEstimate: ...
687
+
688
+
689
+ @dataclass(frozen=True, slots=True)
690
+ class MaterializedActionReturnPriorPrediction:
691
+ """Authenticated portable prior for one not-yet-evaluated action."""
692
+
693
+ action_sha256: str
694
+ mean: float
695
+ standard_deviation: float
696
+ effective_sample_size: float
697
+ evidence_sha256: str
698
+ prediction_sha256: str = field(init=False)
699
+
700
+ def __post_init__(self) -> None:
701
+ require_sha256(self.action_sha256, "action_sha256")
702
+ _require_probability(self.mean, name="mean")
703
+ _require_probability(
704
+ self.standard_deviation,
705
+ name="standard_deviation",
706
+ )
707
+ if (
708
+ type(self.effective_sample_size) is not float
709
+ or not math.isfinite(self.effective_sample_size)
710
+ or self.effective_sample_size <= 0.0
711
+ ):
712
+ raise ValueError(
713
+ "effective_sample_size must be a positive finite exact float"
714
+ )
715
+ require_sha256(self.evidence_sha256, "evidence_sha256")
716
+ object.__setattr__(
717
+ self,
718
+ "prediction_sha256",
719
+ _hash(_RETURN_PRIOR_PREDICTION_DOMAIN, self._unsigned_record()),
720
+ )
721
+
722
+ def _unsigned_record(self) -> dict[str, object]:
723
+ return {
724
+ "schema_version": 1,
725
+ "action_sha256": self.action_sha256,
726
+ "mean_hex": self.mean.hex(),
727
+ "standard_deviation_hex": self.standard_deviation.hex(),
728
+ "effective_sample_size_hex": self.effective_sample_size.hex(),
729
+ "evidence_sha256": self.evidence_sha256,
730
+ }
731
+
732
+ def to_record(self) -> dict[str, object]:
733
+ self.__post_init__()
734
+ return {
735
+ **self._unsigned_record(),
736
+ "prediction_sha256": self.prediction_sha256,
737
+ }
738
+
739
+
740
+ @runtime_checkable
741
+ class MaterializedActionReturnPriorPort(Protocol):
742
+ """Project an action into a frozen cross-run return prior.
743
+
744
+ Workload adapters may compute generic numerical features or consult a
745
+ content-addressed feature panel, but the application core receives only a
746
+ common normalized-return distribution. The predictor definition must
747
+ authenticate its feature schema, fit, training cutoff, and evidence panel.
748
+ """
749
+
750
+ definition_sha256: str
751
+
752
+ def predict(
753
+ self,
754
+ action: MaterializedActionDescriptor,
755
+ ) -> MaterializedActionReturnPriorPrediction | None: ...
756
+
757
+
758
+ @dataclass(frozen=True, slots=True)
759
+ class MaterializedActionOpportunityEvidence:
760
+ """Authenticated current-archive opportunity for an action's lineage.
761
+
762
+ ``source_opportunity`` and ``archive_opportunity_scale`` use the same
763
+ normalized archive-utility currency as realized return. The former is
764
+ normally the source parent's leave-one-out contribution; the latter is a
765
+ strictly-prior, action-independent scale such as the maximum contribution
766
+ on the current front. Workload adapters own the projection, while the
767
+ broker sees neither objective names nor workload fields.
768
+ """
769
+
770
+ action_sha256: str
771
+ source_opportunity: float
772
+ archive_opportunity_scale: float
773
+ evidence_sha256: str
774
+ opportunity_sha256: str = field(init=False)
775
+
776
+ def __post_init__(self) -> None:
777
+ require_sha256(self.action_sha256, "action_sha256")
778
+ _require_probability(
779
+ self.source_opportunity,
780
+ name="source_opportunity",
781
+ )
782
+ _require_probability(
783
+ self.archive_opportunity_scale,
784
+ name="archive_opportunity_scale",
785
+ )
786
+ if self.archive_opportunity_scale <= 0.0:
787
+ raise ValueError("archive_opportunity_scale must be positive")
788
+ if self.source_opportunity > self.archive_opportunity_scale:
789
+ raise ValueError(
790
+ "source opportunity cannot exceed the archive opportunity scale"
791
+ )
792
+ require_sha256(self.evidence_sha256, "evidence_sha256")
793
+ object.__setattr__(
794
+ self,
795
+ "opportunity_sha256",
796
+ _hash(
797
+ _ACTION_OPPORTUNITY_EVIDENCE_DOMAIN,
798
+ self._unsigned_record(),
799
+ ),
800
+ )
801
+
802
+ @property
803
+ def relative_source_opportunity(self) -> float:
804
+ return self.source_opportunity / self.archive_opportunity_scale
805
+
806
+ def _unsigned_record(self) -> dict[str, object]:
807
+ return {
808
+ "schema_version": 1,
809
+ "action_sha256": self.action_sha256,
810
+ "source_opportunity_hex": self.source_opportunity.hex(),
811
+ "archive_opportunity_scale_hex": (self.archive_opportunity_scale.hex()),
812
+ "evidence_sha256": self.evidence_sha256,
813
+ }
814
+
815
+ def to_record(self) -> dict[str, object]:
816
+ self.__post_init__()
817
+ return {
818
+ **self._unsigned_record(),
819
+ "relative_source_opportunity_hex": (self.relative_source_opportunity.hex()),
820
+ "opportunity_sha256": self.opportunity_sha256,
821
+ }
822
+
823
+
824
+ @runtime_checkable
825
+ class MaterializedActionOpportunityPort(Protocol):
826
+ """Project strictly-prior archive geometry into one portable scalar."""
827
+
828
+ definition_sha256: str
829
+
830
+ def estimate(
831
+ self,
832
+ action: MaterializedActionDescriptor,
833
+ ) -> MaterializedActionOpportunityEvidence | None: ...
834
+
835
+
836
+ @dataclass(frozen=True, slots=True)
837
+ class OpportunityConditionedMaterializedActionReturnValue:
838
+ """Condition return on source opportunity and cap uncertainty in its units.
839
+
840
+ Parent opportunity is bounded evidence about the size of the source basin,
841
+ not outcome credit. It can multiply the predicted mean by at most
842
+ ``maximum_parent_multiplier``. The same current-archive scale caps the
843
+ standard deviation that otherwise defaults to a unit-interval worst case,
844
+ preventing an unobserved arm from receiving orders-of-magnitude more
845
+ information value solely because its prior is absent.
846
+ """
847
+
848
+ base: MaterializedActionReturnValuePort = field(
849
+ repr=False,
850
+ compare=False,
851
+ )
852
+ opportunity: MaterializedActionOpportunityPort = field(
853
+ repr=False,
854
+ compare=False,
855
+ )
856
+ maximum_parent_multiplier: float = 2.0
857
+ definition_sha256: str = field(init=False)
858
+
859
+ def __post_init__(self) -> None:
860
+ if not isinstance(self.base, MaterializedActionReturnValuePort):
861
+ raise TypeError("base must implement MaterializedActionReturnValuePort")
862
+ if not isinstance(
863
+ self.opportunity,
864
+ MaterializedActionOpportunityPort,
865
+ ):
866
+ raise TypeError(
867
+ "opportunity must implement MaterializedActionOpportunityPort"
868
+ )
869
+ require_sha256(self.base.definition_sha256, "base definition_sha256")
870
+ require_sha256(
871
+ self.opportunity.definition_sha256,
872
+ "opportunity definition_sha256",
873
+ )
874
+ if (
875
+ type(self.maximum_parent_multiplier) is not float
876
+ or not math.isfinite(self.maximum_parent_multiplier)
877
+ or not 1.0 <= self.maximum_parent_multiplier <= 2.0
878
+ ):
879
+ raise ValueError(
880
+ "maximum_parent_multiplier must be a finite float in [1, 2]"
881
+ )
882
+ object.__setattr__(
883
+ self,
884
+ "definition_sha256",
885
+ _hash(
886
+ _OPPORTUNITY_CONDITIONED_RETURN_VALUE_DOMAIN,
887
+ {
888
+ "schema_version": 1,
889
+ "base_definition_sha256": self.base.definition_sha256,
890
+ "opportunity_definition_sha256": (
891
+ self.opportunity.definition_sha256
892
+ ),
893
+ "maximum_parent_multiplier_hex": (
894
+ self.maximum_parent_multiplier.hex()
895
+ ),
896
+ "mean": (
897
+ "base_mean_times_one_plus_bounded_relative_source_opportunity"
898
+ ),
899
+ "uncertainty": (
900
+ "minimum_of_base_standard_deviation_and_maximum_of_"
901
+ "conditioned_mean_and_archive_opportunity_scale"
902
+ ),
903
+ "strictly_prior_only": True,
904
+ "workload_model_provider_branches": False,
905
+ },
906
+ ),
907
+ )
908
+
909
+ def estimate(
910
+ self,
911
+ action: MaterializedActionDescriptor,
912
+ ) -> BrokerReturnEstimate:
913
+ if type(action) is not MaterializedActionDescriptor:
914
+ raise TypeError("action must be an exact MaterializedActionDescriptor")
915
+ base = self.base.estimate(action)
916
+ if type(base) is not BrokerReturnEstimate:
917
+ raise TypeError("base return value produced a foreign estimate")
918
+ base.__post_init__()
919
+ opportunity = self.opportunity.estimate(action)
920
+ if opportunity is None:
921
+ return base
922
+ if type(opportunity) is not MaterializedActionOpportunityEvidence:
923
+ raise TypeError("opportunity port produced foreign evidence")
924
+ opportunity.__post_init__()
925
+ if opportunity.action_sha256 != action.action_sha256:
926
+ raise ValueError("opportunity evidence identifies another action")
927
+ multiplier = (
928
+ 1.0
929
+ + (self.maximum_parent_multiplier - 1.0)
930
+ * opportunity.relative_source_opportunity
931
+ )
932
+ mean = min(1.0, base.mean * multiplier)
933
+ uncertainty_cap = max(
934
+ opportunity.archive_opportunity_scale,
935
+ mean,
936
+ )
937
+ return BrokerReturnEstimate(
938
+ mean=float(mean),
939
+ standard_deviation=float(min(base.standard_deviation, uncertainty_cap)),
940
+ local_count=base.local_count,
941
+ global_count=base.global_count,
942
+ resolved_count=base.resolved_count,
943
+ provisional_count=base.provisional_count,
944
+ local_mean=base.local_mean,
945
+ global_mean=base.global_mean,
946
+ shrinkage_weight=base.shrinkage_weight,
947
+ )
948
+
949
+
950
+ @dataclass(frozen=True, slots=True)
951
+ class EmpiricalBayesMaterializedActionReturnValue:
952
+ """Fuse a frozen portable meta-prior with strictly prior live outcomes."""
953
+
954
+ ledger: "MaterializedActionEvidenceLedger" = field(
955
+ repr=False,
956
+ compare=False,
957
+ )
958
+ prior: MaterializedActionReturnPriorPort = field(
959
+ repr=False,
960
+ compare=False,
961
+ )
962
+ hierarchical_kappa: float = 4.0
963
+ definition_sha256: str = field(init=False)
964
+
965
+ def __post_init__(self) -> None:
966
+ if type(self.ledger) is not MaterializedActionEvidenceLedger:
967
+ raise TypeError("ledger must be an exact MaterializedActionEvidenceLedger")
968
+ if not isinstance(self.prior, MaterializedActionReturnPriorPort):
969
+ raise TypeError("prior must implement MaterializedActionReturnPriorPort")
970
+ require_sha256(self.prior.definition_sha256, "prior definition_sha256")
971
+ if (
972
+ type(self.hierarchical_kappa) is not float
973
+ or not math.isfinite(self.hierarchical_kappa)
974
+ or self.hierarchical_kappa <= 0.0
975
+ ):
976
+ raise ValueError("hierarchical_kappa must be a positive finite float")
977
+ object.__setattr__(
978
+ self,
979
+ "definition_sha256",
980
+ _hash(
981
+ _EMPIRICAL_BAYES_RETURN_VALUE_DOMAIN,
982
+ {
983
+ "schema_version": 1,
984
+ "prior_definition_sha256": self.prior.definition_sha256,
985
+ "hierarchical_kappa_hex": self.hierarchical_kappa.hex(),
986
+ "fusion": (
987
+ "effective-sample-size-weighted-mean-and-total-variance"
988
+ ),
989
+ "live_evidence_cutoff": "strictly_prior_decision_index",
990
+ "workload_model_provider_branches": False,
991
+ },
992
+ ),
993
+ )
994
+
995
+ def estimate(
996
+ self,
997
+ action: MaterializedActionDescriptor,
998
+ ) -> BrokerReturnEstimate:
999
+ if type(action) is not MaterializedActionDescriptor:
1000
+ raise TypeError("action must be an exact MaterializedActionDescriptor")
1001
+ empirical = self.ledger.estimate_return(
1002
+ action,
1003
+ kappa=self.hierarchical_kappa,
1004
+ )
1005
+ prediction = self.prior.predict(action)
1006
+ if prediction is None:
1007
+ return empirical
1008
+ if type(prediction) is not MaterializedActionReturnPriorPrediction:
1009
+ raise TypeError("return prior produced a foreign prediction")
1010
+ prediction.__post_init__()
1011
+ if prediction.action_sha256 != action.action_sha256:
1012
+ raise ValueError("return prior prediction identifies another action")
1013
+
1014
+ empirical_weight = empirical.global_count / (
1015
+ empirical.global_count + prediction.effective_sample_size
1016
+ )
1017
+ prior_weight = 1.0 - empirical_weight
1018
+ mean = prior_weight * prediction.mean + empirical_weight * empirical.mean
1019
+ variance = prior_weight * (
1020
+ prediction.standard_deviation**2 + (prediction.mean - mean) ** 2
1021
+ ) + empirical_weight * (
1022
+ empirical.standard_deviation**2 + (empirical.mean - mean) ** 2
1023
+ )
1024
+ return BrokerReturnEstimate(
1025
+ mean=float(min(1.0, max(0.0, mean))),
1026
+ standard_deviation=float(min(1.0, math.sqrt(max(0.0, variance)))),
1027
+ local_count=empirical.local_count,
1028
+ global_count=empirical.global_count,
1029
+ resolved_count=empirical.resolved_count,
1030
+ provisional_count=empirical.provisional_count,
1031
+ local_mean=empirical.local_mean,
1032
+ global_mean=empirical.global_mean,
1033
+ shrinkage_weight=empirical.shrinkage_weight,
1034
+ )
1035
+
1036
+
1037
+ @dataclass(slots=True)
1038
+ class MaterializedActionEvidenceLedger:
1039
+ """Append-only immediate and delayed evidence used by the broker."""
1040
+
1041
+ outcomes: list[MaterializedActionOutcome] = field(default_factory=list)
1042
+ delayed_credits: list[MaterializedActionDelayedCredit] = field(default_factory=list)
1043
+ resolved_returns: list[MaterializedActionResolvedReturn] = field(
1044
+ default_factory=list
1045
+ )
1046
+
1047
+ def append_outcome(self, value: MaterializedActionOutcome) -> None:
1048
+ if type(value) is not MaterializedActionOutcome:
1049
+ raise TypeError("value must be an exact MaterializedActionOutcome")
1050
+ value.__post_init__()
1051
+ if any(
1052
+ item.action.action_sha256 == value.action.action_sha256
1053
+ for item in self.outcomes
1054
+ ):
1055
+ raise ValueError(
1056
+ "one materialized action can have only one immediate outcome"
1057
+ )
1058
+ self.outcomes.append(value)
1059
+
1060
+ def append_delayed_credit(self, value: MaterializedActionDelayedCredit) -> None:
1061
+ if type(value) is not MaterializedActionDelayedCredit:
1062
+ raise TypeError("value must be an exact MaterializedActionDelayedCredit")
1063
+ value.__post_init__()
1064
+ if not any(
1065
+ item.outcome_sha256 == value.outcome.outcome_sha256
1066
+ for item in self.outcomes
1067
+ ):
1068
+ raise ValueError(
1069
+ "delayed credit source is absent from the immediate ledger"
1070
+ )
1071
+ for existing in self.delayed_credits:
1072
+ if existing.outcome.outcome_sha256 != value.outcome.outcome_sha256:
1073
+ continue
1074
+ for name in (
1075
+ "stage_front_survived",
1076
+ "terminal_front_persisted",
1077
+ "useful_descendant_observed",
1078
+ ):
1079
+ if (
1080
+ getattr(existing, name) is not None
1081
+ and getattr(value, name) is not None
1082
+ ):
1083
+ raise ValueError("a delayed channel can be adjudicated only once")
1084
+ self.delayed_credits.append(value)
1085
+
1086
+ def append_resolved_return(self, value: MaterializedActionResolvedReturn) -> None:
1087
+ if type(value) is not MaterializedActionResolvedReturn:
1088
+ raise TypeError("value must be an exact MaterializedActionResolvedReturn")
1089
+ value.__post_init__()
1090
+ if not any(
1091
+ item.outcome_sha256 == value.outcome.outcome_sha256
1092
+ for item in self.outcomes
1093
+ ):
1094
+ raise ValueError("resolved return source is absent from the outcome ledger")
1095
+ prior = tuple(
1096
+ item
1097
+ for item in self.resolved_returns
1098
+ if item.outcome.outcome_sha256 == value.outcome.outcome_sha256
1099
+ )
1100
+ if prior:
1101
+ latest = max(prior, key=lambda item: item.available_at_decision_index)
1102
+ if value.available_at_decision_index <= latest.available_at_decision_index:
1103
+ raise ValueError("return resolutions must advance their availability")
1104
+ if value.horizon_end_decision_index < latest.horizon_end_decision_index:
1105
+ raise ValueError("return resolution horizon cannot move backward")
1106
+ if latest.fully_resolved:
1107
+ raise ValueError("a fully resolved return cannot be revised")
1108
+ self.resolved_returns.append(value)
1109
+
1110
+ @staticmethod
1111
+ def _arm_matches(
1112
+ action: MaterializedActionDescriptor,
1113
+ target: MaterializedActionDescriptor,
1114
+ ) -> bool:
1115
+ return action.arm_key == target.arm_key
1116
+
1117
+ def _values(
1118
+ self,
1119
+ action: MaterializedActionDescriptor,
1120
+ channel: BrokerEvidenceChannel,
1121
+ *,
1122
+ local: bool,
1123
+ ) -> tuple[float, ...]:
1124
+ def eligible(source: MaterializedActionDescriptor) -> bool:
1125
+ return self._arm_matches(source, action) and (
1126
+ not local
1127
+ or source.context.state_signature_sha256
1128
+ == action.context.state_signature_sha256
1129
+ )
1130
+
1131
+ values: list[float] = []
1132
+ if channel is BrokerEvidenceChannel.REALIZATION:
1133
+ return tuple(
1134
+ 1.0 if outcome.realized else 0.0
1135
+ for outcome in self.outcomes
1136
+ if eligible(outcome.action)
1137
+ )
1138
+ if channel in {
1139
+ BrokerEvidenceChannel.FEASIBILITY,
1140
+ BrokerEvidenceChannel.GAIN,
1141
+ BrokerEvidenceChannel.POSITIVE,
1142
+ BrokerEvidenceChannel.FORECAST_ERROR,
1143
+ }:
1144
+ for outcome in self.outcomes:
1145
+ if not eligible(outcome.action) or not outcome.realized:
1146
+ continue
1147
+ if channel is BrokerEvidenceChannel.FEASIBILITY:
1148
+ assert outcome.feasible is not None
1149
+ values.append(1.0 if outcome.feasible else 0.0)
1150
+ elif channel is BrokerEvidenceChannel.GAIN:
1151
+ assert outcome.normalized_archive_gain is not None
1152
+ values.append(outcome.normalized_archive_gain)
1153
+ elif channel is BrokerEvidenceChannel.POSITIVE:
1154
+ assert outcome.positive_marginal_utility is not None
1155
+ values.append(1.0 if outcome.positive_marginal_utility else 0.0)
1156
+ elif outcome.normalized_forecast_error is not None:
1157
+ values.append(outcome.normalized_forecast_error)
1158
+ return tuple(values)
1159
+ field_name = {
1160
+ BrokerEvidenceChannel.STAGE_SURVIVAL: "stage_front_survived",
1161
+ BrokerEvidenceChannel.TERMINAL_PERSISTENCE: "terminal_front_persisted",
1162
+ BrokerEvidenceChannel.DESCENDANT: "useful_descendant_observed",
1163
+ }[channel]
1164
+ for credit in self.delayed_credits:
1165
+ if not eligible(credit.outcome.action):
1166
+ continue
1167
+ verdict = getattr(credit, field_name)
1168
+ if verdict is not None:
1169
+ values.append(1.0 if verdict else 0.0)
1170
+ return tuple(values)
1171
+
1172
+ def estimate(
1173
+ self,
1174
+ action: MaterializedActionDescriptor,
1175
+ channel: BrokerEvidenceChannel,
1176
+ *,
1177
+ kappa: float,
1178
+ ) -> BrokerChannelEstimate:
1179
+ if type(action) is not MaterializedActionDescriptor:
1180
+ raise TypeError("action must be an exact MaterializedActionDescriptor")
1181
+ if type(channel) is not BrokerEvidenceChannel:
1182
+ raise TypeError("channel must be an exact BrokerEvidenceChannel")
1183
+ if type(kappa) is not float or not math.isfinite(kappa) or kappa <= 0.0:
1184
+ raise ValueError("kappa must be a positive finite exact float")
1185
+ local_values = self._values(action, channel, local=True)
1186
+ global_values = self._values(action, channel, local=False)
1187
+ local_mean, local_sd, local_count_float = _bounded_empirical(local_values)
1188
+ global_mean, global_sd, global_count_float = _bounded_empirical(global_values)
1189
+ local_count = int(local_count_float)
1190
+ global_count = int(global_count_float)
1191
+ weight = local_count / (local_count + kappa)
1192
+ mean = weight * local_mean + (1.0 - weight) * global_mean
1193
+ sd = math.sqrt(weight * local_sd**2 + (1.0 - weight) * global_sd**2)
1194
+ return BrokerChannelEstimate(
1195
+ channel=channel,
1196
+ mean=float(mean),
1197
+ standard_deviation=float(min(1.0, sd)),
1198
+ local_count=local_count,
1199
+ global_count=global_count,
1200
+ local_mean=float(local_mean),
1201
+ global_mean=float(global_mean),
1202
+ shrinkage_weight=float(weight),
1203
+ )
1204
+
1205
+ def _return_values(
1206
+ self,
1207
+ action: MaterializedActionDescriptor,
1208
+ *,
1209
+ local: bool,
1210
+ ) -> tuple[tuple[float, bool], ...]:
1211
+ """Return prior-only values and whether each is lineage-resolved."""
1212
+
1213
+ cutoff = action.context.decision_index
1214
+ newest: dict[str, MaterializedActionResolvedReturn] = {}
1215
+ for value in self.resolved_returns:
1216
+ if value.available_at_decision_index >= cutoff:
1217
+ continue
1218
+ key = value.outcome.outcome_sha256
1219
+ prior = newest.get(key)
1220
+ if prior is None or (
1221
+ value.available_at_decision_index > prior.available_at_decision_index
1222
+ ):
1223
+ newest[key] = value
1224
+ values: list[tuple[float, bool]] = []
1225
+ for outcome in self.outcomes:
1226
+ source = outcome.action
1227
+ if source.context.decision_index >= cutoff:
1228
+ continue
1229
+ if not self._arm_matches(source, action):
1230
+ continue
1231
+ if local and (
1232
+ source.context.state_signature_sha256
1233
+ != action.context.state_signature_sha256
1234
+ ):
1235
+ continue
1236
+ resolved = newest.get(outcome.outcome_sha256)
1237
+ if resolved is not None:
1238
+ values.append((resolved.normalized_horizon_return, True))
1239
+ continue
1240
+ # Immediate real archive gain is a censored lower-bound observation
1241
+ # until a lineage resolver publishes a strictly later resolution.
1242
+ provisional = (
1243
+ 0.0 if not outcome.realized else outcome.normalized_archive_gain
1244
+ )
1245
+ assert provisional is not None
1246
+ values.append((provisional, False))
1247
+ return tuple(values)
1248
+
1249
+ def estimate_return(
1250
+ self,
1251
+ action: MaterializedActionDescriptor,
1252
+ *,
1253
+ kappa: float,
1254
+ ) -> BrokerReturnEstimate:
1255
+ if type(action) is not MaterializedActionDescriptor:
1256
+ raise TypeError("action must be an exact MaterializedActionDescriptor")
1257
+ if type(kappa) is not float or not math.isfinite(kappa) or kappa <= 0.0:
1258
+ raise ValueError("kappa must be a positive finite exact float")
1259
+ local_rows = self._return_values(action, local=True)
1260
+ global_rows = self._return_values(action, local=False)
1261
+ local_values = tuple(value for value, _resolved in local_rows)
1262
+ global_values = tuple(value for value, _resolved in global_rows)
1263
+ local_mean, local_sd, local_count_float = _bounded_empirical(local_values)
1264
+ global_mean, global_sd, global_count_float = _bounded_empirical(global_values)
1265
+ local_count = int(local_count_float)
1266
+ global_count = int(global_count_float)
1267
+ weight = local_count / (local_count + kappa)
1268
+ mean = weight * local_mean + (1.0 - weight) * global_mean
1269
+ sd = math.sqrt(weight * local_sd**2 + (1.0 - weight) * global_sd**2)
1270
+ resolved_count = sum(resolved for _value, resolved in global_rows)
1271
+ return BrokerReturnEstimate(
1272
+ mean=float(mean),
1273
+ standard_deviation=float(min(1.0, sd)),
1274
+ local_count=local_count,
1275
+ global_count=global_count,
1276
+ resolved_count=resolved_count,
1277
+ provisional_count=global_count - resolved_count,
1278
+ local_mean=float(local_mean),
1279
+ global_mean=float(global_mean),
1280
+ shrinkage_weight=float(weight),
1281
+ )
1282
+
1283
+
1284
+ @dataclass(frozen=True, slots=True)
1285
+ class BrokerActionScore:
1286
+ action_sha256: str
1287
+ value: float
1288
+ lower_confidence_bound: float
1289
+ upper_confidence_bound: float
1290
+ selection_index: float
1291
+ return_estimator_definition_sha256: str
1292
+ return_estimate: BrokerReturnEstimate
1293
+ estimates: tuple[BrokerChannelEstimate, ...]
1294
+
1295
+ def __post_init__(self) -> None:
1296
+ require_sha256(self.action_sha256, "action_sha256")
1297
+ for name in (
1298
+ "value",
1299
+ "lower_confidence_bound",
1300
+ "upper_confidence_bound",
1301
+ "selection_index",
1302
+ ):
1303
+ value = getattr(self, name)
1304
+ if type(value) is not float or not math.isfinite(value):
1305
+ raise TypeError(f"{name} must be a finite exact float")
1306
+ if self.lower_confidence_bound > self.value:
1307
+ raise ValueError("lower confidence bound exceeds value")
1308
+ if self.upper_confidence_bound < self.value:
1309
+ raise ValueError("upper confidence bound is below value")
1310
+ if not 0.0 <= self.selection_index <= 1.0:
1311
+ raise ValueError("selection_index must lie in [0, 1]")
1312
+ require_sha256(
1313
+ self.return_estimator_definition_sha256,
1314
+ "return_estimator_definition_sha256",
1315
+ )
1316
+ if type(self.return_estimate) is not BrokerReturnEstimate:
1317
+ raise TypeError("return_estimate must be exact")
1318
+ self.return_estimate.__post_init__()
1319
+ if type(self.estimates) is not tuple or tuple(
1320
+ item.channel for item in self.estimates
1321
+ ) != tuple(BrokerEvidenceChannel):
1322
+ raise ValueError("estimates must cover every channel in canonical order")
1323
+
1324
+ def to_record(self) -> dict[str, object]:
1325
+ self.__post_init__()
1326
+ return {
1327
+ "action_sha256": self.action_sha256,
1328
+ "value_hex": self.value.hex(),
1329
+ "lower_confidence_bound_hex": self.lower_confidence_bound.hex(),
1330
+ "upper_confidence_bound_hex": self.upper_confidence_bound.hex(),
1331
+ "selection_index_hex": self.selection_index.hex(),
1332
+ "return_estimator_definition_sha256": (
1333
+ self.return_estimator_definition_sha256
1334
+ ),
1335
+ "return_estimate": self.return_estimate.to_record(),
1336
+ "estimates": [value.to_record() for value in self.estimates],
1337
+ }
1338
+
1339
+
1340
+ @runtime_checkable
1341
+ class MaterializedSlateValuePort(Protocol):
1342
+ """Predict residual complementarity not explained by member returns.
1343
+
1344
+ The value must lie in ``[0, 1]``. Zero means that the member-level return
1345
+ posterior completely explains the slate. One means that the slate is
1346
+ expected to capture all return headroom remaining after additive member
1347
+ credit. This closed meaning composes with coalition-efficient action
1348
+ returns without a hand-authored joint/individual mixing weight.
1349
+ """
1350
+
1351
+ definition_sha256: str
1352
+
1353
+ def value(self, actions: tuple[MaterializedActionDescriptor, ...]) -> float: ...
1354
+
1355
+
1356
+ @runtime_checkable
1357
+ class MaterializedSlateFeasibilityPort(Protocol):
1358
+ """Apply exact generic materialization constraints to one slate."""
1359
+
1360
+ definition_sha256: str
1361
+
1362
+ def permits(self, actions: tuple[MaterializedActionDescriptor, ...]) -> bool: ...
1363
+
1364
+
1365
+ @dataclass(frozen=True, slots=True)
1366
+ class MaterializedActionExplorationRequirement:
1367
+ """Authenticated actions reserved by a strictly prequential policy."""
1368
+
1369
+ policy_id: str
1370
+ policy_version: int
1371
+ policy_definition_sha256: str
1372
+ required_action_sha256s: tuple[str, ...]
1373
+ prior_outcome_count: int
1374
+ cold_start: bool
1375
+ evidence: FrozenJsonObject
1376
+ requirement_sha256: str = field(init=False)
1377
+
1378
+ def __post_init__(self) -> None:
1379
+ _require_token(self.policy_id, name="policy_id")
1380
+ if type(self.policy_version) is not int or self.policy_version <= 0:
1381
+ raise ValueError("policy_version must be a positive exact integer")
1382
+ require_sha256(
1383
+ self.policy_definition_sha256,
1384
+ "policy_definition_sha256",
1385
+ )
1386
+ if type(self.required_action_sha256s) is not tuple:
1387
+ raise TypeError("required action hashes must be an exact tuple")
1388
+ if self.required_action_sha256s != tuple(
1389
+ sorted(set(self.required_action_sha256s))
1390
+ ):
1391
+ raise ValueError("required action hashes must be unique and canonical")
1392
+ for value in self.required_action_sha256s:
1393
+ require_sha256(value, "required action sha256")
1394
+ if type(self.prior_outcome_count) is not int or self.prior_outcome_count < 0:
1395
+ raise ValueError("prior_outcome_count must be non-negative")
1396
+ if type(self.cold_start) is not bool:
1397
+ raise TypeError("cold_start must be an exact bool")
1398
+ if self.cold_start != (self.prior_outcome_count == 0):
1399
+ raise ValueError("cold_start must exactly reflect prior outcome count")
1400
+ if (
1401
+ type(self.evidence) is not FrozenJsonObject
1402
+ or freeze_json(self.evidence) is not self.evidence
1403
+ ):
1404
+ raise TypeError("exploration evidence must be an exact frozen object")
1405
+ object.__setattr__(
1406
+ self,
1407
+ "requirement_sha256",
1408
+ _hash(
1409
+ _EXPLORATION_REQUIREMENT_DOMAIN,
1410
+ self._unsigned_record(),
1411
+ ),
1412
+ )
1413
+
1414
+ def _unsigned_record(self) -> dict[str, object]:
1415
+ return {
1416
+ "schema_version": 1,
1417
+ "policy": {
1418
+ "policy_id": self.policy_id,
1419
+ "policy_version": self.policy_version,
1420
+ "definition_sha256": self.policy_definition_sha256,
1421
+ },
1422
+ "required_action_sha256s": list(self.required_action_sha256s),
1423
+ "prior_outcome_count": self.prior_outcome_count,
1424
+ "cold_start": self.cold_start,
1425
+ "evidence_sha256": typed_json_sha256(self.evidence),
1426
+ "strictly_prior_outcomes_only": True,
1427
+ }
1428
+
1429
+ def to_record(self, *, include_evidence: bool = False) -> dict[str, object]:
1430
+ self.__post_init__()
1431
+ record = {
1432
+ **self._unsigned_record(),
1433
+ "requirement_sha256": self.requirement_sha256,
1434
+ }
1435
+ if include_evidence:
1436
+ record["evidence"] = thaw_json(self.evidence)
1437
+ return record
1438
+
1439
+
1440
+ @dataclass(frozen=True, slots=True)
1441
+ class MaterializedActionAllocationRequirement:
1442
+ """Authenticated outcome-blind constraint produced after proposal sealing.
1443
+
1444
+ The application core treats the policy and its evidence as opaque. The
1445
+ hashes bind the requirement to one residual request and one exact proposal
1446
+ universe, while ``candidate_outcomes_observed`` makes the information
1447
+ boundary explicit and mechanically rejects post-hoc selectors.
1448
+ """
1449
+
1450
+ policy_id: str
1451
+ policy_version: int
1452
+ policy_definition_sha256: str
1453
+ residual_request_sha256: str
1454
+ proposal_sha256s: tuple[str, ...]
1455
+ required_action_sha256s: tuple[str, ...]
1456
+ candidate_outcomes_observed: bool
1457
+ evidence: FrozenJsonObject
1458
+ requirement_sha256: str = field(init=False)
1459
+
1460
+ def __post_init__(self) -> None:
1461
+ _require_token(self.policy_id, name="policy_id")
1462
+ if type(self.policy_version) is not int or self.policy_version <= 0:
1463
+ raise ValueError("policy_version must be a positive exact integer")
1464
+ require_sha256(
1465
+ self.policy_definition_sha256,
1466
+ "policy_definition_sha256",
1467
+ )
1468
+ require_sha256(
1469
+ self.residual_request_sha256,
1470
+ "residual_request_sha256",
1471
+ )
1472
+ if type(self.proposal_sha256s) is not tuple or not self.proposal_sha256s:
1473
+ raise ValueError("proposal hashes must be a non-empty exact tuple")
1474
+ if self.proposal_sha256s != tuple(sorted(set(self.proposal_sha256s))):
1475
+ raise ValueError("proposal hashes must be unique and canonical")
1476
+ for value in self.proposal_sha256s:
1477
+ require_sha256(value, "proposal sha256")
1478
+ if type(self.required_action_sha256s) is not tuple:
1479
+ raise TypeError("required action hashes must be an exact tuple")
1480
+ if self.required_action_sha256s != tuple(
1481
+ sorted(set(self.required_action_sha256s))
1482
+ ):
1483
+ raise ValueError("required action hashes must be unique and canonical")
1484
+ for value in self.required_action_sha256s:
1485
+ require_sha256(value, "required action sha256")
1486
+ if type(self.candidate_outcomes_observed) is not bool:
1487
+ raise TypeError("candidate_outcomes_observed must be an exact bool")
1488
+ if self.candidate_outcomes_observed:
1489
+ raise ValueError(
1490
+ "allocation requirements cannot observe candidate outcomes"
1491
+ )
1492
+ if (
1493
+ type(self.evidence) is not FrozenJsonObject
1494
+ or freeze_json(self.evidence) is not self.evidence
1495
+ ):
1496
+ raise TypeError("allocation evidence must be an exact frozen object")
1497
+ object.__setattr__(
1498
+ self,
1499
+ "requirement_sha256",
1500
+ _hash(
1501
+ _ALLOCATION_REQUIREMENT_DOMAIN,
1502
+ self._unsigned_record(),
1503
+ ),
1504
+ )
1505
+
1506
+ def _unsigned_record(self) -> dict[str, object]:
1507
+ return {
1508
+ "schema_version": 1,
1509
+ "policy": {
1510
+ "policy_id": self.policy_id,
1511
+ "policy_version": self.policy_version,
1512
+ "definition_sha256": self.policy_definition_sha256,
1513
+ },
1514
+ "residual_request_sha256": self.residual_request_sha256,
1515
+ "proposal_sha256s": list(self.proposal_sha256s),
1516
+ "required_action_sha256s": list(self.required_action_sha256s),
1517
+ "candidate_outcomes_observed": self.candidate_outcomes_observed,
1518
+ "evidence_sha256": typed_json_sha256(self.evidence),
1519
+ }
1520
+
1521
+ def to_record(self, *, include_evidence: bool = False) -> dict[str, object]:
1522
+ self.__post_init__()
1523
+ record = {
1524
+ **self._unsigned_record(),
1525
+ "requirement_sha256": self.requirement_sha256,
1526
+ }
1527
+ if include_evidence:
1528
+ record["evidence"] = thaw_json(self.evidence)
1529
+ return record
1530
+
1531
+
1532
+ MaterializedActionAllocationConstraint = (
1533
+ MaterializedActionAllocationRequirement | AdaptiveActionAllocationDirective
1534
+ )
1535
+
1536
+
1537
+ def _validate_allocation_constraint(
1538
+ value: MaterializedActionAllocationConstraint,
1539
+ ) -> None:
1540
+ """Accept only the two authenticated allocation information boundaries."""
1541
+
1542
+ if type(value) not in (
1543
+ MaterializedActionAllocationRequirement,
1544
+ AdaptiveActionAllocationDirective,
1545
+ ):
1546
+ raise TypeError(
1547
+ "allocation_requirement must be an exact static requirement, "
1548
+ "an exact adaptive directive, or None"
1549
+ )
1550
+ value.__post_init__()
1551
+
1552
+
1553
+ @runtime_checkable
1554
+ class MaterializedActionExplorationPort(Protocol):
1555
+ """Reserve a bounded action subset using only current proposals and priors."""
1556
+
1557
+ policy_id: str
1558
+ policy_version: int
1559
+ definition_sha256: str
1560
+
1561
+ def require(
1562
+ self,
1563
+ request: "MaterializedActionBrokerRequest",
1564
+ ledger: MaterializedActionEvidenceLedger,
1565
+ required_reference_action_sha256s: tuple[str, ...],
1566
+ ) -> MaterializedActionExplorationRequirement: ...
1567
+
1568
+
1569
+ @dataclass(frozen=True, slots=True)
1570
+ class MaterializedActionBrokerRequest:
1571
+ actions: tuple[MaterializedActionDescriptor, ...]
1572
+ evaluation_slots: int
1573
+ slate_value: MaterializedSlateValuePort
1574
+ slate_feasibility: MaterializedSlateFeasibilityPort
1575
+ reference_escrow_slots: int = 1
1576
+ allocation_requirement: MaterializedActionAllocationConstraint | None = None
1577
+
1578
+ def __post_init__(self) -> None:
1579
+ if type(self.actions) is not tuple or not self.actions:
1580
+ raise ValueError("actions must be a non-empty exact tuple")
1581
+ for action in self.actions:
1582
+ if type(action) is not MaterializedActionDescriptor:
1583
+ raise TypeError("actions must contain exact descriptors")
1584
+ action.__post_init__()
1585
+ if len({value.action_sha256 for value in self.actions}) != len(self.actions):
1586
+ raise ValueError("action identities must be unique")
1587
+ contexts = {
1588
+ (value.context.campaign_scope_sha256, value.context.decision_index)
1589
+ for value in self.actions
1590
+ }
1591
+ if len(contexts) != 1:
1592
+ raise ValueError("one broker request cannot mix decision cutoffs")
1593
+ if type(
1594
+ self.evaluation_slots
1595
+ ) is not int or not 1 <= self.evaluation_slots <= len(self.actions):
1596
+ raise ValueError("evaluation_slots must fit the supplied action universe")
1597
+ if not isinstance(self.slate_value, MaterializedSlateValuePort):
1598
+ raise TypeError("slate_value must implement MaterializedSlateValuePort")
1599
+ if not isinstance(self.slate_feasibility, MaterializedSlateFeasibilityPort):
1600
+ raise TypeError(
1601
+ "slate_feasibility must implement MaterializedSlateFeasibilityPort"
1602
+ )
1603
+ require_sha256(self.slate_value.definition_sha256, "slate value definition")
1604
+ require_sha256(
1605
+ self.slate_feasibility.definition_sha256,
1606
+ "slate feasibility definition",
1607
+ )
1608
+ if (
1609
+ type(self.reference_escrow_slots) is not int
1610
+ or not 0 <= self.reference_escrow_slots <= self.evaluation_slots
1611
+ ):
1612
+ raise ValueError("reference_escrow_slots must fit the evaluation capacity")
1613
+ if self.allocation_requirement is not None:
1614
+ _validate_allocation_constraint(self.allocation_requirement)
1615
+ action_sha256s = {value.action_sha256 for value in self.actions}
1616
+ if not set(self.allocation_requirement.required_action_sha256s).issubset(
1617
+ action_sha256s
1618
+ ):
1619
+ raise ValueError(
1620
+ "allocation policy required an action outside the request"
1621
+ )
1622
+ if (
1623
+ len(self.allocation_requirement.required_action_sha256s)
1624
+ > self.evaluation_slots
1625
+ ):
1626
+ raise ValueError("allocation requirement exceeds evaluation capacity")
1627
+
1628
+
1629
+ @dataclass(frozen=True, slots=True)
1630
+ class MaterializedActionBrokerDecision:
1631
+ selected_actions: tuple[MaterializedActionDescriptor, ...]
1632
+ scores: tuple[BrokerActionScore, ...]
1633
+ required_reference_action_sha256s: tuple[str, ...]
1634
+ exploration_requirement: MaterializedActionExplorationRequirement | None
1635
+ allocation_requirement: MaterializedActionAllocationConstraint | None
1636
+ reference_displaced_count: int
1637
+ search_mode: str
1638
+ complete_slate_count_considered: int
1639
+ residual_complementarity_value: float
1640
+ exploration_action_sha256: str | None
1641
+ broker_definition_sha256: str = MATERIALIZED_ACTION_BROKER_DEFINITION_SHA256
1642
+ decision_sha256: str = field(init=False)
1643
+
1644
+ def __post_init__(self) -> None:
1645
+ if type(self.selected_actions) is not tuple or not self.selected_actions:
1646
+ raise ValueError("selected_actions must be a non-empty exact tuple")
1647
+ if len(
1648
+ {value.phenotype_identity_sha256 for value in self.selected_actions}
1649
+ ) != len(self.selected_actions):
1650
+ raise ValueError("selected actions must have unique phenotypes")
1651
+ if tuple(
1652
+ sorted(value.action_sha256 for value in self.selected_actions)
1653
+ ) != tuple(value.action_sha256 for value in self.selected_actions):
1654
+ raise ValueError("selected actions must use canonical action order")
1655
+ if type(self.scores) is not tuple or not self.scores:
1656
+ raise ValueError("scores must be a non-empty exact tuple")
1657
+ if type(self.required_reference_action_sha256s) is not tuple:
1658
+ raise TypeError("required reference identities must be an exact tuple")
1659
+ if tuple(sorted(set(self.required_reference_action_sha256s))) != (
1660
+ self.required_reference_action_sha256s
1661
+ ):
1662
+ raise ValueError("required reference identities must be unique/canonical")
1663
+ selected_sha256s = {value.action_sha256 for value in self.selected_actions}
1664
+ for value in self.required_reference_action_sha256s:
1665
+ require_sha256(value, "required reference action sha256")
1666
+ if value not in selected_sha256s:
1667
+ raise ValueError("required reference action is absent from the slate")
1668
+ if self.exploration_requirement is not None:
1669
+ if (
1670
+ type(self.exploration_requirement)
1671
+ is not MaterializedActionExplorationRequirement
1672
+ ):
1673
+ raise TypeError("exploration_requirement must be exact or None")
1674
+ self.exploration_requirement.__post_init__()
1675
+ if not set(self.exploration_requirement.required_action_sha256s).issubset(
1676
+ selected_sha256s
1677
+ ):
1678
+ raise ValueError("required exploration action is absent from the slate")
1679
+ if self.allocation_requirement is not None:
1680
+ _validate_allocation_constraint(self.allocation_requirement)
1681
+ if not set(self.allocation_requirement.required_action_sha256s).issubset(
1682
+ selected_sha256s
1683
+ ):
1684
+ raise ValueError("required allocation action is absent from the slate")
1685
+ if (
1686
+ type(self.reference_displaced_count) is not int
1687
+ or self.reference_displaced_count < 0
1688
+ ):
1689
+ raise ValueError("reference_displaced_count must be non-negative")
1690
+ _require_token(self.search_mode, name="search_mode")
1691
+ if (
1692
+ type(self.complete_slate_count_considered) is not int
1693
+ or self.complete_slate_count_considered <= 0
1694
+ ):
1695
+ raise ValueError("at least one complete slate must be considered")
1696
+ _require_probability(
1697
+ self.residual_complementarity_value,
1698
+ name="residual_complementarity_value",
1699
+ )
1700
+ if self.exploration_action_sha256 is not None:
1701
+ require_sha256(
1702
+ self.exploration_action_sha256,
1703
+ "exploration_action_sha256",
1704
+ )
1705
+ selected = {value.action_sha256: value for value in self.selected_actions}
1706
+ action = selected.get(self.exploration_action_sha256)
1707
+ if action is None:
1708
+ raise ValueError("exploration action is absent from selected slate")
1709
+ if action.context.phase is SearchPhase.TERMINAL_CONVERSION:
1710
+ raise ValueError("terminal decisions cannot purchase information")
1711
+ require_sha256(self.broker_definition_sha256, "broker_definition_sha256")
1712
+ object.__setattr__(
1713
+ self,
1714
+ "decision_sha256",
1715
+ _hash(_DECISION_DOMAIN, self._unsigned_record()),
1716
+ )
1717
+
1718
+ def _unsigned_record(self) -> dict[str, object]:
1719
+ return {
1720
+ "schema_version": 1,
1721
+ "broker_id": MATERIALIZED_ACTION_BROKER_ID,
1722
+ "broker_version": MATERIALIZED_ACTION_BROKER_VERSION,
1723
+ "broker_definition_sha256": self.broker_definition_sha256,
1724
+ "selected_action_sha256s": [
1725
+ value.action_sha256 for value in self.selected_actions
1726
+ ],
1727
+ "scores": [value.to_record() for value in self.scores],
1728
+ "required_reference_action_sha256s": list(
1729
+ self.required_reference_action_sha256s
1730
+ ),
1731
+ "exploration_requirement": (
1732
+ None
1733
+ if self.exploration_requirement is None
1734
+ else self.exploration_requirement.to_record()
1735
+ ),
1736
+ "allocation_requirement": (
1737
+ None
1738
+ if self.allocation_requirement is None
1739
+ else self.allocation_requirement.to_record()
1740
+ ),
1741
+ "reference_displaced_count": self.reference_displaced_count,
1742
+ "search_mode": self.search_mode,
1743
+ "complete_slate_count_considered": self.complete_slate_count_considered,
1744
+ "residual_complementarity_value_hex": (
1745
+ self.residual_complementarity_value.hex()
1746
+ ),
1747
+ "exploration_action_sha256": self.exploration_action_sha256,
1748
+ }
1749
+
1750
+ @property
1751
+ def required_reference_action_sha256(self) -> str | None:
1752
+ """Compatibility view for callers that escrow exactly one reference."""
1753
+
1754
+ if not self.required_reference_action_sha256s:
1755
+ return None
1756
+ return self.required_reference_action_sha256s[0]
1757
+
1758
+ @property
1759
+ def reference_displaced(self) -> bool:
1760
+ return self.reference_displaced_count > 0
1761
+
1762
+ def to_record(
1763
+ self,
1764
+ *,
1765
+ include_allocation_evidence: bool = False,
1766
+ ) -> dict[str, object]:
1767
+ self.__post_init__()
1768
+ record = {
1769
+ **self._unsigned_record(),
1770
+ "decision_sha256": self.decision_sha256,
1771
+ }
1772
+ if include_allocation_evidence and self.allocation_requirement is not None:
1773
+ record["allocation_requirement"] = self.allocation_requirement.to_record(
1774
+ include_evidence=True
1775
+ )
1776
+ return record
1777
+
1778
+
1779
+ @dataclass(frozen=True, slots=True)
1780
+ class RegretBrokeredMaterializedActionPolicy:
1781
+ """Broker normalized lineage return without cross-channel score weights."""
1782
+
1783
+ ledger: MaterializedActionEvidenceLedger
1784
+ return_value: MaterializedActionReturnValuePort | None = None
1785
+ exploration_policy: MaterializedActionExplorationPort | None = None
1786
+ hierarchical_kappa: float = 4.0
1787
+ confidence_width: float = 1.0
1788
+ exact_combination_limit: int = 250_000
1789
+ beam_width: int = 512
1790
+
1791
+ def __post_init__(self) -> None:
1792
+ if type(self.ledger) is not MaterializedActionEvidenceLedger:
1793
+ raise TypeError("ledger must be an exact MaterializedActionEvidenceLedger")
1794
+ if self.return_value is not None:
1795
+ if not isinstance(self.return_value, MaterializedActionReturnValuePort):
1796
+ raise TypeError(
1797
+ "return_value must implement MaterializedActionReturnValuePort"
1798
+ )
1799
+ require_sha256(
1800
+ self.return_value.definition_sha256,
1801
+ "return value definition_sha256",
1802
+ )
1803
+ if self.exploration_policy is not None:
1804
+ if not isinstance(
1805
+ self.exploration_policy,
1806
+ MaterializedActionExplorationPort,
1807
+ ):
1808
+ raise TypeError(
1809
+ "exploration_policy must implement "
1810
+ "MaterializedActionExplorationPort"
1811
+ )
1812
+ _require_token(
1813
+ self.exploration_policy.policy_id,
1814
+ name="exploration policy_id",
1815
+ )
1816
+ if (
1817
+ type(self.exploration_policy.policy_version) is not int
1818
+ or self.exploration_policy.policy_version <= 0
1819
+ ):
1820
+ raise ValueError("exploration policy_version must be positive")
1821
+ require_sha256(
1822
+ self.exploration_policy.definition_sha256,
1823
+ "exploration policy definition_sha256",
1824
+ )
1825
+ for name in ("hierarchical_kappa", "confidence_width"):
1826
+ value = getattr(self, name)
1827
+ if type(value) is not float or not math.isfinite(value) or value <= 0.0:
1828
+ raise ValueError(f"{name} must be a positive finite exact float")
1829
+ for name in ("exact_combination_limit", "beam_width"):
1830
+ value = getattr(self, name)
1831
+ if type(value) is not int or value <= 0:
1832
+ raise ValueError(f"{name} must be a positive exact integer")
1833
+
1834
+ def score(self, action: MaterializedActionDescriptor) -> BrokerActionScore:
1835
+ if self.return_value is None:
1836
+ return_estimate = self.ledger.estimate_return(
1837
+ action,
1838
+ kappa=self.hierarchical_kappa,
1839
+ )
1840
+ return_estimator_definition_sha256 = (
1841
+ EMPIRICAL_RETURN_ESTIMATOR_DEFINITION_SHA256
1842
+ )
1843
+ else:
1844
+ return_estimate = self.return_value.estimate(action)
1845
+ if type(return_estimate) is not BrokerReturnEstimate:
1846
+ raise TypeError("return value port returned a foreign estimate")
1847
+ return_estimate.__post_init__()
1848
+ return_estimator_definition_sha256 = self.return_value.definition_sha256
1849
+ estimates = tuple(
1850
+ self.ledger.estimate(
1851
+ action,
1852
+ channel,
1853
+ kappa=self.hierarchical_kappa,
1854
+ )
1855
+ for channel in BrokerEvidenceChannel
1856
+ )
1857
+ value = return_estimate.mean
1858
+ radius = self.confidence_width * return_estimate.standard_deviation
1859
+ lower = max(0.0, value - radius)
1860
+ upper = min(1.0, value + radius)
1861
+ # Information has no endpoint value when there is no future decision
1862
+ # that can consume it. Earlier waves use the posterior upper bound as
1863
+ # the action index; the terminal wave uses posterior mean exactly.
1864
+ selection_index = (
1865
+ value if action.context.phase is SearchPhase.TERMINAL_CONVERSION else upper
1866
+ )
1867
+ return BrokerActionScore(
1868
+ action_sha256=action.action_sha256,
1869
+ value=float(value),
1870
+ lower_confidence_bound=float(lower),
1871
+ upper_confidence_bound=float(upper),
1872
+ selection_index=float(selection_index),
1873
+ return_estimator_definition_sha256=(return_estimator_definition_sha256),
1874
+ return_estimate=return_estimate,
1875
+ estimates=estimates,
1876
+ )
1877
+
1878
+ @staticmethod
1879
+ def _canonical_slate(
1880
+ actions: tuple[MaterializedActionDescriptor, ...],
1881
+ ) -> tuple[MaterializedActionDescriptor, ...]:
1882
+ return tuple(sorted(actions, key=lambda value: value.action_sha256))
1883
+
1884
+ @staticmethod
1885
+ def _cold_start_tie_key(
1886
+ actions: tuple[MaterializedActionDescriptor, ...],
1887
+ ) -> tuple[tuple[int, ...], int, tuple[str, ...]]:
1888
+ """Respect native expert order without comparing heterogeneous scores."""
1889
+
1890
+ return (
1891
+ tuple(sorted(value.native_rank for value in actions)),
1892
+ -len({value.expert_id for value in actions}),
1893
+ tuple(value.action_sha256 for value in actions),
1894
+ )
1895
+
1896
+ @staticmethod
1897
+ def _unique_phenotypes(
1898
+ actions: tuple[MaterializedActionDescriptor, ...],
1899
+ ) -> bool:
1900
+ return len({value.phenotype_identity_sha256 for value in actions}) == len(
1901
+ actions
1902
+ )
1903
+
1904
+ def _slate_score(
1905
+ self,
1906
+ actions: tuple[MaterializedActionDescriptor, ...],
1907
+ scores: dict[str, BrokerActionScore],
1908
+ value_port: MaterializedSlateValuePort,
1909
+ cache: dict[tuple[str, ...], tuple[float, float, str | None]],
1910
+ ) -> tuple[float, float, str | None]:
1911
+ identity = tuple(value.action_sha256 for value in actions)
1912
+ cached = cache.get(identity)
1913
+ if cached is not None:
1914
+ return cached
1915
+ residual_complementarity = value_port.value(actions)
1916
+ _require_probability(
1917
+ residual_complementarity,
1918
+ name="residual complementarity",
1919
+ )
1920
+ # Shapley-attributed archive returns are additive contributions, not
1921
+ # independent success probabilities. Sum their empirical means and
1922
+ # cap only at the normalized archive-return boundary.
1923
+ member_return = min(
1924
+ 1.0,
1925
+ math.fsum(scores[value.action_sha256].value for value in actions),
1926
+ )
1927
+
1928
+ # Information is a scarce action, not a bonus silently attached to
1929
+ # every member. At most one nonterminal member receives its optimistic
1930
+ # increment; all remaining slots are selected by empirical return.
1931
+ exploration_action_sha256: str | None = None
1932
+ if actions[0].context.phase is not SearchPhase.TERMINAL_CONVERSION:
1933
+ exploratory = max(
1934
+ actions,
1935
+ key=lambda value: (
1936
+ scores[value.action_sha256].selection_index
1937
+ - scores[value.action_sha256].value,
1938
+ scores[value.action_sha256].selection_index,
1939
+ value.action_sha256,
1940
+ ),
1941
+ )
1942
+ exploratory_score = scores[exploratory.action_sha256]
1943
+ exploration_increment = min(
1944
+ 1.0 - member_return,
1945
+ exploratory_score.selection_index - exploratory_score.value,
1946
+ )
1947
+ if exploration_increment > 0.0:
1948
+ member_return += exploration_increment
1949
+ exploration_action_sha256 = exploratory.action_sha256
1950
+
1951
+ slate_return = member_return + (1.0 - member_return) * residual_complementarity
1952
+ result = (
1953
+ float(slate_return),
1954
+ residual_complementarity,
1955
+ exploration_action_sha256,
1956
+ )
1957
+ cache[identity] = result
1958
+ return result
1959
+
1960
+ def _required_references(
1961
+ self,
1962
+ request: MaterializedActionBrokerRequest,
1963
+ scores: dict[str, BrokerActionScore],
1964
+ ) -> tuple[tuple[str, ...], int]:
1965
+ if request.reference_escrow_slots == 0:
1966
+ return (), 0
1967
+ references = tuple(value for value in request.actions if value.reference_action)
1968
+ nonreferences = tuple(
1969
+ value for value in request.actions if not value.reference_action
1970
+ )
1971
+ if not references:
1972
+ return (), 0
1973
+ ordered_references = sorted(
1974
+ references,
1975
+ key=lambda value: (
1976
+ value.native_rank,
1977
+ -scores[value.action_sha256].value,
1978
+ value.action_sha256,
1979
+ ),
1980
+ )
1981
+ protected = ordered_references[: request.reference_escrow_slots]
1982
+ if not nonreferences or not protected:
1983
+ return tuple(sorted(value.action_sha256 for value in protected)), 0
1984
+ challengers = sorted(
1985
+ nonreferences,
1986
+ key=lambda value: (
1987
+ -scores[value.action_sha256].lower_confidence_bound,
1988
+ -scores[value.action_sha256].value,
1989
+ value.action_sha256,
1990
+ ),
1991
+ )
1992
+ retained = list(protected)
1993
+ displaced_count = 0
1994
+ # Challenge the weakest protected reference first. A challenger only
1995
+ # removes escrow authority when its lower bound is strictly above that
1996
+ # reference's upper bound; the joint optimizer still decides whether
1997
+ # the challenger belongs in the final slate.
1998
+ for challenger, reference in zip(
1999
+ challengers,
2000
+ reversed(protected),
2001
+ strict=False,
2002
+ ):
2003
+ if (
2004
+ scores[challenger.action_sha256].lower_confidence_bound
2005
+ > scores[reference.action_sha256].upper_confidence_bound
2006
+ ):
2007
+ retained.remove(reference)
2008
+ displaced_count += 1
2009
+ return (
2010
+ tuple(sorted(value.action_sha256 for value in retained)),
2011
+ displaced_count,
2012
+ )
2013
+
2014
+ def _admissible(
2015
+ self,
2016
+ slate: tuple[MaterializedActionDescriptor, ...],
2017
+ request: MaterializedActionBrokerRequest,
2018
+ required_references: tuple[str, ...],
2019
+ cache: dict[tuple[tuple[str, ...], tuple[str, ...]], bool],
2020
+ ) -> bool:
2021
+ identity = tuple(value.action_sha256 for value in slate)
2022
+ cache_key = (identity, required_references)
2023
+ cached = cache.get(cache_key)
2024
+ if cached is not None:
2025
+ return cached
2026
+ result = (
2027
+ self._unique_phenotypes(slate)
2028
+ and (
2029
+ not required_references
2030
+ or set(required_references).issubset(
2031
+ {value.action_sha256 for value in slate}
2032
+ )
2033
+ )
2034
+ and request.slate_feasibility.permits(slate)
2035
+ )
2036
+ cache[cache_key] = result
2037
+ return result
2038
+
2039
+ def _exact_search(
2040
+ self,
2041
+ request: MaterializedActionBrokerRequest,
2042
+ scores: dict[str, BrokerActionScore],
2043
+ required_references: tuple[str, ...],
2044
+ slate_score_cache: dict[tuple[str, ...], tuple[float, float, str | None]],
2045
+ admissibility_cache: dict[tuple[tuple[str, ...], tuple[str, ...]], bool],
2046
+ ) -> tuple[tuple[MaterializedActionDescriptor, ...], float, str | None, int]:
2047
+ action_by_sha256 = {value.action_sha256: value for value in request.actions}
2048
+ if not set(required_references).issubset(action_by_sha256):
2049
+ raise ValueError("required action is absent from the broker request")
2050
+ required = tuple(action_by_sha256[value] for value in required_references)
2051
+ optional = tuple(
2052
+ value
2053
+ for value in request.actions
2054
+ if value.action_sha256 not in required_references
2055
+ )
2056
+ remaining_slots = request.evaluation_slots - len(required)
2057
+ if remaining_slots < 0:
2058
+ raise ValueError("required actions exceed evaluation capacity")
2059
+ best: tuple[MaterializedActionDescriptor, ...] | None = None
2060
+ best_score = -math.inf
2061
+ best_joint = 0.0
2062
+ best_exploration: str | None = None
2063
+ count = 0
2064
+ for raw in combinations(optional, remaining_slots):
2065
+ slate = self._canonical_slate((*required, *raw))
2066
+ if not self._admissible(
2067
+ slate,
2068
+ request,
2069
+ required_references,
2070
+ admissibility_cache,
2071
+ ):
2072
+ continue
2073
+ count += 1
2074
+ score, joint, exploration = self._slate_score(
2075
+ slate,
2076
+ scores,
2077
+ request.slate_value,
2078
+ slate_score_cache,
2079
+ )
2080
+ tie_key = self._cold_start_tie_key(slate)
2081
+ best_tie_key = None if best is None else self._cold_start_tie_key(best)
2082
+ if score > best_score or (
2083
+ score == best_score and (best_tie_key is None or tie_key < best_tie_key)
2084
+ ):
2085
+ best, best_score, best_joint, best_exploration = (
2086
+ slate,
2087
+ score,
2088
+ joint,
2089
+ exploration,
2090
+ )
2091
+ if best is None:
2092
+ raise ValueError("no feasible complete materialized-action slate exists")
2093
+ return best, best_joint, best_exploration, count
2094
+
2095
+ def _beam_search(
2096
+ self,
2097
+ request: MaterializedActionBrokerRequest,
2098
+ scores: dict[str, BrokerActionScore],
2099
+ required_references: tuple[str, ...],
2100
+ slate_score_cache: dict[tuple[str, ...], tuple[float, float, str | None]],
2101
+ admissibility_cache: dict[tuple[tuple[str, ...], tuple[str, ...]], bool],
2102
+ ) -> tuple[tuple[MaterializedActionDescriptor, ...], float, str | None, int]:
2103
+ action_by_sha256 = {value.action_sha256: value for value in request.actions}
2104
+ if not set(required_references).issubset(action_by_sha256):
2105
+ raise ValueError("required action is absent from the broker request")
2106
+ required = tuple(action_by_sha256[value] for value in required_references)
2107
+ ordered = tuple(
2108
+ sorted(
2109
+ (
2110
+ value
2111
+ for value in request.actions
2112
+ if value.action_sha256 not in required_references
2113
+ ),
2114
+ key=lambda value: value.action_sha256,
2115
+ )
2116
+ )
2117
+ remaining_slots = request.evaluation_slots - len(required)
2118
+ if remaining_slots < 0:
2119
+ raise ValueError("required actions exceed evaluation capacity")
2120
+ if not self._unique_phenotypes(required):
2121
+ raise ValueError("required actions repeat a materialized phenotype")
2122
+ beam: tuple[tuple[MaterializedActionDescriptor, ...], ...] = ((),)
2123
+ complete: dict[
2124
+ tuple[str, ...],
2125
+ tuple[
2126
+ tuple[MaterializedActionDescriptor, ...],
2127
+ float,
2128
+ float,
2129
+ str | None,
2130
+ ],
2131
+ ] = {}
2132
+ for _depth in range(remaining_slots):
2133
+ expanded: dict[
2134
+ tuple[str, ...], tuple[MaterializedActionDescriptor, ...]
2135
+ ] = {}
2136
+ for partial in beam:
2137
+ # ``partial`` is already canonical. Extending only with a
2138
+ # larger identity enumerates each unordered slate once instead
2139
+ # of revisiting every permutation before dictionary dedup.
2140
+ lower_bound = "" if not partial else partial[-1].action_sha256
2141
+ for action in ordered:
2142
+ if action.action_sha256 <= lower_bound:
2143
+ continue
2144
+ candidate = (*partial, action)
2145
+ candidate_slate = self._canonical_slate((*required, *candidate))
2146
+ if not self._unique_phenotypes(candidate_slate):
2147
+ continue
2148
+ remaining_after_candidate = remaining_slots - len(candidate)
2149
+ if remaining_after_candidate:
2150
+ used_phenotypes = {
2151
+ value.phenotype_identity_sha256 for value in candidate_slate
2152
+ }
2153
+ available_suffix_phenotypes = {
2154
+ value.phenotype_identity_sha256
2155
+ for value in ordered
2156
+ if (
2157
+ value.action_sha256 > action.action_sha256
2158
+ and value.phenotype_identity_sha256
2159
+ not in used_phenotypes
2160
+ )
2161
+ }
2162
+ if len(available_suffix_phenotypes) < remaining_after_candidate:
2163
+ # The canonical identity ordering is an
2164
+ # enumeration device, not a quality signal. A
2165
+ # bounded beam must not retain a high-scoring
2166
+ # late-identity partial that can no longer be
2167
+ # completed while pruning every feasible prefix.
2168
+ continue
2169
+ if not self._admissible(
2170
+ candidate_slate,
2171
+ request,
2172
+ required_references=(),
2173
+ cache=admissibility_cache,
2174
+ ):
2175
+ continue
2176
+ identity = tuple(value.action_sha256 for value in candidate)
2177
+ expanded[identity] = candidate
2178
+ ranked = sorted(
2179
+ expanded.values(),
2180
+ key=lambda partial: (
2181
+ -self._slate_score(
2182
+ self._canonical_slate((*required, *partial)),
2183
+ scores,
2184
+ request.slate_value,
2185
+ slate_score_cache,
2186
+ )[0],
2187
+ self._cold_start_tie_key(
2188
+ self._canonical_slate((*required, *partial))
2189
+ ),
2190
+ ),
2191
+ )
2192
+ beam = tuple(ranked[: self.beam_width])
2193
+ if not beam:
2194
+ break
2195
+ for partial in beam:
2196
+ slate = self._canonical_slate((*required, *partial))
2197
+ if len(slate) != request.evaluation_slots or not self._admissible(
2198
+ slate,
2199
+ request,
2200
+ required_references,
2201
+ admissibility_cache,
2202
+ ):
2203
+ continue
2204
+ score, joint, exploration = self._slate_score(
2205
+ slate,
2206
+ scores,
2207
+ request.slate_value,
2208
+ slate_score_cache,
2209
+ )
2210
+ identity = tuple(value.action_sha256 for value in slate)
2211
+ complete[identity] = (slate, score, joint, exploration)
2212
+ if not complete:
2213
+ raise ValueError(
2214
+ "beam search found no feasible complete materialized slate"
2215
+ )
2216
+ best = min(
2217
+ complete.values(),
2218
+ key=lambda value: (
2219
+ -value[1],
2220
+ self._cold_start_tie_key(value[0]),
2221
+ ),
2222
+ )
2223
+ return best[0], best[2], best[3], len(complete)
2224
+
2225
+ def select(
2226
+ self,
2227
+ request: MaterializedActionBrokerRequest,
2228
+ ) -> MaterializedActionBrokerDecision:
2229
+ if type(request) is not MaterializedActionBrokerRequest:
2230
+ raise TypeError("request must be an exact MaterializedActionBrokerRequest")
2231
+ request.__post_init__()
2232
+ scores = {value.action_sha256: self.score(value) for value in request.actions}
2233
+ required_references, displaced_count = self._required_references(
2234
+ request,
2235
+ scores,
2236
+ )
2237
+ exploration_requirement = (
2238
+ None
2239
+ if self.exploration_policy is None
2240
+ else self.exploration_policy.require(
2241
+ request,
2242
+ self.ledger,
2243
+ required_references,
2244
+ )
2245
+ )
2246
+ if exploration_requirement is not None:
2247
+ if (
2248
+ type(exploration_requirement)
2249
+ is not MaterializedActionExplorationRequirement
2250
+ ):
2251
+ raise TypeError("exploration policy returned a foreign requirement")
2252
+ exploration_requirement.__post_init__()
2253
+ if (
2254
+ exploration_requirement.policy_id != self.exploration_policy.policy_id
2255
+ or exploration_requirement.policy_version
2256
+ != self.exploration_policy.policy_version
2257
+ or exploration_requirement.policy_definition_sha256
2258
+ != self.exploration_policy.definition_sha256
2259
+ ):
2260
+ raise ValueError("exploration requirement differs from its policy")
2261
+ action_sha256s = {value.action_sha256 for value in request.actions}
2262
+ if not set(exploration_requirement.required_action_sha256s).issubset(
2263
+ action_sha256s
2264
+ ):
2265
+ raise ValueError(
2266
+ "exploration policy required an action outside the request"
2267
+ )
2268
+ required_exploration = (
2269
+ ()
2270
+ if exploration_requirement is None
2271
+ else exploration_requirement.required_action_sha256s
2272
+ )
2273
+ required_allocation = (
2274
+ ()
2275
+ if request.allocation_requirement is None
2276
+ else request.allocation_requirement.required_action_sha256s
2277
+ )
2278
+ required_actions = tuple(
2279
+ sorted(
2280
+ set(required_references)
2281
+ | set(required_exploration)
2282
+ | set(required_allocation)
2283
+ )
2284
+ )
2285
+ if len(required_actions) > request.evaluation_slots:
2286
+ raise ValueError("reference and exploration requirements exceed capacity")
2287
+ slate_score_cache: dict[tuple[str, ...], tuple[float, float, str | None]] = {}
2288
+ admissibility_cache: dict[tuple[tuple[str, ...], tuple[str, ...]], bool] = {}
2289
+ optional_action_count = len(request.actions) - len(required_actions)
2290
+ remaining_slot_count = request.evaluation_slots - len(required_actions)
2291
+ combination_count = math.comb(
2292
+ optional_action_count,
2293
+ remaining_slot_count,
2294
+ )
2295
+ if combination_count <= self.exact_combination_limit:
2296
+ selected, joint, exploration, considered = self._exact_search(
2297
+ request,
2298
+ scores,
2299
+ required_actions,
2300
+ slate_score_cache,
2301
+ admissibility_cache,
2302
+ )
2303
+ mode = "exact_joint"
2304
+ else:
2305
+ selected, joint, exploration, considered = self._beam_search(
2306
+ request,
2307
+ scores,
2308
+ required_actions,
2309
+ slate_score_cache,
2310
+ admissibility_cache,
2311
+ )
2312
+ mode = "bounded_joint_beam"
2313
+ ordered_scores = tuple(
2314
+ scores[action.action_sha256]
2315
+ for action in sorted(request.actions, key=lambda value: value.action_sha256)
2316
+ )
2317
+ return MaterializedActionBrokerDecision(
2318
+ selected_actions=selected,
2319
+ scores=ordered_scores,
2320
+ required_reference_action_sha256s=required_references,
2321
+ exploration_requirement=exploration_requirement,
2322
+ allocation_requirement=request.allocation_requirement,
2323
+ reference_displaced_count=displaced_count,
2324
+ search_mode=mode,
2325
+ complete_slate_count_considered=considered,
2326
+ residual_complementarity_value=joint,
2327
+ exploration_action_sha256=exploration,
2328
+ )