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,1151 @@
1
+ """Wave-level allocation across concurrent evolutionary parent lanes.
2
+
3
+ Lane-local selection duplicates protected exploration roles and can spend the
4
+ same wave on structurally equivalent actions from different parents. This
5
+ module coordinates only after every lane has independently produced its sealed
6
+ finite action set and consequence forecasts. It owns no workload adapter and
7
+ never sees evaluator outcomes from the current or a future wave.
8
+
9
+ The policy gives a nonterminal wave one global bridge and one global probe,
10
+ uses the remaining capacity for exploitation, preserves every lane's hard
11
+ portfolio contract, and compares both lane orderings with and without
12
+ cross-lane redundancy avoidance. Existing deterministic allocators remain the
13
+ hard-feasibility authority for every candidate slate.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import hashlib
20
+ import itertools
21
+ import json
22
+ import math
23
+ from dataclasses import dataclass, field, replace
24
+ from typing import Protocol, runtime_checkable
25
+
26
+ from agent_evolve.application.action_role_value import (
27
+ RoleFactorizedActionPortfolioUtility,
28
+ )
29
+ from agent_evolve.application.action_structural_signature import (
30
+ ActionStructuralSignature,
31
+ action_structural_signatures_by_option,
32
+ )
33
+ from agent_evolve.application.contextual_action_allocation import (
34
+ contextual_action_arm_count_constraints,
35
+ contextual_allocation_realization,
36
+ )
37
+ from agent_evolve.application.target_conditioned_action_forecast import (
38
+ TargetConditionedActionForecastPlan,
39
+ allocate_target_conditioned_actions,
40
+ build_target_conditioned_action_utility,
41
+ )
42
+ from agent_evolve.domain.typed_json import FrozenJsonObject, freeze_json
43
+ from agent_evolve.ports.action_allocation import ActionAllocationResult
44
+ from agent_evolve.ports.action_forecast import ResolvedActionForecastBatch
45
+ from agent_evolve.ports.contextual_search_allocation import (
46
+ ContextualPortfolioAllocationContract,
47
+ )
48
+ from agent_evolve.ports.portfolio_selection import PortfolioSelectionRequest
49
+
50
+ GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_ID = "global_role_balanced_wave"
51
+ GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_VERSION = 7
52
+ GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_DEFINITION_SHA256 = hashlib.sha256(
53
+ b"agent-evolve:global-role-balanced-wave:v7;"
54
+ b"scope=concurrent-parent-wave;"
55
+ b"nonterminal-roles=one-bridge-one-probe-remainder-exploit;"
56
+ b"protected-role-placement=exhaustive-small-finite-set;"
57
+ b"memory-search=bounded-primary-then-exhaustive-feasibility-fallback;"
58
+ b"eligible-universe=plan-authenticated-forecast-contract;"
59
+ b"hard-feasibility=existing-lane-allocator-plus-contextual-arm-marginals;"
60
+ b"contextual-count-recourse=minimum-l1-materialized-feasible-projection-"
61
+ b"only-after-exact-search-exhaustion;"
62
+ b"cross-lane-diversity=family-path-signature-plus-p50-objective-cell;"
63
+ b"search=bounded-memory-pairings-times-role-placements-times-lane-rotations;"
64
+ b"current-future-outcomes=false;workload-model-provider-branches=false"
65
+ ).hexdigest()
66
+ _RECEIPT_DOMAIN = b"agent-evolve:global-wave-action-allocation:v1\x00"
67
+
68
+
69
+ def _canonical_json(value: object) -> bytes:
70
+ return json.dumps(
71
+ value,
72
+ allow_nan=False,
73
+ ensure_ascii=True,
74
+ separators=(",", ":"),
75
+ sort_keys=True,
76
+ ).encode("ascii", errors="strict")
77
+
78
+
79
+ def _object(value: dict[str, object]) -> FrozenJsonObject:
80
+ frozen = freeze_json(value)
81
+ if type(frozen) is not FrozenJsonObject: # pragma: no cover - closed root.
82
+ raise AssertionError("global allocation audit did not freeze to an object")
83
+ return frozen
84
+
85
+
86
+ MemoryAssignment = tuple[tuple[str, str], ...]
87
+ RoleSlots = tuple[int, int, int]
88
+
89
+
90
+ @dataclass(frozen=True, slots=True)
91
+ class GlobalWaveActionAllocationLane:
92
+ """One fully forecast lane submitted to the wave-level trusted policy."""
93
+
94
+ generation: int
95
+ request: PortfolioSelectionRequest
96
+ plan: TargetConditionedActionForecastPlan
97
+ forecasts: ResolvedActionForecastBatch
98
+ utility_mode: str
99
+ target_kind: str
100
+ memory_assignments: tuple[MemoryAssignment, ...]
101
+ risk_aversion: float
102
+ diversity_weight: float
103
+ beam_width: int
104
+ contextual_allocation: ContextualPortfolioAllocationContract | None = None
105
+
106
+ def __post_init__(self) -> None:
107
+ if type(self.generation) is not int or self.generation <= 0:
108
+ raise ValueError("generation must be a positive exact integer")
109
+ if type(self.request) is not PortfolioSelectionRequest:
110
+ raise TypeError("request must be an exact PortfolioSelectionRequest")
111
+ self.request.__post_init__()
112
+ if type(self.plan) is not TargetConditionedActionForecastPlan:
113
+ raise TypeError("plan must be exact")
114
+ self.plan.__post_init__()
115
+ if type(self.forecasts) is not ResolvedActionForecastBatch:
116
+ raise TypeError("forecasts must be exact")
117
+ self.plan.assess(self.forecasts)
118
+ if type(self.utility_mode) is not str or not self.utility_mode:
119
+ raise ValueError("utility_mode must be non-empty")
120
+ if type(self.target_kind) is not str or not self.target_kind:
121
+ raise ValueError("target_kind must be non-empty")
122
+ if type(self.memory_assignments) is not tuple or not self.memory_assignments:
123
+ raise ValueError("memory_assignments must be a non-empty tuple")
124
+ for assignment in self.memory_assignments:
125
+ if (
126
+ type(assignment) is not tuple
127
+ or assignment != tuple(sorted(set(assignment)))
128
+ or any(
129
+ type(item) is not tuple
130
+ or len(item) != 2
131
+ or any(type(value) is not str or not value for value in item)
132
+ for item in assignment
133
+ )
134
+ ):
135
+ raise ValueError("memory assignments must be canonical string pairs")
136
+ for name in ("risk_aversion", "diversity_weight"):
137
+ value = getattr(self, name)
138
+ if type(value) is not float or not math.isfinite(value) or value < 0.0:
139
+ raise ValueError(f"{name} must be finite and non-negative")
140
+ if type(self.beam_width) is not int or self.beam_width <= 0:
141
+ raise ValueError("beam_width must be a positive exact integer")
142
+ if self.contextual_allocation is not None:
143
+ if type(self.contextual_allocation) is not (
144
+ ContextualPortfolioAllocationContract
145
+ ):
146
+ raise TypeError("contextual_allocation must be exact or None")
147
+ self.contextual_allocation.__post_init__()
148
+ if (
149
+ self.contextual_allocation.campaign_generation != self.generation
150
+ or self.contextual_allocation.slice_id
151
+ != self.plan.campaign_target.lane_id
152
+ or self.contextual_allocation.evaluation_slots
153
+ != self.request.portfolio_size
154
+ ):
155
+ raise ValueError("contextual allocation differs from its wave lane")
156
+
157
+ @property
158
+ def lane_id(self) -> str:
159
+ # The exact frozen lane is validated at construction and again at the
160
+ # public policy/coordinator boundaries. Revalidating its complete
161
+ # forecast and finite contract on every identity lookup made the joint
162
+ # counterfactual search repeatedly hash the same immutable graph.
163
+ return self.plan.campaign_target.lane_id
164
+
165
+ @property
166
+ def wave_key(self) -> tuple[int, str]:
167
+ # See ``lane_id``: this is a pure projection of already validated,
168
+ # immutable fields, not a trust boundary.
169
+ return (
170
+ self.generation,
171
+ self.plan.campaign_target.archive_utility_snapshot_sha256,
172
+ )
173
+
174
+
175
+ @dataclass(frozen=True, slots=True)
176
+ class GlobalWaveActionAllocationLaneResult:
177
+ """One lane projection of a jointly selected wave portfolio."""
178
+
179
+ allocation: ActionAllocationResult
180
+ memory_assignment: MemoryAssignment
181
+ role_slots: RoleSlots | None
182
+ eligible_option_ids: tuple[str, ...] | None
183
+ global_receipt_sha256: str
184
+ audit: FrozenJsonObject
185
+
186
+ def __post_init__(self) -> None:
187
+ if type(self.allocation) is not ActionAllocationResult:
188
+ raise TypeError("allocation must be exact")
189
+ self.allocation.__post_init__()
190
+ if type(self.memory_assignment) is not tuple:
191
+ raise TypeError("memory_assignment must be exact")
192
+ if self.role_slots is not None and (
193
+ type(self.role_slots) is not tuple
194
+ or len(self.role_slots) != 3
195
+ or any(type(value) is not int or value < 0 for value in self.role_slots)
196
+ ):
197
+ raise ValueError("role_slots must be non-negative exact integers")
198
+ if self.eligible_option_ids is not None and (
199
+ type(self.eligible_option_ids) is not tuple
200
+ or self.eligible_option_ids != tuple(sorted(set(self.eligible_option_ids)))
201
+ ):
202
+ raise ValueError("eligible_option_ids must be canonical or None")
203
+ if (
204
+ type(self.global_receipt_sha256) is not str
205
+ or len(self.global_receipt_sha256) != 64
206
+ ):
207
+ raise ValueError("global_receipt_sha256 must be a SHA-256 digest")
208
+ if (
209
+ type(self.audit) is not FrozenJsonObject
210
+ or freeze_json(self.audit) is not self.audit
211
+ ):
212
+ raise TypeError("audit must be an exact frozen object")
213
+
214
+
215
+ @runtime_checkable
216
+ class GlobalWaveActionAllocationPolicy(Protocol):
217
+ def allocate(
218
+ self,
219
+ lanes: tuple[GlobalWaveActionAllocationLane, ...],
220
+ ) -> dict[str, GlobalWaveActionAllocationLaneResult]: ...
221
+
222
+
223
+ @runtime_checkable
224
+ class GlobalWaveActionAllocationCoordinator(Protocol):
225
+ async def allocate(
226
+ self,
227
+ lane: GlobalWaveActionAllocationLane,
228
+ ) -> GlobalWaveActionAllocationLaneResult: ...
229
+
230
+
231
+ @dataclass(frozen=True, slots=True)
232
+ class _Trial:
233
+ allocations: tuple[tuple[str, ActionAllocationResult], ...]
234
+ memory_assignments: tuple[tuple[str, MemoryAssignment], ...]
235
+ role_slots: tuple[tuple[str, RoleSlots | None], ...]
236
+ eligible_option_ids: tuple[tuple[str, tuple[str, ...] | None], ...]
237
+ lane_order: tuple[str, ...]
238
+ avoided_redundancy: bool
239
+ structural_collisions: int
240
+ near_outcome_collisions: int
241
+ local_utility: float
242
+ total_score: float
243
+ contextual_source_l1_deviation: int = 0
244
+ contextual_operator_l1_deviation: int = 0
245
+ contextual_projection_count: int = 0
246
+
247
+ @property
248
+ def tie_break(self) -> tuple[str, ...]:
249
+ return tuple(
250
+ allocation.decision.receipt_sha256 for _, allocation in self.allocations
251
+ )
252
+
253
+ @property
254
+ def contextual_l1_deviation(self) -> int:
255
+ return (
256
+ self.contextual_source_l1_deviation
257
+ + self.contextual_operator_l1_deviation
258
+ )
259
+
260
+
261
+ @dataclass(frozen=True, slots=True)
262
+ class _LaneAllocationAttempt:
263
+ allocation: ActionAllocationResult
264
+ source_l1_deviation: int
265
+ operator_l1_deviation: int
266
+ contextual_counts_projected: bool
267
+
268
+
269
+ def _memory_map(assignment: MemoryAssignment) -> dict[str, str]:
270
+ return dict(assignment)
271
+
272
+
273
+ def _required_option_ids(
274
+ lane: GlobalWaveActionAllocationLane,
275
+ assignment: MemoryAssignment,
276
+ ) -> tuple[str, ...]:
277
+ return tuple(
278
+ sorted(
279
+ {
280
+ *lane.request.candidate_pool_required_option_ids,
281
+ *(option_id for option_id, _ in assignment),
282
+ }
283
+ )
284
+ )
285
+
286
+
287
+ def _p50_cells(
288
+ lane: GlobalWaveActionAllocationLane,
289
+ ) -> dict[str, tuple[tuple[str, float], ...]]:
290
+ aliases = {
291
+ value.target_metric_id: value.forecast_metric_id for value in lane.plan.aliases
292
+ }
293
+ result: dict[str, tuple[tuple[str, float], ...]] = {}
294
+ for forecast in lane.forecasts.forecasts:
295
+ metrics = {value.metric_id: value for value in forecast.metric_forecasts}
296
+ result[forecast.option_id] = tuple(
297
+ (
298
+ axis.metric_id,
299
+ axis.normalize(
300
+ axis.parent_value + metrics[aliases[axis.metric_id]].p50_delta
301
+ ),
302
+ )
303
+ for axis in lane.plan.objective_target.axes
304
+ )
305
+ return result
306
+
307
+
308
+ def _cell_distance(
309
+ left: tuple[tuple[str, float], ...],
310
+ right: tuple[tuple[str, float], ...],
311
+ ) -> float | None:
312
+ if tuple(value[0] for value in left) != tuple(value[0] for value in right):
313
+ return None
314
+ return sum(abs(a[1] - b[1]) for a, b in zip(left, right, strict=True)) / len(left)
315
+
316
+
317
+ def _role_budgets(
318
+ lanes: tuple[GlobalWaveActionAllocationLane, ...],
319
+ ) -> tuple[dict[str, RoleSlots | None], ...]:
320
+ if any(value.utility_mode != "role_factorized" for value in lanes):
321
+ return ({value.lane_id: None for value in lanes},)
322
+ results: list[dict[str, RoleSlots | None]] = []
323
+ for bridge_lane in lanes:
324
+ for probe_lane in lanes:
325
+ budget: dict[str, RoleSlots | None] = {}
326
+ feasible = True
327
+ for lane in lanes:
328
+ bridge = int(lane.lane_id == bridge_lane.lane_id)
329
+ probe = int(lane.lane_id == probe_lane.lane_id)
330
+ exploit = lane.request.portfolio_size - bridge - probe
331
+ if exploit <= 0:
332
+ feasible = False
333
+ break
334
+ budget[lane.lane_id] = (exploit, bridge, probe)
335
+ if feasible and budget not in results:
336
+ results.append(budget)
337
+ if not results:
338
+ raise ValueError("wave capacity cannot support one global bridge and probe")
339
+
340
+ def screen(budget: dict[str, RoleSlots | None]) -> float:
341
+ total = 0.0
342
+ for lane in lanes:
343
+ role_slots = budget[lane.lane_id]
344
+ assert role_slots is not None
345
+ binding = build_target_conditioned_action_utility(
346
+ plan=lane.plan,
347
+ forecasts=lane.forecasts,
348
+ portfolio_size=lane.request.portfolio_size,
349
+ utility_mode="role_factorized",
350
+ role_slots=role_slots,
351
+ )
352
+ utility = binding.utility
353
+ if type(utility) is not RoleFactorizedActionPortfolioUtility:
354
+ raise AssertionError("role utility factory returned a foreign type")
355
+ exploit_slots, bridge_slots, probe_slots = role_slots
356
+ bridge_choices: tuple[tuple[str | None, float], ...] = (
357
+ ((None, 0.0),)
358
+ if bridge_slots == 0
359
+ else tuple(
360
+ (row.option_id, row.bridge_rank_scores[1])
361
+ for row in utility.score_rows
362
+ )
363
+ )
364
+ probe_choices: tuple[tuple[str | None, float], ...] = (
365
+ ((None, 0.0),)
366
+ if probe_slots == 0
367
+ else tuple(
368
+ (row.option_id, row.probe_rank_score) for row in utility.score_rows
369
+ )
370
+ )
371
+ rows = {value.option_id: value for value in utility.score_rows}
372
+ best: float | None = None
373
+ for bridge_id, bridge_score in bridge_choices:
374
+ for probe_id, probe_score in probe_choices:
375
+ if bridge_id is not None and bridge_id == probe_id:
376
+ continue
377
+ excluded = {value for value in (bridge_id, probe_id) if value}
378
+ exploit = sorted(
379
+ (
380
+ value.exploit_rank_scores[1]
381
+ for value in rows.values()
382
+ if value.option_id not in excluded
383
+ ),
384
+ reverse=True,
385
+ )[:exploit_slots]
386
+ if len(exploit) != exploit_slots:
387
+ continue
388
+ value = bridge_score + probe_score + sum(exploit)
389
+ if best is None or value > best:
390
+ best = value
391
+ if best is None:
392
+ raise ValueError("role budget has no injective action assignment")
393
+ total += best
394
+ return total
395
+
396
+ ranked = sorted(
397
+ results,
398
+ key=lambda value: (
399
+ -screen(value),
400
+ tuple((lane_id, value[lane_id]) for lane_id in sorted(value)),
401
+ ),
402
+ )
403
+ # Screening orders the tiny protected-role placement set; it must not
404
+ # discard alternatives before memory, patch, and family feasibility are
405
+ # enforced by the authoritative lane allocator. In particular, a
406
+ # high-scoring bridge/probe placement may be incompatible with a required
407
+ # memory action while another placement remains fully feasible.
408
+ return tuple(ranked)
409
+
410
+
411
+ def _memory_combinations(
412
+ lanes: tuple[GlobalWaveActionAllocationLane, ...],
413
+ *,
414
+ per_lane_limit: int,
415
+ combination_limit: int,
416
+ ) -> tuple[dict[str, MemoryAssignment], ...]:
417
+ candidates = tuple(lane.memory_assignments[:per_lane_limit] for lane in lanes)
418
+ indexed = itertools.product(*(tuple(enumerate(value)) for value in candidates))
419
+ rows = sorted(
420
+ indexed,
421
+ key=lambda row: (
422
+ sum(value[0] for value in row),
423
+ tuple(value[0] for value in row),
424
+ ),
425
+ )[:combination_limit]
426
+ return tuple(
427
+ {
428
+ lane.lane_id: indexed_assignment[1]
429
+ for lane, indexed_assignment in zip(lanes, row, strict=True)
430
+ }
431
+ for row in rows
432
+ )
433
+
434
+
435
+ @dataclass(slots=True)
436
+ class GlobalRoleBalancedWaveActionAllocationPolicy:
437
+ """Bounded joint search with one wave-level exploration role budget."""
438
+
439
+ structural_redundancy_penalty: float = 0.25
440
+ near_outcome_redundancy_penalty: float = 0.15
441
+ near_outcome_distance: float = 0.03
442
+ memory_assignments_per_lane: int = 8
443
+ memory_combination_limit: int = 4
444
+
445
+ def __post_init__(self) -> None:
446
+ for name in (
447
+ "structural_redundancy_penalty",
448
+ "near_outcome_redundancy_penalty",
449
+ "near_outcome_distance",
450
+ ):
451
+ value = getattr(self, name)
452
+ if type(value) is not float or not math.isfinite(value) or value < 0.0:
453
+ raise ValueError(f"{name} must be finite and non-negative")
454
+ for name in ("memory_assignments_per_lane", "memory_combination_limit"):
455
+ value = getattr(self, name)
456
+ if type(value) is not int or value <= 0:
457
+ raise ValueError(f"{name} must be a positive exact integer")
458
+
459
+ def _allocate_lane(
460
+ self,
461
+ lane: GlobalWaveActionAllocationLane,
462
+ *,
463
+ assignment: MemoryAssignment,
464
+ role_slots: RoleSlots | None,
465
+ eligible_option_ids: tuple[str, ...] | None,
466
+ ) -> ActionAllocationResult:
467
+ return allocate_target_conditioned_actions(
468
+ plan=lane.plan,
469
+ forecasts=lane.forecasts,
470
+ portfolio_size=lane.request.portfolio_size,
471
+ eligible_option_ids=eligible_option_ids,
472
+ risk_aversion=lane.risk_aversion,
473
+ diversity_weight=lane.diversity_weight,
474
+ beam_width=lane.beam_width,
475
+ utility_mode=lane.utility_mode,
476
+ required_option_ids=_required_option_ids(lane, assignment),
477
+ role_slots=role_slots,
478
+ exact_arm_count_constraints=contextual_action_arm_count_constraints(
479
+ # The forecast plan is the authenticated allocation universe.
480
+ # The selection request may intentionally retain a much larger
481
+ # source union for support auditing and must not be used to
482
+ # reconstruct constraints over the screened forecast batch.
483
+ finite_contract=lane.plan.request.finite_variation_contract,
484
+ allocation=lane.contextual_allocation,
485
+ portfolio_size=lane.request.portfolio_size,
486
+ ),
487
+ minimum_single_path_interventions=(
488
+ 0
489
+ if lane.contextual_allocation is None
490
+ else lane.contextual_allocation.minimum_single_path_interventions
491
+ ),
492
+ minimum_disjoint_parent_patch_pairs=(
493
+ 0
494
+ if lane.contextual_allocation is None
495
+ else lane.contextual_allocation.minimum_disjoint_parent_patch_pairs
496
+ ),
497
+ )
498
+
499
+ @staticmethod
500
+ def _count_vectors(
501
+ target: tuple[tuple[str, int], ...],
502
+ *,
503
+ slots: int,
504
+ ) -> tuple[tuple[tuple[str, int], ...], ...]:
505
+ """Enumerate canonical exposure vectors by distance from one request."""
506
+
507
+ arm_ids = tuple(value[0] for value in target)
508
+ requested = dict(target)
509
+ rows = tuple(
510
+ tuple(zip(arm_ids, counts, strict=True))
511
+ for counts in itertools.product(range(slots + 1), repeat=len(arm_ids))
512
+ if sum(counts) == slots
513
+ )
514
+ return tuple(
515
+ sorted(
516
+ rows,
517
+ key=lambda row: (
518
+ sum(abs(count - requested[arm_id]) for arm_id, count in row),
519
+ row,
520
+ ),
521
+ )
522
+ )
523
+
524
+ def _allocate_lane_with_count_recourse(
525
+ self,
526
+ lane: GlobalWaveActionAllocationLane,
527
+ *,
528
+ assignment: MemoryAssignment,
529
+ role_slots: RoleSlots | None,
530
+ eligible_option_ids: tuple[str, ...] | None,
531
+ ) -> _LaneAllocationAttempt:
532
+ """Project an unrealizable count request onto the nearest legal slate.
533
+
534
+ Role, memory, structural, phenotype, and evaluator constraints remain
535
+ hard. Only the controller's desired source/operator marginals may
536
+ move, and the first successful L1 shell is exhausted so forecast
537
+ utility breaks ties without permitting a more distant exposure vector.
538
+ """
539
+
540
+ original = lane.contextual_allocation
541
+ if original is None:
542
+ return _LaneAllocationAttempt(
543
+ allocation=self._allocate_lane(
544
+ lane,
545
+ assignment=assignment,
546
+ role_slots=role_slots,
547
+ eligible_option_ids=eligible_option_ids,
548
+ ),
549
+ source_l1_deviation=0,
550
+ operator_l1_deviation=0,
551
+ contextual_counts_projected=False,
552
+ )
553
+
554
+ source_rows = self._count_vectors(
555
+ original.source_target_counts,
556
+ slots=lane.request.portfolio_size,
557
+ )
558
+ operator_rows = self._count_vectors(
559
+ original.operator_target_counts,
560
+ slots=lane.request.portfolio_size,
561
+ )
562
+ requested_source = dict(original.source_target_counts)
563
+ requested_operator = dict(original.operator_target_counts)
564
+ candidates = sorted(
565
+ itertools.product(source_rows, operator_rows),
566
+ key=lambda rows: (
567
+ sum(
568
+ abs(count - requested_source[arm_id])
569
+ for arm_id, count in rows[0]
570
+ )
571
+ + sum(
572
+ abs(count - requested_operator[arm_id])
573
+ for arm_id, count in rows[1]
574
+ ),
575
+ rows,
576
+ ),
577
+ )
578
+ best: tuple[ActionAllocationResult, int, int] | None = None
579
+ best_distance: int | None = None
580
+ failures: list[BaseException] = []
581
+ for source_counts, operator_counts in candidates:
582
+ source_l1 = sum(
583
+ abs(count - requested_source[arm_id])
584
+ for arm_id, count in source_counts
585
+ )
586
+ operator_l1 = sum(
587
+ abs(count - requested_operator[arm_id])
588
+ for arm_id, count in operator_counts
589
+ )
590
+ distance = source_l1 + operator_l1
591
+ if best_distance is not None and distance > best_distance:
592
+ break
593
+ projected_contract = replace(
594
+ original,
595
+ source_target_counts=source_counts,
596
+ operator_target_counts=operator_counts,
597
+ feasibility_witness_option_identity_sha256s=(),
598
+ )
599
+ try:
600
+ allocation = self._allocate_lane(
601
+ replace(lane, contextual_allocation=projected_contract),
602
+ assignment=assignment,
603
+ role_slots=role_slots,
604
+ eligible_option_ids=eligible_option_ids,
605
+ )
606
+ except (TypeError, ValueError, RuntimeError) as error:
607
+ failures.append(error)
608
+ continue
609
+ if best is None or (
610
+ allocation.decision.final_score.total_utility,
611
+ allocation.decision.receipt_sha256,
612
+ ) > (
613
+ best[0].decision.final_score.total_utility,
614
+ best[0].decision.receipt_sha256,
615
+ ):
616
+ best = (allocation, source_l1, operator_l1)
617
+ best_distance = distance
618
+ if best is None:
619
+ detail = "" if not failures else f"; {type(failures[0]).__name__}:{failures[0]}"
620
+ raise ValueError(
621
+ "contextual count projection found no hard-feasible lane slate"
622
+ + detail
623
+ )
624
+ allocation, source_l1, operator_l1 = best
625
+ selected_option_ids = tuple(
626
+ value.option_id for value in allocation.decision.members
627
+ )
628
+ realization = contextual_allocation_realization(
629
+ finite_contract=lane.plan.request.finite_variation_contract,
630
+ allocation=original,
631
+ selected_option_ids=selected_option_ids,
632
+ )
633
+ if realization is None: # pragma: no cover - original is non-null.
634
+ raise AssertionError("contextual projection lost its source contract")
635
+ if (
636
+ realization.source_l1_deviation != source_l1
637
+ or realization.operator_l1_deviation != operator_l1
638
+ ):
639
+ raise RuntimeError("projected count receipt differs from selected slate")
640
+ return _LaneAllocationAttempt(
641
+ allocation=allocation,
642
+ source_l1_deviation=source_l1,
643
+ operator_l1_deviation=operator_l1,
644
+ contextual_counts_projected=not realization.exact,
645
+ )
646
+
647
+ def _eligible_after_prior(
648
+ self,
649
+ lane: GlobalWaveActionAllocationLane,
650
+ *,
651
+ assignment: MemoryAssignment,
652
+ prior: tuple[
653
+ tuple[GlobalWaveActionAllocationLane, ActionAllocationResult], ...
654
+ ],
655
+ signatures: dict[str, dict[str, ActionStructuralSignature]],
656
+ cells: dict[str, dict[str, tuple[tuple[str, float], ...]]],
657
+ ) -> tuple[str, ...]:
658
+ required = set(_required_option_ids(lane, assignment))
659
+ blocked_signatures = {
660
+ signatures[prior_lane.lane_id][member.option_id]
661
+ for prior_lane, allocation in prior
662
+ for member in allocation.decision.members
663
+ }
664
+ prior_cells = tuple(
665
+ cells[prior_lane.lane_id][member.option_id]
666
+ for prior_lane, allocation in prior
667
+ for member in allocation.decision.members
668
+ )
669
+ eligible: list[str] = []
670
+ for forecast in lane.forecasts.forecasts:
671
+ option_id = forecast.option_id
672
+ if option_id in required:
673
+ eligible.append(option_id)
674
+ continue
675
+ if signatures[lane.lane_id][option_id] in blocked_signatures:
676
+ continue
677
+ cell = cells[lane.lane_id][option_id]
678
+ if any(
679
+ distance is not None and distance <= self.near_outcome_distance
680
+ for distance in (_cell_distance(cell, value) for value in prior_cells)
681
+ ):
682
+ continue
683
+ eligible.append(option_id)
684
+ return tuple(sorted(eligible))
685
+
686
+ def _collision_counts(
687
+ self,
688
+ allocations: dict[str, ActionAllocationResult],
689
+ *,
690
+ signatures: dict[str, dict[str, ActionStructuralSignature]],
691
+ cells: dict[str, dict[str, tuple[tuple[str, float], ...]]],
692
+ ) -> tuple[int, int]:
693
+ structural = 0
694
+ near = 0
695
+ lane_ids = tuple(sorted(allocations))
696
+ for left_index, left_id in enumerate(lane_ids):
697
+ for right_id in lane_ids[left_index + 1 :]:
698
+ for left in allocations[left_id].decision.members:
699
+ for right in allocations[right_id].decision.members:
700
+ if (
701
+ signatures[left_id][left.option_id]
702
+ == signatures[right_id][right.option_id]
703
+ ):
704
+ structural += 1
705
+ distance = _cell_distance(
706
+ cells[left_id][left.option_id],
707
+ cells[right_id][right.option_id],
708
+ )
709
+ if (
710
+ distance is not None
711
+ and distance <= self.near_outcome_distance
712
+ ):
713
+ near += 1
714
+ return structural, near
715
+
716
+ def allocate(
717
+ self,
718
+ lanes: tuple[GlobalWaveActionAllocationLane, ...],
719
+ ) -> dict[str, GlobalWaveActionAllocationLaneResult]:
720
+ self.__post_init__()
721
+ if type(lanes) is not tuple or not lanes:
722
+ raise ValueError("lanes must be a non-empty exact tuple")
723
+ for lane in lanes:
724
+ if type(lane) is not GlobalWaveActionAllocationLane:
725
+ raise TypeError("lanes must contain exact lane requests")
726
+ lane.__post_init__()
727
+ lanes = tuple(sorted(lanes, key=lambda value: value.lane_id))
728
+ if len({value.lane_id for value in lanes}) != len(lanes):
729
+ raise ValueError("global wave cannot repeat a lane")
730
+ if len({value.wave_key for value in lanes}) != 1:
731
+ raise ValueError("global allocation lanes belong to different waves")
732
+ signatures = {
733
+ lane.lane_id: action_structural_signatures_by_option(
734
+ lane.plan.request.finite_variation_contract
735
+ )
736
+ for lane in lanes
737
+ }
738
+ cells = {lane.lane_id: _p50_cells(lane) for lane in lanes}
739
+ role_budgets = _role_budgets(lanes)
740
+ maximum_memory_combinations = math.prod(
741
+ min(len(lane.memory_assignments), self.memory_assignments_per_lane)
742
+ for lane in lanes
743
+ )
744
+ all_memory_combinations = _memory_combinations(
745
+ lanes,
746
+ per_lane_limit=self.memory_assignments_per_lane,
747
+ combination_limit=maximum_memory_combinations,
748
+ )
749
+ primary_memory_combinations = all_memory_combinations[
750
+ : self.memory_combination_limit
751
+ ]
752
+ fallback_memory_combinations = all_memory_combinations[
753
+ len(primary_memory_combinations) :
754
+ ]
755
+ memory_batches = (
756
+ primary_memory_combinations,
757
+ fallback_memory_combinations,
758
+ )
759
+ orders = tuple(
760
+ (*lanes[index:], *lanes[:index]) for index in range(len(lanes))
761
+ )
762
+ best: _Trial | None = None
763
+ projected_best: _Trial | None = None
764
+ failures: list[str] = []
765
+ projection_failures: list[str] = []
766
+ trial_count = 0
767
+ projection_trial_count = 0
768
+ projection_candidates: list[
769
+ tuple[
770
+ dict[str, RoleSlots | None],
771
+ dict[str, MemoryAssignment],
772
+ tuple[GlobalWaveActionAllocationLane, ...],
773
+ bool,
774
+ ]
775
+ ] = []
776
+ allocation_cache: dict[
777
+ tuple[
778
+ str,
779
+ MemoryAssignment,
780
+ RoleSlots | None,
781
+ tuple[str, ...] | None,
782
+ bool,
783
+ ],
784
+ _LaneAllocationAttempt,
785
+ ] = {}
786
+ fallback_memory_search_used = False
787
+
788
+ def attempt_trial(
789
+ *,
790
+ role_budget: dict[str, RoleSlots | None],
791
+ memory: dict[str, MemoryAssignment],
792
+ order: tuple[GlobalWaveActionAllocationLane, ...],
793
+ avoid_redundancy: bool,
794
+ permit_count_projection: bool,
795
+ ) -> _Trial:
796
+ allocations: dict[str, ActionAllocationResult] = {}
797
+ eligible_by_lane: dict[str, tuple[str, ...] | None] = {}
798
+ prior: list[
799
+ tuple[GlobalWaveActionAllocationLane, ActionAllocationResult]
800
+ ] = []
801
+ source_l1 = 0
802
+ operator_l1 = 0
803
+ projection_count = 0
804
+ for lane in order:
805
+ eligible = None
806
+ if avoid_redundancy and prior:
807
+ candidate_eligible = self._eligible_after_prior(
808
+ lane,
809
+ assignment=memory[lane.lane_id],
810
+ prior=tuple(prior),
811
+ signatures=signatures,
812
+ cells=cells,
813
+ )
814
+ if len(candidate_eligible) >= lane.request.portfolio_size:
815
+ eligible = candidate_eligible
816
+ cache_key = (
817
+ lane.lane_id,
818
+ memory[lane.lane_id],
819
+ role_budget[lane.lane_id],
820
+ eligible,
821
+ permit_count_projection,
822
+ )
823
+ attempt = allocation_cache.get(cache_key)
824
+ if attempt is None:
825
+ if permit_count_projection:
826
+ attempt = self._allocate_lane_with_count_recourse(
827
+ lane,
828
+ assignment=memory[lane.lane_id],
829
+ role_slots=role_budget[lane.lane_id],
830
+ eligible_option_ids=eligible,
831
+ )
832
+ else:
833
+ attempt = _LaneAllocationAttempt(
834
+ allocation=self._allocate_lane(
835
+ lane,
836
+ assignment=memory[lane.lane_id],
837
+ role_slots=role_budget[lane.lane_id],
838
+ eligible_option_ids=eligible,
839
+ ),
840
+ source_l1_deviation=0,
841
+ operator_l1_deviation=0,
842
+ contextual_counts_projected=False,
843
+ )
844
+ allocation_cache[cache_key] = attempt
845
+ allocation = attempt.allocation
846
+ allocations[lane.lane_id] = allocation
847
+ eligible_by_lane[lane.lane_id] = eligible
848
+ prior.append((lane, allocation))
849
+ source_l1 += attempt.source_l1_deviation
850
+ operator_l1 += attempt.operator_l1_deviation
851
+ projection_count += attempt.contextual_counts_projected
852
+ structural, near = self._collision_counts(
853
+ allocations,
854
+ signatures=signatures,
855
+ cells=cells,
856
+ )
857
+ local = sum(
858
+ value.decision.final_score.total_utility
859
+ for value in allocations.values()
860
+ )
861
+ total = (
862
+ local
863
+ - self.structural_redundancy_penalty * structural
864
+ - self.near_outcome_redundancy_penalty * near
865
+ )
866
+ return _Trial(
867
+ allocations=tuple(sorted(allocations.items())),
868
+ memory_assignments=tuple(sorted(memory.items())),
869
+ role_slots=tuple(sorted(role_budget.items())),
870
+ eligible_option_ids=tuple(sorted(eligible_by_lane.items())),
871
+ lane_order=tuple(value.lane_id for value in order),
872
+ avoided_redundancy=avoid_redundancy,
873
+ structural_collisions=structural,
874
+ near_outcome_collisions=near,
875
+ local_utility=float(local),
876
+ total_score=float(total),
877
+ contextual_source_l1_deviation=source_l1,
878
+ contextual_operator_l1_deviation=operator_l1,
879
+ contextual_projection_count=projection_count,
880
+ )
881
+
882
+ for batch_index, memory_combinations in enumerate(memory_batches):
883
+ if not memory_combinations:
884
+ continue
885
+ if batch_index == 1:
886
+ fallback_memory_search_used = True
887
+ for role_budget in role_budgets:
888
+ for memory in memory_combinations:
889
+ for order in orders:
890
+ for avoid_redundancy in (False, True):
891
+ trial_count += 1
892
+ try:
893
+ trial = attempt_trial(
894
+ role_budget=role_budget,
895
+ memory=memory,
896
+ order=order,
897
+ avoid_redundancy=avoid_redundancy,
898
+ permit_count_projection=False,
899
+ )
900
+ except (TypeError, ValueError, RuntimeError) as error:
901
+ failures.append(f"{type(error).__name__}:{error}")
902
+ projection_candidates.append(
903
+ (
904
+ role_budget,
905
+ memory,
906
+ order,
907
+ avoid_redundancy,
908
+ )
909
+ )
910
+ continue
911
+ if best is None or (
912
+ trial.total_score,
913
+ trial.tie_break,
914
+ ) > (
915
+ best.total_score,
916
+ best.tie_break,
917
+ ):
918
+ best = trial
919
+ # The bounded primary set is a quality-prioritized search. The
920
+ # larger set is a feasibility recourse only; do not pay its cost
921
+ # once any primary assignment produces a legal joint slate.
922
+ if best is not None:
923
+ break
924
+ if best is None:
925
+ for (
926
+ role_budget,
927
+ memory,
928
+ order,
929
+ avoid_redundancy,
930
+ ) in projection_candidates:
931
+ projection_trial_count += 1
932
+ try:
933
+ projected = attempt_trial(
934
+ role_budget=role_budget,
935
+ memory=memory,
936
+ order=order,
937
+ avoid_redundancy=avoid_redundancy,
938
+ permit_count_projection=True,
939
+ )
940
+ except (TypeError, ValueError, RuntimeError) as projected_error:
941
+ projection_failures.append(
942
+ f"{type(projected_error).__name__}:{projected_error}"
943
+ )
944
+ else:
945
+ if projected_best is None or (
946
+ projected.contextual_l1_deviation,
947
+ -projected.total_score,
948
+ projected.tie_break,
949
+ ) < (
950
+ projected_best.contextual_l1_deviation,
951
+ -projected_best.total_score,
952
+ projected_best.tie_break,
953
+ ):
954
+ projected_best = projected
955
+ contextual_count_recourse_used = best is None and projected_best is not None
956
+ if best is None:
957
+ best = projected_best
958
+ if best is None:
959
+ detail = "; ".join((*failures[:2], *projection_failures[:2]))
960
+ raise ValueError(
961
+ "global wave allocation found no hard-feasible joint slate"
962
+ + ("; " + detail if detail else "")
963
+ )
964
+ record = {
965
+ "schema_version": 1,
966
+ "policy": {
967
+ "policy_id": GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_ID,
968
+ "policy_version": GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_VERSION,
969
+ "definition_sha256": (
970
+ GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_DEFINITION_SHA256
971
+ ),
972
+ },
973
+ "wave_key": {
974
+ "generation": lanes[0].generation,
975
+ "archive_utility_snapshot_sha256": lanes[0].wave_key[1],
976
+ },
977
+ "lane_order": list(best.lane_order),
978
+ "avoided_redundancy": best.avoided_redundancy,
979
+ "role_slots": {
980
+ lane_id: None if slots is None else list(slots)
981
+ for lane_id, slots in best.role_slots
982
+ },
983
+ "selected_option_ids": {
984
+ lane_id: [value.option_id for value in allocation.decision.members]
985
+ for lane_id, allocation in best.allocations
986
+ },
987
+ "eligible_option_ids": {
988
+ lane_id: (None if option_ids is None else list(option_ids))
989
+ for lane_id, option_ids in best.eligible_option_ids
990
+ },
991
+ "allocation_receipt_sha256s": {
992
+ lane_id: allocation.decision.receipt_sha256
993
+ for lane_id, allocation in best.allocations
994
+ },
995
+ "memory_assignments": {
996
+ lane_id: dict(assignment)
997
+ for lane_id, assignment in best.memory_assignments
998
+ },
999
+ "contextual_allocation_contract_sha256s": {
1000
+ lane.lane_id: (
1001
+ None
1002
+ if lane.contextual_allocation is None
1003
+ else lane.contextual_allocation.contract_sha256
1004
+ )
1005
+ for lane in lanes
1006
+ },
1007
+ "source_finite_contract_sha256s": {
1008
+ lane.lane_id: lane.request.finite_variation_contract.identity_sha256
1009
+ for lane in lanes
1010
+ },
1011
+ "allocation_finite_contract_sha256s": {
1012
+ lane.lane_id: (
1013
+ lane.plan.request.finite_variation_contract.identity_sha256
1014
+ )
1015
+ for lane in lanes
1016
+ },
1017
+ "structural_collisions": best.structural_collisions,
1018
+ "near_outcome_collisions": best.near_outcome_collisions,
1019
+ "near_outcome_distance_hex": self.near_outcome_distance.hex(),
1020
+ "local_utility_hex": best.local_utility.hex(),
1021
+ "structural_penalty_hex": self.structural_redundancy_penalty.hex(),
1022
+ "near_outcome_penalty_hex": self.near_outcome_redundancy_penalty.hex(),
1023
+ "total_score_hex": best.total_score.hex(),
1024
+ "trial_count": trial_count,
1025
+ "failed_trial_count": len(failures),
1026
+ "memory_combination_count": len(all_memory_combinations),
1027
+ "primary_memory_combination_count": len(primary_memory_combinations),
1028
+ "fallback_memory_search_used": fallback_memory_search_used,
1029
+ "contextual_count_recourse_used": contextual_count_recourse_used,
1030
+ "contextual_projection_deferred_until_exact_exhaustion": True,
1031
+ "contextual_projection_eligible_trial_count": len(
1032
+ projection_candidates
1033
+ ),
1034
+ "contextual_projection_trial_count": projection_trial_count,
1035
+ "contextual_projection_failure_count": len(projection_failures),
1036
+ "contextual_source_l1_deviation": (
1037
+ best.contextual_source_l1_deviation
1038
+ ),
1039
+ "contextual_operator_l1_deviation": (
1040
+ best.contextual_operator_l1_deviation
1041
+ ),
1042
+ "contextual_projection_count": best.contextual_projection_count,
1043
+ }
1044
+ receipt = hashlib.sha256(_RECEIPT_DOMAIN + _canonical_json(record)).hexdigest()
1045
+ audit = _object({**record, "receipt_sha256": receipt})
1046
+ allocation_by_id = dict(best.allocations)
1047
+ memory_by_id = dict(best.memory_assignments)
1048
+ roles_by_id = dict(best.role_slots)
1049
+ eligible_by_id = dict(best.eligible_option_ids)
1050
+ return {
1051
+ lane.lane_id: GlobalWaveActionAllocationLaneResult(
1052
+ allocation=allocation_by_id[lane.lane_id],
1053
+ memory_assignment=memory_by_id[lane.lane_id],
1054
+ role_slots=roles_by_id[lane.lane_id],
1055
+ eligible_option_ids=eligible_by_id[lane.lane_id],
1056
+ global_receipt_sha256=receipt,
1057
+ audit=audit,
1058
+ )
1059
+ for lane in lanes
1060
+ }
1061
+
1062
+
1063
+ @dataclass(slots=True)
1064
+ class BarrierGlobalWaveActionAllocationCoordinator:
1065
+ """Cancellation-safe rendezvous for independently forecast parent lanes."""
1066
+
1067
+ policy: GlobalWaveActionAllocationPolicy
1068
+ expected_lane_count: int = 2
1069
+ _lock: asyncio.Lock = field(default_factory=asyncio.Lock, init=False, repr=False)
1070
+ _pending: dict[
1071
+ tuple[int, str],
1072
+ dict[
1073
+ str,
1074
+ tuple[
1075
+ GlobalWaveActionAllocationLane,
1076
+ asyncio.Future[GlobalWaveActionAllocationLaneResult],
1077
+ ],
1078
+ ],
1079
+ ] = field(default_factory=dict, init=False, repr=False)
1080
+
1081
+ def __post_init__(self) -> None:
1082
+ if not isinstance(self.policy, GlobalWaveActionAllocationPolicy):
1083
+ raise TypeError("policy must satisfy GlobalWaveActionAllocationPolicy")
1084
+ if type(self.expected_lane_count) is not int or self.expected_lane_count <= 0:
1085
+ raise ValueError("expected_lane_count must be a positive exact integer")
1086
+
1087
+ async def allocate(
1088
+ self,
1089
+ lane: GlobalWaveActionAllocationLane,
1090
+ ) -> GlobalWaveActionAllocationLaneResult:
1091
+ self.__post_init__()
1092
+ if type(lane) is not GlobalWaveActionAllocationLane:
1093
+ raise TypeError("lane must be exact")
1094
+ lane.__post_init__()
1095
+ loop = asyncio.get_running_loop()
1096
+ future: asyncio.Future[GlobalWaveActionAllocationLaneResult] = (
1097
+ loop.create_future()
1098
+ )
1099
+ key = lane.wave_key
1100
+ async with self._lock:
1101
+ bucket = self._pending.setdefault(key, {})
1102
+ if lane.lane_id in bucket:
1103
+ raise ValueError("global allocation received the same lane twice")
1104
+ bucket[lane.lane_id] = (lane, future)
1105
+ if len(bucket) > self.expected_lane_count:
1106
+ bucket.pop(lane.lane_id)
1107
+ raise ValueError("global allocation exceeded expected lane count")
1108
+ if len(bucket) == self.expected_lane_count:
1109
+ entries = tuple(bucket.values())
1110
+ self._pending.pop(key)
1111
+ try:
1112
+ results = self.policy.allocate(tuple(value[0] for value in entries))
1113
+ if set(results) != {value[0].lane_id for value in entries}:
1114
+ raise ValueError("global allocation result differs from cohort")
1115
+ except BaseException as error: # noqa: BLE001 - release all waiters.
1116
+ for _, pending_future in entries:
1117
+ if not pending_future.done():
1118
+ pending_future.set_exception(error)
1119
+ else:
1120
+ for pending_lane, pending_future in entries:
1121
+ pending_future.set_result(results[pending_lane.lane_id])
1122
+ try:
1123
+ return await asyncio.shield(future)
1124
+ except BaseException:
1125
+ async with self._lock:
1126
+ bucket = self._pending.get(key)
1127
+ if (
1128
+ bucket is not None
1129
+ and bucket.get(lane.lane_id, (None, None))[1] is future
1130
+ ):
1131
+ bucket.pop(lane.lane_id)
1132
+ if not bucket:
1133
+ self._pending.pop(key)
1134
+ if not future.done():
1135
+ future.cancel()
1136
+ raise
1137
+
1138
+
1139
+ __all__ = [
1140
+ "GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_DEFINITION_SHA256",
1141
+ "GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_ID",
1142
+ "GLOBAL_WAVE_ACTION_ALLOCATION_POLICY_VERSION",
1143
+ "BarrierGlobalWaveActionAllocationCoordinator",
1144
+ "GlobalRoleBalancedWaveActionAllocationPolicy",
1145
+ "GlobalWaveActionAllocationCoordinator",
1146
+ "GlobalWaveActionAllocationLane",
1147
+ "GlobalWaveActionAllocationLaneResult",
1148
+ "GlobalWaveActionAllocationPolicy",
1149
+ "MemoryAssignment",
1150
+ "RoleSlots",
1151
+ ]