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,1131 @@
1
+ """Benchmark-inverted execution of the post-reflection AgentEvolve stage.
2
+
3
+ This module deliberately starts at the first point at which a benchmark has
4
+ finished its outcome-blind G1 sample and the reflection workflow has projected
5
+ scientific M/P views. The benchmark supplies those prepared forecast requests,
6
+ an identified set utility, and an identified evaluator. Trusted framework code
7
+ then performs the part that must be identical across every problem domain:
8
+
9
+ * run the M/P/N all-option forecasts concurrently;
10
+ * allocate a portfolio independently in each arm while excluding every G1 arm;
11
+ * evaluate G2 concurrently under an explicit, receipt-bound reuse policy; and
12
+ * synchronously cross an optional-or-required durability/interception boundary
13
+ after every hash-bound phase and before post-decision evaluation authority.
14
+
15
+ No benchmark metric, configuration schema, evaluator runtime, provider, or
16
+ prompt framework is imported here. A later outer workflow can compose G1 and
17
+ strict batched reflection around this service without changing this boundary.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import asyncio
23
+ import hashlib
24
+ import inspect
25
+ import json
26
+ import re
27
+ from collections.abc import Awaitable
28
+ from dataclasses import dataclass, field
29
+ from decimal import Decimal
30
+ from enum import Enum
31
+ from typing import Protocol, runtime_checkable
32
+
33
+ from agent_evolve.domain.finite_variation import (
34
+ FiniteVariationContract,
35
+ FiniteVariationOption,
36
+ validate_finite_variation_contract,
37
+ validate_finite_variation_option,
38
+ )
39
+ from agent_evolve.domain.ids import RunId
40
+ from agent_evolve.domain.patch import require_sha256
41
+ from agent_evolve.domain.typed_json import (
42
+ FrozenJsonObject,
43
+ freeze_json,
44
+ thaw_json,
45
+ typed_json_sha256,
46
+ )
47
+ from agent_evolve.ports.action_allocation import (
48
+ ActionAllocationRequest,
49
+ ActionAllocationResult,
50
+ DeterministicActionAllocator,
51
+ ForecastPortfolioUtilityBinding,
52
+ validate_action_portfolio_decision,
53
+ )
54
+ from agent_evolve.ports.action_forecast import (
55
+ ActionForecastEvidenceMode,
56
+ ActionForecastPolicy,
57
+ ActionForecastRequest,
58
+ ActionForecastResult,
59
+ validate_resolved_action_forecasts,
60
+ )
61
+ from agent_evolve.ports.agentic_generator import AgenticCallTelemetry
62
+ from agent_evolve.ports.portfolio_selection import (
63
+ PortfolioExperimentalArm,
64
+ )
65
+
66
+
67
+ _TOKEN = re.compile(r"^[a-z][a-z0-9_.-]{0,95}$")
68
+ _OPTION_ID = re.compile(r"^[a-z][a-z0-9_.-]{0,255}$")
69
+ _REQUEST_DOMAIN = b"agent-evolve:prepared-two-stage-action-request:v1\x00"
70
+ _EVALUATION_REQUEST_DOMAIN = b"agent-evolve:finite-action-evaluation-request:v1\x00"
71
+ _EVALUATION_RESULT_DOMAIN = b"agent-evolve:finite-action-evaluation-result:v1\x00"
72
+ _PHASE_RECEIPT_DOMAIN = b"agent-evolve:two-stage-phase-receipt:v1\x00"
73
+ _RESULT_DOMAIN = b"agent-evolve:prepared-two-stage-action-result:v1\x00"
74
+ ACTION_EVALUATION_REUSE_POLICY_ID = "action_evaluation_reuse"
75
+ ACTION_EVALUATION_REUSE_POLICY_VERSION = 1
76
+ ACTION_EVALUATION_REUSE_POLICY_DEFINITION_SHA256 = hashlib.sha256(
77
+ b"agent-evolve:action-evaluation-reuse:v1:"
78
+ b"per_arm=evaluate-each-arm-member-with-no-cross-arm-reuse;"
79
+ b"unique_action=evaluate-each-contract-action-once-and-bind-all-selecting-arms"
80
+ ).hexdigest()
81
+ DURABLE_PHASE_COMMIT_POLICY_ID = "durable_phase_commit"
82
+ DURABLE_PHASE_COMMIT_POLICY_VERSION = 1
83
+ DURABLE_PHASE_COMMIT_POLICY_DEFINITION_SHA256 = hashlib.sha256(
84
+ b"agent-evolve:durable-phase-commit:v1:"
85
+ b"optional=phase-receipts-are-produced-and-a-supplied-sink-must-succeed;"
86
+ b"required=a-sink-must-be-present-and-each-phase-commit-must-complete-before-next-phase;"
87
+ b"allocation-commit-completes-before-evaluator-capability-is-used"
88
+ ).hexdigest()
89
+
90
+ SCIENTIFIC_ARM_ORDER = (
91
+ PortfolioExperimentalArm.MEMORY,
92
+ PortfolioExperimentalArm.PERMUTED_PLACEBO,
93
+ PortfolioExperimentalArm.NEUTRAL,
94
+ )
95
+ _ARM_INDEX = {arm: index for index, arm in enumerate(SCIENTIFIC_ARM_ORDER)}
96
+
97
+
98
+ def _canonical_json(value: object) -> bytes:
99
+ return json.dumps(
100
+ value,
101
+ allow_nan=False,
102
+ ensure_ascii=True,
103
+ separators=(",", ":"),
104
+ sort_keys=True,
105
+ ).encode("ascii")
106
+
107
+
108
+ def _hash(domain: bytes, value: object) -> str:
109
+ return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
110
+
111
+
112
+ def _agentic_call_telemetry_record(
113
+ telemetry: AgenticCallTelemetry | None,
114
+ ) -> dict[str, object] | None:
115
+ """Project provider telemetry without lossy Decimal-to-float conversion."""
116
+
117
+ if telemetry is None:
118
+ return None
119
+ if type(telemetry) is not AgenticCallTelemetry:
120
+ raise TypeError("telemetry must be exact AgenticCallTelemetry or None")
121
+ telemetry.__post_init__()
122
+ for name in ("provider_response_id", "finish_reason"):
123
+ value = getattr(telemetry, name)
124
+ if value is not None and type(value) is not str:
125
+ raise TypeError(f"telemetry {name} must be an exact string or None")
126
+ if telemetry.cost_usd is not None:
127
+ if type(telemetry.cost_usd) is not Decimal:
128
+ raise TypeError("telemetry cost_usd must be an exact Decimal or None")
129
+ if not telemetry.cost_usd.is_finite():
130
+ raise ValueError("telemetry cost_usd must be finite or None")
131
+ cost_usd: str | None = str(telemetry.cost_usd)
132
+ else:
133
+ cost_usd = None
134
+ return {
135
+ "requested_model": telemetry.requested_model,
136
+ "resolved_model": telemetry.resolved_model,
137
+ "resolved_provider": telemetry.resolved_provider,
138
+ "provider_response_id": telemetry.provider_response_id,
139
+ "finish_reason": telemetry.finish_reason,
140
+ "input_tokens": telemetry.input_tokens,
141
+ "output_tokens": telemetry.output_tokens,
142
+ "reasoning_tokens": telemetry.reasoning_tokens,
143
+ "cache_read_tokens": telemetry.cache_read_tokens,
144
+ "cache_write_tokens": telemetry.cache_write_tokens,
145
+ # Decimal is encoded as its exact canonical text, never a binary float.
146
+ "cost_usd": cost_usd,
147
+ "latency_ns": telemetry.latency_ns,
148
+ "attempt_count": telemetry.attempt_count,
149
+ }
150
+
151
+
152
+ @dataclass(frozen=True, slots=True)
153
+ class ActionForecastArmPlan:
154
+ """One fully prepared scientific forecast call."""
155
+
156
+ arm: PortfolioExperimentalArm
157
+ request: ActionForecastRequest
158
+
159
+ def __post_init__(self) -> None:
160
+ if type(self.arm) is not PortfolioExperimentalArm:
161
+ raise TypeError("arm must be an exact PortfolioExperimentalArm")
162
+ if type(self.request) is not ActionForecastRequest:
163
+ raise TypeError("request must be an exact ActionForecastRequest")
164
+ self.request.__post_init__()
165
+ receipt = self.request.experimental_view_receipt
166
+ if self.arm is PortfolioExperimentalArm.MEMORY:
167
+ if self.request.evidence_mode is not ActionForecastEvidenceMode.GROUNDED:
168
+ raise ValueError("M must be a grounded forecast request")
169
+ if receipt is None or receipt.arm is not PortfolioExperimentalArm.MEMORY:
170
+ raise ValueError("M must carry a MEMORY experimental-view receipt")
171
+ elif self.arm is PortfolioExperimentalArm.PERMUTED_PLACEBO:
172
+ if self.request.evidence_mode is not ActionForecastEvidenceMode.GROUNDED:
173
+ raise ValueError("P must be a grounded forecast request")
174
+ if (
175
+ receipt is None
176
+ or receipt.arm is not PortfolioExperimentalArm.PERMUTED_PLACEBO
177
+ ):
178
+ raise ValueError(
179
+ "P must carry a PERMUTED_PLACEBO experimental-view receipt"
180
+ )
181
+ else:
182
+ if self.request.evidence_mode is not ActionForecastEvidenceMode.CATALOG_ONLY:
183
+ raise ValueError("N must be a catalog-only forecast request")
184
+ if receipt is not None:
185
+ raise ValueError("N cannot carry an experimental-view receipt")
186
+
187
+ def to_record(self) -> dict[str, object]:
188
+ self.__post_init__()
189
+ return {"arm": self.arm.value, "request_sha256": self.request.request_sha256}
190
+
191
+
192
+ @runtime_checkable
193
+ class FiniteActionEvaluator(Protocol):
194
+ """Benchmark-owned asynchronous evaluation of one sealed child."""
195
+
196
+ async def evaluate(
197
+ self,
198
+ request: "FiniteActionEvaluationRequest",
199
+ ) -> FrozenJsonObject: ...
200
+
201
+
202
+ @dataclass(frozen=True, slots=True)
203
+ class FiniteActionEvaluatorBinding:
204
+ """Identified benchmark evaluator injected through the application boundary."""
205
+
206
+ evaluator: FiniteActionEvaluator = field(repr=False, compare=False)
207
+ evaluator_id: str
208
+ evaluator_version: int
209
+ definition_sha256: str
210
+
211
+ def __post_init__(self) -> None:
212
+ if not callable(getattr(self.evaluator, "evaluate", None)):
213
+ raise TypeError("evaluator must expose an async evaluate method")
214
+ if type(self.evaluator_id) is not str or _TOKEN.fullmatch(
215
+ self.evaluator_id
216
+ ) is None:
217
+ raise ValueError("evaluator_id must use the closed token grammar")
218
+ if type(self.evaluator_version) is not int or self.evaluator_version <= 0:
219
+ raise ValueError("evaluator_version must be a positive exact integer")
220
+ require_sha256(self.definition_sha256, "definition_sha256")
221
+
222
+ def to_record(self) -> dict[str, object]:
223
+ self.__post_init__()
224
+ return {
225
+ "evaluator_id": self.evaluator_id,
226
+ "evaluator_version": self.evaluator_version,
227
+ "definition_sha256": self.definition_sha256,
228
+ }
229
+
230
+
231
+ class ActionEvaluationReuseMode(str, Enum):
232
+ """Whether identical G2 actions may share evaluation across study arms."""
233
+
234
+ PER_ARM = "per_arm"
235
+ UNIQUE_ACTION = "unique_action"
236
+
237
+
238
+ @dataclass(frozen=True, slots=True)
239
+ class ActionEvaluationReusePolicyBinding:
240
+ """Identified compute-accounting policy for cross-arm evaluation reuse."""
241
+
242
+ mode: ActionEvaluationReuseMode
243
+ policy_id: str = ACTION_EVALUATION_REUSE_POLICY_ID
244
+ policy_version: int = ACTION_EVALUATION_REUSE_POLICY_VERSION
245
+ definition_sha256: str = ACTION_EVALUATION_REUSE_POLICY_DEFINITION_SHA256
246
+
247
+ def __post_init__(self) -> None:
248
+ if type(self.mode) is not ActionEvaluationReuseMode:
249
+ raise TypeError("mode must be an exact ActionEvaluationReuseMode")
250
+ if type(self.policy_id) is not str or _TOKEN.fullmatch(self.policy_id) is None:
251
+ raise ValueError("policy_id must use the closed token grammar")
252
+ if type(self.policy_version) is not int or self.policy_version <= 0:
253
+ raise ValueError("policy_version must be a positive exact integer")
254
+ require_sha256(self.definition_sha256, "definition_sha256")
255
+
256
+ def to_record(self) -> dict[str, object]:
257
+ self.__post_init__()
258
+ return {
259
+ "mode": self.mode.value,
260
+ "policy_id": self.policy_id,
261
+ "policy_version": self.policy_version,
262
+ "definition_sha256": self.definition_sha256,
263
+ }
264
+
265
+
266
+ def per_arm_evaluation_reuse_policy() -> ActionEvaluationReusePolicyBinding:
267
+ """Return the fail-safe default: no compute reuse across scientific arms."""
268
+
269
+ return ActionEvaluationReusePolicyBinding(ActionEvaluationReuseMode.PER_ARM)
270
+
271
+
272
+ class DurablePhaseCommitRequirement(str, Enum):
273
+ """Whether a run may proceed without a durable phase-commit sink."""
274
+
275
+ OPTIONAL = "optional"
276
+ REQUIRED = "required"
277
+
278
+
279
+ @dataclass(frozen=True, slots=True)
280
+ class DurablePhaseCommitPolicyBinding:
281
+ """Identified interception policy bound into the scientific run request."""
282
+
283
+ requirement: DurablePhaseCommitRequirement
284
+ policy_id: str = DURABLE_PHASE_COMMIT_POLICY_ID
285
+ policy_version: int = DURABLE_PHASE_COMMIT_POLICY_VERSION
286
+ definition_sha256: str = DURABLE_PHASE_COMMIT_POLICY_DEFINITION_SHA256
287
+
288
+ def __post_init__(self) -> None:
289
+ if type(self.requirement) is not DurablePhaseCommitRequirement:
290
+ raise TypeError(
291
+ "requirement must be an exact DurablePhaseCommitRequirement"
292
+ )
293
+ if type(self.policy_id) is not str or _TOKEN.fullmatch(self.policy_id) is None:
294
+ raise ValueError("policy_id must use the closed token grammar")
295
+ if type(self.policy_version) is not int or self.policy_version <= 0:
296
+ raise ValueError("policy_version must be a positive exact integer")
297
+ require_sha256(self.definition_sha256, "definition_sha256")
298
+
299
+ def to_record(self) -> dict[str, object]:
300
+ self.__post_init__()
301
+ return {
302
+ "requirement": self.requirement.value,
303
+ "policy_id": self.policy_id,
304
+ "policy_version": self.policy_version,
305
+ "definition_sha256": self.definition_sha256,
306
+ }
307
+
308
+
309
+ def optional_phase_commit_policy() -> DurablePhaseCommitPolicyBinding:
310
+ """Return the simple-use policy under which a commit sink may be omitted."""
311
+
312
+ return DurablePhaseCommitPolicyBinding(DurablePhaseCommitRequirement.OPTIONAL)
313
+
314
+
315
+ def required_scientific_phase_commit_policy() -> DurablePhaseCommitPolicyBinding:
316
+ """Return the fail-closed policy for prospective scientific execution."""
317
+
318
+ return DurablePhaseCommitPolicyBinding(DurablePhaseCommitRequirement.REQUIRED)
319
+
320
+
321
+ @dataclass(frozen=True, slots=True)
322
+ class PreparedTwoStageActionEvolutionRequest:
323
+ """Prepared G1/reflection outputs and policies for one generic M/P/N run."""
324
+
325
+ run_id: RunId
326
+ arm_plans: tuple[ActionForecastArmPlan, ...]
327
+ g1_option_ids: tuple[str, ...]
328
+ portfolio_size: int
329
+ utility: ForecastPortfolioUtilityBinding
330
+ evaluator: FiniteActionEvaluatorBinding
331
+ evaluation_context: FrozenJsonObject
332
+ evaluation_reuse: ActionEvaluationReusePolicyBinding = field(
333
+ default_factory=per_arm_evaluation_reuse_policy
334
+ )
335
+ phase_commit_policy: DurablePhaseCommitPolicyBinding = field(
336
+ default_factory=optional_phase_commit_policy
337
+ )
338
+
339
+ def __post_init__(self) -> None:
340
+ if type(self.run_id) is not RunId:
341
+ raise TypeError("run_id must be an exact RunId")
342
+ RunId.__post_init__(self.run_id)
343
+ if type(self.arm_plans) is not tuple or any(
344
+ type(value) is not ActionForecastArmPlan for value in self.arm_plans
345
+ ):
346
+ raise TypeError("arm_plans must be an exact ActionForecastArmPlan tuple")
347
+ for plan in self.arm_plans:
348
+ plan.__post_init__()
349
+ if tuple(plan.arm for plan in self.arm_plans) != SCIENTIFIC_ARM_ORDER:
350
+ raise ValueError("arm_plans must contain canonical M/P/N order exactly")
351
+ requests = tuple(plan.request for plan in self.arm_plans)
352
+ baseline = requests[0]
353
+ common = (
354
+ baseline.operation,
355
+ baseline.instruction,
356
+ baseline.context_sha256,
357
+ baseline.optimization_semantics.semantics_id,
358
+ baseline.optimization_semantics.semantics_version,
359
+ baseline.optimization_semantics.definition_sha256,
360
+ baseline.finite_variation_contract.identity_sha256,
361
+ baseline.parent_metric_values,
362
+ baseline.metric_scales,
363
+ baseline.max_output_tokens,
364
+ baseline.temperature,
365
+ )
366
+ for candidate in requests[1:]:
367
+ candidate_common = (
368
+ candidate.operation,
369
+ candidate.instruction,
370
+ candidate.context_sha256,
371
+ candidate.optimization_semantics.semantics_id,
372
+ candidate.optimization_semantics.semantics_version,
373
+ candidate.optimization_semantics.definition_sha256,
374
+ candidate.finite_variation_contract.identity_sha256,
375
+ candidate.parent_metric_values,
376
+ candidate.metric_scales,
377
+ candidate.max_output_tokens,
378
+ candidate.temperature,
379
+ )
380
+ if candidate_common != common:
381
+ raise ValueError(
382
+ "M/P/N may differ only in call identity and evidence treatment"
383
+ )
384
+ if len({request.call_id for request in requests}) != len(requests):
385
+ raise ValueError("M/P/N require distinct logical call IDs")
386
+ memory_registry = requests[0].source_registry
387
+ placebo_registry = requests[1].source_registry
388
+ assert memory_registry is not None and placebo_registry is not None
389
+ if memory_registry.registry_sha256 != placebo_registry.registry_sha256:
390
+ raise ValueError("M and P must use the same admitted source registry")
391
+
392
+ if type(self.g1_option_ids) is not tuple or any(
393
+ type(value) is not str or _OPTION_ID.fullmatch(value) is None
394
+ for value in self.g1_option_ids
395
+ ):
396
+ raise TypeError("g1_option_ids must be an exact option-ID tuple")
397
+ if not self.g1_option_ids:
398
+ raise ValueError("g1_option_ids must be non-empty")
399
+ if self.g1_option_ids != tuple(sorted(set(self.g1_option_ids))):
400
+ raise ValueError("g1_option_ids must be unique and canonical")
401
+ contract = baseline.finite_variation_contract
402
+ validate_finite_variation_contract(contract)
403
+ contract_ids = {option.option_id for option in contract.options}
404
+ if not set(self.g1_option_ids).issubset(contract_ids):
405
+ raise ValueError("g1_option_ids contains an option outside the contract")
406
+ eligible_count = len(contract_ids - set(self.g1_option_ids))
407
+ if type(self.portfolio_size) is not int or self.portfolio_size <= 0:
408
+ raise ValueError("portfolio_size must be a positive exact integer")
409
+ if self.portfolio_size > eligible_count:
410
+ raise ValueError("portfolio_size exceeds the non-G1 action count")
411
+ if type(self.utility) is not ForecastPortfolioUtilityBinding:
412
+ raise TypeError("utility must be an exact identified binding")
413
+ self.utility.__post_init__()
414
+ if type(self.evaluator) is not FiniteActionEvaluatorBinding:
415
+ raise TypeError("evaluator must be an exact identified binding")
416
+ self.evaluator.__post_init__()
417
+ if type(self.evaluation_context) is not FrozenJsonObject:
418
+ raise TypeError("evaluation_context must be an exact FrozenJsonObject")
419
+ if freeze_json(self.evaluation_context) is not self.evaluation_context:
420
+ raise TypeError("evaluation_context must already be frozen typed JSON")
421
+ if type(self.evaluation_reuse) is not ActionEvaluationReusePolicyBinding:
422
+ raise TypeError("evaluation_reuse must be an exact identified binding")
423
+ self.evaluation_reuse.__post_init__()
424
+ if type(self.phase_commit_policy) is not DurablePhaseCommitPolicyBinding:
425
+ raise TypeError("phase_commit_policy must be an exact identified binding")
426
+ self.phase_commit_policy.__post_init__()
427
+
428
+ @property
429
+ def finite_variation_contract(self) -> FiniteVariationContract:
430
+ self.__post_init__()
431
+ return self.arm_plans[0].request.finite_variation_contract
432
+
433
+ @property
434
+ def eligible_option_ids(self) -> tuple[str, ...]:
435
+ self.__post_init__()
436
+ excluded = set(self.g1_option_ids)
437
+ return tuple(
438
+ sorted(
439
+ option.option_id
440
+ for option in self.finite_variation_contract.options
441
+ if option.option_id not in excluded
442
+ )
443
+ )
444
+
445
+ def to_record(self) -> dict[str, object]:
446
+ self.__post_init__()
447
+ return {
448
+ "schema_version": 1,
449
+ "run_id": self.run_id.value,
450
+ "arm_plans": [plan.to_record() for plan in self.arm_plans],
451
+ "finite_contract_identity_sha256": (
452
+ self.finite_variation_contract.identity_sha256
453
+ ),
454
+ "g1_option_ids": list(self.g1_option_ids),
455
+ "eligible_option_ids": list(self.eligible_option_ids),
456
+ "portfolio_size": self.portfolio_size,
457
+ "utility": self.utility.to_record(),
458
+ "evaluator": self.evaluator.to_record(),
459
+ "evaluation_context_sha256": typed_json_sha256(
460
+ self.evaluation_context
461
+ ),
462
+ "evaluation_reuse": self.evaluation_reuse.to_record(),
463
+ "phase_commit_policy": self.phase_commit_policy.to_record(),
464
+ }
465
+
466
+ @property
467
+ def request_sha256(self) -> str:
468
+ return _hash(_REQUEST_DOMAIN, self.to_record())
469
+
470
+
471
+ @dataclass(frozen=True, slots=True)
472
+ class ActionForecastArmExecution:
473
+ arm: PortfolioExperimentalArm
474
+ request_sha256: str
475
+ result: ActionForecastResult
476
+
477
+ def __post_init__(self) -> None:
478
+ if type(self.arm) is not PortfolioExperimentalArm:
479
+ raise TypeError("arm must be exact")
480
+ require_sha256(self.request_sha256, "request_sha256")
481
+ if type(self.result) is not ActionForecastResult:
482
+ raise TypeError("result must be an exact ActionForecastResult")
483
+ self.result.__post_init__()
484
+ if self.result.forecasts.request_sha256 != self.request_sha256:
485
+ raise ValueError("forecast result is bound to a different arm request")
486
+
487
+ def to_record(self) -> dict[str, object]:
488
+ self.__post_init__()
489
+ return {
490
+ "arm": self.arm.value,
491
+ "request_sha256": self.request_sha256,
492
+ "forecast_receipt_sha256": self.result.forecasts.receipt_sha256,
493
+ }
494
+
495
+
496
+ @dataclass(frozen=True, slots=True)
497
+ class ActionAllocationArmExecution:
498
+ arm: PortfolioExperimentalArm
499
+ request: ActionAllocationRequest
500
+ result: ActionAllocationResult
501
+
502
+ def __post_init__(self) -> None:
503
+ if type(self.arm) is not PortfolioExperimentalArm:
504
+ raise TypeError("arm must be exact")
505
+ if type(self.request) is not ActionAllocationRequest:
506
+ raise TypeError("request must be exact ActionAllocationRequest")
507
+ self.request.__post_init__()
508
+ if type(self.result) is not ActionAllocationResult:
509
+ raise TypeError("result must be exact ActionAllocationResult")
510
+ self.result.__post_init__()
511
+ validate_action_portfolio_decision(self.request, self.result.decision)
512
+
513
+ def to_record(self) -> dict[str, object]:
514
+ self.__post_init__()
515
+ return {
516
+ "arm": self.arm.value,
517
+ "allocation_request_sha256": self.request.request_sha256,
518
+ "decision_receipt_sha256": self.result.decision.receipt_sha256,
519
+ "selected_option_ids": [
520
+ member.option_id for member in self.result.decision.members
521
+ ],
522
+ }
523
+
524
+
525
+ @dataclass(frozen=True, slots=True)
526
+ class FiniteActionEvaluationRequest:
527
+ run_id: RunId
528
+ finite_contract_identity_sha256: str
529
+ option: FiniteVariationOption
530
+ selected_by_arms: tuple[PortfolioExperimentalArm, ...]
531
+ context: FrozenJsonObject
532
+
533
+ def __post_init__(self) -> None:
534
+ if type(self.run_id) is not RunId:
535
+ raise TypeError("run_id must be exact")
536
+ RunId.__post_init__(self.run_id)
537
+ require_sha256(
538
+ self.finite_contract_identity_sha256,
539
+ "finite_contract_identity_sha256",
540
+ )
541
+ validate_finite_variation_option(self.option)
542
+ if type(self.selected_by_arms) is not tuple or not self.selected_by_arms:
543
+ raise ValueError("selected_by_arms must be a non-empty exact tuple")
544
+ if any(type(arm) is not PortfolioExperimentalArm for arm in self.selected_by_arms):
545
+ raise TypeError("selected_by_arms must contain exact arms")
546
+ if self.selected_by_arms != tuple(
547
+ sorted(set(self.selected_by_arms), key=_ARM_INDEX.__getitem__)
548
+ ):
549
+ raise ValueError("selected_by_arms must be unique and canonical")
550
+ if type(self.context) is not FrozenJsonObject:
551
+ raise TypeError("context must be an exact FrozenJsonObject")
552
+ if freeze_json(self.context) is not self.context:
553
+ raise TypeError("context must already be frozen typed JSON")
554
+
555
+ def to_record(self) -> dict[str, object]:
556
+ self.__post_init__()
557
+ return {
558
+ "schema_version": 1,
559
+ "run_id": self.run_id.value,
560
+ "finite_contract_identity_sha256": self.finite_contract_identity_sha256,
561
+ "option": self.option.evidence_record(),
562
+ "selected_by_arms": [arm.value for arm in self.selected_by_arms],
563
+ "context_sha256": typed_json_sha256(self.context),
564
+ }
565
+
566
+ @property
567
+ def request_sha256(self) -> str:
568
+ return _hash(_EVALUATION_REQUEST_DOMAIN, self.to_record())
569
+
570
+
571
+ @dataclass(frozen=True, slots=True)
572
+ class FiniteActionEvaluationResult:
573
+ request: FiniteActionEvaluationRequest
574
+ outcome: FrozenJsonObject
575
+ evaluator_id: str
576
+ evaluator_version: int
577
+ evaluator_definition_sha256: str
578
+
579
+ def __post_init__(self) -> None:
580
+ if type(self.request) is not FiniteActionEvaluationRequest:
581
+ raise TypeError("request must be an exact FiniteActionEvaluationRequest")
582
+ self.request.__post_init__()
583
+ if type(self.outcome) is not FrozenJsonObject:
584
+ raise TypeError("outcome must be an exact FrozenJsonObject")
585
+ if freeze_json(self.outcome) is not self.outcome:
586
+ raise TypeError("outcome must already be frozen typed JSON")
587
+ if type(self.evaluator_id) is not str or _TOKEN.fullmatch(
588
+ self.evaluator_id
589
+ ) is None:
590
+ raise ValueError("evaluator_id must use the closed token grammar")
591
+ if type(self.evaluator_version) is not int or self.evaluator_version <= 0:
592
+ raise ValueError("evaluator_version must be positive")
593
+ require_sha256(
594
+ self.evaluator_definition_sha256,
595
+ "evaluator_definition_sha256",
596
+ )
597
+
598
+ def _unsigned_record(self) -> dict[str, object]:
599
+ self.__post_init__()
600
+ return {
601
+ "schema_version": 1,
602
+ "evaluation_request_sha256": self.request.request_sha256,
603
+ "option_id": self.request.option.option_id,
604
+ "option_identity_sha256": self.request.option.identity_sha256,
605
+ "child_configuration_sha256": (
606
+ self.request.option.child_configuration_sha256
607
+ ),
608
+ "selected_by_arms": [arm.value for arm in self.request.selected_by_arms],
609
+ "outcome_sha256": typed_json_sha256(self.outcome),
610
+ "evaluator": {
611
+ "evaluator_id": self.evaluator_id,
612
+ "evaluator_version": self.evaluator_version,
613
+ "definition_sha256": self.evaluator_definition_sha256,
614
+ },
615
+ }
616
+
617
+ @property
618
+ def receipt_sha256(self) -> str:
619
+ return _hash(_EVALUATION_RESULT_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
+
625
+ class TwoStageActionPhase(str, Enum):
626
+ FORECAST = "forecast"
627
+ ALLOCATE = "allocate"
628
+ EVALUATE = "evaluate"
629
+
630
+
631
+ @dataclass(frozen=True, slots=True)
632
+ class TwoStageActionPhaseReceipt:
633
+ phase: TwoStageActionPhase
634
+ input_sha256: str
635
+ output_sha256: str
636
+
637
+ def __post_init__(self) -> None:
638
+ if type(self.phase) is not TwoStageActionPhase:
639
+ raise TypeError("phase must be an exact TwoStageActionPhase")
640
+ require_sha256(self.input_sha256, "input_sha256")
641
+ require_sha256(self.output_sha256, "output_sha256")
642
+
643
+ def _unsigned_record(self) -> dict[str, object]:
644
+ self.__post_init__()
645
+ return {
646
+ "schema_version": 1,
647
+ "phase": self.phase.value,
648
+ "input_sha256": self.input_sha256,
649
+ "output_sha256": self.output_sha256,
650
+ }
651
+
652
+ @property
653
+ def receipt_sha256(self) -> str:
654
+ return _hash(_PHASE_RECEIPT_DOMAIN, self._unsigned_record())
655
+
656
+ def to_record(self) -> dict[str, object]:
657
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
658
+
659
+
660
+ @dataclass(frozen=True, slots=True)
661
+ class TwoStageActionPhaseCommit:
662
+ """Exact receipt/payload pair handed to an external durability boundary."""
663
+
664
+ receipt: TwoStageActionPhaseReceipt
665
+ payload: FrozenJsonObject
666
+
667
+ def __post_init__(self) -> None:
668
+ if type(self.receipt) is not TwoStageActionPhaseReceipt:
669
+ raise TypeError("receipt must be an exact TwoStageActionPhaseReceipt")
670
+ self.receipt.__post_init__()
671
+ if type(self.payload) is not FrozenJsonObject:
672
+ raise TypeError("payload must be an exact FrozenJsonObject")
673
+ if freeze_json(self.payload) is not self.payload:
674
+ raise TypeError("payload must already be frozen typed JSON")
675
+ if typed_json_sha256(self.payload) != self.receipt.output_sha256:
676
+ raise ValueError("phase payload differs from its hash-bound receipt")
677
+
678
+ def to_record(self) -> dict[str, object]:
679
+ self.__post_init__()
680
+ return {
681
+ "schema_version": 1,
682
+ "receipt": self.receipt.to_record(),
683
+ "payload_sha256": typed_json_sha256(self.payload),
684
+ }
685
+
686
+
687
+ @runtime_checkable
688
+ class TwoStageActionPhaseCommitSink(Protocol):
689
+ """Durably commit one completed phase before the coordinator can continue."""
690
+
691
+ def commit(
692
+ self,
693
+ phase_commit: TwoStageActionPhaseCommit,
694
+ ) -> Awaitable[None] | None: ...
695
+
696
+
697
+ class TwoStageActionPhaseCommitError(RuntimeError):
698
+ """A required or supplied phase durability boundary did not complete."""
699
+
700
+
701
+ def _freeze_phase_payload(value: dict[str, object]) -> FrozenJsonObject:
702
+ frozen = freeze_json(value)
703
+ if type(frozen) is not FrozenJsonObject:
704
+ raise AssertionError("phase payload root must freeze as an object")
705
+ return frozen
706
+
707
+
708
+ def _phase_commit(
709
+ *,
710
+ receipt: TwoStageActionPhaseReceipt,
711
+ payload: FrozenJsonObject,
712
+ ) -> TwoStageActionPhaseCommit:
713
+ return TwoStageActionPhaseCommit(receipt=receipt, payload=payload)
714
+
715
+
716
+ async def _publish_phase_commit(
717
+ *,
718
+ policy: DurablePhaseCommitPolicyBinding,
719
+ sink: TwoStageActionPhaseCommitSink | None,
720
+ phase_commit: TwoStageActionPhaseCommit,
721
+ ) -> None:
722
+ policy.__post_init__()
723
+ phase_commit.__post_init__()
724
+ if sink is None:
725
+ if policy.requirement is DurablePhaseCommitRequirement.REQUIRED:
726
+ raise TwoStageActionPhaseCommitError(
727
+ "required durable phase-commit sink is absent"
728
+ )
729
+ return
730
+ commit_method = getattr(sink, "commit", None)
731
+ if not callable(commit_method):
732
+ raise TypeError("phase_commit_sink must expose a commit method")
733
+ try:
734
+ result = commit_method(phase_commit)
735
+ if inspect.isawaitable(result):
736
+ result = await result
737
+ if result is not None:
738
+ raise TypeError("phase commit sinks must return None")
739
+ except Exception as exc:
740
+ raise TwoStageActionPhaseCommitError(
741
+ f"{phase_commit.receipt.phase.value} phase commit failed"
742
+ ) from exc
743
+
744
+
745
+ async def _gather_all_settled(
746
+ awaitables: tuple[Awaitable[object], ...],
747
+ ) -> tuple[object, ...]:
748
+ """Await every sibling and then raise the first failure in input order.
749
+
750
+ Scientific multi-arm calls are an all-or-nothing stage. Waiting for every
751
+ submitted sibling gives the outer artifact recorder a complete physical-
752
+ attempt ledger, while input-order failure selection keeps the observable
753
+ error deterministic. A failed stage never yields partial results.
754
+ """
755
+
756
+ tasks = tuple(asyncio.ensure_future(awaitable) for awaitable in awaitables)
757
+ try:
758
+ results = tuple(
759
+ await asyncio.gather(*tasks, return_exceptions=True)
760
+ )
761
+ except asyncio.CancelledError:
762
+ # Cancellation is not scientific partial success either. Explicitly
763
+ # cancel and settle every sibling so provider/evaluator cleanup and its
764
+ # physical-attempt journal finish before cancellation escapes.
765
+ for task in tasks:
766
+ if not task.done():
767
+ task.cancel()
768
+ await asyncio.gather(*tasks, return_exceptions=True)
769
+ raise
770
+ for result in results:
771
+ if isinstance(result, BaseException):
772
+ raise result
773
+ return results
774
+
775
+
776
+ @dataclass(frozen=True, slots=True, eq=False)
777
+ class PreparedTwoStageActionEvolutionResult:
778
+ request_sha256: str
779
+ forecasts: tuple[ActionForecastArmExecution, ...]
780
+ allocations: tuple[ActionAllocationArmExecution, ...]
781
+ evaluations: tuple[FiniteActionEvaluationResult, ...]
782
+ evaluation_reuse: ActionEvaluationReusePolicyBinding
783
+ phase_commit_policy: DurablePhaseCommitPolicyBinding
784
+ phase_receipts: tuple[TwoStageActionPhaseReceipt, ...]
785
+
786
+ def __post_init__(self) -> None:
787
+ require_sha256(self.request_sha256, "request_sha256")
788
+ if tuple(value.arm for value in self.forecasts) != SCIENTIFIC_ARM_ORDER:
789
+ raise ValueError("forecasts must use canonical M/P/N order")
790
+ if tuple(value.arm for value in self.allocations) != SCIENTIFIC_ARM_ORDER:
791
+ raise ValueError("allocations must use canonical M/P/N order")
792
+ for value in self.forecasts:
793
+ value.__post_init__()
794
+ for value in self.allocations:
795
+ value.__post_init__()
796
+ if type(self.evaluations) is not tuple or not self.evaluations:
797
+ raise ValueError("evaluations must be a non-empty exact tuple")
798
+ for value in self.evaluations:
799
+ if type(value) is not FiniteActionEvaluationResult:
800
+ raise TypeError("evaluations must contain exact results")
801
+ value.__post_init__()
802
+ evaluation_ids = tuple(
803
+ value.request.request_sha256 for value in self.evaluations
804
+ )
805
+ if len(set(evaluation_ids)) != len(evaluation_ids):
806
+ raise ValueError("an exact G2 evaluation request may execute only once")
807
+ if type(self.evaluation_reuse) is not ActionEvaluationReusePolicyBinding:
808
+ raise TypeError("evaluation_reuse must be an exact identified binding")
809
+ self.evaluation_reuse.__post_init__()
810
+ if type(self.phase_commit_policy) is not DurablePhaseCommitPolicyBinding:
811
+ raise TypeError("phase_commit_policy must be an exact identified binding")
812
+ self.phase_commit_policy.__post_init__()
813
+ expected_phases = tuple(TwoStageActionPhase)
814
+ if tuple(value.phase for value in self.phase_receipts) != expected_phases:
815
+ raise ValueError("phase_receipts must use forecast/allocate/evaluate order")
816
+ for value in self.phase_receipts:
817
+ value.__post_init__()
818
+
819
+ def _unsigned_record(self) -> dict[str, object]:
820
+ self.__post_init__()
821
+ return {
822
+ "schema_version": 1,
823
+ "request_sha256": self.request_sha256,
824
+ "forecasts": [value.to_record() for value in self.forecasts],
825
+ "allocations": [value.to_record() for value in self.allocations],
826
+ "evaluations": [value.to_record() for value in self.evaluations],
827
+ "evaluation_reuse": self.evaluation_reuse.to_record(),
828
+ "phase_commit_policy": self.phase_commit_policy.to_record(),
829
+ "phase_receipts": [value.to_record() for value in self.phase_receipts],
830
+ }
831
+
832
+ @property
833
+ def receipt_sha256(self) -> str:
834
+ return _hash(_RESULT_DOMAIN, self._unsigned_record())
835
+
836
+ def to_record(self) -> dict[str, object]:
837
+ return {**self._unsigned_record(), "receipt_sha256": self.receipt_sha256}
838
+
839
+ def __eq__(self, other: object) -> bool:
840
+ return (
841
+ type(self) is PreparedTwoStageActionEvolutionResult
842
+ and type(other) is PreparedTwoStageActionEvolutionResult
843
+ and self.receipt_sha256 == other.receipt_sha256
844
+ )
845
+
846
+ __hash__ = None
847
+
848
+
849
+ @dataclass(frozen=True, slots=True)
850
+ class PreparedTwoStageActionEvolution:
851
+ """Execute prepared M/P/N forecasts, allocations, and policy-bound G2 evaluation."""
852
+
853
+ forecaster: ActionForecastPolicy
854
+ allocator: DeterministicActionAllocator
855
+
856
+ def __post_init__(self) -> None:
857
+ if not callable(getattr(self.forecaster, "forecast", None)):
858
+ raise TypeError("forecaster must expose an async forecast method")
859
+ if not callable(getattr(self.allocator, "allocate", None)):
860
+ raise TypeError("allocator must expose an allocate method")
861
+
862
+ async def run(
863
+ self,
864
+ request: PreparedTwoStageActionEvolutionRequest,
865
+ *,
866
+ phase_commit_sink: TwoStageActionPhaseCommitSink | None = None,
867
+ ) -> PreparedTwoStageActionEvolutionResult:
868
+ if type(request) is not PreparedTwoStageActionEvolutionRequest:
869
+ raise TypeError("request must be exact PreparedTwoStageActionEvolutionRequest")
870
+ request.__post_init__()
871
+ self.__post_init__()
872
+ if (
873
+ request.phase_commit_policy.requirement
874
+ is DurablePhaseCommitRequirement.REQUIRED
875
+ and phase_commit_sink is None
876
+ ):
877
+ # Fail before spending provider or evaluator compute when a
878
+ # prospective run cannot establish its durability boundary.
879
+ raise TwoStageActionPhaseCommitError(
880
+ "required durable phase-commit sink is absent"
881
+ )
882
+ if phase_commit_sink is not None and not callable(
883
+ getattr(phase_commit_sink, "commit", None)
884
+ ):
885
+ raise TypeError("phase_commit_sink must expose a commit method")
886
+
887
+ raw_forecasts = await _gather_all_settled(
888
+ tuple(
889
+ self.forecaster.forecast(plan.request)
890
+ for plan in request.arm_plans
891
+ )
892
+ )
893
+ forecast_executions: list[ActionForecastArmExecution] = []
894
+ for plan, result in zip(request.arm_plans, raw_forecasts, strict=True):
895
+ if type(result) is not ActionForecastResult:
896
+ raise TypeError("forecaster returned a non-ActionForecastResult")
897
+ validate_resolved_action_forecasts(plan.request, result.forecasts)
898
+ forecast_executions.append(
899
+ ActionForecastArmExecution(
900
+ arm=plan.arm,
901
+ request_sha256=plan.request.request_sha256,
902
+ result=result,
903
+ )
904
+ )
905
+ forecasts = tuple(forecast_executions)
906
+ forecast_payload = _freeze_phase_payload(
907
+ {
908
+ "schema_version": 2,
909
+ "phase": TwoStageActionPhase.FORECAST.value,
910
+ "run_request_sha256": request.request_sha256,
911
+ "arm_executions": [
912
+ {
913
+ "arm": value.arm.value,
914
+ "request_sha256": value.request_sha256,
915
+ "resolved_action_forecast_batch": (
916
+ value.result.forecasts.to_record()
917
+ ),
918
+ "telemetry": _agentic_call_telemetry_record(
919
+ value.result.telemetry
920
+ ),
921
+ }
922
+ for value in forecasts
923
+ ],
924
+ }
925
+ )
926
+ forecast_receipt = TwoStageActionPhaseReceipt(
927
+ phase=TwoStageActionPhase.FORECAST,
928
+ input_sha256=_hash(
929
+ b"agent-evolve:two-stage-forecast-input:v1\x00",
930
+ [plan.to_record() for plan in request.arm_plans],
931
+ ),
932
+ output_sha256=typed_json_sha256(forecast_payload),
933
+ )
934
+ await _publish_phase_commit(
935
+ policy=request.phase_commit_policy,
936
+ sink=phase_commit_sink,
937
+ phase_commit=_phase_commit(
938
+ receipt=forecast_receipt,
939
+ payload=forecast_payload,
940
+ ),
941
+ )
942
+
943
+ allocations_list: list[ActionAllocationArmExecution] = []
944
+ for plan, forecast in zip(request.arm_plans, forecasts, strict=True):
945
+ allocation_request = ActionAllocationRequest(
946
+ forecast_request=plan.request,
947
+ forecasts=forecast.result.forecasts,
948
+ eligible_option_ids=request.eligible_option_ids,
949
+ portfolio_size=request.portfolio_size,
950
+ utility=request.utility,
951
+ )
952
+ allocation_result = self.allocator.allocate(allocation_request)
953
+ if type(allocation_result) is not ActionAllocationResult:
954
+ raise TypeError("allocator returned a non-ActionAllocationResult")
955
+ allocations_list.append(
956
+ ActionAllocationArmExecution(
957
+ arm=plan.arm,
958
+ request=allocation_request,
959
+ result=allocation_result,
960
+ )
961
+ )
962
+ allocations = tuple(allocations_list)
963
+ allocation_payload = _freeze_phase_payload(
964
+ {
965
+ "schema_version": 1,
966
+ "phase": TwoStageActionPhase.ALLOCATE.value,
967
+ "run_request_sha256": request.request_sha256,
968
+ "arm_executions": [
969
+ {
970
+ "arm": value.arm.value,
971
+ "allocation_request": value.request.to_record(),
972
+ "decision": value.result.decision.to_record(),
973
+ }
974
+ for value in allocations
975
+ ],
976
+ }
977
+ )
978
+ allocation_receipt = TwoStageActionPhaseReceipt(
979
+ phase=TwoStageActionPhase.ALLOCATE,
980
+ input_sha256=forecast_receipt.output_sha256,
981
+ output_sha256=typed_json_sha256(allocation_payload),
982
+ )
983
+ # This await is the oracle-firewall boundary: no evaluator coroutine is
984
+ # even constructed until the selected decisions are durably accepted.
985
+ await _publish_phase_commit(
986
+ policy=request.phase_commit_policy,
987
+ sink=phase_commit_sink,
988
+ phase_commit=_phase_commit(
989
+ receipt=allocation_receipt,
990
+ payload=allocation_payload,
991
+ ),
992
+ )
993
+
994
+ if request.evaluation_reuse.mode is ActionEvaluationReuseMode.PER_ARM:
995
+ # Arm order and allocated rank are already canonical. Repeating the
996
+ # same option across arms intentionally consumes matched compute.
997
+ evaluation_requests = tuple(
998
+ FiniteActionEvaluationRequest(
999
+ run_id=request.run_id,
1000
+ finite_contract_identity_sha256=(
1001
+ request.finite_variation_contract.identity_sha256
1002
+ ),
1003
+ option=request.finite_variation_contract.resolve(member.option_id),
1004
+ selected_by_arms=(allocation.arm,),
1005
+ context=request.evaluation_context,
1006
+ )
1007
+ for allocation in allocations
1008
+ for member in allocation.result.decision.members
1009
+ )
1010
+ else:
1011
+ selected_by: dict[str, list[PortfolioExperimentalArm]] = {}
1012
+ for allocation in allocations:
1013
+ for member in allocation.result.decision.members:
1014
+ selected_by.setdefault(member.option_id, []).append(allocation.arm)
1015
+ # Contract order, rather than task completion order, is the durable
1016
+ # ordering for explicitly reusable deterministic evaluations.
1017
+ evaluation_requests = tuple(
1018
+ FiniteActionEvaluationRequest(
1019
+ run_id=request.run_id,
1020
+ finite_contract_identity_sha256=(
1021
+ request.finite_variation_contract.identity_sha256
1022
+ ),
1023
+ option=option,
1024
+ selected_by_arms=tuple(selected_by[option.option_id]),
1025
+ context=request.evaluation_context,
1026
+ )
1027
+ for option in request.finite_variation_contract.options
1028
+ if option.option_id in selected_by
1029
+ )
1030
+ raw_outcomes = await _gather_all_settled(
1031
+ tuple(
1032
+ request.evaluator.evaluator.evaluate(evaluation_request)
1033
+ for evaluation_request in evaluation_requests
1034
+ )
1035
+ )
1036
+ evaluations_list: list[FiniteActionEvaluationResult] = []
1037
+ for evaluation_request, outcome in zip(
1038
+ evaluation_requests,
1039
+ raw_outcomes,
1040
+ strict=True,
1041
+ ):
1042
+ if type(outcome) is not FrozenJsonObject:
1043
+ raise TypeError("evaluator returned a non-FrozenJsonObject outcome")
1044
+ evaluations_list.append(
1045
+ FiniteActionEvaluationResult(
1046
+ request=evaluation_request,
1047
+ outcome=outcome,
1048
+ evaluator_id=request.evaluator.evaluator_id,
1049
+ evaluator_version=request.evaluator.evaluator_version,
1050
+ evaluator_definition_sha256=(
1051
+ request.evaluator.definition_sha256
1052
+ ),
1053
+ )
1054
+ )
1055
+ evaluations = tuple(evaluations_list)
1056
+ evaluation_payload = _freeze_phase_payload(
1057
+ {
1058
+ "schema_version": 1,
1059
+ "phase": TwoStageActionPhase.EVALUATE.value,
1060
+ "run_request_sha256": request.request_sha256,
1061
+ "evaluation_results": [
1062
+ {
1063
+ **value.to_record(),
1064
+ "outcome": thaw_json(value.outcome),
1065
+ }
1066
+ for value in evaluations
1067
+ ],
1068
+ }
1069
+ )
1070
+ evaluation_receipt = TwoStageActionPhaseReceipt(
1071
+ phase=TwoStageActionPhase.EVALUATE,
1072
+ input_sha256=_hash(
1073
+ b"agent-evolve:two-stage-evaluation-input:v1\x00",
1074
+ [value.to_record() for value in evaluation_requests],
1075
+ ),
1076
+ output_sha256=typed_json_sha256(evaluation_payload),
1077
+ )
1078
+ await _publish_phase_commit(
1079
+ policy=request.phase_commit_policy,
1080
+ sink=phase_commit_sink,
1081
+ phase_commit=_phase_commit(
1082
+ receipt=evaluation_receipt,
1083
+ payload=evaluation_payload,
1084
+ ),
1085
+ )
1086
+ return PreparedTwoStageActionEvolutionResult(
1087
+ request_sha256=request.request_sha256,
1088
+ forecasts=forecasts,
1089
+ allocations=allocations,
1090
+ evaluations=evaluations,
1091
+ evaluation_reuse=request.evaluation_reuse,
1092
+ phase_commit_policy=request.phase_commit_policy,
1093
+ phase_receipts=(
1094
+ forecast_receipt,
1095
+ allocation_receipt,
1096
+ evaluation_receipt,
1097
+ ),
1098
+ )
1099
+
1100
+
1101
+ __all__ = [
1102
+ "ACTION_EVALUATION_REUSE_POLICY_DEFINITION_SHA256",
1103
+ "ACTION_EVALUATION_REUSE_POLICY_ID",
1104
+ "ACTION_EVALUATION_REUSE_POLICY_VERSION",
1105
+ "DURABLE_PHASE_COMMIT_POLICY_DEFINITION_SHA256",
1106
+ "DURABLE_PHASE_COMMIT_POLICY_ID",
1107
+ "DURABLE_PHASE_COMMIT_POLICY_VERSION",
1108
+ "ActionEvaluationReuseMode",
1109
+ "ActionEvaluationReusePolicyBinding",
1110
+ "ActionAllocationArmExecution",
1111
+ "ActionForecastArmExecution",
1112
+ "ActionForecastArmPlan",
1113
+ "DurablePhaseCommitPolicyBinding",
1114
+ "DurablePhaseCommitRequirement",
1115
+ "FiniteActionEvaluationRequest",
1116
+ "FiniteActionEvaluationResult",
1117
+ "FiniteActionEvaluator",
1118
+ "FiniteActionEvaluatorBinding",
1119
+ "PreparedTwoStageActionEvolution",
1120
+ "PreparedTwoStageActionEvolutionRequest",
1121
+ "PreparedTwoStageActionEvolutionResult",
1122
+ "SCIENTIFIC_ARM_ORDER",
1123
+ "TwoStageActionPhase",
1124
+ "TwoStageActionPhaseCommit",
1125
+ "TwoStageActionPhaseCommitError",
1126
+ "TwoStageActionPhaseCommitSink",
1127
+ "TwoStageActionPhaseReceipt",
1128
+ "optional_phase_commit_policy",
1129
+ "per_arm_evaluation_reuse_policy",
1130
+ "required_scientific_phase_commit_policy",
1131
+ ]