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,1228 @@
1
+ """Generic propose -> broker -> evaluate orchestration for residual evolution.
2
+
3
+ Proposal experts own workload knowledge, candidate legality, and exact
4
+ materialization. This application service receives only already-materialized
5
+ actions, gives every expert access to the same authenticated prior cutoff, and
6
+ lets the workload-blind action broker choose the expensive evaluations.
7
+
8
+ The service deliberately stops before archive credit. A downstream utility
9
+ policy must turn the jointly evaluated slate into conserved immediate credit,
10
+ and a lineage resolver may later append finite-horizon return. Keeping those
11
+ steps outside proposal execution prevents an expert from grading itself.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import asyncio
17
+ import hashlib
18
+ import json
19
+ import re
20
+ from dataclasses import dataclass, field
21
+ from typing import Protocol, runtime_checkable
22
+
23
+ from agent_evolve.application.agentic_evolution import EvolutionCandidate
24
+ from agent_evolve.application.contextual_search_controller import SearchPhase
25
+ from agent_evolve.application.materialized_action_broker import (
26
+ MaterializedActionBrokerDecision,
27
+ MaterializedActionBrokerRequest,
28
+ MaterializedActionAllocationRequirement,
29
+ MaterializedActionDescriptor,
30
+ MaterializedSlateFeasibilityPort,
31
+ MaterializedSlateValuePort,
32
+ RegretBrokeredMaterializedActionPolicy,
33
+ )
34
+ from agent_evolve.domain.patch import require_sha256
35
+ from agent_evolve.domain.typed_json import (
36
+ FrozenJsonObject,
37
+ freeze_json,
38
+ thaw_json,
39
+ typed_json_sha256,
40
+ )
41
+
42
+
43
+ RESIDUAL_PORTFOLIO_EVOLUTION_ID = "conserved_residual_portfolio_evolution"
44
+ RESIDUAL_PORTFOLIO_EVOLUTION_VERSION = 2
45
+ RESIDUAL_PORTFOLIO_EVOLUTION_DEFINITION_SHA256 = hashlib.sha256(
46
+ b"agent-evolve:conserved-residual-portfolio-evolution:v2;"
47
+ b"boundary=authenticated-prior-cutoff-and-opaque-proposal-context;"
48
+ b"experts=workload-owned-sealed-materialized-action-batches;"
49
+ b"allocation=optional-async-outcome-blind-policy-over-sealed-union;"
50
+ b"broker=workload-model-provider-blind-materialized-action-selection;"
51
+ b"reference=conservative-escrow;"
52
+ b"execution=selected-actions-only-grouped-by-owning-expert-concurrent;"
53
+ b"credit=downstream-conserved-archive-return-not-expert-self-score;"
54
+ b"lineage=downstream-append-only-resolved-return;"
55
+ b"core-workload-model-provider-branches=false"
56
+ ).hexdigest()
57
+ DISJOINT_ACTION_EVALUATION_WAVES_V1 = "disjoint_action_subsets_v1"
58
+
59
+ _TOKEN = re.compile(r"^[a-z][a-z0-9_.:-]{0,255}$")
60
+ _REQUEST_DOMAIN = b"agent-evolve:residual-portfolio-request:v1\x00"
61
+ _PROPOSAL_DOMAIN = b"agent-evolve:residual-portfolio-proposal:v1\x00"
62
+ _EVALUATION_DOMAIN = b"agent-evolve:residual-portfolio-evaluation:v1\x00"
63
+ _RESULT_DOMAIN = b"agent-evolve:residual-portfolio-result:v1\x00"
64
+
65
+
66
+ def _canonical_json(value: object) -> bytes:
67
+ return json.dumps(
68
+ value,
69
+ allow_nan=False,
70
+ ensure_ascii=True,
71
+ separators=(",", ":"),
72
+ sort_keys=True,
73
+ ).encode("ascii", errors="strict")
74
+
75
+
76
+ def _hash(domain: bytes, value: object) -> str:
77
+ return hashlib.sha256(domain + _canonical_json(value)).hexdigest()
78
+
79
+
80
+ def _require_token(value: str, *, name: str) -> None:
81
+ if type(value) is not str or _TOKEN.fullmatch(value) is None:
82
+ raise ValueError(f"{name} must use the closed token grammar")
83
+
84
+
85
+ def _expert_capacity(
86
+ values: tuple[tuple[str, int], ...],
87
+ expert_id: str,
88
+ ) -> int:
89
+ try:
90
+ return dict(values)[expert_id]
91
+ except KeyError as error:
92
+ raise ValueError("proposal expert is absent from the request") from error
93
+
94
+
95
+ def _candidate_record(candidate: EvolutionCandidate) -> dict[str, object]:
96
+ if type(candidate) is not EvolutionCandidate:
97
+ raise TypeError("evaluations must contain exact EvolutionCandidate values")
98
+ EvolutionCandidate.__post_init__(candidate)
99
+ detailed = candidate.detailed_evaluation
100
+ return {
101
+ "candidate_id": candidate.candidate_id.value,
102
+ "configuration_sha256": candidate.occurrence.configuration_hash,
103
+ "generation": candidate.generation,
104
+ "valid": candidate.valid,
105
+ "operator_compliant": candidate.operator_compliant,
106
+ "evidence_compliant": candidate.evidence_compliant,
107
+ "objectives": [
108
+ {"metric_id": metric_id, "value_hex": value.hex()}
109
+ for metric_id, value in candidate.objectives
110
+ ],
111
+ "detailed_evaluation_sha256": (
112
+ None if detailed is None else detailed.evidence_sha256
113
+ ),
114
+ }
115
+
116
+
117
+ @dataclass(frozen=True, slots=True)
118
+ class ResidualPortfolioDecisionRequest:
119
+ """Authenticated common cutoff presented to every proposal expert.
120
+
121
+ ``proposal_context`` is intentionally opaque to the core. A workload
122
+ adapter can place typed, replayable state in that object, while broker
123
+ behavior remains independent of its fields.
124
+ """
125
+
126
+ campaign_scope_sha256: str
127
+ prior_state_sha256: str
128
+ decision_index: int
129
+ phase: SearchPhase
130
+ remaining_decisions: int
131
+ remaining_evaluations: int
132
+ evaluation_slots: int
133
+ expert_proposal_slots: tuple[tuple[str, int], ...]
134
+ proposal_context: FrozenJsonObject
135
+ reference_escrow_slots: int = 1
136
+ request_sha256: str = field(init=False)
137
+
138
+ def __post_init__(self) -> None:
139
+ require_sha256(self.campaign_scope_sha256, "campaign_scope_sha256")
140
+ require_sha256(self.prior_state_sha256, "prior_state_sha256")
141
+ if type(self.decision_index) is not int or self.decision_index <= 0:
142
+ raise ValueError("decision_index must be a positive exact integer")
143
+ if type(self.phase) is not SearchPhase:
144
+ raise TypeError("phase must be an exact SearchPhase")
145
+ for name in ("remaining_decisions", "remaining_evaluations"):
146
+ value = getattr(self, name)
147
+ if type(value) is not int or value <= 0:
148
+ raise ValueError(f"{name} must be a positive exact integer")
149
+ if (
150
+ type(self.evaluation_slots) is not int
151
+ or self.evaluation_slots <= 0
152
+ or self.evaluation_slots > self.remaining_evaluations
153
+ ):
154
+ raise ValueError("evaluation_slots must fit remaining evaluations")
155
+ if (
156
+ type(self.expert_proposal_slots) is not tuple
157
+ or not self.expert_proposal_slots
158
+ ):
159
+ raise ValueError("expert_proposal_slots must be a non-empty exact tuple")
160
+ for expert_id, slots in self.expert_proposal_slots:
161
+ _require_token(expert_id, name="expert_id")
162
+ if type(slots) is not int or slots <= 0:
163
+ raise ValueError("expert proposal slots must be positive")
164
+ expert_ids = tuple(value[0] for value in self.expert_proposal_slots)
165
+ if expert_ids != tuple(sorted(set(expert_ids))):
166
+ raise ValueError("expert proposal capacities must be unique and canonical")
167
+ if sum(value[1] for value in self.expert_proposal_slots) < (
168
+ self.evaluation_slots
169
+ ):
170
+ raise ValueError("proposal capacity cannot cover evaluation capacity")
171
+ if (
172
+ type(self.reference_escrow_slots) is not int
173
+ or not 0 <= self.reference_escrow_slots <= self.evaluation_slots
174
+ ):
175
+ raise ValueError("reference escrow must fit evaluation capacity")
176
+ if (
177
+ type(self.proposal_context) is not FrozenJsonObject
178
+ or freeze_json(self.proposal_context) is not self.proposal_context
179
+ ):
180
+ raise TypeError("proposal_context must be an exact frozen JSON object")
181
+ object.__setattr__(
182
+ self,
183
+ "request_sha256",
184
+ _hash(_REQUEST_DOMAIN, self._unsigned_record()),
185
+ )
186
+
187
+ def proposal_slots_for(self, expert_id: str) -> int:
188
+ _require_token(expert_id, name="expert_id")
189
+ return _expert_capacity(self.expert_proposal_slots, expert_id)
190
+
191
+ def _unsigned_record(self) -> dict[str, object]:
192
+ return {
193
+ "schema_version": 1,
194
+ "campaign_scope_sha256": self.campaign_scope_sha256,
195
+ "prior_state_sha256": self.prior_state_sha256,
196
+ "decision_index": self.decision_index,
197
+ "phase": self.phase.value,
198
+ "remaining_decisions": self.remaining_decisions,
199
+ "remaining_evaluations": self.remaining_evaluations,
200
+ "evaluation_slots": self.evaluation_slots,
201
+ "expert_proposal_slots": [
202
+ {"expert_id": expert_id, "proposal_slots": slots}
203
+ for expert_id, slots in self.expert_proposal_slots
204
+ ],
205
+ "proposal_context_sha256": typed_json_sha256(self.proposal_context),
206
+ "reference_escrow_slots": self.reference_escrow_slots,
207
+ }
208
+
209
+ def to_record(self, *, include_proposal_context: bool = False) -> dict[str, object]:
210
+ self.__post_init__()
211
+ record = {**self._unsigned_record(), "request_sha256": self.request_sha256}
212
+ if include_proposal_context:
213
+ record["proposal_context"] = thaw_json(self.proposal_context)
214
+ return record
215
+
216
+
217
+ @dataclass(frozen=True, slots=True)
218
+ class MaterializedActionProposalBatch:
219
+ """One expert's sealed, unevaluated action batch."""
220
+
221
+ request_sha256: str
222
+ expert_id: str
223
+ expert_version: int
224
+ expert_definition_sha256: str
225
+ actions: tuple[MaterializedActionDescriptor, ...]
226
+ evidence: FrozenJsonObject
227
+ proposal_sha256: str = field(init=False)
228
+
229
+ def __post_init__(self) -> None:
230
+ require_sha256(self.request_sha256, "request_sha256")
231
+ _require_token(self.expert_id, name="expert_id")
232
+ if type(self.expert_version) is not int or self.expert_version <= 0:
233
+ raise ValueError("expert_version must be positive")
234
+ require_sha256(
235
+ self.expert_definition_sha256,
236
+ "expert_definition_sha256",
237
+ )
238
+ if type(self.actions) is not tuple or not self.actions:
239
+ raise ValueError("proposal batch must contain at least one action")
240
+ for action in self.actions:
241
+ if type(action) is not MaterializedActionDescriptor:
242
+ raise TypeError("proposal actions must be exact descriptors")
243
+ action.__post_init__()
244
+ if action.expert_id != self.expert_id:
245
+ raise ValueError("proposal action names another expert")
246
+ if tuple(value.native_rank for value in self.actions) != tuple(
247
+ range(1, len(self.actions) + 1)
248
+ ):
249
+ raise ValueError("expert actions must retain contiguous native ranks")
250
+ for values, name in (
251
+ (
252
+ tuple(value.action_sha256 for value in self.actions),
253
+ "action identities",
254
+ ),
255
+ (
256
+ tuple(value.target_candidate_id for value in self.actions),
257
+ "target candidate IDs",
258
+ ),
259
+ ):
260
+ if len(set(values)) != len(values):
261
+ raise ValueError(f"proposal batch repeats {name}")
262
+ contexts = {
263
+ (
264
+ value.context.campaign_scope_sha256,
265
+ value.context.decision_index,
266
+ value.context.phase,
267
+ )
268
+ for value in self.actions
269
+ }
270
+ if len(contexts) != 1:
271
+ raise ValueError("one proposal batch cannot mix decision cutoffs")
272
+ if (
273
+ type(self.evidence) is not FrozenJsonObject
274
+ or freeze_json(self.evidence) is not self.evidence
275
+ ):
276
+ raise TypeError("proposal evidence must be an exact frozen object")
277
+ object.__setattr__(
278
+ self,
279
+ "proposal_sha256",
280
+ _hash(_PROPOSAL_DOMAIN, self._unsigned_record()),
281
+ )
282
+
283
+ def require_request(self, request: ResidualPortfolioDecisionRequest) -> None:
284
+ if type(request) is not ResidualPortfolioDecisionRequest:
285
+ raise TypeError("request must be exact")
286
+ request.__post_init__()
287
+ if self.request_sha256 != request.request_sha256:
288
+ raise ValueError("proposal targets another residual decision")
289
+ if len(self.actions) > request.proposal_slots_for(self.expert_id):
290
+ raise ValueError("expert exceeded its proposal capacity")
291
+ for action in self.actions:
292
+ context = action.context
293
+ if (
294
+ context.campaign_scope_sha256 != request.campaign_scope_sha256
295
+ or context.decision_index != request.decision_index
296
+ or context.phase is not request.phase
297
+ or context.remaining_decisions != request.remaining_decisions
298
+ or context.remaining_evaluations != request.remaining_evaluations
299
+ ):
300
+ raise ValueError("proposal action context differs from the request")
301
+
302
+ def _unsigned_record(self) -> dict[str, object]:
303
+ return {
304
+ "schema_version": 1,
305
+ "request_sha256": self.request_sha256,
306
+ "expert": {
307
+ "expert_id": self.expert_id,
308
+ "expert_version": self.expert_version,
309
+ "definition_sha256": self.expert_definition_sha256,
310
+ },
311
+ "actions": [value.to_record() for value in self.actions],
312
+ "evidence_sha256": typed_json_sha256(self.evidence),
313
+ "evaluation_performed": False,
314
+ }
315
+
316
+ def to_record(
317
+ self,
318
+ *,
319
+ include_configurations: bool = False,
320
+ include_evidence: bool = False,
321
+ ) -> dict[str, object]:
322
+ self.__post_init__()
323
+ record = {
324
+ **self._unsigned_record(),
325
+ "actions": [
326
+ value.to_record(include_configuration=include_configurations)
327
+ for value in self.actions
328
+ ],
329
+ "proposal_sha256": self.proposal_sha256,
330
+ }
331
+ if include_evidence:
332
+ record["evidence"] = thaw_json(self.evidence)
333
+ return record
334
+
335
+
336
+ @dataclass(frozen=True, slots=True)
337
+ class DisjointActionEvaluationWave:
338
+ """One fail-closed reservation of previously unevaluated actions.
339
+
340
+ Sequential allocation can evaluate a pilot and a continuation from the
341
+ same sealed proposal. The action—not the proposal batch—is therefore the
342
+ exactly-once boundary. A reservation is recorded before invoking the
343
+ authoritative evaluator so an interrupted wave cannot be retried
344
+ ambiguously through the same live expert instance.
345
+ """
346
+
347
+ proposal_sha256: str
348
+ wave_index: int
349
+ previously_attempted_action_count: int
350
+ selected_action_sha256s: tuple[str, ...]
351
+
352
+ def __post_init__(self) -> None:
353
+ require_sha256(self.proposal_sha256, "proposal_sha256")
354
+ if type(self.wave_index) is not int or self.wave_index <= 0:
355
+ raise ValueError("wave_index must be a positive exact integer")
356
+ if (
357
+ type(self.previously_attempted_action_count) is not int
358
+ or self.previously_attempted_action_count < 0
359
+ ):
360
+ raise ValueError(
361
+ "previously_attempted_action_count must be non-negative"
362
+ )
363
+ if (
364
+ type(self.selected_action_sha256s) is not tuple
365
+ or not self.selected_action_sha256s
366
+ or self.selected_action_sha256s
367
+ != tuple(sorted(set(self.selected_action_sha256s)))
368
+ ):
369
+ raise ValueError(
370
+ "selected_action_sha256s must be non-empty and canonical"
371
+ )
372
+ for value in self.selected_action_sha256s:
373
+ require_sha256(value, "selected_action_sha256")
374
+
375
+ def to_record(self) -> dict[str, object]:
376
+ self.__post_init__()
377
+ return {
378
+ "semantics": DISJOINT_ACTION_EVALUATION_WAVES_V1,
379
+ "wave_index": self.wave_index,
380
+ "previously_attempted_action_count": (
381
+ self.previously_attempted_action_count
382
+ ),
383
+ "selected_action_sha256s": list(
384
+ self.selected_action_sha256s
385
+ ),
386
+ "selected_subset_frozen_before_wave": True,
387
+ "selected_actions_disjoint_from_previous_waves": True,
388
+ "repeated_action_evaluation_forbidden": True,
389
+ }
390
+
391
+
392
+ @dataclass(slots=True)
393
+ class DisjointActionEvaluationLedger:
394
+ """Reserve disjoint evaluation waves over any sealed proposal batch."""
395
+
396
+ _attempted_by_proposal: dict[str, set[str]] = field(
397
+ init=False,
398
+ default_factory=dict,
399
+ )
400
+ _wave_count_by_proposal: dict[str, int] = field(
401
+ init=False,
402
+ default_factory=dict,
403
+ )
404
+
405
+ def reserve(
406
+ self,
407
+ proposal: MaterializedActionProposalBatch,
408
+ selected_action_sha256s: tuple[str, ...],
409
+ ) -> DisjointActionEvaluationWave:
410
+ if type(proposal) is not MaterializedActionProposalBatch:
411
+ raise TypeError("proposal must be exact")
412
+ proposal.__post_init__()
413
+ if (
414
+ type(selected_action_sha256s) is not tuple
415
+ or not selected_action_sha256s
416
+ or selected_action_sha256s
417
+ != tuple(sorted(set(selected_action_sha256s)))
418
+ ):
419
+ raise ValueError(
420
+ "selected action hashes must be non-empty and canonical"
421
+ )
422
+ available = {
423
+ value.action_sha256 for value in proposal.actions
424
+ }
425
+ selected = set(selected_action_sha256s)
426
+ if not selected.issubset(available):
427
+ raise ValueError("selected action is outside the sealed proposal")
428
+ attempted = self._attempted_by_proposal.setdefault(
429
+ proposal.proposal_sha256,
430
+ set(),
431
+ )
432
+ overlap = tuple(sorted(selected.intersection(attempted)))
433
+ if overlap:
434
+ raise ValueError(
435
+ "an action cannot be evaluated in more than one wave: "
436
+ + ",".join(overlap)
437
+ )
438
+ wave_index = (
439
+ self._wave_count_by_proposal.get(
440
+ proposal.proposal_sha256,
441
+ 0,
442
+ )
443
+ + 1
444
+ )
445
+ reservation = DisjointActionEvaluationWave(
446
+ proposal_sha256=proposal.proposal_sha256,
447
+ wave_index=wave_index,
448
+ previously_attempted_action_count=len(attempted),
449
+ selected_action_sha256s=selected_action_sha256s,
450
+ )
451
+ # Reserve before any evaluator await. Failure is deliberately
452
+ # fail-closed: a fresh run may replay from durable evidence, while this
453
+ # live expert cannot accidentally double-spend evaluator budget.
454
+ attempted.update(selected)
455
+ self._wave_count_by_proposal[proposal.proposal_sha256] = wave_index
456
+ return reservation
457
+
458
+
459
+ @dataclass(frozen=True, slots=True)
460
+ class MaterializedActionEvaluation:
461
+ """Exact join between one selected action and its real candidate."""
462
+
463
+ action: MaterializedActionDescriptor
464
+ candidate: EvolutionCandidate
465
+ evaluator_receipt_sha256: str
466
+ evaluation_sha256: str = field(init=False)
467
+
468
+ def __post_init__(self) -> None:
469
+ if type(self.action) is not MaterializedActionDescriptor:
470
+ raise TypeError("action must be an exact descriptor")
471
+ self.action.__post_init__()
472
+ if type(self.candidate) is not EvolutionCandidate:
473
+ raise TypeError("candidate must be an exact EvolutionCandidate")
474
+ EvolutionCandidate.__post_init__(self.candidate)
475
+ require_sha256(
476
+ self.evaluator_receipt_sha256,
477
+ "evaluator_receipt_sha256",
478
+ )
479
+ if self.candidate.candidate_id != self.action.target_candidate_id:
480
+ raise ValueError("evaluated candidate ID differs from its action")
481
+ if (
482
+ self.candidate.occurrence.configuration_hash
483
+ != self.action.configuration_sha256
484
+ ):
485
+ raise ValueError("evaluated configuration differs from its action")
486
+ object.__setattr__(
487
+ self,
488
+ "evaluation_sha256",
489
+ _hash(_EVALUATION_DOMAIN, self._unsigned_record()),
490
+ )
491
+
492
+ def _unsigned_record(self) -> dict[str, object]:
493
+ return {
494
+ "schema_version": 1,
495
+ "action_sha256": self.action.action_sha256,
496
+ "evaluator_receipt_sha256": self.evaluator_receipt_sha256,
497
+ "candidate": _candidate_record(self.candidate),
498
+ }
499
+
500
+ def to_record(self) -> dict[str, object]:
501
+ self.__post_init__()
502
+ return {
503
+ **self._unsigned_record(),
504
+ "evaluation_sha256": self.evaluation_sha256,
505
+ }
506
+
507
+
508
+ @dataclass(frozen=True, slots=True)
509
+ class MaterializedActionEvaluationBatch:
510
+ """One expert's exact response for its broker-selected subset."""
511
+
512
+ proposal_sha256: str
513
+ expert_id: str
514
+ expert_version: int
515
+ expert_definition_sha256: str
516
+ selected_action_sha256s: tuple[str, ...]
517
+ evaluations: tuple[MaterializedActionEvaluation, ...]
518
+ evidence: FrozenJsonObject
519
+ batch_sha256: str = field(init=False)
520
+
521
+ def __post_init__(self) -> None:
522
+ require_sha256(self.proposal_sha256, "proposal_sha256")
523
+ _require_token(self.expert_id, name="expert_id")
524
+ if type(self.expert_version) is not int or self.expert_version <= 0:
525
+ raise ValueError("expert_version must be positive")
526
+ require_sha256(
527
+ self.expert_definition_sha256,
528
+ "expert_definition_sha256",
529
+ )
530
+ if (
531
+ type(self.selected_action_sha256s) is not tuple
532
+ or not self.selected_action_sha256s
533
+ ):
534
+ raise ValueError("selected action hashes must be non-empty")
535
+ for value in self.selected_action_sha256s:
536
+ require_sha256(value, "selected_action_sha256")
537
+ if self.selected_action_sha256s != tuple(
538
+ sorted(set(self.selected_action_sha256s))
539
+ ):
540
+ raise ValueError("selected action hashes must be unique and canonical")
541
+ if (
542
+ type(self.evaluations) is not tuple
543
+ or len(self.evaluations) != len(self.selected_action_sha256s)
544
+ ):
545
+ raise ValueError("evaluations must exactly cover selected actions")
546
+ for value in self.evaluations:
547
+ if type(value) is not MaterializedActionEvaluation:
548
+ raise TypeError("evaluations must contain exact values")
549
+ value.__post_init__()
550
+ if value.action.expert_id != self.expert_id:
551
+ raise ValueError("evaluation action names another expert")
552
+ if tuple(value.action.action_sha256 for value in self.evaluations) != (
553
+ self.selected_action_sha256s
554
+ ):
555
+ raise ValueError("evaluation order differs from selected action order")
556
+ if (
557
+ type(self.evidence) is not FrozenJsonObject
558
+ or freeze_json(self.evidence) is not self.evidence
559
+ ):
560
+ raise TypeError("evaluation evidence must be an exact frozen object")
561
+ object.__setattr__(
562
+ self,
563
+ "batch_sha256",
564
+ _hash(_EVALUATION_DOMAIN, self._unsigned_record()),
565
+ )
566
+
567
+ def require_proposal(self, proposal: MaterializedActionProposalBatch) -> None:
568
+ if type(proposal) is not MaterializedActionProposalBatch:
569
+ raise TypeError("proposal must be exact")
570
+ proposal.__post_init__()
571
+ if (
572
+ self.proposal_sha256 != proposal.proposal_sha256
573
+ or (
574
+ self.expert_id,
575
+ self.expert_version,
576
+ self.expert_definition_sha256,
577
+ )
578
+ != (
579
+ proposal.expert_id,
580
+ proposal.expert_version,
581
+ proposal.expert_definition_sha256,
582
+ )
583
+ ):
584
+ raise ValueError("evaluation batch differs from its proposal identity")
585
+ action_by_sha256 = {
586
+ value.action_sha256: value for value in proposal.actions
587
+ }
588
+ try:
589
+ expected = tuple(
590
+ action_by_sha256[value] for value in self.selected_action_sha256s
591
+ )
592
+ except KeyError as error:
593
+ raise ValueError(
594
+ "evaluation selected outside the sealed proposal"
595
+ ) from error
596
+ if tuple(value.action for value in self.evaluations) != expected:
597
+ raise ValueError("evaluation descriptors differ from sealed actions")
598
+
599
+ def _unsigned_record(self) -> dict[str, object]:
600
+ return {
601
+ "schema_version": 1,
602
+ "proposal_sha256": self.proposal_sha256,
603
+ "expert": {
604
+ "expert_id": self.expert_id,
605
+ "expert_version": self.expert_version,
606
+ "definition_sha256": self.expert_definition_sha256,
607
+ },
608
+ "selected_action_sha256s": list(self.selected_action_sha256s),
609
+ "evaluations": [value.to_record() for value in self.evaluations],
610
+ "evidence_sha256": typed_json_sha256(self.evidence),
611
+ }
612
+
613
+ def to_record(self, *, include_evidence: bool = False) -> dict[str, object]:
614
+ self.__post_init__()
615
+ record = {**self._unsigned_record(), "batch_sha256": self.batch_sha256}
616
+ if include_evidence:
617
+ record["evidence"] = thaw_json(self.evidence)
618
+ return record
619
+
620
+
621
+ @runtime_checkable
622
+ class MaterializedActionProposalExpertPort(Protocol):
623
+ """Workload-owned proposal and evaluation boundary.
624
+
625
+ ``evaluate`` may be called more than once for one proposal only when the
626
+ requested action subsets are disjoint. Implementations used by a
627
+ sequential runtime expose
628
+ ``evaluation_wave_semantics=DISJOINT_ACTION_EVALUATION_WAVES_V1`` and
629
+ fail closed on any repeated action.
630
+ """
631
+
632
+ expert_id: str
633
+ expert_version: int
634
+ definition_sha256: str
635
+
636
+ async def propose(
637
+ self,
638
+ request: ResidualPortfolioDecisionRequest,
639
+ ) -> MaterializedActionProposalBatch: ...
640
+
641
+ async def evaluate(
642
+ self,
643
+ proposal: MaterializedActionProposalBatch,
644
+ selected_action_sha256s: tuple[str, ...],
645
+ ) -> MaterializedActionEvaluationBatch: ...
646
+
647
+
648
+ @runtime_checkable
649
+ class MaterializedActionCounterfactualEvaluationPort(Protocol):
650
+ """Optional workload capability for excluded real-evaluation assays.
651
+
652
+ This method is deliberately separate from authoritative ``evaluate``.
653
+ Implementations must execute the same physical evaluator while avoiding
654
+ authoritative budget registration, archive admission, and any state that
655
+ can influence later action selection. A runtime requesting a paired
656
+ counterfactual fails closed when the owning expert does not expose this
657
+ capability.
658
+ """
659
+
660
+ async def evaluate_counterfactual(
661
+ self,
662
+ proposal: MaterializedActionProposalBatch,
663
+ selected_action_sha256s: tuple[str, ...],
664
+ ) -> MaterializedActionEvaluationBatch: ...
665
+
666
+
667
+ @runtime_checkable
668
+ class MaterializedActionAllocationPolicyPort(Protocol):
669
+ """Select authenticated required actions from the sealed proposal union.
670
+
671
+ The policy is asynchronous so an LLM, learned ranker, or committee can
672
+ inspect the complete population without blocking the event loop. It sees
673
+ the prior-cutoff request and unevaluated proposal batches only; real
674
+ candidate evaluations do not exist at this boundary.
675
+ """
676
+
677
+ policy_id: str
678
+ policy_version: int
679
+ definition_sha256: str
680
+
681
+ async def require(
682
+ self,
683
+ request: ResidualPortfolioDecisionRequest,
684
+ proposals: tuple[MaterializedActionProposalBatch, ...],
685
+ ) -> MaterializedActionAllocationRequirement: ...
686
+
687
+
688
+ def _expert_identity(
689
+ expert: MaterializedActionProposalExpertPort,
690
+ ) -> tuple[str, int, str]:
691
+ if not isinstance(expert, MaterializedActionProposalExpertPort):
692
+ raise TypeError("proposal expert must implement its runtime port")
693
+ identity = (
694
+ getattr(expert, "expert_id", None),
695
+ getattr(expert, "expert_version", None),
696
+ getattr(expert, "definition_sha256", None),
697
+ )
698
+ _require_token(identity[0], name="expert_id")
699
+ if type(identity[1]) is not int or identity[1] <= 0:
700
+ raise ValueError("proposal expert version must be positive")
701
+ require_sha256(identity[2], "proposal expert definition_sha256")
702
+ return identity # type: ignore[return-value]
703
+
704
+
705
+ def _allocation_policy_identity(
706
+ policy: MaterializedActionAllocationPolicyPort,
707
+ ) -> tuple[str, int, str]:
708
+ if not isinstance(policy, MaterializedActionAllocationPolicyPort):
709
+ raise TypeError(
710
+ "allocation policy must implement "
711
+ "MaterializedActionAllocationPolicyPort"
712
+ )
713
+ identity = (
714
+ getattr(policy, "policy_id", None),
715
+ getattr(policy, "policy_version", None),
716
+ getattr(policy, "definition_sha256", None),
717
+ )
718
+ _require_token(identity[0], name="allocation policy_id")
719
+ if type(identity[1]) is not int or identity[1] <= 0:
720
+ raise ValueError("allocation policy version must be positive")
721
+ require_sha256(identity[2], "allocation policy definition_sha256")
722
+ return identity # type: ignore[return-value]
723
+
724
+
725
+ async def propose_materialized_action_batches(
726
+ *,
727
+ experts: tuple[MaterializedActionProposalExpertPort, ...],
728
+ request: ResidualPortfolioDecisionRequest,
729
+ ) -> tuple[MaterializedActionProposalBatch, ...]:
730
+ """Run one sealed proposal wave and validate its complete expert join."""
731
+
732
+ if type(request) is not ResidualPortfolioDecisionRequest:
733
+ raise TypeError("request must be an exact residual portfolio request")
734
+ request.__post_init__()
735
+ if type(experts) is not tuple or not experts:
736
+ raise ValueError("experts must be a non-empty exact tuple")
737
+ identities = tuple(_expert_identity(value) for value in experts)
738
+ expert_ids = tuple(value[0] for value in identities)
739
+ if expert_ids != tuple(sorted(set(expert_ids))):
740
+ raise ValueError("experts must use canonical unique IDs")
741
+ requested_expert_ids = tuple(
742
+ value[0] for value in request.expert_proposal_slots
743
+ )
744
+ if requested_expert_ids != expert_ids:
745
+ raise ValueError("request expert capacities differ from runtime experts")
746
+ raw = await asyncio.gather(
747
+ *(expert.propose(request) for expert in experts)
748
+ )
749
+ proposals = tuple(raw)
750
+ for identity, proposal in zip(identities, proposals, strict=True):
751
+ if type(proposal) is not MaterializedActionProposalBatch:
752
+ raise TypeError("proposal expert returned a foreign batch")
753
+ proposal.__post_init__()
754
+ proposal.require_request(request)
755
+ if (
756
+ proposal.expert_id,
757
+ proposal.expert_version,
758
+ proposal.expert_definition_sha256,
759
+ ) != identity:
760
+ raise ValueError("proposal batch has a foreign expert identity")
761
+ actions = tuple(
762
+ action for proposal in proposals for action in proposal.actions
763
+ )
764
+ if len(actions) < request.evaluation_slots:
765
+ raise ValueError("realized proposal union cannot fill evaluation capacity")
766
+ if len({value.action_sha256 for value in actions}) != len(actions):
767
+ raise ValueError("proposal union repeats an action identity")
768
+ if len({value.target_candidate_id for value in actions}) != len(actions):
769
+ raise ValueError("proposal union repeats a target candidate ID")
770
+ return proposals
771
+
772
+
773
+ async def _evaluate_materialized_action_subset(
774
+ *,
775
+ experts: tuple[MaterializedActionProposalExpertPort, ...],
776
+ proposals: tuple[MaterializedActionProposalBatch, ...],
777
+ selected_action_sha256s: tuple[str, ...],
778
+ counterfactual: bool,
779
+ ) -> tuple[MaterializedActionEvaluationBatch, ...]:
780
+ """Evaluate and authenticate one exact subset of a sealed universe."""
781
+
782
+ if (
783
+ type(selected_action_sha256s) is not tuple
784
+ or not selected_action_sha256s
785
+ or selected_action_sha256s
786
+ != tuple(sorted(set(selected_action_sha256s)))
787
+ ):
788
+ raise ValueError(
789
+ "selected_action_sha256s must be non-empty and canonical"
790
+ )
791
+ identities = tuple(_expert_identity(value) for value in experts)
792
+ expert_ids = tuple(value[0] for value in identities)
793
+ if expert_ids != tuple(sorted(set(expert_ids))):
794
+ raise ValueError("experts must use canonical unique IDs")
795
+ if (
796
+ type(proposals) is not tuple
797
+ or tuple(value.expert_id for value in proposals) != expert_ids
798
+ ):
799
+ raise ValueError("proposals must exactly cover runtime experts")
800
+ proposal_by_expert = {value.expert_id: value for value in proposals}
801
+ action_by_sha256 = {
802
+ action.action_sha256: action
803
+ for proposal in proposals
804
+ for action in proposal.actions
805
+ }
806
+ if not set(selected_action_sha256s).issubset(action_by_sha256):
807
+ raise ValueError("selected action is outside the proposal universe")
808
+ selected_by_expert = {
809
+ expert_id: tuple(
810
+ value
811
+ for value in selected_action_sha256s
812
+ if action_by_sha256[value].expert_id == expert_id
813
+ )
814
+ for expert_id in expert_ids
815
+ }
816
+ experts_by_id = {
817
+ identity[0]: expert
818
+ for identity, expert in zip(identities, experts, strict=True)
819
+ }
820
+ active = tuple(
821
+ value for value in expert_ids if selected_by_expert[value]
822
+ )
823
+
824
+ async def evaluate_one(
825
+ expert_id: str,
826
+ ) -> MaterializedActionEvaluationBatch:
827
+ expert = experts_by_id[expert_id]
828
+ proposal = proposal_by_expert[expert_id]
829
+ selected = selected_by_expert[expert_id]
830
+ if not counterfactual:
831
+ return await expert.evaluate(proposal, selected)
832
+ if not isinstance(
833
+ expert,
834
+ MaterializedActionCounterfactualEvaluationPort,
835
+ ):
836
+ raise TypeError(
837
+ "paired counterfactual evaluation requires the owning "
838
+ "expert to implement "
839
+ "MaterializedActionCounterfactualEvaluationPort"
840
+ )
841
+ return await expert.evaluate_counterfactual(proposal, selected)
842
+
843
+ raw = await asyncio.gather(
844
+ *(evaluate_one(expert_id) for expert_id in active)
845
+ )
846
+ batches = tuple(raw)
847
+ for expert_id, batch in zip(active, batches, strict=True):
848
+ if type(batch) is not MaterializedActionEvaluationBatch:
849
+ raise TypeError("proposal expert returned a foreign evaluation batch")
850
+ batch.__post_init__()
851
+ batch.require_proposal(proposal_by_expert[expert_id])
852
+ if batch.selected_action_sha256s != selected_by_expert[expert_id]:
853
+ raise ValueError("expert evaluated a different selected subset")
854
+ identity = _expert_identity(experts_by_id[expert_id])
855
+ if (
856
+ batch.expert_id,
857
+ batch.expert_version,
858
+ batch.expert_definition_sha256,
859
+ ) != identity:
860
+ raise ValueError("evaluation batch has a foreign expert identity")
861
+ evaluated = tuple(
862
+ sorted(
863
+ action_sha256
864
+ for batch in batches
865
+ for action_sha256 in batch.selected_action_sha256s
866
+ )
867
+ )
868
+ if evaluated != selected_action_sha256s:
869
+ raise ValueError("evaluation batches do not close the selected subset")
870
+ return batches
871
+
872
+
873
+ async def evaluate_materialized_action_subset(
874
+ *,
875
+ experts: tuple[MaterializedActionProposalExpertPort, ...],
876
+ proposals: tuple[MaterializedActionProposalBatch, ...],
877
+ selected_action_sha256s: tuple[str, ...],
878
+ ) -> tuple[MaterializedActionEvaluationBatch, ...]:
879
+ """Evaluate an authoritative subset of one sealed proposal universe."""
880
+
881
+ return await _evaluate_materialized_action_subset(
882
+ experts=experts,
883
+ proposals=proposals,
884
+ selected_action_sha256s=selected_action_sha256s,
885
+ counterfactual=False,
886
+ )
887
+
888
+
889
+ async def evaluate_materialized_action_counterfactual_subset(
890
+ *,
891
+ experts: tuple[MaterializedActionProposalExpertPort, ...],
892
+ proposals: tuple[MaterializedActionProposalBatch, ...],
893
+ selected_action_sha256s: tuple[str, ...],
894
+ ) -> tuple[MaterializedActionEvaluationBatch, ...]:
895
+ """Evaluate an excluded real arm without authoritative side effects."""
896
+
897
+ return await _evaluate_materialized_action_subset(
898
+ experts=experts,
899
+ proposals=proposals,
900
+ selected_action_sha256s=selected_action_sha256s,
901
+ counterfactual=True,
902
+ )
903
+
904
+
905
+ @dataclass(frozen=True, slots=True)
906
+ class ResidualPortfolioEvolutionResult:
907
+ """Authenticated execution evidence before downstream archive credit."""
908
+
909
+ request: ResidualPortfolioDecisionRequest
910
+ proposals: tuple[MaterializedActionProposalBatch, ...]
911
+ broker_decision: MaterializedActionBrokerDecision
912
+ evaluation_batches: tuple[MaterializedActionEvaluationBatch, ...]
913
+ slate_value_definition_sha256: str
914
+ slate_feasibility_definition_sha256: str
915
+ method_definition_sha256: str = (
916
+ RESIDUAL_PORTFOLIO_EVOLUTION_DEFINITION_SHA256
917
+ )
918
+ result_sha256: str = field(init=False)
919
+
920
+ def __post_init__(self) -> None:
921
+ if type(self.request) is not ResidualPortfolioDecisionRequest:
922
+ raise TypeError("request must be exact")
923
+ self.request.__post_init__()
924
+ if type(self.proposals) is not tuple or not self.proposals:
925
+ raise ValueError("result must contain proposal batches")
926
+ for value in self.proposals:
927
+ if type(value) is not MaterializedActionProposalBatch:
928
+ raise TypeError("proposals must contain exact batches")
929
+ value.__post_init__()
930
+ value.require_request(self.request)
931
+ proposal_ids = tuple(value.expert_id for value in self.proposals)
932
+ if proposal_ids != tuple(sorted(set(proposal_ids))):
933
+ raise ValueError("proposal batches must be expert-canonical")
934
+ if type(self.broker_decision) is not MaterializedActionBrokerDecision:
935
+ raise TypeError("broker_decision must be exact")
936
+ self.broker_decision.__post_init__()
937
+ if len(self.broker_decision.selected_actions) != self.request.evaluation_slots:
938
+ raise ValueError("broker decision does not fill evaluation capacity")
939
+ action_by_sha256 = {
940
+ action.action_sha256: action
941
+ for proposal in self.proposals
942
+ for action in proposal.actions
943
+ }
944
+ if len(action_by_sha256) != sum(
945
+ len(value.actions) for value in self.proposals
946
+ ):
947
+ raise ValueError("proposal union repeats an action identity")
948
+ for action in self.broker_decision.selected_actions:
949
+ if action_by_sha256.get(action.action_sha256) != action:
950
+ raise ValueError("broker selected outside the proposal union")
951
+ if type(self.evaluation_batches) is not tuple:
952
+ raise TypeError("evaluation_batches must be an exact tuple")
953
+ proposal_by_expert = {value.expert_id: value for value in self.proposals}
954
+ for value in self.evaluation_batches:
955
+ if type(value) is not MaterializedActionEvaluationBatch:
956
+ raise TypeError("evaluation_batches must contain exact values")
957
+ value.__post_init__()
958
+ try:
959
+ proposal = proposal_by_expert[value.expert_id]
960
+ except KeyError as error:
961
+ raise ValueError(
962
+ "evaluation names an absent proposal expert"
963
+ ) from error
964
+ value.require_proposal(proposal)
965
+ evaluation_expert_ids = tuple(
966
+ value.expert_id for value in self.evaluation_batches
967
+ )
968
+ if evaluation_expert_ids != tuple(sorted(set(evaluation_expert_ids))):
969
+ raise ValueError("evaluation batches must be expert-canonical")
970
+ selected = tuple(
971
+ value.action_sha256 for value in self.broker_decision.selected_actions
972
+ )
973
+ evaluated = tuple(
974
+ sorted(
975
+ action_sha256
976
+ for batch in self.evaluation_batches
977
+ for action_sha256 in batch.selected_action_sha256s
978
+ )
979
+ )
980
+ if tuple(sorted(selected)) != evaluated:
981
+ raise ValueError("real evaluations do not exactly cover the broker slate")
982
+ for name in (
983
+ "slate_value_definition_sha256",
984
+ "slate_feasibility_definition_sha256",
985
+ "method_definition_sha256",
986
+ ):
987
+ require_sha256(getattr(self, name), name)
988
+ object.__setattr__(
989
+ self,
990
+ "result_sha256",
991
+ _hash(_RESULT_DOMAIN, self._unsigned_record()),
992
+ )
993
+
994
+ @property
995
+ def evaluations(self) -> tuple[MaterializedActionEvaluation, ...]:
996
+ by_action = {
997
+ value.action.action_sha256: value
998
+ for batch in self.evaluation_batches
999
+ for value in batch.evaluations
1000
+ }
1001
+ return tuple(
1002
+ by_action[value.action_sha256]
1003
+ for value in self.broker_decision.selected_actions
1004
+ )
1005
+
1006
+ @property
1007
+ def candidates(self) -> tuple[EvolutionCandidate, ...]:
1008
+ return tuple(value.candidate for value in self.evaluations)
1009
+
1010
+ def _unsigned_record(self) -> dict[str, object]:
1011
+ return {
1012
+ "schema_version": 1,
1013
+ "method": {
1014
+ "method_id": RESIDUAL_PORTFOLIO_EVOLUTION_ID,
1015
+ "method_version": RESIDUAL_PORTFOLIO_EVOLUTION_VERSION,
1016
+ "definition_sha256": self.method_definition_sha256,
1017
+ },
1018
+ "request_sha256": self.request.request_sha256,
1019
+ "proposal_sha256s": [value.proposal_sha256 for value in self.proposals],
1020
+ "broker_decision_sha256": self.broker_decision.decision_sha256,
1021
+ "evaluation_batch_sha256s": [
1022
+ value.batch_sha256 for value in self.evaluation_batches
1023
+ ],
1024
+ "selected_action_sha256s": [
1025
+ value.action_sha256
1026
+ for value in self.broker_decision.selected_actions
1027
+ ],
1028
+ "candidate_records": [
1029
+ _candidate_record(value) for value in self.candidates
1030
+ ],
1031
+ "slate_value_definition_sha256": (
1032
+ self.slate_value_definition_sha256
1033
+ ),
1034
+ "slate_feasibility_definition_sha256": (
1035
+ self.slate_feasibility_definition_sha256
1036
+ ),
1037
+ "archive_credit_included": False,
1038
+ "lineage_return_resolution_included": False,
1039
+ }
1040
+
1041
+ def to_record(
1042
+ self,
1043
+ *,
1044
+ include_allocation_evidence: bool = False,
1045
+ ) -> dict[str, object]:
1046
+ self.__post_init__()
1047
+ return {
1048
+ **self._unsigned_record(),
1049
+ "broker_decision": self.broker_decision.to_record(
1050
+ include_allocation_evidence=include_allocation_evidence,
1051
+ ),
1052
+ "proposals": [value.to_record() for value in self.proposals],
1053
+ "evaluation_batches": [
1054
+ value.to_record() for value in self.evaluation_batches
1055
+ ],
1056
+ "result_sha256": self.result_sha256,
1057
+ }
1058
+
1059
+
1060
+ @dataclass(frozen=True, slots=True)
1061
+ class ResidualPortfolioEvolution:
1062
+ """Execute one workload-blind residual expert market."""
1063
+
1064
+ experts: tuple[MaterializedActionProposalExpertPort, ...]
1065
+ broker: RegretBrokeredMaterializedActionPolicy
1066
+ slate_value: MaterializedSlateValuePort
1067
+ slate_feasibility: MaterializedSlateFeasibilityPort
1068
+ allocation_policy: MaterializedActionAllocationPolicyPort | None = field(
1069
+ default=None,
1070
+ repr=False,
1071
+ compare=False,
1072
+ )
1073
+
1074
+ def __post_init__(self) -> None:
1075
+ if type(self.experts) is not tuple or not self.experts:
1076
+ raise ValueError("experts must be a non-empty exact tuple")
1077
+ identities = tuple(_expert_identity(value) for value in self.experts)
1078
+ expert_ids = tuple(value[0] for value in identities)
1079
+ if expert_ids != tuple(sorted(set(expert_ids))):
1080
+ raise ValueError("experts must use canonical unique IDs")
1081
+ if type(self.broker) is not RegretBrokeredMaterializedActionPolicy:
1082
+ raise TypeError("broker must be an exact regret-broker policy")
1083
+ self.broker.__post_init__()
1084
+ if not isinstance(self.slate_value, MaterializedSlateValuePort):
1085
+ raise TypeError("slate_value must implement its port")
1086
+ if not isinstance(self.slate_feasibility, MaterializedSlateFeasibilityPort):
1087
+ raise TypeError("slate_feasibility must implement its port")
1088
+ require_sha256(
1089
+ self.slate_value.definition_sha256,
1090
+ "slate value definition_sha256",
1091
+ )
1092
+ require_sha256(
1093
+ self.slate_feasibility.definition_sha256,
1094
+ "slate feasibility definition_sha256",
1095
+ )
1096
+ if self.allocation_policy is not None:
1097
+ _allocation_policy_identity(self.allocation_policy)
1098
+
1099
+ async def run(
1100
+ self,
1101
+ request: ResidualPortfolioDecisionRequest,
1102
+ ) -> ResidualPortfolioEvolutionResult:
1103
+ if type(request) is not ResidualPortfolioDecisionRequest:
1104
+ raise TypeError("request must be an exact residual portfolio request")
1105
+ request.__post_init__()
1106
+ proposals = await propose_materialized_action_batches(
1107
+ experts=self.experts,
1108
+ request=request,
1109
+ )
1110
+
1111
+ actions = tuple(
1112
+ action for proposal in proposals for action in proposal.actions
1113
+ )
1114
+
1115
+ allocation_requirement = None
1116
+ if self.allocation_policy is not None:
1117
+ allocation_requirement = await self.allocation_policy.require(
1118
+ request,
1119
+ proposals,
1120
+ )
1121
+ if (
1122
+ type(allocation_requirement)
1123
+ is not MaterializedActionAllocationRequirement
1124
+ ):
1125
+ raise TypeError(
1126
+ "allocation policy returned a foreign requirement"
1127
+ )
1128
+ allocation_requirement.__post_init__()
1129
+ policy_identity = _allocation_policy_identity(
1130
+ self.allocation_policy
1131
+ )
1132
+ if (
1133
+ allocation_requirement.policy_id,
1134
+ allocation_requirement.policy_version,
1135
+ allocation_requirement.policy_definition_sha256,
1136
+ ) != policy_identity:
1137
+ raise ValueError(
1138
+ "allocation requirement differs from its policy"
1139
+ )
1140
+ if (
1141
+ allocation_requirement.residual_request_sha256
1142
+ != request.request_sha256
1143
+ ):
1144
+ raise ValueError(
1145
+ "allocation requirement targets another residual request"
1146
+ )
1147
+ proposal_sha256s = tuple(
1148
+ sorted(value.proposal_sha256 for value in proposals)
1149
+ )
1150
+ if (
1151
+ allocation_requirement.proposal_sha256s
1152
+ != proposal_sha256s
1153
+ ):
1154
+ raise ValueError(
1155
+ "allocation requirement targets another proposal universe"
1156
+ )
1157
+ action_by_sha256 = {
1158
+ value.action_sha256: value for value in actions
1159
+ }
1160
+ required_action_sha256s = (
1161
+ allocation_requirement.required_action_sha256s
1162
+ )
1163
+ if not set(required_action_sha256s).issubset(action_by_sha256):
1164
+ raise ValueError(
1165
+ "allocation policy required an action outside the "
1166
+ "sealed proposal union"
1167
+ )
1168
+ if len(required_action_sha256s) > request.evaluation_slots:
1169
+ raise ValueError(
1170
+ "allocation requirement exceeds evaluation capacity"
1171
+ )
1172
+ required_phenotypes = {
1173
+ action_by_sha256[value].phenotype_identity_sha256
1174
+ for value in required_action_sha256s
1175
+ }
1176
+ if len(required_phenotypes) != len(required_action_sha256s):
1177
+ raise ValueError(
1178
+ "allocation requirement repeats a materialized phenotype"
1179
+ )
1180
+
1181
+ broker_decision = self.broker.select(
1182
+ MaterializedActionBrokerRequest(
1183
+ actions=actions,
1184
+ evaluation_slots=request.evaluation_slots,
1185
+ slate_value=self.slate_value,
1186
+ slate_feasibility=self.slate_feasibility,
1187
+ reference_escrow_slots=request.reference_escrow_slots,
1188
+ allocation_requirement=allocation_requirement,
1189
+ )
1190
+ )
1191
+ evaluation_batches = await evaluate_materialized_action_subset(
1192
+ experts=self.experts,
1193
+ proposals=proposals,
1194
+ selected_action_sha256s=tuple(
1195
+ value.action_sha256
1196
+ for value in broker_decision.selected_actions
1197
+ ),
1198
+ )
1199
+
1200
+ return ResidualPortfolioEvolutionResult(
1201
+ request=request,
1202
+ proposals=proposals,
1203
+ broker_decision=broker_decision,
1204
+ evaluation_batches=evaluation_batches,
1205
+ slate_value_definition_sha256=self.slate_value.definition_sha256,
1206
+ slate_feasibility_definition_sha256=(
1207
+ self.slate_feasibility.definition_sha256
1208
+ ),
1209
+ )
1210
+
1211
+
1212
+ __all__ = [
1213
+ "RESIDUAL_PORTFOLIO_EVOLUTION_DEFINITION_SHA256",
1214
+ "RESIDUAL_PORTFOLIO_EVOLUTION_ID",
1215
+ "RESIDUAL_PORTFOLIO_EVOLUTION_VERSION",
1216
+ "MaterializedActionEvaluation",
1217
+ "MaterializedActionEvaluationBatch",
1218
+ "MaterializedActionAllocationPolicyPort",
1219
+ "MaterializedActionCounterfactualEvaluationPort",
1220
+ "MaterializedActionProposalBatch",
1221
+ "MaterializedActionProposalExpertPort",
1222
+ "ResidualPortfolioDecisionRequest",
1223
+ "ResidualPortfolioEvolution",
1224
+ "ResidualPortfolioEvolutionResult",
1225
+ "evaluate_materialized_action_counterfactual_subset",
1226
+ "evaluate_materialized_action_subset",
1227
+ "propose_materialized_action_batches",
1228
+ ]