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,733 @@
1
+ """Benchmark-neutral ports and receipts for forecast-based action allocation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ import math
8
+ import re
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum
11
+ from typing import Protocol, runtime_checkable
12
+
13
+ from agent_evolve.core.optimization_semantics import OptimizationSemantics
14
+ from agent_evolve.domain.patch import require_sha256
15
+ from agent_evolve.ports.action_forecast import (
16
+ ActionForecastRequest,
17
+ MetricForecastScale,
18
+ ParentMetricValue,
19
+ ResolvedActionForecast,
20
+ ResolvedActionForecastBatch,
21
+ validate_resolved_action_forecasts,
22
+ )
23
+ from agent_evolve.ports.portfolio_selection import (
24
+ finite_option_ids_have_pairwise_disjoint_parent_patch_subset,
25
+ pairwise_disjoint_parent_patch_pairs,
26
+ pairwise_disjoint_parent_patch_witness,
27
+ single_path_parent_patch_option_ids,
28
+ )
29
+
30
+
31
+ _TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,95}$")
32
+ _OPTION_ID = re.compile(r"^[a-z][a-z0-9_.-]{0,255}$")
33
+ _ARM_ID = re.compile(r"^[a-z][a-z0-9_.:-]{0,255}$")
34
+ _REQUEST_DOMAIN = b"agent-evolve:action-allocation-request:v4\x00"
35
+ _DECISION_DOMAIN = b"agent-evolve:action-portfolio-decision:v1\x00"
36
+ _ELIGIBLE_DOMAIN = b"agent-evolve:eligible-action-set:v1\x00"
37
+
38
+
39
+ def _canonical_json(value: object) -> bytes:
40
+ return json.dumps(
41
+ value,
42
+ allow_nan=False,
43
+ ensure_ascii=True,
44
+ separators=(",", ":"),
45
+ sort_keys=True,
46
+ ).encode("ascii")
47
+
48
+
49
+ def _hash(domain: bytes, value: object) -> str:
50
+ return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
51
+
52
+
53
+ def _finite_float(value: object, name: str) -> float:
54
+ if type(value) is not float or not math.isfinite(value):
55
+ raise TypeError(f"{name} must be a finite canonical float")
56
+ return value
57
+
58
+
59
+ class ForecastQuantile(str, Enum):
60
+ P10 = "p10"
61
+ P50 = "p50"
62
+ P90 = "p90"
63
+
64
+
65
+ @dataclass(frozen=True, slots=True)
66
+ class ForecastPortfolioUtilityInput:
67
+ """One benchmark utility query over a candidate forecast portfolio."""
68
+
69
+ optimization_semantics: OptimizationSemantics
70
+ parent_metric_values: tuple[ParentMetricValue, ...]
71
+ metric_scales: tuple[MetricForecastScale, ...]
72
+ members: tuple[ResolvedActionForecast, ...]
73
+ quantile: ForecastQuantile
74
+
75
+ def __post_init__(self) -> None:
76
+ if type(self.optimization_semantics) is not OptimizationSemantics:
77
+ raise TypeError("optimization_semantics must be exact")
78
+ OptimizationSemantics.__post_init__(self.optimization_semantics)
79
+ if (
80
+ type(self.parent_metric_values) is not tuple
81
+ or not self.parent_metric_values
82
+ ):
83
+ raise ValueError("parent_metric_values must be a non-empty exact tuple")
84
+ if any(
85
+ type(value) is not ParentMetricValue for value in self.parent_metric_values
86
+ ):
87
+ raise TypeError("parent_metric_values must contain exact values")
88
+ if type(self.metric_scales) is not tuple or not self.metric_scales:
89
+ raise ValueError("metric_scales must be a non-empty exact tuple")
90
+ if any(type(value) is not MetricForecastScale for value in self.metric_scales):
91
+ raise TypeError("metric_scales must contain exact values")
92
+ if (
93
+ type(self.members) is not tuple
94
+ or not self.members
95
+ or any(type(value) is not ResolvedActionForecast for value in self.members)
96
+ ):
97
+ raise ValueError("members must be a non-empty exact resolved tuple")
98
+ for value in self.members:
99
+ value.__post_init__()
100
+ if len({value.option_id for value in self.members}) != len(self.members):
101
+ raise ValueError("utility input cannot repeat an action")
102
+ if type(self.quantile) is not ForecastQuantile:
103
+ raise TypeError("quantile must be an exact ForecastQuantile")
104
+
105
+
106
+ @runtime_checkable
107
+ class ForecastPortfolioUtility(Protocol):
108
+ """Deterministic benchmark policy; returned utility is higher-is-better."""
109
+
110
+ def __call__(self, request: ForecastPortfolioUtilityInput) -> float: ...
111
+
112
+
113
+ @dataclass(frozen=True, slots=True)
114
+ class ForecastPortfolioUtilityBinding:
115
+ """Identified executable utility injected by one benchmark adapter."""
116
+
117
+ utility: ForecastPortfolioUtility = field(repr=False, compare=False)
118
+ policy_id: str
119
+ policy_version: int
120
+ definition_sha256: str
121
+
122
+ def __post_init__(self) -> None:
123
+ if not callable(self.utility):
124
+ raise TypeError("utility must be callable")
125
+ if type(self.policy_id) is not str or _TOKEN.fullmatch(self.policy_id) is None:
126
+ raise ValueError("policy_id must use the closed lowercase token grammar")
127
+ if type(self.policy_version) is not int or self.policy_version <= 0:
128
+ raise ValueError("policy_version must be a positive exact integer")
129
+ require_sha256(self.definition_sha256, "definition_sha256")
130
+
131
+ def to_record(self) -> dict[str, object]:
132
+ self.__post_init__()
133
+ return {
134
+ "policy_id": self.policy_id,
135
+ "policy_version": self.policy_version,
136
+ "definition_sha256": self.definition_sha256,
137
+ }
138
+
139
+
140
+ @dataclass(frozen=True, slots=True)
141
+ class ExactActionArmCountConstraint:
142
+ """One objective-blind exact exposure constraint over sealed actions.
143
+
144
+ ``constraint_id`` names a generic attribution axis such as proposal source
145
+ or variation operator. The allocator sees only option-to-arm labels and
146
+ exact counts; workload, model, provider, and objective identifiers remain
147
+ outside this port.
148
+ """
149
+
150
+ constraint_id: str
151
+ option_arm_ids: tuple[tuple[str, str], ...]
152
+ target_counts: tuple[tuple[str, int], ...]
153
+
154
+ def __post_init__(self) -> None:
155
+ if (
156
+ type(self.constraint_id) is not str
157
+ or _TOKEN.fullmatch(self.constraint_id) is None
158
+ ):
159
+ raise ValueError("constraint_id must use the closed token grammar")
160
+ if type(self.option_arm_ids) is not tuple or not self.option_arm_ids:
161
+ raise ValueError("option_arm_ids must be a non-empty exact tuple")
162
+ for option_id, arm_id in self.option_arm_ids:
163
+ if (
164
+ type(option_id) is not str
165
+ or _OPTION_ID.fullmatch(option_id) is None
166
+ or type(arm_id) is not str
167
+ or _ARM_ID.fullmatch(arm_id) is None
168
+ ):
169
+ raise ValueError("option_arm_ids contains an invalid token")
170
+ if self.option_arm_ids != tuple(sorted(self.option_arm_ids)) or len(
171
+ {value[0] for value in self.option_arm_ids}
172
+ ) != len(self.option_arm_ids):
173
+ raise ValueError("option_arm_ids must be option-unique and canonical")
174
+ if type(self.target_counts) is not tuple or not self.target_counts:
175
+ raise ValueError("target_counts must be a non-empty exact tuple")
176
+ for arm_id, count in self.target_counts:
177
+ if type(arm_id) is not str or _ARM_ID.fullmatch(arm_id) is None:
178
+ raise ValueError("target_counts contains an invalid arm token")
179
+ if type(count) is not int or count < 0:
180
+ raise ValueError("target_counts must be non-negative exact integers")
181
+ if self.target_counts != tuple(sorted(self.target_counts)) or len(
182
+ {value[0] for value in self.target_counts}
183
+ ) != len(self.target_counts):
184
+ raise ValueError("target_counts must be arm-unique and canonical")
185
+ if sum(value[1] for value in self.target_counts) <= 0:
186
+ raise ValueError("target_counts must allocate positive capacity")
187
+ attributed_arms = {value[1] for value in self.option_arm_ids}
188
+ target = dict(self.target_counts)
189
+ if not attributed_arms.issubset(target) or any(
190
+ count > 0 and arm_id not in attributed_arms
191
+ for arm_id, count in target.items()
192
+ ):
193
+ raise ValueError(
194
+ "option attribution cannot realize the positive target arms"
195
+ )
196
+
197
+ def to_record(self) -> dict[str, object]:
198
+ self.__post_init__()
199
+ return {
200
+ "constraint_id": self.constraint_id,
201
+ "option_arm_ids": [list(value) for value in self.option_arm_ids],
202
+ "target_counts": [list(value) for value in self.target_counts],
203
+ }
204
+
205
+
206
+ @dataclass(frozen=True, slots=True)
207
+ class ActionAllocationRequest:
208
+ """Allocation input with a cache-safe canonical eligible-action subset."""
209
+
210
+ forecast_request: ActionForecastRequest
211
+ forecasts: ResolvedActionForecastBatch
212
+ eligible_option_ids: tuple[str, ...]
213
+ portfolio_size: int
214
+ utility: ForecastPortfolioUtilityBinding
215
+ min_distinct_families: int | None = None
216
+ require_pairwise_disjoint_parent_patches: bool = False
217
+ minimum_single_path_interventions: int = 0
218
+ minimum_disjoint_parent_patch_pairs: int = 0
219
+ required_option_ids: tuple[str, ...] = ()
220
+ exact_arm_count_constraints: tuple[ExactActionArmCountConstraint, ...] = ()
221
+
222
+ def __post_init__(self) -> None:
223
+ if type(self.forecast_request) is not ActionForecastRequest:
224
+ raise TypeError("forecast_request must be exact ActionForecastRequest")
225
+ self.forecast_request.__post_init__()
226
+ if type(self.forecasts) is not ResolvedActionForecastBatch:
227
+ raise TypeError("forecasts must be exact ResolvedActionForecastBatch")
228
+ validate_resolved_action_forecasts(self.forecast_request, self.forecasts)
229
+ if type(self.eligible_option_ids) is not tuple or any(
230
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
231
+ for value in self.eligible_option_ids
232
+ ):
233
+ raise TypeError("eligible_option_ids must be an exact option-ID tuple")
234
+ if not self.eligible_option_ids:
235
+ raise ValueError("eligible_option_ids must be non-empty")
236
+ if self.eligible_option_ids != tuple(sorted(set(self.eligible_option_ids))):
237
+ raise ValueError("eligible_option_ids must be unique and canonical")
238
+ available = {value.option_id for value in self.forecasts.forecasts}
239
+ if not set(self.eligible_option_ids).issubset(available):
240
+ raise ValueError("eligible_option_ids contains a foreign option")
241
+ if type(self.portfolio_size) is not int or self.portfolio_size <= 0:
242
+ raise ValueError("portfolio_size must be a positive exact integer")
243
+ if self.portfolio_size > len(self.eligible_option_ids):
244
+ raise ValueError("portfolio_size exceeds the eligible action count")
245
+ if type(self.utility) is not ForecastPortfolioUtilityBinding:
246
+ raise TypeError("utility must be an exact identified binding")
247
+ self.utility.__post_init__()
248
+ by_id = {
249
+ value.option_id: value
250
+ for value in self.forecast_request.finite_variation_contract.options
251
+ }
252
+ available_families = {
253
+ by_id[option_id].family for option_id in self.eligible_option_ids
254
+ }
255
+ if self.min_distinct_families is not None:
256
+ if (
257
+ type(self.min_distinct_families) is not int
258
+ or self.min_distinct_families <= 0
259
+ ):
260
+ raise ValueError("min_distinct_families must be positive or None")
261
+ if self.min_distinct_families > self.portfolio_size:
262
+ raise ValueError("min_distinct_families cannot exceed portfolio_size")
263
+ if self.min_distinct_families > len(available_families):
264
+ raise ValueError(
265
+ "eligible actions cannot satisfy min_distinct_families"
266
+ )
267
+ if type(self.require_pairwise_disjoint_parent_patches) is not bool:
268
+ raise TypeError(
269
+ "require_pairwise_disjoint_parent_patches must be exact bool"
270
+ )
271
+ if (
272
+ type(self.minimum_single_path_interventions) is not int
273
+ or not 0
274
+ <= self.minimum_single_path_interventions
275
+ <= self.portfolio_size
276
+ ):
277
+ raise ValueError(
278
+ "minimum_single_path_interventions must lie in portfolio capacity"
279
+ )
280
+ maximum_pairs = self.portfolio_size * (self.portfolio_size - 1) // 2
281
+ if (
282
+ type(self.minimum_disjoint_parent_patch_pairs) is not int
283
+ or not 0
284
+ <= self.minimum_disjoint_parent_patch_pairs
285
+ <= maximum_pairs
286
+ ):
287
+ raise ValueError(
288
+ "minimum_disjoint_parent_patch_pairs must lie in portfolio pair "
289
+ "capacity"
290
+ )
291
+ contract = self.forecast_request.finite_variation_contract
292
+ if self.minimum_single_path_interventions > len(
293
+ single_path_parent_patch_option_ids(contract, self.eligible_option_ids)
294
+ ):
295
+ raise ValueError(
296
+ "eligible actions cannot satisfy the single-path intervention floor"
297
+ )
298
+ if self.minimum_disjoint_parent_patch_pairs > len(
299
+ pairwise_disjoint_parent_patch_pairs(contract, self.eligible_option_ids)
300
+ ):
301
+ raise ValueError(
302
+ "eligible actions cannot satisfy the disjoint parent-pair floor"
303
+ )
304
+ if type(self.required_option_ids) is not tuple or any(
305
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
306
+ for value in self.required_option_ids
307
+ ):
308
+ raise TypeError("required_option_ids must be an exact option-ID tuple")
309
+ if self.required_option_ids != tuple(sorted(set(self.required_option_ids))):
310
+ raise ValueError("required_option_ids must be unique and canonical")
311
+ if not set(self.required_option_ids).issubset(self.eligible_option_ids):
312
+ raise ValueError("required_option_ids contains an ineligible option")
313
+ if len(self.required_option_ids) > self.portfolio_size:
314
+ raise ValueError("required_option_ids exceeds the portfolio size")
315
+ if type(self.exact_arm_count_constraints) is not tuple or any(
316
+ type(value) is not ExactActionArmCountConstraint
317
+ for value in self.exact_arm_count_constraints
318
+ ):
319
+ raise TypeError(
320
+ "exact_arm_count_constraints must be an exact constraint tuple"
321
+ )
322
+ for constraint in self.exact_arm_count_constraints:
323
+ constraint.__post_init__()
324
+ constraint_ids = tuple(
325
+ value.constraint_id for value in self.exact_arm_count_constraints
326
+ )
327
+ if constraint_ids != tuple(sorted(set(constraint_ids))):
328
+ raise ValueError(
329
+ "exact_arm_count_constraints must use unique canonical IDs"
330
+ )
331
+ for constraint in self.exact_arm_count_constraints:
332
+ if sum(value[1] for value in constraint.target_counts) != (
333
+ self.portfolio_size
334
+ ):
335
+ raise ValueError(
336
+ "exact arm targets must allocate the complete portfolio"
337
+ )
338
+ arm_by_option = dict(constraint.option_arm_ids)
339
+ if set(arm_by_option) != available:
340
+ raise ValueError(
341
+ "exact arm attribution must cover the complete forecast batch"
342
+ )
343
+ target = dict(constraint.target_counts)
344
+ eligible_counts = {arm_id: 0 for arm_id in target}
345
+ required_counts = {arm_id: 0 for arm_id in target}
346
+ for option_id in self.eligible_option_ids:
347
+ eligible_counts[arm_by_option[option_id]] += 1
348
+ for option_id in self.required_option_ids:
349
+ required_counts[arm_by_option[option_id]] += 1
350
+ if any(eligible_counts[arm_id] < count for arm_id, count in target.items()):
351
+ raise ValueError("eligible actions cannot satisfy exact arm targets")
352
+ if any(required_counts[arm_id] > count for arm_id, count in target.items()):
353
+ raise ValueError("required actions exceed an exact arm target")
354
+ if self.require_pairwise_disjoint_parent_patches and not (
355
+ finite_option_ids_have_pairwise_disjoint_parent_patch_subset(
356
+ self.forecast_request.finite_variation_contract,
357
+ self.eligible_option_ids,
358
+ portfolio_size=self.portfolio_size,
359
+ min_distinct_families=self.min_distinct_families,
360
+ )
361
+ ):
362
+ raise ValueError(
363
+ "eligible actions contain no structurally feasible portfolio"
364
+ )
365
+ if self.require_pairwise_disjoint_parent_patches and self.required_option_ids:
366
+ witness = pairwise_disjoint_parent_patch_witness(
367
+ self.forecast_request.finite_variation_contract,
368
+ self.eligible_option_ids,
369
+ portfolio_size=self.portfolio_size,
370
+ min_distinct_families=self.min_distinct_families,
371
+ required_option_ids=self.required_option_ids,
372
+ )
373
+ if witness is None:
374
+ raise ValueError(
375
+ "required_option_ids have no structurally feasible completion"
376
+ )
377
+ elif self.min_distinct_families is not None and self.required_option_ids:
378
+ required_families = {
379
+ by_id[value].family for value in self.required_option_ids
380
+ }
381
+ remaining_families = {
382
+ by_id[value].family
383
+ for value in self.eligible_option_ids
384
+ if value not in self.required_option_ids
385
+ }
386
+ slots = self.portfolio_size - len(self.required_option_ids)
387
+ reachable = len(required_families) + min(
388
+ slots,
389
+ len(remaining_families - required_families),
390
+ )
391
+ if reachable < self.min_distinct_families:
392
+ raise ValueError(
393
+ "required_option_ids cannot complete minimum family coverage"
394
+ )
395
+
396
+ @property
397
+ def eligible_options_sha256(self) -> str:
398
+ self.__post_init__()
399
+ return _hash(
400
+ _ELIGIBLE_DOMAIN,
401
+ {"eligible_option_ids": list(self.eligible_option_ids)},
402
+ )
403
+
404
+ def to_record(self) -> dict[str, object]:
405
+ self.__post_init__()
406
+ structural_floor_bound = bool(
407
+ self.minimum_single_path_interventions
408
+ or self.minimum_disjoint_parent_patch_pairs
409
+ )
410
+ record: dict[str, object] = {
411
+ "schema_version": 5 if structural_floor_bound else 4,
412
+ "forecast_request_sha256": self.forecast_request.request_sha256,
413
+ "forecast_receipt_sha256": self.forecasts.receipt_sha256,
414
+ "eligible_option_ids": list(self.eligible_option_ids),
415
+ "eligible_options_sha256": self.eligible_options_sha256,
416
+ "portfolio_size": self.portfolio_size,
417
+ "utility": self.utility.to_record(),
418
+ "min_distinct_families": self.min_distinct_families,
419
+ "require_pairwise_disjoint_parent_patches": (
420
+ self.require_pairwise_disjoint_parent_patches
421
+ ),
422
+ "required_option_ids": list(self.required_option_ids),
423
+ "exact_arm_count_constraints": [
424
+ value.to_record() for value in self.exact_arm_count_constraints
425
+ ],
426
+ }
427
+ if self.minimum_single_path_interventions:
428
+ record["minimum_single_path_interventions"] = (
429
+ self.minimum_single_path_interventions
430
+ )
431
+ if self.minimum_disjoint_parent_patch_pairs:
432
+ record["minimum_disjoint_parent_patch_pairs"] = (
433
+ self.minimum_disjoint_parent_patch_pairs
434
+ )
435
+ return record
436
+
437
+ @property
438
+ def request_sha256(self) -> str:
439
+ return _hash(_REQUEST_DOMAIN, self.to_record())
440
+
441
+
442
+ @dataclass(frozen=True, slots=True)
443
+ class PortfolioAllocationScore:
444
+ """Auditable generic score for one greedy marginal candidate."""
445
+
446
+ p10_utility: float
447
+ p50_utility: float
448
+ p90_utility: float
449
+ downside_utility: float
450
+ risk_penalty: float
451
+ diversity_reward: float
452
+ total_utility: float
453
+
454
+ def __post_init__(self) -> None:
455
+ for name in (
456
+ "p10_utility",
457
+ "p50_utility",
458
+ "p90_utility",
459
+ "downside_utility",
460
+ "risk_penalty",
461
+ "diversity_reward",
462
+ "total_utility",
463
+ ):
464
+ _finite_float(getattr(self, name), name)
465
+ if self.downside_utility != min(self.p10_utility, self.p90_utility):
466
+ raise ValueError("downside_utility must be min(p10_utility,p90_utility)")
467
+ if self.risk_penalty < 0.0:
468
+ raise ValueError("risk_penalty must be non-negative")
469
+ if self.diversity_reward < 0.0:
470
+ raise ValueError("diversity_reward must be non-negative")
471
+
472
+ def to_record(self) -> dict[str, object]:
473
+ self.__post_init__()
474
+ return {
475
+ name: getattr(self, name).hex()
476
+ for name in (
477
+ "p10_utility",
478
+ "p50_utility",
479
+ "p90_utility",
480
+ "downside_utility",
481
+ "risk_penalty",
482
+ "diversity_reward",
483
+ "total_utility",
484
+ )
485
+ }
486
+
487
+
488
+ @dataclass(frozen=True, slots=True)
489
+ class AllocatedActionMember:
490
+ rank: int
491
+ option_id: str
492
+ option_identity_sha256: str
493
+ child_configuration_sha256: str
494
+ family: str
495
+ greedy_step_score: PortfolioAllocationScore
496
+ marginal_total_utility: float
497
+
498
+ def __post_init__(self) -> None:
499
+ if type(self.rank) is not int or self.rank <= 0:
500
+ raise ValueError("rank must be a positive exact integer")
501
+ if (
502
+ type(self.option_id) is not str
503
+ or _OPTION_ID.fullmatch(self.option_id) is None
504
+ ):
505
+ raise ValueError("option_id must use the finite-option grammar")
506
+ if type(self.family) is not str or _TOKEN.fullmatch(self.family) is None:
507
+ raise ValueError("family must use the closed token grammar")
508
+ require_sha256(self.option_identity_sha256, "option_identity_sha256")
509
+ require_sha256(
510
+ self.child_configuration_sha256,
511
+ "child_configuration_sha256",
512
+ )
513
+ if type(self.greedy_step_score) is not PortfolioAllocationScore:
514
+ raise TypeError("greedy_step_score must be exact PortfolioAllocationScore")
515
+ self.greedy_step_score.__post_init__()
516
+ _finite_float(self.marginal_total_utility, "marginal_total_utility")
517
+
518
+ def to_record(self) -> dict[str, object]:
519
+ self.__post_init__()
520
+ return {
521
+ "rank": self.rank,
522
+ "option_id": self.option_id,
523
+ "option_identity_sha256": self.option_identity_sha256,
524
+ "child_configuration_sha256": self.child_configuration_sha256,
525
+ "family": self.family,
526
+ "greedy_step_score": self.greedy_step_score.to_record(),
527
+ "marginal_total_utility_hex": self.marginal_total_utility.hex(),
528
+ }
529
+
530
+
531
+ @dataclass(frozen=True, slots=True, eq=False)
532
+ class ActionPortfolioDecision:
533
+ """Receipt-bound deterministic k-option allocation decision."""
534
+
535
+ allocation_request_sha256: str
536
+ forecast_receipt_sha256: str
537
+ finite_contract_identity_sha256: str
538
+ eligible_options_sha256: str
539
+ members: tuple[AllocatedActionMember, ...]
540
+ final_score: PortfolioAllocationScore
541
+ candidate_evaluations: int
542
+ utility_policy_id: str
543
+ utility_policy_version: int
544
+ utility_definition_sha256: str
545
+ allocator_policy_id: str
546
+ allocator_policy_version: int
547
+ allocator_definition_sha256: str
548
+ allocator_configuration_sha256: str
549
+
550
+ def __post_init__(self) -> None:
551
+ for name in (
552
+ "allocation_request_sha256",
553
+ "forecast_receipt_sha256",
554
+ "finite_contract_identity_sha256",
555
+ "eligible_options_sha256",
556
+ "utility_definition_sha256",
557
+ "allocator_definition_sha256",
558
+ "allocator_configuration_sha256",
559
+ ):
560
+ require_sha256(getattr(self, name), name)
561
+ if (
562
+ type(self.members) is not tuple
563
+ or not self.members
564
+ or any(type(value) is not AllocatedActionMember for value in self.members)
565
+ ):
566
+ raise ValueError("members must be a non-empty exact allocated tuple")
567
+ for value in self.members:
568
+ value.__post_init__()
569
+ if tuple(value.rank for value in self.members) != tuple(
570
+ range(1, len(self.members) + 1)
571
+ ):
572
+ raise ValueError("member ranks must be contiguous and ordered")
573
+ if len({value.option_id for value in self.members}) != len(self.members):
574
+ raise ValueError("allocation decision cannot repeat an option")
575
+ if type(self.final_score) is not PortfolioAllocationScore:
576
+ raise TypeError("final_score must be exact PortfolioAllocationScore")
577
+ self.final_score.__post_init__()
578
+ if self.final_score != self.members[-1].greedy_step_score:
579
+ raise ValueError("final_score must equal the last greedy step score")
580
+ if (
581
+ type(self.candidate_evaluations) is not int
582
+ or self.candidate_evaluations <= 0
583
+ ):
584
+ raise ValueError("candidate_evaluations must be a positive exact integer")
585
+ for prefix in ("utility", "allocator"):
586
+ policy_id = getattr(self, f"{prefix}_policy_id")
587
+ policy_version = getattr(self, f"{prefix}_policy_version")
588
+ if type(policy_id) is not str or _TOKEN.fullmatch(policy_id) is None:
589
+ raise ValueError(f"{prefix}_policy_id must use the token grammar")
590
+ if type(policy_version) is not int or policy_version <= 0:
591
+ raise ValueError(f"{prefix}_policy_version must be positive")
592
+
593
+ def _unsigned_record(self) -> dict[str, object]:
594
+ self.__post_init__()
595
+ return {
596
+ "schema_version": 1,
597
+ "allocation_request_sha256": self.allocation_request_sha256,
598
+ "forecast_receipt_sha256": self.forecast_receipt_sha256,
599
+ "finite_contract_identity_sha256": self.finite_contract_identity_sha256,
600
+ "eligible_options_sha256": self.eligible_options_sha256,
601
+ "members": [value.to_record() for value in self.members],
602
+ "final_score": self.final_score.to_record(),
603
+ "candidate_evaluations": self.candidate_evaluations,
604
+ "utility_policy": {
605
+ "policy_id": self.utility_policy_id,
606
+ "policy_version": self.utility_policy_version,
607
+ "definition_sha256": self.utility_definition_sha256,
608
+ },
609
+ "allocator_policy": {
610
+ "policy_id": self.allocator_policy_id,
611
+ "policy_version": self.allocator_policy_version,
612
+ "definition_sha256": self.allocator_definition_sha256,
613
+ "configuration_sha256": self.allocator_configuration_sha256,
614
+ },
615
+ }
616
+
617
+ @property
618
+ def receipt_sha256(self) -> str:
619
+ return _hash(_DECISION_DOMAIN, self._unsigned_record())
620
+
621
+ def to_record(self) -> dict[str, object]:
622
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
623
+
624
+ def __eq__(self, other: object) -> bool:
625
+ return (
626
+ type(self) is ActionPortfolioDecision
627
+ and type(other) is ActionPortfolioDecision
628
+ and self.receipt_sha256 == other.receipt_sha256
629
+ )
630
+
631
+ __hash__ = None
632
+
633
+
634
+ @dataclass(frozen=True, slots=True)
635
+ class ActionAllocationResult:
636
+ decision: ActionPortfolioDecision
637
+
638
+ def __post_init__(self) -> None:
639
+ if type(self.decision) is not ActionPortfolioDecision:
640
+ raise TypeError("decision must be exact ActionPortfolioDecision")
641
+ self.decision.__post_init__()
642
+
643
+
644
+ @runtime_checkable
645
+ class DeterministicActionAllocator(Protocol):
646
+ def allocate(self, request: ActionAllocationRequest) -> ActionAllocationResult: ...
647
+
648
+
649
+ def validate_action_portfolio_decision(
650
+ request: ActionAllocationRequest,
651
+ decision: ActionPortfolioDecision,
652
+ ) -> None:
653
+ """Validate receipt bindings and selected action identities without rerunning utility."""
654
+
655
+ if type(request) is not ActionAllocationRequest:
656
+ raise TypeError("request must be exact ActionAllocationRequest")
657
+ request.__post_init__()
658
+ if type(decision) is not ActionPortfolioDecision:
659
+ raise TypeError("decision must be exact ActionPortfolioDecision")
660
+ decision.__post_init__()
661
+ if (
662
+ decision.allocation_request_sha256 != request.request_sha256
663
+ or decision.forecast_receipt_sha256 != request.forecasts.receipt_sha256
664
+ or decision.finite_contract_identity_sha256
665
+ != request.forecast_request.finite_variation_contract.identity_sha256
666
+ or decision.eligible_options_sha256 != request.eligible_options_sha256
667
+ ):
668
+ raise ValueError("allocation decision is bound to a different request")
669
+ if len(decision.members) != request.portfolio_size:
670
+ raise ValueError("allocation member count differs from portfolio_size")
671
+ if (
672
+ decision.utility_policy_id != request.utility.policy_id
673
+ or decision.utility_policy_version != request.utility.policy_version
674
+ or decision.utility_definition_sha256 != request.utility.definition_sha256
675
+ ):
676
+ raise ValueError("allocation decision names a different utility policy")
677
+ forecasts = {value.option_id: value for value in request.forecasts.forecasts}
678
+ for member in decision.members:
679
+ if member.option_id not in request.eligible_option_ids:
680
+ raise ValueError("allocation selected an ineligible option")
681
+ forecast = forecasts[member.option_id]
682
+ if (
683
+ member.option_identity_sha256 != forecast.option_identity_sha256
684
+ or member.child_configuration_sha256 != forecast.child_configuration_sha256
685
+ or member.family != forecast.family
686
+ ):
687
+ raise ValueError("allocation member differs from its resolved forecast")
688
+ if not set(request.required_option_ids).issubset(
689
+ member.option_id for member in decision.members
690
+ ):
691
+ raise ValueError("allocation decision omitted a required option")
692
+ selected_ids = tuple(member.option_id for member in decision.members)
693
+ if len(
694
+ single_path_parent_patch_option_ids(
695
+ request.forecast_request.finite_variation_contract,
696
+ selected_ids,
697
+ )
698
+ ) < request.minimum_single_path_interventions:
699
+ raise ValueError(
700
+ "allocation decision violated its single-path intervention floor"
701
+ )
702
+ if len(
703
+ pairwise_disjoint_parent_patch_pairs(
704
+ request.forecast_request.finite_variation_contract,
705
+ selected_ids,
706
+ )
707
+ ) < request.minimum_disjoint_parent_patch_pairs:
708
+ raise ValueError(
709
+ "allocation decision violated its disjoint parent-pair floor"
710
+ )
711
+ for constraint in request.exact_arm_count_constraints:
712
+ arm_by_option = dict(constraint.option_arm_ids)
713
+ realized = {arm_id: 0 for arm_id, _ in constraint.target_counts}
714
+ for option_id in selected_ids:
715
+ realized[arm_by_option[option_id]] += 1
716
+ if tuple(sorted(realized.items())) != constraint.target_counts:
717
+ raise ValueError("allocation decision violated an exact arm target")
718
+
719
+
720
+ __all__ = [
721
+ "ActionAllocationRequest",
722
+ "ActionAllocationResult",
723
+ "ActionPortfolioDecision",
724
+ "AllocatedActionMember",
725
+ "ExactActionArmCountConstraint",
726
+ "DeterministicActionAllocator",
727
+ "ForecastPortfolioUtility",
728
+ "ForecastPortfolioUtilityBinding",
729
+ "ForecastPortfolioUtilityInput",
730
+ "ForecastQuantile",
731
+ "PortfolioAllocationScore",
732
+ "validate_action_portfolio_decision",
733
+ ]